@wibi-global/sdk 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +0 -0
  2. package/dist/artifact-schema/src/biCatalog.d.ts +0 -0
  3. package/dist/artifact-schema/src/biCatalog.d.ts.map +0 -0
  4. package/dist/artifact-schema/src/biCatalog.js +0 -0
  5. package/dist/artifact-schema/src/biCatalog.js.map +0 -0
  6. package/dist/artifact-schema/src/hash.d.ts +0 -0
  7. package/dist/artifact-schema/src/hash.d.ts.map +0 -0
  8. package/dist/artifact-schema/src/hash.js +0 -0
  9. package/dist/artifact-schema/src/hash.js.map +0 -0
  10. package/dist/artifact-schema/src/index.d.ts +0 -0
  11. package/dist/artifact-schema/src/index.d.ts.map +0 -0
  12. package/dist/artifact-schema/src/index.js +0 -0
  13. package/dist/artifact-schema/src/index.js.map +0 -0
  14. package/dist/artifact-schema/src/schema.d.ts +0 -0
  15. package/dist/artifact-schema/src/schema.d.ts.map +0 -0
  16. package/dist/artifact-schema/src/schema.js +0 -0
  17. package/dist/artifact-schema/src/schema.js.map +0 -0
  18. package/dist/artifact-schema/src/types.d.ts +0 -0
  19. package/dist/artifact-schema/src/types.d.ts.map +0 -0
  20. package/dist/artifact-schema/src/types.js +0 -0
  21. package/dist/artifact-schema/src/types.js.map +0 -0
  22. package/dist/artifact-schema/src/validate.d.ts +0 -0
  23. package/dist/artifact-schema/src/validate.d.ts.map +0 -0
  24. package/dist/artifact-schema/src/validate.js +0 -0
  25. package/dist/artifact-schema/src/validate.js.map +0 -0
  26. package/dist/instructions/AGENTS.md +47 -0
  27. package/dist/instructions/AGENT_COMMON.md +0 -0
  28. package/dist/instructions/CLAUDE.md +9 -0
  29. package/dist/instructions/CODEX.md +0 -0
  30. package/dist/instructions/GEMINI.md +0 -0
  31. package/dist/prompts/authoring.wbp +0 -0
  32. package/dist/sdk/src/builder.d.ts +0 -0
  33. package/dist/sdk/src/builder.d.ts.map +0 -0
  34. package/dist/sdk/src/builder.js +0 -0
  35. package/dist/sdk/src/builder.js.map +0 -0
  36. package/dist/sdk/src/context.d.ts +0 -0
  37. package/dist/sdk/src/context.d.ts.map +0 -0
  38. package/dist/sdk/src/context.js +0 -0
  39. package/dist/sdk/src/context.js.map +0 -0
  40. package/dist/sdk/src/dashboard-context.d.ts +0 -0
  41. package/dist/sdk/src/dashboard-context.d.ts.map +0 -0
  42. package/dist/sdk/src/dashboard-context.js +0 -0
  43. package/dist/sdk/src/dashboard-context.js.map +0 -0
  44. package/dist/sdk/src/dashboard-spec.d.ts +0 -0
  45. package/dist/sdk/src/dashboard-spec.d.ts.map +1 -1
  46. package/dist/sdk/src/dashboard-spec.js +172 -18
  47. package/dist/sdk/src/dashboard-spec.js.map +1 -1
  48. package/dist/sdk/src/data/ctx.wbx +0 -0
  49. package/dist/sdk/src/data/index.d.ts +0 -0
  50. package/dist/sdk/src/data/index.d.ts.map +0 -0
  51. package/dist/sdk/src/data/index.js +0 -0
  52. package/dist/sdk/src/data/index.js.map +0 -0
  53. package/dist/sdk/src/data/queries.wbx +0 -0
  54. package/dist/sdk/src/data/serialization/dashboard-serialization.types.d.ts +110 -2
  55. package/dist/sdk/src/data/serialization/dashboard-serialization.types.d.ts.map +1 -1
  56. package/dist/sdk/src/data/serialization/dashboard-serialization.types.js +0 -0
  57. package/dist/sdk/src/data/serialization/dashboard-serialization.types.js.map +0 -0
  58. package/dist/sdk/src/data/serialization/dashboard-serialization.types.ts +161 -32
  59. package/dist/sdk/src/data/serialization/index.d.ts +1 -1
  60. package/dist/sdk/src/data/serialization/index.d.ts.map +1 -1
  61. package/dist/sdk/src/data/serialization/index.js +0 -0
  62. package/dist/sdk/src/data/serialization/index.js.map +0 -0
  63. package/dist/sdk/src/data/syntax.wbx +0 -0
  64. package/dist/sdk/src/data/view-categories.wbx +0 -0
  65. package/dist/sdk/src/data/views.wbx +0 -0
  66. package/dist/sdk/src/data/widgets.wbx +79 -16
  67. package/dist/sdk/src/data.d.ts +0 -0
  68. package/dist/sdk/src/data.d.ts.map +0 -0
  69. package/dist/sdk/src/data.js +0 -0
  70. package/dist/sdk/src/data.js.map +0 -0
  71. package/dist/sdk/src/index.d.ts +0 -0
  72. package/dist/sdk/src/index.d.ts.map +0 -0
  73. package/dist/sdk/src/index.js +0 -0
  74. package/dist/sdk/src/index.js.map +0 -0
  75. package/package.json +2 -2
package/README.md CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -65,6 +65,53 @@ Consulte APENAS estes arquivos locais antes de gerar widgets, queries ou layout:
65
65
  12. Use o mesmo fluxo de validacao e serializacao tanto para geracao inicial quanto para refinamento incremental.
66
66
  13. Nao inclua na resposta final secoes de encerramento como `Proximos Passos`, `Ajustes Recomendados` ou listas operacionais de CLI quando isso ja estiver coberto pelo frontend `DashboardStudio`.
67
67
 
68
+ ## Filtros Internos no progress-list
69
+
70
+ O widget `progress-list` suporta ate 2 filtros dropdown internos (`filter1`, `filter2`) que permitem segmentacao interativa dos dados sem nova query ao banco (filtro client-side).
71
+
72
+ ### Quando usar
73
+
74
+ - Quando o usuario precisa filtrar o ranking por uma dimensao (ex: filial, vendedor, periodo, categoria) sem trocar o widget.
75
+ - Cada filtro tem sua propria query SQL para popular as opcoes do select.
76
+
77
+ ### Regras obrigatorias
78
+
79
+ 1. **`filterField`** deve ser alias camelCase EXATAMENTE igual ao campo nos dados principais (rawData da query `query_id` do widget).
80
+ 2. **`valueField`** e **`labelField`** devem ser aliases camelCase dos campos retornados pela query do filtro.
81
+ 3. As SQLs de `filter1.sql` e `filter2.sql` suportam as mesmas variaveis da query principal: `${startDate}`, `${endDate}`, `${empresaId}`, `${companyFilter:alias}`.
82
+ 4. `showInternalFilters` pode ser omitido (padrao `true` quando filter1/filter2 definidos) ou `false` para ocultar os selects.
83
+ 5. O filtro NAO reexecuta a query principal — ele filtra localmente os dados ja carregados.
84
+
85
+ ### Exemplo completo
86
+
87
+ ```json
88
+ {
89
+ "widget_type": "progress-list",
90
+ "properties": {
91
+ "labelField": "nomeCliente",
92
+ "valueField": "valorVendas",
93
+ "maxItems": 15,
94
+ "valueFormat": "currency",
95
+ "colorScheme": "gradient",
96
+ "showInternalFilters": true,
97
+ "filter1": {
98
+ "label": "Filial",
99
+ "sql": "SELECT filialId AS value, nomeFilial AS label FROM vw_filiais WHERE empresaId = ${empresaId} ORDER BY nomeFilial",
100
+ "valueField": "value",
101
+ "labelField": "label",
102
+ "filterField": "filialId"
103
+ },
104
+ "filter2": {
105
+ "label": "Vendedor",
106
+ "sql": "SELECT vendedorId AS value, nomeVendedor AS label FROM vw_vendedores WHERE empresaId = ${empresaId} ORDER BY nomeVendedor",
107
+ "valueField": "value",
108
+ "labelField": "label",
109
+ "filterField": "vendedorId"
110
+ }
111
+ }
112
+ }
113
+ ```
114
+
68
115
  ## Checklist de requisitos
69
116
 
70
117
  Antes de editar `dashboard.serialized.json`, confirme:
File without changes
@@ -47,6 +47,15 @@ Veja regras detalhadas em `AGENTS.md`.
47
47
  - Em refinamentos, evite recriar o dashboard inteiro; preserve a base existente e aplique diffs pequenos e localizados.
48
48
  - A resposta final nao deve incluir blocos como `Proximos Passos` ou `Ajustes Recomendados`.
49
49
 
50
+ ## Filtros Internos no progress-list
51
+
52
+ O widget `progress-list` suporta `filter1` e `filter2`: selects discretos dentro do widget que filtram os dados localmente.
53
+
54
+ - Consulte `AGENTS.md` para as regras completas e exemplo de uso.
55
+ - `filterField` deve ser alias camelCase existente nos dados da query principal do widget.
56
+ - `filter1.sql` / `filter2.sql` suportam `${startDate}`, `${endDate}`, `${empresaId}`, `${companyFilter:alias}`.
57
+ - O filtro e client-side — nao dispara nova query ao banco.
58
+
50
59
  ## Preferencias de autoria
51
60
 
52
61
  - Prefira editar `dashboard.serialized.json` preservando a estrutura canonica do schema oficial.
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1 +1 @@
1
- {"version":3,"file":"dashboard-spec.d.ts","sourceRoot":"","sources":["../../../src/dashboard-spec.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAGV,mBAAmB,EAepB,MAAM,uDAAuD,CAAA;AAE9D,MAAM,MAAM,aAAa,GAAG,mBAAmB,CAAA;AAE/C,MAAM,MAAM,4BAA4B,GAAG;IACzC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,6BAA6B,GACrC;IACE,EAAE,EAAE,IAAI,CAAA;IACR,MAAM,EAAE,EAAE,CAAA;CACX,GACD;IACE,EAAE,EAAE,KAAK,CAAA;IACT,MAAM,EAAE,4BAA4B,EAAE,CAAA;CACvC,CAAA;AAEL,eAAO,MAAM,2BAA2B,EAAG,OAAgB,CAAA;AAC3D,eAAO,MAAM,+BAA+B,EAAG,eAAwB,CAAA;AACvE,eAAO,MAAM,uCAAuC,+BAAgC,CAAA;AACpF,eAAO,MAAM,sCAAsC,EAAG,IAAa,CAAA;AACnE,eAAO,MAAM,sCAAsC,sNAYzC,CAAA;AAqkCV,eAAO,MAAM,qBAAqB,GAAI,MAAM,aAAa,KAAG,6BAmD3D,CAAA;AAED,eAAO,MAAM,wBAAwB,GAAI,MAAM,aAAa,KAAG,aAS9D,CAAA;AAED,eAAO,MAAM,mBAAmB,GAAI,MAAM,aAAa,KAAG,aAEzD,CAAA"}
1
+ {"version":3,"file":"dashboard-spec.d.ts","sourceRoot":"","sources":["../../../src/dashboard-spec.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAGV,mBAAmB,EAiBpB,MAAM,uDAAuD,CAAA;AAE9D,MAAM,MAAM,aAAa,GAAG,mBAAmB,CAAA;AAE/C,MAAM,MAAM,4BAA4B,GAAG;IACzC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,6BAA6B,GACrC;IACE,EAAE,EAAE,IAAI,CAAA;IACR,MAAM,EAAE,EAAE,CAAA;CACX,GACD;IACE,EAAE,EAAE,KAAK,CAAA;IACT,MAAM,EAAE,4BAA4B,EAAE,CAAA;CACvC,CAAA;AAEL,eAAO,MAAM,2BAA2B,EAAG,OAAgB,CAAA;AAC3D,eAAO,MAAM,+BAA+B,EAAG,eAAwB,CAAA;AACvE,eAAO,MAAM,uCAAuC,+BAAgC,CAAA;AACpF,eAAO,MAAM,sCAAsC,EAAG,IAAa,CAAA;AACnE,eAAO,MAAM,sCAAsC,sNAYzC,CAAA;AAwwCV,eAAO,MAAM,qBAAqB,GAAI,MAAM,aAAa,KAAG,6BAmD3D,CAAA;AAED,eAAO,MAAM,wBAAwB,GAAI,MAAM,aAAa,KAAG,aAS9D,CAAA;AAED,eAAO,MAAM,mBAAmB,GAAI,MAAM,aAAa,KAAG,aAEzD,CAAA"}
@@ -21,20 +21,40 @@ export const WIBI_DASHBOARD_REQUIRED_INITIAL_FIELDS = [
21
21
  ];
22
22
  const STABLE_ID_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
23
23
  const FILTER_ID_PATTERN = /^filter-[a-z0-9]+(?:-[a-z0-9]+)*$/;
24
- const SECTION_ID_PATTERN = /^section-[a-z0-9]+(?:-[a-z0-9]+)*$/;
25
- const QUERY_ID_PATTERN = /^query-[a-z0-9]+(?:-[a-z0-9]+)*$/;
24
+ const SECTION_ID_PATTERN = /^(?:section|sec)-[a-z0-9]+(?:-[a-z0-9]+)*$/;
25
+ const QUERY_ID_PATTERN = /^(?:query|q)-[a-z0-9]+(?:-[a-z0-9]+)*$/;
26
26
  const ISO_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
27
- const WIDGET_TYPES = new Set(['kpi', 'chart', 'grid', 'progress-list', 'panel', 'date', 'companies', 'heading', 'text', 'image', 'divider', 'spacer']);
27
+ const WIDGET_TYPES = new Set([
28
+ 'kpi',
29
+ 'chart',
30
+ 'grid',
31
+ 'pivot',
32
+ 'pivotgrid',
33
+ 'progress-list',
34
+ 'panel',
35
+ 'date',
36
+ 'companies',
37
+ 'heading',
38
+ 'text',
39
+ 'image',
40
+ 'divider',
41
+ 'spacer',
42
+ ]);
28
43
  /** Widget types that are decorative — no data query required */
29
44
  const DECORATIVE_WIDGET_TYPES = new Set(['date', 'companies', 'heading', 'text', 'image', 'divider', 'spacer']);
30
45
  const OFFICIAL_DASHBOARD_ITEM_TYPES = new Set((widgetSchemaJson.dashboardItemType?.fields?.type?.values ?? []).filter(Boolean));
31
46
  const OFFICIAL_WIDGET_CATALOG = (widgetSchemaJson.widgets ?? {});
32
- const OFFICIAL_CHART_VARIANTS = new Set(Object.entries(OFFICIAL_WIDGET_CATALOG)
33
- .filter(([, definition]) => definition.render === 'EChartsChart')
34
- .map(([type]) => type));
47
+ const OFFICIAL_CHART_VARIANTS = new Set([
48
+ ...Object.entries(OFFICIAL_WIDGET_CATALOG)
49
+ .filter(([, definition]) => definition.render === 'EChartsChart')
50
+ .map(([type]) => type),
51
+ ...((widgetSchemaJson.widgets ?? {}).chart?.propertiesDetailed?.chart_type?.values ?? []),
52
+ ].filter(Boolean));
35
53
  const SERIALIZED_WIDGET_TO_OFFICIAL_TYPES = {
36
54
  chart: ['chart'],
37
55
  grid: ['grid'],
56
+ pivot: ['pivot'],
57
+ pivotgrid: ['pivotgrid', 'pivot'],
38
58
  kpi: ['kpi'],
39
59
  'progress-list': ['progress-list'],
40
60
  panel: ['card'],
@@ -78,6 +98,8 @@ const CHART_ALLOWED_PROPERTY_KEYS = new Set([
78
98
  'orientation',
79
99
  'bar_width',
80
100
  'rotate_labels',
101
+ 'x_axis_label_rotate',
102
+ 'x_axis_date_format',
81
103
  'enable_zoom',
82
104
  'smooth',
83
105
  'show_symbol',
@@ -111,6 +133,33 @@ const GRID_ALLOWED_PROPERTY_KEYS = new Set([
111
133
  'empty_message',
112
134
  'loading_message',
113
135
  ]);
136
+ const PIVOT_ALLOWED_PROPERTY_KEYS = new Set([
137
+ 'row_field',
138
+ 'rowField',
139
+ 'col_field',
140
+ 'column_field',
141
+ 'colField',
142
+ 'columnField',
143
+ 'value_field',
144
+ 'valueField',
145
+ 'aggregation',
146
+ 'show_totals',
147
+ 'showTotals',
148
+ 'format',
149
+ 'value_format',
150
+ 'valueFormat',
151
+ 'decimals',
152
+ 'currency',
153
+ 'locale',
154
+ 'searchable',
155
+ 'search_placeholder',
156
+ 'searchPlaceholder',
157
+ 'loading_message',
158
+ 'loadingMessage',
159
+ 'empty_message',
160
+ 'emptyMessage',
161
+ ]);
162
+ const PIVOTGRID_ALLOWED_PROPERTY_KEYS = new Set([...PIVOT_ALLOWED_PROPERTY_KEYS]);
114
163
  const PANEL_ALLOWED_PROPERTY_KEYS = new Set([
115
164
  'panel_type',
116
165
  'title',
@@ -123,17 +172,32 @@ const PANEL_ALLOWED_PROPERTY_KEYS = new Set([
123
172
  ]);
124
173
  const PROGRESS_LIST_ALLOWED_PROPERTY_KEYS = new Set([
125
174
  'labelField',
175
+ 'label_field',
126
176
  'valueField',
177
+ 'value_field',
127
178
  'aggregation',
128
179
  'maxItems',
180
+ 'max_items',
129
181
  'showValues',
182
+ 'show_values',
130
183
  'showPercentage',
184
+ 'show_percentage',
131
185
  'valueFormat',
186
+ 'value_format',
132
187
  'barHeight',
188
+ 'bar_height',
133
189
  'colorScheme',
190
+ 'color_scheme',
134
191
  'primaryColor',
192
+ 'primary_color',
135
193
  'sortBy',
194
+ 'sort_by',
136
195
  'sortOrder',
196
+ 'sort_order',
197
+ 'title',
198
+ 'showInternalFilters',
199
+ 'filter1',
200
+ 'filter2',
137
201
  ]);
138
202
  const DATE_WIDGET_ALLOWED_PROPERTY_KEYS = new Set([
139
203
  'label',
@@ -195,6 +259,8 @@ const ACCEPTED_QUERY_PLACEHOLDER_PATTERNS = [
195
259
  /^\$\{companyFilter:[a-zA-Z_][a-zA-Z0-9_]*\}$/,
196
260
  /^\{\{start_date\}\}$/,
197
261
  /^\{\{end_date\}\}$/,
262
+ /^\{\{startDate\}\}$/,
263
+ /^\{\{endDate\}\}$/,
198
264
  /^\{\{year\}\}$/,
199
265
  /^\{\{date_limit_2y\}\}$/,
200
266
  ];
@@ -202,12 +268,30 @@ const ALLOWED_MANUAL_QUERY_SOURCES = new Set(['manual-bootstrap']);
202
268
  const pushIssue = (issues, path, message) => {
203
269
  issues.push({ path, message });
204
270
  };
205
- const isNonEmptyString = (value) => value.trim().length > 0;
271
+ const isNonEmptyString = (value) => typeof value === 'string' && value.trim().length > 0;
206
272
  const validateNonEmptyString = (issues, path, value, label) => {
207
273
  if (!isNonEmptyString(value)) {
208
274
  pushIssue(issues, path, `${label} must be a non-empty string.`);
209
275
  }
210
276
  };
277
+ const getStringAlias = (record, ...keys) => {
278
+ for (const key of keys) {
279
+ const value = record[key];
280
+ if (typeof value === 'string' && value.trim().length > 0) {
281
+ return value;
282
+ }
283
+ }
284
+ return undefined;
285
+ };
286
+ const getNumberAlias = (record, ...keys) => {
287
+ for (const key of keys) {
288
+ const value = record[key];
289
+ if (typeof value === 'number' && Number.isFinite(value)) {
290
+ return value;
291
+ }
292
+ }
293
+ return undefined;
294
+ };
211
295
  const validateUniqueStrings = (issues, values, path, label) => {
212
296
  const seen = new Set();
213
297
  values.forEach((value, index) => {
@@ -335,6 +419,62 @@ const validateGridProperties = (properties, issues, path) => {
335
419
  pushIssue(issues, path, 'Official grid definition is missing from widgets.wbx.');
336
420
  }
337
421
  };
422
+ const validatePivotBaseProperties = (properties, issues, path, widgetType) => {
423
+ validateAllowedPropertyKeys(issues, path, properties, widgetType === 'pivot' ? PIVOT_ALLOWED_PROPERTY_KEYS : PIVOTGRID_ALLOWED_PROPERTY_KEYS, widgetType);
424
+ const propertyRecord = properties;
425
+ const rowField = getStringAlias(propertyRecord, 'row_field', 'rowField');
426
+ const columnField = getStringAlias(propertyRecord, 'col_field', 'column_field', 'colField', 'columnField');
427
+ const valueField = getStringAlias(propertyRecord, 'value_field', 'valueField');
428
+ const aggregation = getStringAlias(propertyRecord, 'aggregation');
429
+ const showTotals = propertyRecord.show_totals ?? propertyRecord.showTotals;
430
+ const format = getStringAlias(propertyRecord, 'format');
431
+ const valueFormat = getStringAlias(propertyRecord, 'value_format', 'valueFormat');
432
+ const decimals = getNumberAlias(propertyRecord, 'decimals');
433
+ const currency = getStringAlias(propertyRecord, 'currency');
434
+ const locale = getStringAlias(propertyRecord, 'locale');
435
+ validateNonEmptyString(issues, `${path}.row_field`, rowField, `${widgetType} row_field`);
436
+ validateNonEmptyString(issues, `${path}.col_field`, columnField, `${widgetType} col_field`);
437
+ validateNonEmptyString(issues, `${path}.value_field`, valueField, `${widgetType} value_field`);
438
+ if (aggregation !== undefined) {
439
+ const validAggregations = new Set(['sum', 'avg', 'count', 'min', 'max']);
440
+ if (!validAggregations.has(aggregation)) {
441
+ pushIssue(issues, `${path}.aggregation`, `${widgetType} aggregation must be one of sum, avg, count, min or max.`);
442
+ }
443
+ }
444
+ if (showTotals !== undefined && typeof showTotals !== 'boolean') {
445
+ pushIssue(issues, `${path}.show_totals`, `${widgetType} show_totals must be a boolean when provided.`);
446
+ }
447
+ if (format !== undefined || valueFormat !== undefined) {
448
+ const effectiveFormat = format ?? valueFormat;
449
+ const validFormats = new Set(['number', 'currency', 'percent']);
450
+ if (typeof effectiveFormat !== 'string' || !validFormats.has(effectiveFormat)) {
451
+ pushIssue(issues, `${path}.format`, `${widgetType} format must be one of number, currency or percent.`);
452
+ }
453
+ }
454
+ if (decimals !== undefined && (!Number.isInteger(decimals) || decimals < 0)) {
455
+ pushIssue(issues, `${path}.decimals`, `${widgetType} decimals must be a non-negative integer when provided.`);
456
+ }
457
+ if (currency !== undefined) {
458
+ validateNonEmptyString(issues, `${path}.currency`, currency, `${widgetType} currency`);
459
+ }
460
+ if (locale !== undefined) {
461
+ validateNonEmptyString(issues, `${path}.locale`, locale, `${widgetType} locale`);
462
+ }
463
+ };
464
+ const validatePivotProperties = (properties, issues, path) => {
465
+ validatePivotBaseProperties(properties, issues, path, 'pivot');
466
+ const officialPivot = OFFICIAL_WIDGET_CATALOG.pivot;
467
+ if (!officialPivot) {
468
+ pushIssue(issues, path, 'Official pivot definition is missing from widgets.wbx.');
469
+ }
470
+ };
471
+ const validatePivotGridProperties = (properties, issues, path) => {
472
+ validatePivotBaseProperties(properties, issues, path, 'pivotgrid');
473
+ const officialPivotGrid = OFFICIAL_WIDGET_CATALOG.pivotgrid;
474
+ if (!officialPivotGrid) {
475
+ pushIssue(issues, path, 'Official pivotgrid definition is missing from widgets.wbx.');
476
+ }
477
+ };
338
478
  const validatePanelProperties = (properties, issues, path) => {
339
479
  validateAllowedPropertyKeys(issues, path, properties, PANEL_ALLOWED_PROPERTY_KEYS, 'panel');
340
480
  validateNonEmptyString(issues, `${path}.title`, properties.title, 'Panel title');
@@ -360,32 +500,40 @@ const validatePanelProperties = (properties, issues, path) => {
360
500
  };
361
501
  const validateProgressListProperties = (properties, issues, path) => {
362
502
  validateAllowedPropertyKeys(issues, path, properties, PROGRESS_LIST_ALLOWED_PROPERTY_KEYS, 'progress-list');
363
- validateNonEmptyString(issues, `${path}.labelField`, properties.labelField, 'ProgressList labelField');
364
- validateNonEmptyString(issues, `${path}.valueField`, properties.valueField, 'ProgressList valueField');
365
- if (properties.maxItems !== undefined && (!Number.isInteger(properties.maxItems) || properties.maxItems <= 0)) {
503
+ const propertyRecord = properties;
504
+ const labelField = getStringAlias(propertyRecord, 'labelField', 'label_field');
505
+ const valueField = getStringAlias(propertyRecord, 'valueField', 'value_field');
506
+ const maxItems = getNumberAlias(propertyRecord, 'maxItems', 'max_items');
507
+ const valueFormat = getStringAlias(propertyRecord, 'valueFormat', 'value_format');
508
+ const colorScheme = getStringAlias(propertyRecord, 'colorScheme', 'color_scheme');
509
+ const sortBy = getStringAlias(propertyRecord, 'sortBy', 'sort_by');
510
+ const sortOrder = getStringAlias(propertyRecord, 'sortOrder', 'sort_order');
511
+ validateNonEmptyString(issues, `${path}.labelField`, labelField, 'ProgressList labelField');
512
+ validateNonEmptyString(issues, `${path}.valueField`, valueField, 'ProgressList valueField');
513
+ if (maxItems !== undefined && (!Number.isInteger(maxItems) || maxItems <= 0)) {
366
514
  pushIssue(issues, `${path}.maxItems`, 'ProgressList maxItems must be a positive integer when provided.');
367
515
  }
368
- if (properties.valueFormat !== undefined) {
516
+ if (valueFormat !== undefined) {
369
517
  const validFormats = new Set(['number', 'currency', 'compact', 'percentage']);
370
- if (!validFormats.has(properties.valueFormat)) {
518
+ if (!validFormats.has(valueFormat)) {
371
519
  pushIssue(issues, `${path}.valueFormat`, 'ProgressList valueFormat must be one of: number, currency, compact, percentage.');
372
520
  }
373
521
  }
374
- if (properties.colorScheme !== undefined) {
522
+ if (colorScheme !== undefined) {
375
523
  const validSchemes = new Set(['gradient', 'single', 'custom']);
376
- if (!validSchemes.has(properties.colorScheme)) {
524
+ if (!validSchemes.has(colorScheme)) {
377
525
  pushIssue(issues, `${path}.colorScheme`, 'ProgressList colorScheme must be one of: gradient, single, custom.');
378
526
  }
379
527
  }
380
- if (properties.sortBy !== undefined) {
528
+ if (sortBy !== undefined) {
381
529
  const validSortBy = new Set(['value', 'label', 'none']);
382
- if (!validSortBy.has(properties.sortBy)) {
530
+ if (!validSortBy.has(sortBy)) {
383
531
  pushIssue(issues, `${path}.sortBy`, 'ProgressList sortBy must be one of: value, label, none.');
384
532
  }
385
533
  }
386
- if (properties.sortOrder !== undefined) {
534
+ if (sortOrder !== undefined) {
387
535
  const validSortOrder = new Set(['asc', 'desc']);
388
- if (!validSortOrder.has(properties.sortOrder)) {
536
+ if (!validSortOrder.has(sortOrder)) {
389
537
  pushIssue(issues, `${path}.sortOrder`, 'ProgressList sortOrder must be one of: asc, desc.');
390
538
  }
391
539
  }
@@ -488,6 +636,12 @@ const validateWidgetProperties = (widget, issues, path) => {
488
636
  case 'grid':
489
637
  validateGridProperties(widget.properties, issues, `${path}.properties`);
490
638
  break;
639
+ case 'pivot':
640
+ validatePivotProperties(widget.properties, issues, `${path}.properties`);
641
+ break;
642
+ case 'pivotgrid':
643
+ validatePivotGridProperties(widget.properties, issues, `${path}.properties`);
644
+ break;
491
645
  case 'progress-list':
492
646
  validateProgressListProperties(widget.properties, issues, `${path}.properties`);
493
647
  break;