@praxisui/dynamic-form 8.0.0-beta.1 → 8.0.0-beta.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -2
- package/fesm2022/praxisui-dynamic-form.mjs +1283 -452
- package/index.d.ts +33 -11
- package/package.json +6 -6
|
@@ -23,7 +23,7 @@ import { MatBadgeModule } from '@angular/material/badge';
|
|
|
23
23
|
import { firstValueFrom, BehaviorSubject, Subject, throwError, debounceTime as debounceTime$1, takeUntil as takeUntil$1 } from 'rxjs';
|
|
24
24
|
import { map, take, takeUntil, timeout, tap, debounceTime, finalize } from 'rxjs/operators';
|
|
25
25
|
import * as i1$2 from '@praxisui/core';
|
|
26
|
-
import { PraxisI18nService, PraxisIconDirective, providePraxisI18nConfig, RULE_PROPERTY_SCHEMA, FIELD_METADATA_CAPABILITIES, deepMerge, createDefaultFormConfig, normalizeFormConfig as normalizeFormConfig$1, ensureIds, createEmptyRichContentDocument, ASYNC_CONFIG_STORAGE, migrateFormLayoutRule, LoggerService, createCorporateLoggerConfig, ConsoleLoggerSink, ResourceQuickConnectComponent, composeHeadersWithVersion, buildApiUrl, mapFieldDefinitionsToMetadata, reconcileFormConfig, syncWithServerMetadata, PRAXIS_LOADING_CTX, normalizeControlTypeKey, resolveSpan, resolveOffset, resolveOrder, resolveHidden, buildSchemaId, fetchWithETag, resolveControlTypeAlias, getTextTransformer, MemoryCacheAdapter, LocalStorageCacheAdapter, SchemaMetadataClient, CONNECTION_STORAGE, API_URL, PRAXIS_LOADING_RENDERER, FORM_HOOKS_PRESETS, EmptyStateCardComponent, isValidFormConfig, ComponentMetadataRegistry, FieldControlType,
|
|
26
|
+
import { PraxisI18nService, PraxisIconDirective, providePraxisI18nConfig, RULE_PROPERTY_SCHEMA, FIELD_METADATA_CAPABILITIES, deepMerge, createDefaultFormConfig, normalizeFormConfig as normalizeFormConfig$1, ensureIds, createEmptyRichContentDocument, ASYNC_CONFIG_STORAGE, migrateFormLayoutRule, LoggerService, createCorporateLoggerConfig, ConsoleLoggerSink, ResourceQuickConnectComponent, composeHeadersWithVersion, buildApiUrl, mapFieldDefinitionsToMetadata, reconcileFormConfig, syncWithServerMetadata, PRAXIS_LOADING_CTX, normalizeControlTypeKey, resolveSpan, resolveOffset, resolveOrder, resolveHidden, buildSchemaId, fetchWithETag, resolveControlTypeAlias, getTextTransformer, MemoryCacheAdapter, LocalStorageCacheAdapter, SchemaMetadataClient, CONNECTION_STORAGE, API_URL, PRAXIS_LOADING_RENDERER, FORM_HOOKS_PRESETS, EmptyStateCardComponent, isValidFormConfig, ComponentMetadataRegistry, FieldControlType, isRequiredGlobalActionPayloadMissing, GLOBAL_ACTION_CATALOG, PRAXIS_GLOBAL_ACTION_CATALOG, getGlobalActionCatalog, SURFACE_OPEN_I18N_NAMESPACE, getGlobalActionUiSchema, getRequiredGlobalActionPayloadKeys, hasMeaningfulGlobalActionPayloadValue, SurfaceOpenActionEditorComponent, SURFACE_OPEN_I18N_CONFIG, validateGlobalActionRefs, PraxisJsonLogicService } from '@praxisui/core';
|
|
27
27
|
import * as i1 from '@praxisui/dynamic-fields';
|
|
28
28
|
import { getControlTypeCatalog, ConfirmDialogComponent, DynamicFieldLoaderDirective } from '@praxisui/dynamic-fields';
|
|
29
29
|
import { BaseAiAdapter, PraxisAiAssistantComponent } from '@praxisui/ai';
|
|
@@ -179,21 +179,11 @@ function prepareSubmitPayload(rawValue, fieldMetadata, options = {}) {
|
|
|
179
179
|
if (!normalized || typeof normalized !== 'object' || Array.isArray(normalized)) {
|
|
180
180
|
return normalized;
|
|
181
181
|
}
|
|
182
|
-
const payload = { ...normalized };
|
|
183
182
|
const dirtyFields = new Set(options.dirtyFields || []);
|
|
184
|
-
|
|
185
|
-
if (shouldOmitFieldFromSubmit(field, dirtyFields)) {
|
|
186
|
-
delete payload[field.name];
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
if (shouldOmitEmptyOptionalField(field, payload[field.name], dirtyFields)) {
|
|
190
|
-
delete payload[field.name];
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
return payload;
|
|
183
|
+
return filterRecordForSubmit(normalized, fieldMetadata || [], dirtyFields);
|
|
194
184
|
}
|
|
195
|
-
function shouldOmitEmptyOptionalField(field, value, dirtyFields) {
|
|
196
|
-
if (
|
|
185
|
+
function shouldOmitEmptyOptionalField(field, value, dirtyFields, scopePath) {
|
|
186
|
+
if (isFieldDirty(field.name, dirtyFields, scopePath) || isRequiredField(field)) {
|
|
197
187
|
return false;
|
|
198
188
|
}
|
|
199
189
|
return value === null || value === undefined || value === '';
|
|
@@ -202,25 +192,71 @@ function isRequiredField(field) {
|
|
|
202
192
|
const validators = field.validators;
|
|
203
193
|
return field.required === true || validators?.required === true;
|
|
204
194
|
}
|
|
205
|
-
function shouldOmitFieldFromSubmit(field, dirtyFields = new Set()) {
|
|
195
|
+
function shouldOmitFieldFromSubmit(field, dirtyFields = new Set(), scopePath) {
|
|
206
196
|
const policy = field.submitPolicy;
|
|
207
197
|
if (policy) {
|
|
208
|
-
return shouldOmitByPolicy(policy, field.name, dirtyFields);
|
|
198
|
+
return shouldOmitByPolicy(policy, field.name, dirtyFields, scopePath);
|
|
209
199
|
}
|
|
210
200
|
return field.source === 'local' || field.transient === true;
|
|
211
201
|
}
|
|
212
|
-
function shouldOmitByPolicy(policy, fieldName, dirtyFields) {
|
|
202
|
+
function shouldOmitByPolicy(policy, fieldName, dirtyFields, scopePath) {
|
|
213
203
|
switch (policy) {
|
|
214
204
|
case 'include':
|
|
215
205
|
return false;
|
|
216
206
|
case 'omit':
|
|
217
207
|
return true;
|
|
218
208
|
case 'includeWhenDirty':
|
|
219
|
-
return !
|
|
209
|
+
return !isFieldDirty(fieldName, dirtyFields, scopePath);
|
|
220
210
|
default:
|
|
221
211
|
return false;
|
|
222
212
|
}
|
|
223
213
|
}
|
|
214
|
+
function filterRecordForSubmit(value, fields, dirtyFields, scopePath) {
|
|
215
|
+
const payload = { ...value };
|
|
216
|
+
for (const field of fields) {
|
|
217
|
+
if (shouldOmitFieldFromSubmit(field, dirtyFields, scopePath)) {
|
|
218
|
+
delete payload[field.name];
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
const fieldValue = payload[field.name];
|
|
222
|
+
const fieldPath = buildScopedFieldPath(field.name, scopePath);
|
|
223
|
+
payload[field.name] = filterNestedArrayForSubmit(field, fieldValue, dirtyFields, fieldPath);
|
|
224
|
+
if (shouldOmitEmptyOptionalField(field, payload[field.name], dirtyFields, scopePath)) {
|
|
225
|
+
delete payload[field.name];
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return payload;
|
|
229
|
+
}
|
|
230
|
+
function filterNestedArrayForSubmit(field, value, dirtyFields, fieldPath) {
|
|
231
|
+
const itemFields = field.array?.itemSchema?.fields;
|
|
232
|
+
if (!Array.isArray(value) || !itemFields?.length) {
|
|
233
|
+
return value;
|
|
234
|
+
}
|
|
235
|
+
return value.map((item, index) => {
|
|
236
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
237
|
+
return item;
|
|
238
|
+
}
|
|
239
|
+
return filterRecordForSubmit(item, itemFields, dirtyFields, `${fieldPath}.${index}`);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
function isFieldDirty(fieldName, dirtyFields, scopePath) {
|
|
243
|
+
if (dirtyFields.has(fieldName)) {
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
if (!scopePath) {
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
return (dirtyFields.has(scopePath) ||
|
|
250
|
+
dirtyFields.has(buildScopedFieldPath(fieldName, scopePath)) ||
|
|
251
|
+
dirtyFields.has(buildBracketScopedFieldPath(fieldName, scopePath)));
|
|
252
|
+
}
|
|
253
|
+
function buildScopedFieldPath(fieldName, scopePath) {
|
|
254
|
+
return scopePath ? `${scopePath}.${fieldName}` : fieldName;
|
|
255
|
+
}
|
|
256
|
+
function buildBracketScopedFieldPath(fieldName, scopePath) {
|
|
257
|
+
const bracketPath = scopePath.replace(/\.(\d+)(?=\.|$)/g, '[$1]');
|
|
258
|
+
return `${bracketPath}.${fieldName}`;
|
|
259
|
+
}
|
|
224
260
|
|
|
225
261
|
const SECTION_HEADER_INITIALS_MIN = 1;
|
|
226
262
|
const SECTION_HEADER_INITIALS_MAX = 4;
|
|
@@ -717,7 +753,7 @@ class PraxisFormActionsComponent {
|
|
|
717
753
|
event.stopPropagation();
|
|
718
754
|
return;
|
|
719
755
|
}
|
|
720
|
-
const actionId =
|
|
756
|
+
const actionId = this.resolveButtonActionId(button);
|
|
721
757
|
if (!actionId)
|
|
722
758
|
return;
|
|
723
759
|
if (button.type === 'submit') {
|
|
@@ -784,7 +820,7 @@ class PraxisFormActionsComponent {
|
|
|
784
820
|
return this.invalidSubmitHintCacheValue;
|
|
785
821
|
}
|
|
786
822
|
applyOverrides(button) {
|
|
787
|
-
const actionId =
|
|
823
|
+
const actionId = this.resolveButtonActionId(button);
|
|
788
824
|
const override = actionId ? this.actionOverrides?.[actionId] : undefined;
|
|
789
825
|
if (!override)
|
|
790
826
|
return button;
|
|
@@ -821,7 +857,7 @@ class PraxisFormActionsComponent {
|
|
|
821
857
|
return;
|
|
822
858
|
const dispose = this.shortcuts.registerShortcut(shortcut, {
|
|
823
859
|
callback: () => {
|
|
824
|
-
const actionId =
|
|
860
|
+
const actionId = this.resolveButtonActionId(btn);
|
|
825
861
|
if (!actionId)
|
|
826
862
|
return;
|
|
827
863
|
this.action.emit({
|
|
@@ -836,6 +872,15 @@ class PraxisFormActionsComponent {
|
|
|
836
872
|
this.shortcutDisposers.push(dispose);
|
|
837
873
|
});
|
|
838
874
|
}
|
|
875
|
+
resolveButtonActionId(button) {
|
|
876
|
+
const id = button.id?.trim();
|
|
877
|
+
if (id === 'submit' || id === 'cancel' || id === 'reset')
|
|
878
|
+
return id;
|
|
879
|
+
return (button.action?.trim() ||
|
|
880
|
+
id ||
|
|
881
|
+
button.globalAction?.actionId?.trim() ||
|
|
882
|
+
undefined);
|
|
883
|
+
}
|
|
839
884
|
isSecondaryButton(button) {
|
|
840
885
|
const actionId = (button.id || button.action || '').toLowerCase();
|
|
841
886
|
return actionId === 'cancel' || actionId === 'reset';
|
|
@@ -6911,9 +6956,37 @@ class PraxisDynamicForm {
|
|
|
6911
6956
|
};
|
|
6912
6957
|
}
|
|
6913
6958
|
getDirtyFieldNames() {
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
.
|
|
6959
|
+
const dirtyFields = new Set();
|
|
6960
|
+
for (const [fieldName, control] of Object.entries(this.form.controls)) {
|
|
6961
|
+
if (control.dirty) {
|
|
6962
|
+
dirtyFields.add(fieldName);
|
|
6963
|
+
}
|
|
6964
|
+
this.collectDirtyFieldNames(control, fieldName, dirtyFields);
|
|
6965
|
+
}
|
|
6966
|
+
return Array.from(dirtyFields);
|
|
6967
|
+
}
|
|
6968
|
+
collectDirtyFieldNames(control, path, dirtyFields) {
|
|
6969
|
+
const childControls = control.controls;
|
|
6970
|
+
if (!childControls) {
|
|
6971
|
+
return;
|
|
6972
|
+
}
|
|
6973
|
+
if (Array.isArray(childControls)) {
|
|
6974
|
+
childControls.forEach((childControl, index) => {
|
|
6975
|
+
const childPath = `${path}.${index}`;
|
|
6976
|
+
if (childControl.dirty) {
|
|
6977
|
+
dirtyFields.add(childPath);
|
|
6978
|
+
}
|
|
6979
|
+
this.collectDirtyFieldNames(childControl, childPath, dirtyFields);
|
|
6980
|
+
});
|
|
6981
|
+
return;
|
|
6982
|
+
}
|
|
6983
|
+
for (const [childName, childControl] of Object.entries(childControls)) {
|
|
6984
|
+
const childPath = `${path}.${childName}`;
|
|
6985
|
+
if (childControl.dirty) {
|
|
6986
|
+
dirtyFields.add(childPath);
|
|
6987
|
+
}
|
|
6988
|
+
this.collectDirtyFieldNames(childControl, childPath, dirtyFields);
|
|
6989
|
+
}
|
|
6917
6990
|
}
|
|
6918
6991
|
buildCurrentFormRuntimeContext() {
|
|
6919
6992
|
const resourcePath = (this.resourcePath || '').trim();
|
|
@@ -7357,6 +7430,7 @@ class PraxisDynamicForm {
|
|
|
7357
7430
|
const emitAction = () => {
|
|
7358
7431
|
this.customAction.emit({
|
|
7359
7432
|
actionId,
|
|
7433
|
+
...(action.globalAction ? { globalAction: action.globalAction } : {}),
|
|
7360
7434
|
formData: this.form.value,
|
|
7361
7435
|
isValid: this.form.valid,
|
|
7362
7436
|
source: 'section-header',
|
|
@@ -7390,7 +7464,10 @@ class PraxisDynamicForm {
|
|
|
7390
7464
|
};
|
|
7391
7465
|
}
|
|
7392
7466
|
getSectionHeaderActionEventId(action) {
|
|
7393
|
-
return action.action?.trim() ||
|
|
7467
|
+
return (action.action?.trim() ||
|
|
7468
|
+
action.id.trim() ||
|
|
7469
|
+
action.globalAction?.actionId?.trim() ||
|
|
7470
|
+
'');
|
|
7394
7471
|
}
|
|
7395
7472
|
getSectionHeaderActionRuleKeys(section, action) {
|
|
7396
7473
|
const logicalId = this.getSectionHeaderActionEventId(action);
|
|
@@ -8162,7 +8239,7 @@ class PraxisDynamicForm {
|
|
|
8162
8239
|
}
|
|
8163
8240
|
}
|
|
8164
8241
|
_executeAction(button) {
|
|
8165
|
-
const actionId =
|
|
8242
|
+
const actionId = this.getFormActionEventId(button);
|
|
8166
8243
|
if (!actionId)
|
|
8167
8244
|
return;
|
|
8168
8245
|
switch (actionId) {
|
|
@@ -8198,7 +8275,8 @@ class PraxisDynamicForm {
|
|
|
8198
8275
|
default:
|
|
8199
8276
|
// This is a custom action
|
|
8200
8277
|
this.customAction.emit({
|
|
8201
|
-
actionId
|
|
8278
|
+
actionId,
|
|
8279
|
+
...(button.globalAction ? { globalAction: button.globalAction } : {}),
|
|
8202
8280
|
formData: this.form.value,
|
|
8203
8281
|
isValid: this.form.valid,
|
|
8204
8282
|
source: 'button',
|
|
@@ -8206,6 +8284,15 @@ class PraxisDynamicForm {
|
|
|
8206
8284
|
break;
|
|
8207
8285
|
}
|
|
8208
8286
|
}
|
|
8287
|
+
getFormActionEventId(button) {
|
|
8288
|
+
const id = button.id?.trim();
|
|
8289
|
+
if (id === 'submit' || id === 'cancel' || id === 'reset')
|
|
8290
|
+
return id;
|
|
8291
|
+
return (button.action?.trim() ||
|
|
8292
|
+
id ||
|
|
8293
|
+
button.globalAction?.actionId?.trim() ||
|
|
8294
|
+
undefined);
|
|
8295
|
+
}
|
|
8209
8296
|
async onSubmit() {
|
|
8210
8297
|
if (this.submitting)
|
|
8211
8298
|
return;
|
|
@@ -9082,6 +9169,7 @@ class PraxisDynamicForm {
|
|
|
9082
9169
|
materialDesign: fieldMeta.materialDesign,
|
|
9083
9170
|
clearButton: fieldMeta.clearButton,
|
|
9084
9171
|
extra: fieldMeta.extra,
|
|
9172
|
+
optionSource: fieldMeta.optionSource,
|
|
9085
9173
|
controlType,
|
|
9086
9174
|
};
|
|
9087
9175
|
// Seed especÃficos para NUMBER (numericTextBox)
|
|
@@ -9843,6 +9931,22 @@ class PraxisDynamicForm {
|
|
|
9843
9931
|
});
|
|
9844
9932
|
return out;
|
|
9845
9933
|
}
|
|
9934
|
+
mergeMetadataObjectPatch(current, patch) {
|
|
9935
|
+
if (!patch || typeof patch !== 'object' || Array.isArray(patch)) {
|
|
9936
|
+
return patch;
|
|
9937
|
+
}
|
|
9938
|
+
const base = current && typeof current === 'object' && !Array.isArray(current)
|
|
9939
|
+
? { ...current }
|
|
9940
|
+
: {};
|
|
9941
|
+
Object.entries(patch).forEach(([key, value]) => {
|
|
9942
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
9943
|
+
base[key] = this.mergeMetadataObjectPatch(base[key], value);
|
|
9944
|
+
return;
|
|
9945
|
+
}
|
|
9946
|
+
base[key] = value;
|
|
9947
|
+
});
|
|
9948
|
+
return base;
|
|
9949
|
+
}
|
|
9846
9950
|
deleteMetadataPath(target, path) {
|
|
9847
9951
|
if (!target || typeof path !== 'string' || !path.trim())
|
|
9848
9952
|
return;
|
|
@@ -10034,6 +10138,9 @@ class PraxisDynamicForm {
|
|
|
10034
10138
|
fm.dataAttributes = patch.dataAttributes;
|
|
10035
10139
|
}
|
|
10036
10140
|
}
|
|
10141
|
+
if (patch.optionSource !== undefined) {
|
|
10142
|
+
fm.optionSource = this.mergeMetadataObjectPatch(fm.optionSource, patch.optionSource);
|
|
10143
|
+
}
|
|
10037
10144
|
// Generic extras passthrough: merge into fm.extra to allow component-specific appearance/behavior
|
|
10038
10145
|
if (patch.extra !== undefined) {
|
|
10039
10146
|
const currExtra = (fm.extra && typeof fm.extra === 'object') ? fm.extra : {};
|
|
@@ -14878,6 +14985,7 @@ const ACTIONS_EDITOR_I18N_CONFIG = {
|
|
|
14878
14985
|
'global.section.essential': 'Essencial',
|
|
14879
14986
|
'global.section.dialogOptional': 'Diálogo (opcional)',
|
|
14880
14987
|
'global.validation.validJson': 'Informe um JSON válido.',
|
|
14988
|
+
'global.validation.requiredField': 'Campo obrigatório.',
|
|
14881
14989
|
'tabs.defaultButtons': 'Botões padrão',
|
|
14882
14990
|
'tabs.customButtons': 'Botões customizados',
|
|
14883
14991
|
'tabs.layout': 'Layout',
|
|
@@ -14964,6 +15072,7 @@ const ACTIONS_EDITOR_I18N_CONFIG = {
|
|
|
14964
15072
|
'global.section.essential': 'Essential',
|
|
14965
15073
|
'global.section.dialogOptional': 'Dialog (optional)',
|
|
14966
15074
|
'global.validation.validJson': 'Provide valid JSON.',
|
|
15075
|
+
'global.validation.requiredField': 'Required field.',
|
|
14967
15076
|
'tabs.defaultButtons': 'Default buttons',
|
|
14968
15077
|
'tabs.customButtons': 'Custom buttons',
|
|
14969
15078
|
'tabs.layout': 'Layout',
|
|
@@ -15051,204 +15160,32 @@ const ACTIONS_EDITOR_I18N_CONFIG = {
|
|
|
15051
15160
|
|
|
15052
15161
|
function buildGlobalActionCatalog(params) {
|
|
15053
15162
|
const merged = new Map();
|
|
15054
|
-
for (const spec of params.legacySpecs) {
|
|
15055
|
-
const mapped = params.mapLegacySpec
|
|
15056
|
-
? params.mapLegacySpec(spec)
|
|
15057
|
-
: { ...spec };
|
|
15058
|
-
merged.set(mapped.id, mapped);
|
|
15059
|
-
}
|
|
15060
15163
|
const source = params.injectedCatalog.length
|
|
15061
15164
|
? params.injectedCatalog
|
|
15062
15165
|
: params.fallbackCatalog;
|
|
15063
15166
|
for (const entry of source) {
|
|
15064
|
-
if (merged.has(entry.id))
|
|
15065
|
-
continue;
|
|
15066
15167
|
const mapped = params.mapCatalogEntry
|
|
15067
15168
|
? params.mapCatalogEntry(entry)
|
|
15068
15169
|
: {
|
|
15069
15170
|
id: entry.id,
|
|
15070
15171
|
label: entry.label,
|
|
15071
15172
|
description: entry.description,
|
|
15173
|
+
payloadSchema: entry.payloadSchema,
|
|
15072
15174
|
};
|
|
15073
15175
|
merged.set(mapped.id, mapped);
|
|
15074
15176
|
}
|
|
15075
15177
|
return Array.from(merged.values());
|
|
15076
15178
|
}
|
|
15077
|
-
function
|
|
15078
|
-
const
|
|
15079
|
-
|
|
15080
|
-
return { id: customActionValue, param: '', isGlobal: false, raw: '' };
|
|
15081
|
-
}
|
|
15082
|
-
const [prefix, ...rest] = raw.split(':');
|
|
15083
|
-
const candidate = catalog.find((item) => item.id === prefix);
|
|
15084
|
-
if (candidate) {
|
|
15085
|
-
return {
|
|
15086
|
-
id: candidate.id,
|
|
15087
|
-
param: rest.join(':'),
|
|
15088
|
-
isGlobal: true,
|
|
15089
|
-
raw,
|
|
15090
|
-
};
|
|
15091
|
-
}
|
|
15092
|
-
const exact = catalog.find((item) => item.id === raw);
|
|
15093
|
-
if (exact) {
|
|
15094
|
-
return { id: exact.id, param: '', isGlobal: true, raw };
|
|
15095
|
-
}
|
|
15096
|
-
return { id: customActionValue, param: '', isGlobal: false, raw };
|
|
15097
|
-
}
|
|
15098
|
-
function getActionParamInfo(value, catalog, customActionValue = '__custom__') {
|
|
15099
|
-
const parsed = parseActionValue(value, catalog, customActionValue);
|
|
15100
|
-
const param = parsed.param || '';
|
|
15101
|
-
const trimmed = param.trim();
|
|
15102
|
-
const looksJson = (trimmed.startsWith('{') && trimmed.endsWith('}')) ||
|
|
15103
|
-
(trimmed.startsWith('[') && trimmed.endsWith(']'));
|
|
15104
|
-
if (looksJson) {
|
|
15105
|
-
try {
|
|
15106
|
-
const json = JSON.parse(trimmed);
|
|
15107
|
-
if (json && typeof json === 'object') {
|
|
15108
|
-
return { ...parsed, param, isJson: true, json };
|
|
15109
|
-
}
|
|
15110
|
-
}
|
|
15111
|
-
catch {
|
|
15112
|
-
// ignore malformed legacy value
|
|
15113
|
-
}
|
|
15114
|
-
}
|
|
15115
|
-
return { ...parsed, param, isJson: false, json: undefined };
|
|
15116
|
-
}
|
|
15117
|
-
function normalizeSurfaceOpenPayload(payload) {
|
|
15118
|
-
return {
|
|
15119
|
-
presentation: payload?.presentation === 'drawer' ? 'drawer' : 'modal',
|
|
15120
|
-
title: payload?.title,
|
|
15121
|
-
subtitle: payload?.subtitle,
|
|
15122
|
-
icon: payload?.icon,
|
|
15123
|
-
size: payload?.size && Object.keys(payload.size).length
|
|
15124
|
-
? { ...payload.size }
|
|
15125
|
-
: undefined,
|
|
15126
|
-
widget: {
|
|
15127
|
-
id: String(payload?.widget?.id || ''),
|
|
15128
|
-
inputs: { ...(payload?.widget?.inputs || {}) },
|
|
15129
|
-
bindingOrder: payload?.widget?.bindingOrder?.length
|
|
15130
|
-
? [...payload.widget.bindingOrder]
|
|
15131
|
-
: undefined,
|
|
15132
|
-
},
|
|
15133
|
-
bindings: payload?.bindings?.length
|
|
15134
|
-
? payload.bindings.map((binding) => ({
|
|
15135
|
-
...binding,
|
|
15136
|
-
}))
|
|
15137
|
-
: [],
|
|
15138
|
-
context: payload?.context &&
|
|
15139
|
-
typeof payload.context === 'object' &&
|
|
15140
|
-
!Array.isArray(payload.context)
|
|
15141
|
-
? { ...payload.context }
|
|
15142
|
-
: undefined,
|
|
15143
|
-
};
|
|
15144
|
-
}
|
|
15145
|
-
function serializeGlobalActionParam(actionId, values, existingJson) {
|
|
15146
|
-
const payload = buildGlobalActionPayload(values, existingJson);
|
|
15147
|
-
const preferJson = !!(existingJson && Object.keys(existingJson).length);
|
|
15148
|
-
const primary = buildPrimaryParam(actionId, values, payload, preferJson);
|
|
15149
|
-
if (primary)
|
|
15150
|
-
return primary;
|
|
15151
|
-
if (!payload || Object.keys(payload).length === 0)
|
|
15152
|
-
return '';
|
|
15153
|
-
return JSON.stringify(payload);
|
|
15154
|
-
}
|
|
15155
|
-
function isRequiredParamMissing(value, catalog, customActionValue = '__custom__') {
|
|
15156
|
-
const parsed = parseActionValue(value, catalog, customActionValue);
|
|
15157
|
-
const spec = catalog.find((item) => item.id === parsed.id);
|
|
15158
|
-
if (!parsed.isGlobal || !spec?.param?.required)
|
|
15159
|
-
return false;
|
|
15160
|
-
return !String(parsed.param || '').trim();
|
|
15179
|
+
function isGlobalActionId(value, catalog) {
|
|
15180
|
+
const id = String(value || '').trim();
|
|
15181
|
+
return !!id && catalog.some((item) => item.id === id);
|
|
15161
15182
|
}
|
|
15162
|
-
function
|
|
15163
|
-
if (fieldType === 'toggle') {
|
|
15164
|
-
return Boolean(actual) === Boolean(draft);
|
|
15165
|
-
}
|
|
15166
|
-
if (fieldType === 'number') {
|
|
15167
|
-
return String(actual ?? '') === String(draft ?? '');
|
|
15168
|
-
}
|
|
15169
|
-
return String(actual ?? '') === String(draft ?? '');
|
|
15170
|
-
}
|
|
15171
|
-
function parseMethodAndUrl(raw) {
|
|
15172
|
-
const text = String(raw || '').trim();
|
|
15173
|
-
if (!text)
|
|
15174
|
-
return {};
|
|
15175
|
-
const idx = text.indexOf(':');
|
|
15176
|
-
if (idx > 0) {
|
|
15177
|
-
return {
|
|
15178
|
-
method: text.slice(0, idx).toUpperCase(),
|
|
15179
|
-
url: text.slice(idx + 1),
|
|
15180
|
-
};
|
|
15181
|
-
}
|
|
15182
|
-
return { url: text };
|
|
15183
|
-
}
|
|
15184
|
-
function stringifyJson(value) {
|
|
15185
|
-
if (value === undefined || value === null || value === '')
|
|
15186
|
-
return '';
|
|
15187
|
-
if (typeof value === 'string')
|
|
15188
|
-
return value;
|
|
15189
|
-
try {
|
|
15190
|
-
return JSON.stringify(value, null, 2);
|
|
15191
|
-
}
|
|
15192
|
-
catch {
|
|
15193
|
-
return '';
|
|
15194
|
-
}
|
|
15195
|
-
}
|
|
15196
|
-
function buildPrimaryParam(actionId, values, payload, preferJson) {
|
|
15197
|
-
if (preferJson)
|
|
15198
|
-
return '';
|
|
15199
|
-
const onlyPayload = payload ? Object.keys(payload).length : 0;
|
|
15200
|
-
const text = (value) => String(value || '').trim();
|
|
15201
|
-
if (actionId === 'navigate' || actionId === 'openUrl') {
|
|
15202
|
-
const url = text(values.url);
|
|
15203
|
-
if (url && onlyPayload === 1)
|
|
15204
|
-
return url;
|
|
15205
|
-
}
|
|
15206
|
-
if (actionId === 'showAlert' ||
|
|
15207
|
-
actionId === 'confirm' ||
|
|
15208
|
-
actionId === 'alert' ||
|
|
15209
|
-
actionId === 'prompt') {
|
|
15210
|
-
const message = text(values.message);
|
|
15211
|
-
if (message && onlyPayload === 1)
|
|
15212
|
-
return message;
|
|
15213
|
-
}
|
|
15214
|
-
if (actionId === 'download') {
|
|
15215
|
-
const url = text(values.url);
|
|
15216
|
-
if (url && onlyPayload === 1)
|
|
15217
|
-
return url;
|
|
15218
|
-
}
|
|
15219
|
-
if (actionId === 'copyToClipboard') {
|
|
15220
|
-
const copyText = text(values.text);
|
|
15221
|
-
if (copyText && onlyPayload === 1)
|
|
15222
|
-
return copyText;
|
|
15223
|
-
}
|
|
15224
|
-
if (actionId === 'saveFormDraft' ||
|
|
15225
|
-
actionId === 'loadFormDraft' ||
|
|
15226
|
-
actionId === 'clearFormDraft') {
|
|
15227
|
-
const key = text(values.key);
|
|
15228
|
-
if (key && onlyPayload === 1)
|
|
15229
|
-
return key;
|
|
15230
|
-
}
|
|
15231
|
-
if (actionId === 'refreshOptions' || actionId === 'loadDependentData') {
|
|
15232
|
-
const field = text(values.field);
|
|
15233
|
-
if (field && onlyPayload === 1)
|
|
15234
|
-
return field;
|
|
15235
|
-
}
|
|
15236
|
-
if (actionId === 'apiCall') {
|
|
15237
|
-
const method = text(values.method);
|
|
15238
|
-
const url = text(values.url);
|
|
15239
|
-
if (url && onlyPayload <= 2) {
|
|
15240
|
-
return method ? `${method}:${url}` : url;
|
|
15241
|
-
}
|
|
15242
|
-
}
|
|
15243
|
-
return '';
|
|
15244
|
-
}
|
|
15245
|
-
function buildGlobalActionPayload(values, existingJson) {
|
|
15183
|
+
function normalizeGlobalActionPayload(values, existingPayload) {
|
|
15246
15184
|
const payload = {};
|
|
15247
15185
|
const hasText = (value) => {
|
|
15248
15186
|
if (value === undefined || value === null)
|
|
15249
15187
|
return false;
|
|
15250
|
-
|
|
15251
|
-
return text.length > 0;
|
|
15188
|
+
return String(value).trim().length > 0;
|
|
15252
15189
|
};
|
|
15253
15190
|
const toText = (value) => hasText(value) ? String(value).trim() : undefined;
|
|
15254
15191
|
const toNumber = (value) => {
|
|
@@ -15277,8 +15214,8 @@ function buildGlobalActionPayload(values, existingJson) {
|
|
|
15277
15214
|
payload[key] = true;
|
|
15278
15215
|
}
|
|
15279
15216
|
else if (value === false &&
|
|
15280
|
-
|
|
15281
|
-
Object.prototype.hasOwnProperty.call(
|
|
15217
|
+
existingPayload &&
|
|
15218
|
+
Object.prototype.hasOwnProperty.call(existingPayload, key)) {
|
|
15282
15219
|
payload[key] = false;
|
|
15283
15220
|
}
|
|
15284
15221
|
};
|
|
@@ -15316,101 +15253,55 @@ function buildGlobalActionPayload(values, existingJson) {
|
|
|
15316
15253
|
...(animationDuration !== undefined ? { duration: animationDuration } : {}),
|
|
15317
15254
|
};
|
|
15318
15255
|
}
|
|
15319
|
-
const width
|
|
15320
|
-
|
|
15321
|
-
|
|
15322
|
-
|
|
15323
|
-
|
|
15324
|
-
const maxHeight = toText(values.maxHeight);
|
|
15325
|
-
if (width)
|
|
15326
|
-
payload.width = width;
|
|
15327
|
-
if (height)
|
|
15328
|
-
payload.height = height;
|
|
15329
|
-
if (minWidth)
|
|
15330
|
-
payload.minWidth = minWidth;
|
|
15331
|
-
if (maxWidth)
|
|
15332
|
-
payload.maxWidth = maxWidth;
|
|
15333
|
-
if (minHeight)
|
|
15334
|
-
payload.minHeight = minHeight;
|
|
15335
|
-
if (maxHeight)
|
|
15336
|
-
payload.maxHeight = maxHeight;
|
|
15256
|
+
for (const key of ['width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight']) {
|
|
15257
|
+
const value = toText(values[key]);
|
|
15258
|
+
if (value)
|
|
15259
|
+
payload[key] = value;
|
|
15260
|
+
}
|
|
15337
15261
|
includeBool('disableClose', values.disableClose);
|
|
15338
15262
|
includeBool('hasBackdrop', values.hasBackdrop);
|
|
15339
15263
|
includeBool('closeOnBackdropClick', values.closeOnBackdropClick);
|
|
15340
15264
|
includeBool('autoFocus', values.autoFocus);
|
|
15341
15265
|
includeBool('restoreFocus', values.restoreFocus);
|
|
15342
|
-
const
|
|
15343
|
-
|
|
15344
|
-
|
|
15345
|
-
|
|
15346
|
-
|
|
15347
|
-
|
|
15348
|
-
|
|
15349
|
-
|
|
15350
|
-
|
|
15351
|
-
|
|
15352
|
-
|
|
15353
|
-
|
|
15354
|
-
|
|
15355
|
-
|
|
15356
|
-
|
|
15357
|
-
|
|
15358
|
-
|
|
15359
|
-
|
|
15360
|
-
|
|
15361
|
-
|
|
15362
|
-
|
|
15266
|
+
for (const key of [
|
|
15267
|
+
'backdropClass',
|
|
15268
|
+
'panelClass',
|
|
15269
|
+
'autoFocusedElement',
|
|
15270
|
+
'ariaRole',
|
|
15271
|
+
'ariaLabel',
|
|
15272
|
+
'ariaLabelledBy',
|
|
15273
|
+
'ariaDescribedBy',
|
|
15274
|
+
'confirmLabel',
|
|
15275
|
+
'cancelLabel',
|
|
15276
|
+
'okLabel',
|
|
15277
|
+
'placeholder',
|
|
15278
|
+
'defaultValue',
|
|
15279
|
+
'url',
|
|
15280
|
+
'target',
|
|
15281
|
+
'level',
|
|
15282
|
+
'text',
|
|
15283
|
+
]) {
|
|
15284
|
+
const value = toText(values[key]);
|
|
15285
|
+
if (value)
|
|
15286
|
+
payload[key] = value;
|
|
15287
|
+
}
|
|
15363
15288
|
const styles = toJson(values.styles);
|
|
15364
15289
|
if (styles)
|
|
15365
15290
|
payload.styles = styles;
|
|
15366
|
-
|
|
15367
|
-
if (confirmLabel)
|
|
15368
|
-
payload.confirmLabel = confirmLabel;
|
|
15369
|
-
const cancelLabel = toText(values.cancelLabel);
|
|
15370
|
-
if (cancelLabel)
|
|
15371
|
-
payload.cancelLabel = cancelLabel;
|
|
15372
|
-
const okLabel = toText(values.okLabel);
|
|
15373
|
-
if (okLabel)
|
|
15374
|
-
payload.okLabel = okLabel;
|
|
15375
|
-
const placeholder = toText(values.placeholder);
|
|
15376
|
-
if (placeholder)
|
|
15377
|
-
payload.placeholder = placeholder;
|
|
15378
|
-
const defaultValue = toText(values.defaultValue);
|
|
15379
|
-
if (defaultValue)
|
|
15380
|
-
payload.defaultValue = defaultValue;
|
|
15381
|
-
if (values.mode === true) {
|
|
15291
|
+
if (values.mode === true)
|
|
15382
15292
|
payload.mode = 'dialog';
|
|
15383
|
-
}
|
|
15384
|
-
const url = toText(values.url);
|
|
15385
|
-
if (url)
|
|
15386
|
-
payload.url = url;
|
|
15387
|
-
const target = toText(values.target);
|
|
15388
|
-
if (target)
|
|
15389
|
-
payload.target = target;
|
|
15390
15293
|
includeBool('newTab', values.newTab);
|
|
15391
15294
|
includeBool('replaceUrl', values.replaceUrl);
|
|
15392
|
-
|
|
15393
|
-
|
|
15394
|
-
|
|
15395
|
-
|
|
15396
|
-
if (bodySource)
|
|
15397
|
-
payload.bodySource = bodySource;
|
|
15295
|
+
includeBool('useFieldValue', values.useFieldValue);
|
|
15296
|
+
const params = toJson(values.params);
|
|
15297
|
+
if (params !== undefined)
|
|
15298
|
+
payload.params = params;
|
|
15398
15299
|
const headers = toJson(values.headers);
|
|
15399
|
-
if (headers)
|
|
15300
|
+
if (headers !== undefined)
|
|
15400
15301
|
payload.headers = headers;
|
|
15401
15302
|
const body = toJson(values.body);
|
|
15402
|
-
if (body)
|
|
15303
|
+
if (body !== undefined)
|
|
15403
15304
|
payload.body = body;
|
|
15404
|
-
const key = toText(values.key);
|
|
15405
|
-
if (key)
|
|
15406
|
-
payload.key = key;
|
|
15407
|
-
const field = toText(values.field);
|
|
15408
|
-
if (field)
|
|
15409
|
-
payload.field = field;
|
|
15410
|
-
const copyText = toText(values.text);
|
|
15411
|
-
if (copyText)
|
|
15412
|
-
payload.text = copyText;
|
|
15413
|
-
includeBool('useFieldValue', values.useFieldValue);
|
|
15414
15305
|
const contentType = toText(values.contentType);
|
|
15415
15306
|
if (contentType)
|
|
15416
15307
|
payload.contentType = contentType;
|
|
@@ -15429,6 +15320,65 @@ function buildGlobalActionPayload(values, existingJson) {
|
|
|
15429
15320
|
}
|
|
15430
15321
|
return payload;
|
|
15431
15322
|
}
|
|
15323
|
+
function buildGlobalActionRef(actionId, values, existingPayload) {
|
|
15324
|
+
const payload = normalizeGlobalActionPayload(values, existingPayload);
|
|
15325
|
+
return Object.keys(payload).length
|
|
15326
|
+
? { actionId, payload }
|
|
15327
|
+
: { actionId };
|
|
15328
|
+
}
|
|
15329
|
+
function isRequiredParamMissing(ref, catalog) {
|
|
15330
|
+
const spec = catalog.find((item) => item.id === ref?.actionId);
|
|
15331
|
+
return isRequiredGlobalActionPayloadMissing(ref, spec);
|
|
15332
|
+
}
|
|
15333
|
+
function isEquivalentActionFieldValue(actual, draft, fieldType) {
|
|
15334
|
+
if (fieldType === 'toggle') {
|
|
15335
|
+
return Boolean(actual) === Boolean(draft);
|
|
15336
|
+
}
|
|
15337
|
+
if (fieldType === 'number') {
|
|
15338
|
+
return String(actual ?? '') === String(draft ?? '');
|
|
15339
|
+
}
|
|
15340
|
+
return String(actual ?? '') === String(draft ?? '');
|
|
15341
|
+
}
|
|
15342
|
+
function stringifyJson(value) {
|
|
15343
|
+
if (value === undefined || value === null || value === '')
|
|
15344
|
+
return '';
|
|
15345
|
+
if (typeof value === 'string')
|
|
15346
|
+
return value;
|
|
15347
|
+
try {
|
|
15348
|
+
return JSON.stringify(value, null, 2);
|
|
15349
|
+
}
|
|
15350
|
+
catch {
|
|
15351
|
+
return '';
|
|
15352
|
+
}
|
|
15353
|
+
}
|
|
15354
|
+
function normalizeSurfaceOpenPayload(payload) {
|
|
15355
|
+
return {
|
|
15356
|
+
presentation: payload?.presentation === 'drawer' ? 'drawer' : 'modal',
|
|
15357
|
+
title: payload?.title,
|
|
15358
|
+
subtitle: payload?.subtitle,
|
|
15359
|
+
icon: payload?.icon,
|
|
15360
|
+
size: payload?.size && Object.keys(payload.size).length
|
|
15361
|
+
? { ...payload.size }
|
|
15362
|
+
: undefined,
|
|
15363
|
+
widget: {
|
|
15364
|
+
id: String(payload?.widget?.id || ''),
|
|
15365
|
+
inputs: { ...(payload?.widget?.inputs || {}) },
|
|
15366
|
+
bindingOrder: payload?.widget?.bindingOrder?.length
|
|
15367
|
+
? [...payload.widget.bindingOrder]
|
|
15368
|
+
: undefined,
|
|
15369
|
+
},
|
|
15370
|
+
bindings: payload?.bindings?.length
|
|
15371
|
+
? payload.bindings.map((binding) => ({
|
|
15372
|
+
...binding,
|
|
15373
|
+
}))
|
|
15374
|
+
: [],
|
|
15375
|
+
context: payload?.context &&
|
|
15376
|
+
typeof payload.context === 'object' &&
|
|
15377
|
+
!Array.isArray(payload.context)
|
|
15378
|
+
? { ...payload.context }
|
|
15379
|
+
: undefined,
|
|
15380
|
+
};
|
|
15381
|
+
}
|
|
15432
15382
|
|
|
15433
15383
|
let ActionsEditorComponent$1 = class ActionsEditorComponent {
|
|
15434
15384
|
config;
|
|
@@ -15438,9 +15388,7 @@ let ActionsEditorComponent$1 = class ActionsEditorComponent {
|
|
|
15438
15388
|
customActionStyleErrors = new Map();
|
|
15439
15389
|
globalActionCatalogSource = inject(GLOBAL_ACTION_CATALOG, { optional: true }) ?? [];
|
|
15440
15390
|
i18n = inject(PraxisI18nService);
|
|
15441
|
-
legacyActionSpecs = GLOBAL_ACTION_SPEC_CATALOG;
|
|
15442
15391
|
globalActionCatalog = buildGlobalActionCatalog({
|
|
15443
|
-
legacySpecs: this.legacyActionSpecs,
|
|
15444
15392
|
injectedCatalog: getGlobalActionCatalog(this.globalActionCatalogSource),
|
|
15445
15393
|
fallbackCatalog: PRAXIS_GLOBAL_ACTION_CATALOG,
|
|
15446
15394
|
mapCatalogEntry: (entry) => this.mapCatalogEntryToActionSpec(entry),
|
|
@@ -15529,17 +15477,17 @@ let ActionsEditorComponent$1 = class ActionsEditorComponent {
|
|
|
15529
15477
|
getActionSpecById(id) {
|
|
15530
15478
|
return this.globalActionCatalog.find((item) => item.id === id);
|
|
15531
15479
|
}
|
|
15532
|
-
getCustomActionSelectValue(
|
|
15533
|
-
|
|
15534
|
-
return parsed.isGlobal ? parsed.id : this.customActionValue;
|
|
15480
|
+
getCustomActionSelectValue(action) {
|
|
15481
|
+
return action?.globalAction?.actionId || this.customActionValue;
|
|
15535
15482
|
}
|
|
15536
|
-
getCustomActionParam(
|
|
15537
|
-
const
|
|
15538
|
-
|
|
15483
|
+
getCustomActionParam(action) {
|
|
15484
|
+
const payload = action?.globalAction?.payload;
|
|
15485
|
+
if (payload === undefined || payload === null)
|
|
15486
|
+
return '';
|
|
15487
|
+
return typeof payload === 'string' ? payload : stringifyJson(payload);
|
|
15539
15488
|
}
|
|
15540
|
-
getCustomActionCustomValue(
|
|
15541
|
-
|
|
15542
|
-
return parsed.isGlobal ? '' : parsed.raw;
|
|
15489
|
+
getCustomActionCustomValue(action) {
|
|
15490
|
+
return action?.globalAction ? '' : action?.action || '';
|
|
15543
15491
|
}
|
|
15544
15492
|
onCustomActionSelectChange(index, value) {
|
|
15545
15493
|
const current = this.actions.custom?.[index];
|
|
@@ -15547,31 +15495,36 @@ let ActionsEditorComponent$1 = class ActionsEditorComponent {
|
|
|
15547
15495
|
return;
|
|
15548
15496
|
this.clearActionFieldState(current);
|
|
15549
15497
|
if (value === this.customActionValue) {
|
|
15550
|
-
|
|
15551
|
-
if (parsed.isGlobal) {
|
|
15552
|
-
this.updateCustomAction(index, 'action', '');
|
|
15553
|
-
}
|
|
15498
|
+
this.updateCustomActionRef(index, undefined);
|
|
15554
15499
|
return;
|
|
15555
15500
|
}
|
|
15556
|
-
const
|
|
15557
|
-
|
|
15558
|
-
this.updateCustomAction(index, 'action', param ? `${value}:${param}` : value);
|
|
15501
|
+
const payload = current.globalAction?.actionId === value ? current.globalAction.payload : undefined;
|
|
15502
|
+
this.updateCustomActionRef(index, payload !== undefined ? { actionId: value, payload } : { actionId: value });
|
|
15559
15503
|
}
|
|
15560
15504
|
onCustomActionParamChange(index, value) {
|
|
15561
15505
|
const current = this.actions.custom?.[index];
|
|
15562
15506
|
if (!current)
|
|
15563
15507
|
return;
|
|
15564
|
-
const
|
|
15565
|
-
|
|
15566
|
-
|
|
15508
|
+
const id = current.globalAction?.actionId;
|
|
15509
|
+
if (!id)
|
|
15510
|
+
return;
|
|
15511
|
+
const trimmed = String(value || '').trim();
|
|
15512
|
+
if (!trimmed) {
|
|
15513
|
+
this.updateCustomActionRef(index, { actionId: id });
|
|
15514
|
+
return;
|
|
15515
|
+
}
|
|
15516
|
+
try {
|
|
15517
|
+
this.updateCustomActionRef(index, { actionId: id, payload: JSON.parse(trimmed) });
|
|
15518
|
+
}
|
|
15519
|
+
catch {
|
|
15520
|
+
this.updateCustomActionRef(index, { actionId: id, payload: trimmed });
|
|
15521
|
+
}
|
|
15567
15522
|
}
|
|
15568
15523
|
onCustomActionCustomChange(index, value) {
|
|
15569
15524
|
this.updateCustomAction(index, 'action', value);
|
|
15570
15525
|
}
|
|
15571
|
-
|
|
15572
|
-
|
|
15573
|
-
return false;
|
|
15574
|
-
return this.globalActionCatalog.some((item) => item.id === id);
|
|
15526
|
+
isCanonicalGlobalActionId(id) {
|
|
15527
|
+
return isGlobalActionId(id, this.globalActionCatalog);
|
|
15575
15528
|
}
|
|
15576
15529
|
getActionCatalogLabel(spec) {
|
|
15577
15530
|
if (spec?.id === 'surface.open') {
|
|
@@ -15607,6 +15560,7 @@ let ActionsEditorComponent$1 = class ActionsEditorComponent {
|
|
|
15607
15560
|
id: entry.id,
|
|
15608
15561
|
label: this.getActionCatalogLabel(entry),
|
|
15609
15562
|
description: this.getActionCatalogDescription(entry),
|
|
15563
|
+
payloadSchema: entry.payloadSchema,
|
|
15610
15564
|
param: hasPayloadSchema
|
|
15611
15565
|
? {
|
|
15612
15566
|
label: this.tx('globalAction.payload.label', 'Payload (optional JSON)'),
|
|
@@ -15620,20 +15574,16 @@ let ActionsEditorComponent$1 = class ActionsEditorComponent {
|
|
|
15620
15574
|
};
|
|
15621
15575
|
}
|
|
15622
15576
|
getGlobalActionSchema(action) {
|
|
15623
|
-
|
|
15624
|
-
if (!parsed.isGlobal)
|
|
15625
|
-
return undefined;
|
|
15626
|
-
return getGlobalActionUiSchema(parsed.id);
|
|
15577
|
+
return getGlobalActionUiSchema(action.globalAction?.actionId);
|
|
15627
15578
|
}
|
|
15628
15579
|
getSurfaceOpenActionPayload(action) {
|
|
15629
|
-
|
|
15630
|
-
const payload = info.isJson && info.json && typeof info.json === 'object' && !Array.isArray(info.json)
|
|
15631
|
-
? info.json
|
|
15632
|
-
: undefined;
|
|
15633
|
-
return normalizeSurfaceOpenPayload(payload);
|
|
15580
|
+
return normalizeSurfaceOpenPayload(action.globalAction?.payload);
|
|
15634
15581
|
}
|
|
15635
15582
|
onSurfaceOpenActionPayloadChange(action, payload, index) {
|
|
15636
|
-
this.
|
|
15583
|
+
this.updateCustomActionRef(index, {
|
|
15584
|
+
actionId: 'surface.open',
|
|
15585
|
+
payload: normalizeSurfaceOpenPayload(payload),
|
|
15586
|
+
});
|
|
15637
15587
|
}
|
|
15638
15588
|
shouldShowGlobalActionField(action, field) {
|
|
15639
15589
|
if (!field.dependsOnKey)
|
|
@@ -15644,6 +15594,18 @@ let ActionsEditorComponent$1 = class ActionsEditorComponent {
|
|
|
15644
15594
|
const values = this.collectGlobalActionFieldValues(action, schema.fields);
|
|
15645
15595
|
return String(values[field.dependsOnKey] ?? '') === String(field.dependsOnValue ?? '');
|
|
15646
15596
|
}
|
|
15597
|
+
isGlobalActionFieldRequired(action, field) {
|
|
15598
|
+
const actionId = action.globalAction?.actionId;
|
|
15599
|
+
const spec = this.globalActionCatalog.find((item) => item.id === actionId);
|
|
15600
|
+
return getRequiredGlobalActionPayloadKeys(actionId, spec).includes(field.key);
|
|
15601
|
+
}
|
|
15602
|
+
isGlobalActionFieldMissing(action, field) {
|
|
15603
|
+
if (!this.isGlobalActionFieldRequired(action, field))
|
|
15604
|
+
return false;
|
|
15605
|
+
if (!this.shouldShowGlobalActionField(action, field))
|
|
15606
|
+
return false;
|
|
15607
|
+
return !hasMeaningfulGlobalActionPayloadValue(this.getGlobalActionFieldValue(action, field));
|
|
15608
|
+
}
|
|
15647
15609
|
hasActionFieldError(action, key) {
|
|
15648
15610
|
const errors = this.actionFieldErrors.get(this.getActionDraftKey(action));
|
|
15649
15611
|
return !!errors?.[key];
|
|
@@ -15655,48 +15617,19 @@ let ActionsEditorComponent$1 = class ActionsEditorComponent {
|
|
|
15655
15617
|
getGlobalActionFieldValue(action, field) {
|
|
15656
15618
|
const draftKey = this.getActionDraftKey(action);
|
|
15657
15619
|
const draft = this.actionFieldDrafts.get(draftKey)?.[field.key];
|
|
15658
|
-
const
|
|
15659
|
-
const raw = info.param || '';
|
|
15660
|
-
const json = info.json || {};
|
|
15661
|
-
const hasJson = info.isJson;
|
|
15620
|
+
const json = action.globalAction?.payload || {};
|
|
15662
15621
|
let value;
|
|
15663
15622
|
switch (field.key) {
|
|
15664
15623
|
case 'message':
|
|
15665
|
-
value = json.message ?? json.text ??
|
|
15624
|
+
value = json.message ?? json.text ?? '';
|
|
15666
15625
|
break;
|
|
15667
15626
|
case 'url':
|
|
15668
|
-
|
|
15669
|
-
value = json.url;
|
|
15670
|
-
break;
|
|
15671
|
-
}
|
|
15672
|
-
if (!hasJson && info.id === 'apiCall') {
|
|
15673
|
-
const parsed = parseMethodAndUrl(raw);
|
|
15674
|
-
value = parsed.url || '';
|
|
15675
|
-
break;
|
|
15676
|
-
}
|
|
15677
|
-
value = hasJson ? '' : raw;
|
|
15678
|
-
break;
|
|
15679
|
-
case 'method': {
|
|
15680
|
-
if (json.method) {
|
|
15681
|
-
value = String(json.method);
|
|
15682
|
-
break;
|
|
15683
|
-
}
|
|
15684
|
-
if (!hasJson) {
|
|
15685
|
-
const parsed = parseMethodAndUrl(raw);
|
|
15686
|
-
value = parsed.method || '';
|
|
15687
|
-
break;
|
|
15688
|
-
}
|
|
15689
|
-
value = '';
|
|
15690
|
-
break;
|
|
15691
|
-
}
|
|
15692
|
-
case 'bodySource':
|
|
15693
|
-
value = json.bodySource ?? '';
|
|
15627
|
+
value = json.url ?? '';
|
|
15694
15628
|
break;
|
|
15629
|
+
case 'params':
|
|
15695
15630
|
case 'headers':
|
|
15696
|
-
value = stringifyJson(json.headers);
|
|
15697
|
-
break;
|
|
15698
15631
|
case 'body':
|
|
15699
|
-
value = stringifyJson(json.
|
|
15632
|
+
value = stringifyJson(json[field.key]);
|
|
15700
15633
|
break;
|
|
15701
15634
|
case 'contentData':
|
|
15702
15635
|
value = stringifyJson(json.content?.data);
|
|
@@ -15750,11 +15683,7 @@ let ActionsEditorComponent$1 = class ActionsEditorComponent {
|
|
|
15750
15683
|
value = json.target ?? '';
|
|
15751
15684
|
break;
|
|
15752
15685
|
case 'text':
|
|
15753
|
-
value = json.text ??
|
|
15754
|
-
break;
|
|
15755
|
-
case 'key':
|
|
15756
|
-
case 'field':
|
|
15757
|
-
value = json[field.key] ?? (hasJson ? '' : raw);
|
|
15686
|
+
value = json.text ?? '';
|
|
15758
15687
|
break;
|
|
15759
15688
|
default:
|
|
15760
15689
|
value = json[field.key] ?? '';
|
|
@@ -15771,8 +15700,8 @@ let ActionsEditorComponent$1 = class ActionsEditorComponent {
|
|
|
15771
15700
|
return value;
|
|
15772
15701
|
}
|
|
15773
15702
|
onGlobalActionFieldChange(action, field, value, index) {
|
|
15774
|
-
const selectedId = this.getCustomActionSelectValue(action
|
|
15775
|
-
if (!this.
|
|
15703
|
+
const selectedId = this.getCustomActionSelectValue(action);
|
|
15704
|
+
if (!this.isCanonicalGlobalActionId(selectedId))
|
|
15776
15705
|
return;
|
|
15777
15706
|
const schema = getGlobalActionUiSchema(selectedId);
|
|
15778
15707
|
if (!schema)
|
|
@@ -15796,9 +15725,7 @@ let ActionsEditorComponent$1 = class ActionsEditorComponent {
|
|
|
15796
15725
|
}
|
|
15797
15726
|
const values = this.collectGlobalActionFieldValues(action, schema.fields);
|
|
15798
15727
|
values[field.key] = value;
|
|
15799
|
-
|
|
15800
|
-
const param = serializeGlobalActionParam(selectedId, values, info.json);
|
|
15801
|
-
this.updateCustomAction(index, 'action', param ? `${selectedId}:${param}` : selectedId);
|
|
15728
|
+
this.updateCustomActionRef(index, buildGlobalActionRef(selectedId, values, action.globalAction?.payload));
|
|
15802
15729
|
}
|
|
15803
15730
|
setActionFieldError(action, key, message) {
|
|
15804
15731
|
const draftKey = this.getActionDraftKey(action);
|
|
@@ -15820,7 +15747,7 @@ let ActionsEditorComponent$1 = class ActionsEditorComponent {
|
|
|
15820
15747
|
}
|
|
15821
15748
|
}
|
|
15822
15749
|
getActionDraftKey(action) {
|
|
15823
|
-
return String(action.id || action.action || action.label || 'action');
|
|
15750
|
+
return String(action.id || action.action || action.label || action.globalAction?.actionId || 'action');
|
|
15824
15751
|
}
|
|
15825
15752
|
setActionFieldDraft(action, key, value) {
|
|
15826
15753
|
const draftKey = this.getActionDraftKey(action);
|
|
@@ -15884,6 +15811,25 @@ let ActionsEditorComponent$1 = class ActionsEditorComponent {
|
|
|
15884
15811
|
this.syncCustomActionStyleTexts();
|
|
15885
15812
|
}
|
|
15886
15813
|
}
|
|
15814
|
+
updateCustomActionRef(index, globalAction) {
|
|
15815
|
+
const customActions = [...(this.actions.custom || [])];
|
|
15816
|
+
const current = { ...customActions[index] };
|
|
15817
|
+
if (globalAction) {
|
|
15818
|
+
current.globalAction = globalAction;
|
|
15819
|
+
delete current.action;
|
|
15820
|
+
}
|
|
15821
|
+
else {
|
|
15822
|
+
delete current.globalAction;
|
|
15823
|
+
}
|
|
15824
|
+
customActions[index] = current;
|
|
15825
|
+
this.configChange.emit({
|
|
15826
|
+
...this.config,
|
|
15827
|
+
actions: {
|
|
15828
|
+
...this.actions,
|
|
15829
|
+
custom: customActions,
|
|
15830
|
+
},
|
|
15831
|
+
});
|
|
15832
|
+
}
|
|
15887
15833
|
addCustomButton() {
|
|
15888
15834
|
const newButton = {
|
|
15889
15835
|
id: `custom_${Date.now()}`,
|
|
@@ -16052,10 +15998,10 @@ let ActionsEditorComponent$1 = class ActionsEditorComponent {
|
|
|
16052
15998
|
<div class="action-global-grid">
|
|
16053
15999
|
@for (field of schema.fields; track field.key) {
|
|
16054
16000
|
@if (shouldShowGlobalActionField(action, field)) {
|
|
16055
|
-
@if (schema.id === '
|
|
16001
|
+
@if (schema.id === 'dialog.alert' && field.key === 'message') {
|
|
16056
16002
|
<div class="action-section-title">{{ tx('global.section.essential', 'Essential') }}</div>
|
|
16057
16003
|
}
|
|
16058
|
-
@if (schema.id === '
|
|
16004
|
+
@if (schema.id === 'dialog.alert' && field.key === 'title') {
|
|
16059
16005
|
<div class="action-section-title">{{ tx('global.section.dialogOptional', 'Dialog (optional)') }}</div>
|
|
16060
16006
|
}
|
|
16061
16007
|
@if (field.type === 'toggle') {
|
|
@@ -16086,6 +16032,7 @@ let ActionsEditorComponent$1 = class ActionsEditorComponent {
|
|
|
16086
16032
|
(ngModelChange)="onGlobalActionFieldChange(action, field, $event, index)"
|
|
16087
16033
|
[ngModelOptions]="{ standalone: true }"
|
|
16088
16034
|
[placeholder]="field.placeholder || ''"
|
|
16035
|
+
[required]="isGlobalActionFieldRequired(action, field)"
|
|
16089
16036
|
></textarea>
|
|
16090
16037
|
@if (field.hint) {
|
|
16091
16038
|
<button
|
|
@@ -16098,6 +16045,9 @@ let ActionsEditorComponent$1 = class ActionsEditorComponent {
|
|
|
16098
16045
|
<mat-icon>help_outline</mat-icon>
|
|
16099
16046
|
</button>
|
|
16100
16047
|
}
|
|
16048
|
+
@if (isGlobalActionFieldMissing(action, field)) {
|
|
16049
|
+
<mat-error aria-live="polite">{{ tx('global.validation.requiredField', 'Campo obrigatório.') }}</mat-error>
|
|
16050
|
+
}
|
|
16101
16051
|
</mat-form-field>
|
|
16102
16052
|
} @else if (field.type === 'json') {
|
|
16103
16053
|
<mat-form-field>
|
|
@@ -16109,6 +16059,7 @@ let ActionsEditorComponent$1 = class ActionsEditorComponent {
|
|
|
16109
16059
|
(ngModelChange)="onGlobalActionFieldChange(action, field, $event, index)"
|
|
16110
16060
|
[ngModelOptions]="{ standalone: true }"
|
|
16111
16061
|
[placeholder]="field.placeholder || '{ }'"
|
|
16062
|
+
[required]="isGlobalActionFieldRequired(action, field)"
|
|
16112
16063
|
></textarea>
|
|
16113
16064
|
<button
|
|
16114
16065
|
mat-icon-button
|
|
@@ -16121,6 +16072,8 @@ let ActionsEditorComponent$1 = class ActionsEditorComponent {
|
|
|
16121
16072
|
</button>
|
|
16122
16073
|
@if (hasActionFieldError(action, field.key)) {
|
|
16123
16074
|
<mat-error>{{ getActionFieldError(action, field.key) }}</mat-error>
|
|
16075
|
+
} @else if (isGlobalActionFieldMissing(action, field)) {
|
|
16076
|
+
<mat-error aria-live="polite">{{ tx('global.validation.requiredField', 'Campo obrigatório.') }}</mat-error>
|
|
16124
16077
|
}
|
|
16125
16078
|
</mat-form-field>
|
|
16126
16079
|
} @else if (field.type === 'select') {
|
|
@@ -16130,6 +16083,7 @@ let ActionsEditorComponent$1 = class ActionsEditorComponent {
|
|
|
16130
16083
|
[ngModel]="getGlobalActionFieldValue(action, field)"
|
|
16131
16084
|
(ngModelChange)="onGlobalActionFieldChange(action, field, $event, index)"
|
|
16132
16085
|
[ngModelOptions]="{ standalone: true }"
|
|
16086
|
+
[required]="isGlobalActionFieldRequired(action, field)"
|
|
16133
16087
|
>
|
|
16134
16088
|
@for (opt of field.options || []; track opt.value) {
|
|
16135
16089
|
<mat-option [value]="opt.value">{{ opt.label }}</mat-option>
|
|
@@ -16146,6 +16100,9 @@ let ActionsEditorComponent$1 = class ActionsEditorComponent {
|
|
|
16146
16100
|
<mat-icon>help_outline</mat-icon>
|
|
16147
16101
|
</button>
|
|
16148
16102
|
}
|
|
16103
|
+
@if (isGlobalActionFieldMissing(action, field)) {
|
|
16104
|
+
<mat-error aria-live="polite">{{ tx('global.validation.requiredField', 'Campo obrigatório.') }}</mat-error>
|
|
16105
|
+
}
|
|
16149
16106
|
</mat-form-field>
|
|
16150
16107
|
} @else {
|
|
16151
16108
|
<mat-form-field>
|
|
@@ -16157,6 +16114,7 @@ let ActionsEditorComponent$1 = class ActionsEditorComponent {
|
|
|
16157
16114
|
(ngModelChange)="onGlobalActionFieldChange(action, field, $event, index)"
|
|
16158
16115
|
[ngModelOptions]="{ standalone: true }"
|
|
16159
16116
|
[placeholder]="field.placeholder || ''"
|
|
16117
|
+
[required]="isGlobalActionFieldRequired(action, field)"
|
|
16160
16118
|
/>
|
|
16161
16119
|
@if (field.hint) {
|
|
16162
16120
|
<button
|
|
@@ -16169,6 +16127,9 @@ let ActionsEditorComponent$1 = class ActionsEditorComponent {
|
|
|
16169
16127
|
<mat-icon>help_outline</mat-icon>
|
|
16170
16128
|
</button>
|
|
16171
16129
|
}
|
|
16130
|
+
@if (isGlobalActionFieldMissing(action, field)) {
|
|
16131
|
+
<mat-error aria-live="polite">{{ tx('global.validation.requiredField', 'Campo obrigatório.') }}</mat-error>
|
|
16132
|
+
}
|
|
16172
16133
|
</mat-form-field>
|
|
16173
16134
|
}
|
|
16174
16135
|
}
|
|
@@ -16443,7 +16404,7 @@ let ActionsEditorComponent$1 = class ActionsEditorComponent {
|
|
|
16443
16404
|
<mat-accordion multi>
|
|
16444
16405
|
@for (
|
|
16445
16406
|
customAction of actions.custom;
|
|
16446
|
-
track customAction.id || customAction.action || $index;
|
|
16407
|
+
track customAction.id || customAction.globalAction?.actionId || customAction.action || $index;
|
|
16447
16408
|
let i = $index
|
|
16448
16409
|
) {
|
|
16449
16410
|
<div class="custom-action-panel-shell">
|
|
@@ -16500,7 +16461,7 @@ let ActionsEditorComponent$1 = class ActionsEditorComponent {
|
|
|
16500
16461
|
<mat-form-field>
|
|
16501
16462
|
<mat-label>{{ tx('custom.action', 'Action') }}</mat-label>
|
|
16502
16463
|
<mat-select
|
|
16503
|
-
[value]="getCustomActionSelectValue(customAction
|
|
16464
|
+
[value]="getCustomActionSelectValue(customAction)"
|
|
16504
16465
|
(selectionChange)="onCustomActionSelectChange(i, $event.value)"
|
|
16505
16466
|
>
|
|
16506
16467
|
<mat-option [value]="customActionValue">{{ tx('custom.action.custom', 'Custom') }}</mat-option>
|
|
@@ -16520,34 +16481,34 @@ let ActionsEditorComponent$1 = class ActionsEditorComponent {
|
|
|
16520
16481
|
<mat-icon>help_outline</mat-icon>
|
|
16521
16482
|
</button>
|
|
16522
16483
|
</mat-form-field>
|
|
16523
|
-
@if (getCustomActionSelectValue(customAction
|
|
16484
|
+
@if (getCustomActionSelectValue(customAction) === customActionValue) {
|
|
16524
16485
|
<mat-form-field>
|
|
16525
16486
|
<mat-label>{{ tx('custom.action', 'Action') }}</mat-label>
|
|
16526
16487
|
<input
|
|
16527
16488
|
matInput
|
|
16528
|
-
[value]="getCustomActionCustomValue(customAction
|
|
16489
|
+
[value]="getCustomActionCustomValue(customAction)"
|
|
16529
16490
|
(input)="onCustomActionCustomChange(i, $any($event.target).value)"
|
|
16530
16491
|
[placeholder]="tx('custom.action.customPlaceholder', 'Example: custom_action, navigate, delete')"
|
|
16531
16492
|
/>
|
|
16532
16493
|
</mat-form-field>
|
|
16533
16494
|
} @else {
|
|
16534
|
-
@if (
|
|
16495
|
+
@if (isCanonicalGlobalActionId(getCustomActionSelectValue(customAction))) {
|
|
16535
16496
|
<ng-container
|
|
16536
16497
|
[ngTemplateOutlet]="globalActionFields"
|
|
16537
16498
|
[ngTemplateOutletContext]="{ $implicit: customAction, index: i }"
|
|
16538
16499
|
></ng-container>
|
|
16539
16500
|
} @else {
|
|
16540
|
-
@if (getActionSpecById(getCustomActionSelectValue(customAction
|
|
16501
|
+
@if (getActionSpecById(getCustomActionSelectValue(customAction)); as actionSpec) {
|
|
16541
16502
|
@if (actionSpec.param) {
|
|
16542
16503
|
<mat-form-field>
|
|
16543
16504
|
<mat-label>{{ actionSpec.param.label || tx('custom.parameter', 'Parameter') }}</mat-label>
|
|
16544
16505
|
<input
|
|
16545
16506
|
matInput
|
|
16546
|
-
[value]="getCustomActionParam(customAction
|
|
16507
|
+
[value]="getCustomActionParam(customAction)"
|
|
16547
16508
|
(input)="onCustomActionParamChange(i, $any($event.target).value)"
|
|
16548
16509
|
[placeholder]="actionSpec.param.placeholder || ''"
|
|
16549
16510
|
/>
|
|
16550
|
-
@if (actionSpec.param.required && !getCustomActionParam(customAction
|
|
16511
|
+
@if (actionSpec.param.required && !getCustomActionParam(customAction)) {
|
|
16551
16512
|
<mat-error>{{ tx('custom.parameterRequired', 'Required parameter.') }}</mat-error>
|
|
16552
16513
|
}
|
|
16553
16514
|
</mat-form-field>
|
|
@@ -16825,7 +16786,7 @@ let ActionsEditorComponent$1 = class ActionsEditorComponent {
|
|
|
16825
16786
|
</div>
|
|
16826
16787
|
</mat-tab>
|
|
16827
16788
|
</mat-tab-group>
|
|
16828
|
-
`, isInline: true, styles: [".editor-container{padding:16px;display:flex;flex-direction:column;gap:16px}.action-fields{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px;align-items:center}.action-global-fields{grid-column:1 / -1;padding:12px 14px;border:1px dashed var(--md-sys-color-outline-variant, rgba(255, 255, 255, .12));border-radius:10px;background:var(--md-sys-color-surface-container-low)}.action-global-title{font-size:12px;font-weight:600;letter-spacing:.02em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant, #9aa0a6);margin-bottom:10px}.action-global-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px 16px}.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;vertical-align:middle}.help-icon-button mat-icon{font-size:18px;line-height:18px;width:18px;height:18px}.action-global-fields .mat-mdc-form-field-icon-suffix{align-self:center}.action-section-title{width:100%;margin:10px 0 2px;font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant, #9aa0a6)}.add-button{margin-bottom:16px;align-self:flex-start}.custom-action-panel-shell{display:grid;gap:8px}.custom-action-panel-toolbar{display:flex;justify-content:flex-end}.custom-action-order-controls{display:inline-flex;align-items:center;gap:4px;justify-content:flex-end}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4$1.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i4$1.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i7$1.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: MatSlideToggleModule }, { kind: "component", type: i6$2.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: MatSelectModule }, { kind: "component", type: i6$3.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6$3.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i6$3.MatOptgroup, selector: "mat-optgroup", inputs: ["label", "disabled"], exportAs: ["matOptgroup"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i7$3.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i7$3.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: MatExpansionModule }, { kind: "directive", type: i3.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i3.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i3.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i3.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i3.MatExpansionPanelContent, selector: "ng-template[matExpansionPanelContent]" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i4.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: i4.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i9.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: SurfaceOpenActionEditorComponent, selector: "praxis-surface-open-action-editor", inputs: ["value", "hostKind"], outputs: ["valueChange"] }] });
|
|
16789
|
+
`, isInline: true, styles: [".editor-container{padding:16px;display:flex;flex-direction:column;gap:16px}.action-fields{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px;align-items:center}.action-global-fields{grid-column:1 / -1;padding:12px 14px;border:1px dashed var(--md-sys-color-outline-variant, rgba(255, 255, 255, .12));border-radius:10px;background:var(--md-sys-color-surface-container-low)}.action-global-title{font-size:12px;font-weight:600;letter-spacing:.02em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant, #9aa0a6);margin-bottom:10px}.action-global-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px 16px}.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;vertical-align:middle}.help-icon-button mat-icon{font-size:18px;line-height:18px;width:18px;height:18px}.action-field-error{color:var(--md-sys-color-error, #ba1a1a);font-weight:500}.action-global-fields .mat-mdc-form-field-icon-suffix{align-self:center}.action-section-title{width:100%;margin:10px 0 2px;font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant, #9aa0a6)}.add-button{margin-bottom:16px;align-self:flex-start}.custom-action-panel-shell{display:grid;gap:8px}.custom-action-panel-toolbar{display:flex;justify-content:flex-end}.custom-action-order-controls{display:inline-flex;align-items:center;gap:4px;justify-content:flex-end}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4$1.MatError, selector: "mat-error, [matError]", inputs: ["id"] }, { kind: "directive", type: i4$1.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i7$1.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: MatSlideToggleModule }, { kind: "component", type: i6$2.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: MatSelectModule }, { kind: "component", type: i6$3.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6$3.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "component", type: i6$3.MatOptgroup, selector: "mat-optgroup", inputs: ["label", "disabled"], exportAs: ["matOptgroup"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i7$3.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i7$3.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: MatExpansionModule }, { kind: "directive", type: i3.MatAccordion, selector: "mat-accordion", inputs: ["hideToggle", "displayMode", "togglePosition"], exportAs: ["matAccordion"] }, { kind: "component", type: i3.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i3.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i3.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "directive", type: i3.MatExpansionPanelContent, selector: "ng-template[matExpansionPanelContent]" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i4.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: i4.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i9.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: SurfaceOpenActionEditorComponent, selector: "praxis-surface-open-action-editor", inputs: ["value", "hostKind"], outputs: ["valueChange"] }] });
|
|
16829
16790
|
};
|
|
16830
16791
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ActionsEditorComponent$1, decorators: [{
|
|
16831
16792
|
type: Component,
|
|
@@ -16860,10 +16821,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
16860
16821
|
<div class="action-global-grid">
|
|
16861
16822
|
@for (field of schema.fields; track field.key) {
|
|
16862
16823
|
@if (shouldShowGlobalActionField(action, field)) {
|
|
16863
|
-
@if (schema.id === '
|
|
16824
|
+
@if (schema.id === 'dialog.alert' && field.key === 'message') {
|
|
16864
16825
|
<div class="action-section-title">{{ tx('global.section.essential', 'Essential') }}</div>
|
|
16865
16826
|
}
|
|
16866
|
-
@if (schema.id === '
|
|
16827
|
+
@if (schema.id === 'dialog.alert' && field.key === 'title') {
|
|
16867
16828
|
<div class="action-section-title">{{ tx('global.section.dialogOptional', 'Dialog (optional)') }}</div>
|
|
16868
16829
|
}
|
|
16869
16830
|
@if (field.type === 'toggle') {
|
|
@@ -16894,6 +16855,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
16894
16855
|
(ngModelChange)="onGlobalActionFieldChange(action, field, $event, index)"
|
|
16895
16856
|
[ngModelOptions]="{ standalone: true }"
|
|
16896
16857
|
[placeholder]="field.placeholder || ''"
|
|
16858
|
+
[required]="isGlobalActionFieldRequired(action, field)"
|
|
16897
16859
|
></textarea>
|
|
16898
16860
|
@if (field.hint) {
|
|
16899
16861
|
<button
|
|
@@ -16906,6 +16868,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
16906
16868
|
<mat-icon>help_outline</mat-icon>
|
|
16907
16869
|
</button>
|
|
16908
16870
|
}
|
|
16871
|
+
@if (isGlobalActionFieldMissing(action, field)) {
|
|
16872
|
+
<mat-error aria-live="polite">{{ tx('global.validation.requiredField', 'Campo obrigatório.') }}</mat-error>
|
|
16873
|
+
}
|
|
16909
16874
|
</mat-form-field>
|
|
16910
16875
|
} @else if (field.type === 'json') {
|
|
16911
16876
|
<mat-form-field>
|
|
@@ -16917,6 +16882,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
16917
16882
|
(ngModelChange)="onGlobalActionFieldChange(action, field, $event, index)"
|
|
16918
16883
|
[ngModelOptions]="{ standalone: true }"
|
|
16919
16884
|
[placeholder]="field.placeholder || '{ }'"
|
|
16885
|
+
[required]="isGlobalActionFieldRequired(action, field)"
|
|
16920
16886
|
></textarea>
|
|
16921
16887
|
<button
|
|
16922
16888
|
mat-icon-button
|
|
@@ -16929,6 +16895,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
16929
16895
|
</button>
|
|
16930
16896
|
@if (hasActionFieldError(action, field.key)) {
|
|
16931
16897
|
<mat-error>{{ getActionFieldError(action, field.key) }}</mat-error>
|
|
16898
|
+
} @else if (isGlobalActionFieldMissing(action, field)) {
|
|
16899
|
+
<mat-error aria-live="polite">{{ tx('global.validation.requiredField', 'Campo obrigatório.') }}</mat-error>
|
|
16932
16900
|
}
|
|
16933
16901
|
</mat-form-field>
|
|
16934
16902
|
} @else if (field.type === 'select') {
|
|
@@ -16938,6 +16906,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
16938
16906
|
[ngModel]="getGlobalActionFieldValue(action, field)"
|
|
16939
16907
|
(ngModelChange)="onGlobalActionFieldChange(action, field, $event, index)"
|
|
16940
16908
|
[ngModelOptions]="{ standalone: true }"
|
|
16909
|
+
[required]="isGlobalActionFieldRequired(action, field)"
|
|
16941
16910
|
>
|
|
16942
16911
|
@for (opt of field.options || []; track opt.value) {
|
|
16943
16912
|
<mat-option [value]="opt.value">{{ opt.label }}</mat-option>
|
|
@@ -16954,6 +16923,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
16954
16923
|
<mat-icon>help_outline</mat-icon>
|
|
16955
16924
|
</button>
|
|
16956
16925
|
}
|
|
16926
|
+
@if (isGlobalActionFieldMissing(action, field)) {
|
|
16927
|
+
<mat-error aria-live="polite">{{ tx('global.validation.requiredField', 'Campo obrigatório.') }}</mat-error>
|
|
16928
|
+
}
|
|
16957
16929
|
</mat-form-field>
|
|
16958
16930
|
} @else {
|
|
16959
16931
|
<mat-form-field>
|
|
@@ -16965,6 +16937,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
16965
16937
|
(ngModelChange)="onGlobalActionFieldChange(action, field, $event, index)"
|
|
16966
16938
|
[ngModelOptions]="{ standalone: true }"
|
|
16967
16939
|
[placeholder]="field.placeholder || ''"
|
|
16940
|
+
[required]="isGlobalActionFieldRequired(action, field)"
|
|
16968
16941
|
/>
|
|
16969
16942
|
@if (field.hint) {
|
|
16970
16943
|
<button
|
|
@@ -16977,6 +16950,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
16977
16950
|
<mat-icon>help_outline</mat-icon>
|
|
16978
16951
|
</button>
|
|
16979
16952
|
}
|
|
16953
|
+
@if (isGlobalActionFieldMissing(action, field)) {
|
|
16954
|
+
<mat-error aria-live="polite">{{ tx('global.validation.requiredField', 'Campo obrigatório.') }}</mat-error>
|
|
16955
|
+
}
|
|
16980
16956
|
</mat-form-field>
|
|
16981
16957
|
}
|
|
16982
16958
|
}
|
|
@@ -17251,7 +17227,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
17251
17227
|
<mat-accordion multi>
|
|
17252
17228
|
@for (
|
|
17253
17229
|
customAction of actions.custom;
|
|
17254
|
-
track customAction.id || customAction.action || $index;
|
|
17230
|
+
track customAction.id || customAction.globalAction?.actionId || customAction.action || $index;
|
|
17255
17231
|
let i = $index
|
|
17256
17232
|
) {
|
|
17257
17233
|
<div class="custom-action-panel-shell">
|
|
@@ -17308,7 +17284,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
17308
17284
|
<mat-form-field>
|
|
17309
17285
|
<mat-label>{{ tx('custom.action', 'Action') }}</mat-label>
|
|
17310
17286
|
<mat-select
|
|
17311
|
-
[value]="getCustomActionSelectValue(customAction
|
|
17287
|
+
[value]="getCustomActionSelectValue(customAction)"
|
|
17312
17288
|
(selectionChange)="onCustomActionSelectChange(i, $event.value)"
|
|
17313
17289
|
>
|
|
17314
17290
|
<mat-option [value]="customActionValue">{{ tx('custom.action.custom', 'Custom') }}</mat-option>
|
|
@@ -17328,34 +17304,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
17328
17304
|
<mat-icon>help_outline</mat-icon>
|
|
17329
17305
|
</button>
|
|
17330
17306
|
</mat-form-field>
|
|
17331
|
-
@if (getCustomActionSelectValue(customAction
|
|
17307
|
+
@if (getCustomActionSelectValue(customAction) === customActionValue) {
|
|
17332
17308
|
<mat-form-field>
|
|
17333
17309
|
<mat-label>{{ tx('custom.action', 'Action') }}</mat-label>
|
|
17334
17310
|
<input
|
|
17335
17311
|
matInput
|
|
17336
|
-
[value]="getCustomActionCustomValue(customAction
|
|
17312
|
+
[value]="getCustomActionCustomValue(customAction)"
|
|
17337
17313
|
(input)="onCustomActionCustomChange(i, $any($event.target).value)"
|
|
17338
17314
|
[placeholder]="tx('custom.action.customPlaceholder', 'Example: custom_action, navigate, delete')"
|
|
17339
17315
|
/>
|
|
17340
17316
|
</mat-form-field>
|
|
17341
17317
|
} @else {
|
|
17342
|
-
@if (
|
|
17318
|
+
@if (isCanonicalGlobalActionId(getCustomActionSelectValue(customAction))) {
|
|
17343
17319
|
<ng-container
|
|
17344
17320
|
[ngTemplateOutlet]="globalActionFields"
|
|
17345
17321
|
[ngTemplateOutletContext]="{ $implicit: customAction, index: i }"
|
|
17346
17322
|
></ng-container>
|
|
17347
17323
|
} @else {
|
|
17348
|
-
@if (getActionSpecById(getCustomActionSelectValue(customAction
|
|
17324
|
+
@if (getActionSpecById(getCustomActionSelectValue(customAction)); as actionSpec) {
|
|
17349
17325
|
@if (actionSpec.param) {
|
|
17350
17326
|
<mat-form-field>
|
|
17351
17327
|
<mat-label>{{ actionSpec.param.label || tx('custom.parameter', 'Parameter') }}</mat-label>
|
|
17352
17328
|
<input
|
|
17353
17329
|
matInput
|
|
17354
|
-
[value]="getCustomActionParam(customAction
|
|
17330
|
+
[value]="getCustomActionParam(customAction)"
|
|
17355
17331
|
(input)="onCustomActionParamChange(i, $any($event.target).value)"
|
|
17356
17332
|
[placeholder]="actionSpec.param.placeholder || ''"
|
|
17357
17333
|
/>
|
|
17358
|
-
@if (actionSpec.param.required && !getCustomActionParam(customAction
|
|
17334
|
+
@if (actionSpec.param.required && !getCustomActionParam(customAction)) {
|
|
17359
17335
|
<mat-error>{{ tx('custom.parameterRequired', 'Required parameter.') }}</mat-error>
|
|
17360
17336
|
}
|
|
17361
17337
|
</mat-form-field>
|
|
@@ -17633,7 +17609,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
17633
17609
|
</div>
|
|
17634
17610
|
</mat-tab>
|
|
17635
17611
|
</mat-tab-group>
|
|
17636
|
-
`, styles: [".editor-container{padding:16px;display:flex;flex-direction:column;gap:16px}.action-fields{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px;align-items:center}.action-global-fields{grid-column:1 / -1;padding:12px 14px;border:1px dashed var(--md-sys-color-outline-variant, rgba(255, 255, 255, .12));border-radius:10px;background:var(--md-sys-color-surface-container-low)}.action-global-title{font-size:12px;font-weight:600;letter-spacing:.02em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant, #9aa0a6);margin-bottom:10px}.action-global-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px 16px}.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;vertical-align:middle}.help-icon-button mat-icon{font-size:18px;line-height:18px;width:18px;height:18px}.action-global-fields .mat-mdc-form-field-icon-suffix{align-self:center}.action-section-title{width:100%;margin:10px 0 2px;font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant, #9aa0a6)}.add-button{margin-bottom:16px;align-self:flex-start}.custom-action-panel-shell{display:grid;gap:8px}.custom-action-panel-toolbar{display:flex;justify-content:flex-end}.custom-action-order-controls{display:inline-flex;align-items:center;gap:4px;justify-content:flex-end}\n"] }]
|
|
17612
|
+
`, styles: [".editor-container{padding:16px;display:flex;flex-direction:column;gap:16px}.action-fields{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:16px;align-items:center}.action-global-fields{grid-column:1 / -1;padding:12px 14px;border:1px dashed var(--md-sys-color-outline-variant, rgba(255, 255, 255, .12));border-radius:10px;background:var(--md-sys-color-surface-container-low)}.action-global-title{font-size:12px;font-weight:600;letter-spacing:.02em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant, #9aa0a6);margin-bottom:10px}.action-global-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px 16px}.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;vertical-align:middle}.help-icon-button mat-icon{font-size:18px;line-height:18px;width:18px;height:18px}.action-field-error{color:var(--md-sys-color-error, #ba1a1a);font-weight:500}.action-global-fields .mat-mdc-form-field-icon-suffix{align-self:center}.action-section-title{width:100%;margin:10px 0 2px;font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant, #9aa0a6)}.add-button{margin-bottom:16px;align-self:flex-start}.custom-action-panel-shell{display:grid;gap:8px}.custom-action-panel-toolbar{display:flex;justify-content:flex-end}.custom-action-order-controls{display:inline-flex;align-items:center;gap:4px;justify-content:flex-end}\n"] }]
|
|
17637
17613
|
}], propDecorators: { config: [{
|
|
17638
17614
|
type: Input
|
|
17639
17615
|
}], configChange: [{
|
|
@@ -18995,6 +18971,9 @@ class PraxisDynamicFormConfigEditor {
|
|
|
18995
18971
|
serverMeta;
|
|
18996
18972
|
destroy$ = new Subject();
|
|
18997
18973
|
hasProcessedInitialRuleState = false;
|
|
18974
|
+
jsonEditorIsValid = true;
|
|
18975
|
+
globalActionValidationIssues = [];
|
|
18976
|
+
globalActionCatalog;
|
|
18998
18977
|
// Observables obrigatórios da interface SettingsValueProvider
|
|
18999
18978
|
isDirty$ = new BehaviorSubject(false);
|
|
19000
18979
|
isValid$ = new BehaviorSubject(true);
|
|
@@ -19011,11 +18990,12 @@ class PraxisDynamicFormConfigEditor {
|
|
|
19011
18990
|
}
|
|
19012
18991
|
console.debug(message);
|
|
19013
18992
|
}
|
|
19014
|
-
constructor(storage, configService, settingsPanel, cdr, injectedData) {
|
|
18993
|
+
constructor(storage, configService, settingsPanel, cdr, globalActionCatalogSource, injectedData) {
|
|
19015
18994
|
this.storage = storage;
|
|
19016
18995
|
this.configService = configService;
|
|
19017
18996
|
this.settingsPanel = settingsPanel;
|
|
19018
18997
|
this.cdr = cdr;
|
|
18998
|
+
this.globalActionCatalog = this.buildGlobalActionCatalog(globalActionCatalogSource);
|
|
19019
18999
|
const data = injectedData;
|
|
19020
19000
|
this.openedWithCanonicalDocument =
|
|
19021
19001
|
data?.document?.kind === 'praxis.dynamic-form.editor' ||
|
|
@@ -19113,6 +19093,7 @@ class PraxisDynamicFormConfigEditor {
|
|
|
19113
19093
|
}
|
|
19114
19094
|
}
|
|
19115
19095
|
catch { }
|
|
19096
|
+
this.updateValidityState();
|
|
19116
19097
|
// Nota: isValid$ é atualizado em onJsonValidationChange quando necessário
|
|
19117
19098
|
// Para outras validações futuras, verificar se não há validação JSON em andamento
|
|
19118
19099
|
}
|
|
@@ -19179,7 +19160,8 @@ class PraxisDynamicFormConfigEditor {
|
|
|
19179
19160
|
}
|
|
19180
19161
|
onJsonValidationChange(result) {
|
|
19181
19162
|
// Atualizar estado de validação baseado no resultado do JSON
|
|
19182
|
-
this.
|
|
19163
|
+
this.jsonEditorIsValid = result.isValid;
|
|
19164
|
+
this.updateValidityState();
|
|
19183
19165
|
// Evitar marcar dirty apenas por validação (não há mudança real de config)
|
|
19184
19166
|
if (this.isDirty$.value) {
|
|
19185
19167
|
this.updateDirtyState('json-validate');
|
|
@@ -19203,6 +19185,41 @@ class PraxisDynamicFormConfigEditor {
|
|
|
19203
19185
|
this.lastRulesSignature = this.computeRulesSignature(this.ruleBuilderState);
|
|
19204
19186
|
this.updateDirtyState('layout-change');
|
|
19205
19187
|
}
|
|
19188
|
+
updateValidityState() {
|
|
19189
|
+
this.globalActionValidationIssues = this.collectGlobalActionValidationIssues(this.editedConfig);
|
|
19190
|
+
this.isValid$.next(this.jsonEditorIsValid && this.globalActionValidationIssues.length === 0);
|
|
19191
|
+
}
|
|
19192
|
+
collectGlobalActionValidationIssues(config) {
|
|
19193
|
+
return validateGlobalActionRefs(this.collectGlobalActionValidationTargets(config));
|
|
19194
|
+
}
|
|
19195
|
+
collectGlobalActionValidationTargets(config) {
|
|
19196
|
+
const targets = [];
|
|
19197
|
+
const add = (ref, path) => {
|
|
19198
|
+
if (!ref)
|
|
19199
|
+
return;
|
|
19200
|
+
targets.push({
|
|
19201
|
+
ref,
|
|
19202
|
+
path,
|
|
19203
|
+
catalogEntry: this.findGlobalActionCatalogEntry(ref.actionId),
|
|
19204
|
+
});
|
|
19205
|
+
};
|
|
19206
|
+
const actions = config.actions;
|
|
19207
|
+
add(actions?.submit?.globalAction, 'actions.submit.globalAction');
|
|
19208
|
+
add(actions?.cancel?.globalAction, 'actions.cancel.globalAction');
|
|
19209
|
+
add(actions?.reset?.globalAction, 'actions.reset.globalAction');
|
|
19210
|
+
actions?.custom?.forEach((action, index) => add(action?.globalAction, `actions.custom[${index}].globalAction`));
|
|
19211
|
+
config.sections?.forEach((section, sectionIndex) => {
|
|
19212
|
+
section?.headerActions?.forEach((action, actionIndex) => add(action?.globalAction, `sections[${sectionIndex}].headerActions[${actionIndex}].globalAction`));
|
|
19213
|
+
});
|
|
19214
|
+
return targets;
|
|
19215
|
+
}
|
|
19216
|
+
findGlobalActionCatalogEntry(actionId) {
|
|
19217
|
+
return this.globalActionCatalog.find((entry) => entry.id === actionId);
|
|
19218
|
+
}
|
|
19219
|
+
buildGlobalActionCatalog(source) {
|
|
19220
|
+
const injected = getGlobalActionCatalog(source);
|
|
19221
|
+
return injected.length ? injected : PRAXIS_GLOBAL_ACTION_CATALOG;
|
|
19222
|
+
}
|
|
19206
19223
|
onRulesChanged(state) {
|
|
19207
19224
|
if (!this.hasProcessedInitialRuleState) {
|
|
19208
19225
|
const hadInitialRules = this.initialConfig?.formRules?.length ||
|
|
@@ -19432,6 +19449,7 @@ class PraxisDynamicFormConfigEditor {
|
|
|
19432
19449
|
file: FieldType.STRING,
|
|
19433
19450
|
url: FieldType.URL,
|
|
19434
19451
|
boolean: FieldType.BOOLEAN,
|
|
19452
|
+
array: FieldType.JSON,
|
|
19435
19453
|
json: FieldType.JSON,
|
|
19436
19454
|
};
|
|
19437
19455
|
return mapping[dataType ?? 'text'];
|
|
@@ -19686,7 +19704,7 @@ class PraxisDynamicFormConfigEditor {
|
|
|
19686
19704
|
toBackConfigSnapshot() {
|
|
19687
19705
|
return structuredClone(this.backConfig || { returnTo: '', confirmOnDirty: true });
|
|
19688
19706
|
}
|
|
19689
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisDynamicFormConfigEditor, deps: [{ token: ASYNC_CONFIG_STORAGE }, { token: FormConfigService }, { token: i7.SettingsPanelService }, { token: i0.ChangeDetectorRef }, { token: SETTINGS_PANEL_DATA, optional: true }], target: i0.ɵɵFactoryTarget.Component });
|
|
19707
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisDynamicFormConfigEditor, deps: [{ token: ASYNC_CONFIG_STORAGE }, { token: FormConfigService }, { token: i7.SettingsPanelService }, { token: i0.ChangeDetectorRef }, { token: GLOBAL_ACTION_CATALOG, optional: true }, { token: SETTINGS_PANEL_DATA, optional: true }], target: i0.ɵɵFactoryTarget.Component });
|
|
19690
19708
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: PraxisDynamicFormConfigEditor, isStandalone: true, selector: "praxis-dynamic-form-config-editor", providers: [FormConfigService], viewQueries: [{ propertyName: "jsonEditor", first: true, predicate: JsonConfigEditorComponent, descendants: true }], ngImport: i0, template: "<mat-tab-group class=\"config-tabs\" data-testid=\"dynamic-form-config-editor-tabs\">\n <mat-tab label=\"Geral\">\n <div class=\"tab-content\" data-testid=\"config-tab-panel-geral\">\n <mat-card class=\"section-card\">\n <mat-card-content>\n <div class=\"section-header\">\n <div>\n <h3>Dados do formul\u00E1rio</h3>\n <p class=\"text-caption\">Defina o modo de dados e o contexto de edi\u00E7\u00E3o.</p>\n </div>\n </div>\n <div class=\"form-grid\">\n <div class=\"form-row\">\n <mat-form-field appearance=\"fill\" class=\"w-100\">\n <mat-label>Modo de dados</mat-label>\n <mat-select [(ngModel)]=\"inputMode\" (ngModelChange)=\"onInputModeChange()\">\n <mat-option value=\"create\">create</mat-option>\n <mat-option value=\"edit\">edit</mat-option>\n <mat-option value=\"view\">view</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div class=\"block-status\" [class.block-status--implicit]=\"!isBindingsBlockPersisted()\">\n <strong>Bindings</strong>\n <span *ngIf=\"isBindingsBlockPersisted(); else bindingsMissing\">`bindings.mode` ser\u00E1 persistido no documento can\u00F4nico.</span>\n <ng-template #bindingsMissing>Bloco ausente no documento can\u00F4nico. O valor exibido \u00E9 fallback visual e n\u00E3o ser\u00E1 salvo at\u00E9 voc\u00EA editar este campo.</ng-template>\n </div>\n <div class=\"section-help\">\n <span class=\"section-help__label\">Entenda:</span>\n <button\n mat-icon-button\n class=\"help-icon-button\"\n type=\"button\"\n [matTooltip]=\"'O mode controla o fluxo (create/edit/view). enableCustomization s\u00F3 libera edi\u00E7\u00E3o do layout e n\u00E3o altera o mode.'\"\n matTooltipPosition=\"above\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </div>\n </div>\n </mat-card-content>\n </mat-card>\n\n <mat-card\n class=\"section-card\"\n *ngIf=\"hasResolvedRuntimeContract()\"\n data-testid=\"config-tab-runtime-contract\"\n >\n <mat-card-content>\n <div class=\"section-header\">\n <div>\n <h3>Contrato Runtime Resolvido</h3>\n <p class=\"text-caption\">\n Visualiza\u00E7\u00E3o somente leitura do contrato backend resolvido pelo host atual.\n </p>\n </div>\n </div>\n <div class=\"block-status block-status--readonly\">\n <strong>Runtime</strong>\n <span>\n Esses valores v\u00EAm do host/runtime atual e n\u00E3o s\u00E3o persistidos em\n <code>bindings</code> nem em <code>contextSnapshot</code>.\n </span>\n </div>\n <div class=\"runtime-contract-grid\">\n <div class=\"runtime-contract-field\">\n <span class=\"runtime-contract-label\">Schema URL</span>\n <code class=\"runtime-contract-code\">{{ runtimeContract?.schemaUrl || '\u2014' }}</code>\n </div>\n <div class=\"runtime-contract-field\">\n <span class=\"runtime-contract-label\">Origem do schema</span>\n <strong>{{ describeRuntimeSource(runtimeContract?.schemaSource) }}</strong>\n </div>\n <div class=\"runtime-contract-field\">\n <span class=\"runtime-contract-label\">Submit method</span>\n <strong>{{ runtimeContract?.submitMethod || '\u2014' }}</strong>\n </div>\n <div class=\"runtime-contract-field\">\n <span class=\"runtime-contract-label\">Submit URL</span>\n <code class=\"runtime-contract-code\">{{ runtimeContract?.submitUrl || '\u2014' }}</code>\n </div>\n <div class=\"runtime-contract-field\">\n <span class=\"runtime-contract-label\">Origem do submit</span>\n <strong>{{ describeRuntimeSource(runtimeContract?.submitSource) }}</strong>\n </div>\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n </mat-tab>\n <mat-tab label=\"Verifica\u00E7\u00E3o de Schema\">\n <div class=\"tab-content\" data-testid=\"config-tab-panel-schema\">\n <mat-card class=\"section-card\">\n <mat-card-content>\n <div class=\"section-header\">\n <div>\n <h3>Valida\u00E7\u00E3o de schema</h3>\n <p class=\"text-caption\">Controle avisos quando o schema do servidor mudar.</p>\n </div>\n </div>\n <div class=\"form-grid\">\n <div class=\"form-row\">\n <mat-form-field appearance=\"fill\" class=\"w-100\">\n <mat-label>Notificar quando desatualizado</mat-label>\n <mat-select [(ngModel)]=\"schemaPrefs.notifyIfOutdated\" (ngModelChange)=\"onSchemaPrefsChange('schema-prefs-notify')\">\n <mat-option value=\"both\">Banner + Snackbar</mat-option>\n <mat-option value=\"inline\">Somente Banner</mat-option>\n <mat-option value=\"snackbar\">Somente Snackbar</mat-option>\n <mat-option value=\"none\">Nenhum</mat-option>\n </mat-select>\n </mat-form-field>\n </div>\n <div class=\"form-row\">\n <mat-form-field appearance=\"fill\" class=\"w-100\">\n <mat-label>Intervalo de sil\u00EAncio (ms)</mat-label>\n <input matInput type=\"number\" [(ngModel)]=\"schemaPrefs.snoozeMs\" (ngModelChange)=\"onSchemaPrefsChange('schema-prefs-snooze')\" />\n </mat-form-field>\n </div>\n <div class=\"form-row\">\n <mat-slide-toggle [(ngModel)]=\"schemaPrefs.autoOpenSettingsOnOutdated\" (ngModelChange)=\"onSchemaPrefsChange('schema-prefs-auto-open')\">Abrir configura\u00E7\u00F5es\n automaticamente</mat-slide-toggle>\n </div>\n <div class=\"form-row\" *ngIf=\"serverMeta\">\n <div class=\"meta-card\">\n <div class=\"meta-row\">\n <span>Server hash</span>\n <strong>{{ serverMeta.serverHash || '\u2014' }}</strong>\n </div>\n <div class=\"meta-row\">\n <span>\u00DAltima verifica\u00E7\u00E3o</span>\n <strong>{{ serverMeta.lastVerifiedAt || '\u2014' }}</strong>\n </div>\n </div>\n </div>\n <div class=\"block-status\" [class.block-status--implicit]=\"!isSchemaPrefsBlockPersisted()\">\n <strong>Schema Prefs</strong>\n <span *ngIf=\"isSchemaPrefsBlockPersisted(); else schemaPrefsMissing\">`contextSnapshot.schemaPrefs` ser\u00E1 persistido no documento can\u00F4nico.</span>\n <ng-template #schemaPrefsMissing>Bloco ausente no documento can\u00F4nico. Os controles exibem fallback visual e n\u00E3o ser\u00E3o salvos at\u00E9 voc\u00EA editar.</ng-template>\n </div>\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n </mat-tab>\n <mat-tab label=\"Layout\">\n <div class=\"tab-content tab-content--full\" data-testid=\"config-tab-panel-layout\">\n <div class=\"section-header section-header--compact\">\n <div>\n <h3>Layout do formul\u00E1rio</h3>\n <p class=\"text-caption\">Organize se\u00E7\u00F5es, linhas e campos com drag & drop.</p>\n </div>\n </div>\n <div class=\"section-body section-body--flex\">\n <praxis-layout-editor [config]=\"editedConfig\" (configChange)=\"onConfigChange($event)\"\n (select)=\"onLayoutSelect($event)\"></praxis-layout-editor>\n </div>\n </div>\n </mat-tab>\n <mat-tab label=\"Hooks\">\n <div class=\"tab-content\" data-testid=\"config-tab-panel-hooks\">\n <mat-card class=\"section-card\">\n <mat-card-content>\n <div class=\"section-header\">\n <div>\n <h3>Hooks de ciclo de vida</h3>\n <p class=\"text-caption\">Automatize a\u00E7\u00F5es nos eventos do formul\u00E1rio.</p>\n </div>\n </div>\n <praxis-hooks-editor [config]=\"editedConfig\" (configChange)=\"onConfigChange($event)\"></praxis-hooks-editor>\n </mat-card-content>\n </mat-card>\n </div>\n </mat-tab>\n <mat-tab *ngIf=\"isPresentationMode\" label=\"Modo Apresenta\u00E7\u00E3o\">\n <div class=\"tab-content\" data-testid=\"config-tab-panel-presentation\">\n <mat-card class=\"section-card\">\n <mat-card-content>\n <div class=\"section-header\">\n <div>\n <h3>Modo Apresenta\u00E7\u00E3o</h3>\n <p class=\"text-caption\">Configura\u00E7\u00F5es de exibi\u00E7\u00E3o aplicadas somente ao modo apresenta\u00E7\u00E3o.</p>\n </div>\n </div>\n\n <div class=\"presentation-layout\">\n <div class=\"presentation-controls\">\n <div class=\"block-status control-span\" [class.block-status--implicit]=\"!isPresentationBlockPersisted()\">\n <strong>Presentation</strong>\n <span *ngIf=\"isPresentationBlockPersisted(); else presentationMissing\">`contextSnapshot.presentation` ser\u00E1 persistido no documento can\u00F4nico.</span>\n <ng-template #presentationMissing>Bloco ausente no documento can\u00F4nico. A pr\u00E9-visualiza\u00E7\u00E3o usa fallback local e n\u00E3o ser\u00E1 salva at\u00E9 voc\u00EA editar.</ng-template>\n </div>\n <div class=\"control\">\n <label class=\"control__label\">Posi\u00E7\u00E3o do r\u00F3tulo</label>\n <mat-button-toggle-group [(ngModel)]=\"presentationPrefs.labelPosition\" (ngModelChange)=\"onPresentationPrefsChange('presentation-label-position')\" aria-label=\"Posi\u00E7\u00E3o do r\u00F3tulo\"\n class=\"toggle-group\">\n <mat-button-toggle value=\"above\">\n <mat-icon>view_stream</mat-icon>\n <span class=\"sr-only\">Acima</span>\n </mat-button-toggle>\n <mat-button-toggle value=\"left\">\n <mat-icon>view_column</mat-icon>\n <span class=\"sr-only\">\u00C0 esquerda</span>\n </mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n\n <div class=\"control\">\n <label class=\"control__label\">Densidade</label>\n <mat-button-toggle-group [(ngModel)]=\"presentationPrefs.density\" (ngModelChange)=\"onPresentationPrefsChange('presentation-density')\" aria-label=\"Densidade\"\n class=\"toggle-group\">\n <mat-button-toggle value=\"comfortable\">Confort\u00E1vel</mat-button-toggle>\n <mat-button-toggle value=\"cozy\">Intermedi\u00E1ria</mat-button-toggle>\n <mat-button-toggle value=\"compact\">Compacta</mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n\n <div class=\"control\">\n <label class=\"control__label\">Alinhamento do r\u00F3tulo</label>\n <mat-button-toggle-group [(ngModel)]=\"presentationPrefs.labelAlign\" (ngModelChange)=\"onPresentationPrefsChange('presentation-label-align')\" aria-label=\"Alinhamento do r\u00F3tulo\"\n class=\"toggle-group\">\n <mat-button-toggle value=\"start\"><mat-icon>format_align_left</mat-icon></mat-button-toggle>\n <mat-button-toggle value=\"center\"><mat-icon>format_align_center</mat-icon></mat-button-toggle>\n <mat-button-toggle value=\"end\"><mat-icon>format_align_right</mat-icon></mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n\n <div class=\"control\">\n <label class=\"control__label\">Alinhamento do valor</label>\n <mat-button-toggle-group [(ngModel)]=\"presentationPrefs.valueAlign\" (ngModelChange)=\"onPresentationPrefsChange('presentation-value-align')\" aria-label=\"Alinhamento do valor\"\n class=\"toggle-group\">\n <mat-button-toggle value=\"start\"><mat-icon>format_align_left</mat-icon></mat-button-toggle>\n <mat-button-toggle value=\"center\"><mat-icon>format_align_center</mat-icon></mat-button-toggle>\n <mat-button-toggle value=\"end\"><mat-icon>format_align_right</mat-icon></mat-button-toggle>\n </mat-button-toggle-group>\n </div>\n\n <div class=\"control\">\n <mat-form-field appearance=\"fill\" class=\"w-100\">\n <mat-label>Tamanho do r\u00F3tulo</mat-label>\n <input matInput type=\"number\" min=\"10\" [(ngModel)]=\"presentationPrefs.labelFontSize\" (ngModelChange)=\"onPresentationPrefsChange('presentation-label-font-size')\"\n placeholder=\"ex.: 12\" />\n <span matSuffix>px</span>\n </mat-form-field>\n </div>\n\n <div class=\"control\">\n <mat-form-field appearance=\"fill\" class=\"w-100\">\n <mat-label>Tamanho do valor</mat-label>\n <input matInput type=\"number\" min=\"10\" [(ngModel)]=\"presentationPrefs.valueFontSize\" (ngModelChange)=\"onPresentationPrefsChange('presentation-value-font-size')\"\n placeholder=\"ex.: 16\" />\n <span matSuffix>px</span>\n </mat-form-field>\n </div>\n\n <div class=\"control\">\n <mat-form-field appearance=\"fill\" class=\"w-100\">\n <mat-label>Largura do r\u00F3tulo (label \u00E0 esquerda)</mat-label>\n <input matInput type=\"number\" min=\"60\" [(ngModel)]=\"presentationPrefs.labelWidth\" (ngModelChange)=\"onPresentationPrefsChange('presentation-label-width')\"\n placeholder=\"ex.: 140\" />\n <span matSuffix>px</span>\n </mat-form-field>\n </div>\n\n <div class=\"control control--inline\">\n <mat-slide-toggle [(ngModel)]=\"presentationPrefs.compact\" (ngModelChange)=\"onPresentationPrefsChange('presentation-compact')\">\n Modo compacto\n </mat-slide-toggle>\n <mat-checkbox [(ngModel)]=\"truncatePreview\">\n Truncar valores\n </mat-checkbox>\n </div>\n </div>\n\n <div class=\"presentation-preview\">\n <h4>Pr\u00E9\u2011visualiza\u00E7\u00E3o</h4>\n <div class=\"preview-card\">\n <div class=\"praxis-dynamic-form presentation-mode\" [ngClass]=\"{\n 'pres-compact': !!presentationPrefs.compact,\n 'pres-label-left': presentationPrefs.labelPosition === 'left',\n 'pres-label-above': presentationPrefs.labelPosition === 'above',\n 'pres-density-compact': presentationPrefs.density === 'compact',\n 'pres-density-cozy': presentationPrefs.density === 'cozy'\n }\" [style.--pfx-pres-label-size]=\"(presentationPrefs.labelFontSize || 12) + 'px'\"\n [style.--pfx-pres-value-size]=\"(presentationPrefs.valueFontSize || 16) + 'px'\"\n [style.--pfx-pres-label-width]=\"(presentationPrefs.labelWidth || 140) + 'px'\"\n [style.--pfx-pres-label-align]=\"presentationPrefs.labelAlign || 'start'\"\n [style.--pfx-pres-value-align]=\"presentationPrefs.valueAlign || 'start'\">\n <div class=\"preview-row\" *ngFor=\"let item of previewItems\">\n <span class=\"praxis-presentation__label\">{{ item.label }}</span>\n <span class=\"praxis-presentation__value\" [title]=\"truncatePreview ? item.value : null\"\n [style.maxWidth]=\"truncatePreview ? '280px' : null\"\n [style.overflow]=\"truncatePreview ? 'hidden' : null\"\n [style.textOverflow]=\"truncatePreview ? 'ellipsis' : null\"\n [style.whiteSpace]=\"truncatePreview ? 'nowrap' : 'normal'\">\n {{ item.value }}\n </span>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <div class=\"section-help\">\n <span class=\"section-help__label\">Entenda:</span>\n <button\n mat-icon-button\n class=\"help-icon-button\"\n type=\"button\"\n [matTooltip]=\"'Ajustes aplicam somente ao Modo Apresenta\u00E7\u00E3o e s\u00E3o salvos por formul\u00E1rio (via CSS/Classes).'\"\n matTooltipPosition=\"above\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n </mat-tab>\n <mat-tab label=\"Comportamento\">\n <div class=\"tab-content\" data-testid=\"config-tab-panel-comportamento\">\n <mat-card class=\"section-card\">\n <mat-card-content>\n <div class=\"section-header\">\n <div>\n <h3>Comportamento</h3>\n <p class=\"text-caption\">Ajuste valida\u00E7\u00F5es e respostas do formul\u00E1rio.</p>\n </div>\n </div>\n <praxis-behavior-editor [config]=\"editedConfig\" (configChange)=\"onConfigChange($event)\"></praxis-behavior-editor>\n </mat-card-content>\n </mat-card>\n </div>\n </mat-tab>\n <mat-tab label=\"Dicas e Tooltips\">\n <div class=\"tab-content tab-content--dense\" data-testid=\"config-tab-panel-hints\">\n <mat-card class=\"section-card\">\n <mat-card-content>\n <div class=\"section-header\">\n <div>\n <h3>Dicas dos modos</h3>\n <p class=\"text-caption\">Personalize textos de apoio para modos de dados e estados de UI.</p>\n </div>\n </div>\n <p class=\"text-caption\">Personalize os textos usados como tooltip/hint para modos de dados e modos globais de\n UI.</p>\n\n <div class=\"grid-2-cols grid-2-cols--compact\">\n <section>\n <h4>Modos de dados</h4>\n <mat-form-field appearance=\"fill\" class=\"w-100\">\n <mat-label>Criar</mat-label>\n <input matInput [(ngModel)]=\"editedConfig.hints!.dataModes.create\"\n (ngModelChange)=\"updateDirtyState('hints-data-create')\" placeholder=\"Ex.: Preencha os campos obrigat\u00F3rios.\" />\n </mat-form-field>\n <mat-form-field appearance=\"fill\" class=\"w-100\">\n <mat-label>Editar</mat-label>\n <input matInput [(ngModel)]=\"editedConfig.hints!.dataModes.edit\"\n (ngModelChange)=\"updateDirtyState('hints-data-edit')\" placeholder=\"Ex.: Revise os dados antes de salvar.\" />\n </mat-form-field>\n <mat-form-field appearance=\"fill\" class=\"w-100\">\n <mat-label>Visualizar</mat-label>\n <input matInput [(ngModel)]=\"editedConfig.hints!.dataModes.view\"\n (ngModelChange)=\"updateDirtyState('hints-data-view')\" placeholder=\"Ex.: Visualiza\u00E7\u00E3o somente leitura.\" />\n </mat-form-field>\n </section>\n\n <section>\n <h4>Modos de UI</h4>\n <mat-form-field appearance=\"fill\" class=\"w-100\">\n <mat-label>Apresenta\u00E7\u00E3o</mat-label>\n <textarea matInput rows=\"2\" [(ngModel)]=\"editedConfig.hints!.uiModes.presentation\"\n (ngModelChange)=\"updateDirtyState('hints-ui-presentation')\" placeholder=\"Ex.: Modo compacto para leitura r\u00E1pida.\"></textarea>\n </mat-form-field>\n <mat-form-field appearance=\"fill\" class=\"w-100\">\n <mat-label>Leitura (readonly)</mat-label>\n <textarea matInput rows=\"2\" [(ngModel)]=\"editedConfig.hints!.uiModes.readonly\"\n (ngModelChange)=\"updateDirtyState('hints-ui-readonly')\" placeholder=\"Ex.: Campos bloqueados para edi\u00E7\u00E3o.\"></textarea>\n </mat-form-field>\n <mat-form-field appearance=\"fill\" class=\"w-100\">\n <mat-label>Desabilitado</mat-label>\n <textarea matInput rows=\"2\" [(ngModel)]=\"editedConfig.hints!.uiModes.disabled\"\n (ngModelChange)=\"updateDirtyState('hints-ui-disabled')\" placeholder=\"Ex.: Fun\u00E7\u00E3o indispon\u00EDvel no momento.\"></textarea>\n </mat-form-field>\n <mat-form-field appearance=\"fill\" class=\"w-100\">\n <mat-label>Vis\u00EDvel</mat-label>\n <textarea matInput rows=\"2\" [(ngModel)]=\"editedConfig.hints!.uiModes.visible\"\n (ngModelChange)=\"updateDirtyState('hints-ui-visible')\" placeholder=\"Ex.: Campo exibido conforme permiss\u00F5es.\"></textarea>\n </mat-form-field>\n </section>\n </div>\n\n <div class=\"actions-row\">\n <button mat-stroked-button color=\"primary\" type=\"button\" (click)=\"restoreHintsDefaults()\">\n <mat-icon>restore</mat-icon>\n Restaurar padr\u00F5es\n </button>\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n </mat-tab>\n <mat-tab label=\"A\u00E7\u00F5es\">\n <div class=\"tab-content\" data-testid=\"config-tab-panel-acoes\">\n <mat-card class=\"section-card\">\n <mat-card-content>\n <div class=\"section-header\">\n <div>\n <h3>A\u00E7\u00F5es do formul\u00E1rio</h3>\n <p class=\"text-caption\">Configure bot\u00F5es padr\u00E3o e a\u00E7\u00F5es customizadas.</p>\n </div>\n </div>\n <praxis-actions-editor [config]=\"editedConfig\" (configChange)=\"onConfigChange($event)\"></praxis-actions-editor>\n </mat-card-content>\n </mat-card>\n </div>\n </mat-tab>\n <mat-tab label=\"Regras\">\n <div class=\"tab-content visual-builder-content\" data-testid=\"config-tab-panel-regras\">\n <div class=\"builder-header\">\n <div>\n <h3>Regras visuais</h3>\n <p class=\"text-caption\">Defina visibilidade, estilos e rea\u00E7\u00F5es baseadas em condi\u00E7\u00F5es.</p>\n </div>\n </div>\n <praxis-visual-builder [config]=\"ruleBuilderConfig\" [initialRules]=\"ruleBuilderState\"\n (rulesChanged)=\"onRulesChanged($event)\"></praxis-visual-builder>\n </div>\n </mat-tab>\n <mat-tab label=\"Cascatas\">\n <div class=\"tab-content\" data-testid=\"config-tab-panel-cascatas\">\n <mat-card class=\"section-card\">\n <mat-card-content>\n <div class=\"section-header\">\n <div>\n <h3>Depend\u00EAncias entre campos</h3>\n <p class=\"text-caption\">Configure cascatas e carregamento entre campos.</p>\n </div>\n </div>\n <praxis-cascade-manager-tab [fields]=\"editedConfig.fieldMetadata || []\"\n (apply)=\"onCascadeApply($event)\"></praxis-cascade-manager-tab>\n </mat-card-content>\n </mat-card>\n </div>\n </mat-tab>\n <mat-tab label=\"Mensagens\">\n <div class=\"tab-content\" data-testid=\"config-tab-panel-mensagens\">\n <mat-card class=\"section-card\">\n <mat-card-content>\n <div class=\"section-header\">\n <div>\n <h3>Mensagens e feedback</h3>\n <p class=\"text-caption\">Padronize textos de sucesso, erro e confirma\u00E7\u00F5es.</p>\n </div>\n </div>\n <praxis-messages-editor [config]=\"editedConfig\" (configChange)=\"onConfigChange($event)\"></praxis-messages-editor>\n </mat-card-content>\n </mat-card>\n </div>\n </mat-tab>\n <mat-tab label=\"Navega\u00E7\u00E3o\">\n <div class=\"tab-content\" data-testid=\"config-tab-panel-navegacao\">\n <mat-card class=\"section-card\">\n <mat-card-content>\n <div class=\"section-header\">\n <div>\n <h3>Navega\u00E7\u00E3o de retorno</h3>\n <p class=\"text-caption\">Defina o comportamento do bot\u00E3o Voltar em fluxos CRUD.</p>\n </div>\n </div>\n <div class=\"form-grid\">\n <div class=\"form-row\">\n <mat-form-field appearance=\"fill\" class=\"w-100\">\n <mat-label>Rota de retorno</mat-label>\n <input matInput id=\"returnTo\" [(ngModel)]=\"backConfig!.returnTo\" (ngModelChange)=\"onBackConfigChange()\" placeholder=\"/funcionarios\" />\n </mat-form-field>\n </div>\n <div class=\"form-row\">\n <mat-slide-toggle [(ngModel)]=\"backConfig!.confirmOnDirty\" (ngModelChange)=\"onBackConfigChange()\">\n Confirmar ao sair com altera\u00E7\u00F5es\n </mat-slide-toggle>\n </div>\n <div class=\"block-status\" [class.block-status--implicit]=\"!isBackConfigBlockPersisted()\">\n <strong>Back Config</strong>\n <span *ngIf=\"isBackConfigBlockPersisted(); else backConfigMissing\">`contextSnapshot.backConfig` ser\u00E1 persistido no documento can\u00F4nico.</span>\n <ng-template #backConfigMissing>Bloco ausente no documento can\u00F4nico. Os valores exibidos s\u00E3o fallback visual e n\u00E3o ser\u00E3o salvos at\u00E9 voc\u00EA editar.</ng-template>\n </div>\n </div>\n <div class=\"section-help\">\n <span class=\"section-help__label\">Observa\u00E7\u00E3o:</span>\n <button\n mat-icon-button\n class=\"help-icon-button\"\n type=\"button\"\n [matTooltip]=\"'Afeta o fluxo de navega\u00E7\u00E3o do CRUD e pode ser sobreposto por metadados do recurso.'\"\n matTooltipPosition=\"above\"\n >\n <mat-icon>help_outline</mat-icon>\n </button>\n </div>\n </mat-card-content>\n </mat-card>\n </div>\n </mat-tab>\n <mat-tab label=\"JSON\">\n <div class=\"tab-content\" data-testid=\"config-tab-panel-json\">\n <mat-card class=\"section-card\">\n <mat-card-content>\n <div class=\"section-header\">\n <div>\n <h3>Configura\u00E7\u00E3o JSON</h3>\n <p class=\"text-caption\">Edi\u00E7\u00E3o avan\u00E7ada do documento can\u00F4nico de autoria do formul\u00E1rio.</p>\n </div>\n </div>\n <form-json-config-editor [document]=\"jsonDocument\" (documentChange)=\"onJsonConfigChange($event)\"\n (validationChange)=\"onJsonValidationChange($event)\" (editorEvent)=\"onJsonEditorEvent($event)\">\n </form-json-config-editor>\n </mat-card-content>\n </mat-card>\n </div>\n </mat-tab>\n</mat-tab-group>\n", styles: [":host{display:block;height:100%}.config-tabs{height:100%}::ng-deep .mat-mdc-tab-group.config-tabs{display:flex;flex-direction:column}::ng-deep .mat-mdc-tab-group.config-tabs .mat-mdc-tab-body-wrapper{flex:1;min-height:0}::ng-deep .mat-mdc-tab-group.config-tabs .mat-mdc-tab-body-content{height:100%;overflow:auto;display:flex;flex-direction:column;padding:0!important}.tab-content{padding:16px 20px 20px;flex:1}.form-row{display:block;margin-bottom:12px}.form-grid{display:grid;gap:12px}.section-help{display:inline-flex;align-items:center;gap:8px;color:var(--md-sys-color-on-surface-variant);font-size:12px}.section-help__label{font-weight:500}.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}.help-icon-button mat-icon{font-size:18px;width:18px;height:18px}.grid-2-cols{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:16px}.grid-2-cols--compact{gap:12px}.w-100{width:100%}.visual-builder-content{padding:0!important;height:100%;overflow:hidden;display:flex;flex-direction:column}.visual-builder-content>praxis-visual-builder{flex:1;min-height:0}.json-field{width:100%}.presentation-layout{display:flex;gap:20px;align-items:flex-start}.presentation-controls{flex:1 1 60%;display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px 18px}.control{display:flex;flex-direction:column}.control-span{grid-column:1/-1}.block-status{grid-column:1/-1;display:flex;gap:8px;align-items:flex-start;padding:10px 12px;border-radius:10px;background:color-mix(in srgb,var(--md-sys-color-primary-container) 35%,transparent);color:var(--md-sys-color-on-surface);font-size:12px;line-height:1.45}.block-status strong{min-width:112px;font-size:11px;letter-spacing:.04em;text-transform:uppercase;color:var(--md-sys-color-on-surface-variant)}.block-status--implicit{background:color-mix(in srgb,var(--md-sys-color-surface-variant) 42%,transparent);border:1px dashed var(--md-sys-color-outline)}.block-status--readonly{margin-bottom:12px}.block-status code{font-family:var(--md-ref-typeface-mono, \"Courier New\", monospace);font-size:11px}.control__label{font-size:12px;color:var(--md-sys-color-on-surface-variant);margin-bottom:6px}.toggle-group{width:100%}.control--inline{grid-column:1/-1;display:flex;gap:16px;align-items:center}.presentation-preview{flex:0 0 360px;position:sticky;top:16px}.preview-card{border:1px solid var(--md-sys-color-outline);border-radius:12px;padding:14px;background:var(--md-sys-color-surface-container)}.preview-row{display:flex;gap:8px;padding:6px 0;border-bottom:1px dashed var(--md-sys-color-outline-variant)}.preview-row:last-child{border-bottom:none}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.presentation-preview .praxis-presentation__label{color:var(--md-sys-color-on-surface-variant)}.presentation-preview .praxis-presentation__value{color:var(--md-sys-color-on-surface)}.presentation-preview .presentation-mode.pres-density-cozy .preview-row{padding:6px 0}.presentation-preview .presentation-mode.pres-density-compact .preview-row,.presentation-preview .presentation-mode.pres-compact .preview-row{padding:2px 0}.presentation-preview .presentation-mode.pres-label-left .preview-row{display:grid;grid-template-columns:var(--pfx-pres-label-width, 140px) 1fr;align-items:baseline;column-gap:10px}.presentation-preview .presentation-mode.pres-label-left .praxis-presentation__label{margin:0;text-align:var(--pfx-pres-label-align, start)}.presentation-preview .presentation-mode .praxis-presentation__value{text-align:var(--pfx-pres-value-align, start)}.presentation-preview h4{margin:0 0 8px;color:var(--md-sys-color-on-surface);font-size:1rem}.section-card{border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);box-shadow:var(--md-sys-elevation-level1, none)}.section-header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:12px}.tab-content--dense{padding:12px 16px 16px}.tab-content--dense .section-header,.tab-content--dense .form-row{margin-bottom:8px}.section-header h3{margin:0;font-size:1.05rem;color:var(--md-sys-color-on-surface)}.text-caption{margin:6px 0 0;font-size:.9rem;color:var(--md-sys-color-on-surface-variant)}.section-header--compact{margin-bottom:8px}.section-body{display:block}.section-body--flex{flex:1;min-height:0;display:flex;flex-direction:column}.builder-header{padding:12px 16px;border-bottom:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface)}.builder-header h3{margin:0;font-size:1rem;color:var(--md-sys-color-on-surface)}.hint{margin:8px 0 0;padding:10px 12px;border-radius:10px;border:1px dashed var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface-container);color:var(--md-sys-color-on-surface-variant)}.meta-card{display:grid;gap:6px;padding:10px 12px;border-radius:10px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface);color:var(--md-sys-color-on-surface)}.meta-row{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:.9rem;color:var(--md-sys-color-on-surface-variant)}.meta-row strong{color:var(--md-sys-color-on-surface);font-weight:600}.runtime-contract-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px}.runtime-contract-field{display:grid;gap:6px;padding:12px;border-radius:10px;border:1px solid var(--md-sys-color-outline-variant);background:var(--md-sys-color-surface)}.runtime-contract-label{font-size:12px;color:var(--md-sys-color-on-surface-variant)}.runtime-contract-code{display:block;font-family:var(--md-ref-typeface-mono, \"Courier New\", monospace);font-size:12px;line-height:1.45;color:var(--md-sys-color-on-surface);word-break:break-word;white-space:normal}\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: FormsModule }, { kind: "directive", type: i1$3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$3.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$3.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1$3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatTabsModule }, { kind: "component", type: i7$3.MatTab, selector: "mat-tab", inputs: ["disabled", "label", "aria-label", "aria-labelledby", "labelClass", "bodyClass", "id"], exportAs: ["matTab"] }, { kind: "component", type: i7$3.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: MatIconModule }, { kind: "component", type: i5.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i8.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i8.MatCardContent, selector: "mat-card-content" }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i4.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: i4.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4$1.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4$1.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i6$3.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i6$3.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i7$1.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: MatCheckboxModule }, { kind: "component", type: i12$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i6$2.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: MatButtonToggleModule }, { kind: "directive", type: i7$2.MatButtonToggleGroup, selector: "mat-button-toggle-group", inputs: ["appearance", "name", "vertical", "value", "multiple", "disabled", "disabledInteractive", "hideSingleSelectionIndicator", "hideMultipleSelectionIndicator"], outputs: ["valueChange", "change"], exportAs: ["matButtonToggleGroup"] }, { kind: "component", type: i7$2.MatButtonToggle, selector: "mat-button-toggle", inputs: ["aria-label", "aria-labelledby", "id", "name", "value", "tabIndex", "disableRipple", "appearance", "checked", "disabled", "disabledInteractive"], outputs: ["change"], exportAs: ["matButtonToggle"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i9.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: JsonConfigEditorComponent, selector: "form-json-config-editor", inputs: ["config", "document"], outputs: ["configChange", "documentChange", "validationChange", "editorEvent"] }, { kind: "component", type: LayoutEditorComponent, selector: "praxis-layout-editor", inputs: ["config"], outputs: ["configChange", "select"] }, { kind: "component", type: BehaviorEditorComponent, selector: "praxis-behavior-editor", inputs: ["config"], outputs: ["configChange"] }, { kind: "component", type: ActionsEditorComponent$1, selector: "praxis-actions-editor", inputs: ["config"], outputs: ["configChange"] }, { kind: "component", type: MessagesEditorComponent, selector: "praxis-messages-editor", inputs: ["config"], outputs: ["configChange"] }, { kind: "component", type: HooksEditorComponent, selector: "praxis-hooks-editor", inputs: ["config"], outputs: ["configChange"] }, { kind: "component", type: PraxisVisualBuilder, selector: "praxis-visual-builder", inputs: ["config", "initialRules"], outputs: ["rulesChanged", "exportRequested", "importRequested"] }, { kind: "component", type: CascadeManagerTabComponent, selector: "praxis-cascade-manager-tab", inputs: ["fields", "connections"], outputs: ["apply", "cancel"] }] });
|
|
19691
19709
|
}
|
|
19692
19710
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: PraxisDynamicFormConfigEditor, decorators: [{
|
|
@@ -19719,6 +19737,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
19719
19737
|
args: [ASYNC_CONFIG_STORAGE]
|
|
19720
19738
|
}] }, { type: FormConfigService }, { type: i7.SettingsPanelService }, { type: i0.ChangeDetectorRef }, { type: undefined, decorators: [{
|
|
19721
19739
|
type: Optional
|
|
19740
|
+
}, {
|
|
19741
|
+
type: Inject,
|
|
19742
|
+
args: [GLOBAL_ACTION_CATALOG]
|
|
19743
|
+
}] }, { type: undefined, decorators: [{
|
|
19744
|
+
type: Optional
|
|
19722
19745
|
}, {
|
|
19723
19746
|
type: Inject,
|
|
19724
19747
|
args: [SETTINGS_PANEL_DATA]
|
|
@@ -20087,6 +20110,7 @@ const PRAXIS_DYNAMIC_FORM_COMPONENT_METADATA = {
|
|
|
20087
20110
|
inputs: [
|
|
20088
20111
|
{ name: 'resourcePath', type: 'string', label: 'Recurso', description: 'Recurso base para CRUD/schemas' },
|
|
20089
20112
|
{ name: 'resourceId', type: 'string | number', label: 'ID do Registro', description: 'Identificador do registro' },
|
|
20113
|
+
{ name: 'initialValue', type: 'Record<string, unknown> | null', label: 'Valor inicial', description: 'Valores iniciais locais aplicados antes da interacao do usuario' },
|
|
20090
20114
|
{ name: 'editorialContext', type: 'Record<string, unknown> | null', label: 'Contexto editorial', description: 'Contexto compartilhado usado para resolver bindings do rich content hospedado no form' },
|
|
20091
20115
|
{ name: 'mode', type: "'create' | 'edit' | 'view'", default: 'create', label: 'Modo', description: 'Modo do formulario' },
|
|
20092
20116
|
{
|
|
@@ -20533,9 +20557,7 @@ class SectionEditorComponent {
|
|
|
20533
20557
|
isBusy$ = new BehaviorSubject(false);
|
|
20534
20558
|
globalActionCatalogSource = inject(GLOBAL_ACTION_CATALOG, { optional: true }) ?? [];
|
|
20535
20559
|
i18n = inject(PraxisI18nService);
|
|
20536
|
-
legacyActionSpecs = GLOBAL_ACTION_SPEC_CATALOG;
|
|
20537
20560
|
globalActionCatalog = buildGlobalActionCatalog({
|
|
20538
|
-
legacySpecs: this.legacyActionSpecs,
|
|
20539
20561
|
injectedCatalog: getGlobalActionCatalog(this.globalActionCatalogSource),
|
|
20540
20562
|
fallbackCatalog: PRAXIS_GLOBAL_ACTION_CATALOG,
|
|
20541
20563
|
mapCatalogEntry: (entry) => this.mapCatalogEntryToActionSpec(entry),
|
|
@@ -20642,63 +20664,66 @@ class SectionEditorComponent {
|
|
|
20642
20664
|
getHeaderActionSpecById(id) {
|
|
20643
20665
|
return this.globalActionCatalog.find((item) => item.id === id);
|
|
20644
20666
|
}
|
|
20645
|
-
getHeaderActionSelectValue(value) {
|
|
20646
|
-
|
|
20647
|
-
return parsed.isGlobal ? parsed.id : this.customActionValue;
|
|
20667
|
+
getHeaderActionSelectValue(value, globalAction) {
|
|
20668
|
+
return globalAction?.actionId || this.customActionValue;
|
|
20648
20669
|
}
|
|
20649
|
-
getHeaderActionParam(value) {
|
|
20650
|
-
const
|
|
20651
|
-
|
|
20670
|
+
getHeaderActionParam(value, globalAction) {
|
|
20671
|
+
const payload = globalAction?.payload;
|
|
20672
|
+
if (payload === undefined || payload === null)
|
|
20673
|
+
return '';
|
|
20674
|
+
return typeof payload === 'string' ? payload : JSON.stringify(payload, null, 2);
|
|
20652
20675
|
}
|
|
20653
|
-
getHeaderActionCustomValue(value) {
|
|
20654
|
-
|
|
20655
|
-
return parsed.isGlobal ? '' : parsed.raw;
|
|
20676
|
+
getHeaderActionCustomValue(value, globalAction) {
|
|
20677
|
+
return globalAction ? '' : value || '';
|
|
20656
20678
|
}
|
|
20657
|
-
getHeaderGlobalActionSchema(value) {
|
|
20658
|
-
|
|
20659
|
-
if (!parsed.isGlobal)
|
|
20660
|
-
return undefined;
|
|
20661
|
-
return getGlobalActionUiSchema(parsed.id);
|
|
20679
|
+
getHeaderGlobalActionSchema(value, globalAction) {
|
|
20680
|
+
return getGlobalActionUiSchema(globalAction?.actionId);
|
|
20662
20681
|
}
|
|
20663
|
-
getHeaderSurfaceOpenActionPayload(value) {
|
|
20664
|
-
|
|
20665
|
-
const payload = info.isJson && info.json && typeof info.json === 'object' && !Array.isArray(info.json)
|
|
20666
|
-
? info.json
|
|
20667
|
-
: undefined;
|
|
20668
|
-
return normalizeSurfaceOpenPayload(payload);
|
|
20682
|
+
getHeaderSurfaceOpenActionPayload(value, globalAction) {
|
|
20683
|
+
return normalizeSurfaceOpenPayload(globalAction?.payload);
|
|
20669
20684
|
}
|
|
20670
|
-
isHeaderActionParamMissing(value) {
|
|
20671
|
-
return isRequiredParamMissing(
|
|
20685
|
+
isHeaderActionParamMissing(value, globalAction) {
|
|
20686
|
+
return isRequiredParamMissing(globalAction, this.globalActionCatalog);
|
|
20672
20687
|
}
|
|
20673
20688
|
onHeaderActionSelectChange(index, value) {
|
|
20674
20689
|
const group = this.headerActionsArray.at(index);
|
|
20675
20690
|
if (!group)
|
|
20676
20691
|
return;
|
|
20677
|
-
const currentValue = String(group.get('action')?.value || '');
|
|
20678
20692
|
if (value === this.customActionValue) {
|
|
20679
|
-
|
|
20680
|
-
if (parsed.isGlobal) {
|
|
20681
|
-
group.get('action')?.setValue('');
|
|
20682
|
-
}
|
|
20693
|
+
group.get('globalAction')?.setValue(undefined);
|
|
20683
20694
|
return;
|
|
20684
20695
|
}
|
|
20685
|
-
const
|
|
20686
|
-
const
|
|
20687
|
-
group.get('
|
|
20696
|
+
const current = group.get('globalAction')?.value;
|
|
20697
|
+
const payload = current?.actionId === value ? current.payload : undefined;
|
|
20698
|
+
group.get('globalAction')?.setValue(payload !== undefined ? { actionId: value, payload } : { actionId: value });
|
|
20699
|
+
group.get('action')?.setValue('');
|
|
20688
20700
|
}
|
|
20689
20701
|
onHeaderActionParamChange(index, value) {
|
|
20690
20702
|
const group = this.headerActionsArray.at(index);
|
|
20691
20703
|
if (!group)
|
|
20692
20704
|
return;
|
|
20693
|
-
const
|
|
20694
|
-
const id =
|
|
20695
|
-
|
|
20705
|
+
const current = group.get('globalAction')?.value;
|
|
20706
|
+
const id = current?.actionId;
|
|
20707
|
+
if (!id)
|
|
20708
|
+
return;
|
|
20709
|
+
const trimmed = String(value || '').trim();
|
|
20710
|
+
if (!trimmed) {
|
|
20711
|
+
group.get('globalAction')?.setValue({ actionId: id });
|
|
20712
|
+
return;
|
|
20713
|
+
}
|
|
20714
|
+
try {
|
|
20715
|
+
group.get('globalAction')?.setValue({ actionId: id, payload: JSON.parse(trimmed) });
|
|
20716
|
+
}
|
|
20717
|
+
catch {
|
|
20718
|
+
group.get('globalAction')?.setValue({ actionId: id, payload: trimmed });
|
|
20719
|
+
}
|
|
20696
20720
|
}
|
|
20697
20721
|
onHeaderActionCustomChange(index, value) {
|
|
20698
20722
|
const group = this.headerActionsArray.at(index);
|
|
20699
20723
|
if (!group)
|
|
20700
20724
|
return;
|
|
20701
20725
|
group.get('action')?.setValue(value);
|
|
20726
|
+
group.get('globalAction')?.setValue(undefined);
|
|
20702
20727
|
}
|
|
20703
20728
|
async pickHeaderActionIcon(index) {
|
|
20704
20729
|
const group = this.headerActionsArray.at(index);
|
|
@@ -20791,7 +20816,11 @@ class SectionEditorComponent {
|
|
|
20791
20816
|
const group = this.headerActionsArray.at(index);
|
|
20792
20817
|
if (!group)
|
|
20793
20818
|
return;
|
|
20794
|
-
group.get('
|
|
20819
|
+
group.get('globalAction')?.setValue({
|
|
20820
|
+
actionId: 'surface.open',
|
|
20821
|
+
payload: normalizeSurfaceOpenPayload(payload),
|
|
20822
|
+
});
|
|
20823
|
+
group.get('action')?.setValue('');
|
|
20795
20824
|
}
|
|
20796
20825
|
async pickIcon() {
|
|
20797
20826
|
const current = this.form.value.icon;
|
|
@@ -21009,6 +21038,7 @@ class SectionEditorComponent {
|
|
|
21009
21038
|
label: [action?.label ?? ''],
|
|
21010
21039
|
icon: [action?.icon ?? ''],
|
|
21011
21040
|
action: [action?.action ?? ''],
|
|
21041
|
+
globalAction: [action?.globalAction],
|
|
21012
21042
|
tooltip: [action?.tooltip ?? ''],
|
|
21013
21043
|
color: [action?.color ?? ''],
|
|
21014
21044
|
visible: [action?.visible ?? true],
|
|
@@ -21052,6 +21082,7 @@ class SectionEditorComponent {
|
|
|
21052
21082
|
icon,
|
|
21053
21083
|
};
|
|
21054
21084
|
const action = this.normalizeOptionalString(item?.action);
|
|
21085
|
+
const globalAction = this.normalizeHeaderGlobalAction(item?.globalAction);
|
|
21055
21086
|
const tooltip = this.normalizeOptionalString(item?.tooltip);
|
|
21056
21087
|
const normalizedColor = color === 'primary' ||
|
|
21057
21088
|
color === 'accent' ||
|
|
@@ -21061,8 +21092,12 @@ class SectionEditorComponent {
|
|
|
21061
21092
|
: undefined;
|
|
21062
21093
|
const className = this.normalizeOptionalString(item?.className);
|
|
21063
21094
|
const style = this.parseHeaderActionStyle(item?.styleJson ?? item?.style);
|
|
21064
|
-
if (
|
|
21095
|
+
if (globalAction !== undefined) {
|
|
21096
|
+
nextAction.globalAction = globalAction;
|
|
21097
|
+
}
|
|
21098
|
+
else if (action !== undefined) {
|
|
21065
21099
|
nextAction.action = action;
|
|
21100
|
+
}
|
|
21066
21101
|
if (tooltip !== undefined)
|
|
21067
21102
|
nextAction.tooltip = tooltip;
|
|
21068
21103
|
if (normalizedColor !== undefined)
|
|
@@ -21105,6 +21140,15 @@ class SectionEditorComponent {
|
|
|
21105
21140
|
}
|
|
21106
21141
|
return spec?.description || '';
|
|
21107
21142
|
}
|
|
21143
|
+
normalizeHeaderGlobalAction(value) {
|
|
21144
|
+
if (!value || typeof value !== 'object')
|
|
21145
|
+
return undefined;
|
|
21146
|
+
const actionId = this.normalizeOptionalString(value.actionId);
|
|
21147
|
+
if (!actionId || !isGlobalActionId(actionId, this.globalActionCatalog))
|
|
21148
|
+
return undefined;
|
|
21149
|
+
const payload = value.payload;
|
|
21150
|
+
return payload === undefined ? { actionId } : { actionId, payload };
|
|
21151
|
+
}
|
|
21108
21152
|
headerActionStyleValidator = (control) => {
|
|
21109
21153
|
const rawValue = control.value;
|
|
21110
21154
|
if (rawValue === null || rawValue === undefined || rawValue === '') {
|
|
@@ -21464,7 +21508,7 @@ class SectionEditorComponent {
|
|
|
21464
21508
|
<mat-form-field appearance="fill">
|
|
21465
21509
|
<mat-label>{{ tx('headerActions.fields.action', 'Ação') }}</mat-label>
|
|
21466
21510
|
<mat-select
|
|
21467
|
-
[ngModel]="getHeaderActionSelectValue(actionGroup.get('action')?.value)"
|
|
21511
|
+
[ngModel]="getHeaderActionSelectValue(actionGroup.get('action')?.value, actionGroup.get('globalAction')?.value)"
|
|
21468
21512
|
(ngModelChange)="onHeaderActionSelectChange(index, $event)"
|
|
21469
21513
|
[ngModelOptions]="{ standalone: true }"
|
|
21470
21514
|
>
|
|
@@ -21488,23 +21532,23 @@ class SectionEditorComponent {
|
|
|
21488
21532
|
</button>
|
|
21489
21533
|
</mat-form-field>
|
|
21490
21534
|
|
|
21491
|
-
@if (getHeaderActionSelectValue(actionGroup.get('action')?.value) === customActionValue) {
|
|
21535
|
+
@if (getHeaderActionSelectValue(actionGroup.get('action')?.value, actionGroup.get('globalAction')?.value) === customActionValue) {
|
|
21492
21536
|
<mat-form-field appearance="fill">
|
|
21493
21537
|
<mat-label>{{ tx('headerActions.fields.customAction', 'Action customizada') }}</mat-label>
|
|
21494
21538
|
<input
|
|
21495
21539
|
matInput
|
|
21496
|
-
[ngModel]="getHeaderActionCustomValue(actionGroup.get('action')?.value)"
|
|
21540
|
+
[ngModel]="getHeaderActionCustomValue(actionGroup.get('action')?.value, actionGroup.get('globalAction')?.value)"
|
|
21497
21541
|
(ngModelChange)="onHeaderActionCustomChange(index, $event)"
|
|
21498
21542
|
[ngModelOptions]="{ standalone: true }"
|
|
21499
21543
|
[placeholder]="tx('headerActions.placeholders.action', 'Opcional. Se vazio, usa o ID')"
|
|
21500
21544
|
/>
|
|
21501
21545
|
</mat-form-field>
|
|
21502
|
-
} @else if (getHeaderActionSpecById(getHeaderActionSelectValue(actionGroup.get('action')?.value)); as actionSpec) {
|
|
21503
|
-
@if (getHeaderGlobalActionSchema(actionGroup.get('action')?.value); as schema) {
|
|
21546
|
+
} @else if (getHeaderActionSpecById(getHeaderActionSelectValue(actionGroup.get('action')?.value, actionGroup.get('globalAction')?.value)); as actionSpec) {
|
|
21547
|
+
@if (getHeaderGlobalActionSchema(actionGroup.get('action')?.value, actionGroup.get('globalAction')?.value); as schema) {
|
|
21504
21548
|
@if (schema.editorMode === 'surface-open') {
|
|
21505
21549
|
<div class="row-1">
|
|
21506
21550
|
<praxis-surface-open-action-editor
|
|
21507
|
-
[value]="getHeaderSurfaceOpenActionPayload(actionGroup.get('action')?.value)"
|
|
21551
|
+
[value]="getHeaderSurfaceOpenActionPayload(actionGroup.get('action')?.value, actionGroup.get('globalAction')?.value)"
|
|
21508
21552
|
hostKind="form"
|
|
21509
21553
|
(valueChange)="onHeaderSurfaceOpenActionPayloadChange(index, $event)"
|
|
21510
21554
|
></praxis-surface-open-action-editor>
|
|
@@ -21514,7 +21558,7 @@ class SectionEditorComponent {
|
|
|
21514
21558
|
<mat-label>{{ actionSpec.param.label || tx('headerActions.fields.actionParam', 'Parâmetro') }}</mat-label>
|
|
21515
21559
|
<input
|
|
21516
21560
|
matInput
|
|
21517
|
-
[ngModel]="getHeaderActionParam(actionGroup.get('action')?.value)"
|
|
21561
|
+
[ngModel]="getHeaderActionParam(actionGroup.get('action')?.value, actionGroup.get('globalAction')?.value)"
|
|
21518
21562
|
(ngModelChange)="onHeaderActionParamChange(index, $event)"
|
|
21519
21563
|
[ngModelOptions]="{ standalone: true }"
|
|
21520
21564
|
[placeholder]="actionSpec.param.placeholder || ''"
|
|
@@ -21523,7 +21567,7 @@ class SectionEditorComponent {
|
|
|
21523
21567
|
@if (actionSpec.param.hint) {
|
|
21524
21568
|
<mat-hint>{{ actionSpec.param.hint }}</mat-hint>
|
|
21525
21569
|
}
|
|
21526
|
-
@if (actionSpec.param.required && isHeaderActionParamMissing(actionGroup.get('action')?.value)) {
|
|
21570
|
+
@if (actionSpec.param.required && isHeaderActionParamMissing(actionGroup.get('action')?.value, actionGroup.get('globalAction')?.value)) {
|
|
21527
21571
|
<mat-error>{{ tx('headerActions.validation.parameterRequired', 'Parâmetro obrigatório.') }}</mat-error>
|
|
21528
21572
|
}
|
|
21529
21573
|
</mat-form-field>
|
|
@@ -21533,7 +21577,7 @@ class SectionEditorComponent {
|
|
|
21533
21577
|
<mat-label>{{ actionSpec.param.label || tx('headerActions.fields.actionParam', 'Parâmetro') }}</mat-label>
|
|
21534
21578
|
<input
|
|
21535
21579
|
matInput
|
|
21536
|
-
[ngModel]="getHeaderActionParam(actionGroup.get('action')?.value)"
|
|
21580
|
+
[ngModel]="getHeaderActionParam(actionGroup.get('action')?.value, actionGroup.get('globalAction')?.value)"
|
|
21537
21581
|
(ngModelChange)="onHeaderActionParamChange(index, $event)"
|
|
21538
21582
|
[ngModelOptions]="{ standalone: true }"
|
|
21539
21583
|
[placeholder]="actionSpec.param.placeholder || ''"
|
|
@@ -21542,7 +21586,7 @@ class SectionEditorComponent {
|
|
|
21542
21586
|
@if (actionSpec.param.hint) {
|
|
21543
21587
|
<mat-hint>{{ actionSpec.param.hint }}</mat-hint>
|
|
21544
21588
|
}
|
|
21545
|
-
@if (actionSpec.param.required && isHeaderActionParamMissing(actionGroup.get('action')?.value)) {
|
|
21589
|
+
@if (actionSpec.param.required && isHeaderActionParamMissing(actionGroup.get('action')?.value, actionGroup.get('globalAction')?.value)) {
|
|
21546
21590
|
<mat-error>{{ tx('headerActions.validation.parameterRequired', 'Parâmetro obrigatório.') }}</mat-error>
|
|
21547
21591
|
}
|
|
21548
21592
|
</mat-form-field>
|
|
@@ -21555,7 +21599,7 @@ class SectionEditorComponent {
|
|
|
21555
21599
|
</mat-form-field>
|
|
21556
21600
|
</div>
|
|
21557
21601
|
|
|
21558
|
-
@if (getHeaderActionSelectValue(actionGroup.get('action')?.value) !== customActionValue) {
|
|
21602
|
+
@if (getHeaderActionSelectValue(actionGroup.get('action')?.value, actionGroup.get('globalAction')?.value) !== customActionValue) {
|
|
21559
21603
|
<div class="header-action-callout">
|
|
21560
21604
|
<div class="header-action-callout-title">
|
|
21561
21605
|
{{ tx('headerActions.callout.globalTitle', 'Ação global do app') }}
|
|
@@ -22136,7 +22180,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
22136
22180
|
<mat-form-field appearance="fill">
|
|
22137
22181
|
<mat-label>{{ tx('headerActions.fields.action', 'Ação') }}</mat-label>
|
|
22138
22182
|
<mat-select
|
|
22139
|
-
[ngModel]="getHeaderActionSelectValue(actionGroup.get('action')?.value)"
|
|
22183
|
+
[ngModel]="getHeaderActionSelectValue(actionGroup.get('action')?.value, actionGroup.get('globalAction')?.value)"
|
|
22140
22184
|
(ngModelChange)="onHeaderActionSelectChange(index, $event)"
|
|
22141
22185
|
[ngModelOptions]="{ standalone: true }"
|
|
22142
22186
|
>
|
|
@@ -22160,23 +22204,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
22160
22204
|
</button>
|
|
22161
22205
|
</mat-form-field>
|
|
22162
22206
|
|
|
22163
|
-
@if (getHeaderActionSelectValue(actionGroup.get('action')?.value) === customActionValue) {
|
|
22207
|
+
@if (getHeaderActionSelectValue(actionGroup.get('action')?.value, actionGroup.get('globalAction')?.value) === customActionValue) {
|
|
22164
22208
|
<mat-form-field appearance="fill">
|
|
22165
22209
|
<mat-label>{{ tx('headerActions.fields.customAction', 'Action customizada') }}</mat-label>
|
|
22166
22210
|
<input
|
|
22167
22211
|
matInput
|
|
22168
|
-
[ngModel]="getHeaderActionCustomValue(actionGroup.get('action')?.value)"
|
|
22212
|
+
[ngModel]="getHeaderActionCustomValue(actionGroup.get('action')?.value, actionGroup.get('globalAction')?.value)"
|
|
22169
22213
|
(ngModelChange)="onHeaderActionCustomChange(index, $event)"
|
|
22170
22214
|
[ngModelOptions]="{ standalone: true }"
|
|
22171
22215
|
[placeholder]="tx('headerActions.placeholders.action', 'Opcional. Se vazio, usa o ID')"
|
|
22172
22216
|
/>
|
|
22173
22217
|
</mat-form-field>
|
|
22174
|
-
} @else if (getHeaderActionSpecById(getHeaderActionSelectValue(actionGroup.get('action')?.value)); as actionSpec) {
|
|
22175
|
-
@if (getHeaderGlobalActionSchema(actionGroup.get('action')?.value); as schema) {
|
|
22218
|
+
} @else if (getHeaderActionSpecById(getHeaderActionSelectValue(actionGroup.get('action')?.value, actionGroup.get('globalAction')?.value)); as actionSpec) {
|
|
22219
|
+
@if (getHeaderGlobalActionSchema(actionGroup.get('action')?.value, actionGroup.get('globalAction')?.value); as schema) {
|
|
22176
22220
|
@if (schema.editorMode === 'surface-open') {
|
|
22177
22221
|
<div class="row-1">
|
|
22178
22222
|
<praxis-surface-open-action-editor
|
|
22179
|
-
[value]="getHeaderSurfaceOpenActionPayload(actionGroup.get('action')?.value)"
|
|
22223
|
+
[value]="getHeaderSurfaceOpenActionPayload(actionGroup.get('action')?.value, actionGroup.get('globalAction')?.value)"
|
|
22180
22224
|
hostKind="form"
|
|
22181
22225
|
(valueChange)="onHeaderSurfaceOpenActionPayloadChange(index, $event)"
|
|
22182
22226
|
></praxis-surface-open-action-editor>
|
|
@@ -22186,7 +22230,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
22186
22230
|
<mat-label>{{ actionSpec.param.label || tx('headerActions.fields.actionParam', 'Parâmetro') }}</mat-label>
|
|
22187
22231
|
<input
|
|
22188
22232
|
matInput
|
|
22189
|
-
[ngModel]="getHeaderActionParam(actionGroup.get('action')?.value)"
|
|
22233
|
+
[ngModel]="getHeaderActionParam(actionGroup.get('action')?.value, actionGroup.get('globalAction')?.value)"
|
|
22190
22234
|
(ngModelChange)="onHeaderActionParamChange(index, $event)"
|
|
22191
22235
|
[ngModelOptions]="{ standalone: true }"
|
|
22192
22236
|
[placeholder]="actionSpec.param.placeholder || ''"
|
|
@@ -22195,7 +22239,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
22195
22239
|
@if (actionSpec.param.hint) {
|
|
22196
22240
|
<mat-hint>{{ actionSpec.param.hint }}</mat-hint>
|
|
22197
22241
|
}
|
|
22198
|
-
@if (actionSpec.param.required && isHeaderActionParamMissing(actionGroup.get('action')?.value)) {
|
|
22242
|
+
@if (actionSpec.param.required && isHeaderActionParamMissing(actionGroup.get('action')?.value, actionGroup.get('globalAction')?.value)) {
|
|
22199
22243
|
<mat-error>{{ tx('headerActions.validation.parameterRequired', 'Parâmetro obrigatório.') }}</mat-error>
|
|
22200
22244
|
}
|
|
22201
22245
|
</mat-form-field>
|
|
@@ -22205,7 +22249,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
22205
22249
|
<mat-label>{{ actionSpec.param.label || tx('headerActions.fields.actionParam', 'Parâmetro') }}</mat-label>
|
|
22206
22250
|
<input
|
|
22207
22251
|
matInput
|
|
22208
|
-
[ngModel]="getHeaderActionParam(actionGroup.get('action')?.value)"
|
|
22252
|
+
[ngModel]="getHeaderActionParam(actionGroup.get('action')?.value, actionGroup.get('globalAction')?.value)"
|
|
22209
22253
|
(ngModelChange)="onHeaderActionParamChange(index, $event)"
|
|
22210
22254
|
[ngModelOptions]="{ standalone: true }"
|
|
22211
22255
|
[placeholder]="actionSpec.param.placeholder || ''"
|
|
@@ -22214,7 +22258,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
22214
22258
|
@if (actionSpec.param.hint) {
|
|
22215
22259
|
<mat-hint>{{ actionSpec.param.hint }}</mat-hint>
|
|
22216
22260
|
}
|
|
22217
|
-
@if (actionSpec.param.required && isHeaderActionParamMissing(actionGroup.get('action')?.value)) {
|
|
22261
|
+
@if (actionSpec.param.required && isHeaderActionParamMissing(actionGroup.get('action')?.value, actionGroup.get('globalAction')?.value)) {
|
|
22218
22262
|
<mat-error>{{ tx('headerActions.validation.parameterRequired', 'Parâmetro obrigatório.') }}</mat-error>
|
|
22219
22263
|
}
|
|
22220
22264
|
</mat-form-field>
|
|
@@ -22227,7 +22271,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
22227
22271
|
</mat-form-field>
|
|
22228
22272
|
</div>
|
|
22229
22273
|
|
|
22230
|
-
@if (getHeaderActionSelectValue(actionGroup.get('action')?.value) !== customActionValue) {
|
|
22274
|
+
@if (getHeaderActionSelectValue(actionGroup.get('action')?.value, actionGroup.get('globalAction')?.value) !== customActionValue) {
|
|
22231
22275
|
<div class="header-action-callout">
|
|
22232
22276
|
<div class="header-action-callout-title">
|
|
22233
22277
|
{{ tx('headerActions.callout.globalTitle', 'Ação global do app') }}
|
|
@@ -23591,6 +23635,12 @@ const FORM_COMPONENT_AI_CAPABILITIES = {
|
|
|
23591
23635
|
valueKind: 'string',
|
|
23592
23636
|
description: 'ID do registro atual (string ou number).',
|
|
23593
23637
|
},
|
|
23638
|
+
{
|
|
23639
|
+
path: 'inputs.initialValue',
|
|
23640
|
+
category: 'inputs',
|
|
23641
|
+
valueKind: 'object',
|
|
23642
|
+
description: 'Valores iniciais locais para campos host-authored ou complementares.',
|
|
23643
|
+
},
|
|
23594
23644
|
{
|
|
23595
23645
|
path: 'inputs.mode',
|
|
23596
23646
|
category: 'inputs',
|
|
@@ -23991,6 +24041,787 @@ const FORM_COMPONENT_AI_CAPABILITIES = {
|
|
|
23991
24041
|
],
|
|
23992
24042
|
};
|
|
23993
24043
|
|
|
24044
|
+
/**
|
|
24045
|
+
* Manifesto de authoring canônico para o componente praxis-dynamic-form.
|
|
24046
|
+
* Este arquivo define as operações permitidas pela IA para editar formulários.
|
|
24047
|
+
*
|
|
24048
|
+
* @version 1.4.0
|
|
24049
|
+
* @status COMPLIANT - Alinhado com contrato v2, gate de aceitação e semântica local/schema-backed.
|
|
24050
|
+
*/
|
|
24051
|
+
const PRAXIS_DYNAMIC_FORM_AUTHORING_MANIFEST = {
|
|
24052
|
+
schemaVersion: '1.0.0',
|
|
24053
|
+
componentId: 'praxis-dynamic-form',
|
|
24054
|
+
ownerPackage: '@praxisui/dynamic-form',
|
|
24055
|
+
configSchemaId: 'FormConfig',
|
|
24056
|
+
manifestVersion: '1.4.0',
|
|
24057
|
+
runtimeInputs: [
|
|
24058
|
+
{ name: 'config', type: 'FormConfig', description: 'Configuração completa do formulário' },
|
|
24059
|
+
{ name: 'resourcePath', type: 'string', description: 'Caminho do recurso (API)' },
|
|
24060
|
+
{ name: 'mode', type: 'string', allowedValues: ['create', 'edit', 'view'], description: 'Modo de operação' },
|
|
24061
|
+
{ name: 'enableCustomization', type: 'boolean', description: 'Habilita edição visual' }
|
|
24062
|
+
],
|
|
24063
|
+
editableTargets: [
|
|
24064
|
+
/**
|
|
24065
|
+
* 'field' é o target genérico para operações de metadado (label, placeholder, etc.).
|
|
24066
|
+
* Para distinguir schema-backed de local, use 'schemaBackedField' ou 'localField'.
|
|
24067
|
+
*/
|
|
24068
|
+
{ kind: 'field', resolver: 'field-by-name-or-label', description: 'Campos do formulário (metadados schema-backed ou local; use para ops de propriedade)' },
|
|
24069
|
+
{ kind: 'schemaBackedField', resolver: 'field-by-name-or-label', description: 'Campos com origem no schema do backend; não removíveis via authoring' },
|
|
24070
|
+
{ kind: 'localField', resolver: 'field-by-name', description: 'Campos locais com source:"local"; adicionados/removidos via authoring' },
|
|
24071
|
+
{ kind: 'section', resolver: 'section-by-id-or-title', description: 'Seções de layout identificadas por id estável' },
|
|
24072
|
+
{ kind: 'row', resolver: 'row-by-id-in-section', description: 'Linhas dentro de seções, identificadas por id estável' },
|
|
24073
|
+
{ kind: 'column', resolver: 'column-by-id-in-row', description: 'Colunas dentro de linhas, identificadas por id estável' },
|
|
24074
|
+
/**
|
|
24075
|
+
* 'layoutPlacement' representa a posição de um campo no layout (seção+linha+coluna).
|
|
24076
|
+
* Usado conceitualmente por layout.field.move; declarado como target para futuras ops
|
|
24077
|
+
* de reposicionamento granular (ex: layout.placement.swap).
|
|
24078
|
+
*/
|
|
24079
|
+
{ kind: 'layoutPlacement', resolver: 'layout-placement-by-field', description: 'Posicionamento de campo no layout; target para ops de reposicionamento' },
|
|
24080
|
+
{ kind: 'formAction', resolver: 'action-by-id-or-label', description: 'Ações da toolbar (submit, cancel, reset)' },
|
|
24081
|
+
{ kind: 'formRule', resolver: 'rule-by-id-or-name', description: 'Regras de visibilidade e validação' },
|
|
24082
|
+
/**
|
|
24083
|
+
* 'message' declara suporte a mensagens inline de formulário (info, warning, error).
|
|
24084
|
+
* Declarado aqui para conformidade com o gate; ops de criação/remoção de messages
|
|
24085
|
+
* serão adicionadas na próxima versão do manifesto.
|
|
24086
|
+
*/
|
|
24087
|
+
{ kind: 'message', resolver: 'message-by-id', description: 'Mensagens inline do formulário (info, warning, error); ops em desenvolvimento' }
|
|
24088
|
+
],
|
|
24089
|
+
operations: [
|
|
24090
|
+
// ─── FIELD PROPERTY OPERATIONS ───────────────────────────────────────────
|
|
24091
|
+
{
|
|
24092
|
+
operationId: 'field.label.set',
|
|
24093
|
+
title: 'Alterar rótulo do campo',
|
|
24094
|
+
scope: 'field',
|
|
24095
|
+
targetKind: 'field',
|
|
24096
|
+
target: { kind: 'field', resolver: 'field-by-name-or-label', ambiguityPolicy: 'fail', required: true },
|
|
24097
|
+
inputSchema: {
|
|
24098
|
+
type: 'object',
|
|
24099
|
+
required: ['label'],
|
|
24100
|
+
properties: { label: { type: 'string', minLength: 1 } }
|
|
24101
|
+
},
|
|
24102
|
+
effects: [{ kind: 'merge-by-key', path: 'fieldMetadata[]', key: 'name' }],
|
|
24103
|
+
validators: ['field-exists'],
|
|
24104
|
+
affectedPaths: ['fieldMetadata[].label'],
|
|
24105
|
+
submissionImpact: false,
|
|
24106
|
+
preconditions: ['config-initialized', 'target-exists']
|
|
24107
|
+
},
|
|
24108
|
+
{
|
|
24109
|
+
operationId: 'field.placeholder.set',
|
|
24110
|
+
title: 'Alterar placeholder do campo',
|
|
24111
|
+
scope: 'field',
|
|
24112
|
+
targetKind: 'field',
|
|
24113
|
+
target: { kind: 'field', resolver: 'field-by-name-or-label', ambiguityPolicy: 'fail', required: true },
|
|
24114
|
+
inputSchema: {
|
|
24115
|
+
type: 'object',
|
|
24116
|
+
required: ['placeholder'],
|
|
24117
|
+
properties: { placeholder: { type: 'string' } }
|
|
24118
|
+
},
|
|
24119
|
+
effects: [{ kind: 'merge-by-key', path: 'fieldMetadata[]', key: 'name' }],
|
|
24120
|
+
validators: ['field-exists'],
|
|
24121
|
+
affectedPaths: ['fieldMetadata[].placeholder'],
|
|
24122
|
+
submissionImpact: false,
|
|
24123
|
+
preconditions: ['config-initialized', 'target-exists']
|
|
24124
|
+
},
|
|
24125
|
+
{
|
|
24126
|
+
operationId: 'field.required.set',
|
|
24127
|
+
title: 'Definir obrigatoriedade',
|
|
24128
|
+
scope: 'field',
|
|
24129
|
+
targetKind: 'field',
|
|
24130
|
+
target: { kind: 'field', resolver: 'field-by-name-or-label', ambiguityPolicy: 'fail', required: true },
|
|
24131
|
+
inputSchema: {
|
|
24132
|
+
type: 'object',
|
|
24133
|
+
required: ['required'],
|
|
24134
|
+
properties: { required: { type: 'boolean' } }
|
|
24135
|
+
},
|
|
24136
|
+
effects: [{ kind: 'merge-by-key', path: 'fieldMetadata[]', key: 'name' }],
|
|
24137
|
+
validators: ['field-exists'],
|
|
24138
|
+
affectedPaths: ['fieldMetadata[].required'],
|
|
24139
|
+
submissionImpact: false,
|
|
24140
|
+
preconditions: ['config-initialized', 'target-exists']
|
|
24141
|
+
},
|
|
24142
|
+
{
|
|
24143
|
+
operationId: 'field.readOnly.set',
|
|
24144
|
+
title: 'Definir campo como somente leitura',
|
|
24145
|
+
scope: 'field',
|
|
24146
|
+
targetKind: 'field',
|
|
24147
|
+
target: { kind: 'field', resolver: 'field-by-name-or-label', ambiguityPolicy: 'fail', required: true },
|
|
24148
|
+
inputSchema: {
|
|
24149
|
+
type: 'object',
|
|
24150
|
+
required: ['readOnly'],
|
|
24151
|
+
properties: { readOnly: { type: 'boolean' } }
|
|
24152
|
+
},
|
|
24153
|
+
effects: [{ kind: 'merge-by-key', path: 'fieldMetadata[]', key: 'name' }],
|
|
24154
|
+
validators: ['field-exists'],
|
|
24155
|
+
affectedPaths: ['fieldMetadata[].readOnly'],
|
|
24156
|
+
submissionImpact: false,
|
|
24157
|
+
preconditions: ['config-initialized', 'target-exists']
|
|
24158
|
+
},
|
|
24159
|
+
{
|
|
24160
|
+
operationId: 'field.disabled.set',
|
|
24161
|
+
title: 'Definir campo como desabilitado',
|
|
24162
|
+
scope: 'field',
|
|
24163
|
+
targetKind: 'field',
|
|
24164
|
+
target: { kind: 'field', resolver: 'field-by-name-or-label', ambiguityPolicy: 'fail', required: true },
|
|
24165
|
+
inputSchema: {
|
|
24166
|
+
type: 'object',
|
|
24167
|
+
required: ['disabled'],
|
|
24168
|
+
properties: { disabled: { type: 'boolean' } }
|
|
24169
|
+
},
|
|
24170
|
+
effects: [{ kind: 'merge-by-key', path: 'fieldMetadata[]', key: 'name' }],
|
|
24171
|
+
validators: ['field-exists'],
|
|
24172
|
+
affectedPaths: ['fieldMetadata[].disabled'],
|
|
24173
|
+
submissionImpact: false,
|
|
24174
|
+
preconditions: ['config-initialized', 'target-exists']
|
|
24175
|
+
},
|
|
24176
|
+
{
|
|
24177
|
+
operationId: 'field.hidden.set',
|
|
24178
|
+
title: 'Definir visibilidade inicial',
|
|
24179
|
+
scope: 'field',
|
|
24180
|
+
targetKind: 'field',
|
|
24181
|
+
target: { kind: 'field', resolver: 'field-by-name-or-label', ambiguityPolicy: 'fail', required: true },
|
|
24182
|
+
inputSchema: {
|
|
24183
|
+
type: 'object',
|
|
24184
|
+
required: ['hidden'],
|
|
24185
|
+
properties: { hidden: { type: 'boolean' } }
|
|
24186
|
+
},
|
|
24187
|
+
effects: [{ kind: 'merge-by-key', path: 'fieldMetadata[]', key: 'name' }],
|
|
24188
|
+
validators: ['field-exists'],
|
|
24189
|
+
affectedPaths: ['fieldMetadata[].hidden'],
|
|
24190
|
+
submissionImpact: false,
|
|
24191
|
+
preconditions: ['config-initialized', 'target-exists']
|
|
24192
|
+
},
|
|
24193
|
+
{
|
|
24194
|
+
operationId: 'field.controlType.set',
|
|
24195
|
+
title: 'Alterar tipo de controle',
|
|
24196
|
+
scope: 'field',
|
|
24197
|
+
targetKind: 'field',
|
|
24198
|
+
target: { kind: 'field', resolver: 'field-by-name-or-label', ambiguityPolicy: 'fail', required: true },
|
|
24199
|
+
inputSchema: {
|
|
24200
|
+
type: 'object',
|
|
24201
|
+
required: ['controlType'],
|
|
24202
|
+
properties: { controlType: { type: 'string' } }
|
|
24203
|
+
},
|
|
24204
|
+
effects: [{ kind: 'merge-by-key', path: 'fieldMetadata[]', key: 'name' }],
|
|
24205
|
+
destructive: true,
|
|
24206
|
+
requiresConfirmation: true,
|
|
24207
|
+
validators: ['field-exists', 'control-type-compatible'],
|
|
24208
|
+
affectedPaths: ['fieldMetadata[].controlType'],
|
|
24209
|
+
submissionImpact: false,
|
|
24210
|
+
preconditions: ['config-initialized', 'target-exists']
|
|
24211
|
+
},
|
|
24212
|
+
{
|
|
24213
|
+
operationId: 'field.optionSource.set',
|
|
24214
|
+
title: 'Definir fonte de opções do campo',
|
|
24215
|
+
scope: 'field',
|
|
24216
|
+
targetKind: 'field',
|
|
24217
|
+
target: { kind: 'field', resolver: 'field-by-name-or-label', ambiguityPolicy: 'fail', required: true },
|
|
24218
|
+
inputSchema: {
|
|
24219
|
+
type: 'object',
|
|
24220
|
+
required: ['optionSource'],
|
|
24221
|
+
properties: {
|
|
24222
|
+
optionSource: {
|
|
24223
|
+
type: 'object',
|
|
24224
|
+
required: ['type'],
|
|
24225
|
+
properties: {
|
|
24226
|
+
type: { enum: ['static', 'remote', 'context'] },
|
|
24227
|
+
items: { type: 'array', description: 'Opções estáticas (type: static)' },
|
|
24228
|
+
resourcePath: { type: 'string', description: 'URL da API (type: remote)' },
|
|
24229
|
+
contextKey: { type: 'string', description: 'Chave de contexto (type: context)' }
|
|
24230
|
+
},
|
|
24231
|
+
if: { properties: { type: { const: 'static' } } },
|
|
24232
|
+
then: { required: ['items'] },
|
|
24233
|
+
else: {
|
|
24234
|
+
if: { properties: { type: { const: 'remote' } } },
|
|
24235
|
+
then: { required: ['resourcePath'] },
|
|
24236
|
+
else: { required: ['contextKey'] }
|
|
24237
|
+
}
|
|
24238
|
+
}
|
|
24239
|
+
}
|
|
24240
|
+
},
|
|
24241
|
+
effects: [{ kind: 'merge-by-key', path: 'fieldMetadata[]', key: 'name' }],
|
|
24242
|
+
validators: ['field-exists', 'option-source-valid'],
|
|
24243
|
+
affectedPaths: ['fieldMetadata[].optionSource'],
|
|
24244
|
+
submissionImpact: false,
|
|
24245
|
+
preconditions: ['config-initialized', 'target-exists']
|
|
24246
|
+
},
|
|
24247
|
+
// ─── LOCAL FIELD OPERATIONS ───────────────────────────────────────────────
|
|
24248
|
+
{
|
|
24249
|
+
operationId: 'field.local.add',
|
|
24250
|
+
title: 'Adicionar campo local',
|
|
24251
|
+
scope: 'global',
|
|
24252
|
+
targetKind: 'localField',
|
|
24253
|
+
target: { kind: 'localField', resolver: 'field-by-name', ambiguityPolicy: 'fail', required: false },
|
|
24254
|
+
inputSchema: {
|
|
24255
|
+
type: 'object',
|
|
24256
|
+
required: ['name', 'label', 'controlType', 'source'],
|
|
24257
|
+
properties: {
|
|
24258
|
+
name: { type: 'string' },
|
|
24259
|
+
label: { type: 'string' },
|
|
24260
|
+
controlType: { type: 'string' },
|
|
24261
|
+
/**
|
|
24262
|
+
* source:'local' é OBRIGATÓRIO e distingue campos locais de campos schema-backed.
|
|
24263
|
+
* O backend usa este discriminador para aplicar regras de ciclo de vida diferentes.
|
|
24264
|
+
*/
|
|
24265
|
+
source: { const: 'local', description: 'Obrigatório. Discriminador que distingue campos locais de schema-backed.' },
|
|
24266
|
+
/**
|
|
24267
|
+
* transient:true significa que o valor não deve persistir entre navegações nem
|
|
24268
|
+
* ser incluído em snapshots. É independente de submitPolicy.
|
|
24269
|
+
*/
|
|
24270
|
+
transient: { type: 'boolean', default: false, description: 'Se true, o valor não persiste entre navegações.' },
|
|
24271
|
+
/**
|
|
24272
|
+
* submitPolicy controla como o valor é tratado no payload de submissão.
|
|
24273
|
+
* VALORES VÁLIDOS: 'omit' | 'include' | 'includeWhenDirty'
|
|
24274
|
+
* NOTA: 'transient' NÃO é um valor válido aqui — use a propriedade transient:boolean acima.
|
|
24275
|
+
*/
|
|
24276
|
+
submitPolicy: { enum: ['omit', 'include', 'includeWhenDirty'], default: 'omit' }
|
|
24277
|
+
}
|
|
24278
|
+
},
|
|
24279
|
+
effects: [{ kind: 'append-unique', path: 'fieldMetadata[]', key: 'name' }],
|
|
24280
|
+
validators: ['field-name-unique', 'local-schema-name-no-collision'],
|
|
24281
|
+
affectedPaths: ['fieldMetadata[]'],
|
|
24282
|
+
submissionImpact: true,
|
|
24283
|
+
preconditions: ['config-initialized']
|
|
24284
|
+
},
|
|
24285
|
+
{
|
|
24286
|
+
operationId: 'field.local.remove',
|
|
24287
|
+
title: 'Remover campo local',
|
|
24288
|
+
scope: 'field',
|
|
24289
|
+
targetKind: 'localField',
|
|
24290
|
+
target: { kind: 'localField', resolver: 'field-by-name', ambiguityPolicy: 'fail', required: true },
|
|
24291
|
+
inputSchema: { type: 'object', properties: {} },
|
|
24292
|
+
effects: [
|
|
24293
|
+
{ kind: 'remove-by-key', path: 'fieldMetadata[]', key: 'name' },
|
|
24294
|
+
/**
|
|
24295
|
+
* Efeito cascata: remove a referência do campo de todos os slots de layout.
|
|
24296
|
+
* Sem isso, o layout mantém referências órfãs que causam erros de renderização.
|
|
24297
|
+
*/
|
|
24298
|
+
{
|
|
24299
|
+
kind: 'compile-domain-patch',
|
|
24300
|
+
handler: 'form-layout-field-cleanup',
|
|
24301
|
+
handlerContract: {
|
|
24302
|
+
reads: ['fieldMetadata[].name', 'sections[].rows[].columns[].fields'],
|
|
24303
|
+
writes: ['sections[].rows[].columns[].fields'],
|
|
24304
|
+
identityKeys: ['fieldMetadata[].name', 'sections[].id', 'sections[].rows[].id', 'sections[].rows[].columns[].id'],
|
|
24305
|
+
inputSchema: { type: 'object', required: ['target.name'], properties: { 'target.name': { type: 'string' } } },
|
|
24306
|
+
failureModes: ['target-field-not-found', 'target-field-not-local', 'layout-reference-conflict'],
|
|
24307
|
+
description: 'Remove every layout placement that references the removed local field by fieldMetadata[].name.'
|
|
24308
|
+
}
|
|
24309
|
+
}
|
|
24310
|
+
],
|
|
24311
|
+
destructive: true,
|
|
24312
|
+
requiresConfirmation: true,
|
|
24313
|
+
validators: ['field-exists', 'field-is-local'],
|
|
24314
|
+
affectedPaths: ['fieldMetadata[]', 'sections[].rows[].columns[].fields'],
|
|
24315
|
+
submissionImpact: true,
|
|
24316
|
+
preconditions: ['config-initialized', 'target-exists']
|
|
24317
|
+
},
|
|
24318
|
+
{
|
|
24319
|
+
operationId: 'field.submitPolicy.set',
|
|
24320
|
+
title: 'Definir política de submissão do campo',
|
|
24321
|
+
scope: 'field',
|
|
24322
|
+
targetKind: 'localField',
|
|
24323
|
+
target: { kind: 'localField', resolver: 'field-by-name', ambiguityPolicy: 'fail', required: true },
|
|
24324
|
+
inputSchema: {
|
|
24325
|
+
type: 'object',
|
|
24326
|
+
required: ['submitPolicy'],
|
|
24327
|
+
properties: {
|
|
24328
|
+
submitPolicy: { enum: ['omit', 'include', 'includeWhenDirty'] }
|
|
24329
|
+
}
|
|
24330
|
+
},
|
|
24331
|
+
effects: [{ kind: 'merge-by-key', path: 'fieldMetadata[]', key: 'name' }],
|
|
24332
|
+
validators: ['field-exists', 'field-is-local', 'editor-round-trip-preserve'],
|
|
24333
|
+
affectedPaths: ['fieldMetadata[].submitPolicy'],
|
|
24334
|
+
submissionImpact: true,
|
|
24335
|
+
preconditions: ['config-initialized', 'target-exists']
|
|
24336
|
+
},
|
|
24337
|
+
// ─── LAYOUT OPERATIONS ────────────────────────────────────────────────────
|
|
24338
|
+
{
|
|
24339
|
+
operationId: 'layout.section.add',
|
|
24340
|
+
title: 'Adicionar nova seção',
|
|
24341
|
+
scope: 'global',
|
|
24342
|
+
targetKind: 'section',
|
|
24343
|
+
target: { kind: 'section', resolver: 'section-by-id-or-title', ambiguityPolicy: 'fail', required: false },
|
|
24344
|
+
inputSchema: {
|
|
24345
|
+
type: 'object',
|
|
24346
|
+
required: ['id', 'title'],
|
|
24347
|
+
properties: {
|
|
24348
|
+
id: { type: 'string' },
|
|
24349
|
+
title: { type: 'string' },
|
|
24350
|
+
icon: { type: 'string' },
|
|
24351
|
+
collapsible: { type: 'boolean' }
|
|
24352
|
+
}
|
|
24353
|
+
},
|
|
24354
|
+
effects: [{ kind: 'append-unique', path: 'sections[]', key: 'id' }],
|
|
24355
|
+
validators: ['section-id-unique'],
|
|
24356
|
+
affectedPaths: ['sections[]'],
|
|
24357
|
+
submissionImpact: false,
|
|
24358
|
+
preconditions: ['config-initialized']
|
|
24359
|
+
},
|
|
24360
|
+
{
|
|
24361
|
+
operationId: 'layout.section.remove',
|
|
24362
|
+
title: 'Remover seção',
|
|
24363
|
+
scope: 'section',
|
|
24364
|
+
targetKind: 'section',
|
|
24365
|
+
target: { kind: 'section', resolver: 'section-by-id-or-title', ambiguityPolicy: 'fail', required: true },
|
|
24366
|
+
inputSchema: { type: 'object', properties: {} },
|
|
24367
|
+
effects: [
|
|
24368
|
+
{ kind: 'remove-by-key', path: 'sections[]', key: 'id' },
|
|
24369
|
+
/**
|
|
24370
|
+
* Cascata: remove todas as linhas, colunas e referências de campos da seção excluída.
|
|
24371
|
+
* Os registros em fieldMetadata NÃO são removidos — apenas as posições de layout.
|
|
24372
|
+
* O validator field-exists-in-layout sinalizará campos sem posição após esta operação.
|
|
24373
|
+
*/
|
|
24374
|
+
{
|
|
24375
|
+
kind: 'compile-domain-patch',
|
|
24376
|
+
handler: 'form-layout-section-cleanup',
|
|
24377
|
+
handlerContract: {
|
|
24378
|
+
reads: ['sections[].id', 'sections[].rows[].id', 'sections[].rows[].columns[].id', 'sections[].rows[].columns[].fields'],
|
|
24379
|
+
writes: ['sections[]'],
|
|
24380
|
+
identityKeys: ['sections[].id'],
|
|
24381
|
+
inputSchema: { type: 'object', required: ['target.id'], properties: { 'target.id': { type: 'string' } } },
|
|
24382
|
+
failureModes: ['target-section-not-found', 'ambiguous-section-target'],
|
|
24383
|
+
description: 'Removes the target section and its nested rows, columns, and field placements without deleting fieldMetadata records.'
|
|
24384
|
+
}
|
|
24385
|
+
}
|
|
24386
|
+
],
|
|
24387
|
+
destructive: true,
|
|
24388
|
+
requiresConfirmation: true,
|
|
24389
|
+
validators: ['section-exists'],
|
|
24390
|
+
affectedPaths: ['sections[]', 'sections[].rows[]', 'sections[].rows[].columns[]', 'sections[].rows[].columns[].fields'],
|
|
24391
|
+
submissionImpact: false,
|
|
24392
|
+
preconditions: ['config-initialized', 'target-exists']
|
|
24393
|
+
},
|
|
24394
|
+
{
|
|
24395
|
+
operationId: 'layout.row.add',
|
|
24396
|
+
title: 'Adicionar linha numa seção',
|
|
24397
|
+
scope: 'section',
|
|
24398
|
+
targetKind: 'section',
|
|
24399
|
+
target: { kind: 'section', resolver: 'section-by-id-or-title', ambiguityPolicy: 'fail', required: true },
|
|
24400
|
+
inputSchema: {
|
|
24401
|
+
type: 'object',
|
|
24402
|
+
required: ['id'],
|
|
24403
|
+
properties: {
|
|
24404
|
+
id: { type: 'string' },
|
|
24405
|
+
columns: { type: 'number', minimum: 1, maximum: 12, default: 1 }
|
|
24406
|
+
}
|
|
24407
|
+
},
|
|
24408
|
+
effects: [{ kind: 'append-unique', path: 'sections[].rows[]', key: 'id' }],
|
|
24409
|
+
validators: ['section-exists', 'row-id-unique-in-section'],
|
|
24410
|
+
affectedPaths: ['sections[].rows[]'],
|
|
24411
|
+
submissionImpact: false,
|
|
24412
|
+
preconditions: ['config-initialized', 'target-exists']
|
|
24413
|
+
},
|
|
24414
|
+
{
|
|
24415
|
+
operationId: 'layout.column.add',
|
|
24416
|
+
title: 'Adicionar coluna numa linha',
|
|
24417
|
+
scope: 'row',
|
|
24418
|
+
targetKind: 'row',
|
|
24419
|
+
target: { kind: 'row', resolver: 'row-by-id-in-section', ambiguityPolicy: 'fail', required: true },
|
|
24420
|
+
inputSchema: {
|
|
24421
|
+
type: 'object',
|
|
24422
|
+
required: ['id'],
|
|
24423
|
+
properties: {
|
|
24424
|
+
id: { type: 'string' },
|
|
24425
|
+
span: { type: 'number', minimum: 1, maximum: 12, default: 6 }
|
|
24426
|
+
}
|
|
24427
|
+
},
|
|
24428
|
+
effects: [{ kind: 'append-unique', path: 'sections[].rows[].columns[]', key: 'id' }],
|
|
24429
|
+
validators: ['row-exists', 'column-id-unique-in-row'],
|
|
24430
|
+
affectedPaths: ['sections[].rows[].columns[]'],
|
|
24431
|
+
submissionImpact: false,
|
|
24432
|
+
preconditions: ['config-initialized', 'target-exists']
|
|
24433
|
+
},
|
|
24434
|
+
{
|
|
24435
|
+
operationId: 'layout.field.move',
|
|
24436
|
+
title: 'Mover campo no layout',
|
|
24437
|
+
scope: 'field',
|
|
24438
|
+
targetKind: 'field',
|
|
24439
|
+
target: { kind: 'field', resolver: 'field-by-name-or-label', ambiguityPolicy: 'fail', required: true },
|
|
24440
|
+
inputSchema: {
|
|
24441
|
+
type: 'object',
|
|
24442
|
+
required: ['targetSectionId', 'targetRowId', 'targetColumnId'],
|
|
24443
|
+
properties: {
|
|
24444
|
+
targetSectionId: { type: 'string' },
|
|
24445
|
+
targetRowId: { type: 'string' },
|
|
24446
|
+
targetColumnId: { type: 'string' },
|
|
24447
|
+
index: { type: 'number' }
|
|
24448
|
+
}
|
|
24449
|
+
},
|
|
24450
|
+
effects: [{
|
|
24451
|
+
kind: 'compile-domain-patch',
|
|
24452
|
+
handler: 'form-layout-reconciler',
|
|
24453
|
+
handlerContract: {
|
|
24454
|
+
reads: [
|
|
24455
|
+
'fieldMetadata[].name',
|
|
24456
|
+
'sections[].id',
|
|
24457
|
+
'sections[].rows[].id',
|
|
24458
|
+
'sections[].rows[].columns[].id',
|
|
24459
|
+
'sections[].rows[].columns[].fields'
|
|
24460
|
+
],
|
|
24461
|
+
writes: ['sections[].rows[].columns[].fields'],
|
|
24462
|
+
identityKeys: ['fieldMetadata[].name', 'sections[].id', 'sections[].rows[].id', 'sections[].rows[].columns[].id'],
|
|
24463
|
+
inputSchema: {
|
|
24464
|
+
type: 'object',
|
|
24465
|
+
required: ['target.name', 'targetSectionId', 'targetRowId', 'targetColumnId'],
|
|
24466
|
+
properties: {
|
|
24467
|
+
'target.name': { type: 'string' },
|
|
24468
|
+
targetSectionId: { type: 'string' },
|
|
24469
|
+
targetRowId: { type: 'string' },
|
|
24470
|
+
targetColumnId: { type: 'string' },
|
|
24471
|
+
index: { type: 'number' }
|
|
24472
|
+
}
|
|
24473
|
+
},
|
|
24474
|
+
failureModes: ['target-field-not-found', 'target-layout-location-not-found', 'ambiguous-field-target', 'duplicate-field-placement'],
|
|
24475
|
+
description: 'Moves a field placement by stable section, row, column, and field ids; index is insertion position only.'
|
|
24476
|
+
}
|
|
24477
|
+
}],
|
|
24478
|
+
validators: ['field-exists', 'layout-target-exists', 'no-index-as-identity', 'field-exists-in-layout'],
|
|
24479
|
+
affectedPaths: ['sections[].rows[].columns[].fields'],
|
|
24480
|
+
submissionImpact: false,
|
|
24481
|
+
preconditions: ['config-initialized', 'target-exists']
|
|
24482
|
+
},
|
|
24483
|
+
// ─── RULE OPERATIONS ──────────────────────────────────────────────────────
|
|
24484
|
+
{
|
|
24485
|
+
operationId: 'rule.visibility.add',
|
|
24486
|
+
title: 'Adicionar regra de visibilidade',
|
|
24487
|
+
scope: 'global',
|
|
24488
|
+
targetKind: 'formRule',
|
|
24489
|
+
target: { kind: 'formRule', resolver: 'rule-by-id-or-name', ambiguityPolicy: 'fail', required: false },
|
|
24490
|
+
inputSchema: {
|
|
24491
|
+
type: 'object',
|
|
24492
|
+
required: ['id', 'type', 'targets', 'condition'],
|
|
24493
|
+
properties: {
|
|
24494
|
+
id: { type: 'string' },
|
|
24495
|
+
name: { type: 'string' },
|
|
24496
|
+
/** Discriminador de tipo armazenado no item formRules[]; distingue regras de visibilidade das de validação. */
|
|
24497
|
+
type: { const: 'visibility', description: 'Discriminador de tipo da regra; obrigatório para distinção em formRules[]' },
|
|
24498
|
+
targetType: { enum: ['field', 'section', 'row', 'column', 'action'], default: 'field' },
|
|
24499
|
+
targets: { type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
24500
|
+
condition: { type: 'object', description: 'JsonLogic AST' }
|
|
24501
|
+
}
|
|
24502
|
+
},
|
|
24503
|
+
effects: [{ kind: 'append-unique', path: 'formRules[]', key: 'id' }],
|
|
24504
|
+
validators: ['rule-id-unique', 'rule-target-refs-exist', 'json-logic-valid'],
|
|
24505
|
+
affectedPaths: ['formRules[]'],
|
|
24506
|
+
submissionImpact: false,
|
|
24507
|
+
preconditions: ['config-initialized']
|
|
24508
|
+
},
|
|
24509
|
+
{
|
|
24510
|
+
operationId: 'rule.validation.add',
|
|
24511
|
+
title: 'Adicionar regra de validação',
|
|
24512
|
+
scope: 'global',
|
|
24513
|
+
targetKind: 'formRule',
|
|
24514
|
+
target: { kind: 'formRule', resolver: 'rule-by-id-or-name', ambiguityPolicy: 'fail', required: false },
|
|
24515
|
+
inputSchema: {
|
|
24516
|
+
type: 'object',
|
|
24517
|
+
required: ['id', 'type', 'targets', 'condition', 'message'],
|
|
24518
|
+
properties: {
|
|
24519
|
+
id: { type: 'string' },
|
|
24520
|
+
name: { type: 'string' },
|
|
24521
|
+
/** Discriminador de tipo armazenado no item formRules[]; distingue regras de validação das de visibilidade. */
|
|
24522
|
+
type: { const: 'validation', description: 'Discriminador de tipo da regra; obrigatório para distinção em formRules[]' },
|
|
24523
|
+
/**
|
|
24524
|
+
* targetType espelha o campo equivalente de rule.visibility.add.
|
|
24525
|
+
* Regras de validação podem se aplicar a campos, seções ou ações da toolbar.
|
|
24526
|
+
*/
|
|
24527
|
+
targetType: { enum: ['field', 'section', 'row', 'column', 'action'], default: 'field' },
|
|
24528
|
+
targets: { type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
24529
|
+
condition: { type: 'object', description: 'JsonLogic AST; quando true, exibe mensagem de erro' },
|
|
24530
|
+
message: { type: 'string', description: 'Mensagem de erro a exibir ao usuário' },
|
|
24531
|
+
level: { enum: ['error', 'warning'], default: 'error' }
|
|
24532
|
+
}
|
|
24533
|
+
},
|
|
24534
|
+
effects: [{ kind: 'append-unique', path: 'formRules[]', key: 'id' }],
|
|
24535
|
+
validators: ['rule-id-unique', 'rule-target-refs-exist', 'json-logic-valid'],
|
|
24536
|
+
affectedPaths: ['formRules[]'],
|
|
24537
|
+
submissionImpact: false,
|
|
24538
|
+
preconditions: ['config-initialized']
|
|
24539
|
+
},
|
|
24540
|
+
{
|
|
24541
|
+
operationId: 'rule.remove',
|
|
24542
|
+
title: 'Remover regra',
|
|
24543
|
+
scope: 'rule',
|
|
24544
|
+
targetKind: 'formRule',
|
|
24545
|
+
target: { kind: 'formRule', resolver: 'rule-by-id-or-name', ambiguityPolicy: 'fail', required: true },
|
|
24546
|
+
inputSchema: { type: 'object', properties: {} },
|
|
24547
|
+
effects: [{ kind: 'remove-by-key', path: 'formRules[]', key: 'id' }],
|
|
24548
|
+
destructive: true,
|
|
24549
|
+
requiresConfirmation: true,
|
|
24550
|
+
validators: ['rule-exists'],
|
|
24551
|
+
affectedPaths: ['formRules[]'],
|
|
24552
|
+
submissionImpact: false,
|
|
24553
|
+
preconditions: ['config-initialized', 'target-exists']
|
|
24554
|
+
},
|
|
24555
|
+
// ─── ACTION OPERATIONS ────────────────────────────────────────────────────
|
|
24556
|
+
{
|
|
24557
|
+
operationId: 'action.submit.configure',
|
|
24558
|
+
title: 'Configurar botão de envio',
|
|
24559
|
+
scope: 'global',
|
|
24560
|
+
targetKind: 'formAction',
|
|
24561
|
+
target: { kind: 'formAction', resolver: 'action-by-id-or-label', ambiguityPolicy: 'fail', required: false },
|
|
24562
|
+
inputSchema: {
|
|
24563
|
+
type: 'object',
|
|
24564
|
+
minProperties: 1,
|
|
24565
|
+
properties: {
|
|
24566
|
+
label: { type: 'string' },
|
|
24567
|
+
icon: { type: 'string' },
|
|
24568
|
+
visible: { type: 'boolean' },
|
|
24569
|
+
color: { enum: ['primary', 'accent', 'warn', 'basic'] }
|
|
24570
|
+
}
|
|
24571
|
+
},
|
|
24572
|
+
effects: [{ kind: 'merge-object', path: 'actions.submit' }],
|
|
24573
|
+
validators: ['editor-round-trip-preserve'],
|
|
24574
|
+
affectedPaths: ['actions.submit'],
|
|
24575
|
+
submissionImpact: false,
|
|
24576
|
+
preconditions: ['config-initialized']
|
|
24577
|
+
},
|
|
24578
|
+
{
|
|
24579
|
+
operationId: 'action.cancel.configure',
|
|
24580
|
+
title: 'Configurar botão de cancelar',
|
|
24581
|
+
scope: 'global',
|
|
24582
|
+
targetKind: 'formAction',
|
|
24583
|
+
target: { kind: 'formAction', resolver: 'action-by-id-or-label', ambiguityPolicy: 'fail', required: false },
|
|
24584
|
+
inputSchema: {
|
|
24585
|
+
type: 'object',
|
|
24586
|
+
minProperties: 1,
|
|
24587
|
+
properties: {
|
|
24588
|
+
label: { type: 'string' },
|
|
24589
|
+
icon: { type: 'string' },
|
|
24590
|
+
visible: { type: 'boolean' },
|
|
24591
|
+
color: { enum: ['primary', 'accent', 'warn', 'basic'] },
|
|
24592
|
+
navigateTo: { type: 'string' }
|
|
24593
|
+
}
|
|
24594
|
+
},
|
|
24595
|
+
effects: [{ kind: 'merge-object', path: 'actions.cancel' }],
|
|
24596
|
+
validators: ['editor-round-trip-preserve'],
|
|
24597
|
+
affectedPaths: ['actions.cancel'],
|
|
24598
|
+
submissionImpact: false,
|
|
24599
|
+
preconditions: ['config-initialized']
|
|
24600
|
+
},
|
|
24601
|
+
{
|
|
24602
|
+
operationId: 'action.reset.configure',
|
|
24603
|
+
title: 'Configurar botão de reset',
|
|
24604
|
+
scope: 'global',
|
|
24605
|
+
targetKind: 'formAction',
|
|
24606
|
+
target: { kind: 'formAction', resolver: 'action-by-id-or-label', ambiguityPolicy: 'fail', required: false },
|
|
24607
|
+
inputSchema: {
|
|
24608
|
+
type: 'object',
|
|
24609
|
+
minProperties: 1,
|
|
24610
|
+
properties: {
|
|
24611
|
+
label: { type: 'string' },
|
|
24612
|
+
icon: { type: 'string' },
|
|
24613
|
+
visible: { type: 'boolean' },
|
|
24614
|
+
color: { enum: ['primary', 'accent', 'warn', 'basic'] },
|
|
24615
|
+
confirmBeforeReset: { type: 'boolean' }
|
|
24616
|
+
}
|
|
24617
|
+
},
|
|
24618
|
+
effects: [{ kind: 'merge-object', path: 'actions.reset' }],
|
|
24619
|
+
validators: ['editor-round-trip-preserve'],
|
|
24620
|
+
affectedPaths: ['actions.reset'],
|
|
24621
|
+
submissionImpact: false,
|
|
24622
|
+
preconditions: ['config-initialized']
|
|
24623
|
+
}
|
|
24624
|
+
],
|
|
24625
|
+
validators: [
|
|
24626
|
+
{
|
|
24627
|
+
validatorId: 'field-exists',
|
|
24628
|
+
level: 'error',
|
|
24629
|
+
code: 'DF001',
|
|
24630
|
+
description: 'O campo alvo deve existir em fieldMetadata.'
|
|
24631
|
+
},
|
|
24632
|
+
{
|
|
24633
|
+
validatorId: 'field-name-unique',
|
|
24634
|
+
level: 'error',
|
|
24635
|
+
code: 'DF002',
|
|
24636
|
+
description: 'Nomes de campos devem ser únicos em fieldMetadata.'
|
|
24637
|
+
},
|
|
24638
|
+
{
|
|
24639
|
+
validatorId: 'section-id-unique',
|
|
24640
|
+
level: 'error',
|
|
24641
|
+
code: 'DF003',
|
|
24642
|
+
description: 'IDs de seções devem ser únicos em sections[].'
|
|
24643
|
+
},
|
|
24644
|
+
{
|
|
24645
|
+
validatorId: 'section-exists',
|
|
24646
|
+
level: 'error',
|
|
24647
|
+
code: 'DF004',
|
|
24648
|
+
description: 'A seção alvo deve existir em sections[].'
|
|
24649
|
+
},
|
|
24650
|
+
{
|
|
24651
|
+
validatorId: 'field-exists-in-layout',
|
|
24652
|
+
level: 'warning',
|
|
24653
|
+
code: 'DF005',
|
|
24654
|
+
description: 'Campo definido em fieldMetadata mas ausente no layout.'
|
|
24655
|
+
},
|
|
24656
|
+
{
|
|
24657
|
+
validatorId: 'local-schema-name-no-collision',
|
|
24658
|
+
level: 'error',
|
|
24659
|
+
code: 'DF006',
|
|
24660
|
+
description: 'Nome de campo local não pode colidir com campo schema-backed existente.'
|
|
24661
|
+
},
|
|
24662
|
+
{
|
|
24663
|
+
validatorId: 'field-is-local',
|
|
24664
|
+
level: 'error',
|
|
24665
|
+
code: 'DF007',
|
|
24666
|
+
description: 'Operação restrita a campos locais (source:"local"). Campo schema-backed não pode ser removido ou ter submitPolicy alterada.'
|
|
24667
|
+
},
|
|
24668
|
+
{
|
|
24669
|
+
validatorId: 'rule-id-unique',
|
|
24670
|
+
level: 'error',
|
|
24671
|
+
code: 'DF008',
|
|
24672
|
+
description: 'IDs de regras devem ser únicos em formRules[].'
|
|
24673
|
+
},
|
|
24674
|
+
{
|
|
24675
|
+
validatorId: 'rule-exists',
|
|
24676
|
+
level: 'error',
|
|
24677
|
+
code: 'DF009',
|
|
24678
|
+
description: 'A regra alvo deve existir em formRules[].'
|
|
24679
|
+
},
|
|
24680
|
+
{
|
|
24681
|
+
validatorId: 'rule-target-refs-exist',
|
|
24682
|
+
level: 'error',
|
|
24683
|
+
code: 'DF010',
|
|
24684
|
+
description: 'Todos os targets da regra devem referenciar campos, seções ou ações existentes.'
|
|
24685
|
+
},
|
|
24686
|
+
{
|
|
24687
|
+
validatorId: 'json-logic-valid',
|
|
24688
|
+
level: 'error',
|
|
24689
|
+
code: 'DF011',
|
|
24690
|
+
description: 'A expressão JsonLogic deve ser um AST válido (suporte canônico à spec JsonLogic).'
|
|
24691
|
+
},
|
|
24692
|
+
{
|
|
24693
|
+
validatorId: 'layout-target-exists',
|
|
24694
|
+
level: 'error',
|
|
24695
|
+
code: 'DF012',
|
|
24696
|
+
description: 'A seção/linha/coluna de destino devem existir antes de mover o campo.'
|
|
24697
|
+
},
|
|
24698
|
+
{
|
|
24699
|
+
validatorId: 'no-index-as-identity',
|
|
24700
|
+
level: 'error',
|
|
24701
|
+
code: 'DF013',
|
|
24702
|
+
description: 'Operações de layout não devem usar índice de array como identidade canônica; use IDs estáveis.'
|
|
24703
|
+
},
|
|
24704
|
+
{
|
|
24705
|
+
validatorId: 'row-exists',
|
|
24706
|
+
level: 'error',
|
|
24707
|
+
code: 'DF014',
|
|
24708
|
+
description: 'A linha alvo deve existir na seção especificada.'
|
|
24709
|
+
},
|
|
24710
|
+
{
|
|
24711
|
+
validatorId: 'row-id-unique-in-section',
|
|
24712
|
+
level: 'error',
|
|
24713
|
+
code: 'DF015',
|
|
24714
|
+
description: 'O id da nova linha deve ser único dentro da seção alvo.'
|
|
24715
|
+
},
|
|
24716
|
+
{
|
|
24717
|
+
validatorId: 'column-id-unique-in-row',
|
|
24718
|
+
level: 'error',
|
|
24719
|
+
code: 'DF016',
|
|
24720
|
+
description: 'O id da nova coluna deve ser único dentro da linha alvo.'
|
|
24721
|
+
},
|
|
24722
|
+
{
|
|
24723
|
+
validatorId: 'control-type-compatible',
|
|
24724
|
+
level: 'error',
|
|
24725
|
+
code: 'DF017',
|
|
24726
|
+
description: 'O tipo de controle deve ser compatível com o tipo de dado do campo.'
|
|
24727
|
+
},
|
|
24728
|
+
{
|
|
24729
|
+
validatorId: 'option-source-valid',
|
|
24730
|
+
level: 'error',
|
|
24731
|
+
code: 'DF018',
|
|
24732
|
+
description: 'A fonte de opções deve ser válida para o tipo de controle especificado.'
|
|
24733
|
+
},
|
|
24734
|
+
{
|
|
24735
|
+
validatorId: 'editor-round-trip-preserve',
|
|
24736
|
+
level: 'error',
|
|
24737
|
+
code: 'DF019',
|
|
24738
|
+
description: 'Edição não deve corromper propriedades críticas para o round-trip do editor. Aplicado após operações que afetam submitPolicy ou source de campos locais.'
|
|
24739
|
+
}
|
|
24740
|
+
],
|
|
24741
|
+
roundTripRequirements: [
|
|
24742
|
+
'strip-undefined',
|
|
24743
|
+
'normalize-json-logic',
|
|
24744
|
+
'stable-field-order',
|
|
24745
|
+
'preserve-local-field-source',
|
|
24746
|
+
'preserve-section-row-column-ids'
|
|
24747
|
+
],
|
|
24748
|
+
examples: [
|
|
24749
|
+
{
|
|
24750
|
+
id: 'add-local-transient-field',
|
|
24751
|
+
request: 'Adicionar campo auxiliar local "notasInternas" que não deve aparecer no payload de envio',
|
|
24752
|
+
operationId: 'field.local.add',
|
|
24753
|
+
params: {
|
|
24754
|
+
name: 'notasInternas',
|
|
24755
|
+
label: 'Notas Internas',
|
|
24756
|
+
controlType: 'textarea',
|
|
24757
|
+
source: 'local',
|
|
24758
|
+
transient: true,
|
|
24759
|
+
submitPolicy: 'omit'
|
|
24760
|
+
},
|
|
24761
|
+
isPositive: true
|
|
24762
|
+
},
|
|
24763
|
+
{
|
|
24764
|
+
id: 'relabel-schema-backed-field',
|
|
24765
|
+
request: 'Mudar o label do campo email para "E-mail Corporativo"',
|
|
24766
|
+
operationId: 'field.label.set',
|
|
24767
|
+
target: 'email',
|
|
24768
|
+
params: { label: 'E-mail Corporativo' },
|
|
24769
|
+
isPositive: true
|
|
24770
|
+
},
|
|
24771
|
+
{
|
|
24772
|
+
id: 'move-field-to-column',
|
|
24773
|
+
request: 'Mover campo telefone para a primeira coluna da seção de contato',
|
|
24774
|
+
operationId: 'layout.field.move',
|
|
24775
|
+
target: 'telefone',
|
|
24776
|
+
params: { targetSectionId: 'secao-contato', targetRowId: 'row-1', targetColumnId: 'col-1' },
|
|
24777
|
+
isPositive: true
|
|
24778
|
+
},
|
|
24779
|
+
{
|
|
24780
|
+
id: 'add-visibility-rule',
|
|
24781
|
+
request: 'Ocultar o campo cpf quando o tipo de pessoa for jurídica',
|
|
24782
|
+
operationId: 'rule.visibility.add',
|
|
24783
|
+
params: {
|
|
24784
|
+
id: 'ocultar-cpf-pj',
|
|
24785
|
+
name: 'Ocultar CPF para PJ',
|
|
24786
|
+
targetType: 'field',
|
|
24787
|
+
targets: ['cpf'],
|
|
24788
|
+
condition: { '==': [{ var: 'tipoPessoa' }, 'juridica'] }
|
|
24789
|
+
},
|
|
24790
|
+
isPositive: true
|
|
24791
|
+
},
|
|
24792
|
+
{
|
|
24793
|
+
id: 'reject-unknown-field',
|
|
24794
|
+
request: 'Alterar o label do campo "campoInexistente"',
|
|
24795
|
+
operationId: 'field.label.set',
|
|
24796
|
+
target: 'campoInexistente',
|
|
24797
|
+
params: { label: 'Novo Label' },
|
|
24798
|
+
isPositive: false
|
|
24799
|
+
},
|
|
24800
|
+
{
|
|
24801
|
+
id: 'require-confirmation-remove-field',
|
|
24802
|
+
request: 'Remover o campo auxiliar "notasInternas"',
|
|
24803
|
+
operationId: 'field.local.remove',
|
|
24804
|
+
target: 'notasInternas',
|
|
24805
|
+
isPositive: true
|
|
24806
|
+
},
|
|
24807
|
+
{
|
|
24808
|
+
id: 'require-confirmation-remove-section',
|
|
24809
|
+
request: 'Remover a seção "Dados Adicionais"',
|
|
24810
|
+
operationId: 'layout.section.remove',
|
|
24811
|
+
target: 'secao-dados-adicionais',
|
|
24812
|
+
isPositive: true
|
|
24813
|
+
},
|
|
24814
|
+
{
|
|
24815
|
+
id: 'round-trip-no-drift',
|
|
24816
|
+
request: 'Salvar e reabrir o formulário editado sem drift de configuração',
|
|
24817
|
+
operationId: 'field.label.set',
|
|
24818
|
+
target: 'nome',
|
|
24819
|
+
params: { label: 'Nome Completo' },
|
|
24820
|
+
isPositive: true
|
|
24821
|
+
}
|
|
24822
|
+
]
|
|
24823
|
+
};
|
|
24824
|
+
|
|
23994
24825
|
/*
|
|
23995
24826
|
* Public API Surface of praxis-dynamic-form
|
|
23996
24827
|
*/
|
|
@@ -23999,4 +24830,4 @@ const FORM_COMPONENT_AI_CAPABILITIES = {
|
|
|
23999
24830
|
* Generated bundle index. Do not edit.
|
|
24000
24831
|
*/
|
|
24001
24832
|
|
|
24002
|
-
export { ActionsEditorComponent, CanvasStateService, CanvasToolbarComponent, ColumnEditorComponent, DynamicFormLayoutService, FORM_AI_CAPABILITIES, FORM_COMPONENT_AI_CAPABILITIES, FieldConfiguratorComponent, FieldEditorComponent, FormConfigService, FormContextService, FormLayoutService, JsonConfigEditorComponent, LayoutColorToken, LayoutEditorComponent, LayoutPrefsService, PRAXIS_DYNAMIC_FORM_COMPONENT_METADATA, PRAXIS_FILTER_FORM_COMPONENT_METADATA, PraxisDynamicForm, PraxisDynamicFormConfigEditor, PraxisFilterForm, PraxisFormActionsComponent, RowConfiguratorComponent, RowEditorComponent, SETTINGS_PANEL_DYNAMIC_FORM_PROVIDER, SectionEditorComponent, TASK_PRESETS, applyVisibilityRules, buildDynamicFormApplyPlan, createDynamicFormAuthoringDocument, formLayoutRulesToBuilderState, getFormAiCatalog, getFormCapabilities, getFormEnum, isRuleSatisfied, normalizeDateArrays, normalizeDynamicFormAuthoringDocument, parseLegacyOrDynamicFormDocument, providePraxisDynamicFormMetadata, providePraxisFilterFormMetadata, provideSettingsPanelDynamicForm, ruleBuilderStateToFormLayoutRules, serializeDynamicFormAuthoringDocument, stripLegacyFieldMetadata, toCanonicalDynamicFormConfig, validateDynamicFormAuthoringDocument, validateDynamicFormAuthoringInput };
|
|
24833
|
+
export { ActionsEditorComponent, CanvasStateService, CanvasToolbarComponent, ColumnEditorComponent, DynamicFormLayoutService, FORM_AI_CAPABILITIES, FORM_COMPONENT_AI_CAPABILITIES, FieldConfiguratorComponent, FieldEditorComponent, FormConfigService, FormContextService, FormLayoutService, JsonConfigEditorComponent, LayoutColorToken, LayoutEditorComponent, LayoutPrefsService, PRAXIS_DYNAMIC_FORM_AUTHORING_MANIFEST, PRAXIS_DYNAMIC_FORM_COMPONENT_METADATA, PRAXIS_FILTER_FORM_COMPONENT_METADATA, PraxisDynamicForm, PraxisDynamicFormConfigEditor, PraxisFilterForm, PraxisFormActionsComponent, RowConfiguratorComponent, RowEditorComponent, SETTINGS_PANEL_DYNAMIC_FORM_PROVIDER, SectionEditorComponent, TASK_PRESETS, applyVisibilityRules, buildDynamicFormApplyPlan, createDynamicFormAuthoringDocument, formLayoutRulesToBuilderState, getFormAiCatalog, getFormCapabilities, getFormEnum, isRuleSatisfied, normalizeDateArrays, normalizeDynamicFormAuthoringDocument, parseLegacyOrDynamicFormDocument, providePraxisDynamicFormMetadata, providePraxisFilterFormMetadata, provideSettingsPanelDynamicForm, ruleBuilderStateToFormLayoutRules, serializeDynamicFormAuthoringDocument, stripLegacyFieldMetadata, toCanonicalDynamicFormConfig, validateDynamicFormAuthoringDocument, validateDynamicFormAuthoringInput };
|