@wibi-global/sdk 0.1.1 → 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 +176 -19
  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 +1 -1
  51. package/dist/sdk/src/data/index.js +10 -10
  52. package/dist/sdk/src/data/index.js.map +1 -1
  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":"AACA,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"}
@@ -1,4 +1,7 @@
1
- import widgetSchemaJson from './data/widgets.wbx' with { type: 'json' };
1
+ import { readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ const widgetSchemaJson = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), 'data/widgets.wbx'), 'utf8'));
2
5
  export const WIBI_DASHBOARD_SPEC_VERSION = '1.0.0';
3
6
  export const WIBI_DASHBOARD_SPEC_EXPORT_NAME = 'dashboardSpec';
4
7
  export const WIBI_DASHBOARD_TEMPLATE_ALLOWED_IMPORTS = ['@wibi-global/sdk'];
@@ -18,20 +21,40 @@ export const WIBI_DASHBOARD_REQUIRED_INITIAL_FIELDS = [
18
21
  ];
19
22
  const STABLE_ID_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
20
23
  const FILTER_ID_PATTERN = /^filter-[a-z0-9]+(?:-[a-z0-9]+)*$/;
21
- const SECTION_ID_PATTERN = /^section-[a-z0-9]+(?:-[a-z0-9]+)*$/;
22
- 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]+)*$/;
23
26
  const ISO_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
24
- 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
+ ]);
25
43
  /** Widget types that are decorative — no data query required */
26
44
  const DECORATIVE_WIDGET_TYPES = new Set(['date', 'companies', 'heading', 'text', 'image', 'divider', 'spacer']);
27
45
  const OFFICIAL_DASHBOARD_ITEM_TYPES = new Set((widgetSchemaJson.dashboardItemType?.fields?.type?.values ?? []).filter(Boolean));
28
46
  const OFFICIAL_WIDGET_CATALOG = (widgetSchemaJson.widgets ?? {});
29
- const OFFICIAL_CHART_VARIANTS = new Set(Object.entries(OFFICIAL_WIDGET_CATALOG)
30
- .filter(([, definition]) => definition.render === 'EChartsChart')
31
- .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));
32
53
  const SERIALIZED_WIDGET_TO_OFFICIAL_TYPES = {
33
54
  chart: ['chart'],
34
55
  grid: ['grid'],
56
+ pivot: ['pivot'],
57
+ pivotgrid: ['pivotgrid', 'pivot'],
35
58
  kpi: ['kpi'],
36
59
  'progress-list': ['progress-list'],
37
60
  panel: ['card'],
@@ -75,6 +98,8 @@ const CHART_ALLOWED_PROPERTY_KEYS = new Set([
75
98
  'orientation',
76
99
  'bar_width',
77
100
  'rotate_labels',
101
+ 'x_axis_label_rotate',
102
+ 'x_axis_date_format',
78
103
  'enable_zoom',
79
104
  'smooth',
80
105
  'show_symbol',
@@ -108,6 +133,33 @@ const GRID_ALLOWED_PROPERTY_KEYS = new Set([
108
133
  'empty_message',
109
134
  'loading_message',
110
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]);
111
163
  const PANEL_ALLOWED_PROPERTY_KEYS = new Set([
112
164
  'panel_type',
113
165
  'title',
@@ -120,17 +172,32 @@ const PANEL_ALLOWED_PROPERTY_KEYS = new Set([
120
172
  ]);
121
173
  const PROGRESS_LIST_ALLOWED_PROPERTY_KEYS = new Set([
122
174
  'labelField',
175
+ 'label_field',
123
176
  'valueField',
177
+ 'value_field',
124
178
  'aggregation',
125
179
  'maxItems',
180
+ 'max_items',
126
181
  'showValues',
182
+ 'show_values',
127
183
  'showPercentage',
184
+ 'show_percentage',
128
185
  'valueFormat',
186
+ 'value_format',
129
187
  'barHeight',
188
+ 'bar_height',
130
189
  'colorScheme',
190
+ 'color_scheme',
131
191
  'primaryColor',
192
+ 'primary_color',
132
193
  'sortBy',
194
+ 'sort_by',
133
195
  'sortOrder',
196
+ 'sort_order',
197
+ 'title',
198
+ 'showInternalFilters',
199
+ 'filter1',
200
+ 'filter2',
134
201
  ]);
135
202
  const DATE_WIDGET_ALLOWED_PROPERTY_KEYS = new Set([
136
203
  'label',
@@ -192,6 +259,8 @@ const ACCEPTED_QUERY_PLACEHOLDER_PATTERNS = [
192
259
  /^\$\{companyFilter:[a-zA-Z_][a-zA-Z0-9_]*\}$/,
193
260
  /^\{\{start_date\}\}$/,
194
261
  /^\{\{end_date\}\}$/,
262
+ /^\{\{startDate\}\}$/,
263
+ /^\{\{endDate\}\}$/,
195
264
  /^\{\{year\}\}$/,
196
265
  /^\{\{date_limit_2y\}\}$/,
197
266
  ];
@@ -199,12 +268,30 @@ const ALLOWED_MANUAL_QUERY_SOURCES = new Set(['manual-bootstrap']);
199
268
  const pushIssue = (issues, path, message) => {
200
269
  issues.push({ path, message });
201
270
  };
202
- const isNonEmptyString = (value) => value.trim().length > 0;
271
+ const isNonEmptyString = (value) => typeof value === 'string' && value.trim().length > 0;
203
272
  const validateNonEmptyString = (issues, path, value, label) => {
204
273
  if (!isNonEmptyString(value)) {
205
274
  pushIssue(issues, path, `${label} must be a non-empty string.`);
206
275
  }
207
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
+ };
208
295
  const validateUniqueStrings = (issues, values, path, label) => {
209
296
  const seen = new Set();
210
297
  values.forEach((value, index) => {
@@ -332,6 +419,62 @@ const validateGridProperties = (properties, issues, path) => {
332
419
  pushIssue(issues, path, 'Official grid definition is missing from widgets.wbx.');
333
420
  }
334
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
+ };
335
478
  const validatePanelProperties = (properties, issues, path) => {
336
479
  validateAllowedPropertyKeys(issues, path, properties, PANEL_ALLOWED_PROPERTY_KEYS, 'panel');
337
480
  validateNonEmptyString(issues, `${path}.title`, properties.title, 'Panel title');
@@ -357,32 +500,40 @@ const validatePanelProperties = (properties, issues, path) => {
357
500
  };
358
501
  const validateProgressListProperties = (properties, issues, path) => {
359
502
  validateAllowedPropertyKeys(issues, path, properties, PROGRESS_LIST_ALLOWED_PROPERTY_KEYS, 'progress-list');
360
- validateNonEmptyString(issues, `${path}.labelField`, properties.labelField, 'ProgressList labelField');
361
- validateNonEmptyString(issues, `${path}.valueField`, properties.valueField, 'ProgressList valueField');
362
- 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)) {
363
514
  pushIssue(issues, `${path}.maxItems`, 'ProgressList maxItems must be a positive integer when provided.');
364
515
  }
365
- if (properties.valueFormat !== undefined) {
516
+ if (valueFormat !== undefined) {
366
517
  const validFormats = new Set(['number', 'currency', 'compact', 'percentage']);
367
- if (!validFormats.has(properties.valueFormat)) {
518
+ if (!validFormats.has(valueFormat)) {
368
519
  pushIssue(issues, `${path}.valueFormat`, 'ProgressList valueFormat must be one of: number, currency, compact, percentage.');
369
520
  }
370
521
  }
371
- if (properties.colorScheme !== undefined) {
522
+ if (colorScheme !== undefined) {
372
523
  const validSchemes = new Set(['gradient', 'single', 'custom']);
373
- if (!validSchemes.has(properties.colorScheme)) {
524
+ if (!validSchemes.has(colorScheme)) {
374
525
  pushIssue(issues, `${path}.colorScheme`, 'ProgressList colorScheme must be one of: gradient, single, custom.');
375
526
  }
376
527
  }
377
- if (properties.sortBy !== undefined) {
528
+ if (sortBy !== undefined) {
378
529
  const validSortBy = new Set(['value', 'label', 'none']);
379
- if (!validSortBy.has(properties.sortBy)) {
530
+ if (!validSortBy.has(sortBy)) {
380
531
  pushIssue(issues, `${path}.sortBy`, 'ProgressList sortBy must be one of: value, label, none.');
381
532
  }
382
533
  }
383
- if (properties.sortOrder !== undefined) {
534
+ if (sortOrder !== undefined) {
384
535
  const validSortOrder = new Set(['asc', 'desc']);
385
- if (!validSortOrder.has(properties.sortOrder)) {
536
+ if (!validSortOrder.has(sortOrder)) {
386
537
  pushIssue(issues, `${path}.sortOrder`, 'ProgressList sortOrder must be one of: asc, desc.');
387
538
  }
388
539
  }
@@ -485,6 +636,12 @@ const validateWidgetProperties = (widget, issues, path) => {
485
636
  case 'grid':
486
637
  validateGridProperties(widget.properties, issues, `${path}.properties`);
487
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;
488
645
  case 'progress-list':
489
646
  validateProgressListProperties(widget.properties, issues, `${path}.properties`);
490
647
  break;