@zentto/report-core 0.5.0 → 1.0.0
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/dist/engine/conditional-format.d.ts +48 -0
- package/dist/engine/conditional-format.d.ts.map +1 -0
- package/dist/engine/conditional-format.js +167 -0
- package/dist/engine/conditional-format.js.map +1 -0
- package/dist/engine/cross-tab.d.ts +68 -0
- package/dist/engine/cross-tab.d.ts.map +1 -0
- package/dist/engine/cross-tab.js +549 -0
- package/dist/engine/cross-tab.js.map +1 -0
- package/dist/engine/expression.d.ts +46 -2
- package/dist/engine/expression.d.ts.map +1 -1
- package/dist/engine/expression.js +1415 -90
- package/dist/engine/expression.js.map +1 -1
- package/dist/engine/multi-pass-engine.d.ts +74 -0
- package/dist/engine/multi-pass-engine.d.ts.map +1 -0
- package/dist/engine/multi-pass-engine.js +1082 -0
- package/dist/engine/multi-pass-engine.js.map +1 -0
- package/dist/engine/running-totals.d.ts +74 -0
- package/dist/engine/running-totals.d.ts.map +1 -0
- package/dist/engine/running-totals.js +247 -0
- package/dist/engine/running-totals.js.map +1 -0
- package/dist/engine/subreport.d.ts +59 -0
- package/dist/engine/subreport.d.ts.map +1 -0
- package/dist/engine/subreport.js +295 -0
- package/dist/engine/subreport.js.map +1 -0
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/schema/report-schema.d.ts +346 -346
- package/dist/serialization/json.d.ts +88 -88
- package/dist/templates/page-sizes.d.ts.map +1 -1
- package/dist/templates/page-sizes.js +95 -3
- package/dist/templates/page-sizes.js.map +1 -1
- package/dist/types.d.ts +38 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ElementStyle } from '../types.js';
|
|
2
|
+
import type { BindingContext } from './data-binding.js';
|
|
3
|
+
export interface ConditionalFormatRule {
|
|
4
|
+
id: string;
|
|
5
|
+
/** Target element ID, or '*' for all elements */
|
|
6
|
+
targetElementId: string;
|
|
7
|
+
/** Target band ID — if set, rule applies to all elements in that band */
|
|
8
|
+
targetBandId?: string;
|
|
9
|
+
/** Priority — lower number = higher priority */
|
|
10
|
+
priority: number;
|
|
11
|
+
/** Expression that evaluates to boolean (without leading '=') */
|
|
12
|
+
condition: string;
|
|
13
|
+
/** Styles to apply when condition is true */
|
|
14
|
+
style: Partial<ElementStyle>;
|
|
15
|
+
/** Whether to stop evaluating further rules after this one matches */
|
|
16
|
+
stopIfTrue: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface ConditionalFormatResult {
|
|
19
|
+
elementId: string;
|
|
20
|
+
/** Merged style from all matching rules (higher priority wins per property) */
|
|
21
|
+
computedStyle: Partial<ElementStyle>;
|
|
22
|
+
/** IDs of rules that triggered, in order of evaluation */
|
|
23
|
+
triggeredRules: string[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Evaluate all conditional format rules against the current context
|
|
27
|
+
* and return the computed styles for the given element IDs.
|
|
28
|
+
*
|
|
29
|
+
* Rules are sorted by priority (lowest number = highest priority).
|
|
30
|
+
* For each element, matching rules are applied in priority order.
|
|
31
|
+
* If a rule has `stopIfTrue`, no further rules are evaluated for that element.
|
|
32
|
+
* Style properties from higher-priority rules override lower-priority ones.
|
|
33
|
+
*/
|
|
34
|
+
export declare function evaluateConditionalFormats(rules: ConditionalFormatRule[], ctx: BindingContext, elementIds: string[]): ConditionalFormatResult[];
|
|
35
|
+
/**
|
|
36
|
+
* Filter rules that apply to a specific band, expanding band-level
|
|
37
|
+
* rules to all element IDs in that band.
|
|
38
|
+
*
|
|
39
|
+
* Use this to pre-filter rules before calling evaluateConditionalFormats
|
|
40
|
+
* so that band-level targeting works correctly.
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveRulesForBand(rules: ConditionalFormatRule[], bandId: string, bandElementIds: string[]): ConditionalFormatRule[];
|
|
43
|
+
/**
|
|
44
|
+
* Apply conditional format results to an element's existing style.
|
|
45
|
+
* Returns a new style object with the conditional styles merged on top.
|
|
46
|
+
*/
|
|
47
|
+
export declare function applyConditionalStyle(baseStyle: Partial<ElementStyle> | undefined, results: ConditionalFormatResult[], elementId: string): Partial<ElementStyle>;
|
|
48
|
+
//# sourceMappingURL=conditional-format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conditional-format.d.ts","sourceRoot":"","sources":["../../src/engine/conditional-format.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAKxD,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,iDAAiD;IACjD,eAAe,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,iEAAiE;IACjE,SAAS,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IAC7B,sEAAsE;IACtE,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,aAAa,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IACrC,0DAA0D;IAC1D,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAID;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,qBAAqB,EAAE,EAC9B,GAAG,EAAE,cAAc,EACnB,UAAU,EAAE,MAAM,EAAE,GACnB,uBAAuB,EAAE,CAqD3B;AAkED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,qBAAqB,EAAE,EAC9B,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,EAAE,GACvB,qBAAqB,EAAE,CAyBzB;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,SAAS,EAC5C,OAAO,EAAE,uBAAuB,EAAE,EAClC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,YAAY,CAAC,CAcvB"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// @zentto/report-core — Conditional formatting engine
|
|
2
|
+
// Crystal Reports-style conditional formatting with priority rules
|
|
3
|
+
import { evaluateExpression } from './expression.js';
|
|
4
|
+
// ─── Core evaluation ────────────────────────────────────────────────
|
|
5
|
+
/**
|
|
6
|
+
* Evaluate all conditional format rules against the current context
|
|
7
|
+
* and return the computed styles for the given element IDs.
|
|
8
|
+
*
|
|
9
|
+
* Rules are sorted by priority (lowest number = highest priority).
|
|
10
|
+
* For each element, matching rules are applied in priority order.
|
|
11
|
+
* If a rule has `stopIfTrue`, no further rules are evaluated for that element.
|
|
12
|
+
* Style properties from higher-priority rules override lower-priority ones.
|
|
13
|
+
*/
|
|
14
|
+
export function evaluateConditionalFormats(rules, ctx, elementIds) {
|
|
15
|
+
if (rules.length === 0 || elementIds.length === 0)
|
|
16
|
+
return [];
|
|
17
|
+
// Sort rules by priority ascending (lower number = higher priority)
|
|
18
|
+
const sorted = [...rules].sort((a, b) => a.priority - b.priority);
|
|
19
|
+
// Pre-evaluate all rule conditions once (each condition is the same
|
|
20
|
+
// regardless of which element it targets)
|
|
21
|
+
const conditionCache = new Map();
|
|
22
|
+
for (const rule of sorted) {
|
|
23
|
+
if (!conditionCache.has(rule.id)) {
|
|
24
|
+
conditionCache.set(rule.id, evaluateCondition(rule.condition, ctx));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Build results per element
|
|
28
|
+
const results = [];
|
|
29
|
+
for (const elementId of elementIds) {
|
|
30
|
+
const triggeredRules = [];
|
|
31
|
+
// Collect styles from lowest priority to highest so that
|
|
32
|
+
// higher-priority properties overwrite lower ones in the final merge.
|
|
33
|
+
// We iterate highest-priority first and track which props are already set.
|
|
34
|
+
const computedStyle = {};
|
|
35
|
+
const setProps = new Set();
|
|
36
|
+
let stopped = false;
|
|
37
|
+
for (const rule of sorted) {
|
|
38
|
+
if (stopped)
|
|
39
|
+
break;
|
|
40
|
+
// Check if this rule targets the current element
|
|
41
|
+
if (!ruleTargetsElement(rule, elementId))
|
|
42
|
+
continue;
|
|
43
|
+
// Check if condition passed
|
|
44
|
+
const passed = conditionCache.get(rule.id) ?? false;
|
|
45
|
+
if (!passed)
|
|
46
|
+
continue;
|
|
47
|
+
// Rule matched — merge styles (higher-priority props win)
|
|
48
|
+
triggeredRules.push(rule.id);
|
|
49
|
+
mergeStyle(computedStyle, rule.style, setProps);
|
|
50
|
+
if (rule.stopIfTrue) {
|
|
51
|
+
stopped = true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Only include elements that had at least one triggered rule
|
|
55
|
+
if (triggeredRules.length > 0) {
|
|
56
|
+
results.push({ elementId, computedStyle, triggeredRules });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return results;
|
|
60
|
+
}
|
|
61
|
+
// ─── Helpers ────────────────────────────────────────────────────────
|
|
62
|
+
/** Check if a rule targets a specific element ID */
|
|
63
|
+
function ruleTargetsElement(rule, elementId) {
|
|
64
|
+
// Wildcard — targets every element
|
|
65
|
+
if (rule.targetElementId === '*')
|
|
66
|
+
return true;
|
|
67
|
+
// Direct element match
|
|
68
|
+
if (rule.targetElementId === elementId)
|
|
69
|
+
return true;
|
|
70
|
+
// Band-level rule: targetBandId is set and targetElementId is '*'
|
|
71
|
+
// The caller is responsible for passing only the element IDs that
|
|
72
|
+
// belong to the band. When targetBandId is set with a specific
|
|
73
|
+
// targetElementId, we still match only that element.
|
|
74
|
+
// Band-level filtering (which elements belong to which band) is
|
|
75
|
+
// handled externally; here we just match the element ID.
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
/** Evaluate a condition expression against the binding context */
|
|
79
|
+
function evaluateCondition(condition, ctx) {
|
|
80
|
+
if (!condition || condition.trim() === '')
|
|
81
|
+
return false;
|
|
82
|
+
// Normalize: ensure the expression starts with '=' for the expression engine
|
|
83
|
+
const expr = condition.startsWith('=') ? condition : `=${condition}`;
|
|
84
|
+
try {
|
|
85
|
+
const result = evaluateExpression(expr, ctx);
|
|
86
|
+
// Handle various truthy evaluations
|
|
87
|
+
if (typeof result === 'boolean')
|
|
88
|
+
return result;
|
|
89
|
+
if (typeof result === 'number')
|
|
90
|
+
return result !== 0;
|
|
91
|
+
if (typeof result === 'string') {
|
|
92
|
+
if (result === 'true')
|
|
93
|
+
return true;
|
|
94
|
+
if (result === 'false' || result === '' || result.startsWith('#ERR'))
|
|
95
|
+
return false;
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
return !!result;
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Merge source style into target, only setting properties that
|
|
106
|
+
* have not already been set by a higher-priority rule.
|
|
107
|
+
*/
|
|
108
|
+
function mergeStyle(target, source, setProps) {
|
|
109
|
+
for (const [key, value] of Object.entries(source)) {
|
|
110
|
+
if (value === undefined)
|
|
111
|
+
continue;
|
|
112
|
+
if (!setProps.has(key)) {
|
|
113
|
+
target[key] = value;
|
|
114
|
+
setProps.add(key);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// ─── Utility: resolve rules for a band ─────────────────────────────
|
|
119
|
+
/**
|
|
120
|
+
* Filter rules that apply to a specific band, expanding band-level
|
|
121
|
+
* rules to all element IDs in that band.
|
|
122
|
+
*
|
|
123
|
+
* Use this to pre-filter rules before calling evaluateConditionalFormats
|
|
124
|
+
* so that band-level targeting works correctly.
|
|
125
|
+
*/
|
|
126
|
+
export function resolveRulesForBand(rules, bandId, bandElementIds) {
|
|
127
|
+
const result = [];
|
|
128
|
+
for (const rule of rules) {
|
|
129
|
+
// Rule explicitly targets this band
|
|
130
|
+
if (rule.targetBandId === bandId) {
|
|
131
|
+
if (rule.targetElementId === '*') {
|
|
132
|
+
// Expand to one rule per element in the band
|
|
133
|
+
for (const elementId of bandElementIds) {
|
|
134
|
+
result.push({ ...rule, targetElementId: elementId });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
// Specific element within this band
|
|
139
|
+
result.push(rule);
|
|
140
|
+
}
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
// No targetBandId — global rule (wildcard or specific element)
|
|
144
|
+
if (!rule.targetBandId) {
|
|
145
|
+
result.push(rule);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Apply conditional format results to an element's existing style.
|
|
152
|
+
* Returns a new style object with the conditional styles merged on top.
|
|
153
|
+
*/
|
|
154
|
+
export function applyConditionalStyle(baseStyle, results, elementId) {
|
|
155
|
+
const result = { ...(baseStyle ?? {}) };
|
|
156
|
+
const match = results.find(r => r.elementId === elementId);
|
|
157
|
+
if (!match)
|
|
158
|
+
return result;
|
|
159
|
+
// Conditional styles override base styles
|
|
160
|
+
for (const [key, value] of Object.entries(match.computedStyle)) {
|
|
161
|
+
if (value !== undefined) {
|
|
162
|
+
result[key] = value;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=conditional-format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conditional-format.js","sourceRoot":"","sources":["../../src/engine/conditional-format.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { ElementStyle } from '../types.js';
|
|
2
|
+
export interface CrossTabDef {
|
|
3
|
+
id: string;
|
|
4
|
+
dataSource: string;
|
|
5
|
+
/** Fields that define rows */
|
|
6
|
+
rowFields: CrossTabField[];
|
|
7
|
+
/** Fields that define columns (dynamic) */
|
|
8
|
+
columnFields: CrossTabField[];
|
|
9
|
+
/** Summary field and function */
|
|
10
|
+
summaryFields: CrossTabSummary[];
|
|
11
|
+
/** Show row totals */
|
|
12
|
+
showRowTotals: boolean;
|
|
13
|
+
/** Show column totals */
|
|
14
|
+
showColumnTotals: boolean;
|
|
15
|
+
/** Show grand total */
|
|
16
|
+
showGrandTotal: boolean;
|
|
17
|
+
/** Sort row values */
|
|
18
|
+
rowSort?: 'asc' | 'desc' | 'none';
|
|
19
|
+
/** Sort column values */
|
|
20
|
+
columnSort?: 'asc' | 'desc' | 'none';
|
|
21
|
+
/** Style for header cells */
|
|
22
|
+
headerStyle?: Partial<ElementStyle>;
|
|
23
|
+
/** Style for data cells */
|
|
24
|
+
dataStyle?: Partial<ElementStyle>;
|
|
25
|
+
/** Style for total cells */
|
|
26
|
+
totalStyle?: Partial<ElementStyle>;
|
|
27
|
+
/** Show values as percentage of total */
|
|
28
|
+
showAsPercentage?: boolean;
|
|
29
|
+
/** Suppress empty rows */
|
|
30
|
+
suppressEmptyRows?: boolean;
|
|
31
|
+
/** Suppress empty columns */
|
|
32
|
+
suppressEmptyColumns?: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface CrossTabField {
|
|
35
|
+
field: string;
|
|
36
|
+
label?: string;
|
|
37
|
+
groupBy?: 'value' | 'year' | 'quarter' | 'month' | 'week' | 'day';
|
|
38
|
+
}
|
|
39
|
+
export interface CrossTabSummary {
|
|
40
|
+
field: string;
|
|
41
|
+
function: 'sum' | 'avg' | 'count' | 'min' | 'max' | 'distinctCount';
|
|
42
|
+
label?: string;
|
|
43
|
+
format?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface CrossTabResult {
|
|
46
|
+
/** Unique row keys — each entry is an array of grouped values (one per rowField) */
|
|
47
|
+
rowKeys: string[][];
|
|
48
|
+
/** Unique column keys — each entry is an array of grouped values (one per columnField) */
|
|
49
|
+
columnKeys: string[][];
|
|
50
|
+
/** Cell values: [rowIndex][colIndex][summaryIndex] */
|
|
51
|
+
cells: (number | null)[][][];
|
|
52
|
+
/** Row totals: [rowIndex][summaryIndex] */
|
|
53
|
+
rowTotals: number[][];
|
|
54
|
+
/** Column totals: [colIndex][summaryIndex] */
|
|
55
|
+
columnTotals: number[][];
|
|
56
|
+
/** Grand totals: [summaryIndex] */
|
|
57
|
+
grandTotals: number[];
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Process raw data into a CrossTabResult structure.
|
|
61
|
+
* This is the main cross-tab computation engine.
|
|
62
|
+
*/
|
|
63
|
+
export declare function buildCrossTab(def: CrossTabDef, data: Record<string, unknown>[]): CrossTabResult;
|
|
64
|
+
/**
|
|
65
|
+
* Render a CrossTabResult as an HTML table.
|
|
66
|
+
*/
|
|
67
|
+
export declare function renderCrossTabHtml(def: CrossTabDef, result: CrossTabResult, prefix?: string): string;
|
|
68
|
+
//# sourceMappingURL=cross-tab.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cross-tab.d.ts","sourceRoot":"","sources":["../../src/engine/cross-tab.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIhD,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,8BAA8B;IAC9B,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,2CAA2C;IAC3C,YAAY,EAAE,aAAa,EAAE,CAAC;IAC9B,iCAAiC;IACjC,aAAa,EAAE,eAAe,EAAE,CAAC;IACjC,sBAAsB;IACtB,aAAa,EAAE,OAAO,CAAC;IACvB,yBAAyB;IACzB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,uBAAuB;IACvB,cAAc,EAAE,OAAO,CAAC;IACxB,sBAAsB;IACtB,OAAO,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;IAClC,yBAAyB;IACzB,UAAU,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;IACrC,6BAA6B;IAC7B,WAAW,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IACpC,2BAA2B;IAC3B,SAAS,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IAClC,4BAA4B;IAC5B,UAAU,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IACnC,yCAAyC;IACzC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,0BAA0B;IAC1B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,6BAA6B;IAC7B,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;CACnE;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,KAAK,GAAG,KAAK,GAAG,OAAO,GAAG,KAAK,GAAG,KAAK,GAAG,eAAe,CAAC;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,oFAAoF;IACpF,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC;IACpB,0FAA0F;IAC1F,UAAU,EAAE,MAAM,EAAE,EAAE,CAAC;IACvB,sDAAsD;IACtD,KAAK,EAAE,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC;IAC7B,2CAA2C;IAC3C,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC;IACtB,8CAA8C;IAC9C,YAAY,EAAE,MAAM,EAAE,EAAE,CAAC;IACzB,mCAAmC;IACnC,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAgHD;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,WAAW,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAC9B,cAAc,CAyNhB;AAyBD;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,WAAW,EAChB,MAAM,EAAE,cAAc,EACtB,MAAM,GAAE,MAAa,GACpB,MAAM,CAsLR"}
|