@praxisui/tabs 3.0.0-beta.7 → 3.0.0-beta.8

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,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Inject, Component, inject, EventEmitter, signal, Output, Input, ChangeDetectionStrategy, ENVIRONMENT_INITIALIZER } from '@angular/core';
2
+ import { inject, Inject, Component, EventEmitter, signal, Output, Input, ChangeDetectionStrategy, ENVIRONMENT_INITIALIZER } from '@angular/core';
3
3
  import { ActivatedRoute } from '@angular/router';
4
4
  import * as i1$1 from '@angular/common';
5
5
  import { CommonModule } from '@angular/common';
@@ -10,7 +10,7 @@ import { MatIconModule } from '@angular/material/icon';
10
10
  import * as i11 from '@angular/material/tooltip';
11
11
  import { MatTooltipModule } from '@angular/material/tooltip';
12
12
  import * as i1 from '@praxisui/core';
13
- import { PraxisIconDirective, deepMerge, ASYNC_CONFIG_STORAGE, ComponentKeyService, LoggerService, EmptyStateCardComponent, DynamicWidgetLoaderDirective, ComponentMetadataRegistry } from '@praxisui/core';
13
+ import { providePraxisI18n, PraxisI18nService, PraxisIconDirective, providePraxisI18nConfig, deepMerge, ASYNC_CONFIG_STORAGE, ComponentKeyService, LoggerService, EmptyStateCardComponent, DynamicWidgetLoaderDirective, ComponentMetadataRegistry } from '@praxisui/core';
14
14
  import * as i6$1 from '@angular/material/button';
15
15
  import { MatButtonModule } from '@angular/material/button';
16
16
  import * as i3 from '@angular/forms';
@@ -31,32 +31,548 @@ import { MatSnackBar } from '@angular/material/snack-bar';
31
31
  import { take, takeUntil } from 'rxjs/operators';
32
32
  import { BaseAiAdapter, PraxisAiAssistantComponent } from '@praxisui/ai';
33
33
 
34
+ const DOCUMENT_KIND = 'praxis.tabs.editor';
35
+ const DOCUMENT_VERSION = 1;
36
+ function createTabsAuthoringDocument(source) {
37
+ return normalizeTabsAuthoringDocument({
38
+ kind: DOCUMENT_KIND,
39
+ version: DOCUMENT_VERSION,
40
+ config: source.config ?? {},
41
+ bindings: source.bindings,
42
+ });
43
+ }
44
+ function normalizeTabsAuthoringDocument(doc) {
45
+ return {
46
+ kind: DOCUMENT_KIND,
47
+ version: DOCUMENT_VERSION,
48
+ config: normalizeTabsConfig(doc?.config),
49
+ bindings: normalizeBindings(doc?.bindings),
50
+ };
51
+ }
52
+ function validateTabsAuthoringDocument(doc, _context) {
53
+ const normalized = normalizeTabsAuthoringDocument(doc);
54
+ const diagnostics = [];
55
+ validateModeAmbiguity(normalized.config, diagnostics);
56
+ validateDuplicateTabIds(normalized.config, diagnostics);
57
+ validateDuplicateLinkIds(normalized.config, diagnostics);
58
+ validateUnsupportedGuidedFields(normalized.config, diagnostics);
59
+ return diagnostics;
60
+ }
61
+ function toCanonicalTabsConfig(doc, _context) {
62
+ const normalized = normalizeTabsAuthoringDocument(doc);
63
+ return normalizeTabsConfig(stripUnsupportedAuthoringFields(normalized.config));
64
+ }
65
+ function buildTabsApplyPlan(doc, runtime, options) {
66
+ const normalized = normalizeTabsAuthoringDocument(doc);
67
+ const bindingsPatch = normalized.bindings;
68
+ const diff = deriveBindingsDiff(bindingsPatch, runtime?.currentBindings);
69
+ const diagnostics = validateTabsAuthoringDocument(normalized, runtime);
70
+ return {
71
+ canonicalConfig: toCanonicalTabsConfig(normalized, runtime),
72
+ bindingsPatch,
73
+ persistence: {
74
+ saveConfig: options?.saveConfig === true,
75
+ saveBindings: options?.saveBindings === true,
76
+ },
77
+ runtime: {
78
+ refreshSelection: true,
79
+ rebuildLazyState: true,
80
+ refreshPersistenceScope: diff.requiresPersistenceKeyRefresh,
81
+ },
82
+ diff,
83
+ diagnostics,
84
+ };
85
+ }
86
+ function serializeTabsAuthoringDocument(doc) {
87
+ return normalizeTabsAuthoringDocument(doc);
88
+ }
89
+ function normalizeTabsConfig(config) {
90
+ const next = cloneJson(config ?? {});
91
+ if (!Array.isArray(next.tabs) || next.tabs.length === 0) {
92
+ delete next.tabs;
93
+ }
94
+ if (!next.nav || typeof next.nav !== 'object') {
95
+ delete next.nav;
96
+ }
97
+ else if (!Array.isArray(next.nav.links) || next.nav.links.length === 0) {
98
+ delete next.nav.links;
99
+ }
100
+ if (next.group) {
101
+ next.group.selectedIndex = clampIndex(next.group.selectedIndex, next.tabs?.length ?? 0);
102
+ }
103
+ if (next.nav) {
104
+ next.nav.selectedIndex = clampIndex(next.nav.selectedIndex, next.nav.links?.length ?? 0);
105
+ }
106
+ if (Array.isArray(next.nav?.links)) {
107
+ next.nav.links = next.nav.links.map((link) => {
108
+ const copy = { ...link };
109
+ delete copy.active;
110
+ return copy;
111
+ });
112
+ }
113
+ return next;
114
+ }
115
+ function normalizeBindings(bindings) {
116
+ if (!bindings)
117
+ return undefined;
118
+ const next = {
119
+ tabsId: normalizeNullableString(bindings.tabsId),
120
+ componentInstanceId: normalizeNullableString(bindings.componentInstanceId),
121
+ };
122
+ if (next.tabsId === undefined && next.componentInstanceId === undefined) {
123
+ return undefined;
124
+ }
125
+ return next;
126
+ }
127
+ function deriveBindingsDiff(next, current) {
128
+ const normalizedNext = normalizeBindings(next) ?? {};
129
+ const normalizedCurrent = normalizeBindings(current) ?? {};
130
+ const tabsIdChanged = normalizedNext.tabsId !== normalizedCurrent.tabsId;
131
+ const componentInstanceIdChanged = normalizedNext.componentInstanceId !== normalizedCurrent.componentInstanceId;
132
+ return {
133
+ tabsIdChanged,
134
+ componentInstanceIdChanged,
135
+ requiresPersistenceKeyRefresh: tabsIdChanged || componentInstanceIdChanged,
136
+ };
137
+ }
138
+ function validateModeAmbiguity(config, diagnostics) {
139
+ if (config.tabs?.length && config.nav?.links?.length) {
140
+ diagnostics.push(warnDiagnostic('tabs.config.mode.ambiguous', 'Tabs authoring must resolve to a single primary mode: group or nav.', 'config'));
141
+ }
142
+ }
143
+ function validateDuplicateTabIds(config, diagnostics) {
144
+ const seen = new Set();
145
+ (config.tabs ?? []).forEach((tab, index) => {
146
+ const id = normalizeId(tab.id);
147
+ if (!id)
148
+ return;
149
+ if (seen.has(id)) {
150
+ diagnostics.push(errorDiagnostic('tabs.config.tabs.id.duplicate', 'Duplicate tab id detected.', `config.tabs[${index}].id`));
151
+ return;
152
+ }
153
+ seen.add(id);
154
+ });
155
+ }
156
+ function validateDuplicateLinkIds(config, diagnostics) {
157
+ const seen = new Set();
158
+ (config.nav?.links ?? []).forEach((link, index) => {
159
+ const id = normalizeId(link.id);
160
+ if (!id)
161
+ return;
162
+ if (seen.has(id)) {
163
+ diagnostics.push(errorDiagnostic('tabs.config.nav.links.id.duplicate', 'Duplicate nav link id detected.', `config.nav.links[${index}].id`));
164
+ return;
165
+ }
166
+ seen.add(id);
167
+ });
168
+ }
169
+ function validateUnsupportedGuidedFields(config, diagnostics) {
170
+ if (config.group?.contentTabIndex !== undefined) {
171
+ diagnostics.push(infoDiagnostic('tabs.config.group.contentTabIndex.unsupported', 'group.contentTabIndex is outside the supported authoring surface.', 'config.group.contentTabIndex'));
172
+ }
173
+ if (config.accessibility?.ariaLabels !== undefined) {
174
+ diagnostics.push(infoDiagnostic('tabs.config.accessibility.ariaLabels.unsupported', 'accessibility.ariaLabels is outside the supported authoring surface.', 'config.accessibility.ariaLabels'));
175
+ }
176
+ if (config.accessibility?.keyboardNavigation !== undefined) {
177
+ diagnostics.push(infoDiagnostic('tabs.config.accessibility.keyboardNavigation.unsupported', 'accessibility.keyboardNavigation is outside the supported authoring surface.', 'config.accessibility.keyboardNavigation'));
178
+ }
179
+ if (config.appearance?.tokens?.['inactive-ripple-color'] !== undefined) {
180
+ diagnostics.push(infoDiagnostic('tabs.config.appearance.tokens.inactiveRippleColor.unsupported', 'appearance.tokens.inactive-ripple-color is outside the supported authoring surface.', 'config.appearance.tokens.inactive-ripple-color'));
181
+ }
182
+ }
183
+ function stripUnsupportedAuthoringFields(config) {
184
+ const next = cloneJson(config);
185
+ if (next.group) {
186
+ delete next.group.contentTabIndex;
187
+ }
188
+ if (next.accessibility) {
189
+ delete next.accessibility.ariaLabels;
190
+ delete next.accessibility.keyboardNavigation;
191
+ }
192
+ if (next.appearance?.tokens) {
193
+ delete next.appearance.tokens['inactive-ripple-color'];
194
+ }
195
+ if (Array.isArray(next.nav?.links)) {
196
+ next.nav.links = next.nav.links.map((link) => {
197
+ const copy = { ...link };
198
+ delete copy.active;
199
+ return copy;
200
+ });
201
+ }
202
+ return next;
203
+ }
204
+ function clampIndex(index, size) {
205
+ if (size <= 0)
206
+ return 0;
207
+ const numeric = Number(index);
208
+ const safe = Number.isFinite(numeric) ? Math.trunc(numeric) : 0;
209
+ return Math.min(Math.max(safe, 0), size - 1);
210
+ }
211
+ function normalizeNullableString(value) {
212
+ if (value === undefined)
213
+ return undefined;
214
+ if (value === null)
215
+ return null;
216
+ if (typeof value !== 'string')
217
+ return null;
218
+ const trimmed = value.trim();
219
+ return trimmed || null;
220
+ }
221
+ function normalizeId(value) {
222
+ if (typeof value !== 'string')
223
+ return null;
224
+ const trimmed = value.trim();
225
+ return trimmed || null;
226
+ }
227
+ function cloneJson(value) {
228
+ return JSON.parse(JSON.stringify(value ?? null));
229
+ }
230
+ function errorDiagnostic(code, message, path) {
231
+ return { level: 'error', code, message, path };
232
+ }
233
+ function warnDiagnostic(code, message, path) {
234
+ return { level: 'warning', code, message, path };
235
+ }
236
+ function infoDiagnostic(code, message, path) {
237
+ return { level: 'info', code, message, path };
238
+ }
239
+
240
+ const PRAXIS_TABS_I18N_NAMESPACE = 'praxisTabs';
241
+ const PRAXIS_TABS_PT_BR = {
242
+ 'settings.title': 'Configurar abas',
243
+ 'settings.resetPreferences': 'Redefinir preferencias de abas',
244
+ 'settings.resetDone': 'Preferencias de abas redefinidas',
245
+ 'quickSetup.title': 'Criar abas rapidamente',
246
+ 'emptyState.title': 'Nenhuma aba configurada',
247
+ 'emptyState.description': 'Crie rapidamente suas abas ou abra o editor completo.',
248
+ 'emptyState.primaryAction': 'Criar abas rapidamente',
249
+ 'emptyState.secondaryAddEmpty': 'Adicionar aba vazia',
250
+ 'emptyState.secondaryOpenEditor': 'Abrir editor completo',
251
+ 'emptyState.navTitle': 'Sem conteudo neste link',
252
+ 'emptyState.navDescription': 'Adicione conteudo ou use o editor para configurar.',
253
+ 'emptyState.tabTitle': 'Sem conteudo nesta aba',
254
+ 'emptyState.tabDescription': 'Adicione conteudo ou use o editor para configurar.',
255
+ 'emptyState.openEditor': 'Abrir editor',
256
+ 'chrome.closeTab': 'Fechar aba',
257
+ 'chrome.moveTabLeft': 'Mover aba para esquerda',
258
+ 'chrome.moveTabRight': 'Mover aba para direita',
259
+ 'chrome.editTabs': 'Editar abas',
260
+ 'defaults.newTabLabel': 'New Tab',
261
+ 'defaults.newLinkLabel': 'New Link',
262
+ 'editor.tabs.behavior': 'Comportamento',
263
+ 'editor.tabs.group': 'Grupo',
264
+ 'editor.tabs.nav': 'Navegacao',
265
+ 'editor.tabs.json': 'JSON',
266
+ 'editor.tabs.style': 'Estilo',
267
+ 'editor.tabs.items': 'Abas',
268
+ 'editor.tabs.accessibility': 'Acessibilidade',
269
+ 'editor.tabs.links': 'Links',
270
+ 'editor.actions.format': 'Formatar',
271
+ 'editor.actions.reset': 'Resetar',
272
+ 'editor.actions.clearTokens': 'Limpar tokens',
273
+ 'editor.actions.addTab': 'Adicionar aba',
274
+ 'editor.actions.addLink': 'Adicionar link',
275
+ 'editor.actions.add': 'Adicionar',
276
+ 'editor.actions.removeLabel': 'Remover rotulo',
277
+ 'editor.actions.dragToReorder': 'Arrastar para reordenar',
278
+ 'editor.actions.removeComponent': 'Remover componente',
279
+ 'editor.fields.groupAlignTabs': 'Alinhamento das abas',
280
+ 'editor.fields.groupHeaderPosition': 'Posicao do header',
281
+ 'editor.fields.selectedIndex': 'Indice selecionado',
282
+ 'editor.fields.animationDuration': 'Duracao da animacao',
283
+ 'editor.fields.mode': 'Modo principal',
284
+ 'editor.fields.color': 'Cor (M2)',
285
+ 'editor.fields.backgroundColor': 'Cor de fundo (M2)',
286
+ 'editor.fields.ariaLabel': 'aria-label',
287
+ 'editor.fields.ariaLabelledby': 'aria-labelledby',
288
+ 'editor.fields.jsonConfig': 'Configuracao JSON',
289
+ 'editor.fields.themeClass': 'Classe de tema (opcional)',
290
+ 'editor.fields.density': 'Densidade',
291
+ 'editor.fields.customCss': 'CSS a ser injetado no componente',
292
+ 'editor.fields.addLabel': 'Adicionar rotulo',
293
+ 'editor.fields.inputsJson': 'Inputs (JSON)',
294
+ 'editor.fields.outputsJson': 'Outputs (JSON)',
295
+ 'editor.fields.id': 'ID',
296
+ 'editor.fields.label': 'Rotulo',
297
+ 'editor.fields.labelClass': 'Classe do rotulo',
298
+ 'editor.fields.bodyClass': 'Classe do conteudo',
299
+ 'editor.fields.resourcePath': 'Recurso (resourcePath)',
300
+ 'editor.fields.addComponent': 'Adicionar componente',
301
+ 'editor.fields.selectPlaceholder': '(selecione)',
302
+ 'editor.fields.placeholder.animationDuration': '500ms',
303
+ 'editor.fields.placeholder.themeClass': 'ex.: tabs-accented',
304
+ 'editor.fields.placeholder.resourcePath': 'ex.: usuarios',
305
+ 'editor.hints.selectedIndex': 'selectedIndex',
306
+ 'editor.hints.animationDuration': 'animationDuration',
307
+ 'editor.hints.preferTokens': 'Preferir tokens em Estilo.',
308
+ 'editor.hints.tokenValueHelp': 'Valores aceitam CSS vars ou cores hex/rgb.',
309
+ 'editor.json.valid': 'JSON valido',
310
+ 'editor.json.invalidPrefix': 'JSON invalido:',
311
+ 'editor.json.syntaxError': 'Erro de sintaxe JSON',
312
+ 'editor.sections.tokens': 'Tokens (Material 3)',
313
+ 'editor.sections.customCss': 'CSS personalizado',
314
+ 'editor.sections.scssSnippet': 'Snippet SCSS (para uso em styles.scss)',
315
+ 'editor.mode.group': 'Grupo de abas',
316
+ 'editor.mode.nav': 'Navegacao por links',
317
+ 'editor.diagnostics.title': 'Diagnosticos de authoring',
318
+ 'editor.diagnostics.error': 'Erro',
319
+ 'editor.diagnostics.warning': 'Aviso',
320
+ 'editor.diagnostics.info': 'Informacao',
321
+ 'editor.cards.tab': 'Aba',
322
+ 'editor.cards.link': 'Link',
323
+ 'editor.presets.title': 'Presets:',
324
+ 'editor.presets.primary': 'Primario',
325
+ 'editor.presets.neutral': 'Neutro',
326
+ 'editor.presets.highContrast': 'Alto contraste',
327
+ 'editor.density.default': 'Padrao',
328
+ 'editor.density.compact': 'Compacta',
329
+ 'editor.density.comfortable': 'Confortavel',
330
+ 'editor.density.spacious': 'Espacosa',
331
+ 'editor.options.none': '(nenhuma)',
332
+ 'editor.options.default': 'Padrao',
333
+ 'editor.options.start': 'Inicio',
334
+ 'editor.options.center': 'Centro',
335
+ 'editor.options.end': 'Fim',
336
+ 'editor.options.aboveDefault': 'Acima (padrao)',
337
+ 'editor.options.above': 'Acima',
338
+ 'editor.options.below': 'Abaixo',
339
+ 'editor.options.primary': 'Primary',
340
+ 'editor.options.accent': 'Accent',
341
+ 'editor.options.warn': 'Warn',
342
+ 'editor.templates.form': 'Form',
343
+ 'editor.templates.table': 'Tabela',
344
+ 'editor.templates.crud': 'CRUD',
345
+ 'editor.placeholders.tokenValue': 'var(--md-sys-color-primary) / #RRGGBB',
346
+ 'editor.placeholders.customCssExample': '.praxis-tabs-root .mdc-tab__text-label { font-weight: 600; }',
347
+ 'editor.tokens.activeIndicator': 'Indicador ativo',
348
+ 'editor.tokens.activeLabel': 'Texto ativo',
349
+ 'editor.tokens.activeHoverIndicator': 'Indicador ativo (hover)',
350
+ 'editor.tokens.activeHoverLabel': 'Texto ativo (hover)',
351
+ 'editor.tokens.activeFocusIndicator': 'Indicador ativo (focus)',
352
+ 'editor.tokens.activeFocusLabel': 'Texto ativo (focus)',
353
+ 'editor.tokens.inactiveLabel': 'Texto inativo',
354
+ 'editor.tokens.inactiveHoverLabel': 'Texto inativo (hover)',
355
+ 'editor.tokens.inactiveFocusLabel': 'Texto inativo (focus)',
356
+ 'editor.tokens.paginationIcon': 'Icones de paginacao',
357
+ 'editor.tokens.divider': 'Divisor/linha',
358
+ 'editor.tokens.headerBackground': 'Fundo do header',
359
+ 'editor.toggles.closeable': 'Fechavel',
360
+ 'editor.toggles.lazyLoad': 'Carregar sob demanda',
361
+ 'editor.toggles.reorderable': 'Reordenavel',
362
+ 'editor.toggles.dynamicHeight': 'Altura dinamica',
363
+ 'editor.toggles.fitInkBarToContent': 'Indicador ajustado ao conteudo',
364
+ 'editor.toggles.disablePagination': 'Sem paginacao',
365
+ 'editor.toggles.disableRipple': 'Sem ripple',
366
+ 'editor.toggles.preserveContent': 'Preservar conteudo',
367
+ 'editor.toggles.stretchTabs': 'Esticar abas',
368
+ 'editor.toggles.highContrast': 'Alto contraste',
369
+ 'editor.toggles.reduceMotion': 'Reduzir movimento',
370
+ 'editor.toggles.disabled': 'Desativada',
371
+ 'editor.toggles.linkDisabled': 'Desativado',
372
+ 'editor.empty.noLinks': 'Nenhum link definido.',
373
+ 'editor.empty.noTabs': 'Nenhuma aba definida.',
374
+ 'quickSetup.mode.group': 'Grupo de abas',
375
+ 'quickSetup.mode.nav': 'Navegacao por links',
376
+ 'quickSetup.fields.addLabelPlaceholder': 'Ex.: Dados Gerais',
377
+ 'quickSetup.hints.emptyLabels': 'Adicione um ou mais rotulos para criar as abas.',
378
+ };
379
+ const PRAXIS_TABS_EN_US = {
380
+ 'settings.title': 'Configure tabs',
381
+ 'settings.resetPreferences': 'Reset tab preferences',
382
+ 'settings.resetDone': 'Tab preferences reset',
383
+ 'quickSetup.title': 'Create tabs quickly',
384
+ 'emptyState.title': 'No tabs configured',
385
+ 'emptyState.description': 'Create tabs quickly or open the full editor.',
386
+ 'emptyState.primaryAction': 'Create tabs quickly',
387
+ 'emptyState.secondaryAddEmpty': 'Add empty tab',
388
+ 'emptyState.secondaryOpenEditor': 'Open full editor',
389
+ 'emptyState.navTitle': 'No content in this link',
390
+ 'emptyState.navDescription': 'Add content or use the editor to configure it.',
391
+ 'emptyState.tabTitle': 'No content in this tab',
392
+ 'emptyState.tabDescription': 'Add content or use the editor to configure it.',
393
+ 'emptyState.openEditor': 'Open editor',
394
+ 'chrome.closeTab': 'Close tab',
395
+ 'chrome.moveTabLeft': 'Move tab left',
396
+ 'chrome.moveTabRight': 'Move tab right',
397
+ 'chrome.editTabs': 'Edit tabs',
398
+ 'defaults.newTabLabel': 'New Tab',
399
+ 'defaults.newLinkLabel': 'New Link',
400
+ 'editor.tabs.behavior': 'Behavior',
401
+ 'editor.tabs.group': 'Group',
402
+ 'editor.tabs.nav': 'Navigation',
403
+ 'editor.tabs.json': 'JSON',
404
+ 'editor.tabs.style': 'Style',
405
+ 'editor.tabs.items': 'Tabs',
406
+ 'editor.tabs.accessibility': 'Accessibility',
407
+ 'editor.tabs.links': 'Links',
408
+ 'editor.actions.format': 'Format',
409
+ 'editor.actions.reset': 'Reset',
410
+ 'editor.actions.clearTokens': 'Clear tokens',
411
+ 'editor.actions.addTab': 'Add tab',
412
+ 'editor.actions.addLink': 'Add link',
413
+ 'editor.actions.add': 'Add',
414
+ 'editor.actions.removeLabel': 'Remove label',
415
+ 'editor.actions.dragToReorder': 'Drag to reorder',
416
+ 'editor.actions.removeComponent': 'Remove component',
417
+ 'editor.fields.groupAlignTabs': 'Tab alignment',
418
+ 'editor.fields.groupHeaderPosition': 'Header position',
419
+ 'editor.fields.selectedIndex': 'Selected index',
420
+ 'editor.fields.animationDuration': 'Animation duration',
421
+ 'editor.fields.mode': 'Primary mode',
422
+ 'editor.fields.color': 'Color (M2)',
423
+ 'editor.fields.backgroundColor': 'Background color (M2)',
424
+ 'editor.fields.ariaLabel': 'aria-label',
425
+ 'editor.fields.ariaLabelledby': 'aria-labelledby',
426
+ 'editor.fields.jsonConfig': 'JSON configuration',
427
+ 'editor.fields.themeClass': 'Theme class (optional)',
428
+ 'editor.fields.density': 'Density',
429
+ 'editor.fields.customCss': 'CSS injected into the component',
430
+ 'editor.fields.addLabel': 'Add label',
431
+ 'editor.fields.inputsJson': 'Inputs (JSON)',
432
+ 'editor.fields.outputsJson': 'Outputs (JSON)',
433
+ 'editor.fields.id': 'ID',
434
+ 'editor.fields.label': 'Label',
435
+ 'editor.fields.labelClass': 'Label class',
436
+ 'editor.fields.bodyClass': 'Body class',
437
+ 'editor.fields.resourcePath': 'Resource (resourcePath)',
438
+ 'editor.fields.addComponent': 'Add component',
439
+ 'editor.fields.selectPlaceholder': '(select)',
440
+ 'editor.fields.placeholder.animationDuration': '500ms',
441
+ 'editor.fields.placeholder.themeClass': 'e.g. tabs-accented',
442
+ 'editor.fields.placeholder.resourcePath': 'e.g. users',
443
+ 'editor.hints.selectedIndex': 'selectedIndex',
444
+ 'editor.hints.animationDuration': 'animationDuration',
445
+ 'editor.hints.preferTokens': 'Prefer tokens in Style.',
446
+ 'editor.hints.tokenValueHelp': 'Values accept CSS vars or hex/rgb colors.',
447
+ 'editor.json.valid': 'Valid JSON',
448
+ 'editor.json.invalidPrefix': 'Invalid JSON:',
449
+ 'editor.json.syntaxError': 'JSON syntax error',
450
+ 'editor.sections.tokens': 'Tokens (Material 3)',
451
+ 'editor.sections.customCss': 'Custom CSS',
452
+ 'editor.sections.scssSnippet': 'SCSS snippet (for styles.scss)',
453
+ 'editor.mode.group': 'Tab group',
454
+ 'editor.mode.nav': 'Link navigation',
455
+ 'editor.diagnostics.title': 'Authoring diagnostics',
456
+ 'editor.diagnostics.error': 'Error',
457
+ 'editor.diagnostics.warning': 'Warning',
458
+ 'editor.diagnostics.info': 'Info',
459
+ 'editor.cards.tab': 'Tab',
460
+ 'editor.cards.link': 'Link',
461
+ 'editor.presets.title': 'Presets:',
462
+ 'editor.presets.primary': 'Primary',
463
+ 'editor.presets.neutral': 'Neutral',
464
+ 'editor.presets.highContrast': 'High contrast',
465
+ 'editor.density.default': 'Default',
466
+ 'editor.density.compact': 'Compact',
467
+ 'editor.density.comfortable': 'Comfortable',
468
+ 'editor.density.spacious': 'Spacious',
469
+ 'editor.options.none': '(none)',
470
+ 'editor.options.default': 'Default',
471
+ 'editor.options.start': 'Start',
472
+ 'editor.options.center': 'Center',
473
+ 'editor.options.end': 'End',
474
+ 'editor.options.aboveDefault': 'Above (default)',
475
+ 'editor.options.above': 'Above',
476
+ 'editor.options.below': 'Below',
477
+ 'editor.options.primary': 'Primary',
478
+ 'editor.options.accent': 'Accent',
479
+ 'editor.options.warn': 'Warn',
480
+ 'editor.templates.form': 'Form',
481
+ 'editor.templates.table': 'Table',
482
+ 'editor.templates.crud': 'CRUD',
483
+ 'editor.placeholders.tokenValue': 'var(--md-sys-color-primary) / #RRGGBB',
484
+ 'editor.placeholders.customCssExample': '.praxis-tabs-root .mdc-tab__text-label { font-weight: 600; }',
485
+ 'editor.tokens.activeIndicator': 'Active indicator',
486
+ 'editor.tokens.activeLabel': 'Active label',
487
+ 'editor.tokens.activeHoverIndicator': 'Active indicator (hover)',
488
+ 'editor.tokens.activeHoverLabel': 'Active label (hover)',
489
+ 'editor.tokens.activeFocusIndicator': 'Active indicator (focus)',
490
+ 'editor.tokens.activeFocusLabel': 'Active label (focus)',
491
+ 'editor.tokens.inactiveLabel': 'Inactive label',
492
+ 'editor.tokens.inactiveHoverLabel': 'Inactive label (hover)',
493
+ 'editor.tokens.inactiveFocusLabel': 'Inactive label (focus)',
494
+ 'editor.tokens.paginationIcon': 'Pagination icons',
495
+ 'editor.tokens.divider': 'Divider',
496
+ 'editor.tokens.headerBackground': 'Header background',
497
+ 'editor.toggles.closeable': 'Closeable',
498
+ 'editor.toggles.lazyLoad': 'Lazy load',
499
+ 'editor.toggles.reorderable': 'Reorderable',
500
+ 'editor.toggles.dynamicHeight': 'Dynamic height',
501
+ 'editor.toggles.fitInkBarToContent': 'Fit indicator to content',
502
+ 'editor.toggles.disablePagination': 'Disable pagination',
503
+ 'editor.toggles.disableRipple': 'Disable ripple',
504
+ 'editor.toggles.preserveContent': 'Preserve content',
505
+ 'editor.toggles.stretchTabs': 'Stretch tabs',
506
+ 'editor.toggles.highContrast': 'High contrast',
507
+ 'editor.toggles.reduceMotion': 'Reduce motion',
508
+ 'editor.toggles.disabled': 'Disabled',
509
+ 'editor.toggles.linkDisabled': 'Disabled',
510
+ 'editor.empty.noLinks': 'No links defined.',
511
+ 'editor.empty.noTabs': 'No tabs defined.',
512
+ 'quickSetup.mode.group': 'Tab group',
513
+ 'quickSetup.mode.nav': 'Link navigation',
514
+ 'quickSetup.fields.addLabelPlaceholder': 'e.g. General Data',
515
+ 'quickSetup.hints.emptyLabels': 'Add one or more labels to create the tabs.',
516
+ };
517
+ function createPraxisTabsI18nConfig() {
518
+ return {
519
+ fallbackLocale: 'pt-BR',
520
+ namespaces: {
521
+ [PRAXIS_TABS_I18N_NAMESPACE]: {
522
+ 'pt-BR': PRAXIS_TABS_PT_BR,
523
+ 'en-US': PRAXIS_TABS_EN_US,
524
+ },
525
+ },
526
+ };
527
+ }
528
+ const PRAXIS_TABS_I18N_CONFIG = createPraxisTabsI18nConfig();
529
+ function providePraxisTabsI18n() {
530
+ return providePraxisI18n(PRAXIS_TABS_I18N_CONFIG);
531
+ }
532
+
34
533
  class PraxisTabsConfigEditor {
35
534
  registry;
535
+ i18n = inject(PraxisI18nService);
536
+ primaryMode = 'group';
537
+ editedDocument;
36
538
  editedConfig;
37
- initialConfig;
539
+ initialDocument;
540
+ bindings;
38
541
  jsonText = '';
39
542
  isValid = true;
40
543
  errorMsg = '';
544
+ diagnostics = [];
41
545
  componentOptions = [];
42
546
  selectedTabWidgetId = {};
43
547
  selectedLinkWidgetId = {};
44
548
  quickResourcePathTab = {};
45
549
  quickResourcePathLink = {};
550
+ t(key, fallback) {
551
+ return this.i18n.t(key, undefined, fallback, PRAXIS_TABS_I18N_NAMESPACE);
552
+ }
553
+ diagnosticLevelLabel(level) {
554
+ if (level === 'error') {
555
+ return this.t('editor.diagnostics.error', 'Erro');
556
+ }
557
+ if (level === 'warning') {
558
+ return this.t('editor.diagnostics.warning', 'Aviso');
559
+ }
560
+ return this.t('editor.diagnostics.info', 'Informacao');
561
+ }
46
562
  // Simplified token list we support at runtime
47
563
  tokenList = [
48
- { key: 'active-indicator-color', label: 'Indicador ativo' },
49
- { key: 'active-label-text-color', label: 'Texto ativo' },
50
- { key: 'active-hover-indicator-color', label: 'Indicador ativo (hover)' },
51
- { key: 'active-hover-label-text-color', label: 'Texto ativo (hover)' },
52
- { key: 'active-focus-indicator-color', label: 'Indicador ativo (focus)' },
53
- { key: 'active-focus-label-text-color', label: 'Texto ativo (focus)' },
54
- { key: 'inactive-label-text-color', label: 'Texto inativo' },
55
- { key: 'inactive-hover-label-text-color', label: 'Texto inativo (hover)' },
56
- { key: 'inactive-focus-label-text-color', label: 'Texto inativo (focus)' },
57
- { key: 'pagination-icon-color', label: 'Ícones de paginação' },
58
- { key: 'divider-color', label: 'Divisor/linha' },
59
- { key: 'background-color', label: 'Fundo do header' },
564
+ { key: 'active-indicator-color', labelKey: 'editor.tokens.activeIndicator', fallback: 'Indicador ativo' },
565
+ { key: 'active-label-text-color', labelKey: 'editor.tokens.activeLabel', fallback: 'Texto ativo' },
566
+ { key: 'active-hover-indicator-color', labelKey: 'editor.tokens.activeHoverIndicator', fallback: 'Indicador ativo (hover)' },
567
+ { key: 'active-hover-label-text-color', labelKey: 'editor.tokens.activeHoverLabel', fallback: 'Texto ativo (hover)' },
568
+ { key: 'active-focus-indicator-color', labelKey: 'editor.tokens.activeFocusIndicator', fallback: 'Indicador ativo (focus)' },
569
+ { key: 'active-focus-label-text-color', labelKey: 'editor.tokens.activeFocusLabel', fallback: 'Texto ativo (focus)' },
570
+ { key: 'inactive-label-text-color', labelKey: 'editor.tokens.inactiveLabel', fallback: 'Texto inativo' },
571
+ { key: 'inactive-hover-label-text-color', labelKey: 'editor.tokens.inactiveHoverLabel', fallback: 'Texto inativo (hover)' },
572
+ { key: 'inactive-focus-label-text-color', labelKey: 'editor.tokens.inactiveFocusLabel', fallback: 'Texto inativo (focus)' },
573
+ { key: 'pagination-icon-color', labelKey: 'editor.tokens.paginationIcon', fallback: 'Icones de paginacao' },
574
+ { key: 'divider-color', labelKey: 'editor.tokens.divider', fallback: 'Divisor/linha' },
575
+ { key: 'background-color', labelKey: 'editor.tokens.headerBackground', fallback: 'Fundo do header' },
60
576
  ];
61
577
  presets = {
62
578
  primary: {
@@ -92,50 +608,101 @@ class PraxisTabsConfigEditor {
92
608
  isBusy$ = new BehaviorSubject(false);
93
609
  constructor(data, registry) {
94
610
  this.registry = registry;
95
- const cfg = data?.config || data || {};
96
- this.initialConfig = structuredClone(cfg);
97
- this.editedConfig = structuredClone(cfg);
98
- this.isValid$.next(true);
99
- this.jsonText = this.stringify(this.editedConfig);
611
+ const incomingDocument = normalizeTabsAuthoringDocument(data?.document ?? createTabsAuthoringDocument({ config: {} }));
612
+ this.initialDocument = structuredClone(incomingDocument);
613
+ this.editedDocument = structuredClone(incomingDocument);
614
+ this.editedConfig = this.editedDocument.config;
615
+ this.bindings = this.editedDocument.bindings;
616
+ this.primaryMode = this.inferPrimaryMode(this.editedConfig);
617
+ this.jsonText = this.stringify(this.editedDocument);
100
618
  this.updateDirty();
619
+ this.refreshDiagnostics();
101
620
  this.componentOptions = this.registry.getAll().map((m) => ({ id: m.id, friendlyName: m.friendlyName }));
102
621
  }
622
+ inferPrimaryMode(config) {
623
+ return config?.nav?.links?.length ? 'nav' : 'group';
624
+ }
625
+ setPrimaryMode(mode) {
626
+ if (this.primaryMode === mode)
627
+ return;
628
+ this.primaryMode = mode;
629
+ if (mode === 'group') {
630
+ delete this.editedConfig.nav;
631
+ if (!this.editedConfig.group) {
632
+ this.editedConfig.group = { selectedIndex: 0 };
633
+ }
634
+ if (!Array.isArray(this.editedConfig.tabs)) {
635
+ this.editedConfig.tabs = [];
636
+ }
637
+ }
638
+ else {
639
+ delete this.editedConfig.tabs;
640
+ if (!this.editedConfig.nav) {
641
+ this.editedConfig.nav = { links: [], selectedIndex: 0 };
642
+ }
643
+ if (!Array.isArray(this.editedConfig.nav.links)) {
644
+ this.editedConfig.nav.links = [];
645
+ }
646
+ }
647
+ this.onAppearanceChange();
648
+ }
103
649
  updateDirty() {
104
- this.isDirty$.next(JSON.stringify(this.initialConfig) !== JSON.stringify(this.editedConfig));
650
+ this.isDirty$.next(JSON.stringify(this.initialDocument) !== JSON.stringify(this.editedDocument));
651
+ }
652
+ syncEditedDocumentFromConfig() {
653
+ this.editedDocument = createTabsAuthoringDocument({
654
+ config: this.editedConfig,
655
+ bindings: this.bindings,
656
+ });
657
+ }
658
+ syncEditorStateFromDocument(document) {
659
+ this.editedDocument = structuredClone(document);
660
+ this.editedConfig = this.editedDocument.config;
661
+ this.bindings = this.editedDocument.bindings;
662
+ this.primaryMode = this.inferPrimaryMode(this.editedConfig);
663
+ }
664
+ refreshDiagnostics() {
665
+ this.syncEditedDocumentFromConfig();
666
+ this.diagnostics = validateTabsAuthoringDocument(this.editedDocument);
667
+ this.isValid$.next(this.isValid && !this.diagnostics.some((item) => item.level === 'error'));
105
668
  }
106
669
  onJsonTextChange(text) {
107
670
  try {
108
- const parsed = JSON.parse(text);
671
+ const parsed = normalizeTabsAuthoringDocument(JSON.parse(text));
109
672
  this.errorMsg = '';
110
673
  this.isValid = true;
111
- this.isValid$.next(true);
112
- this.editedConfig = parsed;
674
+ this.syncEditorStateFromDocument(parsed);
113
675
  this.updateDirty();
676
+ this.refreshDiagnostics();
114
677
  }
115
678
  catch (e) {
116
679
  this.isValid = false;
117
680
  this.isValid$.next(false);
118
- this.errorMsg = e?.message || 'Erro de sintaxe JSON';
681
+ this.errorMsg = e?.message || this.t('editor.json.syntaxError', 'Erro de sintaxe JSON');
682
+ this.diagnostics = [];
119
683
  }
120
684
  }
121
685
  // stringify helper is public to be used in the template
122
686
  formatJson() {
123
687
  if (!this.isValid)
124
688
  return;
125
- this.jsonText = this.stringify(this.editedConfig);
689
+ this.syncEditedDocumentFromConfig();
690
+ this.jsonText = this.stringify(this.editedDocument);
126
691
  }
127
692
  getSettingsValue() {
128
- return { config: this.editedConfig };
693
+ this.syncEditedDocumentFromConfig();
694
+ return this.editedDocument;
129
695
  }
130
696
  onSave() {
131
- return { config: this.editedConfig };
697
+ this.syncEditedDocumentFromConfig();
698
+ return this.editedDocument;
132
699
  }
133
700
  reset() {
134
- this.editedConfig = structuredClone(this.initialConfig);
135
- this.jsonText = this.stringify(this.editedConfig);
701
+ this.syncEditorStateFromDocument(this.initialDocument);
702
+ this.jsonText = this.stringify(this.editedDocument);
136
703
  this.isValid = true;
137
- this.isValid$.next(true);
138
704
  this.updateDirty();
705
+ this.refreshDiagnostics();
139
706
  }
140
707
  // Appearance helpers
141
708
  get appearance() {
@@ -144,8 +711,10 @@ class PraxisTabsConfigEditor {
144
711
  return ap;
145
712
  }
146
713
  onAppearanceChange() {
714
+ this.syncEditedDocumentFromConfig();
147
715
  this.updateDirty();
148
- this.jsonText = this.stringify(this.editedConfig);
716
+ this.jsonText = this.stringify(this.editedDocument);
717
+ this.refreshDiagnostics();
149
718
  }
150
719
  onTokenChange(key, value) {
151
720
  const ap = this.appearance;
@@ -212,7 +781,10 @@ class PraxisTabsConfigEditor {
212
781
  addTab() {
213
782
  if (!this.editedConfig.tabs)
214
783
  this.editedConfig.tabs = [];
215
- this.editedConfig.tabs.push({ id: `tab${(this.editedConfig.tabs.length + 1)}`, textLabel: 'Nova aba' });
784
+ this.editedConfig.tabs.push({
785
+ id: `tab${(this.editedConfig.tabs.length + 1)}`,
786
+ textLabel: this.t('defaults.newTabLabel', 'New Tab'),
787
+ });
216
788
  this.onAppearanceChange();
217
789
  }
218
790
  removeTab(index) {
@@ -284,7 +856,10 @@ class PraxisTabsConfigEditor {
284
856
  addLink() {
285
857
  if (!this.nav.links)
286
858
  this.nav.links = [];
287
- this.nav.links.push({ id: `link${this.nav.links.length + 1}`, label: 'Novo link' });
859
+ this.nav.links.push({
860
+ id: `link${this.nav.links.length + 1}`,
861
+ label: this.t('defaults.newLinkLabel', 'New Link'),
862
+ });
288
863
  this.onAppearanceChange();
289
864
  }
290
865
  removeLink(index) {
@@ -378,181 +953,207 @@ class PraxisTabsConfigEditor {
378
953
  this.onAppearanceChange();
379
954
  }
380
955
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisTabsConfigEditor, deps: [{ token: SETTINGS_PANEL_DATA }, { token: i1.ComponentMetadataRegistry }], target: i0.ɵɵFactoryTarget.Component });
381
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisTabsConfigEditor, isStandalone: true, selector: "praxis-tabs-config-editor", ngImport: i0, template: `
956
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisTabsConfigEditor, isStandalone: true, selector: "praxis-tabs-config-editor", providers: [providePraxisI18nConfig(PRAXIS_TABS_I18N_CONFIG)], ngImport: i0, template: `
957
+ <div class="editor-shell">
958
+ <div class="editor-topbar">
959
+ <mat-form-field appearance="outline" class="editor-mode-field">
960
+ <mat-label>{{ t('editor.fields.mode', 'Modo principal') }}</mat-label>
961
+ <select matNativeControl [ngModel]="primaryMode" (ngModelChange)="setPrimaryMode($event)">
962
+ <option value="group">{{ t('editor.mode.group', 'Grupo de abas') }}</option>
963
+ <option value="nav">{{ t('editor.mode.nav', 'Navegacao por links') }}</option>
964
+ </select>
965
+ </mat-form-field>
966
+ </div>
967
+
968
+ <div *ngIf="diagnostics.length" class="editor-diagnostics">
969
+ <div class="editor-diagnostics-title">{{ t('editor.diagnostics.title', 'Diagnosticos de authoring') }}</div>
970
+ <div
971
+ *ngFor="let diagnostic of diagnostics"
972
+ class="editor-diagnostic"
973
+ [class.level-error]="diagnostic.level === 'error'"
974
+ [class.level-warning]="diagnostic.level === 'warning'"
975
+ [class.level-info]="diagnostic.level === 'info'"
976
+ >
977
+ <strong>{{ diagnosticLevelLabel(diagnostic.level) }}</strong>
978
+ <span>{{ diagnostic.message }}</span>
979
+ <code *ngIf="diagnostic.path">{{ diagnostic.path }}</code>
980
+ </div>
981
+ </div>
982
+
382
983
  <mat-tab-group class="editor-tabs">
383
- <mat-tab label="Comportamento">
984
+ <mat-tab [label]="t('editor.tabs.behavior', 'Comportamento')">
384
985
  <div class="editor-section">
385
986
  <div class="editor-row">
386
- <mat-slide-toggle [(ngModel)]="behavior.closeable" (ngModelChange)="onAppearanceChange()">Fechavel</mat-slide-toggle>
387
- <mat-slide-toggle [(ngModel)]="behavior.lazyLoad" (ngModelChange)="onAppearanceChange()">Carregar sob demanda</mat-slide-toggle>
388
- <mat-slide-toggle [(ngModel)]="behavior.reorderable" (ngModelChange)="onAppearanceChange()">Reordenavel</mat-slide-toggle>
987
+ <mat-slide-toggle [(ngModel)]="behavior.closeable" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.closeable', 'Fechavel') }}</mat-slide-toggle>
988
+ <mat-slide-toggle [(ngModel)]="behavior.lazyLoad" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.lazyLoad', 'Carregar sob demanda') }}</mat-slide-toggle>
989
+ <mat-slide-toggle [(ngModel)]="behavior.reorderable" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.reorderable', 'Reordenavel') }}</mat-slide-toggle>
389
990
  </div>
390
991
  </div>
391
992
  </mat-tab>
392
- <mat-tab label="Grupo">
993
+ <mat-tab [label]="t('editor.tabs.group', 'Grupo')" [disabled]="primaryMode !== 'group'">
393
994
  <div class="editor-section">
394
995
  <div class="editor-grid two">
395
- <mat-form-field appearance="outline"><mat-label>Alinhamento das abas</mat-label>
996
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.groupAlignTabs', 'Alinhamento das abas') }}</mat-label>
396
997
  <select matNativeControl [(ngModel)]="group.alignTabs" (ngModelChange)="onAppearanceChange()">
397
- <option [ngValue]="undefined">Padrao</option>
398
- <option value="start">Inicio</option>
399
- <option value="center">Centro</option>
400
- <option value="end">Fim</option>
998
+ <option [ngValue]="undefined">{{ t('editor.options.default', 'Padrao') }}</option>
999
+ <option value="start">{{ t('editor.options.start', 'Inicio') }}</option>
1000
+ <option value="center">{{ t('editor.options.center', 'Centro') }}</option>
1001
+ <option value="end">{{ t('editor.options.end', 'Fim') }}</option>
401
1002
  </select>
402
1003
  </mat-form-field>
403
- <mat-form-field appearance="outline"><mat-label>Posicao do header</mat-label>
1004
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.groupHeaderPosition', 'Posicao do header') }}</mat-label>
404
1005
  <select matNativeControl [(ngModel)]="group.headerPosition" (ngModelChange)="onAppearanceChange()">
405
- <option [ngValue]="undefined">Acima (padrao)</option>
406
- <option value="above">Acima</option>
407
- <option value="below">Abaixo</option>
1006
+ <option [ngValue]="undefined">{{ t('editor.options.aboveDefault', 'Acima (padrao)') }}</option>
1007
+ <option value="above">{{ t('editor.options.above', 'Acima') }}</option>
1008
+ <option value="below">{{ t('editor.options.below', 'Abaixo') }}</option>
408
1009
  </select>
409
1010
  </mat-form-field>
410
- <mat-form-field appearance="outline"><mat-label>Indice selecionado</mat-label>
1011
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.selectedIndex', 'Indice selecionado') }}</mat-label>
411
1012
  <input matInput type="number" [(ngModel)]="group.selectedIndex" (ngModelChange)="onAppearanceChange()" />
412
1013
  <button
413
1014
  mat-icon-button
414
1015
  matSuffix
415
1016
  class="help-icon-button"
416
1017
  type="button"
417
- [matTooltip]="'selectedIndex'"
1018
+ [matTooltip]="t('editor.hints.selectedIndex', 'selectedIndex')"
418
1019
  matTooltipPosition="above"
419
1020
  >
420
1021
  <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
421
1022
  </button>
422
1023
  </mat-form-field>
423
- <mat-form-field appearance="outline"><mat-label>Duracao da animacao</mat-label>
424
- <input matInput [(ngModel)]="group.animationDuration" (ngModelChange)="onAppearanceChange()" placeholder="500ms" />
1024
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.animationDuration', 'Duracao da animacao') }}</mat-label>
1025
+ <input matInput [(ngModel)]="group.animationDuration" (ngModelChange)="onAppearanceChange()" [placeholder]="t('editor.fields.placeholder.animationDuration', '500ms')" />
425
1026
  <button
426
1027
  mat-icon-button
427
1028
  matSuffix
428
1029
  class="help-icon-button"
429
1030
  type="button"
430
- [matTooltip]="'animationDuration'"
1031
+ [matTooltip]="t('editor.hints.animationDuration', 'animationDuration')"
431
1032
  matTooltipPosition="above"
432
1033
  >
433
1034
  <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
434
1035
  </button>
435
1036
  </mat-form-field>
436
- <mat-form-field appearance="outline"><mat-label>Cor (M2)</mat-label>
1037
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.color', 'Cor (M2)') }}</mat-label>
437
1038
  <select matNativeControl [(ngModel)]="group.color" (ngModelChange)="onAppearanceChange()">
438
- <option [ngValue]="undefined">(nenhuma)</option>
439
- <option value="primary">Primary</option>
440
- <option value="accent">Accent</option>
441
- <option value="warn">Warn</option>
1039
+ <option [ngValue]="undefined">{{ t('editor.options.none', '(nenhuma)') }}</option>
1040
+ <option value="primary">{{ t('editor.options.primary', 'Primary') }}</option>
1041
+ <option value="accent">{{ t('editor.options.accent', 'Accent') }}</option>
1042
+ <option value="warn">{{ t('editor.options.warn', 'Warn') }}</option>
442
1043
  </select>
443
1044
  <button
444
1045
  mat-icon-button
445
1046
  matSuffix
446
1047
  class="help-icon-button"
447
1048
  type="button"
448
- [matTooltip]="'Preferir tokens em Estilo.'"
1049
+ [matTooltip]="t('editor.hints.preferTokens', 'Preferir tokens em Estilo.')"
449
1050
  matTooltipPosition="above"
450
1051
  >
451
1052
  <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
452
1053
  </button>
453
1054
  </mat-form-field>
454
- <mat-form-field appearance="outline"><mat-label>Cor de fundo (M2)</mat-label>
1055
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.backgroundColor', 'Cor de fundo (M2)') }}</mat-label>
455
1056
  <select matNativeControl [(ngModel)]="group.backgroundColor" (ngModelChange)="onAppearanceChange()">
456
- <option [ngValue]="undefined">(nenhuma)</option>
457
- <option value="primary">Primary</option>
458
- <option value="accent">Accent</option>
459
- <option value="warn">Warn</option>
1057
+ <option [ngValue]="undefined">{{ t('editor.options.none', '(nenhuma)') }}</option>
1058
+ <option value="primary">{{ t('editor.options.primary', 'Primary') }}</option>
1059
+ <option value="accent">{{ t('editor.options.accent', 'Accent') }}</option>
1060
+ <option value="warn">{{ t('editor.options.warn', 'Warn') }}</option>
460
1061
  </select>
461
1062
  <button
462
1063
  mat-icon-button
463
1064
  matSuffix
464
1065
  class="help-icon-button"
465
1066
  type="button"
466
- [matTooltip]="'Preferir tokens em Estilo.'"
1067
+ [matTooltip]="t('editor.hints.preferTokens', 'Preferir tokens em Estilo.')"
467
1068
  matTooltipPosition="above"
468
1069
  >
469
1070
  <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
470
1071
  </button>
471
1072
  </mat-form-field>
472
- <mat-form-field appearance="outline"><mat-label>aria-label</mat-label>
1073
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.ariaLabel', 'aria-label') }}</mat-label>
473
1074
  <input matInput [(ngModel)]="group.ariaLabel" (ngModelChange)="onAppearanceChange()" />
474
1075
  </mat-form-field>
475
- <mat-form-field appearance="outline"><mat-label>aria-labelledby</mat-label>
1076
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.ariaLabelledby', 'aria-labelledby') }}</mat-label>
476
1077
  <input matInput [(ngModel)]="group.ariaLabelledby" (ngModelChange)="onAppearanceChange()" />
477
1078
  </mat-form-field>
478
1079
  </div>
479
1080
  <div class="editor-row">
480
- <mat-slide-toggle [(ngModel)]="group.dynamicHeight" (ngModelChange)="onAppearanceChange()">Altura dinamica</mat-slide-toggle>
481
- <mat-slide-toggle [(ngModel)]="group.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">Indicador ajustado ao conteudo</mat-slide-toggle>
482
- <mat-slide-toggle [(ngModel)]="group.disablePagination" (ngModelChange)="onAppearanceChange()">Sem paginacao</mat-slide-toggle>
483
- <mat-slide-toggle [(ngModel)]="group.disableRipple" (ngModelChange)="onAppearanceChange()">Sem ripple</mat-slide-toggle>
484
- <mat-slide-toggle [(ngModel)]="group.preserveContent" (ngModelChange)="onAppearanceChange()">Preservar conteudo</mat-slide-toggle>
485
- <mat-slide-toggle [(ngModel)]="group.stretchTabs" (ngModelChange)="onAppearanceChange()">Esticar abas</mat-slide-toggle>
1081
+ <mat-slide-toggle [(ngModel)]="group.dynamicHeight" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.dynamicHeight', 'Altura dinamica') }}</mat-slide-toggle>
1082
+ <mat-slide-toggle [(ngModel)]="group.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.fitInkBarToContent', 'Indicador ajustado ao conteudo') }}</mat-slide-toggle>
1083
+ <mat-slide-toggle [(ngModel)]="group.disablePagination" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.disablePagination', 'Sem paginacao') }}</mat-slide-toggle>
1084
+ <mat-slide-toggle [(ngModel)]="group.disableRipple" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.disableRipple', 'Sem ripple') }}</mat-slide-toggle>
1085
+ <mat-slide-toggle [(ngModel)]="group.preserveContent" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.preserveContent', 'Preservar conteudo') }}</mat-slide-toggle>
1086
+ <mat-slide-toggle [(ngModel)]="group.stretchTabs" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.stretchTabs', 'Esticar abas') }}</mat-slide-toggle>
486
1087
  </div>
487
1088
  </div>
488
1089
  </mat-tab>
489
1090
 
490
- <mat-tab label="Navegação">
1091
+ <mat-tab [label]="t('editor.tabs.nav', 'Navegacao')" [disabled]="primaryMode !== 'nav'">
491
1092
  <div class="editor-section">
492
1093
  <div class="editor-grid two">
493
- <mat-form-field appearance="outline"><mat-label>Indice selecionado</mat-label>
1094
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.selectedIndex', 'Indice selecionado') }}</mat-label>
494
1095
  <input matInput type="number" [(ngModel)]="nav.selectedIndex" (ngModelChange)="onAppearanceChange()" />
495
1096
  <button
496
1097
  mat-icon-button
497
1098
  matSuffix
498
1099
  class="help-icon-button"
499
1100
  type="button"
500
- [matTooltip]="'selectedIndex'"
1101
+ [matTooltip]="t('editor.hints.selectedIndex', 'selectedIndex')"
501
1102
  matTooltipPosition="above"
502
1103
  >
503
1104
  <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
504
1105
  </button>
505
1106
  </mat-form-field>
506
- <mat-form-field appearance="outline"><mat-label>Duracao da animacao</mat-label>
507
- <input matInput [(ngModel)]="nav.animationDuration" (ngModelChange)="onAppearanceChange()" placeholder="500ms" />
1107
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.animationDuration', 'Duracao da animacao') }}</mat-label>
1108
+ <input matInput [(ngModel)]="nav.animationDuration" (ngModelChange)="onAppearanceChange()" [placeholder]="t('editor.fields.placeholder.animationDuration', '500ms')" />
508
1109
  <button
509
1110
  mat-icon-button
510
1111
  matSuffix
511
1112
  class="help-icon-button"
512
1113
  type="button"
513
- [matTooltip]="'animationDuration'"
1114
+ [matTooltip]="t('editor.hints.animationDuration', 'animationDuration')"
514
1115
  matTooltipPosition="above"
515
1116
  >
516
1117
  <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
517
1118
  </button>
518
1119
  </mat-form-field>
519
- <mat-form-field appearance="outline"><mat-label>aria-label</mat-label>
1120
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.ariaLabel', 'aria-label') }}</mat-label>
520
1121
  <input matInput [(ngModel)]="nav.ariaLabel" (ngModelChange)="onAppearanceChange()" />
521
1122
  </mat-form-field>
522
- <mat-form-field appearance="outline"><mat-label>aria-labelledby</mat-label>
1123
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.ariaLabelledby', 'aria-labelledby') }}</mat-label>
523
1124
  <input matInput [(ngModel)]="nav.ariaLabelledby" (ngModelChange)="onAppearanceChange()" />
524
1125
  </mat-form-field>
525
- <mat-form-field appearance="outline"><mat-label>Cor (M2)</mat-label>
1126
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.color', 'Cor (M2)') }}</mat-label>
526
1127
  <select matNativeControl [(ngModel)]="nav.color" (ngModelChange)="onAppearanceChange()">
527
- <option [ngValue]="undefined">(nenhuma)</option>
528
- <option value="primary">Primary</option>
529
- <option value="accent">Accent</option>
530
- <option value="warn">Warn</option>
1128
+ <option [ngValue]="undefined">{{ t('editor.options.none', '(nenhuma)') }}</option>
1129
+ <option value="primary">{{ t('editor.options.primary', 'Primary') }}</option>
1130
+ <option value="accent">{{ t('editor.options.accent', 'Accent') }}</option>
1131
+ <option value="warn">{{ t('editor.options.warn', 'Warn') }}</option>
531
1132
  </select>
532
1133
  <button
533
1134
  mat-icon-button
534
1135
  matSuffix
535
1136
  class="help-icon-button"
536
1137
  type="button"
537
- [matTooltip]="'Preferir tokens em Estilo.'"
1138
+ [matTooltip]="t('editor.hints.preferTokens', 'Preferir tokens em Estilo.')"
538
1139
  matTooltipPosition="above"
539
1140
  >
540
1141
  <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
541
1142
  </button>
542
1143
  </mat-form-field>
543
- <mat-form-field appearance="outline"><mat-label>Cor de fundo (M2)</mat-label>
1144
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.backgroundColor', 'Cor de fundo (M2)') }}</mat-label>
544
1145
  <select matNativeControl [(ngModel)]="nav.backgroundColor" (ngModelChange)="onAppearanceChange()">
545
- <option [ngValue]="undefined">(nenhuma)</option>
546
- <option value="primary">Primary</option>
547
- <option value="accent">Accent</option>
548
- <option value="warn">Warn</option>
1146
+ <option [ngValue]="undefined">{{ t('editor.options.none', '(nenhuma)') }}</option>
1147
+ <option value="primary">{{ t('editor.options.primary', 'Primary') }}</option>
1148
+ <option value="accent">{{ t('editor.options.accent', 'Accent') }}</option>
1149
+ <option value="warn">{{ t('editor.options.warn', 'Warn') }}</option>
549
1150
  </select>
550
1151
  <button
551
1152
  mat-icon-button
552
1153
  matSuffix
553
1154
  class="help-icon-button"
554
1155
  type="button"
555
- [matTooltip]="'Preferir tokens em Estilo.'"
1156
+ [matTooltip]="t('editor.hints.preferTokens', 'Preferir tokens em Estilo.')"
556
1157
  matTooltipPosition="above"
557
1158
  >
558
1159
  <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
@@ -560,26 +1161,26 @@ class PraxisTabsConfigEditor {
560
1161
  </mat-form-field>
561
1162
  </div>
562
1163
  <div class="editor-row">
563
- <mat-slide-toggle [(ngModel)]="nav.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">Indicador ajustado ao conteudo</mat-slide-toggle>
564
- <mat-slide-toggle [(ngModel)]="nav.disablePagination" (ngModelChange)="onAppearanceChange()">Sem paginacao</mat-slide-toggle>
565
- <mat-slide-toggle [(ngModel)]="nav.disableRipple" (ngModelChange)="onAppearanceChange()">Sem ripple</mat-slide-toggle>
566
- <mat-slide-toggle [(ngModel)]="nav.stretchTabs" (ngModelChange)="onAppearanceChange()">Esticar abas</mat-slide-toggle>
1164
+ <mat-slide-toggle [(ngModel)]="nav.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.fitInkBarToContent', 'Indicador ajustado ao conteudo') }}</mat-slide-toggle>
1165
+ <mat-slide-toggle [(ngModel)]="nav.disablePagination" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.disablePagination', 'Sem paginacao') }}</mat-slide-toggle>
1166
+ <mat-slide-toggle [(ngModel)]="nav.disableRipple" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.disableRipple', 'Sem ripple') }}</mat-slide-toggle>
1167
+ <mat-slide-toggle [(ngModel)]="nav.stretchTabs" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.stretchTabs', 'Esticar abas') }}</mat-slide-toggle>
567
1168
  </div>
568
1169
  </div>
569
1170
  </mat-tab>
570
- <mat-tab label="JSON">
1171
+ <mat-tab [label]="t('editor.tabs.json', 'JSON')">
571
1172
  <div class="editor-section">
572
1173
  <div class="editor-toolbar">
573
- <button mat-button (click)="formatJson()" [disabled]="!isValid">
574
- <mat-icon [praxisIcon]="'format_align_left'"></mat-icon>Formatar
1174
+ <button mat-button (click)="formatJson()" [disabled]="!isValid">
1175
+ <mat-icon [praxisIcon]="'format_align_left'"></mat-icon>{{ t('editor.actions.format', 'Formatar') }}
575
1176
  </button>
576
- <button mat-button (click)="reset()">
577
- <mat-icon [praxisIcon]="'restart_alt'"></mat-icon>Resetar
1177
+ <button mat-button (click)="reset()">
1178
+ <mat-icon [praxisIcon]="'restart_alt'"></mat-icon>{{ t('editor.actions.reset', 'Resetar') }}
578
1179
  </button>
579
1180
  </div>
580
1181
 
581
1182
  <mat-form-field appearance="outline" class="json-textarea-field full">
582
- <mat-label>Configuracao JSON</mat-label>
1183
+ <mat-label>{{ t('editor.fields.jsonConfig', 'Configuracao JSON') }}</mat-label>
583
1184
  <textarea
584
1185
  matInput
585
1186
  [(ngModel)]="jsonText"
@@ -588,84 +1189,84 @@ class PraxisTabsConfigEditor {
588
1189
  spellcheck="false"
589
1190
  class="editor-json"
590
1191
  ></textarea>
591
- <mat-hint *ngIf="isValid">JSON valido</mat-hint>
592
- <mat-error *ngIf="!isValid && jsonText">JSON invalido: {{ errorMsg }}</mat-error>
1192
+ <mat-hint *ngIf="isValid">{{ t('editor.json.valid', 'JSON valido') }}</mat-hint>
1193
+ <mat-error *ngIf="!isValid && jsonText">{{ t('editor.json.invalidPrefix', 'JSON invalido:') }} {{ errorMsg }}</mat-error>
593
1194
  </mat-form-field>
594
1195
  </div>
595
1196
  </mat-tab>
596
1197
 
597
- <mat-tab label="Estilo">
1198
+ <mat-tab [label]="t('editor.tabs.style', 'Estilo')">
598
1199
  <div class="editor-section editor-section-lg">
599
1200
  <div class="editor-row">
600
- <span class="editor-muted">Presets:</span>
1201
+ <span class="editor-muted">{{ t('editor.presets.title', 'Presets:') }}</span>
601
1202
  <button mat-button color="primary" (click)="applyPreset('primary')">
602
1203
  <mat-icon [praxisIcon]="'palette'"></mat-icon>
603
- Primário
1204
+ {{ t('editor.presets.primary', 'Primario') }}
604
1205
  </button>
605
1206
  <button mat-button (click)="applyPreset('neutral')">
606
1207
  <mat-icon [praxisIcon]="'contrast'"></mat-icon>
607
- Neutro
1208
+ {{ t('editor.presets.neutral', 'Neutro') }}
608
1209
  </button>
609
1210
  <button mat-button (click)="applyPreset('high-contrast')">
610
1211
  <mat-icon [praxisIcon]="'visibility'"></mat-icon>
611
- Alto contraste
1212
+ {{ t('editor.presets.highContrast', 'Alto contraste') }}
612
1213
  </button>
613
1214
  <button mat-button (click)="clearTokens()">
614
1215
  <mat-icon [praxisIcon]="'backspace'"></mat-icon>
615
- Limpar tokens
1216
+ {{ t('editor.actions.clearTokens', 'Limpar tokens') }}
616
1217
  </button>
617
1218
  </div>
618
1219
 
619
1220
  <div class="editor-grid two tight">
620
1221
  <mat-form-field appearance="outline">
621
- <mat-label>Classe de tema (opcional)</mat-label>
622
- <input matInput [(ngModel)]="appearance.themeClass" (ngModelChange)="onAppearanceChange()" placeholder="ex.: tabs-accented" />
1222
+ <mat-label>{{ t('editor.fields.themeClass', 'Classe de tema (opcional)') }}</mat-label>
1223
+ <input matInput [(ngModel)]="appearance.themeClass" (ngModelChange)="onAppearanceChange()" [placeholder]="t('editor.fields.placeholder.themeClass', 'ex.: tabs-accented')" />
623
1224
  </mat-form-field>
624
1225
 
625
1226
  <mat-form-field appearance="outline">
626
- <mat-label>Densidade</mat-label>
1227
+ <mat-label>{{ t('editor.fields.density', 'Densidade') }}</mat-label>
627
1228
  <select matNativeControl [(ngModel)]="appearance.density" (ngModelChange)="onAppearanceChange()">
628
- <option [ngValue]="undefined">Padrao</option>
629
- <option value="compact">Compacta</option>
630
- <option value="comfortable">Confortavel</option>
631
- <option value="spacious">Espacosa</option>
1229
+ <option [ngValue]="undefined">{{ t('editor.density.default', 'Padrao') }}</option>
1230
+ <option value="compact">{{ t('editor.density.compact', 'Compacta') }}</option>
1231
+ <option value="comfortable">{{ t('editor.density.comfortable', 'Confortavel') }}</option>
1232
+ <option value="spacious">{{ t('editor.density.spacious', 'Espacosa') }}</option>
632
1233
  </select>
633
1234
  </mat-form-field>
634
1235
  </div>
635
1236
 
636
1237
  <div>
637
1238
  <div class="editor-title-row">
638
- <h3 class="editor-title">Tokens (Material 3)</h3>
1239
+ <h3 class="editor-title">{{ t('editor.sections.tokens', 'Tokens (Material 3)') }}</h3>
639
1240
  <button
640
1241
  mat-icon-button
641
1242
  class="help-icon-button"
642
1243
  type="button"
643
- [matTooltip]="'Valores aceitam CSS vars (ex.: var(--md-sys-color-primary)) ou cores hex/rgb.'"
1244
+ [matTooltip]="t('editor.hints.tokenValueHelp', 'Valores aceitam CSS vars ou cores hex/rgb.')"
644
1245
  matTooltipPosition="above"
645
1246
  >
646
1247
  <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
647
1248
  </button>
648
1249
  </div>
649
1250
  <div class="editor-grid two">
650
- <ng-container *ngFor="let t of tokenList">
1251
+ <ng-container *ngFor="let token of tokenList">
651
1252
  <mat-form-field appearance="outline">
652
- <mat-label>{{ t.label }}</mat-label>
653
- <input matInput placeholder="var(--md-sys-color-primary) / #RRGGBB" [ngModel]="appearance.tokens?.[t.key]" (ngModelChange)="onTokenChange(t.key, $event)" />
1253
+ <mat-label>{{ t(token.labelKey, token.fallback) }}</mat-label>
1254
+ <input matInput [placeholder]="t('editor.placeholders.tokenValue', 'var(--md-sys-color-primary) / #RRGGBB')" [ngModel]="appearance.tokens?.[token.key]" (ngModelChange)="onTokenChange(token.key, $event)" />
654
1255
  </mat-form-field>
655
1256
  </ng-container>
656
1257
  </div>
657
1258
  </div>
658
1259
 
659
1260
  <div>
660
- <h3 class="editor-title">CSS personalizado</h3>
1261
+ <h3 class="editor-title">{{ t('editor.sections.customCss', 'CSS personalizado') }}</h3>
661
1262
  <mat-form-field appearance="outline" class="json-textarea-field full">
662
- <mat-label>CSS a ser injetado no componente</mat-label>
663
- <textarea matInput rows="10" [(ngModel)]="appearance.customCss" (ngModelChange)="onAppearanceChange()" placeholder=".praxis-tabs-root .mdc-tab__text-label { font-weight: 600; }"></textarea>
1263
+ <mat-label>{{ t('editor.fields.customCss', 'CSS a ser injetado no componente') }}</mat-label>
1264
+ <textarea matInput rows="10" [(ngModel)]="appearance.customCss" (ngModelChange)="onAppearanceChange()" [placeholder]="t('editor.placeholders.customCssExample', '.praxis-tabs-root .mdc-tab__text-label { font-weight: 600; }')"></textarea>
664
1265
  </mat-form-field>
665
1266
  </div>
666
1267
 
667
1268
  <div>
668
- <h3 class="editor-title">Snippet SCSS (para uso em styles.scss)</h3>
1269
+ <h3 class="editor-title">{{ t('editor.sections.scssSnippet', 'Snippet SCSS (para uso em styles.scss)') }}</h3>
669
1270
  <pre class="editor-code">
670
1271
  @use '@angular/material' as mat;
671
1272
  {{ scssSnippet() }}
@@ -674,76 +1275,76 @@ class PraxisTabsConfigEditor {
674
1275
  </div>
675
1276
  </mat-tab>
676
1277
 
677
- <mat-tab label="Abas">
1278
+ <mat-tab [label]="t('editor.tabs.items', 'Abas')" [disabled]="primaryMode !== 'group'">
678
1279
  <div class="editor-section">
679
- <button mat-stroked-button color="primary" (click)="addTab()"><mat-icon [praxisIcon]="'add'"></mat-icon>Adicionar aba</button>
1280
+ <button mat-stroked-button color="primary" (click)="addTab()"><mat-icon [praxisIcon]="'add'"></mat-icon>{{ t('editor.actions.addTab', 'Adicionar aba') }}</button>
680
1281
  <div *ngIf="editedConfig.tabs?.length; else noTabs" class="editor-grid">
681
- <div *ngFor="let t of editedConfig.tabs; let i = index" class="editor-card">
1282
+ <div *ngFor="let tab of editedConfig.tabs; let i = index" class="editor-card">
682
1283
  <div class="editor-card-header">
683
- <strong class="editor-card-title">Aba #{{ i+1 }}</strong>
1284
+ <strong class="editor-card-title">{{ t('editor.cards.tab', 'Aba') }} #{{ i+1 }}</strong>
684
1285
  <button mat-icon-button (click)="moveTab(i, -1)" [disabled]="i===0"><mat-icon [praxisIcon]="'arrow_upward'"></mat-icon></button>
685
1286
  <button mat-icon-button (click)="moveTab(i, 1)" [disabled]="i===editedConfig.tabs!.length-1"><mat-icon [praxisIcon]="'arrow_downward'"></mat-icon></button>
686
1287
  <button mat-icon-button color="warn" (click)="removeTab(i)"><mat-icon [praxisIcon]="'delete'"></mat-icon></button>
687
1288
  </div>
688
1289
  <div class="editor-grid two tight">
689
- <mat-form-field appearance="outline"><mat-label>ID</mat-label>
690
- <input matInput [(ngModel)]="t.id" (ngModelChange)="onAppearanceChange()" />
1290
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.id', 'ID') }}</mat-label>
1291
+ <input matInput [(ngModel)]="tab.id" (ngModelChange)="onAppearanceChange()" />
691
1292
  </mat-form-field>
692
- <mat-form-field appearance="outline"><mat-label>Rotulo</mat-label>
693
- <input matInput [(ngModel)]="t.textLabel" (ngModelChange)="onAppearanceChange()" />
1293
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.label', 'Rotulo') }}</mat-label>
1294
+ <input matInput [(ngModel)]="tab.textLabel" (ngModelChange)="onAppearanceChange()" />
694
1295
  </mat-form-field>
695
- <mat-form-field appearance="outline"><mat-label>Classe do rotulo</mat-label>
696
- <input matInput [(ngModel)]="t.labelClass" (ngModelChange)="onAppearanceChange()" />
1296
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.labelClass', 'Classe do rotulo') }}</mat-label>
1297
+ <input matInput [(ngModel)]="tab.labelClass" (ngModelChange)="onAppearanceChange()" />
697
1298
  </mat-form-field>
698
- <mat-form-field appearance="outline"><mat-label>Classe do conteudo</mat-label>
699
- <input matInput [(ngModel)]="t.bodyClass" (ngModelChange)="onAppearanceChange()" />
1299
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.bodyClass', 'Classe do conteudo') }}</mat-label>
1300
+ <input matInput [(ngModel)]="tab.bodyClass" (ngModelChange)="onAppearanceChange()" />
700
1301
  </mat-form-field>
701
- <mat-form-field appearance="outline"><mat-label>aria-label</mat-label>
702
- <input matInput [(ngModel)]="t.ariaLabel" (ngModelChange)="onAppearanceChange()" />
1302
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.ariaLabel', 'aria-label') }}</mat-label>
1303
+ <input matInput [(ngModel)]="tab.ariaLabel" (ngModelChange)="onAppearanceChange()" />
703
1304
  </mat-form-field>
704
- <mat-form-field appearance="outline"><mat-label>aria-labelledby</mat-label>
705
- <input matInput [(ngModel)]="t.ariaLabelledby" (ngModelChange)="onAppearanceChange()" />
1305
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.ariaLabelledby', 'aria-labelledby') }}</mat-label>
1306
+ <input matInput [(ngModel)]="tab.ariaLabelledby" (ngModelChange)="onAppearanceChange()" />
706
1307
  </mat-form-field>
707
1308
  </div>
708
- <mat-slide-toggle [(ngModel)]="t.disabled" (ngModelChange)="onAppearanceChange()">Desativada</mat-slide-toggle>
1309
+ <mat-slide-toggle [(ngModel)]="tab.disabled" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.disabled', 'Desativada') }}</mat-slide-toggle>
709
1310
 
710
1311
  <!-- Widgets (componentes dinâmicos) -->
711
1312
  <div class="editor-divider editor-grid">
712
1313
  <div class="editor-row">
713
1314
  <mat-form-field appearance="outline" class="editor-field-min">
714
- <mat-label>Adicionar componente</mat-label>
1315
+ <mat-label>{{ t('editor.fields.addComponent', 'Adicionar componente') }}</mat-label>
715
1316
  <select matNativeControl [(ngModel)]="selectedTabWidgetId[i]">
716
- <option [ngValue]="''">(selecione)</option>
1317
+ <option [ngValue]="''">{{ t('editor.fields.selectPlaceholder', '(selecione)') }}</option>
717
1318
  <option *ngFor="let c of componentOptions" [ngValue]="c.id">{{ c.friendlyName || c.id }}</option>
718
1319
  </select>
719
1320
  </mat-form-field>
720
- <button mat-stroked-button (click)="addWidgetToTab(i)"><mat-icon [praxisIcon]="'add'"></mat-icon>Adicionar</button>
1321
+ <button mat-stroked-button (click)="addWidgetToTab(i)"><mat-icon [praxisIcon]="'add'"></mat-icon>{{ t('editor.actions.add', 'Adicionar') }}</button>
721
1322
  <span class="editor-spacer"></span>
722
1323
  <mat-form-field appearance="outline" class="editor-field-240">
723
- <mat-label>Recurso (resourcePath)</mat-label>
724
- <input matInput [(ngModel)]="quickResourcePathTab[i]" placeholder="ex.: usuarios" />
1324
+ <mat-label>{{ t('editor.fields.resourcePath', 'Recurso (resourcePath)') }}</mat-label>
1325
+ <input matInput [(ngModel)]="quickResourcePathTab[i]" [placeholder]="t('editor.fields.placeholder.resourcePath', 'ex.: usuarios')" />
725
1326
  </mat-form-field>
726
- <button mat-button (click)="addPresetToTab(i, 'form')"><mat-icon [praxisIcon]="'description'"></mat-icon>Form</button>
727
- <button mat-button (click)="addPresetToTab(i, 'table')"><mat-icon [praxisIcon]="'grid_on'"></mat-icon>Tabela</button>
728
- <button mat-button (click)="addPresetToTab(i, 'crud')"><mat-icon [praxisIcon]="'dynamic_form'"></mat-icon>CRUD</button>
1327
+ <button mat-button (click)="addPresetToTab(i, 'form')"><mat-icon [praxisIcon]="'description'"></mat-icon>{{ t('editor.templates.form', 'Form') }}</button>
1328
+ <button mat-button (click)="addPresetToTab(i, 'table')"><mat-icon [praxisIcon]="'grid_on'"></mat-icon>{{ t('editor.templates.table', 'Tabela') }}</button>
1329
+ <button mat-button (click)="addPresetToTab(i, 'crud')"><mat-icon [praxisIcon]="'dynamic_form'"></mat-icon>{{ t('editor.templates.crud', 'CRUD') }}</button>
729
1330
  </div>
730
1331
 
731
- <div *ngIf="t.widgets?.length" class="editor-grid" cdkDropList [cdkDropListData]="t.widgets || []" (cdkDropListDropped)="onTabWidgetDrop(i, $event)">
732
- <div *ngFor="let w of t.widgets; let wi = index" cdkDrag class="editor-card dashed">
1332
+ <div *ngIf="tab.widgets?.length" class="editor-grid" cdkDropList [cdkDropListData]="tab.widgets || []" (cdkDropListDropped)="onTabWidgetDrop(i, $event)">
1333
+ <div *ngFor="let w of tab.widgets; let wi = index" cdkDrag class="editor-card dashed">
733
1334
  <div class="editor-card-header">
734
- <button mat-icon-button cdkDragHandle matTooltip="Arrastar para reordenar" aria-label="Arrastar para reordenar">
1335
+ <button mat-icon-button cdkDragHandle [matTooltip]="t('editor.actions.dragToReorder', 'Arrastar para reordenar')" [attr.aria-label]="t('editor.actions.dragToReorder', 'Arrastar para reordenar')">
735
1336
  <mat-icon [praxisIcon]="'drag_indicator'"></mat-icon>
736
1337
  </button>
737
1338
  <strong class="editor-card-title">{{ getCompName(w.id) }}</strong>
738
- <button mat-icon-button color="warn" (click)="removeWidgetFromTab(i, wi)" matTooltip="Remover componente" aria-label="Remover componente"><mat-icon [praxisIcon]="'delete'"></mat-icon></button>
1339
+ <button mat-icon-button color="warn" (click)="removeWidgetFromTab(i, wi)" [matTooltip]="t('editor.actions.removeComponent', 'Remover componente')" [attr.aria-label]="t('editor.actions.removeComponent', 'Remover componente')"><mat-icon [praxisIcon]="'delete'"></mat-icon></button>
739
1340
  </div>
740
1341
  <div class="editor-grid two tight">
741
1342
  <mat-form-field appearance="outline">
742
- <mat-label>Inputs (JSON)</mat-label>
1343
+ <mat-label>{{ t('editor.fields.inputsJson', 'Inputs (JSON)') }}</mat-label>
743
1344
  <textarea matInput rows="4" [ngModel]="stringify(w.inputs)" (ngModelChange)="updateWidgetInputsTab(i, wi, $event)"></textarea>
744
1345
  </mat-form-field>
745
1346
  <mat-form-field appearance="outline">
746
- <mat-label>Outputs (JSON)</mat-label>
1347
+ <mat-label>{{ t('editor.fields.outputsJson', 'Outputs (JSON)') }}</mat-label>
747
1348
  <textarea matInput rows="4" [ngModel]="stringify(w.outputs)" (ngModelChange)="updateWidgetOutputsTab(i, wi, $event)"></textarea>
748
1349
  </mat-form-field>
749
1350
  </div>
@@ -752,82 +1353,81 @@ class PraxisTabsConfigEditor {
752
1353
  </div>
753
1354
  </div>
754
1355
  </div>
755
- <ng-template #noTabs><em class="editor-muted">Nenhuma aba definida.</em></ng-template>
1356
+ <ng-template #noTabs><em class="editor-muted">{{ t('editor.empty.noTabs', 'Nenhuma aba definida.') }}</em></ng-template>
756
1357
  </div>
757
1358
  </mat-tab>
758
1359
 
759
- <mat-tab label="Acessibilidade">
1360
+ <mat-tab [label]="t('editor.tabs.accessibility', 'Acessibilidade')">
760
1361
  <div class="editor-section">
761
1362
  <div class="editor-row">
762
- <mat-slide-toggle [(ngModel)]="accessibility.highContrast" (ngModelChange)="onAppearanceChange()">Alto contraste</mat-slide-toggle>
763
- <mat-slide-toggle [(ngModel)]="accessibility.reduceMotion" (ngModelChange)="onAppearanceChange()">Reduzir movimento</mat-slide-toggle>
1363
+ <mat-slide-toggle [(ngModel)]="accessibility.highContrast" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.highContrast', 'Alto contraste') }}</mat-slide-toggle>
1364
+ <mat-slide-toggle [(ngModel)]="accessibility.reduceMotion" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.reduceMotion', 'Reduzir movimento') }}</mat-slide-toggle>
764
1365
  </div>
765
1366
  </div>
766
1367
  </mat-tab>
767
1368
 
768
- <mat-tab label="Links">
1369
+ <mat-tab [label]="t('editor.tabs.links', 'Links')" [disabled]="primaryMode !== 'nav'">
769
1370
  <div class="editor-section">
770
- <button mat-stroked-button color="primary" (click)="addLink()"><mat-icon [praxisIcon]="'add_link'"></mat-icon>Adicionar link</button>
1371
+ <button mat-stroked-button color="primary" (click)="addLink()"><mat-icon [praxisIcon]="'add_link'"></mat-icon>{{ t('editor.actions.addLink', 'Adicionar link') }}</button>
771
1372
  <div *ngIf="nav.links?.length; else noLinks" class="editor-grid">
772
1373
  <div *ngFor="let l of nav.links; let i = index" class="editor-card">
773
1374
  <div class="editor-card-header">
774
- <strong class="editor-card-title">Link #{{ i+1 }}</strong>
1375
+ <strong class="editor-card-title">{{ t('editor.cards.link', 'Link') }} #{{ i+1 }}</strong>
775
1376
  <button mat-icon-button (click)="moveLink(i, -1)" [disabled]="i===0"><mat-icon [praxisIcon]="'arrow_upward'"></mat-icon></button>
776
1377
  <button mat-icon-button (click)="moveLink(i, 1)" [disabled]="i===nav.links!.length-1"><mat-icon [praxisIcon]="'arrow_downward'"></mat-icon></button>
777
1378
  <button mat-icon-button color="warn" (click)="removeLink(i)"><mat-icon [praxisIcon]="'delete'"></mat-icon></button>
778
1379
  </div>
779
1380
  <div class="editor-grid two tight">
780
- <mat-form-field appearance="outline"><mat-label>ID</mat-label>
1381
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.id', 'ID') }}</mat-label>
781
1382
  <input matInput [(ngModel)]="l.id" (ngModelChange)="onAppearanceChange()" />
782
1383
  </mat-form-field>
783
- <mat-form-field appearance="outline"><mat-label>Rotulo</mat-label>
1384
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.label', 'Rotulo') }}</mat-label>
784
1385
  <input matInput [(ngModel)]="l.label" (ngModelChange)="onAppearanceChange()" />
785
1386
  </mat-form-field>
786
1387
  </div>
787
1388
  <div class="editor-row">
788
- <mat-slide-toggle [(ngModel)]="l.active" (ngModelChange)="onAppearanceChange()">Ativo</mat-slide-toggle>
789
- <mat-slide-toggle [(ngModel)]="l.disabled" (ngModelChange)="onAppearanceChange()">Desativado</mat-slide-toggle>
790
- <mat-slide-toggle [(ngModel)]="l.disableRipple" (ngModelChange)="onAppearanceChange()">Sem ripple</mat-slide-toggle>
791
- <mat-slide-toggle [(ngModel)]="l.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">Indicador ajustado ao conteudo</mat-slide-toggle>
1389
+ <mat-slide-toggle [(ngModel)]="l.disabled" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.linkDisabled', 'Desativado') }}</mat-slide-toggle>
1390
+ <mat-slide-toggle [(ngModel)]="l.disableRipple" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.disableRipple', 'Sem ripple') }}</mat-slide-toggle>
1391
+ <mat-slide-toggle [(ngModel)]="l.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.fitInkBarToContent', 'Indicador ajustado ao conteudo') }}</mat-slide-toggle>
792
1392
  </div>
793
1393
 
794
1394
  <!-- Widgets para Links -->
795
1395
  <div class="editor-divider editor-grid">
796
1396
  <div class="editor-row">
797
1397
  <mat-form-field appearance="outline" class="editor-field-min">
798
- <mat-label>Adicionar componente</mat-label>
1398
+ <mat-label>{{ t('editor.fields.addComponent', 'Adicionar componente') }}</mat-label>
799
1399
  <select matNativeControl [(ngModel)]="selectedLinkWidgetId[i]">
800
- <option [ngValue]="''">(selecione)</option>
1400
+ <option [ngValue]="''">{{ t('editor.fields.selectPlaceholder', '(selecione)') }}</option>
801
1401
  <option *ngFor="let c of componentOptions" [ngValue]="c.id">{{ c.friendlyName || c.id }}</option>
802
1402
  </select>
803
1403
  </mat-form-field>
804
- <button mat-stroked-button (click)="addWidgetToLink(i)"><mat-icon [praxisIcon]="'add'"></mat-icon>Adicionar</button>
1404
+ <button mat-stroked-button (click)="addWidgetToLink(i)"><mat-icon [praxisIcon]="'add'"></mat-icon>{{ t('editor.actions.add', 'Adicionar') }}</button>
805
1405
  <span class="editor-spacer"></span>
806
1406
  <mat-form-field appearance="outline" class="editor-field-240">
807
- <mat-label>Recurso (resourcePath)</mat-label>
808
- <input matInput [(ngModel)]="quickResourcePathLink[i]" placeholder="ex.: usuarios" />
1407
+ <mat-label>{{ t('editor.fields.resourcePath', 'Recurso (resourcePath)') }}</mat-label>
1408
+ <input matInput [(ngModel)]="quickResourcePathLink[i]" [placeholder]="t('editor.fields.placeholder.resourcePath', 'ex.: usuarios')" />
809
1409
  </mat-form-field>
810
- <button mat-button (click)="addPresetToLink(i, 'form')"><mat-icon [praxisIcon]="'description'"></mat-icon>Form</button>
811
- <button mat-button (click)="addPresetToLink(i, 'table')"><mat-icon [praxisIcon]="'grid_on'"></mat-icon>Tabela</button>
812
- <button mat-button (click)="addPresetToLink(i, 'crud')"><mat-icon [praxisIcon]="'dynamic_form'"></mat-icon>CRUD</button>
1410
+ <button mat-button (click)="addPresetToLink(i, 'form')"><mat-icon [praxisIcon]="'description'"></mat-icon>{{ t('editor.templates.form', 'Form') }}</button>
1411
+ <button mat-button (click)="addPresetToLink(i, 'table')"><mat-icon [praxisIcon]="'grid_on'"></mat-icon>{{ t('editor.templates.table', 'Tabela') }}</button>
1412
+ <button mat-button (click)="addPresetToLink(i, 'crud')"><mat-icon [praxisIcon]="'dynamic_form'"></mat-icon>{{ t('editor.templates.crud', 'CRUD') }}</button>
813
1413
  </div>
814
1414
 
815
1415
  <div *ngIf="l.widgets?.length" class="editor-grid" cdkDropList [cdkDropListData]="l.widgets || []" (cdkDropListDropped)="onLinkWidgetDrop(i, $event)">
816
1416
  <div *ngFor="let w of l.widgets; let wi = index" cdkDrag class="editor-card dashed">
817
1417
  <div class="editor-card-header">
818
- <button mat-icon-button cdkDragHandle matTooltip="Arrastar para reordenar" aria-label="Arrastar para reordenar">
1418
+ <button mat-icon-button cdkDragHandle [matTooltip]="t('editor.actions.dragToReorder', 'Arrastar para reordenar')" [attr.aria-label]="t('editor.actions.dragToReorder', 'Arrastar para reordenar')">
819
1419
  <mat-icon [praxisIcon]="'drag_indicator'"></mat-icon>
820
1420
  </button>
821
1421
  <strong class="editor-card-title">{{ getCompName(w.id) }}</strong>
822
- <button mat-icon-button color="warn" (click)="removeWidgetFromLink(i, wi)" matTooltip="Remover componente" aria-label="Remover componente"><mat-icon [praxisIcon]="'delete'"></mat-icon></button>
1422
+ <button mat-icon-button color="warn" (click)="removeWidgetFromLink(i, wi)" [matTooltip]="t('editor.actions.removeComponent', 'Remover componente')" [attr.aria-label]="t('editor.actions.removeComponent', 'Remover componente')"><mat-icon [praxisIcon]="'delete'"></mat-icon></button>
823
1423
  </div>
824
1424
  <div class="editor-grid two tight">
825
1425
  <mat-form-field appearance="outline">
826
- <mat-label>Inputs (JSON)</mat-label>
1426
+ <mat-label>{{ t('editor.fields.inputsJson', 'Inputs (JSON)') }}</mat-label>
827
1427
  <textarea matInput rows="4" [ngModel]="stringify(w.inputs)" (ngModelChange)="updateWidgetInputsLink(i, wi, $event)"></textarea>
828
1428
  </mat-form-field>
829
1429
  <mat-form-field appearance="outline">
830
- <mat-label>Outputs (JSON)</mat-label>
1430
+ <mat-label>{{ t('editor.fields.outputsJson', 'Outputs (JSON)') }}</mat-label>
831
1431
  <textarea matInput rows="4" [ngModel]="stringify(w.outputs)" (ngModelChange)="updateWidgetOutputsLink(i, wi, $event)"></textarea>
832
1432
  </mat-form-field>
833
1433
  </div>
@@ -836,11 +1436,12 @@ class PraxisTabsConfigEditor {
836
1436
  </div>
837
1437
  </div>
838
1438
  </div>
839
- <ng-template #noLinks><em class="editor-muted">Nenhum link definido.</em></ng-template>
1439
+ <ng-template #noLinks><em class="editor-muted">{{ t('editor.empty.noLinks', 'Nenhum link definido.') }}</em></ng-template>
840
1440
  </div>
841
1441
  </mat-tab>
842
1442
  </mat-tab-group>
843
- `, isInline: true, styles: [":host{display:block;color:var(--md-sys-color-on-surface)}.editor-tabs{--editor-surface: var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface));--editor-border: 1px solid var(--md-sys-color-outline-variant);--editor-radius: 12px;--editor-muted: var(--md-sys-color-on-surface-variant)}.editor-section{padding:12px;display:grid;gap:12px;background:var(--editor-surface);border:var(--editor-border);border-radius:var(--editor-radius)}.editor-section-lg{gap:16px}.editor-row{display:flex;gap:10px;flex-wrap:wrap;align-items:center}.editor-grid{display:grid;gap:12px}.editor-grid.two{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-grid.tight{gap:8px}.editor-toolbar{display:flex;gap:8px;flex-wrap:wrap;align-items:center}.editor-muted{color:var(--editor-muted)}.editor-title{margin:6px 0 8px;font-size:1rem}.editor-title-row{display:flex;align-items:center;gap:8px}.editor-code{white-space:pre-wrap;background:var(--md-sys-color-surface-container);padding:8px;border-radius:6px;border:1px solid var(--md-sys-color-outline-variant)}.editor-card{border:1px solid var(--md-sys-color-outline-variant);padding:10px;border-radius:10px;display:grid;gap:8px;background:var(--md-sys-color-surface)}.editor-card.dashed{border-style:dashed}.editor-card-header{display:flex;align-items:center;gap:8px}.editor-card-title{flex:1;font-weight:600}.editor-divider{border-top:1px solid var(--md-sys-color-outline-variant);padding-top:8px}.editor-spacer{flex:1}.editor-field-min{min-width:260px}.editor-section .editor-field-min{width:260px;max-width:260px}.editor-field-240{width:240px}.editor-section .editor-field-240{width:240px;max-width:240px}.editor-json{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.editor-section .mat-mdc-form-field{width:100%;max-width:520px;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var(--md-sys-color-outline-variant);--mdc-outlined-text-field-hover-outline-color: var(--md-sys-color-outline);--mdc-outlined-text-field-focus-outline-color: var(--md-sys-color-primary);--mdc-outlined-text-field-error-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-focus-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-hover-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-label-text-color: var(--md-sys-color-on-surface-variant);--mdc-outlined-text-field-input-text-color: var(--md-sys-color-on-surface);--mdc-outlined-text-field-supporting-text-color: var(--md-sys-color-on-surface-variant)}.editor-section .mat-mdc-form-field.full{max-width:none}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;margin-right:-4px}.help-icon-button mat-icon{font-size:18px;width:18px;height:18px}.mat-mdc-form-field-icon-suffix{align-self:center}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.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: i3.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i3.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i3$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i3$1.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "directive", type: i5.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i5.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i5.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i6.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i9.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i10.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i10.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i10.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i11.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] });
1443
+ </div>
1444
+ `, isInline: true, styles: [":host{display:block;color:var(--md-sys-color-on-surface)}.editor-shell{display:grid;gap:12px}.editor-topbar{display:flex;align-items:start;justify-content:flex-start}.editor-mode-field{width:min(320px,100%)}.editor-diagnostics{display:grid;gap:8px}.editor-diagnostics-title{font-size:.95rem;font-weight:600;color:var(--md-sys-color-on-surface)}.editor-diagnostic{display:grid;gap:4px;padding:10px 12px;border-radius:10px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface)}.editor-diagnostic code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;color:var(--md-sys-color-on-surface-variant)}.editor-diagnostic.level-error{background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container);border-color:var(--md-sys-color-error)}.editor-diagnostic.level-warning{background:var(--md-sys-color-tertiary-container, var(--md-sys-color-surface-container));color:var(--md-sys-color-on-tertiary-container, var(--md-sys-color-on-surface));border-color:var(--md-sys-color-outline-variant)}.editor-tabs{--editor-surface: var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface));--editor-border: 1px solid var(--md-sys-color-outline-variant);--editor-radius: 12px;--editor-muted: var(--md-sys-color-on-surface-variant)}.editor-section{padding:12px;display:grid;gap:12px;background:var(--editor-surface);border:var(--editor-border);border-radius:var(--editor-radius)}.editor-section-lg{gap:16px}.editor-row{display:flex;gap:10px;flex-wrap:wrap;align-items:center}.editor-grid{display:grid;gap:12px}.editor-grid.two{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-grid.tight{gap:8px}.editor-toolbar{display:flex;gap:8px;flex-wrap:wrap;align-items:center}.editor-muted{color:var(--editor-muted)}.editor-title{margin:6px 0 8px;font-size:1rem}.editor-title-row{display:flex;align-items:center;gap:8px}.editor-code{white-space:pre-wrap;background:var(--md-sys-color-surface-container);padding:8px;border-radius:6px;border:1px solid var(--md-sys-color-outline-variant)}.editor-card{border:1px solid var(--md-sys-color-outline-variant);padding:10px;border-radius:10px;display:grid;gap:8px;background:var(--md-sys-color-surface)}.editor-card.dashed{border-style:dashed}.editor-card-header{display:flex;align-items:center;gap:8px}.editor-card-title{flex:1;font-weight:600}.editor-divider{border-top:1px solid var(--md-sys-color-outline-variant);padding-top:8px}.editor-spacer{flex:1}.editor-field-min{min-width:260px}.editor-section .editor-field-min{width:260px;max-width:260px}.editor-field-240{width:240px}.editor-section .editor-field-240{width:240px;max-width:240px}.editor-json{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.editor-section .mat-mdc-form-field{width:100%;max-width:520px;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var(--md-sys-color-outline-variant);--mdc-outlined-text-field-hover-outline-color: var(--md-sys-color-outline);--mdc-outlined-text-field-focus-outline-color: var(--md-sys-color-primary);--mdc-outlined-text-field-error-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-focus-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-hover-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-label-text-color: var(--md-sys-color-on-surface-variant);--mdc-outlined-text-field-input-text-color: var(--md-sys-color-on-surface);--mdc-outlined-text-field-supporting-text-color: var(--md-sys-color-on-surface-variant)}.editor-section .mat-mdc-form-field.full{max-width:none}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;margin-right:-4px}.help-icon-button mat-icon{font-size:18px;width:18px;height:18px}.mat-mdc-form-field-icon-suffix{align-self:center}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3.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: i3.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i3.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i3$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i3$1.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "directive", type: i5.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i5.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i5.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i6.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i9.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i10.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i10.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i10.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i11.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }] });
844
1445
  }
845
1446
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisTabsConfigEditor, decorators: [{
846
1447
  type: Component,
@@ -856,181 +1457,207 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
856
1457
  MatSlideToggleModule,
857
1458
  DragDropModule,
858
1459
  MatTooltipModule,
859
- ], template: `
1460
+ ], providers: [providePraxisI18nConfig(PRAXIS_TABS_I18N_CONFIG)], template: `
1461
+ <div class="editor-shell">
1462
+ <div class="editor-topbar">
1463
+ <mat-form-field appearance="outline" class="editor-mode-field">
1464
+ <mat-label>{{ t('editor.fields.mode', 'Modo principal') }}</mat-label>
1465
+ <select matNativeControl [ngModel]="primaryMode" (ngModelChange)="setPrimaryMode($event)">
1466
+ <option value="group">{{ t('editor.mode.group', 'Grupo de abas') }}</option>
1467
+ <option value="nav">{{ t('editor.mode.nav', 'Navegacao por links') }}</option>
1468
+ </select>
1469
+ </mat-form-field>
1470
+ </div>
1471
+
1472
+ <div *ngIf="diagnostics.length" class="editor-diagnostics">
1473
+ <div class="editor-diagnostics-title">{{ t('editor.diagnostics.title', 'Diagnosticos de authoring') }}</div>
1474
+ <div
1475
+ *ngFor="let diagnostic of diagnostics"
1476
+ class="editor-diagnostic"
1477
+ [class.level-error]="diagnostic.level === 'error'"
1478
+ [class.level-warning]="diagnostic.level === 'warning'"
1479
+ [class.level-info]="diagnostic.level === 'info'"
1480
+ >
1481
+ <strong>{{ diagnosticLevelLabel(diagnostic.level) }}</strong>
1482
+ <span>{{ diagnostic.message }}</span>
1483
+ <code *ngIf="diagnostic.path">{{ diagnostic.path }}</code>
1484
+ </div>
1485
+ </div>
1486
+
860
1487
  <mat-tab-group class="editor-tabs">
861
- <mat-tab label="Comportamento">
1488
+ <mat-tab [label]="t('editor.tabs.behavior', 'Comportamento')">
862
1489
  <div class="editor-section">
863
1490
  <div class="editor-row">
864
- <mat-slide-toggle [(ngModel)]="behavior.closeable" (ngModelChange)="onAppearanceChange()">Fechavel</mat-slide-toggle>
865
- <mat-slide-toggle [(ngModel)]="behavior.lazyLoad" (ngModelChange)="onAppearanceChange()">Carregar sob demanda</mat-slide-toggle>
866
- <mat-slide-toggle [(ngModel)]="behavior.reorderable" (ngModelChange)="onAppearanceChange()">Reordenavel</mat-slide-toggle>
1491
+ <mat-slide-toggle [(ngModel)]="behavior.closeable" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.closeable', 'Fechavel') }}</mat-slide-toggle>
1492
+ <mat-slide-toggle [(ngModel)]="behavior.lazyLoad" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.lazyLoad', 'Carregar sob demanda') }}</mat-slide-toggle>
1493
+ <mat-slide-toggle [(ngModel)]="behavior.reorderable" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.reorderable', 'Reordenavel') }}</mat-slide-toggle>
867
1494
  </div>
868
1495
  </div>
869
1496
  </mat-tab>
870
- <mat-tab label="Grupo">
1497
+ <mat-tab [label]="t('editor.tabs.group', 'Grupo')" [disabled]="primaryMode !== 'group'">
871
1498
  <div class="editor-section">
872
1499
  <div class="editor-grid two">
873
- <mat-form-field appearance="outline"><mat-label>Alinhamento das abas</mat-label>
1500
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.groupAlignTabs', 'Alinhamento das abas') }}</mat-label>
874
1501
  <select matNativeControl [(ngModel)]="group.alignTabs" (ngModelChange)="onAppearanceChange()">
875
- <option [ngValue]="undefined">Padrao</option>
876
- <option value="start">Inicio</option>
877
- <option value="center">Centro</option>
878
- <option value="end">Fim</option>
1502
+ <option [ngValue]="undefined">{{ t('editor.options.default', 'Padrao') }}</option>
1503
+ <option value="start">{{ t('editor.options.start', 'Inicio') }}</option>
1504
+ <option value="center">{{ t('editor.options.center', 'Centro') }}</option>
1505
+ <option value="end">{{ t('editor.options.end', 'Fim') }}</option>
879
1506
  </select>
880
1507
  </mat-form-field>
881
- <mat-form-field appearance="outline"><mat-label>Posicao do header</mat-label>
1508
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.groupHeaderPosition', 'Posicao do header') }}</mat-label>
882
1509
  <select matNativeControl [(ngModel)]="group.headerPosition" (ngModelChange)="onAppearanceChange()">
883
- <option [ngValue]="undefined">Acima (padrao)</option>
884
- <option value="above">Acima</option>
885
- <option value="below">Abaixo</option>
1510
+ <option [ngValue]="undefined">{{ t('editor.options.aboveDefault', 'Acima (padrao)') }}</option>
1511
+ <option value="above">{{ t('editor.options.above', 'Acima') }}</option>
1512
+ <option value="below">{{ t('editor.options.below', 'Abaixo') }}</option>
886
1513
  </select>
887
1514
  </mat-form-field>
888
- <mat-form-field appearance="outline"><mat-label>Indice selecionado</mat-label>
1515
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.selectedIndex', 'Indice selecionado') }}</mat-label>
889
1516
  <input matInput type="number" [(ngModel)]="group.selectedIndex" (ngModelChange)="onAppearanceChange()" />
890
1517
  <button
891
1518
  mat-icon-button
892
1519
  matSuffix
893
1520
  class="help-icon-button"
894
1521
  type="button"
895
- [matTooltip]="'selectedIndex'"
1522
+ [matTooltip]="t('editor.hints.selectedIndex', 'selectedIndex')"
896
1523
  matTooltipPosition="above"
897
1524
  >
898
1525
  <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
899
1526
  </button>
900
1527
  </mat-form-field>
901
- <mat-form-field appearance="outline"><mat-label>Duracao da animacao</mat-label>
902
- <input matInput [(ngModel)]="group.animationDuration" (ngModelChange)="onAppearanceChange()" placeholder="500ms" />
1528
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.animationDuration', 'Duracao da animacao') }}</mat-label>
1529
+ <input matInput [(ngModel)]="group.animationDuration" (ngModelChange)="onAppearanceChange()" [placeholder]="t('editor.fields.placeholder.animationDuration', '500ms')" />
903
1530
  <button
904
1531
  mat-icon-button
905
1532
  matSuffix
906
1533
  class="help-icon-button"
907
1534
  type="button"
908
- [matTooltip]="'animationDuration'"
1535
+ [matTooltip]="t('editor.hints.animationDuration', 'animationDuration')"
909
1536
  matTooltipPosition="above"
910
1537
  >
911
1538
  <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
912
1539
  </button>
913
1540
  </mat-form-field>
914
- <mat-form-field appearance="outline"><mat-label>Cor (M2)</mat-label>
1541
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.color', 'Cor (M2)') }}</mat-label>
915
1542
  <select matNativeControl [(ngModel)]="group.color" (ngModelChange)="onAppearanceChange()">
916
- <option [ngValue]="undefined">(nenhuma)</option>
917
- <option value="primary">Primary</option>
918
- <option value="accent">Accent</option>
919
- <option value="warn">Warn</option>
1543
+ <option [ngValue]="undefined">{{ t('editor.options.none', '(nenhuma)') }}</option>
1544
+ <option value="primary">{{ t('editor.options.primary', 'Primary') }}</option>
1545
+ <option value="accent">{{ t('editor.options.accent', 'Accent') }}</option>
1546
+ <option value="warn">{{ t('editor.options.warn', 'Warn') }}</option>
920
1547
  </select>
921
1548
  <button
922
1549
  mat-icon-button
923
1550
  matSuffix
924
1551
  class="help-icon-button"
925
1552
  type="button"
926
- [matTooltip]="'Preferir tokens em Estilo.'"
1553
+ [matTooltip]="t('editor.hints.preferTokens', 'Preferir tokens em Estilo.')"
927
1554
  matTooltipPosition="above"
928
1555
  >
929
1556
  <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
930
1557
  </button>
931
1558
  </mat-form-field>
932
- <mat-form-field appearance="outline"><mat-label>Cor de fundo (M2)</mat-label>
1559
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.backgroundColor', 'Cor de fundo (M2)') }}</mat-label>
933
1560
  <select matNativeControl [(ngModel)]="group.backgroundColor" (ngModelChange)="onAppearanceChange()">
934
- <option [ngValue]="undefined">(nenhuma)</option>
935
- <option value="primary">Primary</option>
936
- <option value="accent">Accent</option>
937
- <option value="warn">Warn</option>
1561
+ <option [ngValue]="undefined">{{ t('editor.options.none', '(nenhuma)') }}</option>
1562
+ <option value="primary">{{ t('editor.options.primary', 'Primary') }}</option>
1563
+ <option value="accent">{{ t('editor.options.accent', 'Accent') }}</option>
1564
+ <option value="warn">{{ t('editor.options.warn', 'Warn') }}</option>
938
1565
  </select>
939
1566
  <button
940
1567
  mat-icon-button
941
1568
  matSuffix
942
1569
  class="help-icon-button"
943
1570
  type="button"
944
- [matTooltip]="'Preferir tokens em Estilo.'"
1571
+ [matTooltip]="t('editor.hints.preferTokens', 'Preferir tokens em Estilo.')"
945
1572
  matTooltipPosition="above"
946
1573
  >
947
1574
  <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
948
1575
  </button>
949
1576
  </mat-form-field>
950
- <mat-form-field appearance="outline"><mat-label>aria-label</mat-label>
1577
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.ariaLabel', 'aria-label') }}</mat-label>
951
1578
  <input matInput [(ngModel)]="group.ariaLabel" (ngModelChange)="onAppearanceChange()" />
952
1579
  </mat-form-field>
953
- <mat-form-field appearance="outline"><mat-label>aria-labelledby</mat-label>
1580
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.ariaLabelledby', 'aria-labelledby') }}</mat-label>
954
1581
  <input matInput [(ngModel)]="group.ariaLabelledby" (ngModelChange)="onAppearanceChange()" />
955
1582
  </mat-form-field>
956
1583
  </div>
957
1584
  <div class="editor-row">
958
- <mat-slide-toggle [(ngModel)]="group.dynamicHeight" (ngModelChange)="onAppearanceChange()">Altura dinamica</mat-slide-toggle>
959
- <mat-slide-toggle [(ngModel)]="group.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">Indicador ajustado ao conteudo</mat-slide-toggle>
960
- <mat-slide-toggle [(ngModel)]="group.disablePagination" (ngModelChange)="onAppearanceChange()">Sem paginacao</mat-slide-toggle>
961
- <mat-slide-toggle [(ngModel)]="group.disableRipple" (ngModelChange)="onAppearanceChange()">Sem ripple</mat-slide-toggle>
962
- <mat-slide-toggle [(ngModel)]="group.preserveContent" (ngModelChange)="onAppearanceChange()">Preservar conteudo</mat-slide-toggle>
963
- <mat-slide-toggle [(ngModel)]="group.stretchTabs" (ngModelChange)="onAppearanceChange()">Esticar abas</mat-slide-toggle>
1585
+ <mat-slide-toggle [(ngModel)]="group.dynamicHeight" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.dynamicHeight', 'Altura dinamica') }}</mat-slide-toggle>
1586
+ <mat-slide-toggle [(ngModel)]="group.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.fitInkBarToContent', 'Indicador ajustado ao conteudo') }}</mat-slide-toggle>
1587
+ <mat-slide-toggle [(ngModel)]="group.disablePagination" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.disablePagination', 'Sem paginacao') }}</mat-slide-toggle>
1588
+ <mat-slide-toggle [(ngModel)]="group.disableRipple" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.disableRipple', 'Sem ripple') }}</mat-slide-toggle>
1589
+ <mat-slide-toggle [(ngModel)]="group.preserveContent" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.preserveContent', 'Preservar conteudo') }}</mat-slide-toggle>
1590
+ <mat-slide-toggle [(ngModel)]="group.stretchTabs" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.stretchTabs', 'Esticar abas') }}</mat-slide-toggle>
964
1591
  </div>
965
1592
  </div>
966
1593
  </mat-tab>
967
1594
 
968
- <mat-tab label="Navegação">
1595
+ <mat-tab [label]="t('editor.tabs.nav', 'Navegacao')" [disabled]="primaryMode !== 'nav'">
969
1596
  <div class="editor-section">
970
1597
  <div class="editor-grid two">
971
- <mat-form-field appearance="outline"><mat-label>Indice selecionado</mat-label>
1598
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.selectedIndex', 'Indice selecionado') }}</mat-label>
972
1599
  <input matInput type="number" [(ngModel)]="nav.selectedIndex" (ngModelChange)="onAppearanceChange()" />
973
1600
  <button
974
1601
  mat-icon-button
975
1602
  matSuffix
976
1603
  class="help-icon-button"
977
1604
  type="button"
978
- [matTooltip]="'selectedIndex'"
1605
+ [matTooltip]="t('editor.hints.selectedIndex', 'selectedIndex')"
979
1606
  matTooltipPosition="above"
980
1607
  >
981
1608
  <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
982
1609
  </button>
983
1610
  </mat-form-field>
984
- <mat-form-field appearance="outline"><mat-label>Duracao da animacao</mat-label>
985
- <input matInput [(ngModel)]="nav.animationDuration" (ngModelChange)="onAppearanceChange()" placeholder="500ms" />
1611
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.animationDuration', 'Duracao da animacao') }}</mat-label>
1612
+ <input matInput [(ngModel)]="nav.animationDuration" (ngModelChange)="onAppearanceChange()" [placeholder]="t('editor.fields.placeholder.animationDuration', '500ms')" />
986
1613
  <button
987
1614
  mat-icon-button
988
1615
  matSuffix
989
1616
  class="help-icon-button"
990
1617
  type="button"
991
- [matTooltip]="'animationDuration'"
1618
+ [matTooltip]="t('editor.hints.animationDuration', 'animationDuration')"
992
1619
  matTooltipPosition="above"
993
1620
  >
994
1621
  <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
995
1622
  </button>
996
1623
  </mat-form-field>
997
- <mat-form-field appearance="outline"><mat-label>aria-label</mat-label>
1624
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.ariaLabel', 'aria-label') }}</mat-label>
998
1625
  <input matInput [(ngModel)]="nav.ariaLabel" (ngModelChange)="onAppearanceChange()" />
999
1626
  </mat-form-field>
1000
- <mat-form-field appearance="outline"><mat-label>aria-labelledby</mat-label>
1627
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.ariaLabelledby', 'aria-labelledby') }}</mat-label>
1001
1628
  <input matInput [(ngModel)]="nav.ariaLabelledby" (ngModelChange)="onAppearanceChange()" />
1002
1629
  </mat-form-field>
1003
- <mat-form-field appearance="outline"><mat-label>Cor (M2)</mat-label>
1630
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.color', 'Cor (M2)') }}</mat-label>
1004
1631
  <select matNativeControl [(ngModel)]="nav.color" (ngModelChange)="onAppearanceChange()">
1005
- <option [ngValue]="undefined">(nenhuma)</option>
1006
- <option value="primary">Primary</option>
1007
- <option value="accent">Accent</option>
1008
- <option value="warn">Warn</option>
1632
+ <option [ngValue]="undefined">{{ t('editor.options.none', '(nenhuma)') }}</option>
1633
+ <option value="primary">{{ t('editor.options.primary', 'Primary') }}</option>
1634
+ <option value="accent">{{ t('editor.options.accent', 'Accent') }}</option>
1635
+ <option value="warn">{{ t('editor.options.warn', 'Warn') }}</option>
1009
1636
  </select>
1010
1637
  <button
1011
1638
  mat-icon-button
1012
1639
  matSuffix
1013
1640
  class="help-icon-button"
1014
1641
  type="button"
1015
- [matTooltip]="'Preferir tokens em Estilo.'"
1642
+ [matTooltip]="t('editor.hints.preferTokens', 'Preferir tokens em Estilo.')"
1016
1643
  matTooltipPosition="above"
1017
1644
  >
1018
1645
  <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
1019
1646
  </button>
1020
1647
  </mat-form-field>
1021
- <mat-form-field appearance="outline"><mat-label>Cor de fundo (M2)</mat-label>
1648
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.backgroundColor', 'Cor de fundo (M2)') }}</mat-label>
1022
1649
  <select matNativeControl [(ngModel)]="nav.backgroundColor" (ngModelChange)="onAppearanceChange()">
1023
- <option [ngValue]="undefined">(nenhuma)</option>
1024
- <option value="primary">Primary</option>
1025
- <option value="accent">Accent</option>
1026
- <option value="warn">Warn</option>
1650
+ <option [ngValue]="undefined">{{ t('editor.options.none', '(nenhuma)') }}</option>
1651
+ <option value="primary">{{ t('editor.options.primary', 'Primary') }}</option>
1652
+ <option value="accent">{{ t('editor.options.accent', 'Accent') }}</option>
1653
+ <option value="warn">{{ t('editor.options.warn', 'Warn') }}</option>
1027
1654
  </select>
1028
1655
  <button
1029
1656
  mat-icon-button
1030
1657
  matSuffix
1031
1658
  class="help-icon-button"
1032
1659
  type="button"
1033
- [matTooltip]="'Preferir tokens em Estilo.'"
1660
+ [matTooltip]="t('editor.hints.preferTokens', 'Preferir tokens em Estilo.')"
1034
1661
  matTooltipPosition="above"
1035
1662
  >
1036
1663
  <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
@@ -1038,26 +1665,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
1038
1665
  </mat-form-field>
1039
1666
  </div>
1040
1667
  <div class="editor-row">
1041
- <mat-slide-toggle [(ngModel)]="nav.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">Indicador ajustado ao conteudo</mat-slide-toggle>
1042
- <mat-slide-toggle [(ngModel)]="nav.disablePagination" (ngModelChange)="onAppearanceChange()">Sem paginacao</mat-slide-toggle>
1043
- <mat-slide-toggle [(ngModel)]="nav.disableRipple" (ngModelChange)="onAppearanceChange()">Sem ripple</mat-slide-toggle>
1044
- <mat-slide-toggle [(ngModel)]="nav.stretchTabs" (ngModelChange)="onAppearanceChange()">Esticar abas</mat-slide-toggle>
1668
+ <mat-slide-toggle [(ngModel)]="nav.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.fitInkBarToContent', 'Indicador ajustado ao conteudo') }}</mat-slide-toggle>
1669
+ <mat-slide-toggle [(ngModel)]="nav.disablePagination" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.disablePagination', 'Sem paginacao') }}</mat-slide-toggle>
1670
+ <mat-slide-toggle [(ngModel)]="nav.disableRipple" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.disableRipple', 'Sem ripple') }}</mat-slide-toggle>
1671
+ <mat-slide-toggle [(ngModel)]="nav.stretchTabs" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.stretchTabs', 'Esticar abas') }}</mat-slide-toggle>
1045
1672
  </div>
1046
1673
  </div>
1047
1674
  </mat-tab>
1048
- <mat-tab label="JSON">
1675
+ <mat-tab [label]="t('editor.tabs.json', 'JSON')">
1049
1676
  <div class="editor-section">
1050
1677
  <div class="editor-toolbar">
1051
- <button mat-button (click)="formatJson()" [disabled]="!isValid">
1052
- <mat-icon [praxisIcon]="'format_align_left'"></mat-icon>Formatar
1678
+ <button mat-button (click)="formatJson()" [disabled]="!isValid">
1679
+ <mat-icon [praxisIcon]="'format_align_left'"></mat-icon>{{ t('editor.actions.format', 'Formatar') }}
1053
1680
  </button>
1054
- <button mat-button (click)="reset()">
1055
- <mat-icon [praxisIcon]="'restart_alt'"></mat-icon>Resetar
1681
+ <button mat-button (click)="reset()">
1682
+ <mat-icon [praxisIcon]="'restart_alt'"></mat-icon>{{ t('editor.actions.reset', 'Resetar') }}
1056
1683
  </button>
1057
1684
  </div>
1058
1685
 
1059
1686
  <mat-form-field appearance="outline" class="json-textarea-field full">
1060
- <mat-label>Configuracao JSON</mat-label>
1687
+ <mat-label>{{ t('editor.fields.jsonConfig', 'Configuracao JSON') }}</mat-label>
1061
1688
  <textarea
1062
1689
  matInput
1063
1690
  [(ngModel)]="jsonText"
@@ -1066,84 +1693,84 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
1066
1693
  spellcheck="false"
1067
1694
  class="editor-json"
1068
1695
  ></textarea>
1069
- <mat-hint *ngIf="isValid">JSON valido</mat-hint>
1070
- <mat-error *ngIf="!isValid && jsonText">JSON invalido: {{ errorMsg }}</mat-error>
1696
+ <mat-hint *ngIf="isValid">{{ t('editor.json.valid', 'JSON valido') }}</mat-hint>
1697
+ <mat-error *ngIf="!isValid && jsonText">{{ t('editor.json.invalidPrefix', 'JSON invalido:') }} {{ errorMsg }}</mat-error>
1071
1698
  </mat-form-field>
1072
1699
  </div>
1073
1700
  </mat-tab>
1074
1701
 
1075
- <mat-tab label="Estilo">
1702
+ <mat-tab [label]="t('editor.tabs.style', 'Estilo')">
1076
1703
  <div class="editor-section editor-section-lg">
1077
1704
  <div class="editor-row">
1078
- <span class="editor-muted">Presets:</span>
1705
+ <span class="editor-muted">{{ t('editor.presets.title', 'Presets:') }}</span>
1079
1706
  <button mat-button color="primary" (click)="applyPreset('primary')">
1080
1707
  <mat-icon [praxisIcon]="'palette'"></mat-icon>
1081
- Primário
1708
+ {{ t('editor.presets.primary', 'Primario') }}
1082
1709
  </button>
1083
1710
  <button mat-button (click)="applyPreset('neutral')">
1084
1711
  <mat-icon [praxisIcon]="'contrast'"></mat-icon>
1085
- Neutro
1712
+ {{ t('editor.presets.neutral', 'Neutro') }}
1086
1713
  </button>
1087
1714
  <button mat-button (click)="applyPreset('high-contrast')">
1088
1715
  <mat-icon [praxisIcon]="'visibility'"></mat-icon>
1089
- Alto contraste
1716
+ {{ t('editor.presets.highContrast', 'Alto contraste') }}
1090
1717
  </button>
1091
1718
  <button mat-button (click)="clearTokens()">
1092
1719
  <mat-icon [praxisIcon]="'backspace'"></mat-icon>
1093
- Limpar tokens
1720
+ {{ t('editor.actions.clearTokens', 'Limpar tokens') }}
1094
1721
  </button>
1095
1722
  </div>
1096
1723
 
1097
1724
  <div class="editor-grid two tight">
1098
1725
  <mat-form-field appearance="outline">
1099
- <mat-label>Classe de tema (opcional)</mat-label>
1100
- <input matInput [(ngModel)]="appearance.themeClass" (ngModelChange)="onAppearanceChange()" placeholder="ex.: tabs-accented" />
1726
+ <mat-label>{{ t('editor.fields.themeClass', 'Classe de tema (opcional)') }}</mat-label>
1727
+ <input matInput [(ngModel)]="appearance.themeClass" (ngModelChange)="onAppearanceChange()" [placeholder]="t('editor.fields.placeholder.themeClass', 'ex.: tabs-accented')" />
1101
1728
  </mat-form-field>
1102
1729
 
1103
1730
  <mat-form-field appearance="outline">
1104
- <mat-label>Densidade</mat-label>
1731
+ <mat-label>{{ t('editor.fields.density', 'Densidade') }}</mat-label>
1105
1732
  <select matNativeControl [(ngModel)]="appearance.density" (ngModelChange)="onAppearanceChange()">
1106
- <option [ngValue]="undefined">Padrao</option>
1107
- <option value="compact">Compacta</option>
1108
- <option value="comfortable">Confortavel</option>
1109
- <option value="spacious">Espacosa</option>
1733
+ <option [ngValue]="undefined">{{ t('editor.density.default', 'Padrao') }}</option>
1734
+ <option value="compact">{{ t('editor.density.compact', 'Compacta') }}</option>
1735
+ <option value="comfortable">{{ t('editor.density.comfortable', 'Confortavel') }}</option>
1736
+ <option value="spacious">{{ t('editor.density.spacious', 'Espacosa') }}</option>
1110
1737
  </select>
1111
1738
  </mat-form-field>
1112
1739
  </div>
1113
1740
 
1114
1741
  <div>
1115
1742
  <div class="editor-title-row">
1116
- <h3 class="editor-title">Tokens (Material 3)</h3>
1743
+ <h3 class="editor-title">{{ t('editor.sections.tokens', 'Tokens (Material 3)') }}</h3>
1117
1744
  <button
1118
1745
  mat-icon-button
1119
1746
  class="help-icon-button"
1120
1747
  type="button"
1121
- [matTooltip]="'Valores aceitam CSS vars (ex.: var(--md-sys-color-primary)) ou cores hex/rgb.'"
1748
+ [matTooltip]="t('editor.hints.tokenValueHelp', 'Valores aceitam CSS vars ou cores hex/rgb.')"
1122
1749
  matTooltipPosition="above"
1123
1750
  >
1124
1751
  <mat-icon [praxisIcon]="'help_outline'"></mat-icon>
1125
1752
  </button>
1126
1753
  </div>
1127
1754
  <div class="editor-grid two">
1128
- <ng-container *ngFor="let t of tokenList">
1755
+ <ng-container *ngFor="let token of tokenList">
1129
1756
  <mat-form-field appearance="outline">
1130
- <mat-label>{{ t.label }}</mat-label>
1131
- <input matInput placeholder="var(--md-sys-color-primary) / #RRGGBB" [ngModel]="appearance.tokens?.[t.key]" (ngModelChange)="onTokenChange(t.key, $event)" />
1757
+ <mat-label>{{ t(token.labelKey, token.fallback) }}</mat-label>
1758
+ <input matInput [placeholder]="t('editor.placeholders.tokenValue', 'var(--md-sys-color-primary) / #RRGGBB')" [ngModel]="appearance.tokens?.[token.key]" (ngModelChange)="onTokenChange(token.key, $event)" />
1132
1759
  </mat-form-field>
1133
1760
  </ng-container>
1134
1761
  </div>
1135
1762
  </div>
1136
1763
 
1137
1764
  <div>
1138
- <h3 class="editor-title">CSS personalizado</h3>
1765
+ <h3 class="editor-title">{{ t('editor.sections.customCss', 'CSS personalizado') }}</h3>
1139
1766
  <mat-form-field appearance="outline" class="json-textarea-field full">
1140
- <mat-label>CSS a ser injetado no componente</mat-label>
1141
- <textarea matInput rows="10" [(ngModel)]="appearance.customCss" (ngModelChange)="onAppearanceChange()" placeholder=".praxis-tabs-root .mdc-tab__text-label { font-weight: 600; }"></textarea>
1767
+ <mat-label>{{ t('editor.fields.customCss', 'CSS a ser injetado no componente') }}</mat-label>
1768
+ <textarea matInput rows="10" [(ngModel)]="appearance.customCss" (ngModelChange)="onAppearanceChange()" [placeholder]="t('editor.placeholders.customCssExample', '.praxis-tabs-root .mdc-tab__text-label { font-weight: 600; }')"></textarea>
1142
1769
  </mat-form-field>
1143
1770
  </div>
1144
1771
 
1145
1772
  <div>
1146
- <h3 class="editor-title">Snippet SCSS (para uso em styles.scss)</h3>
1773
+ <h3 class="editor-title">{{ t('editor.sections.scssSnippet', 'Snippet SCSS (para uso em styles.scss)') }}</h3>
1147
1774
  <pre class="editor-code">
1148
1775
  @use '@angular/material' as mat;
1149
1776
  {{ scssSnippet() }}
@@ -1152,76 +1779,76 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
1152
1779
  </div>
1153
1780
  </mat-tab>
1154
1781
 
1155
- <mat-tab label="Abas">
1782
+ <mat-tab [label]="t('editor.tabs.items', 'Abas')" [disabled]="primaryMode !== 'group'">
1156
1783
  <div class="editor-section">
1157
- <button mat-stroked-button color="primary" (click)="addTab()"><mat-icon [praxisIcon]="'add'"></mat-icon>Adicionar aba</button>
1784
+ <button mat-stroked-button color="primary" (click)="addTab()"><mat-icon [praxisIcon]="'add'"></mat-icon>{{ t('editor.actions.addTab', 'Adicionar aba') }}</button>
1158
1785
  <div *ngIf="editedConfig.tabs?.length; else noTabs" class="editor-grid">
1159
- <div *ngFor="let t of editedConfig.tabs; let i = index" class="editor-card">
1786
+ <div *ngFor="let tab of editedConfig.tabs; let i = index" class="editor-card">
1160
1787
  <div class="editor-card-header">
1161
- <strong class="editor-card-title">Aba #{{ i+1 }}</strong>
1788
+ <strong class="editor-card-title">{{ t('editor.cards.tab', 'Aba') }} #{{ i+1 }}</strong>
1162
1789
  <button mat-icon-button (click)="moveTab(i, -1)" [disabled]="i===0"><mat-icon [praxisIcon]="'arrow_upward'"></mat-icon></button>
1163
1790
  <button mat-icon-button (click)="moveTab(i, 1)" [disabled]="i===editedConfig.tabs!.length-1"><mat-icon [praxisIcon]="'arrow_downward'"></mat-icon></button>
1164
1791
  <button mat-icon-button color="warn" (click)="removeTab(i)"><mat-icon [praxisIcon]="'delete'"></mat-icon></button>
1165
1792
  </div>
1166
1793
  <div class="editor-grid two tight">
1167
- <mat-form-field appearance="outline"><mat-label>ID</mat-label>
1168
- <input matInput [(ngModel)]="t.id" (ngModelChange)="onAppearanceChange()" />
1794
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.id', 'ID') }}</mat-label>
1795
+ <input matInput [(ngModel)]="tab.id" (ngModelChange)="onAppearanceChange()" />
1169
1796
  </mat-form-field>
1170
- <mat-form-field appearance="outline"><mat-label>Rotulo</mat-label>
1171
- <input matInput [(ngModel)]="t.textLabel" (ngModelChange)="onAppearanceChange()" />
1797
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.label', 'Rotulo') }}</mat-label>
1798
+ <input matInput [(ngModel)]="tab.textLabel" (ngModelChange)="onAppearanceChange()" />
1172
1799
  </mat-form-field>
1173
- <mat-form-field appearance="outline"><mat-label>Classe do rotulo</mat-label>
1174
- <input matInput [(ngModel)]="t.labelClass" (ngModelChange)="onAppearanceChange()" />
1800
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.labelClass', 'Classe do rotulo') }}</mat-label>
1801
+ <input matInput [(ngModel)]="tab.labelClass" (ngModelChange)="onAppearanceChange()" />
1175
1802
  </mat-form-field>
1176
- <mat-form-field appearance="outline"><mat-label>Classe do conteudo</mat-label>
1177
- <input matInput [(ngModel)]="t.bodyClass" (ngModelChange)="onAppearanceChange()" />
1803
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.bodyClass', 'Classe do conteudo') }}</mat-label>
1804
+ <input matInput [(ngModel)]="tab.bodyClass" (ngModelChange)="onAppearanceChange()" />
1178
1805
  </mat-form-field>
1179
- <mat-form-field appearance="outline"><mat-label>aria-label</mat-label>
1180
- <input matInput [(ngModel)]="t.ariaLabel" (ngModelChange)="onAppearanceChange()" />
1806
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.ariaLabel', 'aria-label') }}</mat-label>
1807
+ <input matInput [(ngModel)]="tab.ariaLabel" (ngModelChange)="onAppearanceChange()" />
1181
1808
  </mat-form-field>
1182
- <mat-form-field appearance="outline"><mat-label>aria-labelledby</mat-label>
1183
- <input matInput [(ngModel)]="t.ariaLabelledby" (ngModelChange)="onAppearanceChange()" />
1809
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.ariaLabelledby', 'aria-labelledby') }}</mat-label>
1810
+ <input matInput [(ngModel)]="tab.ariaLabelledby" (ngModelChange)="onAppearanceChange()" />
1184
1811
  </mat-form-field>
1185
1812
  </div>
1186
- <mat-slide-toggle [(ngModel)]="t.disabled" (ngModelChange)="onAppearanceChange()">Desativada</mat-slide-toggle>
1813
+ <mat-slide-toggle [(ngModel)]="tab.disabled" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.disabled', 'Desativada') }}</mat-slide-toggle>
1187
1814
 
1188
1815
  <!-- Widgets (componentes dinâmicos) -->
1189
1816
  <div class="editor-divider editor-grid">
1190
1817
  <div class="editor-row">
1191
1818
  <mat-form-field appearance="outline" class="editor-field-min">
1192
- <mat-label>Adicionar componente</mat-label>
1819
+ <mat-label>{{ t('editor.fields.addComponent', 'Adicionar componente') }}</mat-label>
1193
1820
  <select matNativeControl [(ngModel)]="selectedTabWidgetId[i]">
1194
- <option [ngValue]="''">(selecione)</option>
1821
+ <option [ngValue]="''">{{ t('editor.fields.selectPlaceholder', '(selecione)') }}</option>
1195
1822
  <option *ngFor="let c of componentOptions" [ngValue]="c.id">{{ c.friendlyName || c.id }}</option>
1196
1823
  </select>
1197
1824
  </mat-form-field>
1198
- <button mat-stroked-button (click)="addWidgetToTab(i)"><mat-icon [praxisIcon]="'add'"></mat-icon>Adicionar</button>
1825
+ <button mat-stroked-button (click)="addWidgetToTab(i)"><mat-icon [praxisIcon]="'add'"></mat-icon>{{ t('editor.actions.add', 'Adicionar') }}</button>
1199
1826
  <span class="editor-spacer"></span>
1200
1827
  <mat-form-field appearance="outline" class="editor-field-240">
1201
- <mat-label>Recurso (resourcePath)</mat-label>
1202
- <input matInput [(ngModel)]="quickResourcePathTab[i]" placeholder="ex.: usuarios" />
1828
+ <mat-label>{{ t('editor.fields.resourcePath', 'Recurso (resourcePath)') }}</mat-label>
1829
+ <input matInput [(ngModel)]="quickResourcePathTab[i]" [placeholder]="t('editor.fields.placeholder.resourcePath', 'ex.: usuarios')" />
1203
1830
  </mat-form-field>
1204
- <button mat-button (click)="addPresetToTab(i, 'form')"><mat-icon [praxisIcon]="'description'"></mat-icon>Form</button>
1205
- <button mat-button (click)="addPresetToTab(i, 'table')"><mat-icon [praxisIcon]="'grid_on'"></mat-icon>Tabela</button>
1206
- <button mat-button (click)="addPresetToTab(i, 'crud')"><mat-icon [praxisIcon]="'dynamic_form'"></mat-icon>CRUD</button>
1831
+ <button mat-button (click)="addPresetToTab(i, 'form')"><mat-icon [praxisIcon]="'description'"></mat-icon>{{ t('editor.templates.form', 'Form') }}</button>
1832
+ <button mat-button (click)="addPresetToTab(i, 'table')"><mat-icon [praxisIcon]="'grid_on'"></mat-icon>{{ t('editor.templates.table', 'Tabela') }}</button>
1833
+ <button mat-button (click)="addPresetToTab(i, 'crud')"><mat-icon [praxisIcon]="'dynamic_form'"></mat-icon>{{ t('editor.templates.crud', 'CRUD') }}</button>
1207
1834
  </div>
1208
1835
 
1209
- <div *ngIf="t.widgets?.length" class="editor-grid" cdkDropList [cdkDropListData]="t.widgets || []" (cdkDropListDropped)="onTabWidgetDrop(i, $event)">
1210
- <div *ngFor="let w of t.widgets; let wi = index" cdkDrag class="editor-card dashed">
1836
+ <div *ngIf="tab.widgets?.length" class="editor-grid" cdkDropList [cdkDropListData]="tab.widgets || []" (cdkDropListDropped)="onTabWidgetDrop(i, $event)">
1837
+ <div *ngFor="let w of tab.widgets; let wi = index" cdkDrag class="editor-card dashed">
1211
1838
  <div class="editor-card-header">
1212
- <button mat-icon-button cdkDragHandle matTooltip="Arrastar para reordenar" aria-label="Arrastar para reordenar">
1839
+ <button mat-icon-button cdkDragHandle [matTooltip]="t('editor.actions.dragToReorder', 'Arrastar para reordenar')" [attr.aria-label]="t('editor.actions.dragToReorder', 'Arrastar para reordenar')">
1213
1840
  <mat-icon [praxisIcon]="'drag_indicator'"></mat-icon>
1214
1841
  </button>
1215
1842
  <strong class="editor-card-title">{{ getCompName(w.id) }}</strong>
1216
- <button mat-icon-button color="warn" (click)="removeWidgetFromTab(i, wi)" matTooltip="Remover componente" aria-label="Remover componente"><mat-icon [praxisIcon]="'delete'"></mat-icon></button>
1843
+ <button mat-icon-button color="warn" (click)="removeWidgetFromTab(i, wi)" [matTooltip]="t('editor.actions.removeComponent', 'Remover componente')" [attr.aria-label]="t('editor.actions.removeComponent', 'Remover componente')"><mat-icon [praxisIcon]="'delete'"></mat-icon></button>
1217
1844
  </div>
1218
1845
  <div class="editor-grid two tight">
1219
1846
  <mat-form-field appearance="outline">
1220
- <mat-label>Inputs (JSON)</mat-label>
1847
+ <mat-label>{{ t('editor.fields.inputsJson', 'Inputs (JSON)') }}</mat-label>
1221
1848
  <textarea matInput rows="4" [ngModel]="stringify(w.inputs)" (ngModelChange)="updateWidgetInputsTab(i, wi, $event)"></textarea>
1222
1849
  </mat-form-field>
1223
1850
  <mat-form-field appearance="outline">
1224
- <mat-label>Outputs (JSON)</mat-label>
1851
+ <mat-label>{{ t('editor.fields.outputsJson', 'Outputs (JSON)') }}</mat-label>
1225
1852
  <textarea matInput rows="4" [ngModel]="stringify(w.outputs)" (ngModelChange)="updateWidgetOutputsTab(i, wi, $event)"></textarea>
1226
1853
  </mat-form-field>
1227
1854
  </div>
@@ -1230,82 +1857,81 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
1230
1857
  </div>
1231
1858
  </div>
1232
1859
  </div>
1233
- <ng-template #noTabs><em class="editor-muted">Nenhuma aba definida.</em></ng-template>
1860
+ <ng-template #noTabs><em class="editor-muted">{{ t('editor.empty.noTabs', 'Nenhuma aba definida.') }}</em></ng-template>
1234
1861
  </div>
1235
1862
  </mat-tab>
1236
1863
 
1237
- <mat-tab label="Acessibilidade">
1864
+ <mat-tab [label]="t('editor.tabs.accessibility', 'Acessibilidade')">
1238
1865
  <div class="editor-section">
1239
1866
  <div class="editor-row">
1240
- <mat-slide-toggle [(ngModel)]="accessibility.highContrast" (ngModelChange)="onAppearanceChange()">Alto contraste</mat-slide-toggle>
1241
- <mat-slide-toggle [(ngModel)]="accessibility.reduceMotion" (ngModelChange)="onAppearanceChange()">Reduzir movimento</mat-slide-toggle>
1867
+ <mat-slide-toggle [(ngModel)]="accessibility.highContrast" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.highContrast', 'Alto contraste') }}</mat-slide-toggle>
1868
+ <mat-slide-toggle [(ngModel)]="accessibility.reduceMotion" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.reduceMotion', 'Reduzir movimento') }}</mat-slide-toggle>
1242
1869
  </div>
1243
1870
  </div>
1244
1871
  </mat-tab>
1245
1872
 
1246
- <mat-tab label="Links">
1873
+ <mat-tab [label]="t('editor.tabs.links', 'Links')" [disabled]="primaryMode !== 'nav'">
1247
1874
  <div class="editor-section">
1248
- <button mat-stroked-button color="primary" (click)="addLink()"><mat-icon [praxisIcon]="'add_link'"></mat-icon>Adicionar link</button>
1875
+ <button mat-stroked-button color="primary" (click)="addLink()"><mat-icon [praxisIcon]="'add_link'"></mat-icon>{{ t('editor.actions.addLink', 'Adicionar link') }}</button>
1249
1876
  <div *ngIf="nav.links?.length; else noLinks" class="editor-grid">
1250
1877
  <div *ngFor="let l of nav.links; let i = index" class="editor-card">
1251
1878
  <div class="editor-card-header">
1252
- <strong class="editor-card-title">Link #{{ i+1 }}</strong>
1879
+ <strong class="editor-card-title">{{ t('editor.cards.link', 'Link') }} #{{ i+1 }}</strong>
1253
1880
  <button mat-icon-button (click)="moveLink(i, -1)" [disabled]="i===0"><mat-icon [praxisIcon]="'arrow_upward'"></mat-icon></button>
1254
1881
  <button mat-icon-button (click)="moveLink(i, 1)" [disabled]="i===nav.links!.length-1"><mat-icon [praxisIcon]="'arrow_downward'"></mat-icon></button>
1255
1882
  <button mat-icon-button color="warn" (click)="removeLink(i)"><mat-icon [praxisIcon]="'delete'"></mat-icon></button>
1256
1883
  </div>
1257
1884
  <div class="editor-grid two tight">
1258
- <mat-form-field appearance="outline"><mat-label>ID</mat-label>
1885
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.id', 'ID') }}</mat-label>
1259
1886
  <input matInput [(ngModel)]="l.id" (ngModelChange)="onAppearanceChange()" />
1260
1887
  </mat-form-field>
1261
- <mat-form-field appearance="outline"><mat-label>Rotulo</mat-label>
1888
+ <mat-form-field appearance="outline"><mat-label>{{ t('editor.fields.label', 'Rotulo') }}</mat-label>
1262
1889
  <input matInput [(ngModel)]="l.label" (ngModelChange)="onAppearanceChange()" />
1263
1890
  </mat-form-field>
1264
1891
  </div>
1265
1892
  <div class="editor-row">
1266
- <mat-slide-toggle [(ngModel)]="l.active" (ngModelChange)="onAppearanceChange()">Ativo</mat-slide-toggle>
1267
- <mat-slide-toggle [(ngModel)]="l.disabled" (ngModelChange)="onAppearanceChange()">Desativado</mat-slide-toggle>
1268
- <mat-slide-toggle [(ngModel)]="l.disableRipple" (ngModelChange)="onAppearanceChange()">Sem ripple</mat-slide-toggle>
1269
- <mat-slide-toggle [(ngModel)]="l.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">Indicador ajustado ao conteudo</mat-slide-toggle>
1893
+ <mat-slide-toggle [(ngModel)]="l.disabled" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.linkDisabled', 'Desativado') }}</mat-slide-toggle>
1894
+ <mat-slide-toggle [(ngModel)]="l.disableRipple" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.disableRipple', 'Sem ripple') }}</mat-slide-toggle>
1895
+ <mat-slide-toggle [(ngModel)]="l.fitInkBarToContent" (ngModelChange)="onAppearanceChange()">{{ t('editor.toggles.fitInkBarToContent', 'Indicador ajustado ao conteudo') }}</mat-slide-toggle>
1270
1896
  </div>
1271
1897
 
1272
1898
  <!-- Widgets para Links -->
1273
1899
  <div class="editor-divider editor-grid">
1274
1900
  <div class="editor-row">
1275
1901
  <mat-form-field appearance="outline" class="editor-field-min">
1276
- <mat-label>Adicionar componente</mat-label>
1902
+ <mat-label>{{ t('editor.fields.addComponent', 'Adicionar componente') }}</mat-label>
1277
1903
  <select matNativeControl [(ngModel)]="selectedLinkWidgetId[i]">
1278
- <option [ngValue]="''">(selecione)</option>
1904
+ <option [ngValue]="''">{{ t('editor.fields.selectPlaceholder', '(selecione)') }}</option>
1279
1905
  <option *ngFor="let c of componentOptions" [ngValue]="c.id">{{ c.friendlyName || c.id }}</option>
1280
1906
  </select>
1281
1907
  </mat-form-field>
1282
- <button mat-stroked-button (click)="addWidgetToLink(i)"><mat-icon [praxisIcon]="'add'"></mat-icon>Adicionar</button>
1908
+ <button mat-stroked-button (click)="addWidgetToLink(i)"><mat-icon [praxisIcon]="'add'"></mat-icon>{{ t('editor.actions.add', 'Adicionar') }}</button>
1283
1909
  <span class="editor-spacer"></span>
1284
1910
  <mat-form-field appearance="outline" class="editor-field-240">
1285
- <mat-label>Recurso (resourcePath)</mat-label>
1286
- <input matInput [(ngModel)]="quickResourcePathLink[i]" placeholder="ex.: usuarios" />
1911
+ <mat-label>{{ t('editor.fields.resourcePath', 'Recurso (resourcePath)') }}</mat-label>
1912
+ <input matInput [(ngModel)]="quickResourcePathLink[i]" [placeholder]="t('editor.fields.placeholder.resourcePath', 'ex.: usuarios')" />
1287
1913
  </mat-form-field>
1288
- <button mat-button (click)="addPresetToLink(i, 'form')"><mat-icon [praxisIcon]="'description'"></mat-icon>Form</button>
1289
- <button mat-button (click)="addPresetToLink(i, 'table')"><mat-icon [praxisIcon]="'grid_on'"></mat-icon>Tabela</button>
1290
- <button mat-button (click)="addPresetToLink(i, 'crud')"><mat-icon [praxisIcon]="'dynamic_form'"></mat-icon>CRUD</button>
1914
+ <button mat-button (click)="addPresetToLink(i, 'form')"><mat-icon [praxisIcon]="'description'"></mat-icon>{{ t('editor.templates.form', 'Form') }}</button>
1915
+ <button mat-button (click)="addPresetToLink(i, 'table')"><mat-icon [praxisIcon]="'grid_on'"></mat-icon>{{ t('editor.templates.table', 'Tabela') }}</button>
1916
+ <button mat-button (click)="addPresetToLink(i, 'crud')"><mat-icon [praxisIcon]="'dynamic_form'"></mat-icon>{{ t('editor.templates.crud', 'CRUD') }}</button>
1291
1917
  </div>
1292
1918
 
1293
1919
  <div *ngIf="l.widgets?.length" class="editor-grid" cdkDropList [cdkDropListData]="l.widgets || []" (cdkDropListDropped)="onLinkWidgetDrop(i, $event)">
1294
1920
  <div *ngFor="let w of l.widgets; let wi = index" cdkDrag class="editor-card dashed">
1295
1921
  <div class="editor-card-header">
1296
- <button mat-icon-button cdkDragHandle matTooltip="Arrastar para reordenar" aria-label="Arrastar para reordenar">
1922
+ <button mat-icon-button cdkDragHandle [matTooltip]="t('editor.actions.dragToReorder', 'Arrastar para reordenar')" [attr.aria-label]="t('editor.actions.dragToReorder', 'Arrastar para reordenar')">
1297
1923
  <mat-icon [praxisIcon]="'drag_indicator'"></mat-icon>
1298
1924
  </button>
1299
1925
  <strong class="editor-card-title">{{ getCompName(w.id) }}</strong>
1300
- <button mat-icon-button color="warn" (click)="removeWidgetFromLink(i, wi)" matTooltip="Remover componente" aria-label="Remover componente"><mat-icon [praxisIcon]="'delete'"></mat-icon></button>
1926
+ <button mat-icon-button color="warn" (click)="removeWidgetFromLink(i, wi)" [matTooltip]="t('editor.actions.removeComponent', 'Remover componente')" [attr.aria-label]="t('editor.actions.removeComponent', 'Remover componente')"><mat-icon [praxisIcon]="'delete'"></mat-icon></button>
1301
1927
  </div>
1302
1928
  <div class="editor-grid two tight">
1303
1929
  <mat-form-field appearance="outline">
1304
- <mat-label>Inputs (JSON)</mat-label>
1930
+ <mat-label>{{ t('editor.fields.inputsJson', 'Inputs (JSON)') }}</mat-label>
1305
1931
  <textarea matInput rows="4" [ngModel]="stringify(w.inputs)" (ngModelChange)="updateWidgetInputsLink(i, wi, $event)"></textarea>
1306
1932
  </mat-form-field>
1307
1933
  <mat-form-field appearance="outline">
1308
- <mat-label>Outputs (JSON)</mat-label>
1934
+ <mat-label>{{ t('editor.fields.outputsJson', 'Outputs (JSON)') }}</mat-label>
1309
1935
  <textarea matInput rows="4" [ngModel]="stringify(w.outputs)" (ngModelChange)="updateWidgetOutputsLink(i, wi, $event)"></textarea>
1310
1936
  </mat-form-field>
1311
1937
  </div>
@@ -1314,26 +1940,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
1314
1940
  </div>
1315
1941
  </div>
1316
1942
  </div>
1317
- <ng-template #noLinks><em class="editor-muted">Nenhum link definido.</em></ng-template>
1943
+ <ng-template #noLinks><em class="editor-muted">{{ t('editor.empty.noLinks', 'Nenhum link definido.') }}</em></ng-template>
1318
1944
  </div>
1319
1945
  </mat-tab>
1320
1946
  </mat-tab-group>
1321
- `, styles: [":host{display:block;color:var(--md-sys-color-on-surface)}.editor-tabs{--editor-surface: var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface));--editor-border: 1px solid var(--md-sys-color-outline-variant);--editor-radius: 12px;--editor-muted: var(--md-sys-color-on-surface-variant)}.editor-section{padding:12px;display:grid;gap:12px;background:var(--editor-surface);border:var(--editor-border);border-radius:var(--editor-radius)}.editor-section-lg{gap:16px}.editor-row{display:flex;gap:10px;flex-wrap:wrap;align-items:center}.editor-grid{display:grid;gap:12px}.editor-grid.two{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-grid.tight{gap:8px}.editor-toolbar{display:flex;gap:8px;flex-wrap:wrap;align-items:center}.editor-muted{color:var(--editor-muted)}.editor-title{margin:6px 0 8px;font-size:1rem}.editor-title-row{display:flex;align-items:center;gap:8px}.editor-code{white-space:pre-wrap;background:var(--md-sys-color-surface-container);padding:8px;border-radius:6px;border:1px solid var(--md-sys-color-outline-variant)}.editor-card{border:1px solid var(--md-sys-color-outline-variant);padding:10px;border-radius:10px;display:grid;gap:8px;background:var(--md-sys-color-surface)}.editor-card.dashed{border-style:dashed}.editor-card-header{display:flex;align-items:center;gap:8px}.editor-card-title{flex:1;font-weight:600}.editor-divider{border-top:1px solid var(--md-sys-color-outline-variant);padding-top:8px}.editor-spacer{flex:1}.editor-field-min{min-width:260px}.editor-section .editor-field-min{width:260px;max-width:260px}.editor-field-240{width:240px}.editor-section .editor-field-240{width:240px;max-width:240px}.editor-json{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.editor-section .mat-mdc-form-field{width:100%;max-width:520px;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var(--md-sys-color-outline-variant);--mdc-outlined-text-field-hover-outline-color: var(--md-sys-color-outline);--mdc-outlined-text-field-focus-outline-color: var(--md-sys-color-primary);--mdc-outlined-text-field-error-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-focus-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-hover-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-label-text-color: var(--md-sys-color-on-surface-variant);--mdc-outlined-text-field-input-text-color: var(--md-sys-color-on-surface);--mdc-outlined-text-field-supporting-text-color: var(--md-sys-color-on-surface-variant)}.editor-section .mat-mdc-form-field.full{max-width:none}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;margin-right:-4px}.help-icon-button mat-icon{font-size:18px;width:18px;height:18px}.mat-mdc-form-field-icon-suffix{align-self:center}\n"] }]
1947
+ </div>
1948
+ `, styles: [":host{display:block;color:var(--md-sys-color-on-surface)}.editor-shell{display:grid;gap:12px}.editor-topbar{display:flex;align-items:start;justify-content:flex-start}.editor-mode-field{width:min(320px,100%)}.editor-diagnostics{display:grid;gap:8px}.editor-diagnostics-title{font-size:.95rem;font-weight:600;color:var(--md-sys-color-on-surface)}.editor-diagnostic{display:grid;gap:4px;padding:10px 12px;border-radius:10px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface)}.editor-diagnostic code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;color:var(--md-sys-color-on-surface-variant)}.editor-diagnostic.level-error{background:var(--md-sys-color-error-container);color:var(--md-sys-color-on-error-container);border-color:var(--md-sys-color-error)}.editor-diagnostic.level-warning{background:var(--md-sys-color-tertiary-container, var(--md-sys-color-surface-container));color:var(--md-sys-color-on-tertiary-container, var(--md-sys-color-on-surface));border-color:var(--md-sys-color-outline-variant)}.editor-tabs{--editor-surface: var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface));--editor-border: 1px solid var(--md-sys-color-outline-variant);--editor-radius: 12px;--editor-muted: var(--md-sys-color-on-surface-variant)}.editor-section{padding:12px;display:grid;gap:12px;background:var(--editor-surface);border:var(--editor-border);border-radius:var(--editor-radius)}.editor-section-lg{gap:16px}.editor-row{display:flex;gap:10px;flex-wrap:wrap;align-items:center}.editor-grid{display:grid;gap:12px}.editor-grid.two{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.editor-grid.tight{gap:8px}.editor-toolbar{display:flex;gap:8px;flex-wrap:wrap;align-items:center}.editor-muted{color:var(--editor-muted)}.editor-title{margin:6px 0 8px;font-size:1rem}.editor-title-row{display:flex;align-items:center;gap:8px}.editor-code{white-space:pre-wrap;background:var(--md-sys-color-surface-container);padding:8px;border-radius:6px;border:1px solid var(--md-sys-color-outline-variant)}.editor-card{border:1px solid var(--md-sys-color-outline-variant);padding:10px;border-radius:10px;display:grid;gap:8px;background:var(--md-sys-color-surface)}.editor-card.dashed{border-style:dashed}.editor-card-header{display:flex;align-items:center;gap:8px}.editor-card-title{flex:1;font-weight:600}.editor-divider{border-top:1px solid var(--md-sys-color-outline-variant);padding-top:8px}.editor-spacer{flex:1}.editor-field-min{min-width:260px}.editor-section .editor-field-min{width:260px;max-width:260px}.editor-field-240{width:240px}.editor-section .editor-field-240{width:240px;max-width:240px}.editor-json{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.editor-section .mat-mdc-form-field{width:100%;max-width:520px;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var(--md-sys-color-outline-variant);--mdc-outlined-text-field-hover-outline-color: var(--md-sys-color-outline);--mdc-outlined-text-field-focus-outline-color: var(--md-sys-color-primary);--mdc-outlined-text-field-error-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-focus-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-hover-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-label-text-color: var(--md-sys-color-on-surface-variant);--mdc-outlined-text-field-input-text-color: var(--md-sys-color-on-surface);--mdc-outlined-text-field-supporting-text-color: var(--md-sys-color-on-surface-variant)}.editor-section .mat-mdc-form-field.full{max-width:none}.help-icon-button{--mdc-icon-button-state-layer-size: 28px;--mdc-icon-button-icon-size: 18px;width:28px;height:28px;padding:0;display:inline-flex;align-items:center;justify-content:center;margin-right:-4px}.help-icon-button mat-icon{font-size:18px;width:18px;height:18px}.mat-mdc-form-field-icon-suffix{align-self:center}\n"] }]
1322
1949
  }], ctorParameters: () => [{ type: undefined, decorators: [{
1323
1950
  type: Inject,
1324
1951
  args: [SETTINGS_PANEL_DATA]
1325
1952
  }] }, { type: i1.ComponentMetadataRegistry }] });
1326
1953
 
1327
1954
  class TabsQuickSetupComponent {
1955
+ i18n = inject(PraxisI18nService);
1956
+ document;
1328
1957
  model = { mode: 'group', labels: [], dynamicHeight: true, stretchTabs: true };
1329
1958
  initial = { ...this.model };
1330
1959
  newLabel = '';
1331
1960
  isDirty$ = new BehaviorSubject(false);
1332
1961
  isValid$ = new BehaviorSubject(false);
1333
1962
  isBusy$ = new BehaviorSubject(false);
1963
+ t(key, fallback) {
1964
+ return this.i18n.t(key, undefined, fallback, PRAXIS_TABS_I18N_NAMESPACE);
1965
+ }
1334
1966
  constructor(data) {
1335
- // Optionally seed from existing config
1336
- const cfg = data?.config || undefined;
1967
+ const incomingDocument = normalizeTabsAuthoringDocument(data?.document ?? createTabsAuthoringDocument({ config: {} }));
1968
+ this.document = structuredClone(incomingDocument);
1969
+ const cfg = this.document.config;
1337
1970
  if (cfg) {
1338
1971
  if (cfg.tabs?.length) {
1339
1972
  this.model.mode = 'group';
@@ -1400,50 +2033,56 @@ class TabsQuickSetupComponent {
1400
2033
  },
1401
2034
  };
1402
2035
  }
2036
+ buildOutputDocument() {
2037
+ return createTabsAuthoringDocument({
2038
+ config: this.buildConfig(),
2039
+ bindings: this.document?.bindings,
2040
+ });
2041
+ }
1403
2042
  getSettingsValue() {
1404
- return { config: this.buildConfig() };
2043
+ return this.buildOutputDocument();
1405
2044
  }
1406
2045
  onSave() {
1407
- return { config: this.buildConfig() };
2046
+ return this.buildOutputDocument();
1408
2047
  }
1409
2048
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TabsQuickSetupComponent, deps: [{ token: SETTINGS_PANEL_DATA }], target: i0.ɵɵFactoryTarget.Component });
1410
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: TabsQuickSetupComponent, isStandalone: true, selector: "praxis-tabs-quick-setup", ngImport: i0, template: `
2049
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: TabsQuickSetupComponent, isStandalone: true, selector: "praxis-tabs-quick-setup", providers: [providePraxisI18nConfig(PRAXIS_TABS_I18N_CONFIG)], ngImport: i0, template: `
1411
2050
  <div class="setup-root">
1412
2051
  <div class="setup-row">
1413
2052
  <button mat-stroked-button [color]="model.mode==='group' ? 'primary' : undefined" (click)="setMode('group')">
1414
2053
  <mat-icon [praxisIcon]="'tab'"></mat-icon>
1415
- Grupo de abas
2054
+ {{ t('quickSetup.mode.group', 'Grupo de abas') }}
1416
2055
  </button>
1417
2056
  <button mat-stroked-button [color]="model.mode==='nav' ? 'primary' : undefined" (click)="setMode('nav')">
1418
2057
  <mat-icon [praxisIcon]="'segment'"></mat-icon>
1419
- Navegacao por links
2058
+ {{ t('quickSetup.mode.nav', 'Navegacao por links') }}
1420
2059
  </button>
1421
2060
  </div>
1422
2061
 
1423
2062
  <div class="setup-grid">
1424
2063
  <mat-form-field appearance="outline">
1425
- <mat-label>Adicionar rotulo</mat-label>
1426
- <input matInput [(ngModel)]="newLabel" (keyup.enter)="addLabel()" placeholder="Ex.: Dados Gerais" />
2064
+ <mat-label>{{ t('editor.fields.addLabel', 'Adicionar rotulo') }}</mat-label>
2065
+ <input matInput [(ngModel)]="newLabel" (keyup.enter)="addLabel()" [placeholder]="t('quickSetup.fields.addLabelPlaceholder', 'Ex.: Dados Gerais')" />
1427
2066
  </mat-form-field>
1428
2067
  <button mat-flat-button color="primary" (click)="addLabel()">
1429
2068
  <mat-icon [praxisIcon]="'add'"></mat-icon>
1430
- Adicionar
2069
+ {{ t('editor.actions.add', 'Adicionar') }}
1431
2070
  </button>
1432
2071
  </div>
1433
2072
 
1434
2073
  <div *ngIf="model.labels.length; else emptyLabels" class="setup-row wrap">
1435
2074
  <div *ngFor="let l of model.labels; let i = index" class="chip">
1436
2075
  <span>{{ l }}</span>
1437
- <button mat-icon-button (click)="removeLabel(i)" aria-label="Remover rotulo"><mat-icon [praxisIcon]="'close'"></mat-icon></button>
2076
+ <button mat-icon-button (click)="removeLabel(i)" [attr.aria-label]="t('editor.actions.removeLabel', 'Remover rotulo')"><mat-icon [praxisIcon]="'close'"></mat-icon></button>
1438
2077
  </div>
1439
2078
  </div>
1440
2079
  <ng-template #emptyLabels>
1441
- <em class="hint">Adicione um ou mais rotulos para criar as abas.</em>
2080
+ <em class="hint">{{ t('quickSetup.hints.emptyLabels', 'Adicione um ou mais rotulos para criar as abas.') }}</em>
1442
2081
  </ng-template>
1443
2082
 
1444
2083
  <div class="setup-row">
1445
- <mat-slide-toggle [(ngModel)]="model.dynamicHeight">Altura dinamica</mat-slide-toggle>
1446
- <mat-slide-toggle [(ngModel)]="model.stretchTabs">Esticar abas</mat-slide-toggle>
2084
+ <mat-slide-toggle [(ngModel)]="model.dynamicHeight" (ngModelChange)="updateState()">{{ t('editor.toggles.dynamicHeight', 'Altura dinamica') }}</mat-slide-toggle>
2085
+ <mat-slide-toggle [(ngModel)]="model.stretchTabs" (ngModelChange)="updateState()">{{ t('editor.toggles.stretchTabs', 'Esticar abas') }}</mat-slide-toggle>
1447
2086
  </div>
1448
2087
  </div>
1449
2088
  `, isInline: true, styles: [":host{display:block;color:var(--md-sys-color-on-surface)}.setup-root{display:grid;gap:12px;padding:8px;background:var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface));border:1px solid var(--md-sys-color-outline-variant);border-radius:12px}.setup-row{display:flex;gap:12px;align-items:center;flex-wrap:wrap}.setup-row.wrap{flex-wrap:wrap}.setup-grid{display:grid;grid-template-columns:1fr auto;gap:8px;align-items:start}.setup-root .mat-mdc-form-field{width:100%;max-width:520px;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var(--md-sys-color-outline-variant);--mdc-outlined-text-field-hover-outline-color: var(--md-sys-color-outline);--mdc-outlined-text-field-focus-outline-color: var(--md-sys-color-primary);--mdc-outlined-text-field-error-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-focus-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-hover-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-label-text-color: var(--md-sys-color-on-surface-variant);--mdc-outlined-text-field-input-text-color: var(--md-sys-color-on-surface);--mdc-outlined-text-field-supporting-text-color: var(--md-sys-color-on-surface-variant)}.chip{display:inline-flex;align-items:center;gap:6px;padding:4px 8px;border:1px solid var(--md-sys-color-outline-variant);border-radius:16px;background:var(--md-sys-color-surface)}.hint{color:var(--md-sys-color-on-surface-variant)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3.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: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i6.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6$1.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i9.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }] });
@@ -1459,43 +2098,43 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
1459
2098
  PraxisIconDirective,
1460
2099
  MatButtonModule,
1461
2100
  MatSlideToggleModule,
1462
- ], template: `
2101
+ ], providers: [providePraxisI18nConfig(PRAXIS_TABS_I18N_CONFIG)], template: `
1463
2102
  <div class="setup-root">
1464
2103
  <div class="setup-row">
1465
2104
  <button mat-stroked-button [color]="model.mode==='group' ? 'primary' : undefined" (click)="setMode('group')">
1466
2105
  <mat-icon [praxisIcon]="'tab'"></mat-icon>
1467
- Grupo de abas
2106
+ {{ t('quickSetup.mode.group', 'Grupo de abas') }}
1468
2107
  </button>
1469
2108
  <button mat-stroked-button [color]="model.mode==='nav' ? 'primary' : undefined" (click)="setMode('nav')">
1470
2109
  <mat-icon [praxisIcon]="'segment'"></mat-icon>
1471
- Navegacao por links
2110
+ {{ t('quickSetup.mode.nav', 'Navegacao por links') }}
1472
2111
  </button>
1473
2112
  </div>
1474
2113
 
1475
2114
  <div class="setup-grid">
1476
2115
  <mat-form-field appearance="outline">
1477
- <mat-label>Adicionar rotulo</mat-label>
1478
- <input matInput [(ngModel)]="newLabel" (keyup.enter)="addLabel()" placeholder="Ex.: Dados Gerais" />
2116
+ <mat-label>{{ t('editor.fields.addLabel', 'Adicionar rotulo') }}</mat-label>
2117
+ <input matInput [(ngModel)]="newLabel" (keyup.enter)="addLabel()" [placeholder]="t('quickSetup.fields.addLabelPlaceholder', 'Ex.: Dados Gerais')" />
1479
2118
  </mat-form-field>
1480
2119
  <button mat-flat-button color="primary" (click)="addLabel()">
1481
2120
  <mat-icon [praxisIcon]="'add'"></mat-icon>
1482
- Adicionar
2121
+ {{ t('editor.actions.add', 'Adicionar') }}
1483
2122
  </button>
1484
2123
  </div>
1485
2124
 
1486
2125
  <div *ngIf="model.labels.length; else emptyLabels" class="setup-row wrap">
1487
2126
  <div *ngFor="let l of model.labels; let i = index" class="chip">
1488
2127
  <span>{{ l }}</span>
1489
- <button mat-icon-button (click)="removeLabel(i)" aria-label="Remover rotulo"><mat-icon [praxisIcon]="'close'"></mat-icon></button>
2128
+ <button mat-icon-button (click)="removeLabel(i)" [attr.aria-label]="t('editor.actions.removeLabel', 'Remover rotulo')"><mat-icon [praxisIcon]="'close'"></mat-icon></button>
1490
2129
  </div>
1491
2130
  </div>
1492
2131
  <ng-template #emptyLabels>
1493
- <em class="hint">Adicione um ou mais rotulos para criar as abas.</em>
2132
+ <em class="hint">{{ t('quickSetup.hints.emptyLabels', 'Adicione um ou mais rotulos para criar as abas.') }}</em>
1494
2133
  </ng-template>
1495
2134
 
1496
2135
  <div class="setup-row">
1497
- <mat-slide-toggle [(ngModel)]="model.dynamicHeight">Altura dinamica</mat-slide-toggle>
1498
- <mat-slide-toggle [(ngModel)]="model.stretchTabs">Esticar abas</mat-slide-toggle>
2136
+ <mat-slide-toggle [(ngModel)]="model.dynamicHeight" (ngModelChange)="updateState()">{{ t('editor.toggles.dynamicHeight', 'Altura dinamica') }}</mat-slide-toggle>
2137
+ <mat-slide-toggle [(ngModel)]="model.stretchTabs" (ngModelChange)="updateState()">{{ t('editor.toggles.stretchTabs', 'Esticar abas') }}</mat-slide-toggle>
1499
2138
  </div>
1500
2139
  </div>
1501
2140
  `, styles: [":host{display:block;color:var(--md-sys-color-on-surface)}.setup-root{display:grid;gap:12px;padding:8px;background:var(--md-sys-color-surface-container-lowest, var(--md-sys-color-surface));border:1px solid var(--md-sys-color-outline-variant);border-radius:12px}.setup-row{display:flex;gap:12px;align-items:center;flex-wrap:wrap}.setup-row.wrap{flex-wrap:wrap}.setup-grid{display:grid;grid-template-columns:1fr auto;gap:8px;align-items:start}.setup-root .mat-mdc-form-field{width:100%;max-width:520px;--mdc-outlined-text-field-container-height: 48px;--mdc-outlined-text-field-outline-color: var(--md-sys-color-outline-variant);--mdc-outlined-text-field-hover-outline-color: var(--md-sys-color-outline);--mdc-outlined-text-field-focus-outline-color: var(--md-sys-color-primary);--mdc-outlined-text-field-error-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-focus-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-error-hover-outline-color: var(--md-sys-color-error);--mdc-outlined-text-field-label-text-color: var(--md-sys-color-on-surface-variant);--mdc-outlined-text-field-input-text-color: var(--md-sys-color-on-surface);--mdc-outlined-text-field-supporting-text-color: var(--md-sys-color-on-surface-variant)}.chip{display:inline-flex;align-items:center;gap:6px;padding:4px 8px;border:1px solid var(--md-sys-color-outline-variant);border-radius:16px;background:var(--md-sys-color-surface)}.hint{color:var(--md-sys-color-on-surface-variant)}\n"] }]
@@ -1717,6 +2356,7 @@ class TabsAiAdapter extends BaseAiAdapter {
1717
2356
  }
1718
2357
 
1719
2358
  class PraxisTabs {
2359
+ i18n = inject(PraxisI18nService);
1720
2360
  settings = inject(SettingsPanelService);
1721
2361
  storage = inject(ASYNC_CONFIG_STORAGE);
1722
2362
  snack = inject(MatSnackBar);
@@ -1749,6 +2389,7 @@ class PraxisTabs {
1749
2389
  groupLoaded = new Set();
1750
2390
  navLoaded = new Set();
1751
2391
  destroy$ = new Subject();
2392
+ widgetDefinitionCache = new WeakMap();
1752
2393
  ngOnInit() {
1753
2394
  this.syncSelectionFromConfig();
1754
2395
  // Load stored config if tabsId provided
@@ -1929,31 +2570,31 @@ class PraxisTabs {
1929
2570
  }
1930
2571
  openEditor() {
1931
2572
  const key = this.storageKey() || this.tabsId || 'default';
2573
+ const initialDocument = createTabsAuthoringDocument({
2574
+ config: this.config ?? {},
2575
+ bindings: this.currentTabsBindings(),
2576
+ });
1932
2577
  const ref = this.settings.open({
1933
2578
  id: `praxis-tabs-editor:${key}`,
1934
- title: 'Configurar Tabs',
2579
+ title: this.t('settings.title', 'Configurar abas'),
1935
2580
  content: {
1936
2581
  component: PraxisTabsConfigEditor,
1937
- inputs: { config: this.config, tabsId: this.tabsId },
2582
+ inputs: { document: initialDocument },
1938
2583
  },
1939
2584
  });
1940
- // Essential preview: apply updates without closing
1941
- ref.applied$.pipe(takeUntil(this.destroy$)).subscribe((value) => {
1942
- const nextCfg = value?.config || value;
1943
- if (nextCfg) {
1944
- this.config = produce(this.config || {}, () => nextCfg);
1945
- this.persistConfig(this.config);
1946
- this.syncSelectionFromConfig();
1947
- }
1948
- });
1949
- ref.saved$.pipe(takeUntil(this.destroy$)).subscribe((value) => {
1950
- const nextCfg = value?.config || value;
1951
- if (nextCfg) {
1952
- this.config = produce(this.config || {}, () => nextCfg);
1953
- this.persistConfig(this.config);
1954
- this.syncSelectionFromConfig();
1955
- }
1956
- });
2585
+ const applyDocument = (value) => {
2586
+ const document = normalizeTabsAuthoringDocument(value);
2587
+ const plan = buildTabsApplyPlan(document, {
2588
+ currentBindings: this.currentTabsBindings(),
2589
+ currentConfig: this.config,
2590
+ }, {
2591
+ saveConfig: true,
2592
+ saveBindings: false,
2593
+ });
2594
+ this.applyTabsAuthoringPlan(plan);
2595
+ };
2596
+ ref.applied$.pipe(takeUntil(this.destroy$)).subscribe(applyDocument);
2597
+ ref.saved$.pipe(takeUntil(this.destroy$)).subscribe(applyDocument);
1957
2598
  }
1958
2599
  addEmptyTab() {
1959
2600
  const next = produce(this.config || {}, (draft) => {
@@ -1961,7 +2602,10 @@ class PraxisTabs {
1961
2602
  draft.group = { selectedIndex: 0 };
1962
2603
  if (!draft.tabs)
1963
2604
  draft.tabs = [];
1964
- draft.tabs.push({ id: `tab${(draft.tabs.length || 0) + 1}`, textLabel: 'Nova aba' });
2605
+ draft.tabs.push({
2606
+ id: `tab${(draft.tabs.length || 0) + 1}`,
2607
+ textLabel: this.t('defaults.newTabLabel', 'New Tab'),
2608
+ });
1965
2609
  draft.group.selectedIndex = (draft.tabs.length || 1) - 1;
1966
2610
  });
1967
2611
  this.config = next;
@@ -1978,31 +2622,69 @@ class PraxisTabs {
1978
2622
  }
1979
2623
  catch { }
1980
2624
  try {
1981
- this.snack.open('Preferências de Tabs redefinidas', undefined, { duration: 2000 });
2625
+ this.snack.open(this.t('settings.resetDone', 'Preferencias de abas redefinidas'), undefined, { duration: 2000 });
1982
2626
  }
1983
2627
  catch { }
1984
2628
  // keep current in-memory config; caller may choose to reload
1985
2629
  }
1986
2630
  openQuickSetup() {
1987
2631
  const key = this.storageKey() || this.tabsId || 'default';
2632
+ const initialDocument = createTabsAuthoringDocument({
2633
+ config: this.config ?? {},
2634
+ bindings: this.currentTabsBindings(),
2635
+ });
1988
2636
  const ref = this.settings.open({
1989
2637
  id: `praxis-tabs-quick-setup:${key}`,
1990
- title: 'Criar abas rapidamente',
2638
+ title: this.t('quickSetup.title', 'Criar abas rapidamente'),
1991
2639
  content: {
1992
2640
  component: TabsQuickSetupComponent,
1993
- inputs: { config: this.config },
2641
+ inputs: { document: initialDocument },
1994
2642
  },
1995
2643
  });
1996
- const apply = (value) => {
1997
- const nextCfg = value?.config || value;
1998
- if (nextCfg) {
1999
- this.config = produce(this.config || {}, () => nextCfg);
2000
- this.persistConfig(this.config);
2001
- this.syncSelectionFromConfig();
2002
- }
2644
+ const applyDocument = (value) => {
2645
+ const document = normalizeTabsAuthoringDocument(value);
2646
+ const plan = buildTabsApplyPlan(document, {
2647
+ currentBindings: this.currentTabsBindings(),
2648
+ currentConfig: this.config,
2649
+ }, {
2650
+ saveConfig: true,
2651
+ saveBindings: false,
2652
+ });
2653
+ this.applyTabsAuthoringPlan(plan);
2003
2654
  };
2004
- ref.applied$.pipe(takeUntil(this.destroy$)).subscribe(apply);
2005
- ref.saved$.pipe(takeUntil(this.destroy$)).subscribe(apply);
2655
+ ref.applied$.pipe(takeUntil(this.destroy$)).subscribe(applyDocument);
2656
+ ref.saved$.pipe(takeUntil(this.destroy$)).subscribe(applyDocument);
2657
+ }
2658
+ currentTabsBindings() {
2659
+ return {
2660
+ tabsId: this.tabsId ?? null,
2661
+ componentInstanceId: this.componentInstanceId ?? null,
2662
+ };
2663
+ }
2664
+ t(key, fallback) {
2665
+ return this.i18n.t(key, undefined, fallback, PRAXIS_TABS_I18N_NAMESPACE);
2666
+ }
2667
+ applyTabsAuthoringPlan(plan) {
2668
+ if (plan.diagnostics.some((item) => item.level === 'error')) {
2669
+ return;
2670
+ }
2671
+ this.config = plan.canonicalConfig;
2672
+ this.syncSelectionFromConfig();
2673
+ if (plan.runtime.rebuildLazyState) {
2674
+ this.groupLoaded.clear();
2675
+ this.navLoaded.clear();
2676
+ const groupIndex = this.selectedIndexSignal();
2677
+ const navIndex = this.currentNavIndex();
2678
+ if ((this.config?.tabs?.length ?? 0) > 0) {
2679
+ this.groupLoaded.add(groupIndex);
2680
+ }
2681
+ if ((this.config?.nav?.links?.length ?? 0) > 0) {
2682
+ this.navLoaded.add(navIndex);
2683
+ }
2684
+ }
2685
+ if (plan.persistence.saveConfig) {
2686
+ this.persistConfig(this.config);
2687
+ }
2006
2688
  }
2007
2689
  storageKey() {
2008
2690
  const id = this.componentKeyId();
@@ -2086,8 +2768,42 @@ class PraxisTabs {
2086
2768
  const hasLinks = !!(this.config?.nav?.links && this.config.nav.links.length > 0);
2087
2769
  return !(hasTabs || hasLinks);
2088
2770
  }
2089
- emitWidgetEvent(loc, ev) {
2090
- this.widgetEvent.emit({ ...loc, ...ev });
2771
+ trackNavLink(index, link) {
2772
+ return link.id || `${link.label || 'nav-link'}:${index}`;
2773
+ }
2774
+ trackTab(index, tab) {
2775
+ return tab.id || tab.textLabel || `tab:${index}`;
2776
+ }
2777
+ trackWidgetDefinition(index, widget) {
2778
+ return widget.childWidgetKey || widget.id || `widget:${index}`;
2779
+ }
2780
+ resolveWidgetDefinition(widget) {
2781
+ const cached = this.widgetDefinitionCache.get(widget);
2782
+ if (cached) {
2783
+ return cached;
2784
+ }
2785
+ const clone = this.cloneWidgetDefinition(widget);
2786
+ this.widgetDefinitionCache.set(widget, clone);
2787
+ return clone;
2788
+ }
2789
+ emitWidgetEvent(path, ev) {
2790
+ this.widgetEvent.emit({
2791
+ ...ev,
2792
+ path: [...path, ...(ev.path || [])],
2793
+ });
2794
+ }
2795
+ tabEventPath(tabId, tabIndex) {
2796
+ return [
2797
+ { kind: 'tabs', id: this.tabsId },
2798
+ { kind: 'tab', id: tabId, index: tabIndex },
2799
+ ];
2800
+ }
2801
+ linkEventPath(linkId, linkIndex) {
2802
+ return [
2803
+ { kind: 'tabs', id: this.tabsId },
2804
+ { kind: 'nav', id: this.tabsId },
2805
+ { kind: 'link', id: linkId, index: linkIndex },
2806
+ ];
2091
2807
  }
2092
2808
  styleScopeId() {
2093
2809
  const scopeSeed = [this.tabsId, this.componentInstanceId]
@@ -2226,8 +2942,17 @@ class PraxisTabs {
2226
2942
  }
2227
2943
  return rules.length ? rules.join('\n') : null;
2228
2944
  }
2945
+ cloneWidgetDefinition(widget) {
2946
+ try {
2947
+ if (typeof structuredClone === 'function') {
2948
+ return structuredClone(widget);
2949
+ }
2950
+ }
2951
+ catch { }
2952
+ return JSON.parse(JSON.stringify(widget));
2953
+ }
2229
2954
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisTabs, deps: [], target: i0.ɵɵFactoryTarget.Component });
2230
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisTabs, isStandalone: true, selector: "praxis-tabs", inputs: { config: "config", tabsId: "tabsId", componentInstanceId: "componentInstanceId", enableCustomization: "enableCustomization", form: "form", context: "context" }, outputs: { animationDone: "animationDone", focusChange: "focusChange", selectedIndexChange: "selectedIndexChange", selectedTabChange: "selectedTabChange", indexFocused: "indexFocused", selectFocusedIndex: "selectFocusedIndex", widgetEvent: "widgetEvent" }, usesOnChanges: true, ngImport: i0, template: `
2955
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisTabs, isStandalone: true, selector: "praxis-tabs", inputs: { config: "config", tabsId: "tabsId", componentInstanceId: "componentInstanceId", enableCustomization: "enableCustomization", form: "form", context: "context" }, outputs: { animationDone: "animationDone", focusChange: "focusChange", selectedIndexChange: "selectedIndexChange", selectedTabChange: "selectedTabChange", indexFocused: "indexFocused", selectFocusedIndex: "selectFocusedIndex", widgetEvent: "widgetEvent" }, providers: [providePraxisI18nConfig(PRAXIS_TABS_I18N_CONFIG)], usesOnChanges: true, ngImport: i0, template: `
2231
2956
  <div
2232
2957
  class="praxis-tabs-root"
2233
2958
  [class.density-compact]="config?.appearance?.density === 'compact'"
@@ -2251,12 +2976,12 @@ class PraxisTabs {
2251
2976
  <ng-container *ngIf="isEmptyGlobal(); else notEmpty">
2252
2977
  <praxis-empty-state-card
2253
2978
  icon="tab"
2254
- [title]="'Nenhuma aba configurada'"
2255
- [description]="'Crie rapidamente suas abas ou abra o editor completo.'"
2256
- [primaryAction]="{ label: 'Criar abas rapidamente', icon: 'add', action: openQuickSetup.bind(this) }"
2979
+ [title]="t('emptyState.title', 'Nenhuma aba configurada')"
2980
+ [description]="t('emptyState.description', 'Crie rapidamente suas abas ou abra o editor completo.')"
2981
+ [primaryAction]="{ label: t('emptyState.primaryAction', 'Criar abas rapidamente'), icon: 'add', action: openQuickSetup.bind(this) }"
2257
2982
  [secondaryActions]="[
2258
- { label: 'Adicionar aba vazia', icon: 'tab', action: addEmptyTab.bind(this) },
2259
- { label: 'Abrir editor completo', icon: 'tune', action: openEditor.bind(this) }
2983
+ { label: t('emptyState.secondaryAddEmpty', 'Adicionar aba vazia'), icon: 'tab', action: addEmptyTab.bind(this) },
2984
+ { label: t('emptyState.secondaryOpenEditor', 'Abrir editor completo'), icon: 'tune', action: openEditor.bind(this) }
2260
2985
  ]"
2261
2986
  ></praxis-empty-state-card>
2262
2987
  </ng-container>
@@ -2285,7 +3010,7 @@ class PraxisTabs {
2285
3010
  >
2286
3011
  <a
2287
3012
  mat-tab-link
2288
- *ngFor="let link of config?.nav?.links; let i = index"
3013
+ *ngFor="let link of config?.nav?.links; let i = index; trackBy: trackNavLink"
2289
3014
  cdkDrag
2290
3015
  [cdkDragDisabled]="!config?.behavior?.reorderable"
2291
3016
  cdkDragLockAxis="x"
@@ -2317,11 +3042,11 @@ class PraxisTabs {
2317
3042
  ></ng-container>
2318
3043
  </ng-container>
2319
3044
  <ng-container *ngIf="l.widgets?.length">
2320
- <ng-container *ngFor="let w of l.widgets; let wi = index">
3045
+ <ng-container *ngFor="let w of l.widgets; let wi = index; trackBy: trackWidgetDefinition">
2321
3046
  <ng-container
2322
- [dynamicWidgetLoader]="w"
3047
+ [dynamicWidgetLoader]="resolveWidgetDefinition(w)"
2323
3048
  [context]="context || {}"
2324
- (widgetEvent)="emitWidgetEvent({ linkId: l.id, linkIndex: currentNavIndex() }, $event)"
3049
+ (widgetEvent)="emitWidgetEvent(linkEventPath(l.id, currentNavIndex()), $event)"
2325
3050
  ></ng-container>
2326
3051
  </ng-container>
2327
3052
  </ng-container>
@@ -2330,9 +3055,9 @@ class PraxisTabs {
2330
3055
  <praxis-empty-state-card
2331
3056
  [inline]="true"
2332
3057
  icon="add_to_queue"
2333
- [title]="'Sem conteúdo neste link'"
2334
- [description]="'Adicione conteúdo ou use o editor para configurar.'"
2335
- [primaryAction]="{ label: 'Abrir editor', icon: 'tune', action: openEditor.bind(this) }"
3058
+ [title]="t('emptyState.navTitle', 'Sem conteudo neste link')"
3059
+ [description]="t('emptyState.navDescription', 'Adicione conteudo ou use o editor para configurar.')"
3060
+ [primaryAction]="{ label: t('emptyState.openEditor', 'Abrir editor'), icon: 'tune', action: openEditor.bind(this) }"
2336
3061
  ></praxis-empty-state-card>
2337
3062
  </ng-template>
2338
3063
  </ng-container>
@@ -2364,16 +3089,16 @@ class PraxisTabs {
2364
3089
  class="praxis-tabs-group"
2365
3090
  >
2366
3091
  <mat-tab
2367
- *ngFor="let t of config?.tabs; let i = index"
2368
- [disabled]="t.disabled"
2369
- [labelClass]="t.labelClass ?? ''"
2370
- [bodyClass]="t.bodyClass ?? ''"
2371
- [id]="t.id || ''"
2372
- [attr.aria-label]="t.ariaLabel || null"
2373
- [attr.aria-labelledby]="t.ariaLabelledby || null"
3092
+ *ngFor="let tab of config?.tabs; let i = index; trackBy: trackTab"
3093
+ [disabled]="tab.disabled"
3094
+ [labelClass]="tab.labelClass ?? ''"
3095
+ [bodyClass]="tab.bodyClass ?? ''"
3096
+ [id]="tab.id || ''"
3097
+ [attr.aria-label]="tab.ariaLabel || null"
3098
+ [attr.aria-labelledby]="tab.ariaLabelledby || null"
2374
3099
  >
2375
3100
  <ng-template mat-tab-label>
2376
- <span>{{ t.textLabel }}</span>
3101
+ <span>{{ tab.textLabel }}</span>
2377
3102
  <button
2378
3103
  *ngIf="config?.behavior?.closeable"
2379
3104
  mat-icon-button
@@ -2381,7 +3106,7 @@ class PraxisTabs {
2381
3106
  (click)="closeTab(i); $event.stopPropagation()"
2382
3107
  (keydown.enter)="$event.stopPropagation()"
2383
3108
  (keydown.space)="$event.stopPropagation()"
2384
- aria-label="Fechar aba"
3109
+ [attr.aria-label]="t('chrome.closeTab', 'Fechar aba')"
2385
3110
  >
2386
3111
  <mat-icon fontIcon="close"></mat-icon>
2387
3112
  </button>
@@ -2393,7 +3118,7 @@ class PraxisTabs {
2393
3118
  (keydown.enter)="$event.stopPropagation()"
2394
3119
  (keydown.space)="$event.stopPropagation()"
2395
3120
  [disabled]="i===0"
2396
- aria-label="Mover aba para esquerda"
3121
+ [attr.aria-label]="t('chrome.moveTabLeft', 'Mover aba para esquerda')"
2397
3122
  >
2398
3123
  <mat-icon fontIcon="arrow_back"></mat-icon>
2399
3124
  </button>
@@ -2404,7 +3129,7 @@ class PraxisTabs {
2404
3129
  (keydown.enter)="$event.stopPropagation()"
2405
3130
  (keydown.space)="$event.stopPropagation()"
2406
3131
  [disabled]="i===(config?.tabs?.length||1)-1"
2407
- aria-label="Mover aba para direita"
3132
+ [attr.aria-label]="t('chrome.moveTabRight', 'Mover aba para direita')"
2408
3133
  >
2409
3134
  <mat-icon fontIcon="arrow_forward"></mat-icon>
2410
3135
  </button>
@@ -2412,20 +3137,20 @@ class PraxisTabs {
2412
3137
  </ng-template>
2413
3138
 
2414
3139
  <ng-template matTabContent>
2415
- <ng-container *ngIf="(t.content?.length || t.widgets?.length) && groupContentReady(i); else emptyTab">
2416
- <ng-container *ngIf="t.content && form">
3140
+ <ng-container *ngIf="(tab.content?.length || tab.widgets?.length) && groupContentReady(i); else emptyTab">
3141
+ <ng-container *ngIf="tab.content && form">
2417
3142
  <ng-container
2418
3143
  dynamicFieldLoader
2419
- [fields]="t.content || []"
3144
+ [fields]="tab.content || []"
2420
3145
  [formGroup]="form!"
2421
3146
  ></ng-container>
2422
3147
  </ng-container>
2423
- <ng-container *ngIf="t.widgets?.length">
2424
- <ng-container *ngFor="let w of t.widgets; let wi = index">
3148
+ <ng-container *ngIf="tab.widgets?.length">
3149
+ <ng-container *ngFor="let w of tab.widgets; let wi = index; trackBy: trackWidgetDefinition">
2425
3150
  <ng-container
2426
- [dynamicWidgetLoader]="w"
3151
+ [dynamicWidgetLoader]="resolveWidgetDefinition(w)"
2427
3152
  [context]="context || {}"
2428
- (widgetEvent)="emitWidgetEvent({ tabId: t.id, tabIndex: i }, $event)"
3153
+ (widgetEvent)="emitWidgetEvent(tabEventPath(tab.id, i), $event)"
2429
3154
  ></ng-container>
2430
3155
  </ng-container>
2431
3156
  </ng-container>
@@ -2434,9 +3159,9 @@ class PraxisTabs {
2434
3159
  <praxis-empty-state-card
2435
3160
  [inline]="true"
2436
3161
  icon="dashboard_customize"
2437
- [title]="'Sem conteúdo nesta aba'"
2438
- [description]="'Adicione conteúdo ou use o editor para configurar.'"
2439
- [primaryAction]="{ label: 'Abrir editor', icon: 'tune', action: openEditor.bind(this) }"
3162
+ [title]="t('emptyState.tabTitle', 'Sem conteudo nesta aba')"
3163
+ [description]="t('emptyState.tabDescription', 'Adicione conteudo ou use o editor para configurar.')"
3164
+ [primaryAction]="{ label: t('emptyState.openEditor', 'Abrir editor'), icon: 'tune', action: openEditor.bind(this) }"
2440
3165
  ></praxis-empty-state-card>
2441
3166
  </ng-template>
2442
3167
  </ng-template>
@@ -2450,7 +3175,7 @@ class PraxisTabs {
2450
3175
  *ngIf="enableCustomization"
2451
3176
  mat-fab
2452
3177
  class="edit-fab"
2453
- aria-label="Editar tabs"
3178
+ [attr.aria-label]="t('chrome.editTabs', 'Editar abas')"
2454
3179
  (click)="openEditor()"
2455
3180
  >
2456
3181
  <mat-icon fontIcon="edit"></mat-icon>
@@ -2459,18 +3184,18 @@ class PraxisTabs {
2459
3184
  *ngIf="enableCustomization && tabsId"
2460
3185
  mat-mini-fab
2461
3186
  class="edit-fab edit-fab-secondary"
2462
- aria-label="Redefinir preferências de tabs"
3187
+ [attr.aria-label]="t('settings.resetPreferences', 'Redefinir preferencias de abas')"
2463
3188
  (click)="resetPreferences()"
2464
- matTooltip="Redefinir preferências de tabs"
3189
+ [matTooltip]="t('settings.resetPreferences', 'Redefinir preferencias de abas')"
2465
3190
  >
2466
3191
  <mat-icon [praxisIcon]="'restart_alt'"></mat-icon>
2467
3192
  </button>
2468
3193
  </div>
2469
- `, isInline: true, styles: [".praxis-tabs-root{position:relative;display:block}.praxis-tabs-group.align-start .mat-mdc-tab-header{justify-content:flex-start}.praxis-tabs-group.align-center .mat-mdc-tab-header{justify-content:center}.praxis-tabs-group.align-end .mat-mdc-tab-header{justify-content:flex-end}.density-compact .mat-mdc-tab-body-content{padding:8px}.density-comfortable .mat-mdc-tab-body-content{padding:16px}.density-spacious .mat-mdc-tab-body-content{padding:24px}.tabs-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.edit-fab-secondary{right:56px}.tab-empty{padding:16px;color:var(--md-sys-color-on-surface-variant);font-style:italic}.high-contrast{filter:contrast(1.2)}.reduce-motion{--mat-animation-duration: 0ms}.drag-handle{display:inline-flex;align-items:center;vertical-align:middle;margin-right:4px;cursor:grab}:host-context(.pdx-gridster-item) .praxis-tabs-root{display:flex;flex-direction:column;height:100%;min-height:0}:host-context(.pdx-gridster-item) .praxis-tabs-group,:host-context(.pdx-gridster-item) .mat-mdc-tab-group{flex:1 1 auto;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-wrapper,:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{height:100%;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{overflow:auto}:host-context(.pdx-gridster-item) .praxis-tabnav-content{flex:1 1 auto;min-height:0;overflow:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i3$1.MatTabContent, selector: "[matTabContent]" }, { kind: "directive", type: i3$1.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i3$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i3$1.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "component", type: i3$1.MatTabNav, selector: "[mat-tab-nav-bar]", inputs: ["fitInkBarToContent", "mat-stretch-tabs", "animationDuration", "backgroundColor", "disableRipple", "color", "tabPanel"], exportAs: ["matTabNavBar", "matTabNav"] }, { kind: "component", type: i3$1.MatTabNavPanel, selector: "mat-tab-nav-panel", inputs: ["id"], exportAs: ["matTabNavPanel"] }, { kind: "component", type: i3$1.MatTabLink, selector: "[mat-tab-link], [matTabLink]", inputs: ["active", "disabled", "disableRipple", "tabIndex", "id"], exportAs: ["matTabLink"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i11.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6$1.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6$1.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i10.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i10.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i10.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline", "tone"] }, { kind: "directive", type: DynamicFieldLoaderDirective, selector: "[dynamicFieldLoader]", inputs: ["fields", "formGroup", "enableExternalControlBinding", "itemTemplate", "debugTrace", "debugTraceLabel", "readonlyMode", "disabledMode", "presentationMode", "visible", "canvasMode"], outputs: ["componentsCreated", "fieldCreated", "fieldDestroyed", "renderError", "canvasMouseEnter", "canvasMouseLeave", "canvasClick"] }, { kind: "directive", type: DynamicWidgetLoaderDirective, selector: "[dynamicWidgetLoader]", inputs: ["dynamicWidgetLoader", "context", "strictValidation", "autoWireOutputs"], outputs: ["widgetEvent"], exportAs: ["dynamicWidgetLoader"] }, { kind: "component", type: PraxisAiAssistantComponent, selector: "praxis-ai-assistant", inputs: ["adapter", "riskPolicy", "allowManualPatchEdit"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3194
+ `, isInline: true, styles: [".praxis-tabs-root{position:relative;display:block}.praxis-tabs-group.align-start .mat-mdc-tab-header{justify-content:flex-start}.praxis-tabs-group.align-center .mat-mdc-tab-header{justify-content:center}.praxis-tabs-group.align-end .mat-mdc-tab-header{justify-content:flex-end}.density-compact .mat-mdc-tab-body-content{padding:8px}.density-comfortable .mat-mdc-tab-body-content{padding:16px}.density-spacious .mat-mdc-tab-body-content{padding:24px}.tabs-ai-assistant{position:absolute;right:12px;bottom:72px;z-index:3}.edit-fab{position:absolute;right:12px;bottom:12px;z-index:2}.edit-fab-secondary{right:56px}.tab-empty{padding:16px;color:var(--md-sys-color-on-surface-variant);font-style:italic}.high-contrast{filter:contrast(1.2)}.reduce-motion{--mat-animation-duration: 0ms}.drag-handle{display:inline-flex;align-items:center;vertical-align:middle;margin-right:4px;cursor:grab}:host-context(.pdx-gridster-item) .praxis-tabs-root{display:flex;flex-direction:column;height:100%;min-height:0}:host-context(.pdx-gridster-item) .praxis-tabs-group,:host-context(.pdx-gridster-item) .mat-mdc-tab-group{flex:1 1 auto;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-wrapper,:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{height:100%;min-height:0}:host-context(.pdx-gridster-item) .mat-mdc-tab-body-content{overflow:auto}:host-context(.pdx-gridster-item) .praxis-tabnav-content{flex:1 1 auto;min-height:0;overflow:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "directive", type: i3$1.MatTabContent, selector: "[matTabContent]" }, { kind: "directive", type: i3$1.MatTabLabel, selector: "[mat-tab-label], [matTabLabel]" }, { kind: "component", type: i3$1.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i3$1.MatTabGroup, selector: "mat-tab-group", inputs: ["color", "fitInkBarToContent", "mat-stretch-tabs", "mat-align-tabs", "dynamicHeight", "selectedIndex", "headerPosition", "animationDuration", "contentTabIndex", "disablePagination", "disableRipple", "preserveContent", "backgroundColor", "aria-label", "aria-labelledby"], outputs: ["selectedIndexChange", "focusChange", "animationDone", "selectedTabChange"], exportAs: ["matTabGroup"] }, { kind: "component", type: i3$1.MatTabNav, selector: "[mat-tab-nav-bar]", inputs: ["fitInkBarToContent", "mat-stretch-tabs", "animationDuration", "backgroundColor", "disableRipple", "color", "tabPanel"], exportAs: ["matTabNavBar", "matTabNav"] }, { kind: "component", type: i3$1.MatTabNavPanel, selector: "mat-tab-nav-panel", inputs: ["id"], exportAs: ["matTabNavPanel"] }, { kind: "component", type: i3$1.MatTabLink, selector: "[mat-tab-link], [matTabLink]", inputs: ["active", "disabled", "disableRipple", "tabIndex", "id"], exportAs: ["matTabLink"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i7.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i11.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "directive", type: PraxisIconDirective, selector: "mat-icon[praxisIcon]", inputs: ["praxisIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i6$1.MatMiniFabButton, selector: "button[mat-mini-fab], a[mat-mini-fab], button[matMiniFab], a[matMiniFab]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6$1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i6$1.MatFabButton, selector: "button[mat-fab], a[mat-fab], button[matFab], a[matFab]", inputs: ["extended"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i10.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i10.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i10.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: EmptyStateCardComponent, selector: "praxis-empty-state-card", inputs: ["icon", "title", "description", "primaryAction", "secondaryActions", "inline", "tone"] }, { kind: "directive", type: DynamicFieldLoaderDirective, selector: "[dynamicFieldLoader]", inputs: ["fields", "formGroup", "enableExternalControlBinding", "itemTemplate", "debugTrace", "debugTraceLabel", "readonlyMode", "disabledMode", "presentationMode", "visible", "canvasMode"], outputs: ["componentsCreated", "fieldCreated", "fieldDestroyed", "renderError", "canvasMouseEnter", "canvasMouseLeave", "canvasClick"] }, { kind: "directive", type: DynamicWidgetLoaderDirective, selector: "[dynamicWidgetLoader]", inputs: ["dynamicWidgetLoader", "ownerWidgetKey", "context", "strictValidation", "autoWireOutputs"], outputs: ["widgetEvent", "widgetDiagnostic"], exportAs: ["dynamicWidgetLoader"] }, { kind: "component", type: PraxisAiAssistantComponent, selector: "praxis-ai-assistant", inputs: ["adapter", "riskPolicy", "allowManualPatchEdit"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2470
3195
  }
2471
3196
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisTabs, decorators: [{
2472
3197
  type: Component,
2473
- args: [{ selector: 'praxis-tabs', standalone: true, imports: [
3198
+ args: [{ selector: 'praxis-tabs', standalone: true, providers: [providePraxisI18nConfig(PRAXIS_TABS_I18N_CONFIG)], imports: [
2474
3199
  CommonModule,
2475
3200
  ReactiveFormsModule,
2476
3201
  MatTabsModule,
@@ -2507,12 +3232,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
2507
3232
  <ng-container *ngIf="isEmptyGlobal(); else notEmpty">
2508
3233
  <praxis-empty-state-card
2509
3234
  icon="tab"
2510
- [title]="'Nenhuma aba configurada'"
2511
- [description]="'Crie rapidamente suas abas ou abra o editor completo.'"
2512
- [primaryAction]="{ label: 'Criar abas rapidamente', icon: 'add', action: openQuickSetup.bind(this) }"
3235
+ [title]="t('emptyState.title', 'Nenhuma aba configurada')"
3236
+ [description]="t('emptyState.description', 'Crie rapidamente suas abas ou abra o editor completo.')"
3237
+ [primaryAction]="{ label: t('emptyState.primaryAction', 'Criar abas rapidamente'), icon: 'add', action: openQuickSetup.bind(this) }"
2513
3238
  [secondaryActions]="[
2514
- { label: 'Adicionar aba vazia', icon: 'tab', action: addEmptyTab.bind(this) },
2515
- { label: 'Abrir editor completo', icon: 'tune', action: openEditor.bind(this) }
3239
+ { label: t('emptyState.secondaryAddEmpty', 'Adicionar aba vazia'), icon: 'tab', action: addEmptyTab.bind(this) },
3240
+ { label: t('emptyState.secondaryOpenEditor', 'Abrir editor completo'), icon: 'tune', action: openEditor.bind(this) }
2516
3241
  ]"
2517
3242
  ></praxis-empty-state-card>
2518
3243
  </ng-container>
@@ -2541,7 +3266,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
2541
3266
  >
2542
3267
  <a
2543
3268
  mat-tab-link
2544
- *ngFor="let link of config?.nav?.links; let i = index"
3269
+ *ngFor="let link of config?.nav?.links; let i = index; trackBy: trackNavLink"
2545
3270
  cdkDrag
2546
3271
  [cdkDragDisabled]="!config?.behavior?.reorderable"
2547
3272
  cdkDragLockAxis="x"
@@ -2573,11 +3298,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
2573
3298
  ></ng-container>
2574
3299
  </ng-container>
2575
3300
  <ng-container *ngIf="l.widgets?.length">
2576
- <ng-container *ngFor="let w of l.widgets; let wi = index">
3301
+ <ng-container *ngFor="let w of l.widgets; let wi = index; trackBy: trackWidgetDefinition">
2577
3302
  <ng-container
2578
- [dynamicWidgetLoader]="w"
3303
+ [dynamicWidgetLoader]="resolveWidgetDefinition(w)"
2579
3304
  [context]="context || {}"
2580
- (widgetEvent)="emitWidgetEvent({ linkId: l.id, linkIndex: currentNavIndex() }, $event)"
3305
+ (widgetEvent)="emitWidgetEvent(linkEventPath(l.id, currentNavIndex()), $event)"
2581
3306
  ></ng-container>
2582
3307
  </ng-container>
2583
3308
  </ng-container>
@@ -2586,9 +3311,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
2586
3311
  <praxis-empty-state-card
2587
3312
  [inline]="true"
2588
3313
  icon="add_to_queue"
2589
- [title]="'Sem conteúdo neste link'"
2590
- [description]="'Adicione conteúdo ou use o editor para configurar.'"
2591
- [primaryAction]="{ label: 'Abrir editor', icon: 'tune', action: openEditor.bind(this) }"
3314
+ [title]="t('emptyState.navTitle', 'Sem conteudo neste link')"
3315
+ [description]="t('emptyState.navDescription', 'Adicione conteudo ou use o editor para configurar.')"
3316
+ [primaryAction]="{ label: t('emptyState.openEditor', 'Abrir editor'), icon: 'tune', action: openEditor.bind(this) }"
2592
3317
  ></praxis-empty-state-card>
2593
3318
  </ng-template>
2594
3319
  </ng-container>
@@ -2620,16 +3345,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
2620
3345
  class="praxis-tabs-group"
2621
3346
  >
2622
3347
  <mat-tab
2623
- *ngFor="let t of config?.tabs; let i = index"
2624
- [disabled]="t.disabled"
2625
- [labelClass]="t.labelClass ?? ''"
2626
- [bodyClass]="t.bodyClass ?? ''"
2627
- [id]="t.id || ''"
2628
- [attr.aria-label]="t.ariaLabel || null"
2629
- [attr.aria-labelledby]="t.ariaLabelledby || null"
3348
+ *ngFor="let tab of config?.tabs; let i = index; trackBy: trackTab"
3349
+ [disabled]="tab.disabled"
3350
+ [labelClass]="tab.labelClass ?? ''"
3351
+ [bodyClass]="tab.bodyClass ?? ''"
3352
+ [id]="tab.id || ''"
3353
+ [attr.aria-label]="tab.ariaLabel || null"
3354
+ [attr.aria-labelledby]="tab.ariaLabelledby || null"
2630
3355
  >
2631
3356
  <ng-template mat-tab-label>
2632
- <span>{{ t.textLabel }}</span>
3357
+ <span>{{ tab.textLabel }}</span>
2633
3358
  <button
2634
3359
  *ngIf="config?.behavior?.closeable"
2635
3360
  mat-icon-button
@@ -2637,7 +3362,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
2637
3362
  (click)="closeTab(i); $event.stopPropagation()"
2638
3363
  (keydown.enter)="$event.stopPropagation()"
2639
3364
  (keydown.space)="$event.stopPropagation()"
2640
- aria-label="Fechar aba"
3365
+ [attr.aria-label]="t('chrome.closeTab', 'Fechar aba')"
2641
3366
  >
2642
3367
  <mat-icon fontIcon="close"></mat-icon>
2643
3368
  </button>
@@ -2649,7 +3374,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
2649
3374
  (keydown.enter)="$event.stopPropagation()"
2650
3375
  (keydown.space)="$event.stopPropagation()"
2651
3376
  [disabled]="i===0"
2652
- aria-label="Mover aba para esquerda"
3377
+ [attr.aria-label]="t('chrome.moveTabLeft', 'Mover aba para esquerda')"
2653
3378
  >
2654
3379
  <mat-icon fontIcon="arrow_back"></mat-icon>
2655
3380
  </button>
@@ -2660,7 +3385,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
2660
3385
  (keydown.enter)="$event.stopPropagation()"
2661
3386
  (keydown.space)="$event.stopPropagation()"
2662
3387
  [disabled]="i===(config?.tabs?.length||1)-1"
2663
- aria-label="Mover aba para direita"
3388
+ [attr.aria-label]="t('chrome.moveTabRight', 'Mover aba para direita')"
2664
3389
  >
2665
3390
  <mat-icon fontIcon="arrow_forward"></mat-icon>
2666
3391
  </button>
@@ -2668,20 +3393,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
2668
3393
  </ng-template>
2669
3394
 
2670
3395
  <ng-template matTabContent>
2671
- <ng-container *ngIf="(t.content?.length || t.widgets?.length) && groupContentReady(i); else emptyTab">
2672
- <ng-container *ngIf="t.content && form">
3396
+ <ng-container *ngIf="(tab.content?.length || tab.widgets?.length) && groupContentReady(i); else emptyTab">
3397
+ <ng-container *ngIf="tab.content && form">
2673
3398
  <ng-container
2674
3399
  dynamicFieldLoader
2675
- [fields]="t.content || []"
3400
+ [fields]="tab.content || []"
2676
3401
  [formGroup]="form!"
2677
3402
  ></ng-container>
2678
3403
  </ng-container>
2679
- <ng-container *ngIf="t.widgets?.length">
2680
- <ng-container *ngFor="let w of t.widgets; let wi = index">
3404
+ <ng-container *ngIf="tab.widgets?.length">
3405
+ <ng-container *ngFor="let w of tab.widgets; let wi = index; trackBy: trackWidgetDefinition">
2681
3406
  <ng-container
2682
- [dynamicWidgetLoader]="w"
3407
+ [dynamicWidgetLoader]="resolveWidgetDefinition(w)"
2683
3408
  [context]="context || {}"
2684
- (widgetEvent)="emitWidgetEvent({ tabId: t.id, tabIndex: i }, $event)"
3409
+ (widgetEvent)="emitWidgetEvent(tabEventPath(tab.id, i), $event)"
2685
3410
  ></ng-container>
2686
3411
  </ng-container>
2687
3412
  </ng-container>
@@ -2690,9 +3415,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
2690
3415
  <praxis-empty-state-card
2691
3416
  [inline]="true"
2692
3417
  icon="dashboard_customize"
2693
- [title]="'Sem conteúdo nesta aba'"
2694
- [description]="'Adicione conteúdo ou use o editor para configurar.'"
2695
- [primaryAction]="{ label: 'Abrir editor', icon: 'tune', action: openEditor.bind(this) }"
3418
+ [title]="t('emptyState.tabTitle', 'Sem conteudo nesta aba')"
3419
+ [description]="t('emptyState.tabDescription', 'Adicione conteudo ou use o editor para configurar.')"
3420
+ [primaryAction]="{ label: t('emptyState.openEditor', 'Abrir editor'), icon: 'tune', action: openEditor.bind(this) }"
2696
3421
  ></praxis-empty-state-card>
2697
3422
  </ng-template>
2698
3423
  </ng-template>
@@ -2706,7 +3431,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
2706
3431
  *ngIf="enableCustomization"
2707
3432
  mat-fab
2708
3433
  class="edit-fab"
2709
- aria-label="Editar tabs"
3434
+ [attr.aria-label]="t('chrome.editTabs', 'Editar abas')"
2710
3435
  (click)="openEditor()"
2711
3436
  >
2712
3437
  <mat-icon fontIcon="edit"></mat-icon>
@@ -2715,9 +3440,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
2715
3440
  *ngIf="enableCustomization && tabsId"
2716
3441
  mat-mini-fab
2717
3442
  class="edit-fab edit-fab-secondary"
2718
- aria-label="Redefinir preferências de tabs"
3443
+ [attr.aria-label]="t('settings.resetPreferences', 'Redefinir preferencias de abas')"
2719
3444
  (click)="resetPreferences()"
2720
- matTooltip="Redefinir preferências de tabs"
3445
+ [matTooltip]="t('settings.resetPreferences', 'Redefinir preferencias de abas')"
2721
3446
  >
2722
3447
  <mat-icon [praxisIcon]="'restart_alt'"></mat-icon>
2723
3448
  </button>
@@ -2752,6 +3477,62 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
2752
3477
  type: Output
2753
3478
  }] } });
2754
3479
 
3480
+ const PRAXIS_TABS_PORTS = [
3481
+ {
3482
+ id: 'context',
3483
+ label: 'Contexto',
3484
+ direction: 'input',
3485
+ semanticKind: 'view-context',
3486
+ schema: {
3487
+ id: 'Record<string, unknown>',
3488
+ kind: 'ts-type',
3489
+ ref: 'Record<string, unknown>',
3490
+ },
3491
+ description: 'Contexto de visualizacao entregue ao container e aos widgets internos.',
3492
+ exposure: { public: true, group: 'context' },
3493
+ },
3494
+ {
3495
+ id: 'config',
3496
+ label: 'Configuracao',
3497
+ direction: 'input',
3498
+ semanticKind: 'config-fragment',
3499
+ schema: {
3500
+ id: 'TabsMetadata',
3501
+ kind: 'ts-type',
3502
+ ref: 'TabsMetadata',
3503
+ },
3504
+ description: 'Fragmento canonico de configuracao das tabs/nav e dos widgets internos.',
3505
+ exposure: { public: true, group: 'config' },
3506
+ },
3507
+ {
3508
+ id: 'selectedIndexChange',
3509
+ label: 'Troca de indice selecionado',
3510
+ direction: 'output',
3511
+ semanticKind: 'event',
3512
+ schema: {
3513
+ id: 'number',
3514
+ kind: 'ts-type',
3515
+ ref: 'number',
3516
+ },
3517
+ cardinality: 'stream',
3518
+ description: 'Evento canonico emitido quando a selecao de aba muda.',
3519
+ exposure: { public: true, group: 'events' },
3520
+ },
3521
+ {
3522
+ id: 'widgetEvent',
3523
+ label: 'Evento interno de widget',
3524
+ direction: 'output',
3525
+ semanticKind: 'event',
3526
+ schema: {
3527
+ id: 'WidgetEventEnvelope',
3528
+ kind: 'ts-type',
3529
+ ref: 'WidgetEventEnvelope',
3530
+ },
3531
+ cardinality: 'stream',
3532
+ description: 'Bridge composta temporaria para eventos internos de widgets. Nao representa o contrato final de nested ports.',
3533
+ exposure: { public: true, advanced: true, group: 'composite' },
3534
+ },
3535
+ ];
2755
3536
  const PRAXIS_TABS_COMPONENT_METADATA = {
2756
3537
  id: 'praxis-tabs',
2757
3538
  selector: 'praxis-tabs',
@@ -2802,9 +3583,9 @@ const PRAXIS_TABS_COMPONENT_METADATA = {
2802
3583
  { name: 'selectFocusedIndex', type: 'number', label: 'Selecionar foco' },
2803
3584
  {
2804
3585
  name: 'widgetEvent',
2805
- type: "{ tabId?: string; tabIndex?: number; linkId?: string; linkIndex?: number; sourceId: string; output?: string; payload?: any }",
3586
+ type: 'WidgetEventEnvelope',
2806
3587
  label: 'Evento interno',
2807
- description: 'Eventos reemitidos de componentes dinâmicos dentro da aba/link',
3588
+ description: 'Eventos reemitidos de componentes dinâmicos dentro da aba/link, enriquecidos com contexto hierárquico em `path`.',
2808
3589
  },
2809
3590
  ],
2810
3591
  actions: [
@@ -2881,6 +3662,7 @@ const PRAXIS_TABS_COMPONENT_METADATA = {
2881
3662
  ],
2882
3663
  tags: ['widget', 'tabs', 'configurable', 'stable'],
2883
3664
  lib: '@praxisui/tabs',
3665
+ ports: PRAXIS_TABS_PORTS,
2884
3666
  };
2885
3667
  function providePraxisTabsMetadata() {
2886
3668
  return {
@@ -2901,5 +3683,5 @@ function providePraxisTabsMetadata() {
2901
3683
  * Generated bundle index. Do not edit.
2902
3684
  */
2903
3685
 
2904
- export { PRAXIS_TABS_COMPONENT_METADATA, PraxisTabs, PraxisTabsConfigEditor, TABS_AI_CAPABILITIES, providePraxisTabsMetadata };
3686
+ export { PRAXIS_TABS_COMPONENT_METADATA, PRAXIS_TABS_I18N_CONFIG, PRAXIS_TABS_I18N_NAMESPACE, PraxisTabs, PraxisTabsConfigEditor, TABS_AI_CAPABILITIES, buildTabsApplyPlan, createPraxisTabsI18nConfig, createTabsAuthoringDocument, normalizeTabsAuthoringDocument, providePraxisTabsI18n, providePraxisTabsMetadata, serializeTabsAuthoringDocument, toCanonicalTabsConfig, validateTabsAuthoringDocument };
2905
3687
  //# sourceMappingURL=praxisui-tabs.mjs.map