bways-report 0.2.0 → 0.2.2
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 +64 -0
- package/esm2022/lib/core/config/bways-report-config.mjs +1 -1
- package/esm2022/lib/core/models/report-grid.model.mjs +1 -1
- package/esm2022/lib/core/services/report-overlay.service.mjs +79 -0
- package/esm2022/lib/core/services/report-session-context.service.mjs +1 -44
- package/esm2022/lib/core/utils/report-parameter.util.mjs +81 -16
- package/esm2022/lib/data-access/adapters/endpoint-datasource.adapter.mjs +10 -2
- package/esm2022/lib/data-access/adapters/endpoint-lookup.adapter.mjs +10 -1
- package/esm2022/lib/data-access/adapters/http-datasource.adapter.mjs +10 -1
- package/esm2022/lib/data-access/adapters/http-lookup.adapter.mjs +10 -1
- package/esm2022/lib/features/designer/components/design-canvas/design-canvas.component.mjs +6 -4
- package/esm2022/lib/features/designer/components/dialogs/report-saved-dialog.component.mjs +11 -10
- package/esm2022/lib/features/designer/components/property-editor/parameter-binding-editor.component.mjs +93 -7
- package/esm2022/lib/features/designer/components/property-editor/property-editor-datasource.component.mjs +9 -7
- package/esm2022/lib/features/designer/components/property-editor/property-editor-grid.component.mjs +9 -16
- package/esm2022/lib/features/designer/components/property-editor/property-editor.component.mjs +5 -3
- package/esm2022/lib/features/designer/components/toolbar/designer-toolbar.component.mjs +11 -11
- package/esm2022/lib/features/designer/designer.component.mjs +12 -7
- package/esm2022/lib/features/designer/services/designer-state.service.mjs +5 -3
- package/esm2022/lib/features/runtime/dynamic-renderer.component.mjs +2 -2
- package/esm2022/lib/features/runtime/report-execution.service.mjs +35 -4
- package/esm2022/lib/features/runtime/report-grid-bridge.component.mjs +15 -20
- package/esm2022/lib/primitives/components/dropdown/rpt-dropdown.component.mjs +22 -11
- package/esm2022/lib/primitives/components/radio/rpt-radio.component.mjs +15 -5
- package/esm2022/lib/primitives/components/searchable-select/rpt-searchable-select.component.mjs +6 -6
- package/fesm2022/bways-report.mjs +414 -165
- package/fesm2022/bways-report.mjs.map +1 -1
- package/lib/core/config/bways-report-config.d.ts +3 -0
- package/lib/core/models/report-grid.model.d.ts +1 -1
- package/lib/core/services/report-overlay.service.d.ts +14 -0
- package/lib/core/services/report-session-context.service.d.ts +0 -5
- package/lib/core/utils/report-parameter.util.d.ts +15 -1
- package/lib/features/designer/components/property-editor/parameter-binding-editor.component.d.ts +12 -0
- package/lib/features/designer/components/property-editor/property-editor-datasource.component.d.ts +1 -0
- package/lib/features/designer/designer.component.d.ts +1 -0
- package/lib/features/runtime/report-grid-bridge.component.d.ts +2 -2
- package/lib/primitives/components/dropdown/rpt-dropdown.component.d.ts +1 -2
- package/lib/primitives/components/searchable-select/rpt-searchable-select.component.d.ts +0 -1
- package/package.json +8 -5
- package/styles/theme.css +181 -0
- package/third-party-notices/angular-cdk-LICENSE.txt +21 -0
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Injectable, InjectionToken, inject, Injector, signal, computed, runInInjectionContext, input, Component, ChangeDetectionStrategy, ElementRef, output, viewChild, effect, untracked, HostListener, forwardRef, ChangeDetectorRef, DestroyRef, ViewContainerRef, Directive, Input, ENVIRONMENT_INITIALIZER, makeEnvironmentProviders } from '@angular/core';
|
|
2
|
+
import { Injectable, InjectionToken, inject, Injector, signal, computed, runInInjectionContext, input, Component, ChangeDetectionStrategy, CSP_NONCE, NgZone, ElementRef, output, viewChild, effect, untracked, HostListener, forwardRef, ChangeDetectorRef, DestroyRef, ViewContainerRef, Directive, Input, ENVIRONMENT_INITIALIZER, makeEnvironmentProviders } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/forms';
|
|
4
4
|
import { Validators, FormControl, FormGroup, ReactiveFormsModule, NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms';
|
|
5
5
|
import { toObservable, takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
6
6
|
import { isObservable, throwError, of, map as map$1, catchError as catchError$1, forkJoin, merge, EMPTY, defer, take, timeout as timeout$1, finalize as finalize$1, tap as tap$1, switchMap } from 'rxjs';
|
|
7
7
|
import { timeout, map, catchError, tap, finalize, shareReplay } from 'rxjs/operators';
|
|
8
8
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
|
9
|
+
import * as i1$1 from '@angular/common';
|
|
10
|
+
import { DOCUMENT, CommonModule } from '@angular/common';
|
|
9
11
|
import * as i2 from '@angular/cdk/overlay';
|
|
10
12
|
import { Overlay, OverlayModule } from '@angular/cdk/overlay';
|
|
11
|
-
import
|
|
12
|
-
import { CommonModule, DOCUMENT } from '@angular/common';
|
|
13
|
+
import { ScrollDispatcher, CdkScrollable } from '@angular/cdk/scrolling';
|
|
13
14
|
import * as i1$3 from '@angular/router';
|
|
14
15
|
import { Router, ActivatedRoute, RouterModule } from '@angular/router';
|
|
15
16
|
import { DialogRef, DIALOG_DATA, Dialog, DialogModule } from '@angular/cdk/dialog';
|
|
@@ -753,9 +754,7 @@ class ReportSessionContextService {
|
|
|
753
754
|
}
|
|
754
755
|
}
|
|
755
756
|
const manualMap = this.manualRuntimeContext();
|
|
756
|
-
const autoDetected = this.detectBrowserSession();
|
|
757
757
|
return {
|
|
758
|
-
...autoDetected,
|
|
759
758
|
...providerMap,
|
|
760
759
|
...tokenMap,
|
|
761
760
|
...manualMap,
|
|
@@ -920,47 +919,6 @@ class ReportSessionContextService {
|
|
|
920
919
|
return {};
|
|
921
920
|
}
|
|
922
921
|
}
|
|
923
|
-
/**
|
|
924
|
-
* Safely detects session parameters from host project browser storage
|
|
925
|
-
* (sessionStorage: user_Info, currentUser; localStorage: currentUser, CompanyData, user_Info).
|
|
926
|
-
*/
|
|
927
|
-
detectBrowserSession() {
|
|
928
|
-
const result = {};
|
|
929
|
-
if (typeof window === 'undefined')
|
|
930
|
-
return result;
|
|
931
|
-
const sources = [
|
|
932
|
-
{ storage: typeof sessionStorage !== 'undefined' ? sessionStorage : null, key: 'user_Info' },
|
|
933
|
-
{ storage: typeof localStorage !== 'undefined' ? localStorage : null, key: 'currentUser' },
|
|
934
|
-
{ storage: typeof localStorage !== 'undefined' ? localStorage : null, key: 'CompanyData' },
|
|
935
|
-
{ storage: typeof sessionStorage !== 'undefined' ? sessionStorage : null, key: 'currentUser' },
|
|
936
|
-
{ storage: typeof localStorage !== 'undefined' ? localStorage : null, key: 'user_Info' },
|
|
937
|
-
];
|
|
938
|
-
for (const src of sources) {
|
|
939
|
-
if (!src.storage)
|
|
940
|
-
continue;
|
|
941
|
-
try {
|
|
942
|
-
const item = src.storage.getItem(src.key);
|
|
943
|
-
if (item) {
|
|
944
|
-
const parsed = JSON.parse(item);
|
|
945
|
-
if (parsed && typeof parsed === 'object') {
|
|
946
|
-
for (const [k, v] of Object.entries(parsed)) {
|
|
947
|
-
if (v !== undefined &&
|
|
948
|
-
v !== null &&
|
|
949
|
-
(typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean')) {
|
|
950
|
-
if (!(k in result)) {
|
|
951
|
-
result[k] = v;
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
catch {
|
|
959
|
-
// Ignore JSON parse errors in storage
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
return result;
|
|
963
|
-
}
|
|
964
922
|
/**
|
|
965
923
|
* Normalizes a parameter or session key into a lowercase alphanumeric string
|
|
966
924
|
* for reliable, case-insensitive, and symbol-insensitive matching.
|
|
@@ -1520,6 +1478,40 @@ function extractLookupOptionItem(row, valueField, displayField) {
|
|
|
1520
1478
|
}
|
|
1521
1479
|
|
|
1522
1480
|
const TOKEN_RE = /^\{\{\s*(.+?)\s*\}\}$/;
|
|
1481
|
+
/** Supported wire formats for date-bound procedure/API parameters. */
|
|
1482
|
+
const PARAMETER_DATE_FORMATS = ['yyyy-MM-dd', 'yyyyMMdd', 'dd/MM/yyyy', 'MM/dd/yyyy', 'dd-MM-yyyy'];
|
|
1483
|
+
/** The optional date pipe is persisted in existing string mappings. */
|
|
1484
|
+
function parseParameterToken(raw) {
|
|
1485
|
+
const match = TOKEN_RE.exec(raw);
|
|
1486
|
+
if (!match)
|
|
1487
|
+
return null;
|
|
1488
|
+
const parts = match[1].split(/\s*\|\s*date\s*:\s*/);
|
|
1489
|
+
if (parts.length === 1)
|
|
1490
|
+
return { field: parts[0].trim() };
|
|
1491
|
+
const format = parts[1];
|
|
1492
|
+
return { field: parts[0].trim(), dateFormat: parts.length === 2 ? format : parts.slice(1).join(' | date:') };
|
|
1493
|
+
}
|
|
1494
|
+
/** Format calendar parts directly, avoiding UTC/time-zone shifts and locale parsing. */
|
|
1495
|
+
function formatParameterDate(value, format) {
|
|
1496
|
+
if (!PARAMETER_DATE_FORMATS.includes(format))
|
|
1497
|
+
throw new Error('Unsupported parameter date format. Choose a date format in the Field binding.');
|
|
1498
|
+
if (value === '' || value === null || value === undefined)
|
|
1499
|
+
return '';
|
|
1500
|
+
const raw = value instanceof Date
|
|
1501
|
+
? value.getFullYear() + '-' + String(value.getMonth() + 1).padStart(2, '0') + '-' + String(value.getDate()).padStart(2, '0')
|
|
1502
|
+
: String(value);
|
|
1503
|
+
const match = /^(\d{4})-?(\d{2})-?(\d{2})(?:T.*)?$/.exec(raw);
|
|
1504
|
+
if (!match)
|
|
1505
|
+
throw new Error('Invalid date. Select a valid date in the linked field.');
|
|
1506
|
+
const [, year, month, day] = match;
|
|
1507
|
+
const probe = new Date(0);
|
|
1508
|
+
probe.setFullYear(+year, +month - 1, +day);
|
|
1509
|
+
probe.setHours(0, 0, 0, 0);
|
|
1510
|
+
if (probe.getFullYear() !== +year || probe.getMonth() + 1 !== +month || probe.getDate() !== +day) {
|
|
1511
|
+
throw new Error('Invalid date. Select a valid date in the linked field.');
|
|
1512
|
+
}
|
|
1513
|
+
return format.replace('yyyy', year).replace('MM', month).replace('dd', day);
|
|
1514
|
+
}
|
|
1523
1515
|
const pad$1 = (n) => String(n).padStart(2, '0');
|
|
1524
1516
|
const formatYMD$1 = (d) => `${d.getFullYear()}-${pad$1(d.getMonth() + 1)}-${pad$1(d.getDate())}`;
|
|
1525
1517
|
/** Resolves `{{context.xxx}}` presets such as today / startOfMonth. */
|
|
@@ -1722,31 +1714,62 @@ function parseSqlExecStatement(sql) {
|
|
|
1722
1714
|
function resolveParameterValue(raw, session, formValues) {
|
|
1723
1715
|
if (typeof raw !== 'string')
|
|
1724
1716
|
return raw;
|
|
1725
|
-
const
|
|
1726
|
-
if (!
|
|
1717
|
+
const token = parseParameterToken(raw);
|
|
1718
|
+
if (!token)
|
|
1727
1719
|
return sanitizeSqlParamLiteral(raw);
|
|
1728
|
-
const inner =
|
|
1720
|
+
const inner = token.field;
|
|
1721
|
+
const format = (value) => token.dateFormat ? formatParameterDate(value, token.dateFormat) : value;
|
|
1729
1722
|
if (inner.startsWith('session.')) {
|
|
1730
1723
|
const sessionKey = inner.slice(8);
|
|
1731
1724
|
const val = getFieldCaseInsensitive(session, sessionKey);
|
|
1732
|
-
return val !== undefined && val !== null ? val : '';
|
|
1725
|
+
return format(val !== undefined && val !== null ? val : '');
|
|
1733
1726
|
}
|
|
1734
1727
|
if (inner.startsWith('context.')) {
|
|
1735
|
-
return resolveContextPreset(inner.slice(8));
|
|
1728
|
+
return format(resolveContextPreset(inner.slice(8)));
|
|
1736
1729
|
}
|
|
1737
1730
|
const v = resolveCanvasFieldValue(formValues, inner);
|
|
1738
|
-
return v !== undefined && v !== null ? v : '';
|
|
1731
|
+
return format(v !== undefined && v !== null ? v : '');
|
|
1732
|
+
}
|
|
1733
|
+
/** Replace a single mapping and remove all stale aliases for the same argument. */
|
|
1734
|
+
function setParameterMapping(mappings, name, value) {
|
|
1735
|
+
const key = name.replace(/^@+/, '');
|
|
1736
|
+
if (!key)
|
|
1737
|
+
throw new Error('A parameter name cannot be empty.');
|
|
1738
|
+
const entries = Object.entries(mappings ?? {}).filter(([existing]) => existing.replace(/^@+/, '').toLowerCase() !== key.toLowerCase());
|
|
1739
|
+
return Object.fromEntries([...entries, [key, value]]);
|
|
1739
1740
|
}
|
|
1740
|
-
/**
|
|
1741
|
+
/** Canonical SQL argument names. Equal aliases collapse; conflicting values fail explicitly. */
|
|
1742
|
+
function normalizeParameterNames(parameters) {
|
|
1743
|
+
const out = {};
|
|
1744
|
+
const names = new Map();
|
|
1745
|
+
for (const [rawKey, value] of Object.entries(parameters)) {
|
|
1746
|
+
const key = rawKey.replace(/^@+/, '');
|
|
1747
|
+
if (!key)
|
|
1748
|
+
throw new Error('A parameter name cannot be empty.');
|
|
1749
|
+
const existing = names.get(key.toLowerCase());
|
|
1750
|
+
if (existing !== undefined) {
|
|
1751
|
+
if (!Object.is(out[existing], value) && JSON.stringify(out[existing]) !== JSON.stringify(value)) {
|
|
1752
|
+
throw new Error('Conflicting values for parameter "' + existing + '". Keep one mapping for this parameter.');
|
|
1753
|
+
}
|
|
1754
|
+
continue;
|
|
1755
|
+
}
|
|
1756
|
+
names.set(key.toLowerCase(), key);
|
|
1757
|
+
Object.defineProperty(out, key, { value, enumerable: true, configurable: true, writable: true });
|
|
1758
|
+
}
|
|
1759
|
+
return out;
|
|
1760
|
+
}
|
|
1761
|
+
/** Resolves mappings into one concrete value per parameter, without @ aliases. */
|
|
1741
1762
|
function resolveParameterMappings(mappings, session, formValues) {
|
|
1742
1763
|
const out = {};
|
|
1743
1764
|
for (const [key, raw] of Object.entries(mappings ?? {})) {
|
|
1765
|
+
const token = typeof raw === 'string' ? parseParameterToken(raw) : null;
|
|
1766
|
+
// An unavailable session is not an empty SQL argument. Preserve explicit 0/false values.
|
|
1767
|
+
if (token?.field.startsWith('session.') && getFieldCaseInsensitive(session, token.field.slice(8)) == null)
|
|
1768
|
+
continue;
|
|
1744
1769
|
const val = resolveParameterValue(raw, session, formValues);
|
|
1745
|
-
|
|
1746
|
-
out[cleanKey] = val;
|
|
1747
|
-
out[`@${cleanKey}`] = val;
|
|
1770
|
+
Object.defineProperty(out, key, { value: val, enumerable: true });
|
|
1748
1771
|
}
|
|
1749
|
-
return out;
|
|
1772
|
+
return normalizeParameterNames(out);
|
|
1750
1773
|
}
|
|
1751
1774
|
/**
|
|
1752
1775
|
* Returns canvas field keys referenced by mappings that currently have no value.
|
|
@@ -1757,10 +1780,10 @@ function findMissingCanvasFields(mappings, formValues) {
|
|
|
1757
1780
|
for (const raw of Object.values(mappings ?? {})) {
|
|
1758
1781
|
if (typeof raw !== 'string')
|
|
1759
1782
|
continue;
|
|
1760
|
-
const
|
|
1761
|
-
if (!
|
|
1783
|
+
const token = parseParameterToken(raw);
|
|
1784
|
+
if (!token)
|
|
1762
1785
|
continue;
|
|
1763
|
-
const inner =
|
|
1786
|
+
const inner = token.field;
|
|
1764
1787
|
if (inner.startsWith('session.') || inner.startsWith('context.'))
|
|
1765
1788
|
continue;
|
|
1766
1789
|
const v = resolveCanvasFieldValue(formValues, inner);
|
|
@@ -1784,7 +1807,7 @@ function findMissingCanvasFields(mappings, formValues) {
|
|
|
1784
1807
|
*/
|
|
1785
1808
|
function resolveExecutionDataSource(def) {
|
|
1786
1809
|
const sources = def.dataSources ?? [];
|
|
1787
|
-
const byId = sources.find((d) => d.id === def.grid?.dataSourceId);
|
|
1810
|
+
const byId = sources.find((d) => d.id === def.grid?.dataSourceId || d.name === def.grid?.dataSourceId);
|
|
1788
1811
|
if (byId)
|
|
1789
1812
|
return byId;
|
|
1790
1813
|
if (sources.length > 0)
|
|
@@ -2256,6 +2279,14 @@ class HttpReportLookupAdapter extends ReportLookupAdapter {
|
|
|
2256
2279
|
cache = new Map();
|
|
2257
2280
|
inFlight = new Map();
|
|
2258
2281
|
fetchOptions(request) {
|
|
2282
|
+
if (request.sourceType === 'procedure') {
|
|
2283
|
+
try {
|
|
2284
|
+
request = { ...request, parameters: normalizeParameterNames(request.parameters ?? {}) };
|
|
2285
|
+
}
|
|
2286
|
+
catch (error) {
|
|
2287
|
+
return throwError(() => error);
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2259
2290
|
const cacheKey = this.generateCacheKey(request);
|
|
2260
2291
|
const cached = this.cache.get(cacheKey);
|
|
2261
2292
|
const now = Date.now();
|
|
@@ -2343,6 +2374,14 @@ class HttpReportDataSourceAdapter extends ReportDataSourceAdapter {
|
|
|
2343
2374
|
config = inject(REPORTING_CONFIG, { optional: true });
|
|
2344
2375
|
sessionService = inject(ReportSessionContextService, { optional: true });
|
|
2345
2376
|
execute(dataSource, parameters) {
|
|
2377
|
+
if (dataSource.method === 'STORED_PROCEDURE' || dataSource.type === 'stored_procedure' || dataSource.type === 'procedure' || !dataSource.type) {
|
|
2378
|
+
try {
|
|
2379
|
+
parameters = normalizeParameterNames(parameters);
|
|
2380
|
+
}
|
|
2381
|
+
catch (error) {
|
|
2382
|
+
return throwError(() => error);
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2346
2385
|
const baseUrl = this.config?.apiBaseUrl;
|
|
2347
2386
|
const session = this.sessionService?.sessionContext() ?? {};
|
|
2348
2387
|
if (baseUrl && this.http) {
|
|
@@ -3173,6 +3212,14 @@ class EndpointDataSourceAdapter extends ReportDataSourceAdapter {
|
|
|
3173
3212
|
headers = headers.set('X-Branch-Id', String(session['branchId']));
|
|
3174
3213
|
}
|
|
3175
3214
|
const kind = this.resolveKind(dataSource);
|
|
3215
|
+
if (kind === 'procedure') {
|
|
3216
|
+
try {
|
|
3217
|
+
parameters = normalizeParameterNames(parameters);
|
|
3218
|
+
}
|
|
3219
|
+
catch (error) {
|
|
3220
|
+
return throwError(() => error);
|
|
3221
|
+
}
|
|
3222
|
+
}
|
|
3176
3223
|
const payload = {
|
|
3177
3224
|
kind,
|
|
3178
3225
|
name: dataSource.endpoint || dataSource.name,
|
|
@@ -3227,13 +3274,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3227
3274
|
type: Injectable
|
|
3228
3275
|
}] });
|
|
3229
3276
|
|
|
3230
|
-
/**
|
|
3231
|
-
* Endpoint-based lookup adapter.
|
|
3232
|
-
*
|
|
3233
|
-
* Reuses the `executeEndpoint` to fetch dropdown option rows.
|
|
3234
|
-
* No separate lookup endpoint is needed — we just POST to the
|
|
3235
|
-
* same execute endpoint with the table/SP that backs the dropdown.
|
|
3236
|
-
*/
|
|
3237
3277
|
class EndpointLookupAdapter extends ReportLookupAdapter {
|
|
3238
3278
|
http = inject(HttpClient);
|
|
3239
3279
|
endpoints = inject(BWAYS_REPORT_ENDPOINTS);
|
|
@@ -3242,6 +3282,14 @@ class EndpointLookupAdapter extends ReportLookupAdapter {
|
|
|
3242
3282
|
cache = new Map();
|
|
3243
3283
|
inFlight = new Map();
|
|
3244
3284
|
fetchOptions(request) {
|
|
3285
|
+
if (request.sourceType === 'procedure') {
|
|
3286
|
+
try {
|
|
3287
|
+
request = { ...request, parameters: normalizeParameterNames(request.parameters ?? {}) };
|
|
3288
|
+
}
|
|
3289
|
+
catch (error) {
|
|
3290
|
+
return throwError(() => error);
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3245
3293
|
const cacheKey = this.generateCacheKey(request);
|
|
3246
3294
|
const cached = this.cache.get(cacheKey);
|
|
3247
3295
|
const now = Date.now();
|
|
@@ -3558,14 +3606,87 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
3558
3606
|
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;width:100%;height:100%;box-sizing:border-box;font-family:var(--rpt-font-family, inherit)}.rpt-field-group{display:flex;flex-direction:column;height:100%;gap:4px;box-sizing:border-box}.rpt-label{font-size:var(--rpt-font-size-sm, .75rem);font-weight:500;color:var(--rpt-color-text, #0f172a);flex-shrink:0}.rpt-required-marker{color:var(--rpt-color-danger, #ef4444);margin-left:2px}.rpt-input-wrapper{position:relative;width:100%;flex:1;min-height:0;display:flex}.rpt-input{width:100%;height:100%;min-height:38px;box-sizing:border-box;padding:6px 12px;font-size:var(--rpt-font-size-base, .875rem);color:var(--rpt-color-text, #0f172a);background-color:var(--rpt-color-bg, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);transition:border-color var(--rpt-transition-speed, .15s) ease,box-shadow var(--rpt-transition-speed, .15s) ease;outline:none}.rpt-input:hover:not(:disabled){border-color:var(--rpt-color-border-hover, #94a3b8)}.rpt-input:focus{border-color:var(--rpt-color-border-focus, #2563eb);box-shadow:var(--rpt-box-shadow-focus, 0 0 0 3px rgba(37, 99, 235, .25))}.rpt-input:disabled{background-color:var(--rpt-color-surface, #f8fafc);color:var(--rpt-color-text-muted, #64748b);cursor:not-allowed}.rpt-input.rpt-invalid{border-color:var(--rpt-color-danger, #ef4444)}.rpt-input.rpt-invalid:focus{box-shadow:0 0 0 3px #ef444440}.rpt-error-text{font-size:.7rem;color:var(--rpt-color-danger, #ef4444);margin-top:3px;line-height:1.25;max-width:100%;overflow-wrap:anywhere;word-break:break-word;display:block}\n"] }]
|
|
3559
3607
|
}] });
|
|
3560
3608
|
|
|
3609
|
+
/** CDK structural styles, embedded so standalone components need no host CSS import.
|
|
3610
|
+
* Compatible with CDK's overlay-prebuilt.css; no theme or !important overrides.
|
|
3611
|
+
* CDK structural CSS is MIT licensed; see third-party-notices/angular-cdk-LICENSE.txt.
|
|
3612
|
+
*/
|
|
3613
|
+
const OVERLAY_STYLES = ".cdk-overlay-container,.cdk-global-overlay-wrapper{pointer-events:none;top:0;left:0;height:100%;width:100%}.cdk-overlay-container{position:fixed;z-index:var(--rpt-overlay-z-index,1000)}.cdk-overlay-container:empty{display:none}.cdk-global-overlay-wrapper{display:flex;position:absolute;z-index:1000}.cdk-overlay-pane{position:absolute;pointer-events:auto;box-sizing:border-box;z-index:1000;display:flex;max-width:100%;max-height:100%}.cdk-overlay-backdrop{position:absolute;top:0;bottom:0;left:0;right:0;z-index:1000;pointer-events:auto;-webkit-tap-highlight-color:rgba(0,0,0,0);transition:opacity 400ms cubic-bezier(0.25, 0.8, 0.25, 1);opacity:0}.cdk-overlay-backdrop.cdk-overlay-backdrop-showing{opacity:1}.cdk-high-contrast-active .cdk-overlay-backdrop.cdk-overlay-backdrop-showing{opacity:.6}.cdk-overlay-dark-backdrop{background:rgba(0,0,0,.32)}.cdk-overlay-transparent-backdrop{transition:visibility 1ms linear,opacity 1ms linear;visibility:hidden;opacity:1}.cdk-overlay-transparent-backdrop.cdk-overlay-backdrop-showing{opacity:0;visibility:visible}.cdk-overlay-backdrop-noop-animation{transition:none}.cdk-overlay-connected-position-bounding-box{position:absolute;z-index:1000;display:flex;flex-direction:column;min-width:1px;min-height:1px}.cdk-global-scrollblock{position:fixed;width:100%;overflow-y:scroll}";
|
|
3614
|
+
// Library modal appearance must also work without the optional theme asset.
|
|
3615
|
+
const DIALOG_STYLES = '.rpt-dialog-backdrop{background-color:rgba(15,23,42,.48);backdrop-filter:blur(3px)}';
|
|
3616
|
+
const STYLE_ID = 'rpt-overlay-base-styles';
|
|
3617
|
+
class ReportOverlayService {
|
|
3618
|
+
document = inject(DOCUMENT);
|
|
3619
|
+
nonce = inject(CSP_NONCE, { optional: true });
|
|
3620
|
+
overlay = inject(Overlay);
|
|
3621
|
+
scrollDispatcher = inject(ScrollDispatcher);
|
|
3622
|
+
zone = inject(NgZone);
|
|
3623
|
+
ensureStyles() {
|
|
3624
|
+
if (this.document.getElementById(STYLE_ID))
|
|
3625
|
+
return;
|
|
3626
|
+
const style = this.document.createElement('style');
|
|
3627
|
+
style.id = STYLE_ID;
|
|
3628
|
+
if (this.nonce)
|
|
3629
|
+
style.setAttribute('nonce', this.nonce);
|
|
3630
|
+
style.textContent = OVERLAY_STYLES + DIALOG_STYLES;
|
|
3631
|
+
(this.document.head ?? this.document.documentElement).appendChild(style);
|
|
3632
|
+
}
|
|
3633
|
+
/** Retain CDK's scroll strategy; cover native, unregistered host ancestors too. */
|
|
3634
|
+
scrollStrategy(origin) {
|
|
3635
|
+
this.ensureStyles();
|
|
3636
|
+
const delegate = this.overlay.scrollStrategies.reposition({ scrollThrottle: 16 });
|
|
3637
|
+
const view = this.document.defaultView;
|
|
3638
|
+
let overlayRef;
|
|
3639
|
+
let frame;
|
|
3640
|
+
let enabled = false;
|
|
3641
|
+
const onScroll = (event) => {
|
|
3642
|
+
const target = event.target;
|
|
3643
|
+
if (!target || target.nodeType !== 1 || !target.contains(origin()))
|
|
3644
|
+
return;
|
|
3645
|
+
// Registered ancestors already notify the delegate. Menu-list scrolling
|
|
3646
|
+
// is excluded because the menu does not contain its trigger.
|
|
3647
|
+
if (this.scrollDispatcher.getAncestorScrollContainers(origin()).some(container => container.getElementRef().nativeElement === target))
|
|
3648
|
+
return;
|
|
3649
|
+
if (frame === undefined && view)
|
|
3650
|
+
frame = view.requestAnimationFrame(() => {
|
|
3651
|
+
frame = undefined;
|
|
3652
|
+
if (enabled && overlayRef?.hasAttached())
|
|
3653
|
+
overlayRef.updatePosition();
|
|
3654
|
+
});
|
|
3655
|
+
};
|
|
3656
|
+
return {
|
|
3657
|
+
attach: ref => { overlayRef = ref; delegate.attach(ref); },
|
|
3658
|
+
enable: () => {
|
|
3659
|
+
if (enabled)
|
|
3660
|
+
return;
|
|
3661
|
+
enabled = true;
|
|
3662
|
+
delegate.enable();
|
|
3663
|
+
this.zone.runOutsideAngular(() => this.document.addEventListener('scroll', onScroll, true));
|
|
3664
|
+
},
|
|
3665
|
+
disable: () => {
|
|
3666
|
+
enabled = false;
|
|
3667
|
+
delegate.disable();
|
|
3668
|
+
this.document.removeEventListener('scroll', onScroll, true);
|
|
3669
|
+
if (frame !== undefined && view)
|
|
3670
|
+
view.cancelAnimationFrame(frame);
|
|
3671
|
+
frame = undefined;
|
|
3672
|
+
},
|
|
3673
|
+
};
|
|
3674
|
+
}
|
|
3675
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ReportOverlayService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3676
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ReportOverlayService, providedIn: 'root' });
|
|
3677
|
+
}
|
|
3678
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ReportOverlayService, decorators: [{
|
|
3679
|
+
type: Injectable,
|
|
3680
|
+
args: [{ providedIn: 'root' }]
|
|
3681
|
+
}] });
|
|
3682
|
+
|
|
3561
3683
|
class RptDropdownComponent {
|
|
3562
3684
|
masterDataProvider = inject(MasterDataProvider, { optional: true });
|
|
3563
3685
|
lookupAdapter = inject(ReportLookupAdapter, { optional: true });
|
|
3564
3686
|
sessionService = inject(ReportSessionContextService, { optional: true });
|
|
3565
3687
|
elementRef = inject(ElementRef);
|
|
3566
3688
|
reportingConfig = inject(REPORTING_CONFIG, { optional: true });
|
|
3567
|
-
|
|
3568
|
-
scrollStrategy = this.overlay.scrollStrategies.reposition();
|
|
3689
|
+
scrollStrategy = inject(ReportOverlayService).scrollStrategy(() => this.elementRef.nativeElement);
|
|
3569
3690
|
triggerWidth = signal('auto');
|
|
3570
3691
|
overlayPositions = [
|
|
3571
3692
|
{
|
|
@@ -3985,10 +4106,21 @@ class RptDropdownComponent {
|
|
|
3985
4106
|
return;
|
|
3986
4107
|
}
|
|
3987
4108
|
// Stored procedures only receive explicit mappings so SQL Server doesn't reject unknown arguments.
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
4109
|
+
let resolvedParams;
|
|
4110
|
+
try {
|
|
4111
|
+
resolvedParams = {
|
|
4112
|
+
...(ds.sourceType === 'table' ? session : {}),
|
|
4113
|
+
...resolveParameterMappings(ds.parameterMappings, session, formValues),
|
|
4114
|
+
};
|
|
4115
|
+
}
|
|
4116
|
+
catch (error) {
|
|
4117
|
+
this.lookupSub?.unsubscribe();
|
|
4118
|
+
this.isLoading.set(false);
|
|
4119
|
+
this.dynamicOptions.set([]);
|
|
4120
|
+
this.loadError.set(error instanceof Error ? error.message : 'Invalid parameter value.');
|
|
4121
|
+
this.lastRequestKey = '';
|
|
4122
|
+
return;
|
|
4123
|
+
}
|
|
3992
4124
|
const request = {
|
|
3993
4125
|
sourceType: ds.sourceType ?? (isApi ? 'api' : 'table'),
|
|
3994
4126
|
entityName: ds.entityName ?? (isApi ? ds.apiEndpoint : ''),
|
|
@@ -4031,7 +4163,7 @@ class RptDropdownComponent {
|
|
|
4031
4163
|
const k = String(it.value);
|
|
4032
4164
|
if (!seen.has(k)) {
|
|
4033
4165
|
seen.add(k);
|
|
4034
|
-
deduped.push({ key:
|
|
4166
|
+
deduped.push({ key: it.value, label: it.label || k });
|
|
4035
4167
|
}
|
|
4036
4168
|
}
|
|
4037
4169
|
this.dynamicOptions.set(deduped);
|
|
@@ -4070,14 +4202,14 @@ class RptDropdownComponent {
|
|
|
4070
4202
|
}
|
|
4071
4203
|
}
|
|
4072
4204
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RptDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4073
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: RptDropdownComponent, isStandalone: true, selector: "rpt-dropdown", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null }, control: { classPropertyName: "control", publicName: "control", isSignal: true, isRequired: false, transformFunction: null }, parentForm: { classPropertyName: "parentForm", publicName: "parentForm", isSignal: true, isRequired: false, transformFunction: null }, showStatus: { classPropertyName: "showStatus", publicName: "showStatus", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dropdownClick: "dropdownClick" }, host: { listeners: { "document:click": "onDocumentClick($event)", "document:keydown.escape": "onEscape()" }, properties: { "class.is-open": "isOpen()", "class.opens-above": "opensAbove()" } }, viewQueries: [{ propertyName: "comboboxWrapperEl", first: true, predicate: ["comboboxWrapper"], descendants: true, isSignal: true }, { propertyName: "searchInputEl", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }, { propertyName: "optionsListEl", first: true, predicate: ["optionsListEl"], descendants: true, isSignal: true }], ngImport: i0, template: " <div class=\"rpt-field-group\">\n @if (config().label) {\n <label [for]=\"elementId()\" class=\"rpt-label\">\n {{ config().label }}\n @if (config().validation?.required) {\n <span class=\"rpt-required-marker\">*</span>\n }\n </label>\n }\n\n @if (isSearchable()) {\n <!-- Searchable Typeahead Combobox -->\n <div class=\"rpt-combobox-wrapper\" #comboboxWrapper #comboboxOrigin=\"cdkOverlayOrigin\" cdkOverlayOrigin>\n <button\n type=\"button\"\n class=\"rpt-combobox-trigger\"\n [id]=\"elementId()\"\n [class.rpt-invalid]=\"isInvalid()\"\n [class.is-open]=\"isOpen()\"\n [disabled]=\"formControl().disabled\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"'listbox'\"\n (click)=\"toggleOpen($event)\"\n (keydown)=\"onTriggerKeydown($event)\"\n >\n <span class=\"trigger-label\" [class.is-placeholder]=\"!selectedOption()\">\n {{ selectedOption() ? selectedOption()!.label : placeholder() }}\n </span>\n\n <div class=\"trigger-actions\">\n @if (isLoading()) {\n <span class=\"status-dot dot-pulse\" style=\"color: #2563eb; margin-right: 4px;\" title=\"Executing query on database...\"></span>\n }\n @if (loadError() && !isLoading()) {\n <button\n type=\"button\"\n class=\"trigger-retry-btn\"\n title=\"Retry database query\"\n aria-label=\"Retry database query\"\n (click)=\"retryLookup($event)\"\n >\n \u21BB Retry\n </button>\n }\n @if (selectedOption() && !formControl().disabled) {\n <span\n role=\"button\"\n tabindex=\"0\"\n class=\"trigger-clear-btn\"\n title=\"Clear selection\"\n aria-label=\"Clear selection\"\n (click)=\"clearSelection($event)\"\n (keydown.enter)=\"clearSelection($event)\"\n (keydown.space)=\"clearSelection($event)\"\n >\n \u2715\n </span>\n }\n <span class=\"trigger-arrow\" [class.rotated]=\"isOpen()\" aria-hidden=\"true\">\n <svg width=\"12\" height=\"8\" viewBox=\"0 0 12 8\" fill=\"none\">\n <path d=\"M1 1.5L6 6.5L11 1.5\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n </div>\n </button>\n </div>\n\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"comboboxOrigin\"\n [cdkConnectedOverlayOpen]=\"isOpen()\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayWidth]=\"triggerWidth()\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n [cdkConnectedOverlayViewportMargin]=\"8\"\n [cdkConnectedOverlayPush]=\"true\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n (overlayOutsideClick)=\"closeDropdown()\"\n (positionChange)=\"onPositionChange($event)\"\n (attach)=\"onOverlayAttached()\"\n (detach)=\"closeDropdown()\"\n >\n <div\n class=\"rpt-combobox-menu\"\n [class.opens-above]=\"opensAbove()\"\n role=\"listbox\"\n (click)=\"$event.stopPropagation()\"\n >\n <!-- Real-time Filter Search Box -->\n <div class=\"combobox-search-box\">\n <span class=\"combobox-search-icon\" aria-hidden=\"true\">\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"></circle>\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"></line>\n </svg>\n </span>\n <input\n #searchInput\n type=\"text\"\n class=\"combobox-search-input\"\n placeholder=\"Type to filter...\"\n [value]=\"searchTerm()\"\n [attr.aria-activedescendant]=\"focusedIndex() >= 0 ? (elementId() + '_opt_' + focusedIndex()) : null\"\n (input)=\"onSearchInput($event)\"\n (keydown)=\"onSearchKeydown($event)\"\n />\n @if (searchTerm()) {\n <button\n type=\"button\"\n class=\"combobox-search-clear\"\n title=\"Clear filter\"\n (click)=\"clearSearch($event)\"\n >\n \u2715\n </button>\n }\n </div>\n\n <!-- Filtered Options List -->\n <div class=\"combobox-options-list\" #optionsListEl (scroll)=\"onOptionsScroll($event)\">\n @if (waitingFor().length > 0) {\n <div class=\"combobox-status-row waiting\">\n <span>Waiting for {{ waitingLabel() }}...</span>\n </div>\n } @else if (isLoading()) {\n <div class=\"combobox-status-row loading\">\n <span class=\"status-dot dot-pulse\"></span>\n <span>Executing query on database (may take 1\u20132 minutes on local database)...</span>\n </div>\n } @else if (loadError()) {\n <div class=\"combobox-status-row error\">\n <span>Error: {{ loadError() }}</span>\n <button type=\"button\" class=\"combobox-retry-btn\" (click)=\"retryLookup($event)\">\u21BB Retry Query</button>\n </div>\n } @else if (isDynamicSource() && hasLoaded() && allOptions().length === 0) {\n <div class=\"combobox-status-row empty\">\n <span>No records found (check parameters)</span>\n </div>\n } @else if (filteredOptions().length === 0) {\n <div class=\"combobox-status-row empty\">\n <span>No options matching \"{{ searchTerm() }}\"</span>\n </div>\n } @else {\n @for (opt of visibleFilteredOptions(); track opt.key; let i = $index) {\n <div\n role=\"option\"\n [id]=\"elementId() + '_opt_' + i\"\n class=\"combobox-option-item\"\n [class.is-selected]=\"isSelected(opt)\"\n [class.is-focused]=\"focusedIndex() === i\"\n [attr.aria-selected]=\"isSelected(opt)\"\n (click)=\"selectOption(opt)\"\n (mouseenter)=\"focusedIndex.set(i)\"\n >\n <span class=\"opt-label\">{{ opt.label }}</span>\n @if (isSelected(opt)) {\n <span class=\"opt-check\" aria-hidden=\"true\">\u2713</span>\n }\n </div>\n }\n @if (excessFilteredCount() > 0) {\n <div class=\"combobox-status-row hint\">\n <span>Showing {{ visibleFilteredOptions().length }} of {{ filteredOptions().length }} matches. Scroll down or</span>\n <button\n type=\"button\"\n class=\"combobox-load-all-btn\"\n (click)=\"loadAllFiltered(); $event.stopPropagation();\"\n >\n Load all {{ filteredOptions().length }}\n </button>\n </div>\n }\n }\n </div>\n </div>\n </ng-template>\n } @else {\n <!-- Native Browser Select Fallback -->\n <div class=\"rpt-select-wrapper\">\n <select\n [id]=\"elementId()\"\n [formControl]=\"formControl()\"\n [class.rpt-invalid]=\"isInvalid()\"\n class=\"rpt-select\"\n >\n @if (waitingFor().length > 0) {\n <option value=\"\" disabled selected>Waiting for {{ waitingLabel() }}...</option>\n } @else if (isLoading()) {\n <option value=\"\" disabled selected>Loading options from database...</option>\n } @else if (loadError()) {\n <option value=\"\" disabled selected>Error: {{ loadError() }}</option>\n } @else if (isDynamicSource() && hasLoaded() && allOptions().length === 0) {\n <option value=\"\" disabled selected>-- No records found (check parameters) --</option>\n } @else if (placeholder()) {\n <option value=\"\" [disabled]=\"config().validation?.required\">{{ placeholder() }}</option>\n }\n @for (opt of renderedOptions(); track opt.key) {\n <option [value]=\"opt.key\">{{ opt.label }}</option>\n }\n @if (hasExcessOptions()) {\n <option value=\"\" disabled>... and {{ excessOptionsCount() }} more records (refine filter) ...</option>\n }\n </select>\n <div class=\"rpt-select-arrow\" aria-hidden=\"true\">\n <svg width=\"12\" height=\"8\" viewBox=\"0 0 12 8\" fill=\"none\">\n <path d=\"M1 1.5L6 6.5L11 1.5\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </div>\n </div>\n }\n\n @if (showStatus() && isDynamicSource() && statusText()) {\n <div class=\"rpt-status-badge\" [attr.data-status]=\"statusKind()\">\n @switch (statusKind()) {\n @case ('loading') {\n <span class=\"status-dot dot-pulse\"></span>\n <span>Loading from database\u2026</span>\n }\n @case ('ok') {\n <span class=\"status-dot dot-ok\"></span>\n <span>{{ dynamicOptions().length }} records loaded</span>\n }\n @case ('waiting') {\n <span class=\"status-dot dot-warn\"></span>\n <span>Waiting for:</span>\n <div class=\"waiting-tokens\">\n @for (k of waitingFor(); track k) {\n <span class=\"waiting-token\">{{ k }}</span>\n }\n </div>\n }\n @case ('error') {\n <span class=\"status-dot dot-err\"></span>\n <span>{{ loadError() }}</span>\n <button type=\"button\" class=\"rpt-badge-retry-btn\" (click)=\"retryLookup($event)\">\u21BB Retry</button>\n }\n @case ('empty') {\n <span class=\"status-dot\"></span>\n <span>No records found for parameters</span>\n }\n @default {\n <span class=\"status-dot\"></span>\n }\n }\n </div>\n }\n\n @if (errorMessage()) {\n <span class=\"rpt-error-text\">{{ errorMessage() }}</span>\n }\n </div>", styles: [":host{display:block;width:100%;max-width:100%;min-width:0;box-sizing:border-box;font-family:var(--rpt-font-family, inherit);position:relative}.rpt-status-badge{display:flex;flex-wrap:wrap;align-items:center;gap:4px;font-size:.6875rem;padding:3px 6px;border-radius:4px;margin-top:4px;font-weight:500;line-height:1.3;max-width:100%;box-sizing:border-box;overflow:hidden;word-break:break-word}.rpt-status-badge>span{overflow-wrap:anywhere;word-break:break-word}.rpt-status-badge[data-status=loading]{background:#eff6ff;color:#2563eb;border:1px solid #dbeafe}.rpt-status-badge[data-status=ok]{background:#f0fdf4;color:#16a34a;border:1px solid #dcfce7}.rpt-status-badge[data-status=waiting]{background:#fffbeb;color:#d97706;border:1px solid #fef3c7}.rpt-status-badge[data-status=error]{background:#fef2f2;color:#dc2626;border:1px solid #fee2e2}.rpt-status-badge[data-status=empty]{background:#f8fafc;color:#64748b;border:1px solid #e2e8f0}.waiting-tokens{display:inline-flex;flex-wrap:wrap;align-items:center;gap:3px;margin-left:2px;max-width:100%}.waiting-token{background:#b453091f;border:1px solid rgba(180,83,9,.3);padding:1px 5px;border-radius:3px;font-weight:700;font-size:.625rem;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-block;box-sizing:border-box}.status-dot{width:6px;height:6px;border-radius:50%;background:currentColor}.dot-pulse{animation:rpt-pulse 1s infinite alternate}@keyframes rpt-pulse{0%{opacity:.3}to{opacity:1}}.rpt-field-group{display:flex;flex-direction:column;max-width:100%;min-width:0;gap:4px;box-sizing:border-box;position:relative}.rpt-label{font-size:var(--rpt-font-size-sm, .75rem);font-weight:500;color:var(--rpt-color-text, #0f172a);flex-shrink:0}.rpt-required-marker{color:var(--rpt-color-danger, #ef4444);margin-left:2px}.rpt-select-wrapper{position:relative;width:100%;flex:1;min-height:0;display:flex;align-items:center}.rpt-select{width:100%;height:100%;min-height:38px;box-sizing:border-box;padding:6px 36px 6px 12px;font-size:var(--rpt-font-size-base, .875rem);color:var(--rpt-color-text, #0f172a);background-color:var(--rpt-color-bg, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);appearance:none;-webkit-appearance:none;outline:none;cursor:pointer;transition:border-color var(--rpt-transition-speed, .15s) ease,box-shadow var(--rpt-transition-speed, .15s) ease}.rpt-select:hover:not(:disabled){border-color:var(--rpt-color-border-hover, #94a3b8)}.rpt-select:focus{border-color:var(--rpt-color-border-focus, #2563eb);box-shadow:var(--rpt-box-shadow-focus, 0 0 0 3px rgba(37, 99, 235, .25))}.rpt-select:disabled{background-color:var(--rpt-color-surface, #f8fafc);color:var(--rpt-color-text-muted, #64748b);cursor:not-allowed}.rpt-select.rpt-invalid{border-color:var(--rpt-color-danger, #ef4444)}.rpt-select.rpt-invalid:focus{box-shadow:0 0 0 3px #ef444440}.rpt-select-arrow{position:absolute;right:12px;top:50%;transform:translateY(-50%);pointer-events:none;color:var(--rpt-color-text-muted, #64748b);display:flex;align-items:center}.rpt-combobox-wrapper{position:relative;width:100%;flex:1;min-height:0}.rpt-combobox-trigger{width:100%;height:100%;min-height:38px;box-sizing:border-box;padding:6px 10px 6px 12px;display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:var(--rpt-font-size-base, .875rem);font-family:inherit;color:var(--rpt-color-text, #0f172a);background-color:var(--rpt-color-bg, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);outline:none;cursor:pointer;text-align:left;transition:border-color var(--rpt-transition-speed, .15s) ease,box-shadow var(--rpt-transition-speed, .15s) ease}.rpt-combobox-trigger:hover:not(:disabled){border-color:var(--rpt-color-border-hover, #94a3b8)}.rpt-combobox-trigger:focus-visible,.rpt-combobox-trigger.is-open{border-color:var(--rpt-color-border-focus, #2563eb);box-shadow:var(--rpt-box-shadow-focus, 0 0 0 3px rgba(37, 99, 235, .25))}.rpt-combobox-trigger:disabled{background-color:var(--rpt-color-surface, #f8fafc);color:var(--rpt-color-text-muted, #64748b);cursor:not-allowed}.rpt-combobox-trigger.rpt-invalid{border-color:var(--rpt-color-danger, #ef4444)}.rpt-combobox-trigger.rpt-invalid:focus-visible{box-shadow:0 0 0 3px #ef444440}.trigger-label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.3}.trigger-label.is-placeholder{color:var(--rpt-color-text-muted, #94a3b8)}.trigger-actions{display:flex;align-items:center;gap:6px;flex-shrink:0}.trigger-clear-btn{display:flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;font-size:.6875rem;color:var(--rpt-color-text-muted, #64748b);background-color:#f1f5f9;cursor:pointer;-webkit-user-select:none;user-select:none;transition:background-color .15s ease,color .15s ease}.trigger-clear-btn:hover{background-color:#e2e8f0;color:var(--rpt-color-text, #0f172a)}.trigger-arrow{display:flex;align-items:center;color:var(--rpt-color-text-muted, #64748b);transition:transform .18s ease}.trigger-arrow.rotated{transform:rotate(180deg)}.rpt-combobox-menu{width:100%;min-width:180px;max-width:100vw;box-sizing:border-box;background-color:var(--rpt-color-bg, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);box-shadow:0 10px 25px -5px #00000024,0 8px 10px -6px #00000014;overflow:hidden;display:flex;flex-direction:column;animation:rpt-dropdown-pop .12s cubic-bezier(0,0,.2,1)}.rpt-combobox-menu.opens-above{box-shadow:0 -10px 25px -5px #00000024,0 -8px 10px -6px #00000014;animation:rpt-dropdown-pop-above .12s cubic-bezier(0,0,.2,1)}@keyframes rpt-dropdown-pop{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}@keyframes rpt-dropdown-pop-above{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.combobox-search-box{display:flex;align-items:center;gap:8px;padding:8px 10px;border-bottom:1px solid var(--rpt-color-border, #e2e8f0);background-color:#f8fafc}.combobox-search-icon{color:var(--rpt-color-text-muted, #94a3b8);display:flex;align-items:center;flex-shrink:0}.combobox-search-input{flex:1;border:none;outline:none;background:transparent;font-size:var(--rpt-font-size-sm, .8125rem);font-family:inherit;color:var(--rpt-color-text, #0f172a);min-width:0}.combobox-search-input::placeholder{color:#94a3b8}.combobox-search-clear{border:none;background:#e2e8f0;color:#64748b;width:16px;height:16px;border-radius:50%;font-size:.625rem;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0}.combobox-search-clear:hover{background:#cbd5e1;color:#0f172a}.combobox-options-list{max-height:220px;overflow-y:auto;overscroll-behavior:contain;padding:4px}.combobox-option-item{display:flex;align-items:center;justify-content:space-between;padding:7px 10px;border-radius:4px;font-size:var(--rpt-font-size-base, .84rem);color:var(--rpt-color-text, #0f172a);cursor:pointer;-webkit-user-select:none;user-select:none;transition:background-color .1s ease}.combobox-option-item.is-focused{background-color:var(--rpt-color-surface-hover, #f1f5f9)}.combobox-option-item.is-selected{background-color:#eff6ff;color:var(--rpt-color-primary, #2563eb);font-weight:600}.opt-label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opt-check{font-size:.875rem;font-weight:700;color:var(--rpt-color-primary, #2563eb);margin-left:8px}.combobox-status-row{padding:10px 12px;font-size:.78rem;text-align:center;color:var(--rpt-color-text-muted, #64748b)}.combobox-status-row.loading{display:flex;align-items:center;justify-content:center;gap:6px;color:#2563eb}.combobox-status-row.error{display:flex;flex-direction:column;align-items:center;gap:6px;color:var(--rpt-color-danger, #dc2626)}.trigger-retry-btn,.combobox-retry-btn,.rpt-badge-retry-btn{display:inline-flex;align-items:center;gap:3px;font-size:.6875rem;font-weight:600;color:#dc2626;background:#fee2e2;border:1px solid #fca5a5;border-radius:4px;padding:2px 7px;cursor:pointer;line-height:1.2;transition:background-color .12s ease,color .12s ease}.trigger-retry-btn:hover,.combobox-retry-btn:hover,.rpt-badge-retry-btn:hover{background:#fecaca;color:#b91c1c;border-color:#f87171}.combobox-status-row.hint{font-size:.72rem;color:#64748b;border-top:1px dashed #cbd5e1;margin-top:4px;padding:6px 10px;background-color:#f8fafc;display:flex;align-items:center;justify-content:space-between;gap:8px;position:sticky;bottom:0;z-index:2;box-shadow:0 -2px 6px #0000000a}.combobox-load-all-btn{background:#eff6ff;border:1px solid #bfdbfe;color:#2563eb;font-size:.6875rem;font-weight:600;padding:3px 8px;border-radius:4px;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:all .12s ease}.combobox-load-all-btn:hover{background:#2563eb;color:#fff;border-color:#2563eb}.rpt-error-text{font-size:.7rem;color:var(--rpt-color-danger, #ef4444);margin-top:3px;line-height:1.25;max-width:100%;overflow-wrap:anywhere;word-break:break-word;display:block}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i2.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i2.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4205
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: RptDropdownComponent, isStandalone: true, selector: "rpt-dropdown", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null }, control: { classPropertyName: "control", publicName: "control", isSignal: true, isRequired: false, transformFunction: null }, parentForm: { classPropertyName: "parentForm", publicName: "parentForm", isSignal: true, isRequired: false, transformFunction: null }, showStatus: { classPropertyName: "showStatus", publicName: "showStatus", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dropdownClick: "dropdownClick" }, host: { listeners: { "document:click": "onDocumentClick($event)", "document:keydown.escape": "onEscape()" }, properties: { "class.is-open": "isOpen()", "class.opens-above": "opensAbove()" } }, viewQueries: [{ propertyName: "comboboxWrapperEl", first: true, predicate: ["comboboxWrapper"], descendants: true, isSignal: true }, { propertyName: "searchInputEl", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }, { propertyName: "optionsListEl", first: true, predicate: ["optionsListEl"], descendants: true, isSignal: true }], ngImport: i0, template: " <div class=\"rpt-field-group\">\n @if (config().label) {\n <label [for]=\"elementId()\" class=\"rpt-label\">\n {{ config().label }}\n @if (config().validation?.required) {\n <span class=\"rpt-required-marker\">*</span>\n }\n </label>\n }\n\n @if (isSearchable()) {\n <!-- Searchable Typeahead Combobox -->\n <div class=\"rpt-combobox-wrapper\" #comboboxWrapper #comboboxOrigin=\"cdkOverlayOrigin\" cdkOverlayOrigin>\n <button\n type=\"button\"\n class=\"rpt-combobox-trigger\"\n [id]=\"elementId()\"\n [class.rpt-invalid]=\"isInvalid()\"\n [class.is-open]=\"isOpen()\"\n [disabled]=\"formControl().disabled\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"'listbox'\"\n (click)=\"toggleOpen($event)\"\n (keydown)=\"onTriggerKeydown($event)\"\n >\n <span class=\"trigger-label\" [class.is-placeholder]=\"!selectedOption()\">\n {{ selectedOption() ? selectedOption()!.label : placeholder() }}\n </span>\n\n <div class=\"trigger-actions\">\n @if (isLoading()) {\n <span class=\"status-dot dot-pulse\" style=\"color: #2563eb; margin-right: 4px;\" title=\"Executing query on database...\"></span>\n }\n @if (loadError() && !isLoading()) {\n <button\n type=\"button\"\n class=\"trigger-retry-btn\"\n title=\"Retry database query\"\n aria-label=\"Retry database query\"\n (click)=\"retryLookup($event)\"\n >\n \u21BB Retry\n </button>\n }\n @if (selectedOption() && !formControl().disabled) {\n <span\n role=\"button\"\n tabindex=\"0\"\n class=\"trigger-clear-btn\"\n title=\"Clear selection\"\n aria-label=\"Clear selection\"\n (click)=\"clearSelection($event)\"\n (keydown.enter)=\"clearSelection($event)\"\n (keydown.space)=\"clearSelection($event)\"\n >\n \u2715\n </span>\n }\n <span class=\"trigger-arrow\" [class.rotated]=\"isOpen()\" aria-hidden=\"true\">\n <svg width=\"12\" height=\"8\" viewBox=\"0 0 12 8\" fill=\"none\">\n <path d=\"M1 1.5L6 6.5L11 1.5\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n </div>\n </button>\n </div>\n\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"comboboxOrigin\"\n [cdkConnectedOverlayOpen]=\"isOpen()\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayWidth]=\"triggerWidth()\"\n [cdkConnectedOverlayMinWidth]=\"'min(180px, calc(100vw - 16px))'\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n [cdkConnectedOverlayViewportMargin]=\"8\"\n [cdkConnectedOverlayPush]=\"true\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n (overlayOutsideClick)=\"closeDropdown()\"\n (positionChange)=\"onPositionChange($event)\"\n (attach)=\"onOverlayAttached()\"\n (detach)=\"closeDropdown()\"\n >\n <div\n class=\"rpt-combobox-menu\"\n [class.opens-above]=\"opensAbove()\"\n role=\"listbox\"\n (click)=\"$event.stopPropagation()\"\n >\n <!-- Real-time Filter Search Box -->\n <div class=\"combobox-search-box\">\n <span class=\"combobox-search-icon\" aria-hidden=\"true\">\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"></circle>\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"></line>\n </svg>\n </span>\n <input\n #searchInput\n type=\"text\"\n class=\"combobox-search-input\"\n placeholder=\"Type to filter...\"\n [value]=\"searchTerm()\"\n [attr.aria-activedescendant]=\"focusedIndex() >= 0 ? (elementId() + '_opt_' + focusedIndex()) : null\"\n (input)=\"onSearchInput($event)\"\n (keydown)=\"onSearchKeydown($event)\"\n />\n @if (searchTerm()) {\n <button\n type=\"button\"\n class=\"combobox-search-clear\"\n title=\"Clear filter\"\n (click)=\"clearSearch($event)\"\n >\n \u2715\n </button>\n }\n </div>\n\n <!-- Filtered Options List -->\n <div class=\"combobox-options-list\" #optionsListEl (scroll)=\"onOptionsScroll($event)\">\n @if (waitingFor().length > 0) {\n <div class=\"combobox-status-row waiting\">\n <span>Waiting for {{ waitingLabel() }}...</span>\n </div>\n } @else if (isLoading()) {\n <div class=\"combobox-status-row loading\">\n <span class=\"status-dot dot-pulse\"></span>\n <span>Executing query on database (may take 1\u20132 minutes on local database)...</span>\n </div>\n } @else if (loadError()) {\n <div class=\"combobox-status-row error\">\n <span>Error: {{ loadError() }}</span>\n <button type=\"button\" class=\"combobox-retry-btn\" (click)=\"retryLookup($event)\">\u21BB Retry Query</button>\n </div>\n } @else if (isDynamicSource() && hasLoaded() && allOptions().length === 0) {\n <div class=\"combobox-status-row empty\">\n <span>No records found (check parameters)</span>\n </div>\n } @else if (filteredOptions().length === 0) {\n <div class=\"combobox-status-row empty\">\n <span>No options matching \"{{ searchTerm() }}\"</span>\n </div>\n } @else {\n @for (opt of visibleFilteredOptions(); track opt.key; let i = $index) {\n <div\n role=\"option\"\n [id]=\"elementId() + '_opt_' + i\"\n class=\"combobox-option-item\"\n [class.is-selected]=\"isSelected(opt)\"\n [class.is-focused]=\"focusedIndex() === i\"\n [attr.aria-selected]=\"isSelected(opt)\"\n (click)=\"selectOption(opt)\"\n (mouseenter)=\"focusedIndex.set(i)\"\n >\n <span class=\"opt-label\">{{ opt.label }}</span>\n @if (isSelected(opt)) {\n <span class=\"opt-check\" aria-hidden=\"true\">\u2713</span>\n }\n </div>\n }\n @if (excessFilteredCount() > 0) {\n <div class=\"combobox-status-row hint\">\n <span>Showing {{ visibleFilteredOptions().length }} of {{ filteredOptions().length }} matches. Scroll down or</span>\n <button\n type=\"button\"\n class=\"combobox-load-all-btn\"\n (click)=\"loadAllFiltered(); $event.stopPropagation();\"\n >\n Load all {{ filteredOptions().length }}\n </button>\n </div>\n }\n }\n </div>\n </div>\n </ng-template>\n } @else {\n <!-- Native Browser Select Fallback -->\n <div class=\"rpt-select-wrapper\">\n <select\n [id]=\"elementId()\"\n [formControl]=\"formControl()\"\n [class.rpt-invalid]=\"isInvalid()\"\n class=\"rpt-select\"\n >\n @if (waitingFor().length > 0) {\n <option value=\"\" disabled selected>Waiting for {{ waitingLabel() }}...</option>\n } @else if (isLoading()) {\n <option value=\"\" disabled selected>Loading options from database...</option>\n } @else if (loadError()) {\n <option value=\"\" disabled selected>Error: {{ loadError() }}</option>\n } @else if (isDynamicSource() && hasLoaded() && allOptions().length === 0) {\n <option value=\"\" disabled selected>-- No records found (check parameters) --</option>\n } @else if (placeholder()) {\n <option value=\"\" [disabled]=\"config().validation?.required\">{{ placeholder() }}</option>\n }\n @for (opt of renderedOptions(); track opt.key) {\n <option [ngValue]=\"opt.key\">{{ opt.label }}</option>\n }\n @if (hasExcessOptions()) {\n <option value=\"\" disabled>... and {{ excessOptionsCount() }} more records (refine filter) ...</option>\n }\n </select>\n <div class=\"rpt-select-arrow\" aria-hidden=\"true\">\n <svg width=\"12\" height=\"8\" viewBox=\"0 0 12 8\" fill=\"none\">\n <path d=\"M1 1.5L6 6.5L11 1.5\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </div>\n </div>\n }\n\n @if (showStatus() && isDynamicSource() && statusText()) {\n <div class=\"rpt-status-badge\" [attr.data-status]=\"statusKind()\">\n @switch (statusKind()) {\n @case ('loading') {\n <span class=\"status-dot dot-pulse\"></span>\n <span>Loading from database\u2026</span>\n }\n @case ('ok') {\n <span class=\"status-dot dot-ok\"></span>\n <span>{{ dynamicOptions().length }} records loaded</span>\n }\n @case ('waiting') {\n <span class=\"status-dot dot-warn\"></span>\n <span>Waiting for:</span>\n <div class=\"waiting-tokens\">\n @for (k of waitingFor(); track k) {\n <span class=\"waiting-token\">{{ k }}</span>\n }\n </div>\n }\n @case ('error') {\n <span class=\"status-dot dot-err\"></span>\n <span>{{ loadError() }}</span>\n <button type=\"button\" class=\"rpt-badge-retry-btn\" (click)=\"retryLookup($event)\">\u21BB Retry</button>\n }\n @case ('empty') {\n <span class=\"status-dot\"></span>\n <span>No records found for parameters</span>\n }\n @default {\n <span class=\"status-dot\"></span>\n }\n }\n </div>\n }\n\n @if (errorMessage()) {\n <span class=\"rpt-error-text\">{{ errorMessage() }}</span>\n }\n </div>", styles: [":host{display:block;width:100%;max-width:100%;min-width:0;box-sizing:border-box;font-family:var(--rpt-font-family, inherit);position:relative}.rpt-status-badge{display:flex;flex-wrap:wrap;align-items:center;gap:4px;font-size:.6875rem;padding:3px 6px;border-radius:4px;margin-top:4px;font-weight:500;line-height:1.3;max-width:100%;box-sizing:border-box;overflow:hidden;word-break:break-word}.rpt-status-badge>span{overflow-wrap:anywhere;word-break:break-word}.rpt-status-badge[data-status=loading]{background:#eff6ff;color:#2563eb;border:1px solid #dbeafe}.rpt-status-badge[data-status=ok]{background:#f0fdf4;color:#16a34a;border:1px solid #dcfce7}.rpt-status-badge[data-status=waiting]{background:#fffbeb;color:#d97706;border:1px solid #fef3c7}.rpt-status-badge[data-status=error]{background:#fef2f2;color:#dc2626;border:1px solid #fee2e2}.rpt-status-badge[data-status=empty]{background:#f8fafc;color:#64748b;border:1px solid #e2e8f0}.waiting-tokens{display:inline-flex;flex-wrap:wrap;align-items:center;gap:3px;margin-left:2px;max-width:100%}.waiting-token{background:#b453091f;border:1px solid rgba(180,83,9,.3);padding:1px 5px;border-radius:3px;font-weight:700;font-size:.625rem;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-block;box-sizing:border-box}.status-dot{width:6px;height:6px;border-radius:50%;background:currentColor}.dot-pulse{animation:rpt-pulse 1s infinite alternate}@keyframes rpt-pulse{0%{opacity:.3}to{opacity:1}}.rpt-field-group{display:flex;flex-direction:column;max-width:100%;min-width:0;gap:4px;box-sizing:border-box;position:relative}.rpt-label{font-size:var(--rpt-font-size-sm, .75rem);font-weight:500;color:var(--rpt-color-text, #0f172a);flex-shrink:0}.rpt-required-marker{color:var(--rpt-color-danger, #ef4444);margin-left:2px}.rpt-select-wrapper{position:relative;width:100%;flex:1;min-height:0;display:flex;align-items:center}.rpt-select{width:100%;height:100%;min-height:38px;box-sizing:border-box;padding:6px 36px 6px 12px;font-size:var(--rpt-font-size-base, .875rem);color:var(--rpt-color-text, #0f172a);background-color:var(--rpt-color-bg, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);appearance:none;-webkit-appearance:none;outline:none;cursor:pointer;transition:border-color var(--rpt-transition-speed, .15s) ease,box-shadow var(--rpt-transition-speed, .15s) ease}.rpt-select:hover:not(:disabled){border-color:var(--rpt-color-border-hover, #94a3b8)}.rpt-select:focus{border-color:var(--rpt-color-border-focus, #2563eb);box-shadow:var(--rpt-box-shadow-focus, 0 0 0 3px rgba(37, 99, 235, .25))}.rpt-select:disabled{background-color:var(--rpt-color-surface, #f8fafc);color:var(--rpt-color-text-muted, #64748b);cursor:not-allowed}.rpt-select.rpt-invalid{border-color:var(--rpt-color-danger, #ef4444)}.rpt-select.rpt-invalid:focus{box-shadow:0 0 0 3px #ef444440}.rpt-select-arrow{position:absolute;right:12px;top:50%;transform:translateY(-50%);pointer-events:none;color:var(--rpt-color-text-muted, #64748b);display:flex;align-items:center}.rpt-combobox-wrapper{position:relative;width:100%;flex:1;min-height:0}.rpt-combobox-trigger{width:100%;height:100%;min-height:38px;box-sizing:border-box;padding:6px 10px 6px 12px;display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:var(--rpt-font-size-base, .875rem);font-family:inherit;color:var(--rpt-color-text, #0f172a);background-color:var(--rpt-color-bg, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);outline:none;cursor:pointer;text-align:left;transition:border-color var(--rpt-transition-speed, .15s) ease,box-shadow var(--rpt-transition-speed, .15s) ease}.rpt-combobox-trigger:hover:not(:disabled){border-color:var(--rpt-color-border-hover, #94a3b8)}.rpt-combobox-trigger:focus-visible,.rpt-combobox-trigger.is-open{border-color:var(--rpt-color-border-focus, #2563eb);box-shadow:var(--rpt-box-shadow-focus, 0 0 0 3px rgba(37, 99, 235, .25))}.rpt-combobox-trigger:disabled{background-color:var(--rpt-color-surface, #f8fafc);color:var(--rpt-color-text-muted, #64748b);cursor:not-allowed}.rpt-combobox-trigger.rpt-invalid{border-color:var(--rpt-color-danger, #ef4444)}.rpt-combobox-trigger.rpt-invalid:focus-visible{box-shadow:0 0 0 3px #ef444440}.trigger-label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.3}.trigger-label.is-placeholder{color:var(--rpt-color-text-muted, #94a3b8)}.trigger-actions{display:flex;align-items:center;gap:6px;flex-shrink:0}.trigger-clear-btn{display:flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;font-size:.6875rem;color:var(--rpt-color-text-muted, #64748b);background-color:#f1f5f9;cursor:pointer;-webkit-user-select:none;user-select:none;transition:background-color .15s ease,color .15s ease}.trigger-clear-btn:hover{background-color:#e2e8f0;color:var(--rpt-color-text, #0f172a)}.trigger-arrow{display:flex;align-items:center;color:var(--rpt-color-text-muted, #64748b);transition:transform .18s ease}.trigger-arrow.rotated{transform:rotate(180deg)}.rpt-combobox-menu{width:100%;min-width:0;max-width:100vw;box-sizing:border-box;background-color:var(--rpt-color-bg, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);box-shadow:0 10px 25px -5px #00000024,0 8px 10px -6px #00000014;overflow:hidden;display:flex;flex-direction:column;animation:rpt-dropdown-pop .12s cubic-bezier(0,0,.2,1)}.rpt-combobox-menu.opens-above{box-shadow:0 -10px 25px -5px #00000024,0 -8px 10px -6px #00000014;animation:rpt-dropdown-pop-above .12s cubic-bezier(0,0,.2,1)}@keyframes rpt-dropdown-pop{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}@keyframes rpt-dropdown-pop-above{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.combobox-search-box{display:flex;align-items:center;gap:8px;padding:8px 10px;border-bottom:1px solid var(--rpt-color-border, #e2e8f0);background-color:#f8fafc}.combobox-search-icon{color:var(--rpt-color-text-muted, #94a3b8);display:flex;align-items:center;flex-shrink:0}.combobox-search-input{flex:1;border:none;outline:none;background:transparent;font-size:var(--rpt-font-size-sm, .8125rem);font-family:inherit;color:var(--rpt-color-text, #0f172a);min-width:0}.combobox-search-input::placeholder{color:#94a3b8}.combobox-search-clear{border:none;background:#e2e8f0;color:#64748b;width:16px;height:16px;border-radius:50%;font-size:.625rem;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0}.combobox-search-clear:hover{background:#cbd5e1;color:#0f172a}.combobox-options-list{max-height:220px;overflow-y:auto;overscroll-behavior:contain;padding:4px}.combobox-option-item{display:flex;align-items:center;justify-content:space-between;padding:7px 10px;border-radius:4px;font-size:var(--rpt-font-size-base, .84rem);color:var(--rpt-color-text, #0f172a);cursor:pointer;-webkit-user-select:none;user-select:none;transition:background-color .1s ease}.combobox-option-item.is-focused{background-color:var(--rpt-color-surface-hover, #f1f5f9)}.combobox-option-item.is-selected{background-color:#eff6ff;color:var(--rpt-color-primary, #2563eb);font-weight:600}.opt-label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opt-check{font-size:.875rem;font-weight:700;color:var(--rpt-color-primary, #2563eb);margin-left:8px}.combobox-status-row{padding:10px 12px;font-size:.78rem;text-align:center;color:var(--rpt-color-text-muted, #64748b)}.combobox-status-row.loading{display:flex;align-items:center;justify-content:center;gap:6px;color:#2563eb}.combobox-status-row.error{display:flex;flex-direction:column;align-items:center;gap:6px;color:var(--rpt-color-danger, #dc2626)}.trigger-retry-btn,.combobox-retry-btn,.rpt-badge-retry-btn{display:inline-flex;align-items:center;gap:3px;font-size:.6875rem;font-weight:600;color:#dc2626;background:#fee2e2;border:1px solid #fca5a5;border-radius:4px;padding:2px 7px;cursor:pointer;line-height:1.2;transition:background-color .12s ease,color .12s ease}.trigger-retry-btn:hover,.combobox-retry-btn:hover,.rpt-badge-retry-btn:hover{background:#fecaca;color:#b91c1c;border-color:#f87171}.combobox-status-row.hint{font-size:.72rem;color:#64748b;border-top:1px dashed #cbd5e1;margin-top:4px;padding:6px 10px;background-color:#f8fafc;display:flex;align-items:center;justify-content:space-between;gap:8px;position:sticky;bottom:0;z-index:2;box-shadow:0 -2px 6px #0000000a}.combobox-load-all-btn{background:#eff6ff;border:1px solid #bfdbfe;color:#2563eb;font-size:.6875rem;font-weight:600;padding:3px 8px;border-radius:4px;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:all .12s ease}.combobox-load-all-btn:hover{background:#2563eb;color:#fff;border-color:#2563eb}.rpt-error-text{font-size:.7rem;color:var(--rpt-color-danger, #ef4444);margin-top:3px;line-height:1.25;max-width:100%;overflow-wrap:anywhere;word-break:break-word;display:block}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i2.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i2.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4074
4206
|
}
|
|
4075
4207
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RptDropdownComponent, decorators: [{
|
|
4076
4208
|
type: Component,
|
|
4077
4209
|
args: [{ selector: 'rpt-dropdown', standalone: true, imports: [ReactiveFormsModule, OverlayModule], host: {
|
|
4078
4210
|
'[class.is-open]': 'isOpen()',
|
|
4079
4211
|
'[class.opens-above]': 'opensAbove()',
|
|
4080
|
-
}, changeDetection: ChangeDetectionStrategy.OnPush, template: " <div class=\"rpt-field-group\">\n @if (config().label) {\n <label [for]=\"elementId()\" class=\"rpt-label\">\n {{ config().label }}\n @if (config().validation?.required) {\n <span class=\"rpt-required-marker\">*</span>\n }\n </label>\n }\n\n @if (isSearchable()) {\n <!-- Searchable Typeahead Combobox -->\n <div class=\"rpt-combobox-wrapper\" #comboboxWrapper #comboboxOrigin=\"cdkOverlayOrigin\" cdkOverlayOrigin>\n <button\n type=\"button\"\n class=\"rpt-combobox-trigger\"\n [id]=\"elementId()\"\n [class.rpt-invalid]=\"isInvalid()\"\n [class.is-open]=\"isOpen()\"\n [disabled]=\"formControl().disabled\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"'listbox'\"\n (click)=\"toggleOpen($event)\"\n (keydown)=\"onTriggerKeydown($event)\"\n >\n <span class=\"trigger-label\" [class.is-placeholder]=\"!selectedOption()\">\n {{ selectedOption() ? selectedOption()!.label : placeholder() }}\n </span>\n\n <div class=\"trigger-actions\">\n @if (isLoading()) {\n <span class=\"status-dot dot-pulse\" style=\"color: #2563eb; margin-right: 4px;\" title=\"Executing query on database...\"></span>\n }\n @if (loadError() && !isLoading()) {\n <button\n type=\"button\"\n class=\"trigger-retry-btn\"\n title=\"Retry database query\"\n aria-label=\"Retry database query\"\n (click)=\"retryLookup($event)\"\n >\n \u21BB Retry\n </button>\n }\n @if (selectedOption() && !formControl().disabled) {\n <span\n role=\"button\"\n tabindex=\"0\"\n class=\"trigger-clear-btn\"\n title=\"Clear selection\"\n aria-label=\"Clear selection\"\n (click)=\"clearSelection($event)\"\n (keydown.enter)=\"clearSelection($event)\"\n (keydown.space)=\"clearSelection($event)\"\n >\n \u2715\n </span>\n }\n <span class=\"trigger-arrow\" [class.rotated]=\"isOpen()\" aria-hidden=\"true\">\n <svg width=\"12\" height=\"8\" viewBox=\"0 0 12 8\" fill=\"none\">\n <path d=\"M1 1.5L6 6.5L11 1.5\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n </div>\n </button>\n </div>\n\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"comboboxOrigin\"\n [cdkConnectedOverlayOpen]=\"isOpen()\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayWidth]=\"triggerWidth()\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n [cdkConnectedOverlayViewportMargin]=\"8\"\n [cdkConnectedOverlayPush]=\"true\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n (overlayOutsideClick)=\"closeDropdown()\"\n (positionChange)=\"onPositionChange($event)\"\n (attach)=\"onOverlayAttached()\"\n (detach)=\"closeDropdown()\"\n >\n <div\n class=\"rpt-combobox-menu\"\n [class.opens-above]=\"opensAbove()\"\n role=\"listbox\"\n (click)=\"$event.stopPropagation()\"\n >\n <!-- Real-time Filter Search Box -->\n <div class=\"combobox-search-box\">\n <span class=\"combobox-search-icon\" aria-hidden=\"true\">\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"></circle>\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"></line>\n </svg>\n </span>\n <input\n #searchInput\n type=\"text\"\n class=\"combobox-search-input\"\n placeholder=\"Type to filter...\"\n [value]=\"searchTerm()\"\n [attr.aria-activedescendant]=\"focusedIndex() >= 0 ? (elementId() + '_opt_' + focusedIndex()) : null\"\n (input)=\"onSearchInput($event)\"\n (keydown)=\"onSearchKeydown($event)\"\n />\n @if (searchTerm()) {\n <button\n type=\"button\"\n class=\"combobox-search-clear\"\n title=\"Clear filter\"\n (click)=\"clearSearch($event)\"\n >\n \u2715\n </button>\n }\n </div>\n\n <!-- Filtered Options List -->\n <div class=\"combobox-options-list\" #optionsListEl (scroll)=\"onOptionsScroll($event)\">\n @if (waitingFor().length > 0) {\n <div class=\"combobox-status-row waiting\">\n <span>Waiting for {{ waitingLabel() }}...</span>\n </div>\n } @else if (isLoading()) {\n <div class=\"combobox-status-row loading\">\n <span class=\"status-dot dot-pulse\"></span>\n <span>Executing query on database (may take 1\u20132 minutes on local database)...</span>\n </div>\n } @else if (loadError()) {\n <div class=\"combobox-status-row error\">\n <span>Error: {{ loadError() }}</span>\n <button type=\"button\" class=\"combobox-retry-btn\" (click)=\"retryLookup($event)\">\u21BB Retry Query</button>\n </div>\n } @else if (isDynamicSource() && hasLoaded() && allOptions().length === 0) {\n <div class=\"combobox-status-row empty\">\n <span>No records found (check parameters)</span>\n </div>\n } @else if (filteredOptions().length === 0) {\n <div class=\"combobox-status-row empty\">\n <span>No options matching \"{{ searchTerm() }}\"</span>\n </div>\n } @else {\n @for (opt of visibleFilteredOptions(); track opt.key; let i = $index) {\n <div\n role=\"option\"\n [id]=\"elementId() + '_opt_' + i\"\n class=\"combobox-option-item\"\n [class.is-selected]=\"isSelected(opt)\"\n [class.is-focused]=\"focusedIndex() === i\"\n [attr.aria-selected]=\"isSelected(opt)\"\n (click)=\"selectOption(opt)\"\n (mouseenter)=\"focusedIndex.set(i)\"\n >\n <span class=\"opt-label\">{{ opt.label }}</span>\n @if (isSelected(opt)) {\n <span class=\"opt-check\" aria-hidden=\"true\">\u2713</span>\n }\n </div>\n }\n @if (excessFilteredCount() > 0) {\n <div class=\"combobox-status-row hint\">\n <span>Showing {{ visibleFilteredOptions().length }} of {{ filteredOptions().length }} matches. Scroll down or</span>\n <button\n type=\"button\"\n class=\"combobox-load-all-btn\"\n (click)=\"loadAllFiltered(); $event.stopPropagation();\"\n >\n Load all {{ filteredOptions().length }}\n </button>\n </div>\n }\n }\n </div>\n </div>\n </ng-template>\n } @else {\n <!-- Native Browser Select Fallback -->\n <div class=\"rpt-select-wrapper\">\n <select\n [id]=\"elementId()\"\n [formControl]=\"formControl()\"\n [class.rpt-invalid]=\"isInvalid()\"\n class=\"rpt-select\"\n >\n @if (waitingFor().length > 0) {\n <option value=\"\" disabled selected>Waiting for {{ waitingLabel() }}...</option>\n } @else if (isLoading()) {\n <option value=\"\" disabled selected>Loading options from database...</option>\n } @else if (loadError()) {\n <option value=\"\" disabled selected>Error: {{ loadError() }}</option>\n } @else if (isDynamicSource() && hasLoaded() && allOptions().length === 0) {\n <option value=\"\" disabled selected>-- No records found (check parameters) --</option>\n } @else if (placeholder()) {\n <option value=\"\" [disabled]=\"config().validation?.required\">{{ placeholder() }}</option>\n }\n @for (opt of renderedOptions(); track opt.key) {\n <option [value]=\"opt.key\">{{ opt.label }}</option>\n }\n @if (hasExcessOptions()) {\n <option value=\"\" disabled>... and {{ excessOptionsCount() }} more records (refine filter) ...</option>\n }\n </select>\n <div class=\"rpt-select-arrow\" aria-hidden=\"true\">\n <svg width=\"12\" height=\"8\" viewBox=\"0 0 12 8\" fill=\"none\">\n <path d=\"M1 1.5L6 6.5L11 1.5\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </div>\n </div>\n }\n\n @if (showStatus() && isDynamicSource() && statusText()) {\n <div class=\"rpt-status-badge\" [attr.data-status]=\"statusKind()\">\n @switch (statusKind()) {\n @case ('loading') {\n <span class=\"status-dot dot-pulse\"></span>\n <span>Loading from database\u2026</span>\n }\n @case ('ok') {\n <span class=\"status-dot dot-ok\"></span>\n <span>{{ dynamicOptions().length }} records loaded</span>\n }\n @case ('waiting') {\n <span class=\"status-dot dot-warn\"></span>\n <span>Waiting for:</span>\n <div class=\"waiting-tokens\">\n @for (k of waitingFor(); track k) {\n <span class=\"waiting-token\">{{ k }}</span>\n }\n </div>\n }\n @case ('error') {\n <span class=\"status-dot dot-err\"></span>\n <span>{{ loadError() }}</span>\n <button type=\"button\" class=\"rpt-badge-retry-btn\" (click)=\"retryLookup($event)\">\u21BB Retry</button>\n }\n @case ('empty') {\n <span class=\"status-dot\"></span>\n <span>No records found for parameters</span>\n }\n @default {\n <span class=\"status-dot\"></span>\n }\n }\n </div>\n }\n\n @if (errorMessage()) {\n <span class=\"rpt-error-text\">{{ errorMessage() }}</span>\n }\n </div>", styles: [":host{display:block;width:100%;max-width:100%;min-width:0;box-sizing:border-box;font-family:var(--rpt-font-family, inherit);position:relative}.rpt-status-badge{display:flex;flex-wrap:wrap;align-items:center;gap:4px;font-size:.6875rem;padding:3px 6px;border-radius:4px;margin-top:4px;font-weight:500;line-height:1.3;max-width:100%;box-sizing:border-box;overflow:hidden;word-break:break-word}.rpt-status-badge>span{overflow-wrap:anywhere;word-break:break-word}.rpt-status-badge[data-status=loading]{background:#eff6ff;color:#2563eb;border:1px solid #dbeafe}.rpt-status-badge[data-status=ok]{background:#f0fdf4;color:#16a34a;border:1px solid #dcfce7}.rpt-status-badge[data-status=waiting]{background:#fffbeb;color:#d97706;border:1px solid #fef3c7}.rpt-status-badge[data-status=error]{background:#fef2f2;color:#dc2626;border:1px solid #fee2e2}.rpt-status-badge[data-status=empty]{background:#f8fafc;color:#64748b;border:1px solid #e2e8f0}.waiting-tokens{display:inline-flex;flex-wrap:wrap;align-items:center;gap:3px;margin-left:2px;max-width:100%}.waiting-token{background:#b453091f;border:1px solid rgba(180,83,9,.3);padding:1px 5px;border-radius:3px;font-weight:700;font-size:.625rem;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-block;box-sizing:border-box}.status-dot{width:6px;height:6px;border-radius:50%;background:currentColor}.dot-pulse{animation:rpt-pulse 1s infinite alternate}@keyframes rpt-pulse{0%{opacity:.3}to{opacity:1}}.rpt-field-group{display:flex;flex-direction:column;max-width:100%;min-width:0;gap:4px;box-sizing:border-box;position:relative}.rpt-label{font-size:var(--rpt-font-size-sm, .75rem);font-weight:500;color:var(--rpt-color-text, #0f172a);flex-shrink:0}.rpt-required-marker{color:var(--rpt-color-danger, #ef4444);margin-left:2px}.rpt-select-wrapper{position:relative;width:100%;flex:1;min-height:0;display:flex;align-items:center}.rpt-select{width:100%;height:100%;min-height:38px;box-sizing:border-box;padding:6px 36px 6px 12px;font-size:var(--rpt-font-size-base, .875rem);color:var(--rpt-color-text, #0f172a);background-color:var(--rpt-color-bg, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);appearance:none;-webkit-appearance:none;outline:none;cursor:pointer;transition:border-color var(--rpt-transition-speed, .15s) ease,box-shadow var(--rpt-transition-speed, .15s) ease}.rpt-select:hover:not(:disabled){border-color:var(--rpt-color-border-hover, #94a3b8)}.rpt-select:focus{border-color:var(--rpt-color-border-focus, #2563eb);box-shadow:var(--rpt-box-shadow-focus, 0 0 0 3px rgba(37, 99, 235, .25))}.rpt-select:disabled{background-color:var(--rpt-color-surface, #f8fafc);color:var(--rpt-color-text-muted, #64748b);cursor:not-allowed}.rpt-select.rpt-invalid{border-color:var(--rpt-color-danger, #ef4444)}.rpt-select.rpt-invalid:focus{box-shadow:0 0 0 3px #ef444440}.rpt-select-arrow{position:absolute;right:12px;top:50%;transform:translateY(-50%);pointer-events:none;color:var(--rpt-color-text-muted, #64748b);display:flex;align-items:center}.rpt-combobox-wrapper{position:relative;width:100%;flex:1;min-height:0}.rpt-combobox-trigger{width:100%;height:100%;min-height:38px;box-sizing:border-box;padding:6px 10px 6px 12px;display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:var(--rpt-font-size-base, .875rem);font-family:inherit;color:var(--rpt-color-text, #0f172a);background-color:var(--rpt-color-bg, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);outline:none;cursor:pointer;text-align:left;transition:border-color var(--rpt-transition-speed, .15s) ease,box-shadow var(--rpt-transition-speed, .15s) ease}.rpt-combobox-trigger:hover:not(:disabled){border-color:var(--rpt-color-border-hover, #94a3b8)}.rpt-combobox-trigger:focus-visible,.rpt-combobox-trigger.is-open{border-color:var(--rpt-color-border-focus, #2563eb);box-shadow:var(--rpt-box-shadow-focus, 0 0 0 3px rgba(37, 99, 235, .25))}.rpt-combobox-trigger:disabled{background-color:var(--rpt-color-surface, #f8fafc);color:var(--rpt-color-text-muted, #64748b);cursor:not-allowed}.rpt-combobox-trigger.rpt-invalid{border-color:var(--rpt-color-danger, #ef4444)}.rpt-combobox-trigger.rpt-invalid:focus-visible{box-shadow:0 0 0 3px #ef444440}.trigger-label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.3}.trigger-label.is-placeholder{color:var(--rpt-color-text-muted, #94a3b8)}.trigger-actions{display:flex;align-items:center;gap:6px;flex-shrink:0}.trigger-clear-btn{display:flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;font-size:.6875rem;color:var(--rpt-color-text-muted, #64748b);background-color:#f1f5f9;cursor:pointer;-webkit-user-select:none;user-select:none;transition:background-color .15s ease,color .15s ease}.trigger-clear-btn:hover{background-color:#e2e8f0;color:var(--rpt-color-text, #0f172a)}.trigger-arrow{display:flex;align-items:center;color:var(--rpt-color-text-muted, #64748b);transition:transform .18s ease}.trigger-arrow.rotated{transform:rotate(180deg)}.rpt-combobox-menu{width:100%;min-width:180px;max-width:100vw;box-sizing:border-box;background-color:var(--rpt-color-bg, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);box-shadow:0 10px 25px -5px #00000024,0 8px 10px -6px #00000014;overflow:hidden;display:flex;flex-direction:column;animation:rpt-dropdown-pop .12s cubic-bezier(0,0,.2,1)}.rpt-combobox-menu.opens-above{box-shadow:0 -10px 25px -5px #00000024,0 -8px 10px -6px #00000014;animation:rpt-dropdown-pop-above .12s cubic-bezier(0,0,.2,1)}@keyframes rpt-dropdown-pop{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}@keyframes rpt-dropdown-pop-above{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.combobox-search-box{display:flex;align-items:center;gap:8px;padding:8px 10px;border-bottom:1px solid var(--rpt-color-border, #e2e8f0);background-color:#f8fafc}.combobox-search-icon{color:var(--rpt-color-text-muted, #94a3b8);display:flex;align-items:center;flex-shrink:0}.combobox-search-input{flex:1;border:none;outline:none;background:transparent;font-size:var(--rpt-font-size-sm, .8125rem);font-family:inherit;color:var(--rpt-color-text, #0f172a);min-width:0}.combobox-search-input::placeholder{color:#94a3b8}.combobox-search-clear{border:none;background:#e2e8f0;color:#64748b;width:16px;height:16px;border-radius:50%;font-size:.625rem;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0}.combobox-search-clear:hover{background:#cbd5e1;color:#0f172a}.combobox-options-list{max-height:220px;overflow-y:auto;overscroll-behavior:contain;padding:4px}.combobox-option-item{display:flex;align-items:center;justify-content:space-between;padding:7px 10px;border-radius:4px;font-size:var(--rpt-font-size-base, .84rem);color:var(--rpt-color-text, #0f172a);cursor:pointer;-webkit-user-select:none;user-select:none;transition:background-color .1s ease}.combobox-option-item.is-focused{background-color:var(--rpt-color-surface-hover, #f1f5f9)}.combobox-option-item.is-selected{background-color:#eff6ff;color:var(--rpt-color-primary, #2563eb);font-weight:600}.opt-label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opt-check{font-size:.875rem;font-weight:700;color:var(--rpt-color-primary, #2563eb);margin-left:8px}.combobox-status-row{padding:10px 12px;font-size:.78rem;text-align:center;color:var(--rpt-color-text-muted, #64748b)}.combobox-status-row.loading{display:flex;align-items:center;justify-content:center;gap:6px;color:#2563eb}.combobox-status-row.error{display:flex;flex-direction:column;align-items:center;gap:6px;color:var(--rpt-color-danger, #dc2626)}.trigger-retry-btn,.combobox-retry-btn,.rpt-badge-retry-btn{display:inline-flex;align-items:center;gap:3px;font-size:.6875rem;font-weight:600;color:#dc2626;background:#fee2e2;border:1px solid #fca5a5;border-radius:4px;padding:2px 7px;cursor:pointer;line-height:1.2;transition:background-color .12s ease,color .12s ease}.trigger-retry-btn:hover,.combobox-retry-btn:hover,.rpt-badge-retry-btn:hover{background:#fecaca;color:#b91c1c;border-color:#f87171}.combobox-status-row.hint{font-size:.72rem;color:#64748b;border-top:1px dashed #cbd5e1;margin-top:4px;padding:6px 10px;background-color:#f8fafc;display:flex;align-items:center;justify-content:space-between;gap:8px;position:sticky;bottom:0;z-index:2;box-shadow:0 -2px 6px #0000000a}.combobox-load-all-btn{background:#eff6ff;border:1px solid #bfdbfe;color:#2563eb;font-size:.6875rem;font-weight:600;padding:3px 8px;border-radius:4px;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:all .12s ease}.combobox-load-all-btn:hover{background:#2563eb;color:#fff;border-color:#2563eb}.rpt-error-text{font-size:.7rem;color:var(--rpt-color-danger, #ef4444);margin-top:3px;line-height:1.25;max-width:100%;overflow-wrap:anywhere;word-break:break-word;display:block}\n"] }]
|
|
4212
|
+
}, changeDetection: ChangeDetectionStrategy.OnPush, template: " <div class=\"rpt-field-group\">\n @if (config().label) {\n <label [for]=\"elementId()\" class=\"rpt-label\">\n {{ config().label }}\n @if (config().validation?.required) {\n <span class=\"rpt-required-marker\">*</span>\n }\n </label>\n }\n\n @if (isSearchable()) {\n <!-- Searchable Typeahead Combobox -->\n <div class=\"rpt-combobox-wrapper\" #comboboxWrapper #comboboxOrigin=\"cdkOverlayOrigin\" cdkOverlayOrigin>\n <button\n type=\"button\"\n class=\"rpt-combobox-trigger\"\n [id]=\"elementId()\"\n [class.rpt-invalid]=\"isInvalid()\"\n [class.is-open]=\"isOpen()\"\n [disabled]=\"formControl().disabled\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"'listbox'\"\n (click)=\"toggleOpen($event)\"\n (keydown)=\"onTriggerKeydown($event)\"\n >\n <span class=\"trigger-label\" [class.is-placeholder]=\"!selectedOption()\">\n {{ selectedOption() ? selectedOption()!.label : placeholder() }}\n </span>\n\n <div class=\"trigger-actions\">\n @if (isLoading()) {\n <span class=\"status-dot dot-pulse\" style=\"color: #2563eb; margin-right: 4px;\" title=\"Executing query on database...\"></span>\n }\n @if (loadError() && !isLoading()) {\n <button\n type=\"button\"\n class=\"trigger-retry-btn\"\n title=\"Retry database query\"\n aria-label=\"Retry database query\"\n (click)=\"retryLookup($event)\"\n >\n \u21BB Retry\n </button>\n }\n @if (selectedOption() && !formControl().disabled) {\n <span\n role=\"button\"\n tabindex=\"0\"\n class=\"trigger-clear-btn\"\n title=\"Clear selection\"\n aria-label=\"Clear selection\"\n (click)=\"clearSelection($event)\"\n (keydown.enter)=\"clearSelection($event)\"\n (keydown.space)=\"clearSelection($event)\"\n >\n \u2715\n </span>\n }\n <span class=\"trigger-arrow\" [class.rotated]=\"isOpen()\" aria-hidden=\"true\">\n <svg width=\"12\" height=\"8\" viewBox=\"0 0 12 8\" fill=\"none\">\n <path d=\"M1 1.5L6 6.5L11 1.5\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n </div>\n </button>\n </div>\n\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"comboboxOrigin\"\n [cdkConnectedOverlayOpen]=\"isOpen()\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayWidth]=\"triggerWidth()\"\n [cdkConnectedOverlayMinWidth]=\"'min(180px, calc(100vw - 16px))'\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n [cdkConnectedOverlayViewportMargin]=\"8\"\n [cdkConnectedOverlayPush]=\"true\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n (overlayOutsideClick)=\"closeDropdown()\"\n (positionChange)=\"onPositionChange($event)\"\n (attach)=\"onOverlayAttached()\"\n (detach)=\"closeDropdown()\"\n >\n <div\n class=\"rpt-combobox-menu\"\n [class.opens-above]=\"opensAbove()\"\n role=\"listbox\"\n (click)=\"$event.stopPropagation()\"\n >\n <!-- Real-time Filter Search Box -->\n <div class=\"combobox-search-box\">\n <span class=\"combobox-search-icon\" aria-hidden=\"true\">\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"></circle>\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"></line>\n </svg>\n </span>\n <input\n #searchInput\n type=\"text\"\n class=\"combobox-search-input\"\n placeholder=\"Type to filter...\"\n [value]=\"searchTerm()\"\n [attr.aria-activedescendant]=\"focusedIndex() >= 0 ? (elementId() + '_opt_' + focusedIndex()) : null\"\n (input)=\"onSearchInput($event)\"\n (keydown)=\"onSearchKeydown($event)\"\n />\n @if (searchTerm()) {\n <button\n type=\"button\"\n class=\"combobox-search-clear\"\n title=\"Clear filter\"\n (click)=\"clearSearch($event)\"\n >\n \u2715\n </button>\n }\n </div>\n\n <!-- Filtered Options List -->\n <div class=\"combobox-options-list\" #optionsListEl (scroll)=\"onOptionsScroll($event)\">\n @if (waitingFor().length > 0) {\n <div class=\"combobox-status-row waiting\">\n <span>Waiting for {{ waitingLabel() }}...</span>\n </div>\n } @else if (isLoading()) {\n <div class=\"combobox-status-row loading\">\n <span class=\"status-dot dot-pulse\"></span>\n <span>Executing query on database (may take 1\u20132 minutes on local database)...</span>\n </div>\n } @else if (loadError()) {\n <div class=\"combobox-status-row error\">\n <span>Error: {{ loadError() }}</span>\n <button type=\"button\" class=\"combobox-retry-btn\" (click)=\"retryLookup($event)\">\u21BB Retry Query</button>\n </div>\n } @else if (isDynamicSource() && hasLoaded() && allOptions().length === 0) {\n <div class=\"combobox-status-row empty\">\n <span>No records found (check parameters)</span>\n </div>\n } @else if (filteredOptions().length === 0) {\n <div class=\"combobox-status-row empty\">\n <span>No options matching \"{{ searchTerm() }}\"</span>\n </div>\n } @else {\n @for (opt of visibleFilteredOptions(); track opt.key; let i = $index) {\n <div\n role=\"option\"\n [id]=\"elementId() + '_opt_' + i\"\n class=\"combobox-option-item\"\n [class.is-selected]=\"isSelected(opt)\"\n [class.is-focused]=\"focusedIndex() === i\"\n [attr.aria-selected]=\"isSelected(opt)\"\n (click)=\"selectOption(opt)\"\n (mouseenter)=\"focusedIndex.set(i)\"\n >\n <span class=\"opt-label\">{{ opt.label }}</span>\n @if (isSelected(opt)) {\n <span class=\"opt-check\" aria-hidden=\"true\">\u2713</span>\n }\n </div>\n }\n @if (excessFilteredCount() > 0) {\n <div class=\"combobox-status-row hint\">\n <span>Showing {{ visibleFilteredOptions().length }} of {{ filteredOptions().length }} matches. Scroll down or</span>\n <button\n type=\"button\"\n class=\"combobox-load-all-btn\"\n (click)=\"loadAllFiltered(); $event.stopPropagation();\"\n >\n Load all {{ filteredOptions().length }}\n </button>\n </div>\n }\n }\n </div>\n </div>\n </ng-template>\n } @else {\n <!-- Native Browser Select Fallback -->\n <div class=\"rpt-select-wrapper\">\n <select\n [id]=\"elementId()\"\n [formControl]=\"formControl()\"\n [class.rpt-invalid]=\"isInvalid()\"\n class=\"rpt-select\"\n >\n @if (waitingFor().length > 0) {\n <option value=\"\" disabled selected>Waiting for {{ waitingLabel() }}...</option>\n } @else if (isLoading()) {\n <option value=\"\" disabled selected>Loading options from database...</option>\n } @else if (loadError()) {\n <option value=\"\" disabled selected>Error: {{ loadError() }}</option>\n } @else if (isDynamicSource() && hasLoaded() && allOptions().length === 0) {\n <option value=\"\" disabled selected>-- No records found (check parameters) --</option>\n } @else if (placeholder()) {\n <option value=\"\" [disabled]=\"config().validation?.required\">{{ placeholder() }}</option>\n }\n @for (opt of renderedOptions(); track opt.key) {\n <option [ngValue]=\"opt.key\">{{ opt.label }}</option>\n }\n @if (hasExcessOptions()) {\n <option value=\"\" disabled>... and {{ excessOptionsCount() }} more records (refine filter) ...</option>\n }\n </select>\n <div class=\"rpt-select-arrow\" aria-hidden=\"true\">\n <svg width=\"12\" height=\"8\" viewBox=\"0 0 12 8\" fill=\"none\">\n <path d=\"M1 1.5L6 6.5L11 1.5\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </div>\n </div>\n }\n\n @if (showStatus() && isDynamicSource() && statusText()) {\n <div class=\"rpt-status-badge\" [attr.data-status]=\"statusKind()\">\n @switch (statusKind()) {\n @case ('loading') {\n <span class=\"status-dot dot-pulse\"></span>\n <span>Loading from database\u2026</span>\n }\n @case ('ok') {\n <span class=\"status-dot dot-ok\"></span>\n <span>{{ dynamicOptions().length }} records loaded</span>\n }\n @case ('waiting') {\n <span class=\"status-dot dot-warn\"></span>\n <span>Waiting for:</span>\n <div class=\"waiting-tokens\">\n @for (k of waitingFor(); track k) {\n <span class=\"waiting-token\">{{ k }}</span>\n }\n </div>\n }\n @case ('error') {\n <span class=\"status-dot dot-err\"></span>\n <span>{{ loadError() }}</span>\n <button type=\"button\" class=\"rpt-badge-retry-btn\" (click)=\"retryLookup($event)\">\u21BB Retry</button>\n }\n @case ('empty') {\n <span class=\"status-dot\"></span>\n <span>No records found for parameters</span>\n }\n @default {\n <span class=\"status-dot\"></span>\n }\n }\n </div>\n }\n\n @if (errorMessage()) {\n <span class=\"rpt-error-text\">{{ errorMessage() }}</span>\n }\n </div>", styles: [":host{display:block;width:100%;max-width:100%;min-width:0;box-sizing:border-box;font-family:var(--rpt-font-family, inherit);position:relative}.rpt-status-badge{display:flex;flex-wrap:wrap;align-items:center;gap:4px;font-size:.6875rem;padding:3px 6px;border-radius:4px;margin-top:4px;font-weight:500;line-height:1.3;max-width:100%;box-sizing:border-box;overflow:hidden;word-break:break-word}.rpt-status-badge>span{overflow-wrap:anywhere;word-break:break-word}.rpt-status-badge[data-status=loading]{background:#eff6ff;color:#2563eb;border:1px solid #dbeafe}.rpt-status-badge[data-status=ok]{background:#f0fdf4;color:#16a34a;border:1px solid #dcfce7}.rpt-status-badge[data-status=waiting]{background:#fffbeb;color:#d97706;border:1px solid #fef3c7}.rpt-status-badge[data-status=error]{background:#fef2f2;color:#dc2626;border:1px solid #fee2e2}.rpt-status-badge[data-status=empty]{background:#f8fafc;color:#64748b;border:1px solid #e2e8f0}.waiting-tokens{display:inline-flex;flex-wrap:wrap;align-items:center;gap:3px;margin-left:2px;max-width:100%}.waiting-token{background:#b453091f;border:1px solid rgba(180,83,9,.3);padding:1px 5px;border-radius:3px;font-weight:700;font-size:.625rem;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-block;box-sizing:border-box}.status-dot{width:6px;height:6px;border-radius:50%;background:currentColor}.dot-pulse{animation:rpt-pulse 1s infinite alternate}@keyframes rpt-pulse{0%{opacity:.3}to{opacity:1}}.rpt-field-group{display:flex;flex-direction:column;max-width:100%;min-width:0;gap:4px;box-sizing:border-box;position:relative}.rpt-label{font-size:var(--rpt-font-size-sm, .75rem);font-weight:500;color:var(--rpt-color-text, #0f172a);flex-shrink:0}.rpt-required-marker{color:var(--rpt-color-danger, #ef4444);margin-left:2px}.rpt-select-wrapper{position:relative;width:100%;flex:1;min-height:0;display:flex;align-items:center}.rpt-select{width:100%;height:100%;min-height:38px;box-sizing:border-box;padding:6px 36px 6px 12px;font-size:var(--rpt-font-size-base, .875rem);color:var(--rpt-color-text, #0f172a);background-color:var(--rpt-color-bg, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);appearance:none;-webkit-appearance:none;outline:none;cursor:pointer;transition:border-color var(--rpt-transition-speed, .15s) ease,box-shadow var(--rpt-transition-speed, .15s) ease}.rpt-select:hover:not(:disabled){border-color:var(--rpt-color-border-hover, #94a3b8)}.rpt-select:focus{border-color:var(--rpt-color-border-focus, #2563eb);box-shadow:var(--rpt-box-shadow-focus, 0 0 0 3px rgba(37, 99, 235, .25))}.rpt-select:disabled{background-color:var(--rpt-color-surface, #f8fafc);color:var(--rpt-color-text-muted, #64748b);cursor:not-allowed}.rpt-select.rpt-invalid{border-color:var(--rpt-color-danger, #ef4444)}.rpt-select.rpt-invalid:focus{box-shadow:0 0 0 3px #ef444440}.rpt-select-arrow{position:absolute;right:12px;top:50%;transform:translateY(-50%);pointer-events:none;color:var(--rpt-color-text-muted, #64748b);display:flex;align-items:center}.rpt-combobox-wrapper{position:relative;width:100%;flex:1;min-height:0}.rpt-combobox-trigger{width:100%;height:100%;min-height:38px;box-sizing:border-box;padding:6px 10px 6px 12px;display:flex;align-items:center;justify-content:space-between;gap:8px;font-size:var(--rpt-font-size-base, .875rem);font-family:inherit;color:var(--rpt-color-text, #0f172a);background-color:var(--rpt-color-bg, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);outline:none;cursor:pointer;text-align:left;transition:border-color var(--rpt-transition-speed, .15s) ease,box-shadow var(--rpt-transition-speed, .15s) ease}.rpt-combobox-trigger:hover:not(:disabled){border-color:var(--rpt-color-border-hover, #94a3b8)}.rpt-combobox-trigger:focus-visible,.rpt-combobox-trigger.is-open{border-color:var(--rpt-color-border-focus, #2563eb);box-shadow:var(--rpt-box-shadow-focus, 0 0 0 3px rgba(37, 99, 235, .25))}.rpt-combobox-trigger:disabled{background-color:var(--rpt-color-surface, #f8fafc);color:var(--rpt-color-text-muted, #64748b);cursor:not-allowed}.rpt-combobox-trigger.rpt-invalid{border-color:var(--rpt-color-danger, #ef4444)}.rpt-combobox-trigger.rpt-invalid:focus-visible{box-shadow:0 0 0 3px #ef444440}.trigger-label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.3}.trigger-label.is-placeholder{color:var(--rpt-color-text-muted, #94a3b8)}.trigger-actions{display:flex;align-items:center;gap:6px;flex-shrink:0}.trigger-clear-btn{display:flex;align-items:center;justify-content:center;width:18px;height:18px;border-radius:50%;font-size:.6875rem;color:var(--rpt-color-text-muted, #64748b);background-color:#f1f5f9;cursor:pointer;-webkit-user-select:none;user-select:none;transition:background-color .15s ease,color .15s ease}.trigger-clear-btn:hover{background-color:#e2e8f0;color:var(--rpt-color-text, #0f172a)}.trigger-arrow{display:flex;align-items:center;color:var(--rpt-color-text-muted, #64748b);transition:transform .18s ease}.trigger-arrow.rotated{transform:rotate(180deg)}.rpt-combobox-menu{width:100%;min-width:0;max-width:100vw;box-sizing:border-box;background-color:var(--rpt-color-bg, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);box-shadow:0 10px 25px -5px #00000024,0 8px 10px -6px #00000014;overflow:hidden;display:flex;flex-direction:column;animation:rpt-dropdown-pop .12s cubic-bezier(0,0,.2,1)}.rpt-combobox-menu.opens-above{box-shadow:0 -10px 25px -5px #00000024,0 -8px 10px -6px #00000014;animation:rpt-dropdown-pop-above .12s cubic-bezier(0,0,.2,1)}@keyframes rpt-dropdown-pop{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}@keyframes rpt-dropdown-pop-above{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.combobox-search-box{display:flex;align-items:center;gap:8px;padding:8px 10px;border-bottom:1px solid var(--rpt-color-border, #e2e8f0);background-color:#f8fafc}.combobox-search-icon{color:var(--rpt-color-text-muted, #94a3b8);display:flex;align-items:center;flex-shrink:0}.combobox-search-input{flex:1;border:none;outline:none;background:transparent;font-size:var(--rpt-font-size-sm, .8125rem);font-family:inherit;color:var(--rpt-color-text, #0f172a);min-width:0}.combobox-search-input::placeholder{color:#94a3b8}.combobox-search-clear{border:none;background:#e2e8f0;color:#64748b;width:16px;height:16px;border-radius:50%;font-size:.625rem;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0}.combobox-search-clear:hover{background:#cbd5e1;color:#0f172a}.combobox-options-list{max-height:220px;overflow-y:auto;overscroll-behavior:contain;padding:4px}.combobox-option-item{display:flex;align-items:center;justify-content:space-between;padding:7px 10px;border-radius:4px;font-size:var(--rpt-font-size-base, .84rem);color:var(--rpt-color-text, #0f172a);cursor:pointer;-webkit-user-select:none;user-select:none;transition:background-color .1s ease}.combobox-option-item.is-focused{background-color:var(--rpt-color-surface-hover, #f1f5f9)}.combobox-option-item.is-selected{background-color:#eff6ff;color:var(--rpt-color-primary, #2563eb);font-weight:600}.opt-label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.opt-check{font-size:.875rem;font-weight:700;color:var(--rpt-color-primary, #2563eb);margin-left:8px}.combobox-status-row{padding:10px 12px;font-size:.78rem;text-align:center;color:var(--rpt-color-text-muted, #64748b)}.combobox-status-row.loading{display:flex;align-items:center;justify-content:center;gap:6px;color:#2563eb}.combobox-status-row.error{display:flex;flex-direction:column;align-items:center;gap:6px;color:var(--rpt-color-danger, #dc2626)}.trigger-retry-btn,.combobox-retry-btn,.rpt-badge-retry-btn{display:inline-flex;align-items:center;gap:3px;font-size:.6875rem;font-weight:600;color:#dc2626;background:#fee2e2;border:1px solid #fca5a5;border-radius:4px;padding:2px 7px;cursor:pointer;line-height:1.2;transition:background-color .12s ease,color .12s ease}.trigger-retry-btn:hover,.combobox-retry-btn:hover,.rpt-badge-retry-btn:hover{background:#fecaca;color:#b91c1c;border-color:#f87171}.combobox-status-row.hint{font-size:.72rem;color:#64748b;border-top:1px dashed #cbd5e1;margin-top:4px;padding:6px 10px;background-color:#f8fafc;display:flex;align-items:center;justify-content:space-between;gap:8px;position:sticky;bottom:0;z-index:2;box-shadow:0 -2px 6px #0000000a}.combobox-load-all-btn{background:#eff6ff;border:1px solid #bfdbfe;color:#2563eb;font-size:.6875rem;font-weight:600;padding:3px 8px;border-radius:4px;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:all .12s ease}.combobox-load-all-btn:hover{background:#2563eb;color:#fff;border-color:#2563eb}.rpt-error-text{font-size:.7rem;color:var(--rpt-color-danger, #ef4444);margin-top:3px;line-height:1.25;max-width:100%;overflow-wrap:anywhere;word-break:break-word;display:block}\n"] }]
|
|
4081
4213
|
}], ctorParameters: () => [], propDecorators: { onDocumentClick: [{
|
|
4082
4214
|
type: HostListener,
|
|
4083
4215
|
args: ['document:click', ['$event']]
|
|
@@ -5961,9 +6093,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
5961
6093
|
|
|
5962
6094
|
class RptSearchableSelectComponent {
|
|
5963
6095
|
elementRef;
|
|
5964
|
-
overlay = inject(Overlay);
|
|
5965
6096
|
panelId = 'rpt-select-panel-' + Math.random().toString(36).substring(2, 9);
|
|
5966
|
-
scrollStrategy = this.
|
|
6097
|
+
scrollStrategy = inject(ReportOverlayService).scrollStrategy(() => this.elementRef.nativeElement);
|
|
5967
6098
|
triggerWidth = signal('auto');
|
|
5968
6099
|
overlayPositions = [
|
|
5969
6100
|
{
|
|
@@ -6313,7 +6444,7 @@ class RptSearchableSelectComponent {
|
|
|
6313
6444
|
useExisting: forwardRef(() => RptSearchableSelectComponent),
|
|
6314
6445
|
multi: true,
|
|
6315
6446
|
},
|
|
6316
|
-
], viewQueries: [{ propertyName: "triggerBtnEl", first: true, predicate: ["triggerBtn"], descendants: true, isSignal: true }, { propertyName: "searchInputEl", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }, { propertyName: "optionsListEl", first: true, predicate: ["optionsList"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"rpt-search-select-wrapper\" #origin=\"cdkOverlayOrigin\" cdkOverlayOrigin>\n <!-- Trigger Button -->\n <button\n #triggerBtn\n type=\"button\"\n class=\"select-trigger\"\n [id]=\"id()\"\n [disabled]=\"isDisabled()\"\n [class.is-open]=\"isOpen()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-label]=\"ariaLabel() || placeholder()\"\n (click)=\"toggle($event)\"\n (keydown)=\"onTriggerKeydown($event)\"\n >\n <div class=\"trigger-label\" [class.is-placeholder]=\"!selectedOption()\">\n @if (selectedOption()) {\n <span class=\"selected-text\">{{ selectedOption()!.label }}</span>\n @if (selectedOption()!.sublabel) {\n <span class=\"selected-sublabel\">{{ selectedOption()!.sublabel }}</span>\n }\n @if (selectedOption()!.badge) {\n <span class=\"selected-badge\">{{ selectedOption()!.badge }}</span>\n }\n } @else {\n <span class=\"placeholder-text\">{{ placeholder() }}</span>\n }\n </div>\n\n <div class=\"trigger-actions\">\n @if (loading()) {\n <span class=\"spinner-dot\" title=\"Loading options...\"></span>\n }\n\n @if (selectedOption() && clearable() && !isDisabled()) {\n <button\n type=\"button\"\n class=\"trigger-clear-btn\"\n title=\"Clear selection\"\n aria-label=\"Clear selection\"\n (click)=\"clearSelection($event)\"\n >\n \u2715\n </button>\n }\n\n <span class=\"trigger-arrow\" [class.rotated]=\"isOpen()\" aria-hidden=\"true\">\n <svg width=\"10\" height=\"6\" viewBox=\"0 0 10 6\" fill=\"none\">\n <path d=\"M1 1L5 5L9 1\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n </div>\n </button>\n</div>\n\n<!-- Dropdown Popover Panel in CDK Overlay (attached to body, outside any clipped card) -->\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"origin\"\n [cdkConnectedOverlayOpen]=\"isOpen()\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayWidth]=\"triggerWidth()\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n [cdkConnectedOverlayViewportMargin]=\"8\"\n [cdkConnectedOverlayPush]=\"true\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n (overlayOutsideClick)=\"close()\"\n (positionChange)=\"onPositionChange($event)\"\n (attach)=\"onOverlayAttached()\"\n (detach)=\"close()\"\n>\n <div\n class=\"select-panel\"\n [id]=\"panelId\"\n [class.opens-above]=\"opensAbove()\"\n [style.max-height]=\"panelMaxHeight()\"\n role=\"listbox\"\n (click)=\"$event.stopPropagation()\"\n >\n <!-- Search Input Box -->\n @if (searchable()) {\n <div class=\"select-search-box\">\n <span class=\"search-icon\" aria-hidden=\"true\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"></circle>\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"></line>\n </svg>\n </span>\n <input\n #searchInput\n type=\"text\"\n class=\"search-input\"\n [placeholder]=\"searchPlaceholder()\"\n [value]=\"searchTerm()\"\n (input)=\"onSearchInput($event)\"\n (keydown)=\"onSearchKeydown($event)\"\n />\n @if (searchTerm()) {\n <button\n type=\"button\"\n class=\"search-clear-btn\"\n title=\"Clear search\"\n (click)=\"clearSearch($event)\"\n >\n \u2715\n </button>\n }\n </div>\n }\n\n <!-- Options List -->\n <div class=\"select-options-list\" #optionsList (scroll)=\"onOptionsScroll($event)\">\n @if (loading()) {\n <div class=\"select-status-row loading\">\n <span class=\"spinner-dot\"></span>\n <span>{{ loadingText() }}</span>\n </div>\n } @else if (normalizedOptions().length === 0) {\n <div class=\"select-status-row empty\">\n <span>{{ emptyText() }}</span>\n </div>\n } @else if (visibleFilteredOptions().length === 0) {\n <div class=\"select-status-row empty\">\n <span>No options match \"{{ searchTerm() }}\"</span>\n </div>\n } @else {\n @for (opt of visibleFilteredOptions(); track opt.value; let i = $index) {\n <div\n role=\"option\"\n class=\"select-option-item\"\n [class.is-selected]=\"isSelected(opt)\"\n [class.is-focused]=\"focusedIndex() === i\"\n [class.is-disabled]=\"opt.disabled\"\n [attr.aria-selected]=\"isSelected(opt)\"\n (click)=\"selectOption(opt)\"\n (mouseenter)=\"focusedIndex.set(i)\"\n >\n <div class=\"option-content\">\n <span class=\"option-label\" [title]=\"opt.label\">{{ opt.label }}</span>\n @if (opt.sublabel) {\n <span class=\"option-sublabel\" [title]=\"opt.sublabel\">{{ opt.sublabel }}</span>\n }\n </div>\n\n <div class=\"option-meta\">\n @if (opt.badge) {\n <span class=\"option-badge\">{{ opt.badge }}</span>\n }\n @if (isSelected(opt)) {\n <span class=\"option-check\" aria-hidden=\"true\">\u2713</span>\n }\n </div>\n </div>\n }\n\n @if (excessCount() > 0) {\n <div class=\"select-excess-hint\">\n <span class=\"excess-text\">Showing {{ visibleFilteredOptions().length }} of {{ filteredOptions().length }} matches. Scroll down to browse or</span>\n <button\n type=\"button\"\n class=\"select-load-all-btn\"\n (click)=\"loadAll(); $event.stopPropagation();\"\n >\n Load all {{ filteredOptions().length }}\n </button>\n </div>\n }\n }\n </div>\n </div>\n</ng-template>\n", styles: [":host{display:block;width:100%;position:relative;box-sizing:border-box}.rpt-search-select-wrapper{position:relative;width:100%;box-sizing:border-box}:host(.size-sm) .select-trigger{min-height:28px;padding:4px 8px;font-size:var(--rpt-font-size-sm, .75rem)}:host(.size-md) .select-trigger{min-height:36px;padding:6px 10px;font-size:var(--rpt-font-size-base, .8125rem)}:host(.size-lg) .select-trigger{min-height:42px;padding:8px 12px;font-size:var(--rpt-font-size-base, .875rem)}.select-trigger{width:100%;box-sizing:border-box;display:flex;align-items:center;justify-content:space-between;gap:6px;background-color:var(--rpt-color-bg, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-sm, 4px);color:var(--rpt-color-text, #0f172a);font-family:inherit;outline:none;cursor:pointer;text-align:left;transition:border-color var(--rpt-transition-speed, .15s) ease,box-shadow var(--rpt-transition-speed, .15s) ease}.select-trigger:hover:not(:disabled){border-color:var(--rpt-color-border-hover, #94a3b8)}.select-trigger:focus-visible,.select-trigger.is-open{border-color:var(--rpt-color-primary, #2563eb);box-shadow:0 0 0 2px #2563eb33}.select-trigger:disabled{background-color:var(--rpt-color-surface, #f8fafc);color:var(--rpt-color-text-muted, #94a3b8);border-color:var(--rpt-color-border, #e2e8f0);cursor:not-allowed}.trigger-label{flex:1;min-width:0;display:flex;align-items:center;gap:6px;overflow:hidden}.selected-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500;color:var(--rpt-color-text, #0f172a)}.selected-sublabel{font-size:.675rem;color:var(--rpt-color-text-muted, #64748b);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex-shrink:0}.selected-badge{font-size:.6rem;font-weight:600;padding:1px 4px;border-radius:3px;background-color:#e2e8f0;color:#475569;text-transform:uppercase;letter-spacing:.03em;flex-shrink:0}.placeholder-text{color:var(--rpt-color-text-muted, #94a3b8);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.trigger-actions{display:flex;align-items:center;gap:4px;flex-shrink:0}.trigger-clear-btn{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;border-radius:50%;border:none;font-size:.55rem;color:var(--rpt-color-text-muted, #64748b);background-color:#f1f5f9;cursor:pointer;padding:0;line-height:1;transition:background-color .12s ease,color .12s ease}.trigger-clear-btn:hover{background-color:#e2e8f0;color:var(--rpt-color-text, #0f172a)}.trigger-arrow{display:flex;align-items:center;color:var(--rpt-color-text-muted, #64748b);transition:transform .18s ease}.trigger-arrow.rotated{transform:rotate(180deg)}.select-panel{width:100%;min-width:180px;max-width:100vw;box-sizing:border-box;background-color:var(--rpt-color-bg, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);box-shadow:0 10px 25px -5px #00000024,0 8px 10px -6px #00000014;overflow:hidden;display:flex;flex-direction:column;animation:select-pop .12s cubic-bezier(0,0,.2,1)}.select-panel.opens-above{box-shadow:0 -10px 25px -5px #00000024,0 -8px 10px -6px #00000014;animation:select-pop-above .12s cubic-bezier(0,0,.2,1)}@keyframes select-pop{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}@keyframes select-pop-above{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.select-search-box{display:flex;align-items:center;gap:6px;padding:6px 8px;background-color:#f8fafc;border-bottom:1px solid var(--rpt-color-border, #e2e8f0)}.search-icon{color:var(--rpt-color-text-muted, #94a3b8);display:flex;align-items:center;flex-shrink:0}.search-input{flex:1;border:none;outline:none;background:transparent;font-size:var(--rpt-font-size-sm, .75rem);font-family:inherit;color:var(--rpt-color-text, #0f172a);min-width:0}.search-input::placeholder{color:#94a3b8}.search-clear-btn{border:none;background:#e2e8f0;color:#64748b;width:14px;height:14px;border-radius:50%;font-size:.5rem;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;transition:background-color .12s}.search-clear-btn:hover{background:#cbd5e1;color:#0f172a}.select-options-list{overflow-y:auto;overscroll-behavior:contain;padding:3px}.select-option-item{display:flex;align-items:center;justify-content:space-between;gap:6px;padding:5px 8px;border-radius:4px;font-size:var(--rpt-font-size-sm, .75rem);color:var(--rpt-color-text, #0f172a);cursor:pointer;-webkit-user-select:none;user-select:none;transition:background-color .1s ease}.select-option-item.is-focused{background-color:var(--rpt-color-surface-hover, #f1f5f9)}.select-option-item.is-selected{background-color:#eff6ff;color:var(--rpt-color-primary, #2563eb);font-weight:600}.select-option-item.is-disabled{opacity:.45;cursor:not-allowed;pointer-events:none}.option-content{display:flex;flex-direction:column;min-width:0;flex:1}.option-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.35}.option-sublabel{font-size:.65rem;color:var(--rpt-color-text-muted, #64748b);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.2}.option-meta{display:flex;align-items:center;gap:6px;flex-shrink:0}.option-badge{font-size:.6rem;font-weight:600;padding:1px 4px;border-radius:3px;background-color:#e2e8f0;color:#475569;text-transform:uppercase;letter-spacing:.03em}.select-option-item.is-selected .option-badge{background-color:#dbeafe;color:#1e40af}.option-check{font-size:.75rem;font-weight:700;color:var(--rpt-color-primary, #2563eb)}.select-status-row{padding:10px 12px;font-size:.72rem;text-align:center;color:var(--rpt-color-text-muted, #64748b)}.select-status-row.loading{display:flex;align-items:center;justify-content:center;gap:6px;color:var(--rpt-color-primary, #2563eb)}.select-status-row.empty{color:var(--rpt-color-text-muted, #94a3b8)}.select-excess-hint{font-size:.6875rem;color:var(--rpt-color-text-secondary, #475569);display:flex;align-items:center;justify-content:space-between;gap:8px;padding:6px 8px;background-color:#f8fafc;border-top:1px dashed var(--rpt-color-border, #cbd5e1);margin-top:2px;position:sticky;bottom:0;z-index:2;box-shadow:0 -2px 6px #0000000a}.excess-text{flex:1;line-height:1.25}.select-load-all-btn{background:#eff6ff;border:1px solid #bfdbfe;color:#2563eb;font-size:.6875rem;font-weight:600;padding:3px 8px;border-radius:4px;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:all .12s ease}.select-load-all-btn:hover{background:#2563eb;color:#fff;border-color:#2563eb}.spinner-dot{display:inline-block;width:11px;height:11px;border:2px solid #bfdbfe;border-top-color:var(--rpt-color-primary, #2563eb);border-radius:50%;animation:select-spin .6s linear infinite}@keyframes select-spin{to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i2.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i2.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6447
|
+
], viewQueries: [{ propertyName: "triggerBtnEl", first: true, predicate: ["triggerBtn"], descendants: true, isSignal: true }, { propertyName: "searchInputEl", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }, { propertyName: "optionsListEl", first: true, predicate: ["optionsList"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"rpt-search-select-wrapper\" #origin=\"cdkOverlayOrigin\" cdkOverlayOrigin>\n <!-- Trigger Button -->\n <button\n #triggerBtn\n type=\"button\"\n class=\"select-trigger\"\n [id]=\"id()\"\n [disabled]=\"isDisabled()\"\n [class.is-open]=\"isOpen()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-label]=\"ariaLabel() || placeholder()\"\n (click)=\"toggle($event)\"\n (keydown)=\"onTriggerKeydown($event)\"\n >\n <div class=\"trigger-label\" [class.is-placeholder]=\"!selectedOption()\">\n @if (selectedOption()) {\n <span class=\"selected-text\">{{ selectedOption()!.label }}</span>\n @if (selectedOption()!.sublabel) {\n <span class=\"selected-sublabel\">{{ selectedOption()!.sublabel }}</span>\n }\n @if (selectedOption()!.badge) {\n <span class=\"selected-badge\">{{ selectedOption()!.badge }}</span>\n }\n } @else {\n <span class=\"placeholder-text\">{{ placeholder() }}</span>\n }\n </div>\n\n <div class=\"trigger-actions\">\n @if (loading()) {\n <span class=\"spinner-dot\" title=\"Loading options...\"></span>\n }\n\n @if (selectedOption() && clearable() && !isDisabled()) {\n <button\n type=\"button\"\n class=\"trigger-clear-btn\"\n title=\"Clear selection\"\n aria-label=\"Clear selection\"\n (click)=\"clearSelection($event)\"\n >\n \u2715\n </button>\n }\n\n <span class=\"trigger-arrow\" [class.rotated]=\"isOpen()\" aria-hidden=\"true\">\n <svg width=\"10\" height=\"6\" viewBox=\"0 0 10 6\" fill=\"none\">\n <path d=\"M1 1L5 5L9 1\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n </div>\n </button>\n</div>\n\n<!-- Dropdown Popover Panel in CDK Overlay (attached to body, outside any clipped card) -->\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"origin\"\n [cdkConnectedOverlayOpen]=\"isOpen()\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayWidth]=\"triggerWidth()\"\n [cdkConnectedOverlayMinWidth]=\"'min(180px, calc(100vw - 16px))'\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n [cdkConnectedOverlayViewportMargin]=\"8\"\n [cdkConnectedOverlayPush]=\"true\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n (overlayOutsideClick)=\"close()\"\n (positionChange)=\"onPositionChange($event)\"\n (attach)=\"onOverlayAttached()\"\n (detach)=\"close()\"\n>\n <div\n class=\"select-panel\"\n [id]=\"panelId\"\n [class.opens-above]=\"opensAbove()\"\n [style.max-height]=\"panelMaxHeight()\"\n role=\"listbox\"\n (click)=\"$event.stopPropagation()\"\n >\n <!-- Search Input Box -->\n @if (searchable()) {\n <div class=\"select-search-box\">\n <span class=\"search-icon\" aria-hidden=\"true\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"></circle>\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"></line>\n </svg>\n </span>\n <input\n #searchInput\n type=\"text\"\n class=\"search-input\"\n [placeholder]=\"searchPlaceholder()\"\n [value]=\"searchTerm()\"\n (input)=\"onSearchInput($event)\"\n (keydown)=\"onSearchKeydown($event)\"\n />\n @if (searchTerm()) {\n <button\n type=\"button\"\n class=\"search-clear-btn\"\n title=\"Clear search\"\n (click)=\"clearSearch($event)\"\n >\n \u2715\n </button>\n }\n </div>\n }\n\n <!-- Options List -->\n <div class=\"select-options-list\" #optionsList (scroll)=\"onOptionsScroll($event)\">\n @if (loading()) {\n <div class=\"select-status-row loading\">\n <span class=\"spinner-dot\"></span>\n <span>{{ loadingText() }}</span>\n </div>\n } @else if (normalizedOptions().length === 0) {\n <div class=\"select-status-row empty\">\n <span>{{ emptyText() }}</span>\n </div>\n } @else if (visibleFilteredOptions().length === 0) {\n <div class=\"select-status-row empty\">\n <span>No options match \"{{ searchTerm() }}\"</span>\n </div>\n } @else {\n @for (opt of visibleFilteredOptions(); track opt.value; let i = $index) {\n <div\n role=\"option\"\n class=\"select-option-item\"\n [class.is-selected]=\"isSelected(opt)\"\n [class.is-focused]=\"focusedIndex() === i\"\n [class.is-disabled]=\"opt.disabled\"\n [attr.aria-selected]=\"isSelected(opt)\"\n (click)=\"selectOption(opt)\"\n (mouseenter)=\"focusedIndex.set(i)\"\n >\n <div class=\"option-content\">\n <span class=\"option-label\" [title]=\"opt.label\">{{ opt.label }}</span>\n @if (opt.sublabel) {\n <span class=\"option-sublabel\" [title]=\"opt.sublabel\">{{ opt.sublabel }}</span>\n }\n </div>\n\n <div class=\"option-meta\">\n @if (opt.badge) {\n <span class=\"option-badge\">{{ opt.badge }}</span>\n }\n @if (isSelected(opt)) {\n <span class=\"option-check\" aria-hidden=\"true\">\u2713</span>\n }\n </div>\n </div>\n }\n\n @if (excessCount() > 0) {\n <div class=\"select-excess-hint\">\n <span class=\"excess-text\">Showing {{ visibleFilteredOptions().length }} of {{ filteredOptions().length }} matches. Scroll down to browse or</span>\n <button\n type=\"button\"\n class=\"select-load-all-btn\"\n (click)=\"loadAll(); $event.stopPropagation();\"\n >\n Load all {{ filteredOptions().length }}\n </button>\n </div>\n }\n }\n </div>\n </div>\n</ng-template>\n", styles: [":host{display:block;width:100%;position:relative;box-sizing:border-box}.rpt-search-select-wrapper{position:relative;width:100%;box-sizing:border-box}:host(.size-sm) .select-trigger{min-height:28px;padding:4px 8px;font-size:var(--rpt-font-size-sm, .75rem)}:host(.size-md) .select-trigger{min-height:36px;padding:6px 10px;font-size:var(--rpt-font-size-base, .8125rem)}:host(.size-lg) .select-trigger{min-height:42px;padding:8px 12px;font-size:var(--rpt-font-size-base, .875rem)}.select-trigger{width:100%;box-sizing:border-box;display:flex;align-items:center;justify-content:space-between;gap:6px;background-color:var(--rpt-color-bg, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-sm, 4px);color:var(--rpt-color-text, #0f172a);font-family:inherit;outline:none;cursor:pointer;text-align:left;transition:border-color var(--rpt-transition-speed, .15s) ease,box-shadow var(--rpt-transition-speed, .15s) ease}.select-trigger:hover:not(:disabled){border-color:var(--rpt-color-border-hover, #94a3b8)}.select-trigger:focus-visible,.select-trigger.is-open{border-color:var(--rpt-color-primary, #2563eb);box-shadow:0 0 0 2px #2563eb33}.select-trigger:disabled{background-color:var(--rpt-color-surface, #f8fafc);color:var(--rpt-color-text-muted, #94a3b8);border-color:var(--rpt-color-border, #e2e8f0);cursor:not-allowed}.trigger-label{flex:1;min-width:0;display:flex;align-items:center;gap:6px;overflow:hidden}.selected-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500;color:var(--rpt-color-text, #0f172a)}.selected-sublabel{font-size:.675rem;color:var(--rpt-color-text-muted, #64748b);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex-shrink:0}.selected-badge{font-size:.6rem;font-weight:600;padding:1px 4px;border-radius:3px;background-color:#e2e8f0;color:#475569;text-transform:uppercase;letter-spacing:.03em;flex-shrink:0}.placeholder-text{color:var(--rpt-color-text-muted, #94a3b8);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.trigger-actions{display:flex;align-items:center;gap:4px;flex-shrink:0}.trigger-clear-btn{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;border-radius:50%;border:none;font-size:.55rem;color:var(--rpt-color-text-muted, #64748b);background-color:#f1f5f9;cursor:pointer;padding:0;line-height:1;transition:background-color .12s ease,color .12s ease}.trigger-clear-btn:hover{background-color:#e2e8f0;color:var(--rpt-color-text, #0f172a)}.trigger-arrow{display:flex;align-items:center;color:var(--rpt-color-text-muted, #64748b);transition:transform .18s ease}.trigger-arrow.rotated{transform:rotate(180deg)}.select-panel{width:100%;min-width:0;max-width:100vw;box-sizing:border-box;background-color:var(--rpt-color-bg, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);box-shadow:0 10px 25px -5px #00000024,0 8px 10px -6px #00000014;overflow:hidden;display:flex;flex-direction:column;animation:select-pop .12s cubic-bezier(0,0,.2,1)}.select-panel.opens-above{box-shadow:0 -10px 25px -5px #00000024,0 -8px 10px -6px #00000014;animation:select-pop-above .12s cubic-bezier(0,0,.2,1)}@keyframes select-pop{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}@keyframes select-pop-above{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.select-search-box{display:flex;align-items:center;gap:6px;padding:6px 8px;background-color:#f8fafc;border-bottom:1px solid var(--rpt-color-border, #e2e8f0)}.search-icon{color:var(--rpt-color-text-muted, #94a3b8);display:flex;align-items:center;flex-shrink:0}.search-input{flex:1;border:none;outline:none;background:transparent;font-size:var(--rpt-font-size-sm, .75rem);font-family:inherit;color:var(--rpt-color-text, #0f172a);min-width:0}.search-input::placeholder{color:#94a3b8}.search-clear-btn{border:none;background:#e2e8f0;color:#64748b;width:14px;height:14px;border-radius:50%;font-size:.5rem;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;transition:background-color .12s}.search-clear-btn:hover{background:#cbd5e1;color:#0f172a}.select-options-list{overflow-y:auto;overscroll-behavior:contain;padding:3px}.select-option-item{display:flex;align-items:center;justify-content:space-between;gap:6px;padding:5px 8px;border-radius:4px;font-size:var(--rpt-font-size-sm, .75rem);color:var(--rpt-color-text, #0f172a);cursor:pointer;-webkit-user-select:none;user-select:none;transition:background-color .1s ease}.select-option-item.is-focused{background-color:var(--rpt-color-surface-hover, #f1f5f9)}.select-option-item.is-selected{background-color:#eff6ff;color:var(--rpt-color-primary, #2563eb);font-weight:600}.select-option-item.is-disabled{opacity:.45;cursor:not-allowed;pointer-events:none}.option-content{display:flex;flex-direction:column;min-width:0;flex:1}.option-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.35}.option-sublabel{font-size:.65rem;color:var(--rpt-color-text-muted, #64748b);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.2}.option-meta{display:flex;align-items:center;gap:6px;flex-shrink:0}.option-badge{font-size:.6rem;font-weight:600;padding:1px 4px;border-radius:3px;background-color:#e2e8f0;color:#475569;text-transform:uppercase;letter-spacing:.03em}.select-option-item.is-selected .option-badge{background-color:#dbeafe;color:#1e40af}.option-check{font-size:.75rem;font-weight:700;color:var(--rpt-color-primary, #2563eb)}.select-status-row{padding:10px 12px;font-size:.72rem;text-align:center;color:var(--rpt-color-text-muted, #64748b)}.select-status-row.loading{display:flex;align-items:center;justify-content:center;gap:6px;color:var(--rpt-color-primary, #2563eb)}.select-status-row.empty{color:var(--rpt-color-text-muted, #94a3b8)}.select-excess-hint{font-size:.6875rem;color:var(--rpt-color-text-secondary, #475569);display:flex;align-items:center;justify-content:space-between;gap:8px;padding:6px 8px;background-color:#f8fafc;border-top:1px dashed var(--rpt-color-border, #cbd5e1);margin-top:2px;position:sticky;bottom:0;z-index:2;box-shadow:0 -2px 6px #0000000a}.excess-text{flex:1;line-height:1.25}.select-load-all-btn{background:#eff6ff;border:1px solid #bfdbfe;color:#2563eb;font-size:.6875rem;font-weight:600;padding:3px 8px;border-radius:4px;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:all .12s ease}.select-load-all-btn:hover{background:#2563eb;color:#fff;border-color:#2563eb}.spinner-dot{display:inline-block;width:11px;height:11px;border:2px solid #bfdbfe;border-top-color:var(--rpt-color-primary, #2563eb);border-radius:50%;animation:select-spin .6s linear infinite}@keyframes select-spin{to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i2.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i2.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6317
6448
|
}
|
|
6318
6449
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RptSearchableSelectComponent, decorators: [{
|
|
6319
6450
|
type: Component,
|
|
@@ -6330,7 +6461,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
6330
6461
|
'[class.size-sm]': 'size() === "sm"',
|
|
6331
6462
|
'[class.size-md]': 'size() === "md"',
|
|
6332
6463
|
'[class.size-lg]': 'size() === "lg"',
|
|
6333
|
-
}, template: "<div class=\"rpt-search-select-wrapper\" #origin=\"cdkOverlayOrigin\" cdkOverlayOrigin>\n <!-- Trigger Button -->\n <button\n #triggerBtn\n type=\"button\"\n class=\"select-trigger\"\n [id]=\"id()\"\n [disabled]=\"isDisabled()\"\n [class.is-open]=\"isOpen()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-label]=\"ariaLabel() || placeholder()\"\n (click)=\"toggle($event)\"\n (keydown)=\"onTriggerKeydown($event)\"\n >\n <div class=\"trigger-label\" [class.is-placeholder]=\"!selectedOption()\">\n @if (selectedOption()) {\n <span class=\"selected-text\">{{ selectedOption()!.label }}</span>\n @if (selectedOption()!.sublabel) {\n <span class=\"selected-sublabel\">{{ selectedOption()!.sublabel }}</span>\n }\n @if (selectedOption()!.badge) {\n <span class=\"selected-badge\">{{ selectedOption()!.badge }}</span>\n }\n } @else {\n <span class=\"placeholder-text\">{{ placeholder() }}</span>\n }\n </div>\n\n <div class=\"trigger-actions\">\n @if (loading()) {\n <span class=\"spinner-dot\" title=\"Loading options...\"></span>\n }\n\n @if (selectedOption() && clearable() && !isDisabled()) {\n <button\n type=\"button\"\n class=\"trigger-clear-btn\"\n title=\"Clear selection\"\n aria-label=\"Clear selection\"\n (click)=\"clearSelection($event)\"\n >\n \u2715\n </button>\n }\n\n <span class=\"trigger-arrow\" [class.rotated]=\"isOpen()\" aria-hidden=\"true\">\n <svg width=\"10\" height=\"6\" viewBox=\"0 0 10 6\" fill=\"none\">\n <path d=\"M1 1L5 5L9 1\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n </div>\n </button>\n</div>\n\n<!-- Dropdown Popover Panel in CDK Overlay (attached to body, outside any clipped card) -->\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"origin\"\n [cdkConnectedOverlayOpen]=\"isOpen()\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayWidth]=\"triggerWidth()\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n [cdkConnectedOverlayViewportMargin]=\"8\"\n [cdkConnectedOverlayPush]=\"true\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n (overlayOutsideClick)=\"close()\"\n (positionChange)=\"onPositionChange($event)\"\n (attach)=\"onOverlayAttached()\"\n (detach)=\"close()\"\n>\n <div\n class=\"select-panel\"\n [id]=\"panelId\"\n [class.opens-above]=\"opensAbove()\"\n [style.max-height]=\"panelMaxHeight()\"\n role=\"listbox\"\n (click)=\"$event.stopPropagation()\"\n >\n <!-- Search Input Box -->\n @if (searchable()) {\n <div class=\"select-search-box\">\n <span class=\"search-icon\" aria-hidden=\"true\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"></circle>\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"></line>\n </svg>\n </span>\n <input\n #searchInput\n type=\"text\"\n class=\"search-input\"\n [placeholder]=\"searchPlaceholder()\"\n [value]=\"searchTerm()\"\n (input)=\"onSearchInput($event)\"\n (keydown)=\"onSearchKeydown($event)\"\n />\n @if (searchTerm()) {\n <button\n type=\"button\"\n class=\"search-clear-btn\"\n title=\"Clear search\"\n (click)=\"clearSearch($event)\"\n >\n \u2715\n </button>\n }\n </div>\n }\n\n <!-- Options List -->\n <div class=\"select-options-list\" #optionsList (scroll)=\"onOptionsScroll($event)\">\n @if (loading()) {\n <div class=\"select-status-row loading\">\n <span class=\"spinner-dot\"></span>\n <span>{{ loadingText() }}</span>\n </div>\n } @else if (normalizedOptions().length === 0) {\n <div class=\"select-status-row empty\">\n <span>{{ emptyText() }}</span>\n </div>\n } @else if (visibleFilteredOptions().length === 0) {\n <div class=\"select-status-row empty\">\n <span>No options match \"{{ searchTerm() }}\"</span>\n </div>\n } @else {\n @for (opt of visibleFilteredOptions(); track opt.value; let i = $index) {\n <div\n role=\"option\"\n class=\"select-option-item\"\n [class.is-selected]=\"isSelected(opt)\"\n [class.is-focused]=\"focusedIndex() === i\"\n [class.is-disabled]=\"opt.disabled\"\n [attr.aria-selected]=\"isSelected(opt)\"\n (click)=\"selectOption(opt)\"\n (mouseenter)=\"focusedIndex.set(i)\"\n >\n <div class=\"option-content\">\n <span class=\"option-label\" [title]=\"opt.label\">{{ opt.label }}</span>\n @if (opt.sublabel) {\n <span class=\"option-sublabel\" [title]=\"opt.sublabel\">{{ opt.sublabel }}</span>\n }\n </div>\n\n <div class=\"option-meta\">\n @if (opt.badge) {\n <span class=\"option-badge\">{{ opt.badge }}</span>\n }\n @if (isSelected(opt)) {\n <span class=\"option-check\" aria-hidden=\"true\">\u2713</span>\n }\n </div>\n </div>\n }\n\n @if (excessCount() > 0) {\n <div class=\"select-excess-hint\">\n <span class=\"excess-text\">Showing {{ visibleFilteredOptions().length }} of {{ filteredOptions().length }} matches. Scroll down to browse or</span>\n <button\n type=\"button\"\n class=\"select-load-all-btn\"\n (click)=\"loadAll(); $event.stopPropagation();\"\n >\n Load all {{ filteredOptions().length }}\n </button>\n </div>\n }\n }\n </div>\n </div>\n</ng-template>\n", styles: [":host{display:block;width:100%;position:relative;box-sizing:border-box}.rpt-search-select-wrapper{position:relative;width:100%;box-sizing:border-box}:host(.size-sm) .select-trigger{min-height:28px;padding:4px 8px;font-size:var(--rpt-font-size-sm, .75rem)}:host(.size-md) .select-trigger{min-height:36px;padding:6px 10px;font-size:var(--rpt-font-size-base, .8125rem)}:host(.size-lg) .select-trigger{min-height:42px;padding:8px 12px;font-size:var(--rpt-font-size-base, .875rem)}.select-trigger{width:100%;box-sizing:border-box;display:flex;align-items:center;justify-content:space-between;gap:6px;background-color:var(--rpt-color-bg, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-sm, 4px);color:var(--rpt-color-text, #0f172a);font-family:inherit;outline:none;cursor:pointer;text-align:left;transition:border-color var(--rpt-transition-speed, .15s) ease,box-shadow var(--rpt-transition-speed, .15s) ease}.select-trigger:hover:not(:disabled){border-color:var(--rpt-color-border-hover, #94a3b8)}.select-trigger:focus-visible,.select-trigger.is-open{border-color:var(--rpt-color-primary, #2563eb);box-shadow:0 0 0 2px #2563eb33}.select-trigger:disabled{background-color:var(--rpt-color-surface, #f8fafc);color:var(--rpt-color-text-muted, #94a3b8);border-color:var(--rpt-color-border, #e2e8f0);cursor:not-allowed}.trigger-label{flex:1;min-width:0;display:flex;align-items:center;gap:6px;overflow:hidden}.selected-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500;color:var(--rpt-color-text, #0f172a)}.selected-sublabel{font-size:.675rem;color:var(--rpt-color-text-muted, #64748b);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex-shrink:0}.selected-badge{font-size:.6rem;font-weight:600;padding:1px 4px;border-radius:3px;background-color:#e2e8f0;color:#475569;text-transform:uppercase;letter-spacing:.03em;flex-shrink:0}.placeholder-text{color:var(--rpt-color-text-muted, #94a3b8);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.trigger-actions{display:flex;align-items:center;gap:4px;flex-shrink:0}.trigger-clear-btn{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;border-radius:50%;border:none;font-size:.55rem;color:var(--rpt-color-text-muted, #64748b);background-color:#f1f5f9;cursor:pointer;padding:0;line-height:1;transition:background-color .12s ease,color .12s ease}.trigger-clear-btn:hover{background-color:#e2e8f0;color:var(--rpt-color-text, #0f172a)}.trigger-arrow{display:flex;align-items:center;color:var(--rpt-color-text-muted, #64748b);transition:transform .18s ease}.trigger-arrow.rotated{transform:rotate(180deg)}.select-panel{width:100%;min-width:180px;max-width:100vw;box-sizing:border-box;background-color:var(--rpt-color-bg, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);box-shadow:0 10px 25px -5px #00000024,0 8px 10px -6px #00000014;overflow:hidden;display:flex;flex-direction:column;animation:select-pop .12s cubic-bezier(0,0,.2,1)}.select-panel.opens-above{box-shadow:0 -10px 25px -5px #00000024,0 -8px 10px -6px #00000014;animation:select-pop-above .12s cubic-bezier(0,0,.2,1)}@keyframes select-pop{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}@keyframes select-pop-above{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.select-search-box{display:flex;align-items:center;gap:6px;padding:6px 8px;background-color:#f8fafc;border-bottom:1px solid var(--rpt-color-border, #e2e8f0)}.search-icon{color:var(--rpt-color-text-muted, #94a3b8);display:flex;align-items:center;flex-shrink:0}.search-input{flex:1;border:none;outline:none;background:transparent;font-size:var(--rpt-font-size-sm, .75rem);font-family:inherit;color:var(--rpt-color-text, #0f172a);min-width:0}.search-input::placeholder{color:#94a3b8}.search-clear-btn{border:none;background:#e2e8f0;color:#64748b;width:14px;height:14px;border-radius:50%;font-size:.5rem;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;transition:background-color .12s}.search-clear-btn:hover{background:#cbd5e1;color:#0f172a}.select-options-list{overflow-y:auto;overscroll-behavior:contain;padding:3px}.select-option-item{display:flex;align-items:center;justify-content:space-between;gap:6px;padding:5px 8px;border-radius:4px;font-size:var(--rpt-font-size-sm, .75rem);color:var(--rpt-color-text, #0f172a);cursor:pointer;-webkit-user-select:none;user-select:none;transition:background-color .1s ease}.select-option-item.is-focused{background-color:var(--rpt-color-surface-hover, #f1f5f9)}.select-option-item.is-selected{background-color:#eff6ff;color:var(--rpt-color-primary, #2563eb);font-weight:600}.select-option-item.is-disabled{opacity:.45;cursor:not-allowed;pointer-events:none}.option-content{display:flex;flex-direction:column;min-width:0;flex:1}.option-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.35}.option-sublabel{font-size:.65rem;color:var(--rpt-color-text-muted, #64748b);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.2}.option-meta{display:flex;align-items:center;gap:6px;flex-shrink:0}.option-badge{font-size:.6rem;font-weight:600;padding:1px 4px;border-radius:3px;background-color:#e2e8f0;color:#475569;text-transform:uppercase;letter-spacing:.03em}.select-option-item.is-selected .option-badge{background-color:#dbeafe;color:#1e40af}.option-check{font-size:.75rem;font-weight:700;color:var(--rpt-color-primary, #2563eb)}.select-status-row{padding:10px 12px;font-size:.72rem;text-align:center;color:var(--rpt-color-text-muted, #64748b)}.select-status-row.loading{display:flex;align-items:center;justify-content:center;gap:6px;color:var(--rpt-color-primary, #2563eb)}.select-status-row.empty{color:var(--rpt-color-text-muted, #94a3b8)}.select-excess-hint{font-size:.6875rem;color:var(--rpt-color-text-secondary, #475569);display:flex;align-items:center;justify-content:space-between;gap:8px;padding:6px 8px;background-color:#f8fafc;border-top:1px dashed var(--rpt-color-border, #cbd5e1);margin-top:2px;position:sticky;bottom:0;z-index:2;box-shadow:0 -2px 6px #0000000a}.excess-text{flex:1;line-height:1.25}.select-load-all-btn{background:#eff6ff;border:1px solid #bfdbfe;color:#2563eb;font-size:.6875rem;font-weight:600;padding:3px 8px;border-radius:4px;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:all .12s ease}.select-load-all-btn:hover{background:#2563eb;color:#fff;border-color:#2563eb}.spinner-dot{display:inline-block;width:11px;height:11px;border:2px solid #bfdbfe;border-top-color:var(--rpt-color-primary, #2563eb);border-radius:50%;animation:select-spin .6s linear infinite}@keyframes select-spin{to{transform:rotate(360deg)}}\n"] }]
|
|
6464
|
+
}, template: "<div class=\"rpt-search-select-wrapper\" #origin=\"cdkOverlayOrigin\" cdkOverlayOrigin>\n <!-- Trigger Button -->\n <button\n #triggerBtn\n type=\"button\"\n class=\"select-trigger\"\n [id]=\"id()\"\n [disabled]=\"isDisabled()\"\n [class.is-open]=\"isOpen()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-controls]=\"panelId\"\n [attr.aria-label]=\"ariaLabel() || placeholder()\"\n (click)=\"toggle($event)\"\n (keydown)=\"onTriggerKeydown($event)\"\n >\n <div class=\"trigger-label\" [class.is-placeholder]=\"!selectedOption()\">\n @if (selectedOption()) {\n <span class=\"selected-text\">{{ selectedOption()!.label }}</span>\n @if (selectedOption()!.sublabel) {\n <span class=\"selected-sublabel\">{{ selectedOption()!.sublabel }}</span>\n }\n @if (selectedOption()!.badge) {\n <span class=\"selected-badge\">{{ selectedOption()!.badge }}</span>\n }\n } @else {\n <span class=\"placeholder-text\">{{ placeholder() }}</span>\n }\n </div>\n\n <div class=\"trigger-actions\">\n @if (loading()) {\n <span class=\"spinner-dot\" title=\"Loading options...\"></span>\n }\n\n @if (selectedOption() && clearable() && !isDisabled()) {\n <button\n type=\"button\"\n class=\"trigger-clear-btn\"\n title=\"Clear selection\"\n aria-label=\"Clear selection\"\n (click)=\"clearSelection($event)\"\n >\n \u2715\n </button>\n }\n\n <span class=\"trigger-arrow\" [class.rotated]=\"isOpen()\" aria-hidden=\"true\">\n <svg width=\"10\" height=\"6\" viewBox=\"0 0 10 6\" fill=\"none\">\n <path d=\"M1 1L5 5L9 1\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n </svg>\n </span>\n </div>\n </button>\n</div>\n\n<!-- Dropdown Popover Panel in CDK Overlay (attached to body, outside any clipped card) -->\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"origin\"\n [cdkConnectedOverlayOpen]=\"isOpen()\"\n [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayWidth]=\"triggerWidth()\"\n [cdkConnectedOverlayMinWidth]=\"'min(180px, calc(100vw - 16px))'\"\n [cdkConnectedOverlayFlexibleDimensions]=\"true\"\n [cdkConnectedOverlayViewportMargin]=\"8\"\n [cdkConnectedOverlayPush]=\"true\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n (overlayOutsideClick)=\"close()\"\n (positionChange)=\"onPositionChange($event)\"\n (attach)=\"onOverlayAttached()\"\n (detach)=\"close()\"\n>\n <div\n class=\"select-panel\"\n [id]=\"panelId\"\n [class.opens-above]=\"opensAbove()\"\n [style.max-height]=\"panelMaxHeight()\"\n role=\"listbox\"\n (click)=\"$event.stopPropagation()\"\n >\n <!-- Search Input Box -->\n @if (searchable()) {\n <div class=\"select-search-box\">\n <span class=\"search-icon\" aria-hidden=\"true\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"></circle>\n <line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"></line>\n </svg>\n </span>\n <input\n #searchInput\n type=\"text\"\n class=\"search-input\"\n [placeholder]=\"searchPlaceholder()\"\n [value]=\"searchTerm()\"\n (input)=\"onSearchInput($event)\"\n (keydown)=\"onSearchKeydown($event)\"\n />\n @if (searchTerm()) {\n <button\n type=\"button\"\n class=\"search-clear-btn\"\n title=\"Clear search\"\n (click)=\"clearSearch($event)\"\n >\n \u2715\n </button>\n }\n </div>\n }\n\n <!-- Options List -->\n <div class=\"select-options-list\" #optionsList (scroll)=\"onOptionsScroll($event)\">\n @if (loading()) {\n <div class=\"select-status-row loading\">\n <span class=\"spinner-dot\"></span>\n <span>{{ loadingText() }}</span>\n </div>\n } @else if (normalizedOptions().length === 0) {\n <div class=\"select-status-row empty\">\n <span>{{ emptyText() }}</span>\n </div>\n } @else if (visibleFilteredOptions().length === 0) {\n <div class=\"select-status-row empty\">\n <span>No options match \"{{ searchTerm() }}\"</span>\n </div>\n } @else {\n @for (opt of visibleFilteredOptions(); track opt.value; let i = $index) {\n <div\n role=\"option\"\n class=\"select-option-item\"\n [class.is-selected]=\"isSelected(opt)\"\n [class.is-focused]=\"focusedIndex() === i\"\n [class.is-disabled]=\"opt.disabled\"\n [attr.aria-selected]=\"isSelected(opt)\"\n (click)=\"selectOption(opt)\"\n (mouseenter)=\"focusedIndex.set(i)\"\n >\n <div class=\"option-content\">\n <span class=\"option-label\" [title]=\"opt.label\">{{ opt.label }}</span>\n @if (opt.sublabel) {\n <span class=\"option-sublabel\" [title]=\"opt.sublabel\">{{ opt.sublabel }}</span>\n }\n </div>\n\n <div class=\"option-meta\">\n @if (opt.badge) {\n <span class=\"option-badge\">{{ opt.badge }}</span>\n }\n @if (isSelected(opt)) {\n <span class=\"option-check\" aria-hidden=\"true\">\u2713</span>\n }\n </div>\n </div>\n }\n\n @if (excessCount() > 0) {\n <div class=\"select-excess-hint\">\n <span class=\"excess-text\">Showing {{ visibleFilteredOptions().length }} of {{ filteredOptions().length }} matches. Scroll down to browse or</span>\n <button\n type=\"button\"\n class=\"select-load-all-btn\"\n (click)=\"loadAll(); $event.stopPropagation();\"\n >\n Load all {{ filteredOptions().length }}\n </button>\n </div>\n }\n }\n </div>\n </div>\n</ng-template>\n", styles: [":host{display:block;width:100%;position:relative;box-sizing:border-box}.rpt-search-select-wrapper{position:relative;width:100%;box-sizing:border-box}:host(.size-sm) .select-trigger{min-height:28px;padding:4px 8px;font-size:var(--rpt-font-size-sm, .75rem)}:host(.size-md) .select-trigger{min-height:36px;padding:6px 10px;font-size:var(--rpt-font-size-base, .8125rem)}:host(.size-lg) .select-trigger{min-height:42px;padding:8px 12px;font-size:var(--rpt-font-size-base, .875rem)}.select-trigger{width:100%;box-sizing:border-box;display:flex;align-items:center;justify-content:space-between;gap:6px;background-color:var(--rpt-color-bg, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-sm, 4px);color:var(--rpt-color-text, #0f172a);font-family:inherit;outline:none;cursor:pointer;text-align:left;transition:border-color var(--rpt-transition-speed, .15s) ease,box-shadow var(--rpt-transition-speed, .15s) ease}.select-trigger:hover:not(:disabled){border-color:var(--rpt-color-border-hover, #94a3b8)}.select-trigger:focus-visible,.select-trigger.is-open{border-color:var(--rpt-color-primary, #2563eb);box-shadow:0 0 0 2px #2563eb33}.select-trigger:disabled{background-color:var(--rpt-color-surface, #f8fafc);color:var(--rpt-color-text-muted, #94a3b8);border-color:var(--rpt-color-border, #e2e8f0);cursor:not-allowed}.trigger-label{flex:1;min-width:0;display:flex;align-items:center;gap:6px;overflow:hidden}.selected-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500;color:var(--rpt-color-text, #0f172a)}.selected-sublabel{font-size:.675rem;color:var(--rpt-color-text-muted, #64748b);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex-shrink:0}.selected-badge{font-size:.6rem;font-weight:600;padding:1px 4px;border-radius:3px;background-color:#e2e8f0;color:#475569;text-transform:uppercase;letter-spacing:.03em;flex-shrink:0}.placeholder-text{color:var(--rpt-color-text-muted, #94a3b8);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.trigger-actions{display:flex;align-items:center;gap:4px;flex-shrink:0}.trigger-clear-btn{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;border-radius:50%;border:none;font-size:.55rem;color:var(--rpt-color-text-muted, #64748b);background-color:#f1f5f9;cursor:pointer;padding:0;line-height:1;transition:background-color .12s ease,color .12s ease}.trigger-clear-btn:hover{background-color:#e2e8f0;color:var(--rpt-color-text, #0f172a)}.trigger-arrow{display:flex;align-items:center;color:var(--rpt-color-text-muted, #64748b);transition:transform .18s ease}.trigger-arrow.rotated{transform:rotate(180deg)}.select-panel{width:100%;min-width:0;max-width:100vw;box-sizing:border-box;background-color:var(--rpt-color-bg, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);box-shadow:0 10px 25px -5px #00000024,0 8px 10px -6px #00000014;overflow:hidden;display:flex;flex-direction:column;animation:select-pop .12s cubic-bezier(0,0,.2,1)}.select-panel.opens-above{box-shadow:0 -10px 25px -5px #00000024,0 -8px 10px -6px #00000014;animation:select-pop-above .12s cubic-bezier(0,0,.2,1)}@keyframes select-pop{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}@keyframes select-pop-above{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.select-search-box{display:flex;align-items:center;gap:6px;padding:6px 8px;background-color:#f8fafc;border-bottom:1px solid var(--rpt-color-border, #e2e8f0)}.search-icon{color:var(--rpt-color-text-muted, #94a3b8);display:flex;align-items:center;flex-shrink:0}.search-input{flex:1;border:none;outline:none;background:transparent;font-size:var(--rpt-font-size-sm, .75rem);font-family:inherit;color:var(--rpt-color-text, #0f172a);min-width:0}.search-input::placeholder{color:#94a3b8}.search-clear-btn{border:none;background:#e2e8f0;color:#64748b;width:14px;height:14px;border-radius:50%;font-size:.5rem;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;transition:background-color .12s}.search-clear-btn:hover{background:#cbd5e1;color:#0f172a}.select-options-list{overflow-y:auto;overscroll-behavior:contain;padding:3px}.select-option-item{display:flex;align-items:center;justify-content:space-between;gap:6px;padding:5px 8px;border-radius:4px;font-size:var(--rpt-font-size-sm, .75rem);color:var(--rpt-color-text, #0f172a);cursor:pointer;-webkit-user-select:none;user-select:none;transition:background-color .1s ease}.select-option-item.is-focused{background-color:var(--rpt-color-surface-hover, #f1f5f9)}.select-option-item.is-selected{background-color:#eff6ff;color:var(--rpt-color-primary, #2563eb);font-weight:600}.select-option-item.is-disabled{opacity:.45;cursor:not-allowed;pointer-events:none}.option-content{display:flex;flex-direction:column;min-width:0;flex:1}.option-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.35}.option-sublabel{font-size:.65rem;color:var(--rpt-color-text-muted, #64748b);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;line-height:1.2}.option-meta{display:flex;align-items:center;gap:6px;flex-shrink:0}.option-badge{font-size:.6rem;font-weight:600;padding:1px 4px;border-radius:3px;background-color:#e2e8f0;color:#475569;text-transform:uppercase;letter-spacing:.03em}.select-option-item.is-selected .option-badge{background-color:#dbeafe;color:#1e40af}.option-check{font-size:.75rem;font-weight:700;color:var(--rpt-color-primary, #2563eb)}.select-status-row{padding:10px 12px;font-size:.72rem;text-align:center;color:var(--rpt-color-text-muted, #64748b)}.select-status-row.loading{display:flex;align-items:center;justify-content:center;gap:6px;color:var(--rpt-color-primary, #2563eb)}.select-status-row.empty{color:var(--rpt-color-text-muted, #94a3b8)}.select-excess-hint{font-size:.6875rem;color:var(--rpt-color-text-secondary, #475569);display:flex;align-items:center;justify-content:space-between;gap:8px;padding:6px 8px;background-color:#f8fafc;border-top:1px dashed var(--rpt-color-border, #cbd5e1);margin-top:2px;position:sticky;bottom:0;z-index:2;box-shadow:0 -2px 6px #0000000a}.excess-text{flex:1;line-height:1.25}.select-load-all-btn{background:#eff6ff;border:1px solid #bfdbfe;color:#2563eb;font-size:.6875rem;font-weight:600;padding:3px 8px;border-radius:4px;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:all .12s ease}.select-load-all-btn:hover{background:#2563eb;color:#fff;border-color:#2563eb}.spinner-dot{display:inline-block;width:11px;height:11px;border:2px solid #bfdbfe;border-top-color:var(--rpt-color-primary, #2563eb);border-radius:50%;animation:select-spin .6s linear infinite}@keyframes select-spin{to{transform:rotate(360deg)}}\n"] }]
|
|
6334
6465
|
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { onDocumentClick: [{
|
|
6335
6466
|
type: HostListener,
|
|
6336
6467
|
args: ['document:click', ['$event']]
|
|
@@ -6551,10 +6682,20 @@ class RptRadioComponent {
|
|
|
6551
6682
|
return;
|
|
6552
6683
|
}
|
|
6553
6684
|
const session = this.sessionService?.sessionContext() ?? {};
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6685
|
+
let resolvedParams;
|
|
6686
|
+
try {
|
|
6687
|
+
resolvedParams = {
|
|
6688
|
+
...(ds.sourceType === 'table' ? session : {}),
|
|
6689
|
+
...resolveParameterMappings(ds.parameterMappings, session, formValues),
|
|
6690
|
+
};
|
|
6691
|
+
}
|
|
6692
|
+
catch (error) {
|
|
6693
|
+
this.lookupSub?.unsubscribe();
|
|
6694
|
+
this.isLoading.set(false);
|
|
6695
|
+
this.dynamicOptions.set([]);
|
|
6696
|
+
this.loadError.set(error instanceof Error ? error.message : 'Invalid parameter value.');
|
|
6697
|
+
return;
|
|
6698
|
+
}
|
|
6558
6699
|
const request = {
|
|
6559
6700
|
sourceType: (ds.sourceType ?? (isApi ? 'api' : 'table')),
|
|
6560
6701
|
entityName: ds.entityName ?? (isApi ? ds.apiEndpoint : ''),
|
|
@@ -8141,20 +8282,14 @@ class ReportGridBridgeComponent {
|
|
|
8141
8282
|
});
|
|
8142
8283
|
isCheckboxSelectionEnabled = computed(() => {
|
|
8143
8284
|
const opts = this.gridDefinition()?.options;
|
|
8144
|
-
|
|
8145
|
-
return opts.checkboxSelection;
|
|
8146
|
-
}
|
|
8147
|
-
if (opts?.selectable) {
|
|
8148
|
-
return true;
|
|
8149
|
-
}
|
|
8150
|
-
return this.visibleColumns().some((c) => c.checkboxSelection);
|
|
8285
|
+
return this.isSelectionEnabled() && (opts?.checkboxSelection ?? true);
|
|
8151
8286
|
});
|
|
8152
8287
|
effectiveRowSelection = computed(() => {
|
|
8153
8288
|
const opts = this.gridDefinition()?.options;
|
|
8154
8289
|
return opts?.selectionMode || opts?.rowSelection || 'multiple';
|
|
8155
8290
|
});
|
|
8156
8291
|
isHeaderCheckboxSelectionEnabled = computed(() => {
|
|
8157
|
-
if (this.effectiveRowSelection() === 'single') {
|
|
8292
|
+
if (!this.isCheckboxSelectionEnabled() || this.effectiveRowSelection() === 'single') {
|
|
8158
8293
|
return false;
|
|
8159
8294
|
}
|
|
8160
8295
|
const opts = this.gridDefinition()?.options;
|
|
@@ -8165,7 +8300,7 @@ class ReportGridBridgeComponent {
|
|
|
8165
8300
|
});
|
|
8166
8301
|
isSelectionEnabled = computed(() => {
|
|
8167
8302
|
const opts = this.gridDefinition()?.options;
|
|
8168
|
-
return
|
|
8303
|
+
return opts?.selectable ?? true;
|
|
8169
8304
|
});
|
|
8170
8305
|
sortColumn = signal(null);
|
|
8171
8306
|
sortDirection = signal('asc');
|
|
@@ -8204,7 +8339,7 @@ class ReportGridBridgeComponent {
|
|
|
8204
8339
|
}
|
|
8205
8340
|
let colCheckboxSelection = col.checkboxSelection;
|
|
8206
8341
|
let colHeaderCheckboxSelection = col.headerCheckboxSelection;
|
|
8207
|
-
if (!hasExplicitColCheckbox && isGridCheckbox && idx === 0) {
|
|
8342
|
+
if (!hasExplicitColCheckbox && isGridCheckbox && idx === 0 && col.checkboxSelection !== false) {
|
|
8208
8343
|
colCheckboxSelection = true;
|
|
8209
8344
|
colHeaderCheckboxSelection = isGridHeaderCheckbox;
|
|
8210
8345
|
}
|
|
@@ -8216,8 +8351,8 @@ class ReportGridBridgeComponent {
|
|
|
8216
8351
|
hide: col.visible === false,
|
|
8217
8352
|
width: widthNum,
|
|
8218
8353
|
aggFunc: col.aggregate && col.aggregate !== 'none' ? col.aggregate : undefined,
|
|
8219
|
-
checkboxSelection: colCheckboxSelection,
|
|
8220
|
-
headerCheckboxSelection: colHeaderCheckboxSelection,
|
|
8354
|
+
checkboxSelection: isGridCheckbox ? colCheckboxSelection : false,
|
|
8355
|
+
headerCheckboxSelection: isGridHeaderCheckbox ? colHeaderCheckboxSelection : false,
|
|
8221
8356
|
};
|
|
8222
8357
|
});
|
|
8223
8358
|
});
|
|
@@ -8243,6 +8378,7 @@ class ReportGridBridgeComponent {
|
|
|
8243
8378
|
statusBar: opts.statusBar !== undefined ? opts.statusBar : true,
|
|
8244
8379
|
globalSearch: opts.globalSearch !== undefined ? opts.globalSearch : true,
|
|
8245
8380
|
...opts,
|
|
8381
|
+
selectable: this.isSelectionEnabled(),
|
|
8246
8382
|
rowSelection: rowSel,
|
|
8247
8383
|
selectionMode: rowSel,
|
|
8248
8384
|
checkboxSelection: hasCheckbox,
|
|
@@ -8318,7 +8454,7 @@ class ReportGridBridgeComponent {
|
|
|
8318
8454
|
return this.gridDefinition().options?.pageSize ?? 10;
|
|
8319
8455
|
});
|
|
8320
8456
|
colSpan = computed(() => {
|
|
8321
|
-
return this.visibleColumns().length + (this.showRowNumbers() ? 1 : 0) + (this.
|
|
8457
|
+
return this.visibleColumns().length + (this.showRowNumbers() ? 1 : 0) + (this.isCheckboxSelectionEnabled() ? 1 : 0);
|
|
8322
8458
|
});
|
|
8323
8459
|
hasAggregates = computed(() => {
|
|
8324
8460
|
return (this.gridDefinition().options?.showFooter &&
|
|
@@ -8543,7 +8679,7 @@ class ReportGridBridgeComponent {
|
|
|
8543
8679
|
@if (showRowNumbers()) {
|
|
8544
8680
|
<th class="rpt-th rpt-th-num">#</th>
|
|
8545
8681
|
}
|
|
8546
|
-
@if (
|
|
8682
|
+
@if (isCheckboxSelectionEnabled()) {
|
|
8547
8683
|
<th class="rpt-th rpt-th-cb">
|
|
8548
8684
|
@if (effectiveRowSelection() !== 'single' && isHeaderCheckboxSelectionEnabled()) {
|
|
8549
8685
|
<input
|
|
@@ -8595,7 +8731,7 @@ class ReportGridBridgeComponent {
|
|
|
8595
8731
|
@if (showRowNumbers()) {
|
|
8596
8732
|
<td class="rpt-td rpt-td-num">{{ (currentPage() - 1) * pageSize() + $index + 1 }}</td>
|
|
8597
8733
|
}
|
|
8598
|
-
@if (
|
|
8734
|
+
@if (isCheckboxSelectionEnabled()) {
|
|
8599
8735
|
<td class="rpt-td rpt-td-cb" (click)="$event.stopPropagation()">
|
|
8600
8736
|
<input
|
|
8601
8737
|
[type]="effectiveRowSelection() === 'single' ? 'radio' : 'checkbox'"
|
|
@@ -8630,7 +8766,7 @@ class ReportGridBridgeComponent {
|
|
|
8630
8766
|
@if (showRowNumbers()) {
|
|
8631
8767
|
<td class="rpt-td-num"></td>
|
|
8632
8768
|
}
|
|
8633
|
-
@if (
|
|
8769
|
+
@if (isCheckboxSelectionEnabled()) {
|
|
8634
8770
|
<td class="rpt-td-cb"></td>
|
|
8635
8771
|
}
|
|
8636
8772
|
@for (col of visibleColumns(); track col.field) {
|
|
@@ -8710,7 +8846,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
8710
8846
|
@if (showRowNumbers()) {
|
|
8711
8847
|
<th class="rpt-th rpt-th-num">#</th>
|
|
8712
8848
|
}
|
|
8713
|
-
@if (
|
|
8849
|
+
@if (isCheckboxSelectionEnabled()) {
|
|
8714
8850
|
<th class="rpt-th rpt-th-cb">
|
|
8715
8851
|
@if (effectiveRowSelection() !== 'single' && isHeaderCheckboxSelectionEnabled()) {
|
|
8716
8852
|
<input
|
|
@@ -8762,7 +8898,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
8762
8898
|
@if (showRowNumbers()) {
|
|
8763
8899
|
<td class="rpt-td rpt-td-num">{{ (currentPage() - 1) * pageSize() + $index + 1 }}</td>
|
|
8764
8900
|
}
|
|
8765
|
-
@if (
|
|
8901
|
+
@if (isCheckboxSelectionEnabled()) {
|
|
8766
8902
|
<td class="rpt-td rpt-td-cb" (click)="$event.stopPropagation()">
|
|
8767
8903
|
<input
|
|
8768
8904
|
[type]="effectiveRowSelection() === 'single' ? 'radio' : 'checkbox'"
|
|
@@ -8797,7 +8933,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
8797
8933
|
@if (showRowNumbers()) {
|
|
8798
8934
|
<td class="rpt-td-num"></td>
|
|
8799
8935
|
}
|
|
8800
|
-
@if (
|
|
8936
|
+
@if (isCheckboxSelectionEnabled()) {
|
|
8801
8937
|
<td class="rpt-td-cb"></td>
|
|
8802
8938
|
}
|
|
8803
8939
|
@for (col of visibleColumns(); track col.field) {
|
|
@@ -9050,7 +9186,7 @@ class DynamicRendererComponent {
|
|
|
9050
9186
|
inputs['config'] = comp;
|
|
9051
9187
|
}
|
|
9052
9188
|
if (comp.key && comp.type !== 'button' && comp.type !== 'kpi-card' && comp.type !== 'chart') {
|
|
9053
|
-
inputs['control'] = this.form().
|
|
9189
|
+
inputs['control'] = this.form().controls[comp.key];
|
|
9054
9190
|
}
|
|
9055
9191
|
if (comp.type === 'dropdown' || comp.type === 'radio') {
|
|
9056
9192
|
inputs['parentForm'] = this.form();
|
|
@@ -9488,7 +9624,13 @@ class ReportExecutionService {
|
|
|
9488
9624
|
return throwError(() => new Error('No data source configured. Select the Run Report button and choose a stored procedure in its Report Data section.'));
|
|
9489
9625
|
}
|
|
9490
9626
|
const session = this.session?.getCurrentSessionContext() ?? {};
|
|
9491
|
-
|
|
9627
|
+
let resolved;
|
|
9628
|
+
try {
|
|
9629
|
+
resolved = resolveParameterMappings(ds.parameterMappings, session, formValues);
|
|
9630
|
+
}
|
|
9631
|
+
catch (error) {
|
|
9632
|
+
return throwError(() => error);
|
|
9633
|
+
}
|
|
9492
9634
|
// Expand compound objects (e.g. dateRange: { from, to }) into top-level parameters
|
|
9493
9635
|
const flattenedFormValues = { ...formValues };
|
|
9494
9636
|
for (const [k, v] of Object.entries(formValues)) {
|
|
@@ -9506,7 +9648,6 @@ class ReportExecutionService {
|
|
|
9506
9648
|
}
|
|
9507
9649
|
}
|
|
9508
9650
|
}
|
|
9509
|
-
const params = { ...flattenedFormValues, ...resolved };
|
|
9510
9651
|
// Delegate to ReportApiExecutionService if data source is an API endpoint
|
|
9511
9652
|
const isApiSource = ds.type === 'api' ||
|
|
9512
9653
|
ds.endpoint.startsWith('http://') ||
|
|
@@ -9525,6 +9666,32 @@ class ReportExecutionService {
|
|
|
9525
9666
|
if (!this.adapter) {
|
|
9526
9667
|
return throwError(() => new Error('No ReportDataSourceAdapter provided to execute the data source.'));
|
|
9527
9668
|
}
|
|
9669
|
+
const isProcedure = ds.method === 'STORED_PROCEDURE' || ds.type === 'stored_procedure' || ds.type === 'procedure' || !ds.type;
|
|
9670
|
+
let params = { ...flattenedFormValues, ...resolved };
|
|
9671
|
+
if (isProcedure) {
|
|
9672
|
+
// Form controls are expression inputs, never an implicit SQL argument list.
|
|
9673
|
+
const declared = {};
|
|
9674
|
+
const mappedNames = new Set(Object.keys(resolved).map(key => key.toLowerCase()));
|
|
9675
|
+
const keys = ds.parameterKeys ?? (ds.parameterMappings === undefined ? [
|
|
9676
|
+
...def.parameters.map(parameter => parameter.key),
|
|
9677
|
+
...def.components.flatMap(component => component.binding?.parameterKey ? [component.binding.parameterKey] : []),
|
|
9678
|
+
] : []);
|
|
9679
|
+
for (const key of keys) {
|
|
9680
|
+
if (mappedNames.has(key.replace(/^@+/, '').toLowerCase()))
|
|
9681
|
+
continue;
|
|
9682
|
+
const control = def.components.find(component => component.binding?.parameterKey === key);
|
|
9683
|
+
const parameter = def.parameters.find(item => item.key === key);
|
|
9684
|
+
const value = resolveCanvasFieldValue(formValues, control?.key || key) ?? resolveCanvasFieldValue(formValues, key.replace(/^@+/, '')) ?? parameter?.defaultValue;
|
|
9685
|
+
if (value !== undefined)
|
|
9686
|
+
Object.defineProperty(declared, key, { value, enumerable: true, configurable: true });
|
|
9687
|
+
}
|
|
9688
|
+
try {
|
|
9689
|
+
params = normalizeParameterNames({ ...declared, ...resolved });
|
|
9690
|
+
}
|
|
9691
|
+
catch (error) {
|
|
9692
|
+
return throwError(() => error);
|
|
9693
|
+
}
|
|
9694
|
+
}
|
|
9528
9695
|
return this.guard(this.adapter.execute({ ...ds, name: ds.name || ds.endpoint, type: ds.type ?? 'stored_procedure' }, params));
|
|
9529
9696
|
}
|
|
9530
9697
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ReportExecutionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
@@ -10951,9 +11118,9 @@ class DesignerStateService {
|
|
|
10951
11118
|
*/
|
|
10952
11119
|
updateGridDataSourceParameters(mappings) {
|
|
10953
11120
|
this.mutateDefinition((def) => {
|
|
10954
|
-
const gridDsId = def.grid?.dataSourceId;
|
|
10955
11121
|
const sources = def.dataSources ?? [];
|
|
10956
|
-
const
|
|
11122
|
+
const effective = resolveExecutionDataSource(def);
|
|
11123
|
+
const targetIdx = sources.findIndex(s => s.id === effective?.id);
|
|
10957
11124
|
if (targetIdx >= 0) {
|
|
10958
11125
|
const nextSources = [...sources];
|
|
10959
11126
|
nextSources[targetIdx] = {
|
|
@@ -10963,6 +11130,7 @@ class DesignerStateService {
|
|
|
10963
11130
|
return {
|
|
10964
11131
|
...def,
|
|
10965
11132
|
dataSources: nextSources,
|
|
11133
|
+
grid: def.grid ? { ...def.grid, dataSourceId: nextSources[targetIdx].id } : def.grid,
|
|
10966
11134
|
};
|
|
10967
11135
|
}
|
|
10968
11136
|
return def;
|
|
@@ -11535,9 +11703,9 @@ class ReportSavedDialogComponent {
|
|
|
11535
11703
|
</div>
|
|
11536
11704
|
|
|
11537
11705
|
<!-- Modal Body (Two-Column Split) -->
|
|
11538
|
-
<div class="rpt-dialog-body" [attr.inert]="updateConfirmation() || isPreparingSave() ? '' : null">
|
|
11706
|
+
<div class="rpt-dialog-body" cdkScrollable [attr.inert]="updateConfirmation() || isPreparingSave() ? '' : null">
|
|
11539
11707
|
<!-- Left Column: Settings & Publishing -->
|
|
11540
|
-
<div class="rpt-settings-column">
|
|
11708
|
+
<div class="rpt-settings-column" cdkScrollable>
|
|
11541
11709
|
<div class="rpt-section-header">Page Navigation & Settings</div>
|
|
11542
11710
|
|
|
11543
11711
|
<div class="rpt-form-group">
|
|
@@ -11720,7 +11888,7 @@ class ReportSavedDialogComponent {
|
|
|
11720
11888
|
<span class="rpt-preview-hint">Interactive preview matching runtime page</span>
|
|
11721
11889
|
</div>
|
|
11722
11890
|
|
|
11723
|
-
<div class="rpt-preview-runtime-wrapper">
|
|
11891
|
+
<div class="rpt-preview-runtime-wrapper" cdkScrollable>
|
|
11724
11892
|
<div class="rpt-preview-card-frame">
|
|
11725
11893
|
<rpt-runtime [report]="currentPreviewDef()" />
|
|
11726
11894
|
</div>
|
|
@@ -11807,11 +11975,11 @@ class ReportSavedDialogComponent {
|
|
|
11807
11975
|
</div>
|
|
11808
11976
|
}
|
|
11809
11977
|
</div>
|
|
11810
|
-
`, isInline: true, styles: ["@keyframes rptSpin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.rpt-spin{display:inline-block;animation:rptSpin 1s linear infinite}@keyframes rptOverlayIn{0%{opacity:0}to{opacity:1}}@keyframes rptCardIn{0%{opacity:0;transform:scale(.96) translateY(6px)}to{opacity:1;transform:scale(1) translateY(0)}}.rpt-id-actions{display:flex;align-items:center;gap:6px;margin-top:4px}.rpt-id-action-btn{display:inline-flex;align-items:center;gap:5px;height:25px;padding:0 9px;font-size:.7rem;font-weight:500;color:#334155;background:#fff;border:1px solid #cbd5e1;border-radius:5px;cursor:pointer;transition:all .15s ease;box-sizing:border-box;white-space:nowrap;font-family:inherit;line-height:1}.rpt-id-action-btn:hover:not(:disabled){background:#f1f5f9;color:#0f172a;border-color:#94a3b8;box-shadow:0 1px 2px #0f172a0d}.rpt-id-action-btn:active:not(:disabled){background:#e2e8f0}.rpt-id-action-btn:disabled{opacity:.5;cursor:not-allowed}.rpt-id-action-btn.is-checking{color:#2563eb;border-color:#93c5fd;background:#eff6ff}.rpt-id-status{display:inline-flex;align-items:center;gap:5px;font-size:.72rem;line-height:1.3;margin:4px 0 0;padding:3px 8px;border-radius:5px;transition:all .15s ease;width:fit-content;max-width:100%;box-sizing:border-box}.rpt-id-status:empty{display:none}.rpt-status-icon{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}.rpt-id-status-success{color:#15803d;background:#f0fdf4;border:1px solid #bbf7d0}.rpt-id-status-warning{color:#b45309;background:#fffbeb;border:1px solid #fef3c7}.rpt-id-status-error{color:#b91c1c;background:#fef2f2;border:1px solid #fecaca}.rpt-id-status-checking{color:#1d4ed8;background:#eff6ff;border:1px solid #dbeafe}.rpt-update-overlay{position:absolute;inset:0;z-index:50;background:#0f172a73;backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);display:flex;align-items:center;justify-content:center;padding:16px;box-sizing:border-box;animation:rptOverlayIn .16s cubic-bezier(.16,1,.3,1) forwards}.rpt-update-card{box-sizing:border-box;width:min(440px,100%);max-height:94%;background:#fff;border-radius:10px;box-shadow:0 20px 45px -8px #0f172a52,0 0 0 1px #0f172a14;border:1px solid #e2e8f0;overflow:hidden;display:flex;flex-direction:column;animation:rptCardIn .18s cubic-bezier(.16,1,.3,1) forwards}.rpt-update-card-header{display:flex;align-items:center;gap:12px;padding:12px 16px;background:#fffbeb;border-bottom:1px solid #fef3c7;flex-shrink:0}.rpt-update-icon-badge{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:8px;background:#fef3c7;border:1px solid #fde68a;color:#b45309;flex-shrink:0}.rpt-update-header-info{flex:1;min-width:0}.rpt-update-title{margin:0;font-size:.925rem;font-weight:700;color:#92400e;line-height:1.25}.rpt-update-subtitle{font-size:.7rem;color:#b45309;margin-top:1px;display:block}.rpt-update-card-body{padding:14px 16px;display:flex;flex-direction:column;gap:11px;overflow-y:auto;box-sizing:border-box}.rpt-update-prompt{margin:0;font-size:.8rem;line-height:1.45;color:#334155}.rpt-conflict-card{background:#f8fafc;border:1px solid #e2e8f0;border-radius:7px;padding:9px 12px;display:flex;flex-direction:column;gap:7px}.rpt-conflict-row{display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:.735rem}.rpt-conflict-label{color:#64748b;font-weight:500;flex-shrink:0}.rpt-conflict-code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.72rem;background:#e2e8f0;color:#0f172a;padding:2px 6px;border-radius:4px;font-weight:600}.rpt-conflict-name{font-weight:600;color:#0f172a;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rpt-conflict-badge{font-size:.68rem;font-weight:600;padding:1px 7px;border-radius:9999px;background:#e0f2fe;color:#0369a1;border:1px solid #bae6fd}.rpt-overwrite-notice{display:flex;align-items:flex-start;gap:7px;font-size:.715rem;color:#b45309;background:#fffbeb;border:1px solid #fef3c7;padding:7px 10px;border-radius:6px;line-height:1.35}.rpt-overwrite-notice rpt-icon{flex-shrink:0;margin-top:1px}.rpt-update-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:10px 16px;background:#f8fafc;border-top:1px solid #e2e8f0;flex-shrink:0}.rpt-btn-confirm-save{background:linear-gradient(135deg,#2563eb,#1d4ed8);color:#fff;box-shadow:0 1px 2px #2563eb33}.rpt-btn-confirm-save:hover{background:linear-gradient(135deg,#1d4ed8,#1e40af);box-shadow:0 2px 4px #2563eb40}#rpt-dlg-id-error:empty,#rpt-dlg-id-status:empty{display:none}.rpt-dialog-footer,.rpt-footer-actions{flex-wrap:wrap;gap:8px}.rpt-btn,.rpt-dialog-close{min-height:36px}@media (max-width: 640px){.rpt-btn,.rpt-dialog-close{min-height:44px;min-width:44px}.rpt-footer-left{width:100%}}.rpt-field-error{color:#b91c1c;font-size:.8rem;margin:4px 0}button:disabled{opacity:.6;cursor:wait}textarea.rpt-input{resize:vertical;min-height:56px}.rpt-footer-left{min-width:0;padding-right:12px}:host{display:flex;align-items:center;justify-content:center;font-family:var(--rpt-font-family, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif);color:#0f172a;box-sizing:border-box}.rpt-dialog-shell{position:relative;display:flex;flex-direction:column;width:min(840px,92vw);max-height:min(540px,84vh);background:#fff;border-radius:10px;box-shadow:0 20px 40px -12px #0f172a47,0 0 0 1px #0f172a14;overflow:hidden;box-sizing:border-box;animation:rptDialogIn .18s cubic-bezier(.16,1,.3,1) forwards}@keyframes rptDialogIn{0%{opacity:0;transform:scale(.97) translateY(4px)}to{opacity:1;transform:scale(1) translateY(0)}}.rpt-dialog-header{display:flex;align-items:center;justify-content:space-between;padding:10px 16px;border-bottom:1px solid #e2e8f0;background:#f8fafc;flex-shrink:0;box-sizing:border-box}.rpt-header-left{display:flex;align-items:center;gap:10px}.rpt-review-icon-badge{display:flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:50%;background:#dbeafe;border:1px solid #a7f3d0;color:#1d4ed8;flex-shrink:0}.rpt-dialog-title{margin:0;font-size:.925rem;font-weight:700;color:#0f172a;line-height:1.25}.rpt-dialog-subtitle{margin:1px 0 0;font-size:.725rem;color:#64748b;line-height:1.2}.rpt-dialog-close{background:transparent;border:none;color:#94a3b8;cursor:pointer;padding:4px;border-radius:5px;display:flex;align-items:center;justify-content:center;transition:all .12s ease}.rpt-dialog-close:hover{background:#e2e8f0;color:#0f172a}.rpt-dialog-body{display:grid;grid-template-columns:280px 1fr;flex:1;min-height:360px;max-height:calc(84vh - 96px);overflow:hidden;box-sizing:border-box}@media (max-width: 780px){.rpt-dialog-body{grid-template-columns:1fr;overflow-y:auto}}.rpt-settings-column{display:flex;flex-direction:column;gap:9px;padding:12px 14px;background:#f8fafc;border-right:1px solid #e2e8f0;overflow-y:auto;box-sizing:border-box}.rpt-section-header{font-size:.675rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:#475569;margin-bottom:1px}.rpt-form-group{display:flex;flex-direction:column;gap:3px}.rpt-label{font-size:.725rem;font-weight:600;color:#334155}.rpt-input{width:100%;box-sizing:border-box;height:28px;padding:4px 8px;font-size:.775rem;border:1px solid #cbd5e1;border-radius:5px;background:#fff;color:#0f172a;outline:none;transition:all .12s ease}.rpt-input:focus{border-color:#2563eb;box-shadow:0 0 0 2px #2563eb24}.rpt-slug-input-wrapper{display:flex;align-items:center;background:#fff;border:1px solid #cbd5e1;border-radius:5px;height:28px;overflow:hidden;box-sizing:border-box;transition:all .12s ease}.rpt-slug-input-wrapper:focus-within{border-color:#2563eb;box-shadow:0 0 0 2px #2563eb24}.rpt-slug-prefix{padding:0 0 0 8px;font-size:.725rem;color:#64748b;background:transparent;-webkit-user-select:none;user-select:none;line-height:28px}.rpt-slug-field{border:none!important;padding-left:2px!important;box-shadow:none!important;height:100%!important}.rpt-help-text{font-size:.65rem;color:#64748b;line-height:1.2}.rpt-publish-card{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:6px 10px;background:#fff;border:1px solid #e2e8f0;border-radius:6px;box-sizing:border-box}.rpt-publish-text{display:flex;flex-direction:column;gap:1px}.rpt-publish-title{font-size:.75rem;font-weight:600;color:#0f172a}.rpt-publish-desc{font-size:.65rem;color:#64748b;line-height:1.2}.rpt-switch{position:relative;display:inline-block;width:32px;height:18px;flex-shrink:0}.rpt-switch input{opacity:0;width:0;height:0}.rpt-slider{position:absolute;cursor:pointer;inset:0;background-color:#cbd5e1;transition:.18s;border-radius:9999px}.rpt-slider:before{position:absolute;content:\"\";height:14px;width:14px;left:2px;bottom:2px;background-color:#fff;transition:.18s;border-radius:50%}input:checked+.rpt-slider{background-color:#2563eb}input:checked+.rpt-slider:before{transform:translate(14px)}.rpt-share-card{padding:6px 9px;background:#eff6ff;border:1px solid #bfdbfe;border-radius:6px;box-sizing:border-box}.rpt-share-url-row{display:flex;align-items:center;gap:6px;overflow:hidden}.rpt-share-label{font-size:.675rem;font-weight:600;color:#1e40af;flex-shrink:0}.rpt-share-code{flex:1;font-size:.6875rem;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;color:#1e3a8a;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rpt-btn-copy{display:inline-flex;align-items:center;gap:3px;padding:2px 6px;background:#fff;border:1px solid #93c5fd;border-radius:4px;font-size:.65rem;font-weight:600;color:#2563eb;cursor:pointer;flex-shrink:0;transition:all .12s ease;height:20px}.rpt-btn-copy:hover{background:#dbeafe}.rpt-preview-column{display:flex;flex-direction:column;background:#fff;overflow:hidden;min-width:0;box-sizing:border-box}.rpt-preview-header{display:flex;align-items:center;justify-content:space-between;padding:8px 14px;border-bottom:1px solid #e2e8f0;background:#fff;flex-shrink:0;box-sizing:border-box}.rpt-preview-badge{display:flex;align-items:center;gap:6px;font-size:.725rem;font-weight:600;color:#0f172a}.rpt-live-dot{width:6px;height:6px;border-radius:50%;background:#10b981;box-shadow:0 0 0 2px #10b98133}.rpt-preview-hint{font-size:.675rem;color:#64748b}.rpt-preview-runtime-wrapper{flex:1;overflow-y:auto;overflow-x:hidden;padding:10px 12px;background:#f8fafc;box-sizing:border-box}.rpt-preview-runtime-wrapper::-webkit-scrollbar,.rpt-settings-column::-webkit-scrollbar{width:5px}.rpt-preview-runtime-wrapper::-webkit-scrollbar-thumb,.rpt-settings-column::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:9999px}.rpt-preview-runtime-wrapper::-webkit-scrollbar-thumb:hover,.rpt-settings-column::-webkit-scrollbar-thumb:hover{background:#94a3b8}.rpt-preview-card-frame{background:#fff;border:1px solid #e2e8f0;border-radius:8px;box-shadow:0 1px 3px #0f172a0a;padding:10px;min-height:100%;box-sizing:border-box;overflow-x:hidden}.rpt-dialog-footer{display:flex;align-items:center;justify-content:space-between;padding:8px 16px;background:#f8fafc;border-top:1px solid #e2e8f0;flex-shrink:0;box-sizing:border-box}.rpt-saved-pill{display:inline-flex;align-items:center;gap:4px;font-size:.7rem;font-weight:600;color:#15803d;background:#dcfce7;padding:2px 7px;border-radius:9999px}.rpt-footer-actions{display:flex;align-items:center;gap:8px;margin-left:auto}.rpt-btn{display:inline-flex;align-items:center;gap:5px;padding:5px 13px;font-size:.75rem;font-weight:600;border-radius:6px;cursor:pointer;transition:all .12s ease;border:1px solid transparent;box-sizing:border-box;line-height:1.4}.rpt-btn-secondary{background:#fff;border-color:#cbd5e1;color:#334155}.rpt-btn-secondary:hover{background:#f1f5f9;color:#0f172a;border-color:#94a3b8}.rpt-btn-primary{background:linear-gradient(135deg,#2563eb,#1d4ed8);color:#fff;box-shadow:0 1px 2px #2563eb33}.rpt-btn-primary:hover{background:linear-gradient(135deg,#1d4ed8,#1e40af);box-shadow:0 2px 4px #2563eb40}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.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.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i2$1.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "component", type: ReportRuntimeComponent, selector: "rpt-report-runtime, rpt-runtime", inputs: ["reportId", "report", "resultData"], outputs: ["resetRequested", "execute", "executed", "exportRequested", "printRequested", "customActionTriggered", "gridStateChange", "columnDefsChange", "selectionChange", "selectionChanged", "rowActionTriggered", "drilldownRequested"] }, { kind: "component", type: RptIconComponent, selector: "rpt-icon", inputs: ["name", "size", "color", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
11978
|
+
`, isInline: true, styles: ["@keyframes rptSpin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.rpt-spin{display:inline-block;animation:rptSpin 1s linear infinite}@keyframes rptOverlayIn{0%{opacity:0}to{opacity:1}}@keyframes rptCardIn{0%{opacity:0;transform:scale(.96) translateY(6px)}to{opacity:1;transform:scale(1) translateY(0)}}.rpt-id-actions{display:flex;align-items:center;gap:6px;margin-top:4px}.rpt-id-action-btn{display:inline-flex;align-items:center;gap:5px;height:25px;padding:0 9px;font-size:.7rem;font-weight:500;color:#334155;background:#fff;border:1px solid #cbd5e1;border-radius:5px;cursor:pointer;transition:all .15s ease;box-sizing:border-box;white-space:nowrap;font-family:inherit;line-height:1}.rpt-id-action-btn:hover:not(:disabled){background:#f1f5f9;color:#0f172a;border-color:#94a3b8;box-shadow:0 1px 2px #0f172a0d}.rpt-id-action-btn:active:not(:disabled){background:#e2e8f0}.rpt-id-action-btn:disabled{opacity:.5;cursor:not-allowed}.rpt-id-action-btn.is-checking{color:#2563eb;border-color:#93c5fd;background:#eff6ff}.rpt-id-status{display:inline-flex;align-items:center;gap:5px;font-size:.72rem;line-height:1.3;margin:4px 0 0;padding:3px 8px;border-radius:5px;transition:all .15s ease;width:fit-content;max-width:100%;box-sizing:border-box}.rpt-id-status:empty{display:none}.rpt-status-icon{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}.rpt-id-status-success{color:#15803d;background:#f0fdf4;border:1px solid #bbf7d0}.rpt-id-status-warning{color:#b45309;background:#fffbeb;border:1px solid #fef3c7}.rpt-id-status-error{color:#b91c1c;background:#fef2f2;border:1px solid #fecaca}.rpt-id-status-checking{color:#1d4ed8;background:#eff6ff;border:1px solid #dbeafe}.rpt-update-overlay{position:absolute;inset:0;z-index:50;background:#0f172a73;backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);display:flex;align-items:center;justify-content:center;padding:16px;box-sizing:border-box;animation:rptOverlayIn .16s cubic-bezier(.16,1,.3,1) forwards}.rpt-update-card{box-sizing:border-box;width:min(440px,100%);max-height:94%;background:#fff;border-radius:10px;box-shadow:0 20px 45px -8px #0f172a52,0 0 0 1px #0f172a14;border:1px solid #e2e8f0;overflow:hidden;display:flex;flex-direction:column;animation:rptCardIn .18s cubic-bezier(.16,1,.3,1) forwards}.rpt-update-card-header{display:flex;align-items:center;gap:12px;padding:12px 16px;background:#fffbeb;border-bottom:1px solid #fef3c7;flex-shrink:0}.rpt-update-icon-badge{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:8px;background:#fef3c7;border:1px solid #fde68a;color:#b45309;flex-shrink:0}.rpt-update-header-info{flex:1;min-width:0}.rpt-update-title{margin:0;font-size:.925rem;font-weight:700;color:#92400e;line-height:1.25}.rpt-update-subtitle{font-size:.7rem;color:#b45309;margin-top:1px;display:block}.rpt-update-card-body{padding:14px 16px;display:flex;flex-direction:column;gap:11px;overflow-y:auto;box-sizing:border-box}.rpt-update-prompt{margin:0;font-size:.8rem;line-height:1.45;color:#334155}.rpt-conflict-card{background:#f8fafc;border:1px solid #e2e8f0;border-radius:7px;padding:9px 12px;display:flex;flex-direction:column;gap:7px}.rpt-conflict-row{display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:.735rem}.rpt-conflict-label{color:#64748b;font-weight:500;flex-shrink:0}.rpt-conflict-code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.72rem;background:#e2e8f0;color:#0f172a;padding:2px 6px;border-radius:4px;font-weight:600}.rpt-conflict-name{font-weight:600;color:#0f172a;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rpt-conflict-badge{font-size:.68rem;font-weight:600;padding:1px 7px;border-radius:9999px;background:#e0f2fe;color:#0369a1;border:1px solid #bae6fd}.rpt-overwrite-notice{display:flex;align-items:flex-start;gap:7px;font-size:.715rem;color:#b45309;background:#fffbeb;border:1px solid #fef3c7;padding:7px 10px;border-radius:6px;line-height:1.35}.rpt-overwrite-notice rpt-icon{flex-shrink:0;margin-top:1px}.rpt-update-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:10px 16px;background:#f8fafc;border-top:1px solid #e2e8f0;flex-shrink:0}.rpt-btn-confirm-save{background:linear-gradient(135deg,#2563eb,#1d4ed8);color:#fff;box-shadow:0 1px 2px #2563eb33}.rpt-btn-confirm-save:hover{background:linear-gradient(135deg,#1d4ed8,#1e40af);box-shadow:0 2px 4px #2563eb40}#rpt-dlg-id-error:empty,#rpt-dlg-id-status:empty{display:none}.rpt-dialog-footer,.rpt-footer-actions{flex-wrap:wrap;gap:8px}.rpt-btn,.rpt-dialog-close{min-height:36px}@media (max-width: 640px){.rpt-btn,.rpt-dialog-close{min-height:44px;min-width:44px}.rpt-footer-left{width:100%}}.rpt-field-error{color:#b91c1c;font-size:.8rem;margin:4px 0}button:disabled{opacity:.6;cursor:wait}textarea.rpt-input{resize:vertical;min-height:56px}.rpt-footer-left{min-width:0;padding-right:12px}:host{display:flex;width:100%;min-width:0;min-height:0;max-height:inherit;align-items:center;justify-content:center;font-family:var(--rpt-font-family, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif);color:#0f172a;box-sizing:border-box}.rpt-dialog-shell{position:relative;display:flex;flex-direction:column;width:100%;max-width:100%;min-height:0;max-height:min(540px,calc(100vh - 32px));max-height:min(540px,calc(100dvh - 32px));background:#fff;border-radius:10px;box-shadow:0 20px 40px -12px #0f172a47,0 0 0 1px #0f172a14;overflow:hidden;box-sizing:border-box;animation:rptDialogIn .18s cubic-bezier(.16,1,.3,1) forwards}@keyframes rptDialogIn{0%{opacity:0;transform:scale(.97) translateY(4px)}to{opacity:1;transform:scale(1) translateY(0)}}.rpt-dialog-header{display:flex;align-items:center;justify-content:space-between;padding:10px 16px;border-bottom:1px solid #e2e8f0;background:#f8fafc;flex-shrink:0;box-sizing:border-box}.rpt-header-left{display:flex;align-items:center;gap:10px}.rpt-review-icon-badge{display:flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:50%;background:#dbeafe;border:1px solid #a7f3d0;color:#1d4ed8;flex-shrink:0}.rpt-dialog-title{margin:0;font-size:.925rem;font-weight:700;color:#0f172a;line-height:1.25}.rpt-dialog-subtitle{margin:1px 0 0;font-size:.725rem;color:#64748b;line-height:1.2}.rpt-dialog-close{background:transparent;border:none;color:#94a3b8;cursor:pointer;padding:4px;border-radius:5px;display:flex;align-items:center;justify-content:center;transition:all .12s ease}.rpt-dialog-close:hover{background:#e2e8f0;color:#0f172a}.rpt-dialog-body{display:grid;grid-template-columns:280px minmax(0,1fr);grid-template-rows:minmax(0,1fr);flex:1;min-height:0;min-width:0;overflow:hidden;box-sizing:border-box}@media (max-width: 780px){.rpt-dialog-body{grid-template-columns:minmax(0,1fr);grid-template-rows:auto auto;overflow-y:auto;overscroll-behavior:contain}}.rpt-settings-column{display:flex;flex-direction:column;gap:9px;padding:12px 14px;background:#f8fafc;border-right:1px solid #e2e8f0;overflow-y:auto;min-height:0;overscroll-behavior:contain;box-sizing:border-box}.rpt-section-header{font-size:.675rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:#475569;margin-bottom:1px}.rpt-form-group{display:flex;flex-direction:column;gap:3px}.rpt-label{font-size:.725rem;font-weight:600;color:#334155}.rpt-input{width:100%;box-sizing:border-box;height:28px;padding:4px 8px;font-size:.775rem;border:1px solid #cbd5e1;border-radius:5px;background:#fff;color:#0f172a;outline:none;transition:all .12s ease}.rpt-input:focus{border-color:#2563eb;box-shadow:0 0 0 2px #2563eb24}.rpt-slug-input-wrapper{display:flex;align-items:center;background:#fff;border:1px solid #cbd5e1;border-radius:5px;height:28px;overflow:hidden;box-sizing:border-box;transition:all .12s ease}.rpt-slug-input-wrapper:focus-within{border-color:#2563eb;box-shadow:0 0 0 2px #2563eb24}.rpt-slug-prefix{padding:0 0 0 8px;font-size:.725rem;color:#64748b;background:transparent;-webkit-user-select:none;user-select:none;line-height:28px}.rpt-slug-field{border:none!important;padding-left:2px!important;box-shadow:none!important;height:100%!important}.rpt-help-text{font-size:.65rem;color:#64748b;line-height:1.2}.rpt-publish-card{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:6px 10px;background:#fff;border:1px solid #e2e8f0;border-radius:6px;box-sizing:border-box}.rpt-publish-text{display:flex;flex-direction:column;gap:1px}.rpt-publish-title{font-size:.75rem;font-weight:600;color:#0f172a}.rpt-publish-desc{font-size:.65rem;color:#64748b;line-height:1.2}.rpt-switch{position:relative;display:inline-block;width:32px;height:18px;flex-shrink:0}.rpt-switch input{opacity:0;width:0;height:0}.rpt-slider{position:absolute;cursor:pointer;inset:0;background-color:#cbd5e1;transition:.18s;border-radius:9999px}.rpt-slider:before{position:absolute;content:\"\";height:14px;width:14px;left:2px;bottom:2px;background-color:#fff;transition:.18s;border-radius:50%}input:checked+.rpt-slider{background-color:#2563eb}input:checked+.rpt-slider:before{transform:translate(14px)}.rpt-share-card{padding:6px 9px;background:#eff6ff;border:1px solid #bfdbfe;border-radius:6px;box-sizing:border-box}.rpt-share-url-row{display:flex;align-items:center;gap:6px;overflow:hidden}.rpt-share-label{font-size:.675rem;font-weight:600;color:#1e40af;flex-shrink:0}.rpt-share-code{flex:1;font-size:.6875rem;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;color:#1e3a8a;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rpt-btn-copy{display:inline-flex;align-items:center;gap:3px;padding:2px 6px;background:#fff;border:1px solid #93c5fd;border-radius:4px;font-size:.65rem;font-weight:600;color:#2563eb;cursor:pointer;flex-shrink:0;transition:all .12s ease;height:20px}.rpt-btn-copy:hover{background:#dbeafe}.rpt-preview-column{display:flex;flex-direction:column;background:#fff;overflow:hidden;min-width:0;box-sizing:border-box}.rpt-preview-header{display:flex;align-items:center;justify-content:space-between;padding:8px 14px;border-bottom:1px solid #e2e8f0;background:#fff;flex-shrink:0;box-sizing:border-box}.rpt-preview-badge{display:flex;align-items:center;gap:6px;font-size:.725rem;font-weight:600;color:#0f172a}.rpt-live-dot{width:6px;height:6px;border-radius:50%;background:#10b981;box-shadow:0 0 0 2px #10b98133}.rpt-preview-hint{font-size:.675rem;color:#64748b}.rpt-preview-runtime-wrapper{flex:1;min-height:0;overscroll-behavior:contain;overflow-y:auto;overflow-x:hidden;padding:10px 12px;background:#f8fafc;box-sizing:border-box}.rpt-preview-runtime-wrapper::-webkit-scrollbar,.rpt-settings-column::-webkit-scrollbar{width:5px}.rpt-preview-runtime-wrapper::-webkit-scrollbar-thumb,.rpt-settings-column::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:9999px}.rpt-preview-runtime-wrapper::-webkit-scrollbar-thumb:hover,.rpt-settings-column::-webkit-scrollbar-thumb:hover{background:#94a3b8}.rpt-preview-card-frame{background:#fff;border:1px solid #e2e8f0;border-radius:8px;box-shadow:0 1px 3px #0f172a0a;padding:10px;min-height:100%;box-sizing:border-box;overflow-x:hidden}@media (max-width: 780px){.rpt-settings-column{overflow-y:visible}.rpt-preview-runtime-wrapper{flex:none;max-height:320px}.rpt-preview-hint{display:none}}.rpt-dialog-footer{display:flex;flex-wrap:wrap;gap:8px;align-items:center;justify-content:space-between;padding:8px 16px;background:#f8fafc;border-top:1px solid #e2e8f0;flex-shrink:0;box-sizing:border-box}.rpt-saved-pill{display:inline-flex;align-items:center;gap:4px;font-size:.7rem;font-weight:600;color:#15803d;background:#dcfce7;padding:2px 7px;border-radius:9999px}.rpt-footer-actions{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-left:auto}.rpt-btn{display:inline-flex;align-items:center;gap:5px;padding:5px 13px;font-size:.75rem;font-weight:600;border-radius:6px;cursor:pointer;transition:all .12s ease;border:1px solid transparent;box-sizing:border-box;line-height:1.4}.rpt-btn-secondary{background:#fff;border-color:#cbd5e1;color:#334155}.rpt-btn-secondary:hover{background:#f1f5f9;color:#0f172a;border-color:#94a3b8}.rpt-btn-primary{background:linear-gradient(135deg,#2563eb,#1d4ed8);color:#fff;box-shadow:0 1px 2px #2563eb33}.rpt-btn-primary:hover{background:linear-gradient(135deg,#1d4ed8,#1e40af);box-shadow:0 2px 4px #2563eb40}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.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.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: i2$1.CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }, { kind: "directive", type: CdkScrollable, selector: "[cdk-scrollable], [cdkScrollable]" }, { kind: "component", type: ReportRuntimeComponent, selector: "rpt-report-runtime, rpt-runtime", inputs: ["reportId", "report", "resultData"], outputs: ["resetRequested", "execute", "executed", "exportRequested", "printRequested", "customActionTriggered", "gridStateChange", "columnDefsChange", "selectionChange", "selectionChanged", "rowActionTriggered", "drilldownRequested"] }, { kind: "component", type: RptIconComponent, selector: "rpt-icon", inputs: ["name", "size", "color", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
11811
11979
|
}
|
|
11812
11980
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ReportSavedDialogComponent, decorators: [{
|
|
11813
11981
|
type: Component,
|
|
11814
|
-
args: [{ selector: 'rpt-report-saved-dialog', standalone: true, imports: [CommonModule, FormsModule, A11yModule, ReportRuntimeComponent, RptIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
11982
|
+
args: [{ selector: 'rpt-report-saved-dialog', standalone: true, imports: [CommonModule, FormsModule, A11yModule, CdkScrollable, ReportRuntimeComponent, RptIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
11815
11983
|
<div class="rpt-dialog-shell" [attr.aria-busy]="isSaving() || isCheckingId()">
|
|
11816
11984
|
<!-- Review before the only persistence request -->
|
|
11817
11985
|
<div class="rpt-dialog-header" [attr.inert]="updateConfirmation() ? '' : null">
|
|
@@ -11836,9 +12004,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
11836
12004
|
</div>
|
|
11837
12005
|
|
|
11838
12006
|
<!-- Modal Body (Two-Column Split) -->
|
|
11839
|
-
<div class="rpt-dialog-body" [attr.inert]="updateConfirmation() || isPreparingSave() ? '' : null">
|
|
12007
|
+
<div class="rpt-dialog-body" cdkScrollable [attr.inert]="updateConfirmation() || isPreparingSave() ? '' : null">
|
|
11840
12008
|
<!-- Left Column: Settings & Publishing -->
|
|
11841
|
-
<div class="rpt-settings-column">
|
|
12009
|
+
<div class="rpt-settings-column" cdkScrollable>
|
|
11842
12010
|
<div class="rpt-section-header">Page Navigation & Settings</div>
|
|
11843
12011
|
|
|
11844
12012
|
<div class="rpt-form-group">
|
|
@@ -12021,7 +12189,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
12021
12189
|
<span class="rpt-preview-hint">Interactive preview matching runtime page</span>
|
|
12022
12190
|
</div>
|
|
12023
12191
|
|
|
12024
|
-
<div class="rpt-preview-runtime-wrapper">
|
|
12192
|
+
<div class="rpt-preview-runtime-wrapper" cdkScrollable>
|
|
12025
12193
|
<div class="rpt-preview-card-frame">
|
|
12026
12194
|
<rpt-runtime [report]="currentPreviewDef()" />
|
|
12027
12195
|
</div>
|
|
@@ -12108,7 +12276,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
12108
12276
|
</div>
|
|
12109
12277
|
}
|
|
12110
12278
|
</div>
|
|
12111
|
-
`, styles: ["@keyframes rptSpin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.rpt-spin{display:inline-block;animation:rptSpin 1s linear infinite}@keyframes rptOverlayIn{0%{opacity:0}to{opacity:1}}@keyframes rptCardIn{0%{opacity:0;transform:scale(.96) translateY(6px)}to{opacity:1;transform:scale(1) translateY(0)}}.rpt-id-actions{display:flex;align-items:center;gap:6px;margin-top:4px}.rpt-id-action-btn{display:inline-flex;align-items:center;gap:5px;height:25px;padding:0 9px;font-size:.7rem;font-weight:500;color:#334155;background:#fff;border:1px solid #cbd5e1;border-radius:5px;cursor:pointer;transition:all .15s ease;box-sizing:border-box;white-space:nowrap;font-family:inherit;line-height:1}.rpt-id-action-btn:hover:not(:disabled){background:#f1f5f9;color:#0f172a;border-color:#94a3b8;box-shadow:0 1px 2px #0f172a0d}.rpt-id-action-btn:active:not(:disabled){background:#e2e8f0}.rpt-id-action-btn:disabled{opacity:.5;cursor:not-allowed}.rpt-id-action-btn.is-checking{color:#2563eb;border-color:#93c5fd;background:#eff6ff}.rpt-id-status{display:inline-flex;align-items:center;gap:5px;font-size:.72rem;line-height:1.3;margin:4px 0 0;padding:3px 8px;border-radius:5px;transition:all .15s ease;width:fit-content;max-width:100%;box-sizing:border-box}.rpt-id-status:empty{display:none}.rpt-status-icon{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}.rpt-id-status-success{color:#15803d;background:#f0fdf4;border:1px solid #bbf7d0}.rpt-id-status-warning{color:#b45309;background:#fffbeb;border:1px solid #fef3c7}.rpt-id-status-error{color:#b91c1c;background:#fef2f2;border:1px solid #fecaca}.rpt-id-status-checking{color:#1d4ed8;background:#eff6ff;border:1px solid #dbeafe}.rpt-update-overlay{position:absolute;inset:0;z-index:50;background:#0f172a73;backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);display:flex;align-items:center;justify-content:center;padding:16px;box-sizing:border-box;animation:rptOverlayIn .16s cubic-bezier(.16,1,.3,1) forwards}.rpt-update-card{box-sizing:border-box;width:min(440px,100%);max-height:94%;background:#fff;border-radius:10px;box-shadow:0 20px 45px -8px #0f172a52,0 0 0 1px #0f172a14;border:1px solid #e2e8f0;overflow:hidden;display:flex;flex-direction:column;animation:rptCardIn .18s cubic-bezier(.16,1,.3,1) forwards}.rpt-update-card-header{display:flex;align-items:center;gap:12px;padding:12px 16px;background:#fffbeb;border-bottom:1px solid #fef3c7;flex-shrink:0}.rpt-update-icon-badge{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:8px;background:#fef3c7;border:1px solid #fde68a;color:#b45309;flex-shrink:0}.rpt-update-header-info{flex:1;min-width:0}.rpt-update-title{margin:0;font-size:.925rem;font-weight:700;color:#92400e;line-height:1.25}.rpt-update-subtitle{font-size:.7rem;color:#b45309;margin-top:1px;display:block}.rpt-update-card-body{padding:14px 16px;display:flex;flex-direction:column;gap:11px;overflow-y:auto;box-sizing:border-box}.rpt-update-prompt{margin:0;font-size:.8rem;line-height:1.45;color:#334155}.rpt-conflict-card{background:#f8fafc;border:1px solid #e2e8f0;border-radius:7px;padding:9px 12px;display:flex;flex-direction:column;gap:7px}.rpt-conflict-row{display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:.735rem}.rpt-conflict-label{color:#64748b;font-weight:500;flex-shrink:0}.rpt-conflict-code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.72rem;background:#e2e8f0;color:#0f172a;padding:2px 6px;border-radius:4px;font-weight:600}.rpt-conflict-name{font-weight:600;color:#0f172a;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rpt-conflict-badge{font-size:.68rem;font-weight:600;padding:1px 7px;border-radius:9999px;background:#e0f2fe;color:#0369a1;border:1px solid #bae6fd}.rpt-overwrite-notice{display:flex;align-items:flex-start;gap:7px;font-size:.715rem;color:#b45309;background:#fffbeb;border:1px solid #fef3c7;padding:7px 10px;border-radius:6px;line-height:1.35}.rpt-overwrite-notice rpt-icon{flex-shrink:0;margin-top:1px}.rpt-update-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:10px 16px;background:#f8fafc;border-top:1px solid #e2e8f0;flex-shrink:0}.rpt-btn-confirm-save{background:linear-gradient(135deg,#2563eb,#1d4ed8);color:#fff;box-shadow:0 1px 2px #2563eb33}.rpt-btn-confirm-save:hover{background:linear-gradient(135deg,#1d4ed8,#1e40af);box-shadow:0 2px 4px #2563eb40}#rpt-dlg-id-error:empty,#rpt-dlg-id-status:empty{display:none}.rpt-dialog-footer,.rpt-footer-actions{flex-wrap:wrap;gap:8px}.rpt-btn,.rpt-dialog-close{min-height:36px}@media (max-width: 640px){.rpt-btn,.rpt-dialog-close{min-height:44px;min-width:44px}.rpt-footer-left{width:100%}}.rpt-field-error{color:#b91c1c;font-size:.8rem;margin:4px 0}button:disabled{opacity:.6;cursor:wait}textarea.rpt-input{resize:vertical;min-height:56px}.rpt-footer-left{min-width:0;padding-right:12px}:host{display:flex;align-items:center;justify-content:center;font-family:var(--rpt-font-family, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif);color:#0f172a;box-sizing:border-box}.rpt-dialog-shell{position:relative;display:flex;flex-direction:column;width:min(840px,92vw);max-height:min(540px,84vh);background:#fff;border-radius:10px;box-shadow:0 20px 40px -12px #0f172a47,0 0 0 1px #0f172a14;overflow:hidden;box-sizing:border-box;animation:rptDialogIn .18s cubic-bezier(.16,1,.3,1) forwards}@keyframes rptDialogIn{0%{opacity:0;transform:scale(.97) translateY(4px)}to{opacity:1;transform:scale(1) translateY(0)}}.rpt-dialog-header{display:flex;align-items:center;justify-content:space-between;padding:10px 16px;border-bottom:1px solid #e2e8f0;background:#f8fafc;flex-shrink:0;box-sizing:border-box}.rpt-header-left{display:flex;align-items:center;gap:10px}.rpt-review-icon-badge{display:flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:50%;background:#dbeafe;border:1px solid #a7f3d0;color:#1d4ed8;flex-shrink:0}.rpt-dialog-title{margin:0;font-size:.925rem;font-weight:700;color:#0f172a;line-height:1.25}.rpt-dialog-subtitle{margin:1px 0 0;font-size:.725rem;color:#64748b;line-height:1.2}.rpt-dialog-close{background:transparent;border:none;color:#94a3b8;cursor:pointer;padding:4px;border-radius:5px;display:flex;align-items:center;justify-content:center;transition:all .12s ease}.rpt-dialog-close:hover{background:#e2e8f0;color:#0f172a}.rpt-dialog-body{display:grid;grid-template-columns:280px 1fr;flex:1;min-height:360px;max-height:calc(84vh - 96px);overflow:hidden;box-sizing:border-box}@media (max-width: 780px){.rpt-dialog-body{grid-template-columns:1fr;overflow-y:auto}}.rpt-settings-column{display:flex;flex-direction:column;gap:9px;padding:12px 14px;background:#f8fafc;border-right:1px solid #e2e8f0;overflow-y:auto;box-sizing:border-box}.rpt-section-header{font-size:.675rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:#475569;margin-bottom:1px}.rpt-form-group{display:flex;flex-direction:column;gap:3px}.rpt-label{font-size:.725rem;font-weight:600;color:#334155}.rpt-input{width:100%;box-sizing:border-box;height:28px;padding:4px 8px;font-size:.775rem;border:1px solid #cbd5e1;border-radius:5px;background:#fff;color:#0f172a;outline:none;transition:all .12s ease}.rpt-input:focus{border-color:#2563eb;box-shadow:0 0 0 2px #2563eb24}.rpt-slug-input-wrapper{display:flex;align-items:center;background:#fff;border:1px solid #cbd5e1;border-radius:5px;height:28px;overflow:hidden;box-sizing:border-box;transition:all .12s ease}.rpt-slug-input-wrapper:focus-within{border-color:#2563eb;box-shadow:0 0 0 2px #2563eb24}.rpt-slug-prefix{padding:0 0 0 8px;font-size:.725rem;color:#64748b;background:transparent;-webkit-user-select:none;user-select:none;line-height:28px}.rpt-slug-field{border:none!important;padding-left:2px!important;box-shadow:none!important;height:100%!important}.rpt-help-text{font-size:.65rem;color:#64748b;line-height:1.2}.rpt-publish-card{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:6px 10px;background:#fff;border:1px solid #e2e8f0;border-radius:6px;box-sizing:border-box}.rpt-publish-text{display:flex;flex-direction:column;gap:1px}.rpt-publish-title{font-size:.75rem;font-weight:600;color:#0f172a}.rpt-publish-desc{font-size:.65rem;color:#64748b;line-height:1.2}.rpt-switch{position:relative;display:inline-block;width:32px;height:18px;flex-shrink:0}.rpt-switch input{opacity:0;width:0;height:0}.rpt-slider{position:absolute;cursor:pointer;inset:0;background-color:#cbd5e1;transition:.18s;border-radius:9999px}.rpt-slider:before{position:absolute;content:\"\";height:14px;width:14px;left:2px;bottom:2px;background-color:#fff;transition:.18s;border-radius:50%}input:checked+.rpt-slider{background-color:#2563eb}input:checked+.rpt-slider:before{transform:translate(14px)}.rpt-share-card{padding:6px 9px;background:#eff6ff;border:1px solid #bfdbfe;border-radius:6px;box-sizing:border-box}.rpt-share-url-row{display:flex;align-items:center;gap:6px;overflow:hidden}.rpt-share-label{font-size:.675rem;font-weight:600;color:#1e40af;flex-shrink:0}.rpt-share-code{flex:1;font-size:.6875rem;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;color:#1e3a8a;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rpt-btn-copy{display:inline-flex;align-items:center;gap:3px;padding:2px 6px;background:#fff;border:1px solid #93c5fd;border-radius:4px;font-size:.65rem;font-weight:600;color:#2563eb;cursor:pointer;flex-shrink:0;transition:all .12s ease;height:20px}.rpt-btn-copy:hover{background:#dbeafe}.rpt-preview-column{display:flex;flex-direction:column;background:#fff;overflow:hidden;min-width:0;box-sizing:border-box}.rpt-preview-header{display:flex;align-items:center;justify-content:space-between;padding:8px 14px;border-bottom:1px solid #e2e8f0;background:#fff;flex-shrink:0;box-sizing:border-box}.rpt-preview-badge{display:flex;align-items:center;gap:6px;font-size:.725rem;font-weight:600;color:#0f172a}.rpt-live-dot{width:6px;height:6px;border-radius:50%;background:#10b981;box-shadow:0 0 0 2px #10b98133}.rpt-preview-hint{font-size:.675rem;color:#64748b}.rpt-preview-runtime-wrapper{flex:1;overflow-y:auto;overflow-x:hidden;padding:10px 12px;background:#f8fafc;box-sizing:border-box}.rpt-preview-runtime-wrapper::-webkit-scrollbar,.rpt-settings-column::-webkit-scrollbar{width:5px}.rpt-preview-runtime-wrapper::-webkit-scrollbar-thumb,.rpt-settings-column::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:9999px}.rpt-preview-runtime-wrapper::-webkit-scrollbar-thumb:hover,.rpt-settings-column::-webkit-scrollbar-thumb:hover{background:#94a3b8}.rpt-preview-card-frame{background:#fff;border:1px solid #e2e8f0;border-radius:8px;box-shadow:0 1px 3px #0f172a0a;padding:10px;min-height:100%;box-sizing:border-box;overflow-x:hidden}.rpt-dialog-footer{display:flex;align-items:center;justify-content:space-between;padding:8px 16px;background:#f8fafc;border-top:1px solid #e2e8f0;flex-shrink:0;box-sizing:border-box}.rpt-saved-pill{display:inline-flex;align-items:center;gap:4px;font-size:.7rem;font-weight:600;color:#15803d;background:#dcfce7;padding:2px 7px;border-radius:9999px}.rpt-footer-actions{display:flex;align-items:center;gap:8px;margin-left:auto}.rpt-btn{display:inline-flex;align-items:center;gap:5px;padding:5px 13px;font-size:.75rem;font-weight:600;border-radius:6px;cursor:pointer;transition:all .12s ease;border:1px solid transparent;box-sizing:border-box;line-height:1.4}.rpt-btn-secondary{background:#fff;border-color:#cbd5e1;color:#334155}.rpt-btn-secondary:hover{background:#f1f5f9;color:#0f172a;border-color:#94a3b8}.rpt-btn-primary{background:linear-gradient(135deg,#2563eb,#1d4ed8);color:#fff;box-shadow:0 1px 2px #2563eb33}.rpt-btn-primary:hover{background:linear-gradient(135deg,#1d4ed8,#1e40af);box-shadow:0 2px 4px #2563eb40}\n"] }]
|
|
12279
|
+
`, styles: ["@keyframes rptSpin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.rpt-spin{display:inline-block;animation:rptSpin 1s linear infinite}@keyframes rptOverlayIn{0%{opacity:0}to{opacity:1}}@keyframes rptCardIn{0%{opacity:0;transform:scale(.96) translateY(6px)}to{opacity:1;transform:scale(1) translateY(0)}}.rpt-id-actions{display:flex;align-items:center;gap:6px;margin-top:4px}.rpt-id-action-btn{display:inline-flex;align-items:center;gap:5px;height:25px;padding:0 9px;font-size:.7rem;font-weight:500;color:#334155;background:#fff;border:1px solid #cbd5e1;border-radius:5px;cursor:pointer;transition:all .15s ease;box-sizing:border-box;white-space:nowrap;font-family:inherit;line-height:1}.rpt-id-action-btn:hover:not(:disabled){background:#f1f5f9;color:#0f172a;border-color:#94a3b8;box-shadow:0 1px 2px #0f172a0d}.rpt-id-action-btn:active:not(:disabled){background:#e2e8f0}.rpt-id-action-btn:disabled{opacity:.5;cursor:not-allowed}.rpt-id-action-btn.is-checking{color:#2563eb;border-color:#93c5fd;background:#eff6ff}.rpt-id-status{display:inline-flex;align-items:center;gap:5px;font-size:.72rem;line-height:1.3;margin:4px 0 0;padding:3px 8px;border-radius:5px;transition:all .15s ease;width:fit-content;max-width:100%;box-sizing:border-box}.rpt-id-status:empty{display:none}.rpt-status-icon{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0}.rpt-id-status-success{color:#15803d;background:#f0fdf4;border:1px solid #bbf7d0}.rpt-id-status-warning{color:#b45309;background:#fffbeb;border:1px solid #fef3c7}.rpt-id-status-error{color:#b91c1c;background:#fef2f2;border:1px solid #fecaca}.rpt-id-status-checking{color:#1d4ed8;background:#eff6ff;border:1px solid #dbeafe}.rpt-update-overlay{position:absolute;inset:0;z-index:50;background:#0f172a73;backdrop-filter:blur(3px);-webkit-backdrop-filter:blur(3px);display:flex;align-items:center;justify-content:center;padding:16px;box-sizing:border-box;animation:rptOverlayIn .16s cubic-bezier(.16,1,.3,1) forwards}.rpt-update-card{box-sizing:border-box;width:min(440px,100%);max-height:94%;background:#fff;border-radius:10px;box-shadow:0 20px 45px -8px #0f172a52,0 0 0 1px #0f172a14;border:1px solid #e2e8f0;overflow:hidden;display:flex;flex-direction:column;animation:rptCardIn .18s cubic-bezier(.16,1,.3,1) forwards}.rpt-update-card-header{display:flex;align-items:center;gap:12px;padding:12px 16px;background:#fffbeb;border-bottom:1px solid #fef3c7;flex-shrink:0}.rpt-update-icon-badge{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:8px;background:#fef3c7;border:1px solid #fde68a;color:#b45309;flex-shrink:0}.rpt-update-header-info{flex:1;min-width:0}.rpt-update-title{margin:0;font-size:.925rem;font-weight:700;color:#92400e;line-height:1.25}.rpt-update-subtitle{font-size:.7rem;color:#b45309;margin-top:1px;display:block}.rpt-update-card-body{padding:14px 16px;display:flex;flex-direction:column;gap:11px;overflow-y:auto;box-sizing:border-box}.rpt-update-prompt{margin:0;font-size:.8rem;line-height:1.45;color:#334155}.rpt-conflict-card{background:#f8fafc;border:1px solid #e2e8f0;border-radius:7px;padding:9px 12px;display:flex;flex-direction:column;gap:7px}.rpt-conflict-row{display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:.735rem}.rpt-conflict-label{color:#64748b;font-weight:500;flex-shrink:0}.rpt-conflict-code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.72rem;background:#e2e8f0;color:#0f172a;padding:2px 6px;border-radius:4px;font-weight:600}.rpt-conflict-name{font-weight:600;color:#0f172a;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rpt-conflict-badge{font-size:.68rem;font-weight:600;padding:1px 7px;border-radius:9999px;background:#e0f2fe;color:#0369a1;border:1px solid #bae6fd}.rpt-overwrite-notice{display:flex;align-items:flex-start;gap:7px;font-size:.715rem;color:#b45309;background:#fffbeb;border:1px solid #fef3c7;padding:7px 10px;border-radius:6px;line-height:1.35}.rpt-overwrite-notice rpt-icon{flex-shrink:0;margin-top:1px}.rpt-update-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:10px 16px;background:#f8fafc;border-top:1px solid #e2e8f0;flex-shrink:0}.rpt-btn-confirm-save{background:linear-gradient(135deg,#2563eb,#1d4ed8);color:#fff;box-shadow:0 1px 2px #2563eb33}.rpt-btn-confirm-save:hover{background:linear-gradient(135deg,#1d4ed8,#1e40af);box-shadow:0 2px 4px #2563eb40}#rpt-dlg-id-error:empty,#rpt-dlg-id-status:empty{display:none}.rpt-dialog-footer,.rpt-footer-actions{flex-wrap:wrap;gap:8px}.rpt-btn,.rpt-dialog-close{min-height:36px}@media (max-width: 640px){.rpt-btn,.rpt-dialog-close{min-height:44px;min-width:44px}.rpt-footer-left{width:100%}}.rpt-field-error{color:#b91c1c;font-size:.8rem;margin:4px 0}button:disabled{opacity:.6;cursor:wait}textarea.rpt-input{resize:vertical;min-height:56px}.rpt-footer-left{min-width:0;padding-right:12px}:host{display:flex;width:100%;min-width:0;min-height:0;max-height:inherit;align-items:center;justify-content:center;font-family:var(--rpt-font-family, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif);color:#0f172a;box-sizing:border-box}.rpt-dialog-shell{position:relative;display:flex;flex-direction:column;width:100%;max-width:100%;min-height:0;max-height:min(540px,calc(100vh - 32px));max-height:min(540px,calc(100dvh - 32px));background:#fff;border-radius:10px;box-shadow:0 20px 40px -12px #0f172a47,0 0 0 1px #0f172a14;overflow:hidden;box-sizing:border-box;animation:rptDialogIn .18s cubic-bezier(.16,1,.3,1) forwards}@keyframes rptDialogIn{0%{opacity:0;transform:scale(.97) translateY(4px)}to{opacity:1;transform:scale(1) translateY(0)}}.rpt-dialog-header{display:flex;align-items:center;justify-content:space-between;padding:10px 16px;border-bottom:1px solid #e2e8f0;background:#f8fafc;flex-shrink:0;box-sizing:border-box}.rpt-header-left{display:flex;align-items:center;gap:10px}.rpt-review-icon-badge{display:flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:50%;background:#dbeafe;border:1px solid #a7f3d0;color:#1d4ed8;flex-shrink:0}.rpt-dialog-title{margin:0;font-size:.925rem;font-weight:700;color:#0f172a;line-height:1.25}.rpt-dialog-subtitle{margin:1px 0 0;font-size:.725rem;color:#64748b;line-height:1.2}.rpt-dialog-close{background:transparent;border:none;color:#94a3b8;cursor:pointer;padding:4px;border-radius:5px;display:flex;align-items:center;justify-content:center;transition:all .12s ease}.rpt-dialog-close:hover{background:#e2e8f0;color:#0f172a}.rpt-dialog-body{display:grid;grid-template-columns:280px minmax(0,1fr);grid-template-rows:minmax(0,1fr);flex:1;min-height:0;min-width:0;overflow:hidden;box-sizing:border-box}@media (max-width: 780px){.rpt-dialog-body{grid-template-columns:minmax(0,1fr);grid-template-rows:auto auto;overflow-y:auto;overscroll-behavior:contain}}.rpt-settings-column{display:flex;flex-direction:column;gap:9px;padding:12px 14px;background:#f8fafc;border-right:1px solid #e2e8f0;overflow-y:auto;min-height:0;overscroll-behavior:contain;box-sizing:border-box}.rpt-section-header{font-size:.675rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:#475569;margin-bottom:1px}.rpt-form-group{display:flex;flex-direction:column;gap:3px}.rpt-label{font-size:.725rem;font-weight:600;color:#334155}.rpt-input{width:100%;box-sizing:border-box;height:28px;padding:4px 8px;font-size:.775rem;border:1px solid #cbd5e1;border-radius:5px;background:#fff;color:#0f172a;outline:none;transition:all .12s ease}.rpt-input:focus{border-color:#2563eb;box-shadow:0 0 0 2px #2563eb24}.rpt-slug-input-wrapper{display:flex;align-items:center;background:#fff;border:1px solid #cbd5e1;border-radius:5px;height:28px;overflow:hidden;box-sizing:border-box;transition:all .12s ease}.rpt-slug-input-wrapper:focus-within{border-color:#2563eb;box-shadow:0 0 0 2px #2563eb24}.rpt-slug-prefix{padding:0 0 0 8px;font-size:.725rem;color:#64748b;background:transparent;-webkit-user-select:none;user-select:none;line-height:28px}.rpt-slug-field{border:none!important;padding-left:2px!important;box-shadow:none!important;height:100%!important}.rpt-help-text{font-size:.65rem;color:#64748b;line-height:1.2}.rpt-publish-card{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:6px 10px;background:#fff;border:1px solid #e2e8f0;border-radius:6px;box-sizing:border-box}.rpt-publish-text{display:flex;flex-direction:column;gap:1px}.rpt-publish-title{font-size:.75rem;font-weight:600;color:#0f172a}.rpt-publish-desc{font-size:.65rem;color:#64748b;line-height:1.2}.rpt-switch{position:relative;display:inline-block;width:32px;height:18px;flex-shrink:0}.rpt-switch input{opacity:0;width:0;height:0}.rpt-slider{position:absolute;cursor:pointer;inset:0;background-color:#cbd5e1;transition:.18s;border-radius:9999px}.rpt-slider:before{position:absolute;content:\"\";height:14px;width:14px;left:2px;bottom:2px;background-color:#fff;transition:.18s;border-radius:50%}input:checked+.rpt-slider{background-color:#2563eb}input:checked+.rpt-slider:before{transform:translate(14px)}.rpt-share-card{padding:6px 9px;background:#eff6ff;border:1px solid #bfdbfe;border-radius:6px;box-sizing:border-box}.rpt-share-url-row{display:flex;align-items:center;gap:6px;overflow:hidden}.rpt-share-label{font-size:.675rem;font-weight:600;color:#1e40af;flex-shrink:0}.rpt-share-code{flex:1;font-size:.6875rem;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;color:#1e3a8a;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rpt-btn-copy{display:inline-flex;align-items:center;gap:3px;padding:2px 6px;background:#fff;border:1px solid #93c5fd;border-radius:4px;font-size:.65rem;font-weight:600;color:#2563eb;cursor:pointer;flex-shrink:0;transition:all .12s ease;height:20px}.rpt-btn-copy:hover{background:#dbeafe}.rpt-preview-column{display:flex;flex-direction:column;background:#fff;overflow:hidden;min-width:0;box-sizing:border-box}.rpt-preview-header{display:flex;align-items:center;justify-content:space-between;padding:8px 14px;border-bottom:1px solid #e2e8f0;background:#fff;flex-shrink:0;box-sizing:border-box}.rpt-preview-badge{display:flex;align-items:center;gap:6px;font-size:.725rem;font-weight:600;color:#0f172a}.rpt-live-dot{width:6px;height:6px;border-radius:50%;background:#10b981;box-shadow:0 0 0 2px #10b98133}.rpt-preview-hint{font-size:.675rem;color:#64748b}.rpt-preview-runtime-wrapper{flex:1;min-height:0;overscroll-behavior:contain;overflow-y:auto;overflow-x:hidden;padding:10px 12px;background:#f8fafc;box-sizing:border-box}.rpt-preview-runtime-wrapper::-webkit-scrollbar,.rpt-settings-column::-webkit-scrollbar{width:5px}.rpt-preview-runtime-wrapper::-webkit-scrollbar-thumb,.rpt-settings-column::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:9999px}.rpt-preview-runtime-wrapper::-webkit-scrollbar-thumb:hover,.rpt-settings-column::-webkit-scrollbar-thumb:hover{background:#94a3b8}.rpt-preview-card-frame{background:#fff;border:1px solid #e2e8f0;border-radius:8px;box-shadow:0 1px 3px #0f172a0a;padding:10px;min-height:100%;box-sizing:border-box;overflow-x:hidden}@media (max-width: 780px){.rpt-settings-column{overflow-y:visible}.rpt-preview-runtime-wrapper{flex:none;max-height:320px}.rpt-preview-hint{display:none}}.rpt-dialog-footer{display:flex;flex-wrap:wrap;gap:8px;align-items:center;justify-content:space-between;padding:8px 16px;background:#f8fafc;border-top:1px solid #e2e8f0;flex-shrink:0;box-sizing:border-box}.rpt-saved-pill{display:inline-flex;align-items:center;gap:4px;font-size:.7rem;font-weight:600;color:#15803d;background:#dcfce7;padding:2px 7px;border-radius:9999px}.rpt-footer-actions{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-left:auto}.rpt-btn{display:inline-flex;align-items:center;gap:5px;padding:5px 13px;font-size:.75rem;font-weight:600;border-radius:6px;cursor:pointer;transition:all .12s ease;border:1px solid transparent;box-sizing:border-box;line-height:1.4}.rpt-btn-secondary{background:#fff;border-color:#cbd5e1;color:#334155}.rpt-btn-secondary:hover{background:#f1f5f9;color:#0f172a;border-color:#94a3b8}.rpt-btn-primary{background:linear-gradient(135deg,#2563eb,#1d4ed8);color:#fff;box-shadow:0 1px 2px #2563eb33}.rpt-btn-primary:hover{background:linear-gradient(135deg,#1d4ed8,#1e40af);box-shadow:0 2px 4px #2563eb40}\n"] }]
|
|
12112
12280
|
}] });
|
|
12113
12281
|
|
|
12114
12282
|
/** Private state owned by one designer; never mutates application providers. */
|
|
@@ -12417,9 +12585,9 @@ class DesignerToolbarComponent {
|
|
|
12417
12585
|
}
|
|
12418
12586
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DesignerToolbarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
12419
12587
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: DesignerToolbarComponent, isStandalone: true, selector: "rpt-designer-toolbar", inputs: { isLeftPanelCollapsed: { classPropertyName: "isLeftPanelCollapsed", publicName: "isLeftPanelCollapsed", isSignal: true, isRequired: false, transformFunction: null }, isRightPanelCollapsed: { classPropertyName: "isRightPanelCollapsed", publicName: "isRightPanelCollapsed", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { toggleLeftPanel: "toggleLeftPanel", toggleRightPanel: "toggleRightPanel", saveRequested: "saveRequested" }, ngImport: i0, template: `
|
|
12420
|
-
<div class="toolbar-root">
|
|
12588
|
+
<div class="rpt-toolbar-root">
|
|
12421
12589
|
<!-- Left: Panel toggle, Title, Inline Rename, + New Report, Switcher & Dirty indicator -->
|
|
12422
|
-
<div class="toolbar-left">
|
|
12590
|
+
<div class="rpt-toolbar-left">
|
|
12423
12591
|
@if (viewMode() === 'design') {
|
|
12424
12592
|
<button
|
|
12425
12593
|
type="button"
|
|
@@ -12494,7 +12662,7 @@ class DesignerToolbarComponent {
|
|
|
12494
12662
|
</div>
|
|
12495
12663
|
|
|
12496
12664
|
<!-- Center: Mode Switcher -->
|
|
12497
|
-
<div class="toolbar-center">
|
|
12665
|
+
<div class="rpt-toolbar-center">
|
|
12498
12666
|
<div class="mode-pill-group" role="tablist" aria-label="Workbench View Mode">
|
|
12499
12667
|
<button
|
|
12500
12668
|
type="button"
|
|
@@ -12530,7 +12698,7 @@ class DesignerToolbarComponent {
|
|
|
12530
12698
|
</div>
|
|
12531
12699
|
|
|
12532
12700
|
<!-- Right: Undo, Redo, Blueprints, Grid, Export, Save -->
|
|
12533
|
-
<div class="toolbar-right">
|
|
12701
|
+
<div class="rpt-toolbar-right">
|
|
12534
12702
|
<div class="history-group">
|
|
12535
12703
|
<button
|
|
12536
12704
|
type="button"
|
|
@@ -12657,14 +12825,14 @@ class DesignerToolbarComponent {
|
|
|
12657
12825
|
</div>
|
|
12658
12826
|
}
|
|
12659
12827
|
</div>
|
|
12660
|
-
`, isInline: true, styles: [":host{display:block;background:var(--rpt-color-surface, #ffffff);color:var(--rpt-color-text, #0f172a);height:56px;font-family:var(--rpt-font-family, inherit);border-bottom:1px solid var(--rpt-color-border, #cbd5e1);box-sizing:border-box;position:relative;box-shadow:0 1px 3px #0000000a;z-index:30;flex-shrink:0}.toolbar-root{display:flex;align-items:center;justify-content:flex-start;height:100%;padding:0 16px;gap:8px;overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-webkit-overflow-scrolling:touch;box-sizing:border-box}.toolbar-root::-webkit-scrollbar{display:none}.toolbar-left{display:flex;align-items:center;gap:8px;min-width:0;flex-shrink:0}.toolbar-right{display:flex;align-items:center;gap:6px;min-width:0;flex-shrink:0;margin-left:auto;justify-content:flex-end}.toolbar-center{display:flex;align-items:center;justify-content:center;flex-shrink:0;margin:0 auto}.report-identity{display:flex;align-items:center;gap:8px;min-width:0}.report-name{font-weight:600;font-size:.9rem;letter-spacing:-.01em;color:var(--rpt-color-text, #0f172a);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:220px;cursor:pointer;padding:2px 6px;border-radius:4px;border:1px dashed transparent;transition:all .12s ease}.report-name:hover{background:#f1f5f9;border-color:#cbd5e1}.report-name-input{font-weight:600;font-size:.9rem;color:var(--rpt-color-text, #0f172a);border:1px solid var(--rpt-color-primary, #2563eb);border-radius:4px;padding:2px 6px;outline:none;box-shadow:0 0 0 2px #2563eb33;background:#fff;max-width:220px;box-sizing:border-box}.btn-new-report{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;padding:0;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:4px;background:#f8fafc;color:var(--rpt-color-text-muted, #64748b);cursor:pointer;transition:all .12s ease}.btn-new-report:hover{background:#e2e8f0;color:var(--rpt-color-primary, #2563eb);border-color:var(--rpt-color-primary, #2563eb)}.report-switcher-select{height:26px;padding:0 6px;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:4px;background:#f8fafc;color:var(--rpt-color-text, #334155);font-size:.75rem;font-weight:500;cursor:pointer;max-width:140px;outline:none;transition:border-color .12s ease}.report-switcher-select:hover{border-color:#94a3b8}.dirty-badge{font-size:.65rem;font-weight:600;color:#b45309;background:#fef3c7;border:1px solid #fde68a;padding:1px 6px;border-radius:9999px;white-space:nowrap;flex-shrink:0}.mode-pill-group{display:flex;background:#f1f5f9;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-lg, 8px);padding:3px;gap:2px;flex-shrink:0}.mode-btn{display:inline-flex;align-items:center;gap:6px;padding:5px 12px;font-size:var(--rpt-font-size-sm, .75rem);font-weight:500;color:var(--rpt-color-text-muted, #64748b);background:transparent;border:none;border-radius:var(--rpt-border-radius, 6px);cursor:pointer;transition:all var(--rpt-transition-speed, .15s) ease;white-space:nowrap}.mode-btn:hover{color:var(--rpt-color-text, #0f172a);background:#0000000a}.mode-btn.active{background:#fff;color:var(--rpt-color-primary, #2563eb);font-weight:600;box-shadow:0 1px 3px #00000014}.history-group{display:flex;gap:2px;flex-shrink:0}.tool-btn{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;font-size:var(--rpt-font-size-sm, .75rem);font-weight:500;color:var(--rpt-color-text-secondary, #334155);background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);cursor:pointer;transition:all var(--rpt-transition-speed, .15s) ease;white-space:nowrap;flex-shrink:0}.tool-btn:hover:not(:disabled){background:var(--rpt-color-surface-hover, #f8fafc);color:var(--rpt-color-text, #0f172a);border-color:var(--rpt-color-border-hover, #94a3b8)}.tool-btn:disabled{opacity:.45;cursor:not-allowed}.btn-panel-toggle{padding:6px 8px;color:var(--rpt-color-text-muted, #64748b)}.btn-panel-toggle:hover:not(:disabled){color:var(--rpt-color-text, #0f172a);background:var(--rpt-color-surface-hover, #f8fafc)}.btn-panel-toggle.active{background:#eff6ff;border-color:#bfdbfe;color:var(--rpt-color-primary, #2563eb)}.actions-divider{width:1px;height:20px;background:var(--rpt-color-border, #cbd5e1);flex-shrink:0}.btn-blueprints{background:#faf5ff;border-color:#e9d5ff;color:#7c3aed}.btn-blueprints:hover:not(:disabled){background:#f3e8ff;color:#6d28d9;border-color:#c084fc}.btn-save{background:var(--rpt-color-primary, #2563eb);border-color:var(--rpt-color-primary, #2563eb);color:#fff;font-weight:600}.btn-save:hover:not(:disabled){background:var(--rpt-color-primary-hover, #1d4ed8);border-color:var(--rpt-color-primary-hover, #1d4ed8)}.btn-export{background:#fff;color:var(--rpt-color-text-secondary, #334155)}.btn-grid-action{background:#f0fdf4;border-color:#bbf7d0;color:#16a34a}.btn-grid-action:hover:not(:disabled){background:#dcfce7;color:#15803d;border-color:#86efac}.btn-grid-configured{background:#eff6ff;border-color:#bfdbfe;color:#2563eb}.btn-grid-configured:hover:not(:disabled){background:#dbeafe;color:#1d4ed8;border-color:#93c5fd}@media (max-width: 1024px){.toolbar-root{padding:0 12px;gap:6px}.report-name{max-width:140px}.btn-blueprints .btn-label,.btn-export .btn-label{display:none}.tool-btn{padding:6px 9px}}@media (max-width: 900px){:host{height:48px}.toolbar-root{padding:0 8px;gap:4px}.btn-panel-toggle{display:none!important}.report-name{max-width:100px;font-size:.8125rem}.actions-divider{display:none}.tool-btn .btn-label{display:none!important}.tool-btn{padding:5px 7px;min-width:30px;height:30px;justify-content:center}.mode-pill-group{padding:2px;gap:1px}.mode-btn{padding:4px 8px;font-size:.725rem;gap:4px}}@media (max-width: 680px){:host{height:46px}.toolbar-root{padding:0 6px;gap:3px}.report-name{max-width:75px;font-size:.75rem}.dirty-badge{font-size:.6rem;padding:1px 4px}.mode-btn .mode-label{display:none}.mode-btn{padding:5px 8px}.tool-btn{min-width:28px;height:28px;padding:4px 6px}.toolbar-right{gap:3px}}.blueprint-backdrop{position:fixed;inset:0;background:#0f172a73;backdrop-filter:blur(4px);z-index:2000;animation:rpt-fade-in .15s ease-out}.blueprint-modal{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:min(840px,92vw);max-height:85vh;background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-xl, 12px);box-shadow:var(--rpt-box-shadow-xl, 0 20px 25px -5px rgba(0, 0, 0, .15));z-index:2001;display:flex;flex-direction:column;overflow:hidden;animation:rpt-modal-in .18s cubic-bezier(.16,1,.3,1)}@keyframes rpt-fade-in{0%{opacity:0}to{opacity:1}}@keyframes rpt-modal-in{0%{transform:translate(-50%,-46%) scale(.96);opacity:0}to{transform:translate(-50%,-50%) scale(1);opacity:1}}.blueprint-modal-header{display:flex;align-items:flex-start;justify-content:space-between;padding:20px 24px;border-bottom:1px solid var(--rpt-color-border, #cbd5e1);background:var(--rpt-color-surface, #f8fafc)}.blueprint-modal-title{font-size:var(--rpt-font-size-lg, 1.125rem);font-weight:700;color:var(--rpt-color-text, #0f172a);margin:0 0 4px}.blueprint-modal-sub{font-size:var(--rpt-font-size-sm, .75rem);color:var(--rpt-color-text-muted, #64748b);margin:0}.btn-close-modal{background:transparent;border:none;color:var(--rpt-color-text-muted, #64748b);font-size:1.25rem;cursor:pointer;padding:4px;line-height:1;border-radius:4px;transition:all .12s ease}.btn-close-modal:hover{color:var(--rpt-color-text, #0f172a);background:#e2e8f0}.blueprint-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:16px;padding:20px 24px;overflow-y:auto}.blueprint-card{background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-lg, 8px);padding:16px;cursor:pointer;display:flex;flex-direction:column;transition:all .15s ease}.blueprint-card:hover{border-color:var(--rpt-color-primary, #2563eb);transform:translateY(-2px);box-shadow:0 8px 24px #2563eb1f}.blueprint-card-top{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.blueprint-cat-badge{font-size:.65rem;font-weight:600;color:#7c3aed;background:#f5f3ff;border:1px solid #ddd6fe;padding:2px 6px;border-radius:4px}.blueprint-card-icon{color:var(--rpt-color-primary, #2563eb)}.blueprint-card-title{font-size:var(--rpt-font-size-base, .875rem);font-weight:700;color:var(--rpt-color-text, #0f172a);margin:0 0 6px}.blueprint-card-desc{font-size:var(--rpt-font-size-sm, .75rem);color:var(--rpt-color-text-muted, #64748b);line-height:1.4;margin:0 0 16px;flex:1}.blueprint-card-footer{margin-top:auto}.btn-apply-blueprint{width:100%;background:var(--rpt-color-primary, #2563eb);color:#fff;border:none;border-radius:var(--rpt-border-radius, 6px);padding:6px 12px;font-size:.75rem;font-weight:600;cursor:pointer;transition:background .12s ease}.blueprint-card:hover .btn-apply-blueprint{background:var(--rpt-color-primary-hover, #1d4ed8)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "component", type: RptIconComponent, selector: "rpt-icon", inputs: ["name", "size", "color", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
12828
|
+
`, isInline: true, styles: [":host{display:block;background:var(--rpt-color-surface, #ffffff);color:var(--rpt-color-text, #0f172a);height:56px;font-family:var(--rpt-font-family, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Arial, sans-serif);border-bottom:1px solid var(--rpt-color-border, #cbd5e1);box-sizing:border-box;position:relative;box-shadow:0 1px 3px #0000000a;z-index:30;flex-shrink:0}:host .rpt-toolbar-root>:is(.rpt-toolbar-left,.rpt-toolbar-center,.rpt-toolbar-right) button{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;align-self:center;width:auto;min-width:0;max-width:none;height:var(--rpt-toolbar-control-height, 32px);min-height:0;max-height:none;margin:0;font-family:var(--rpt-font-family, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Arial, sans-serif);font-size:var(--rpt-font-size-sm, 12px);font-style:normal;font-weight:500;line-height:1.25;letter-spacing:normal;text-transform:none;white-space:nowrap}:host .rpt-toolbar-root>:is(.rpt-toolbar-left,.rpt-toolbar-center,.rpt-toolbar-right) button.btn-new-report{width:26px;height:26px;padding:0}:host .rpt-toolbar-root>:is(.rpt-toolbar-left,.rpt-toolbar-center,.rpt-toolbar-right) button.mode-btn{height:calc(var(--rpt-toolbar-control-height, 32px) - 4px)}:host .rpt-toolbar-root>:is(.rpt-toolbar-left,.rpt-toolbar-center,.rpt-toolbar-right) button.btn-save,:host .rpt-toolbar-root>:is(.rpt-toolbar-left,.rpt-toolbar-center,.rpt-toolbar-right) button.mode-btn.active{font-weight:600}:host .rpt-toolbar-root input,:host .rpt-toolbar-root select{flex:0 0 auto;font-family:inherit;font-style:normal;line-height:1.25;text-transform:none;letter-spacing:normal}.rpt-toolbar-root{display:flex;align-items:center;justify-content:flex-start;height:100%;padding:0 16px;gap:8px;overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-webkit-overflow-scrolling:touch;box-sizing:border-box}.rpt-toolbar-root::-webkit-scrollbar{display:none}.rpt-toolbar-left{display:flex;align-items:center;gap:8px;min-width:0;flex-shrink:0}.rpt-toolbar-right{display:flex;align-items:center;gap:6px;min-width:0;flex-shrink:0;margin-left:auto;justify-content:flex-end}.rpt-toolbar-center{display:flex;align-items:center;justify-content:center;flex-shrink:0;margin:0 auto}.report-identity{display:flex;align-items:center;gap:8px;min-width:0}.report-name{font-weight:600;font-size:14.4px;letter-spacing:-.01em;color:var(--rpt-color-text, #0f172a);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:220px;cursor:pointer;padding:2px 6px;border-radius:4px;border:1px dashed transparent;transition:all .12s ease}.report-name:hover{background:#f1f5f9;border-color:#cbd5e1}.report-name-input{font-weight:600;font-size:14.4px;color:var(--rpt-color-text, #0f172a);border:1px solid var(--rpt-color-primary, #2563eb);border-radius:4px;padding:2px 6px;outline:none;box-shadow:0 0 0 2px #2563eb33;background:#fff;max-width:220px;box-sizing:border-box}.btn-new-report{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;padding:0;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:4px;background:#f8fafc;color:var(--rpt-color-text-muted, #64748b);cursor:pointer;transition:all .12s ease}.btn-new-report:hover{background:#e2e8f0;color:var(--rpt-color-primary, #2563eb);border-color:var(--rpt-color-primary, #2563eb)}.report-switcher-select{height:26px;padding:0 6px;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:4px;background:#f8fafc;color:var(--rpt-color-text, #334155);font-size:12px;font-weight:500;cursor:pointer;max-width:140px;outline:none;transition:border-color .12s ease}.report-switcher-select:hover{border-color:#94a3b8}.dirty-badge{font-size:10.4px;font-weight:600;color:#b45309;background:#fef3c7;border:1px solid #fde68a;padding:1px 6px;border-radius:9999px;white-space:nowrap;flex-shrink:0}.mode-pill-group{display:flex;background:#f1f5f9;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-lg, 8px);padding:3px;gap:2px;flex-shrink:0}.mode-btn{display:inline-flex;align-items:center;gap:6px;padding:5px 12px;font-size:var(--rpt-font-size-sm, 12px);font-weight:500;color:var(--rpt-color-text-muted, #64748b);background:transparent;border:none;border-radius:var(--rpt-border-radius, 6px);cursor:pointer;transition:all var(--rpt-transition-speed, .15s) ease;white-space:nowrap}.mode-btn:hover{color:var(--rpt-color-text, #0f172a);background:#0000000a}.mode-btn.active{background:#fff;color:var(--rpt-color-primary, #2563eb);font-weight:600;box-shadow:0 1px 3px #00000014}.history-group{display:flex;gap:2px;flex-shrink:0}.tool-btn{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;font-size:var(--rpt-font-size-sm, 12px);font-weight:500;color:var(--rpt-color-text-secondary, #334155);background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);cursor:pointer;transition:all var(--rpt-transition-speed, .15s) ease;white-space:nowrap;flex-shrink:0}.tool-btn:hover:not(:disabled){background:var(--rpt-color-surface-hover, #f8fafc);color:var(--rpt-color-text, #0f172a);border-color:var(--rpt-color-border-hover, #94a3b8)}.tool-btn:disabled{opacity:.45;cursor:not-allowed}.btn-panel-toggle{padding:6px 8px;color:var(--rpt-color-text-muted, #64748b)}.btn-panel-toggle:hover:not(:disabled){color:var(--rpt-color-text, #0f172a);background:var(--rpt-color-surface-hover, #f8fafc)}.btn-panel-toggle.active{background:#eff6ff;border-color:#bfdbfe;color:var(--rpt-color-primary, #2563eb)}.actions-divider{width:1px;height:20px;background:var(--rpt-color-border, #cbd5e1);flex-shrink:0}.btn-blueprints{background:#faf5ff;border-color:#e9d5ff;color:#7c3aed}.btn-blueprints:hover:not(:disabled){background:#f3e8ff;color:#6d28d9;border-color:#c084fc}.btn-save{background:var(--rpt-color-primary, #2563eb);border-color:var(--rpt-color-primary, #2563eb);color:#fff;font-weight:600}.btn-save:hover:not(:disabled){background:var(--rpt-color-primary-hover, #1d4ed8);border-color:var(--rpt-color-primary-hover, #1d4ed8)}.btn-export{background:#fff;color:var(--rpt-color-text-secondary, #334155)}.btn-grid-action{background:#f0fdf4;border-color:#bbf7d0;color:#16a34a}.btn-grid-action:hover:not(:disabled){background:#dcfce7;color:#15803d;border-color:#86efac}.btn-grid-configured{background:#eff6ff;border-color:#bfdbfe;color:#2563eb}.btn-grid-configured:hover:not(:disabled){background:#dbeafe;color:#1d4ed8;border-color:#93c5fd}@media (max-width: 1024px){.rpt-toolbar-root{padding:0 12px;gap:6px}.report-name{max-width:140px}.btn-blueprints .btn-label,.btn-export .btn-label{display:none}.tool-btn{padding:6px 9px}}@media (max-width: 900px){:host{--rpt-toolbar-control-height: 30px}:host{height:48px}.rpt-toolbar-root{padding:0 8px;gap:4px}.btn-panel-toggle{display:none!important}.report-name{max-width:100px;font-size:13px}.actions-divider{display:none}.tool-btn .btn-label{display:none!important}.tool-btn{padding:5px 7px;min-width:30px;height:30px;justify-content:center}.mode-pill-group{padding:2px;gap:1px}.mode-btn{padding:4px 8px;font-size:11.6px;gap:4px}}@media (max-width: 680px){:host{--rpt-toolbar-control-height: 28px}:host{height:46px}.rpt-toolbar-root{padding:0 6px;gap:3px}.report-name{max-width:75px;font-size:12px}.dirty-badge{font-size:9.6px;padding:1px 4px}.mode-btn .mode-label{display:none}.mode-btn{padding:5px 8px}.tool-btn{min-width:28px;height:28px;padding:4px 6px}.rpt-toolbar-right{gap:3px}}.blueprint-backdrop{position:fixed;inset:0;background:#0f172a73;backdrop-filter:blur(4px);z-index:2000;animation:rpt-fade-in .15s ease-out}.blueprint-modal{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:min(840px,92vw);max-height:85vh;background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-xl, 12px);box-shadow:var(--rpt-box-shadow-xl, 0 20px 25px -5px rgba(0, 0, 0, .15));z-index:2001;display:flex;flex-direction:column;overflow:hidden;animation:rpt-modal-in .18s cubic-bezier(.16,1,.3,1)}@keyframes rpt-fade-in{0%{opacity:0}to{opacity:1}}@keyframes rpt-modal-in{0%{transform:translate(-50%,-46%) scale(.96);opacity:0}to{transform:translate(-50%,-50%) scale(1);opacity:1}}.blueprint-modal-header{display:flex;align-items:flex-start;justify-content:space-between;padding:20px 24px;border-bottom:1px solid var(--rpt-color-border, #cbd5e1);background:var(--rpt-color-surface, #f8fafc)}.blueprint-modal-title{font-size:var(--rpt-font-size-lg, 18px);font-weight:700;color:var(--rpt-color-text, #0f172a);margin:0 0 4px}.blueprint-modal-sub{font-size:var(--rpt-font-size-sm, 12px);color:var(--rpt-color-text-muted, #64748b);margin:0}.btn-close-modal{background:transparent;border:none;color:var(--rpt-color-text-muted, #64748b);font-size:20px;cursor:pointer;padding:4px;line-height:1;border-radius:4px;transition:all .12s ease}.btn-close-modal:hover{color:var(--rpt-color-text, #0f172a);background:#e2e8f0}.blueprint-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:16px;padding:20px 24px;overflow-y:auto}.blueprint-card{background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-lg, 8px);padding:16px;cursor:pointer;display:flex;flex-direction:column;transition:all .15s ease}.blueprint-card:hover{border-color:var(--rpt-color-primary, #2563eb);transform:translateY(-2px);box-shadow:0 8px 24px #2563eb1f}.blueprint-card-top{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.blueprint-cat-badge{font-size:10.4px;font-weight:600;color:#7c3aed;background:#f5f3ff;border:1px solid #ddd6fe;padding:2px 6px;border-radius:4px}.blueprint-card-icon{color:var(--rpt-color-primary, #2563eb)}.blueprint-card-title{font-size:var(--rpt-font-size-base, 14px);font-weight:700;color:var(--rpt-color-text, #0f172a);margin:0 0 6px}.blueprint-card-desc{font-size:var(--rpt-font-size-sm, 12px);color:var(--rpt-color-text-muted, #64748b);line-height:1.4;margin:0 0 16px;flex:1}.blueprint-card-footer{margin-top:auto}.btn-apply-blueprint{width:100%;background:var(--rpt-color-primary, #2563eb);color:#fff;border:none;border-radius:var(--rpt-border-radius, 6px);padding:6px 12px;font-size:12px;font-weight:600;cursor:pointer;transition:background .12s ease}.blueprint-card:hover .btn-apply-blueprint{background:var(--rpt-color-primary-hover, #1d4ed8)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "component", type: RptIconComponent, selector: "rpt-icon", inputs: ["name", "size", "color", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
12661
12829
|
}
|
|
12662
12830
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DesignerToolbarComponent, decorators: [{
|
|
12663
12831
|
type: Component,
|
|
12664
12832
|
args: [{ selector: 'rpt-designer-toolbar', standalone: true, imports: [CommonModule, FormsModule, RptIconComponent], template: `
|
|
12665
|
-
<div class="toolbar-root">
|
|
12833
|
+
<div class="rpt-toolbar-root">
|
|
12666
12834
|
<!-- Left: Panel toggle, Title, Inline Rename, + New Report, Switcher & Dirty indicator -->
|
|
12667
|
-
<div class="toolbar-left">
|
|
12835
|
+
<div class="rpt-toolbar-left">
|
|
12668
12836
|
@if (viewMode() === 'design') {
|
|
12669
12837
|
<button
|
|
12670
12838
|
type="button"
|
|
@@ -12739,7 +12907,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
12739
12907
|
</div>
|
|
12740
12908
|
|
|
12741
12909
|
<!-- Center: Mode Switcher -->
|
|
12742
|
-
<div class="toolbar-center">
|
|
12910
|
+
<div class="rpt-toolbar-center">
|
|
12743
12911
|
<div class="mode-pill-group" role="tablist" aria-label="Workbench View Mode">
|
|
12744
12912
|
<button
|
|
12745
12913
|
type="button"
|
|
@@ -12775,7 +12943,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
12775
12943
|
</div>
|
|
12776
12944
|
|
|
12777
12945
|
<!-- Right: Undo, Redo, Blueprints, Grid, Export, Save -->
|
|
12778
|
-
<div class="toolbar-right">
|
|
12946
|
+
<div class="rpt-toolbar-right">
|
|
12779
12947
|
<div class="history-group">
|
|
12780
12948
|
<button
|
|
12781
12949
|
type="button"
|
|
@@ -12902,7 +13070,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
12902
13070
|
</div>
|
|
12903
13071
|
}
|
|
12904
13072
|
</div>
|
|
12905
|
-
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;background:var(--rpt-color-surface, #ffffff);color:var(--rpt-color-text, #0f172a);height:56px;font-family:var(--rpt-font-family, inherit);border-bottom:1px solid var(--rpt-color-border, #cbd5e1);box-sizing:border-box;position:relative;box-shadow:0 1px 3px #0000000a;z-index:30;flex-shrink:0}.toolbar-root{display:flex;align-items:center;justify-content:flex-start;height:100%;padding:0 16px;gap:8px;overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-webkit-overflow-scrolling:touch;box-sizing:border-box}.toolbar-root::-webkit-scrollbar{display:none}.toolbar-left{display:flex;align-items:center;gap:8px;min-width:0;flex-shrink:0}.toolbar-right{display:flex;align-items:center;gap:6px;min-width:0;flex-shrink:0;margin-left:auto;justify-content:flex-end}.toolbar-center{display:flex;align-items:center;justify-content:center;flex-shrink:0;margin:0 auto}.report-identity{display:flex;align-items:center;gap:8px;min-width:0}.report-name{font-weight:600;font-size:.9rem;letter-spacing:-.01em;color:var(--rpt-color-text, #0f172a);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:220px;cursor:pointer;padding:2px 6px;border-radius:4px;border:1px dashed transparent;transition:all .12s ease}.report-name:hover{background:#f1f5f9;border-color:#cbd5e1}.report-name-input{font-weight:600;font-size:.9rem;color:var(--rpt-color-text, #0f172a);border:1px solid var(--rpt-color-primary, #2563eb);border-radius:4px;padding:2px 6px;outline:none;box-shadow:0 0 0 2px #2563eb33;background:#fff;max-width:220px;box-sizing:border-box}.btn-new-report{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;padding:0;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:4px;background:#f8fafc;color:var(--rpt-color-text-muted, #64748b);cursor:pointer;transition:all .12s ease}.btn-new-report:hover{background:#e2e8f0;color:var(--rpt-color-primary, #2563eb);border-color:var(--rpt-color-primary, #2563eb)}.report-switcher-select{height:26px;padding:0 6px;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:4px;background:#f8fafc;color:var(--rpt-color-text, #334155);font-size:.75rem;font-weight:500;cursor:pointer;max-width:140px;outline:none;transition:border-color .12s ease}.report-switcher-select:hover{border-color:#94a3b8}.dirty-badge{font-size:.65rem;font-weight:600;color:#b45309;background:#fef3c7;border:1px solid #fde68a;padding:1px 6px;border-radius:9999px;white-space:nowrap;flex-shrink:0}.mode-pill-group{display:flex;background:#f1f5f9;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-lg, 8px);padding:3px;gap:2px;flex-shrink:0}.mode-btn{display:inline-flex;align-items:center;gap:6px;padding:5px 12px;font-size:var(--rpt-font-size-sm, .75rem);font-weight:500;color:var(--rpt-color-text-muted, #64748b);background:transparent;border:none;border-radius:var(--rpt-border-radius, 6px);cursor:pointer;transition:all var(--rpt-transition-speed, .15s) ease;white-space:nowrap}.mode-btn:hover{color:var(--rpt-color-text, #0f172a);background:#0000000a}.mode-btn.active{background:#fff;color:var(--rpt-color-primary, #2563eb);font-weight:600;box-shadow:0 1px 3px #00000014}.history-group{display:flex;gap:2px;flex-shrink:0}.tool-btn{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;font-size:var(--rpt-font-size-sm, .75rem);font-weight:500;color:var(--rpt-color-text-secondary, #334155);background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);cursor:pointer;transition:all var(--rpt-transition-speed, .15s) ease;white-space:nowrap;flex-shrink:0}.tool-btn:hover:not(:disabled){background:var(--rpt-color-surface-hover, #f8fafc);color:var(--rpt-color-text, #0f172a);border-color:var(--rpt-color-border-hover, #94a3b8)}.tool-btn:disabled{opacity:.45;cursor:not-allowed}.btn-panel-toggle{padding:6px 8px;color:var(--rpt-color-text-muted, #64748b)}.btn-panel-toggle:hover:not(:disabled){color:var(--rpt-color-text, #0f172a);background:var(--rpt-color-surface-hover, #f8fafc)}.btn-panel-toggle.active{background:#eff6ff;border-color:#bfdbfe;color:var(--rpt-color-primary, #2563eb)}.actions-divider{width:1px;height:20px;background:var(--rpt-color-border, #cbd5e1);flex-shrink:0}.btn-blueprints{background:#faf5ff;border-color:#e9d5ff;color:#7c3aed}.btn-blueprints:hover:not(:disabled){background:#f3e8ff;color:#6d28d9;border-color:#c084fc}.btn-save{background:var(--rpt-color-primary, #2563eb);border-color:var(--rpt-color-primary, #2563eb);color:#fff;font-weight:600}.btn-save:hover:not(:disabled){background:var(--rpt-color-primary-hover, #1d4ed8);border-color:var(--rpt-color-primary-hover, #1d4ed8)}.btn-export{background:#fff;color:var(--rpt-color-text-secondary, #334155)}.btn-grid-action{background:#f0fdf4;border-color:#bbf7d0;color:#16a34a}.btn-grid-action:hover:not(:disabled){background:#dcfce7;color:#15803d;border-color:#86efac}.btn-grid-configured{background:#eff6ff;border-color:#bfdbfe;color:#2563eb}.btn-grid-configured:hover:not(:disabled){background:#dbeafe;color:#1d4ed8;border-color:#93c5fd}@media (max-width: 1024px){.toolbar-root{padding:0 12px;gap:6px}.report-name{max-width:140px}.btn-blueprints .btn-label,.btn-export .btn-label{display:none}.tool-btn{padding:6px 9px}}@media (max-width: 900px){:host{height:48px}.toolbar-root{padding:0 8px;gap:4px}.btn-panel-toggle{display:none!important}.report-name{max-width:100px;font-size:.8125rem}.actions-divider{display:none}.tool-btn .btn-label{display:none!important}.tool-btn{padding:5px 7px;min-width:30px;height:30px;justify-content:center}.mode-pill-group{padding:2px;gap:1px}.mode-btn{padding:4px 8px;font-size:.725rem;gap:4px}}@media (max-width: 680px){:host{height:46px}.toolbar-root{padding:0 6px;gap:3px}.report-name{max-width:75px;font-size:.75rem}.dirty-badge{font-size:.6rem;padding:1px 4px}.mode-btn .mode-label{display:none}.mode-btn{padding:5px 8px}.tool-btn{min-width:28px;height:28px;padding:4px 6px}.toolbar-right{gap:3px}}.blueprint-backdrop{position:fixed;inset:0;background:#0f172a73;backdrop-filter:blur(4px);z-index:2000;animation:rpt-fade-in .15s ease-out}.blueprint-modal{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:min(840px,92vw);max-height:85vh;background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-xl, 12px);box-shadow:var(--rpt-box-shadow-xl, 0 20px 25px -5px rgba(0, 0, 0, .15));z-index:2001;display:flex;flex-direction:column;overflow:hidden;animation:rpt-modal-in .18s cubic-bezier(.16,1,.3,1)}@keyframes rpt-fade-in{0%{opacity:0}to{opacity:1}}@keyframes rpt-modal-in{0%{transform:translate(-50%,-46%) scale(.96);opacity:0}to{transform:translate(-50%,-50%) scale(1);opacity:1}}.blueprint-modal-header{display:flex;align-items:flex-start;justify-content:space-between;padding:20px 24px;border-bottom:1px solid var(--rpt-color-border, #cbd5e1);background:var(--rpt-color-surface, #f8fafc)}.blueprint-modal-title{font-size:var(--rpt-font-size-lg, 1.125rem);font-weight:700;color:var(--rpt-color-text, #0f172a);margin:0 0 4px}.blueprint-modal-sub{font-size:var(--rpt-font-size-sm, .75rem);color:var(--rpt-color-text-muted, #64748b);margin:0}.btn-close-modal{background:transparent;border:none;color:var(--rpt-color-text-muted, #64748b);font-size:1.25rem;cursor:pointer;padding:4px;line-height:1;border-radius:4px;transition:all .12s ease}.btn-close-modal:hover{color:var(--rpt-color-text, #0f172a);background:#e2e8f0}.blueprint-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:16px;padding:20px 24px;overflow-y:auto}.blueprint-card{background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-lg, 8px);padding:16px;cursor:pointer;display:flex;flex-direction:column;transition:all .15s ease}.blueprint-card:hover{border-color:var(--rpt-color-primary, #2563eb);transform:translateY(-2px);box-shadow:0 8px 24px #2563eb1f}.blueprint-card-top{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.blueprint-cat-badge{font-size:.65rem;font-weight:600;color:#7c3aed;background:#f5f3ff;border:1px solid #ddd6fe;padding:2px 6px;border-radius:4px}.blueprint-card-icon{color:var(--rpt-color-primary, #2563eb)}.blueprint-card-title{font-size:var(--rpt-font-size-base, .875rem);font-weight:700;color:var(--rpt-color-text, #0f172a);margin:0 0 6px}.blueprint-card-desc{font-size:var(--rpt-font-size-sm, .75rem);color:var(--rpt-color-text-muted, #64748b);line-height:1.4;margin:0 0 16px;flex:1}.blueprint-card-footer{margin-top:auto}.btn-apply-blueprint{width:100%;background:var(--rpt-color-primary, #2563eb);color:#fff;border:none;border-radius:var(--rpt-border-radius, 6px);padding:6px 12px;font-size:.75rem;font-weight:600;cursor:pointer;transition:background .12s ease}.blueprint-card:hover .btn-apply-blueprint{background:var(--rpt-color-primary-hover, #1d4ed8)}\n"] }]
|
|
13073
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block;background:var(--rpt-color-surface, #ffffff);color:var(--rpt-color-text, #0f172a);height:56px;font-family:var(--rpt-font-family, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Arial, sans-serif);border-bottom:1px solid var(--rpt-color-border, #cbd5e1);box-sizing:border-box;position:relative;box-shadow:0 1px 3px #0000000a;z-index:30;flex-shrink:0}:host .rpt-toolbar-root>:is(.rpt-toolbar-left,.rpt-toolbar-center,.rpt-toolbar-right) button{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;align-self:center;width:auto;min-width:0;max-width:none;height:var(--rpt-toolbar-control-height, 32px);min-height:0;max-height:none;margin:0;font-family:var(--rpt-font-family, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Arial, sans-serif);font-size:var(--rpt-font-size-sm, 12px);font-style:normal;font-weight:500;line-height:1.25;letter-spacing:normal;text-transform:none;white-space:nowrap}:host .rpt-toolbar-root>:is(.rpt-toolbar-left,.rpt-toolbar-center,.rpt-toolbar-right) button.btn-new-report{width:26px;height:26px;padding:0}:host .rpt-toolbar-root>:is(.rpt-toolbar-left,.rpt-toolbar-center,.rpt-toolbar-right) button.mode-btn{height:calc(var(--rpt-toolbar-control-height, 32px) - 4px)}:host .rpt-toolbar-root>:is(.rpt-toolbar-left,.rpt-toolbar-center,.rpt-toolbar-right) button.btn-save,:host .rpt-toolbar-root>:is(.rpt-toolbar-left,.rpt-toolbar-center,.rpt-toolbar-right) button.mode-btn.active{font-weight:600}:host .rpt-toolbar-root input,:host .rpt-toolbar-root select{flex:0 0 auto;font-family:inherit;font-style:normal;line-height:1.25;text-transform:none;letter-spacing:normal}.rpt-toolbar-root{display:flex;align-items:center;justify-content:flex-start;height:100%;padding:0 16px;gap:8px;overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-webkit-overflow-scrolling:touch;box-sizing:border-box}.rpt-toolbar-root::-webkit-scrollbar{display:none}.rpt-toolbar-left{display:flex;align-items:center;gap:8px;min-width:0;flex-shrink:0}.rpt-toolbar-right{display:flex;align-items:center;gap:6px;min-width:0;flex-shrink:0;margin-left:auto;justify-content:flex-end}.rpt-toolbar-center{display:flex;align-items:center;justify-content:center;flex-shrink:0;margin:0 auto}.report-identity{display:flex;align-items:center;gap:8px;min-width:0}.report-name{font-weight:600;font-size:14.4px;letter-spacing:-.01em;color:var(--rpt-color-text, #0f172a);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:220px;cursor:pointer;padding:2px 6px;border-radius:4px;border:1px dashed transparent;transition:all .12s ease}.report-name:hover{background:#f1f5f9;border-color:#cbd5e1}.report-name-input{font-weight:600;font-size:14.4px;color:var(--rpt-color-text, #0f172a);border:1px solid var(--rpt-color-primary, #2563eb);border-radius:4px;padding:2px 6px;outline:none;box-shadow:0 0 0 2px #2563eb33;background:#fff;max-width:220px;box-sizing:border-box}.btn-new-report{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;padding:0;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:4px;background:#f8fafc;color:var(--rpt-color-text-muted, #64748b);cursor:pointer;transition:all .12s ease}.btn-new-report:hover{background:#e2e8f0;color:var(--rpt-color-primary, #2563eb);border-color:var(--rpt-color-primary, #2563eb)}.report-switcher-select{height:26px;padding:0 6px;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:4px;background:#f8fafc;color:var(--rpt-color-text, #334155);font-size:12px;font-weight:500;cursor:pointer;max-width:140px;outline:none;transition:border-color .12s ease}.report-switcher-select:hover{border-color:#94a3b8}.dirty-badge{font-size:10.4px;font-weight:600;color:#b45309;background:#fef3c7;border:1px solid #fde68a;padding:1px 6px;border-radius:9999px;white-space:nowrap;flex-shrink:0}.mode-pill-group{display:flex;background:#f1f5f9;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-lg, 8px);padding:3px;gap:2px;flex-shrink:0}.mode-btn{display:inline-flex;align-items:center;gap:6px;padding:5px 12px;font-size:var(--rpt-font-size-sm, 12px);font-weight:500;color:var(--rpt-color-text-muted, #64748b);background:transparent;border:none;border-radius:var(--rpt-border-radius, 6px);cursor:pointer;transition:all var(--rpt-transition-speed, .15s) ease;white-space:nowrap}.mode-btn:hover{color:var(--rpt-color-text, #0f172a);background:#0000000a}.mode-btn.active{background:#fff;color:var(--rpt-color-primary, #2563eb);font-weight:600;box-shadow:0 1px 3px #00000014}.history-group{display:flex;gap:2px;flex-shrink:0}.tool-btn{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;font-size:var(--rpt-font-size-sm, 12px);font-weight:500;color:var(--rpt-color-text-secondary, #334155);background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);cursor:pointer;transition:all var(--rpt-transition-speed, .15s) ease;white-space:nowrap;flex-shrink:0}.tool-btn:hover:not(:disabled){background:var(--rpt-color-surface-hover, #f8fafc);color:var(--rpt-color-text, #0f172a);border-color:var(--rpt-color-border-hover, #94a3b8)}.tool-btn:disabled{opacity:.45;cursor:not-allowed}.btn-panel-toggle{padding:6px 8px;color:var(--rpt-color-text-muted, #64748b)}.btn-panel-toggle:hover:not(:disabled){color:var(--rpt-color-text, #0f172a);background:var(--rpt-color-surface-hover, #f8fafc)}.btn-panel-toggle.active{background:#eff6ff;border-color:#bfdbfe;color:var(--rpt-color-primary, #2563eb)}.actions-divider{width:1px;height:20px;background:var(--rpt-color-border, #cbd5e1);flex-shrink:0}.btn-blueprints{background:#faf5ff;border-color:#e9d5ff;color:#7c3aed}.btn-blueprints:hover:not(:disabled){background:#f3e8ff;color:#6d28d9;border-color:#c084fc}.btn-save{background:var(--rpt-color-primary, #2563eb);border-color:var(--rpt-color-primary, #2563eb);color:#fff;font-weight:600}.btn-save:hover:not(:disabled){background:var(--rpt-color-primary-hover, #1d4ed8);border-color:var(--rpt-color-primary-hover, #1d4ed8)}.btn-export{background:#fff;color:var(--rpt-color-text-secondary, #334155)}.btn-grid-action{background:#f0fdf4;border-color:#bbf7d0;color:#16a34a}.btn-grid-action:hover:not(:disabled){background:#dcfce7;color:#15803d;border-color:#86efac}.btn-grid-configured{background:#eff6ff;border-color:#bfdbfe;color:#2563eb}.btn-grid-configured:hover:not(:disabled){background:#dbeafe;color:#1d4ed8;border-color:#93c5fd}@media (max-width: 1024px){.rpt-toolbar-root{padding:0 12px;gap:6px}.report-name{max-width:140px}.btn-blueprints .btn-label,.btn-export .btn-label{display:none}.tool-btn{padding:6px 9px}}@media (max-width: 900px){:host{--rpt-toolbar-control-height: 30px}:host{height:48px}.rpt-toolbar-root{padding:0 8px;gap:4px}.btn-panel-toggle{display:none!important}.report-name{max-width:100px;font-size:13px}.actions-divider{display:none}.tool-btn .btn-label{display:none!important}.tool-btn{padding:5px 7px;min-width:30px;height:30px;justify-content:center}.mode-pill-group{padding:2px;gap:1px}.mode-btn{padding:4px 8px;font-size:11.6px;gap:4px}}@media (max-width: 680px){:host{--rpt-toolbar-control-height: 28px}:host{height:46px}.rpt-toolbar-root{padding:0 6px;gap:3px}.report-name{max-width:75px;font-size:12px}.dirty-badge{font-size:9.6px;padding:1px 4px}.mode-btn .mode-label{display:none}.mode-btn{padding:5px 8px}.tool-btn{min-width:28px;height:28px;padding:4px 6px}.rpt-toolbar-right{gap:3px}}.blueprint-backdrop{position:fixed;inset:0;background:#0f172a73;backdrop-filter:blur(4px);z-index:2000;animation:rpt-fade-in .15s ease-out}.blueprint-modal{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:min(840px,92vw);max-height:85vh;background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-xl, 12px);box-shadow:var(--rpt-box-shadow-xl, 0 20px 25px -5px rgba(0, 0, 0, .15));z-index:2001;display:flex;flex-direction:column;overflow:hidden;animation:rpt-modal-in .18s cubic-bezier(.16,1,.3,1)}@keyframes rpt-fade-in{0%{opacity:0}to{opacity:1}}@keyframes rpt-modal-in{0%{transform:translate(-50%,-46%) scale(.96);opacity:0}to{transform:translate(-50%,-50%) scale(1);opacity:1}}.blueprint-modal-header{display:flex;align-items:flex-start;justify-content:space-between;padding:20px 24px;border-bottom:1px solid var(--rpt-color-border, #cbd5e1);background:var(--rpt-color-surface, #f8fafc)}.blueprint-modal-title{font-size:var(--rpt-font-size-lg, 18px);font-weight:700;color:var(--rpt-color-text, #0f172a);margin:0 0 4px}.blueprint-modal-sub{font-size:var(--rpt-font-size-sm, 12px);color:var(--rpt-color-text-muted, #64748b);margin:0}.btn-close-modal{background:transparent;border:none;color:var(--rpt-color-text-muted, #64748b);font-size:20px;cursor:pointer;padding:4px;line-height:1;border-radius:4px;transition:all .12s ease}.btn-close-modal:hover{color:var(--rpt-color-text, #0f172a);background:#e2e8f0}.blueprint-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:16px;padding:20px 24px;overflow-y:auto}.blueprint-card{background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-lg, 8px);padding:16px;cursor:pointer;display:flex;flex-direction:column;transition:all .15s ease}.blueprint-card:hover{border-color:var(--rpt-color-primary, #2563eb);transform:translateY(-2px);box-shadow:0 8px 24px #2563eb1f}.blueprint-card-top{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.blueprint-cat-badge{font-size:10.4px;font-weight:600;color:#7c3aed;background:#f5f3ff;border:1px solid #ddd6fe;padding:2px 6px;border-radius:4px}.blueprint-card-icon{color:var(--rpt-color-primary, #2563eb)}.blueprint-card-title{font-size:var(--rpt-font-size-base, 14px);font-weight:700;color:var(--rpt-color-text, #0f172a);margin:0 0 6px}.blueprint-card-desc{font-size:var(--rpt-font-size-sm, 12px);color:var(--rpt-color-text-muted, #64748b);line-height:1.4;margin:0 0 16px;flex:1}.blueprint-card-footer{margin-top:auto}.btn-apply-blueprint{width:100%;background:var(--rpt-color-primary, #2563eb);color:#fff;border:none;border-radius:var(--rpt-border-radius, 6px);padding:6px 12px;font-size:12px;font-weight:600;cursor:pointer;transition:background .12s ease}.blueprint-card:hover .btn-apply-blueprint{background:var(--rpt-color-primary-hover, #1d4ed8)}\n"] }]
|
|
12906
13074
|
}], ctorParameters: () => [] });
|
|
12907
13075
|
|
|
12908
13076
|
class ComponentPaletteComponent {
|
|
@@ -13672,7 +13840,7 @@ class DesignCanvasComponent {
|
|
|
13672
13840
|
getCanvasControl(comp) {
|
|
13673
13841
|
if (!comp.key)
|
|
13674
13842
|
return null;
|
|
13675
|
-
return this.canvasForm().
|
|
13843
|
+
return this.canvasForm().controls[comp.key] ?? null;
|
|
13676
13844
|
}
|
|
13677
13845
|
highlightedFieldKeys = signal(new Set());
|
|
13678
13846
|
layoutJustUpdated = signal(false);
|
|
@@ -14099,12 +14267,13 @@ class DesignCanvasComponent {
|
|
|
14099
14267
|
this.designerState.addComponent(type);
|
|
14100
14268
|
}
|
|
14101
14269
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DesignCanvasComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14102
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: DesignCanvasComponent, isStandalone: true, selector: "rpt-design-canvas", host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: " <div class=\"canvas-wrapper\" (click)=\"onCanvasBackgroundClick($event)\">\n <!-- Canvas Status / Quick Controls -->\n <div class=\"canvas-toolbar\">\n <div class=\"canvas-info\">\n <span class=\"info-label\">Grid Layout:</span>\n <span class=\"info-value\">{{ totalColumns() }} Columns</span>\n <span class=\"info-divider\">\u2022</span>\n <span class=\"info-label\">Components:</span>\n <span class=\"info-value\">{{ components().length }}</span>\n <span class=\"info-divider\">\u2022</span>\n <span class=\"live-mode-badge\" title=\"Interactive canvas: Values typed here run live queries and test dropdowns.\">\n \u26A1 Live Interactive Mode\n </span>\n </div>\n\n <div class=\"canvas-actions\">\n @if (!definition().grid) {\n <button\n type=\"button\"\n class=\"canvas-action-btn btn-grid-add\"\n (click)=\"addResultGrid($event)\"\n title=\"Add a Result Grid to this report\"\n >\n <rpt-icon name=\"table\" [size]=\"14\" />\n <span>+ Add Grid</span>\n </button>\n } @else {\n <button\n type=\"button\"\n class=\"canvas-action-btn btn-grid-configured\"\n (click)=\"selectGridSettings($event)\"\n title=\"Configure Result Data Grid\"\n >\n <rpt-icon name=\"table\" [size]=\"14\" />\n <span>Grid ({{ definition().grid?.columns?.length ?? 0 }})</span>\n </button>\n }\n\n <button\n type=\"button\"\n class=\"canvas-action-btn\"\n (click)=\"selectReportSettings($event)\"\n title=\"Configure Page Layout & Grid Settings\"\n >\n <rpt-icon name=\"settings\" [size]=\"14\" />\n <span>Report Settings</span>\n </button>\n </div>\n </div>\n\n <!-- Canvas Drop Area -->\n <div class=\"canvas-scroll-area\">\n <div\n class=\"canvas-artboard\"\n [style.padding.px]=\"definition().layout.padding ?? 24\"\n >\n <!-- Report Header Preview -->\n <div class=\"canvas-report-header\">\n <h2 class=\"canvas-report-title\">{{ definition().name || 'Untitled Report' }}</h2>\n @if (definition().description) {\n <p class=\"canvas-report-desc\">{{ definition().description }}</p>\n }\n </div>\n\n <!-- Component Drop List Grid with 2D mixed orientation and data binding -->\n <div\n cdkDropList\n id=\"designer-canvas-drop-list\"\n class=\"canvas-grid\"\n cdkDropListOrientation=\"mixed\"\n [cdkDropListData]=\"sortedComponents()\"\n [style.grid-template-columns]=\"gridTemplateColumns()\"\n [style.gap.px]=\"definition().layout.gap ?? 16\"\n (cdkDropListDropped)=\"onDrop($event)\"\n >\n @for (comp of sortedComponents(); track comp.id; let idx = $index) {\n <div\n cdkDrag\n [cdkDragData]=\"comp\"\n [cdkDragDisabled]=\"isAnyResizing()\"\n class=\"canvas-component-card\"\n [class.is-selected]=\"isSelected(comp.id)\"\n [class.is-resizing]=\"resizingCompId() === comp.id\"\n [class.is-field-highlighted]=\"isFieldHighlighted(comp)\"\n [class.is-narrow]=\"getSpan(comp) <= 4\"\n [class.is-compact]=\"getSpan(comp) <= 2\"\n [style.grid-column]=\"getGridColumn(comp)\"\n [style.grid-row]=\"getGridRow(comp)\"\n [style.width]=\"comp.layout?.width || 'auto'\"\n [style.height]=\"getComponentHeight(comp)\"\n [style.min-height]=\"getComponentMinHeight(comp)\"\n (click)=\"onSelect(comp.id, $event)\"\n >\n <!-- Card Header with Drag Handle & Actions -->\n <div class=\"card-header\">\n <div class=\"card-title-group\" cdkDragHandle title=\"Drag to reorder\">\n <span class=\"drag-handle-dots\">\u22EE\u22EE</span>\n <rpt-icon [name]=\"getComponentIconName(comp.type)\" [size]=\"14\" />\n <span class=\"comp-title\" [title]=\"comp.label || comp.key || comp.type\">\n {{ comp.label || comp.key || comp.type }}\n </span>\n </div>\n\n <div class=\"card-actions\">\n <!-- Static span pill & new row / start col pill when not hovered / not selected -->\n @if (comp.layout?.column; as col) {\n <span class=\"new-row-pill-static\" title=\"Starts at Column {{ col }}\">Col {{ col }}</span>\n } @else if (comp.layout?.newRow) {\n <span class=\"new-row-pill-static\" title=\"Starts on separate row\">\u21B3 Row</span>\n }\n <span\n class=\"span-pill-static\"\n title=\"Column span: {{ getSpan(comp) }} of {{ totalColumns() }} columns\"\n >\n {{ getSpan(comp) }}/{{ totalColumns() }}\n </span>\n\n <!-- Quick Action Stepper & Buttons (revealed on hover or selection) -->\n <div class=\"card-quick-controls\">\n <button\n type=\"button\"\n class=\"card-action-btn\"\n [class.btn-active-toggle]=\"comp.layout?.newRow\"\n (click)=\"toggleNewRow(comp, $event)\"\n title=\"{{ comp.layout?.newRow ? 'Starts on new row (Click to place inline)' : 'Start on separate row (Row Break)' }}\"\n >\n <rpt-icon name=\"corner-down-right\" [size]=\"11\" />\n </button>\n\n <div class=\"span-control-group\" title=\"Adjust Column Span\">\n <button\n type=\"button\"\n class=\"span-btn\"\n [disabled]=\"getSpan(comp) <= 1\"\n (click)=\"adjustSpan(comp, -1, $event)\"\n title=\"Shrink Column Span\"\n >\n <rpt-icon name=\"minus\" [size]=\"9\" />\n </button>\n <span class=\"span-badge\" [title]=\"'Column span: ' + getSpan(comp) + ' of ' + totalColumns()\">\n <span class=\"span-num\">{{ getSpan(comp) }}</span><span class=\"span-total\">/{{ totalColumns() }}</span>\n </span>\n <button\n type=\"button\"\n class=\"span-btn\"\n [disabled]=\"getSpan(comp) >= totalColumns()\"\n (click)=\"adjustSpan(comp, 1, $event)\"\n title=\"Expand Column Span\"\n >\n <rpt-icon name=\"plus\" [size]=\"9\" />\n </button>\n </div>\n\n <button\n type=\"button\"\n class=\"card-action-btn\"\n (click)=\"onDuplicate(comp.id, $event)\"\n title=\"Duplicate component\"\n >\n <rpt-icon name=\"copy\" [size]=\"12\" />\n </button>\n <button\n type=\"button\"\n class=\"card-action-btn btn-delete\"\n (click)=\"onDelete(comp.id, $event)\"\n title=\"Remove component\"\n >\n <rpt-icon name=\"trash\" [size]=\"12\" />\n </button>\n </div>\n </div>\n </div>\n\n <!-- Card Body Visual Preview -->\n <div class=\"card-body\">\n\n @switch (comp.type) {\n @case ('input') {\n <div class=\"mock-input-field\" (click)=\"onControlInteract(comp, $event)\">\n <span class=\"mock-label\">{{ comp.label || 'Input' }}</span>\n @if (getCanvasControl(comp); as ctrl) {\n <input\n type=\"text\"\n class=\"canvas-active-input\"\n [placeholder]=\"comp.properties?.['placeholder'] || 'Enter text...'\"\n [formControl]=\"ctrl\"\n (focus)=\"onControlInteract(comp, $event)\"\n />\n } @else {\n <div class=\"mock-input-box\">{{ comp.properties?.['placeholder'] || 'Enter text...' }}</div>\n }\n </div>\n }\n @case ('dropdown') {\n <div class=\"canvas-live-control-wrapper\" (click)=\"onControlInteract(comp, $event)\">\n <rpt-dropdown\n [config]=\"comp\"\n [showStatus]=\"true\"\n [parentForm]=\"canvasForm()\"\n [control]=\"getCanvasControl(comp)\"\n (dropdownClick)=\"onControlInteract(comp)\"\n />\n </div>\n }\n @case ('date') {\n <div class=\"mock-input-field\" (click)=\"onControlInteract(comp, $event)\">\n <span class=\"mock-label\">{{ comp.label || 'Date' }}</span>\n @if (getCanvasControl(comp); as ctrl) {\n <input\n type=\"date\"\n class=\"canvas-active-input\"\n [formControl]=\"ctrl\"\n (focus)=\"onControlInteract(comp, $event)\"\n />\n } @else {\n <div class=\"mock-select-box\">\n <span>YYYY-MM-DD</span>\n <rpt-icon name=\"calendar\" [size]=\"12\" />\n </div>\n }\n </div>\n }\n @case ('date-range') {\n <div class=\"canvas-live-control-wrapper\" (click)=\"onControlInteract(comp, $event)\">\n <rpt-date-range\n [config]=\"comp\"\n [control]=\"getCanvasControl(comp)\"\n />\n </div>\n }\n @case ('number') {\n <div class=\"mock-input-field\" (click)=\"onControlInteract(comp, $event)\">\n <span class=\"mock-label\">{{ comp.label || 'Number' }}</span>\n @if (getCanvasControl(comp); as ctrl) {\n <input\n type=\"number\"\n class=\"canvas-active-input\"\n [formControl]=\"ctrl\"\n (focus)=\"onControlInteract(comp, $event)\"\n />\n } @else {\n <div class=\"mock-input-box\">\n {{ comp.properties?.['prefix'] || '' }} 0.00 {{ comp.properties?.['suffix'] || '' }}\n </div>\n }\n </div>\n }\n @case ('checkbox') {\n <div class=\"mock-checkbox-field\" (click)=\"onControlInteract(comp, $event)\">\n @if (getCanvasControl(comp); as ctrl) {\n <label class=\"canvas-active-checkbox-lbl\">\n <input type=\"checkbox\" [formControl]=\"ctrl\" (focus)=\"onControlInteract(comp, $event)\" />\n <span class=\"mock-label\">{{ comp.label || 'Checkbox' }}</span>\n </label>\n } @else {\n <span class=\"mock-check-box\">\n <rpt-icon name=\"check\" [size]=\"10\" />\n </span>\n <span class=\"mock-label\">{{ comp.label || 'Checkbox' }}</span>\n }\n </div>\n }\n @case ('radio') {\n <div class=\"canvas-live-control-wrapper\" (click)=\"onControlInteract(comp, $event)\">\n <rpt-radio\n [config]=\"comp\"\n [control]=\"getCanvasControl(comp)\"\n [parentForm]=\"canvasForm()\"\n />\n </div>\n }\n @case ('button') {\n <div class=\"mock-btn-wrapper\">\n <button\n type=\"button\"\n class=\"mock-btn\"\n [class]=\"'btn-' + (comp.properties?.['variant'] || 'primary')\"\n [class.mock-btn-full]=\"comp.properties?.['fullWidth'] === true || comp.layout?.width === '100%'\"\n [style.height]=\"getCustomBtnHeight(comp)\"\n [style.width]=\"getCustomBtnWidth(comp)\"\n (click)=\"onCanvasButtonClick(comp, $event)\"\n >\n @if (getCustomBtnIcon(comp); as btnIcon) {\n <rpt-icon [name]=\"btnIcon\" [size]=\"15\" />\n }\n <span>{{ comp.label || 'Button' }}</span>\n </button>\n </div>\n }\n @case ('kpi-card') {\n <div class=\"mock-kpi-field\">\n <div class=\"mock-kpi-header\">\n <span class=\"mock-label\">{{ comp.label || 'KPI Metric' }}</span>\n <rpt-icon name=\"kpi\" [size]=\"14\" />\n </div>\n <div class=\"mock-kpi-value\">{{ comp.properties?.['value'] || '$128,450' }}</div>\n <div class=\"mock-kpi-footer\">\n <span class=\"mock-kpi-trend\">+12.5%</span>\n <span class=\"mock-kpi-sub\">vs last period</span>\n </div>\n </div>\n }\n @case ('chart') {\n <div class=\"mock-chart-field\">\n <div class=\"mock-chart-header\">\n <span class=\"mock-label\">{{ comp.label || 'Quarterly Sales Revenue ($000)' }}</span>\n </div>\n <div class=\"mock-chart-bars\">\n <div class=\"mock-chart-col\">\n <div class=\"mock-chart-bar\" style=\"height: 44%\"></div>\n <span class=\"mock-bar-lbl\">Q1 Jan-Mar</span>\n </div>\n <div class=\"mock-chart-col\">\n <div class=\"mock-chart-bar\" style=\"height: 61%\"></div>\n <span class=\"mock-bar-lbl\">Q2 Apr-Jun</span>\n </div>\n <div class=\"mock-chart-col\">\n <div class=\"mock-chart-bar\" style=\"height: 79%\"></div>\n <span class=\"mock-bar-lbl\">Q3 Jul-Sep</span>\n </div>\n <div class=\"mock-chart-col\">\n <div class=\"mock-chart-bar\" style=\"height: 100%\"></div>\n <span class=\"mock-bar-lbl\">Q4 Oct-Dec</span>\n </div>\n </div>\n </div>\n }\n @default {\n <div class=\"mock-generic-box\">\n <span>{{ comp.type }}</span>\n </div>\n }\n }\n </div>\n\n <!-- Resizing Handles (East, South, South-East) -->\n <div\n class=\"resize-handle handle-e\"\n (pointerdown)=\"startResize($event, comp, 'e')\"\n (dblclick)=\"resetDimensions(comp, $event)\"\n title=\"Drag to resize column width \u2022 Double-click to reset\"\n ></div>\n <div\n class=\"resize-handle handle-s\"\n (pointerdown)=\"startResize($event, comp, 's')\"\n (dblclick)=\"resetDimensions(comp, $event)\"\n title=\"Drag to resize height \u2022 Double-click to reset\"\n ></div>\n <div\n class=\"resize-handle handle-se\"\n (pointerdown)=\"startResize($event, comp, 'se')\"\n (dblclick)=\"resetDimensions(comp, $event)\"\n title=\"Drag to resize width and height \u2022 Double-click to reset\"\n ></div>\n\n <!-- Resizing Dimension Ghost / Badge -->\n @if (resizingCompId() === comp.id) {\n <div class=\"resize-dimension-tooltip\">\n {{ resizingTooltip(comp) }}\n </div>\n }\n\n <!-- Drag placeholder with matched grid column span -->\n <div\n *cdkDragPlaceholder\n class=\"canvas-drag-placeholder\"\n [style.grid-column]=\"getGridColumn(comp)\"\n ></div>\n\n <!-- Drag preview -->\n <div *cdkDragPreview class=\"canvas-drag-preview\">\n <span class=\"drag-handle-dots\">\u22EE\u22EE</span>\n <rpt-icon [name]=\"getComponentIconName(comp.type)\" [size]=\"14\" />\n <span class=\"preview-title\">{{ comp.label || comp.key || comp.type }}</span>\n <span class=\"preview-span\">{{ getSpan(comp) }}/{{ totalColumns() }}</span>\n </div>\n </div>\n }\n\n @if (components().length === 0) {\n <div class=\"canvas-empty-state\" role=\"region\" aria-label=\"Empty canvas starter options\">\n <div class=\"empty-hero-badge\">\n <rpt-icon name=\"design\" [size]=\"28\" />\n </div>\n <h3 class=\"empty-hero-title\">Start Designing Your Custom Report</h3>\n <p class=\"empty-hero-desc\">\n Drag controls from the left component palette, or jumpstart your design with pre-configured enterprise blueprints.\n </p>\n\n <div class=\"empty-hero-actions\">\n <button\n type=\"button\"\n class=\"empty-btn empty-btn-primary\"\n (click)=\"applyBlueprint('blueprint-audit-register')\"\n title=\"Load complete Audit Register blueprint with parameters and result grid\"\n >\n <rpt-icon name=\"table\" [size]=\"15\" />\n <span>Audit Register Blueprint</span>\n </button>\n <button\n type=\"button\"\n class=\"empty-btn empty-btn-secondary\"\n (click)=\"applyBlueprint('blueprint-kpi-summary')\"\n title=\"Load Executive KPI summary blueprint\"\n >\n <rpt-icon name=\"chart-bar\" [size]=\"15\" />\n <span>KPI Summary Blueprint</span>\n </button>\n <button\n type=\"button\"\n class=\"empty-btn empty-btn-outline\"\n (click)=\"addStarterComponent('date-range')\"\n title=\"Add a Date Range filter component\"\n >\n <rpt-icon name=\"calendar\" [size]=\"14\" />\n <span>+ Add Date Filter</span>\n </button>\n <button\n type=\"button\"\n class=\"empty-btn empty-btn-outline\"\n (click)=\"addStarterComponent('button')\"\n title=\"Add an Execute Action button\"\n >\n <rpt-icon name=\"play\" [size]=\"14\" />\n <span>+ Add Run Button</span>\n </button>\n </div>\n </div>\n }\n </div>\n\n <!-- Live Result Grid Preview with Bways-Grid Features -->\n @if (definition().grid) {\n <div class=\"canvas-grid-box\">\n <div class=\"grid-preview-header\">\n <div style=\"display: flex; align-items: center; gap: 8px;\">\n <rpt-icon name=\"table\" [size]=\"15\" />\n <span class=\"grid-title\">Bways Grid ({{ definition().grid?.columns?.length ?? 0 }} columns)</span>\n @if (isExecutingGrid()) {\n <span class=\"grid-exec-badge is-loading\">Loading data from database...</span>\n } @else if (gridExecError()) {\n <span class=\"grid-exec-badge is-error\">{{ gridExecError() }}</span>\n } @else if (canvasGridData().length > 0) {\n <span class=\"grid-exec-badge is-success\">{{ canvasGridData().length }} rows loaded</span>\n }\n </div>\n <div style=\"display: flex; align-items: center; gap: 8px;\">\n <button\n type=\"button\"\n class=\"btn-canvas-run-grid\"\n (click)=\"executeCanvasReport($event)\"\n [disabled]=\"isExecutingGrid()\"\n title=\"Execute query and load data into Bways Grid\"\n >\n <rpt-icon name=\"play\" [size]=\"11\" />\n <span>{{ isExecutingGrid() ? 'Running...' : 'Run / Refresh Grid' }}</span>\n </button>\n <button\n type=\"button\"\n class=\"btn-canvas-edit-grid\"\n (click)=\"selectGridSettings($event)\"\n title=\"Configure columns and grid options\"\n >\n <rpt-icon name=\"settings\" [size]=\"11\" />\n <span>Configure</span>\n </button>\n <button\n type=\"button\"\n class=\"btn-remove-grid\"\n (click)=\"removeGrid($event)\"\n title=\"Remove Result Grid from this report\"\n >\n <rpt-icon name=\"trash\" [size]=\"13\" />\n </button>\n </div>\n </div>\n\n @if (getGridSummaryText(); as summary) {\n <div class=\"grid-layout-status-strip\">\n <div class=\"grid-status-info\">\n <rpt-icon name=\"table\" [size]=\"12\" />\n <span>{{ summary }}</span>\n </div>\n <div class=\"grid-status-sync\" [class.is-pulsing]=\"layoutJustUpdated()\">\n <rpt-icon name=\"check\" [size]=\"11\" />\n <span>Layout auto-persisted</span>\n </div>\n </div>\n }\n\n <!-- Live Bways Grid bridge -->\n <div class=\"canvas-live-grid-container\" (click)=\"$event.stopPropagation()\" style=\"position: relative; min-height: 250px;\">\n @if (isExecutingGrid()) {\n <div class=\"canvas-grid-loading-overlay\">\n <span class=\"status-dot dot-pulse\" style=\"width: 14px; height: 14px; color: #2563eb; background: #2563eb; border-radius: 50%;\"></span>\n <span class=\"grid-loading-title\">Executing stored procedure on SQL Server...</span>\n <span class=\"grid-loading-sub\">Calculating outstanding balances and ledger amounts. Please wait...</span>\n </div>\n }\n <rpt-grid-bridge\n [gridDefinition]=\"definition().grid!\"\n [data]=\"canvasGridData()\"\n (gridStateChange)=\"onCanvasGridStateChange($event)\"\n (columnDefsChange)=\"onCanvasColumnDefsChange($event)\"\n />\n </div>\n </div>\n } @else {\n <div class=\"canvas-add-grid-banner\" (click)=\"addResultGrid($event)\">\n <div class=\"add-grid-icon\">\n <rpt-icon name=\"table\" [size]=\"20\" />\n </div>\n <div class=\"add-grid-text\">\n <span class=\"add-grid-title\">+ Add Result Data Grid</span>\n <span class=\"add-grid-sub\">Attach an interactive data table or UltraGrid to display report execution output</span>\n </div>\n <button type=\"button\" class=\"btn-banner-add\">Add Grid</button>\n </div>\n }\n </div>\n </div>\n </div>", styles: [":host{display:block;flex:1;min-height:0;min-width:0;height:100%;background-color:#f1f5f9;font-family:var(--rpt-font-family, inherit);box-sizing:border-box}.canvas-wrapper{min-height:0;display:flex;flex-direction:column;height:100%}.canvas-toolbar{display:flex;align-items:center;justify-content:space-between;min-height:42px;height:42px;padding:0 24px;background:var(--rpt-color-surface, #ffffff);border-bottom:1px solid var(--rpt-color-border, #cbd5e1);box-sizing:border-box;gap:12px;overflow-x:auto;scrollbar-width:none;font-family:var(--rpt-font-family, inherit);flex-shrink:0}.canvas-toolbar::-webkit-scrollbar{display:none}.canvas-info{display:flex;align-items:center;gap:8px;font-family:var(--rpt-font-family, inherit);font-size:var(--rpt-font-size-sm, .75rem);color:var(--rpt-color-text-muted, #64748b);white-space:nowrap;line-height:1}.info-label{color:var(--rpt-color-text-muted, #64748b);font-weight:500;font-size:var(--rpt-font-size-sm, .75rem)}.info-value{font-weight:600;color:var(--rpt-color-text, #0f172a);font-size:var(--rpt-font-size-sm, .75rem)}.info-divider{color:var(--rpt-color-border, #cbd5e1);margin:0 2px}.live-mode-badge{display:inline-flex;align-items:center;gap:4px;font-family:var(--rpt-font-family, inherit);font-size:var(--rpt-font-size-xs, .7rem);font-weight:600;color:#047857;background:#ecfdf5;border:1px solid #a7f3d0;padding:2px 8px;border-radius:9999px;line-height:1}.canvas-actions{display:flex;align-items:center;gap:8px;white-space:nowrap}.canvas-action-btn{display:inline-flex;align-items:center;gap:6px;height:28px;padding:0 10px;font-family:var(--rpt-font-family, inherit);font-size:var(--rpt-font-size-sm, .75rem);font-weight:500;letter-spacing:-.01em;color:var(--rpt-color-text-secondary, #334155);background:var(--rpt-color-surface, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);cursor:pointer;line-height:1;white-space:nowrap;box-shadow:0 1px 2px #0000000a;transition:all var(--rpt-transition-speed, .15s) ease}.canvas-action-btn:hover{background:var(--rpt-color-surface-hover, #f8fafc);border-color:var(--rpt-color-border-hover, #94a3b8);color:var(--rpt-color-text, #0f172a);box-shadow:0 2px 4px #0000000f}.canvas-action-btn.btn-grid-configured{background:#eff6ff;border-color:#bfdbfe;color:#2563eb;font-weight:600}.canvas-action-btn.btn-grid-configured:hover{background:#dbeafe;color:#1d4ed8;border-color:#93c5fd}.canvas-action-btn.btn-grid-add{background:#f0fdf4;border-color:#bbf7d0;color:#16a34a;font-weight:600}.canvas-action-btn.btn-grid-add:hover{background:#dcfce7;color:#15803d;border-color:#86efac}.canvas-action-btn:active{transform:translateY(1px)}.canvas-scroll-area{flex:1;min-height:0;overflow-y:auto;padding:16px}.canvas-artboard{width:100%;max-width:1400px;margin:0 auto;background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-lg, 8px);box-shadow:0 4px 6px -1px #0000000d;min-height:480px;box-sizing:border-box}@media (max-width: 900px){.canvas-toolbar{min-height:36px;height:36px;padding:0 10px;gap:6px}.live-mode-badge{display:none}.canvas-info{font-size:.75rem;gap:5px}.canvas-action-btn{padding:0 8px;font-size:.725rem;height:28px;gap:4px}.canvas-scroll-area{padding:10px}.canvas-artboard{min-height:360px}}@media (max-width: 600px){.canvas-toolbar{padding:0 8px;gap:4px}.canvas-info .info-label,.canvas-action-btn span{display:none}.canvas-action-btn{padding:0 6px;min-width:28px;justify-content:center}}.canvas-report-header{margin-bottom:24px;padding-bottom:16px;border-bottom:1px solid var(--rpt-color-border, #e2e8f0)}.canvas-report-title{font-size:var(--rpt-font-size-xl, 1.5rem);font-weight:700;color:var(--rpt-color-text, #0f172a);margin:0 0 6px}.canvas-report-desc{font-size:var(--rpt-font-size-base, .875rem);color:var(--rpt-color-text-muted, #64748b);margin:0}.canvas-grid{display:grid;width:100%;min-height:180px;align-items:start;border-radius:6px;transition:all var(--rpt-transition-speed, .15s) ease}.canvas-component-card{container-type:inline-size;background:#fff;border:1.5px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);box-shadow:var(--rpt-box-shadow, 0 1px 2px rgba(0, 0, 0, .05));box-sizing:border-box;transition:all var(--rpt-transition-speed, .15s) ease;cursor:pointer;position:relative;overflow:visible;display:flex;flex-direction:column;min-height:104px;z-index:1}.canvas-component-card:hover{border-color:var(--rpt-color-primary, #2563eb);box-shadow:0 4px 6px -1px #2563eb1a;z-index:2}.canvas-component-card.is-selected{border-color:var(--rpt-color-primary, #2563eb);box-shadow:0 0 0 2px #2563eb40;z-index:10}.canvas-component-card:focus-within,.canvas-component-card:has(.rpt-combobox-menu),.canvas-component-card:has(.is-open){z-index:100}.canvas-component-card.is-resizing{-webkit-user-select:none;user-select:none;border-color:var(--rpt-color-primary, #2563eb);box-shadow:0 0 0 2px #2563eb59;z-index:30;transition:none!important}.resize-handle{position:absolute;z-index:20;touch-action:none;opacity:0;pointer-events:none;transition:opacity .15s ease}.canvas-component-card.is-selected .resize-handle{opacity:1;pointer-events:auto}.resize-handle.handle-e{top:0;right:0;width:6px;height:100%;cursor:ew-resize;display:flex;align-items:center;justify-content:center}.resize-handle.handle-e:after{content:\"\";width:3px;height:24px;background:#cbd5e1;border-radius:2px;transition:background .15s ease}.resize-handle.handle-e:hover:after,.canvas-component-card.is-selected .resize-handle.handle-e:after{background:var(--rpt-color-primary, #2563eb)}.resize-handle.handle-s{bottom:0;left:0;height:6px;width:100%;cursor:ns-resize;display:flex;align-items:center;justify-content:center}.resize-handle.handle-s:after{content:\"\";height:3px;width:24px;background:#cbd5e1;border-radius:2px;transition:background .15s ease}.resize-handle.handle-s:hover:after,.canvas-component-card.is-selected .resize-handle.handle-s:after{background:var(--rpt-color-primary, #2563eb)}.resize-handle.handle-se{bottom:0;right:0;width:12px;height:12px;cursor:nwse-resize;display:flex;align-items:flex-end;justify-content:flex-end;padding:2px}.resize-handle.handle-se:after{content:\"\";width:5px;height:5px;border-right:2px solid #cbd5e1;border-bottom:2px solid #cbd5e1;transition:border-color .15s ease}.resize-handle.handle-se:hover:after,.canvas-component-card.is-selected .resize-handle.handle-se:after{border-color:var(--rpt-color-primary, #2563eb)}.resize-dimension-tooltip{position:absolute;top:38px;right:8px;background:#0f172ae6;color:#fff;font-size:.7rem;font-weight:600;padding:3px 8px;border-radius:4px;white-space:nowrap;pointer-events:none;z-index:50;box-shadow:0 2px 4px #0003}.card-header{display:flex;align-items:center;justify-content:space-between;gap:6px;padding:5px 8px;background:var(--rpt-color-surface, #f8fafc);border-bottom:1px solid var(--rpt-color-border, #e2e8f0);border-top-left-radius:var(--rpt-border-radius, 6px);border-top-right-radius:var(--rpt-border-radius, 6px);min-height:32px;box-sizing:border-box;overflow:hidden;position:relative}.canvas-component-card.is-selected .card-header{overflow:visible}.card-title-group{display:flex;align-items:center;gap:5px;cursor:grab;min-width:0;flex:1;overflow:hidden}.drag-handle-dots{color:var(--rpt-color-text-muted, #94a3b8);font-size:.85rem;letter-spacing:-2px;flex-shrink:0;-webkit-user-select:none;user-select:none}.comp-title{font-size:.75rem;font-weight:600;color:var(--rpt-color-text, #0f172a);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;-webkit-user-select:none;user-select:none}.card-actions{display:flex;align-items:center;gap:4px;flex-shrink:0;margin-left:auto}.new-row-pill-static{font-size:.65rem;font-weight:600;color:#7c3aed;background:#f5f3ff;border:1px solid #ddd6fe;padding:1px 5px;border-radius:9999px;white-space:nowrap;line-height:1;flex-shrink:0}.span-pill-static{display:inline-flex;align-items:center;justify-content:center;font-size:.65rem;font-weight:600;color:var(--rpt-color-text-muted, #64748b);background:var(--rpt-color-surface, #f1f5f9);border:1px solid var(--rpt-color-border, #e2e8f0);padding:1px 6px;border-radius:9999px;line-height:1.2;white-space:nowrap;-webkit-user-select:none;user-select:none}.card-quick-controls{display:none;align-items:center;gap:3px;flex-shrink:0}.canvas-component-card:hover .span-pill-static,.canvas-component-card.is-selected .span-pill-static,.canvas-component-card:hover .new-row-pill-static,.canvas-component-card.is-selected .new-row-pill-static{display:none}.canvas-component-card:hover .card-quick-controls,.canvas-component-card.is-selected .card-quick-controls{display:inline-flex}.canvas-component-card.is-narrow .card-header{padding:4px 6px;gap:3px}.canvas-component-card.is-narrow:hover .comp-title,.canvas-component-card.is-narrow.is-selected .comp-title,.canvas-component-card.is-narrow:hover .drag-handle-dots,.canvas-component-card.is-narrow.is-selected .drag-handle-dots{display:none}.canvas-component-card.is-narrow .card-quick-controls{gap:2px}.canvas-component-card.is-narrow .span-control-group{height:18px}.canvas-component-card.is-narrow .span-btn{width:14px;height:16px}.canvas-component-card.is-narrow .span-badge{font-size:.6rem;padding:0 2px}.canvas-component-card.is-narrow .span-total{display:none}.canvas-component-card.is-narrow .card-action-btn{width:18px;height:18px}@container (max-width: 250px){.card-header{padding:4px 6px;gap:3px}.canvas-component-card:hover .comp-title,.canvas-component-card.is-selected .comp-title,.canvas-component-card:hover .drag-handle-dots,.canvas-component-card.is-selected .drag-handle-dots,.span-total{display:none}.card-quick-controls{gap:2px}.card-action-btn{width:18px;height:18px}.span-control-group{height:18px}.span-btn{width:14px;height:16px}.span-badge{font-size:.6rem;padding:0 2px}}@container (max-width: 160px){.canvas-component-card.is-selected .card-quick-controls{position:absolute;top:-30px;right:0;background:#fff;border:1.5px solid var(--rpt-color-primary, #2563eb);border-radius:6px;padding:2px 4px;gap:3px;box-shadow:0 4px 12px #2563eb2e,0 2px 4px #00000014;z-index:120;white-space:nowrap}}@media (max-width: 900px){.canvas-component-card:hover .comp-title,.canvas-component-card.is-selected .comp-title,.canvas-component-card:hover .drag-handle-dots,.canvas-component-card.is-selected .drag-handle-dots,.canvas-component-card .span-total{display:none}.canvas-component-card.is-selected .card-header{overflow:visible}.canvas-component-card.is-selected .card-quick-controls{position:absolute;top:-30px;right:0;background:#fff;border:1.5px solid var(--rpt-color-primary, #2563eb);border-radius:6px;padding:2px 4px;gap:3px;box-shadow:0 4px 12px #2563eb2e,0 2px 4px #00000014;z-index:120;white-space:nowrap}}.span-control-group{display:inline-flex;align-items:center;background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:4px;height:20px;padding:0 1px;white-space:nowrap;flex-shrink:0}.span-btn{width:16px;height:18px;display:inline-flex;align-items:center;justify-content:center;border:none;background:transparent;padding:0;color:var(--rpt-color-text-muted, #64748b);cursor:pointer;border-radius:2px;transition:all .12s ease}.span-btn:hover:not(:disabled){background:var(--rpt-color-surface-hover, #f1f5f9);color:var(--rpt-color-primary, #2563eb)}.span-btn:disabled{opacity:.3;cursor:not-allowed}.span-badge{font-size:.65rem;font-weight:600;color:var(--rpt-color-text, #0f172a);padding:0 3px;white-space:nowrap;-webkit-user-select:none;user-select:none;line-height:1}.span-total{font-size:.6rem;color:var(--rpt-color-text-muted, #64748b);font-weight:500}.card-action-btn{width:20px;height:20px;border:none;background:transparent;border-radius:4px;display:inline-flex;align-items:center;justify-content:center;padding:0;cursor:pointer;color:var(--rpt-color-text-muted, #64748b);transition:all .12s ease;flex-shrink:0}.card-action-btn:hover{background:#e2e8f0;color:var(--rpt-color-text, #0f172a)}.card-action-btn.btn-active-toggle{background:#7c3aed;color:#fff;border-color:#7c3aed}.card-action-btn.btn-active-toggle:hover{background:#6d28d9;color:#fff}.btn-delete:hover{background:#fee2e2;color:#ef4444}.card-body{padding:8px 10px 14px;flex:1;display:flex;flex-direction:column;min-height:0;min-width:0;max-width:100%;box-sizing:border-box;overflow:visible}.mock-input-field{display:flex;flex-direction:column;gap:4px;flex:1;height:100%;min-height:0}.mock-label{font-size:.7rem;font-weight:600;color:var(--rpt-color-text-muted, #475569);flex-shrink:0}.mock-input-box,.mock-select-box{border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:6px;padding:6px 12px;font-size:var(--rpt-font-size-base, .875rem);color:var(--rpt-color-text-muted, #94a3b8);background:#fff;flex:1;min-height:38px;height:100%;display:flex;align-items:center;justify-content:space-between;box-sizing:border-box}.mock-checkbox-field{display:flex;align-items:center;gap:6px;flex:1;height:100%;min-height:0;padding-top:14px}.mock-check-box{width:18px;height:18px;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:.65rem;color:var(--rpt-color-primary, #2563eb);flex-shrink:0}.mock-btn-wrapper{display:flex;flex:1;height:100%;min-height:38px;align-items:center;justify-content:flex-start;width:100%;min-width:0;box-sizing:border-box}.mock-btn{border-radius:var(--rpt-border-radius, 6px);padding:0 16px;height:36px;font-size:var(--rpt-font-size-base, .875rem);font-weight:500;border:1px solid transparent;cursor:pointer;width:auto;max-width:100%;display:inline-flex;align-items:center;justify-content:center;gap:6px;box-sizing:border-box;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:all .15s ease}.mock-btn.mock-btn-full{width:100%}.canvas-component-card.is-narrow .mock-btn{padding:0 6px;font-size:.775rem;width:100%;height:34px}.mock-kpi-field{display:flex;flex-direction:column;justify-content:space-between;flex:1;height:100%;min-height:0}.mock-kpi-header{display:flex;align-items:center;justify-content:space-between}.mock-kpi-value{font-size:1.25rem;font-weight:700;color:var(--rpt-color-text, #0f172a);margin:2px 0}.mock-kpi-footer{display:flex;align-items:center;gap:6px;font-size:.65rem}.mock-kpi-trend{color:#16a34a;font-weight:600}.mock-kpi-sub{color:#64748b}.mock-chart-field{display:flex;flex-direction:column;flex:1;height:100%;min-height:140px}.mock-chart-bars{display:flex;align-items:flex-end;justify-content:space-around;gap:16px;flex:1;min-height:100px;padding-top:12px;box-sizing:border-box}.mock-chart-col{flex:1;display:flex;flex-direction:column;align-items:center;height:100%;min-width:60px;max-width:140px}.mock-chart-bar{width:38px;max-width:44px;margin:0 auto;background:var(--rpt-color-primary, #2563eb);border-radius:6px 6px 0 0;opacity:.85;min-height:10px;transition:height .2s ease}.mock-bar-lbl{font-size:.6875rem;color:#64748b;margin-top:6px;text-align:center;white-space:normal;line-height:1.2}.btn-primary{background:var(--rpt-color-primary, #2563eb);color:#fff}.btn-secondary{background:#f1f5f9;color:#0f172a;border:1px solid #cbd5e1}.btn-outline{background:transparent;color:#2563eb;border:1px solid #2563eb}.btn-danger{background:#ef4444;color:#fff}.mock-generic-box{border:1px dashed #cbd5e1;border-radius:4px;padding:8px;font-size:.75rem;text-align:center;color:#64748b}.canvas-drag-placeholder{background:#2563eb0f;border:2px dashed var(--rpt-color-primary, #2563eb);border-radius:6px;min-height:80px;box-sizing:border-box;transition:transform .15s ease}.canvas-drag-preview,::ng-deep .canvas-drag-preview{background:#fff;border:2px solid var(--rpt-color-primary, #2563eb);border-radius:6px;padding:8px 14px;display:inline-flex;align-items:center;gap:8px;font-family:var(--rpt-font-family, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif)!important;font-size:.8rem;font-weight:600;color:var(--rpt-color-text, #0f172a);box-shadow:0 12px 24px -4px #00000040;z-index:10000}.preview-title,::ng-deep .preview-title{font-family:inherit!important;max-width:160px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.preview-span,::ng-deep .preview-span{font-family:inherit!important;font-size:.65rem;font-weight:600;color:var(--rpt-color-primary, #2563eb);background:#eff6ff;border:1px solid #bfdbfe;padding:1px 6px;border-radius:9999px}.canvas-empty-state{grid-column:1 / -1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:56px 28px;border:2px dashed var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-xl, 12px);background:var(--rpt-color-surface, #ffffff);text-align:center;box-shadow:var(--rpt-box-shadow-xs, 0 1px 2px 0 rgba(0, 0, 0, .04));transition:border-color var(--rpt-transition-speed, .15s) ease}.canvas-empty-state:hover{border-color:var(--rpt-color-primary, #2563eb)}.empty-hero-badge{width:56px;height:56px;border-radius:var(--rpt-border-radius-xl, 12px);background:var(--rpt-color-primary-light, #eff6ff);color:var(--rpt-color-primary, #2563eb);display:flex;align-items:center;justify-content:center;margin-bottom:16px;box-shadow:0 2px 8px #2563eb1f}.empty-hero-title{font-size:var(--rpt-font-size-lg, 1.125rem);font-weight:700;color:var(--rpt-color-text, #0f172a);margin:0 0 8px;letter-spacing:-.01em}.empty-hero-desc{font-size:var(--rpt-font-size-base, .875rem);color:var(--rpt-color-text-muted, #64748b);max-width:520px;line-height:1.5;margin:0 0 24px}.empty-hero-actions{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:10px}.empty-btn{display:inline-flex;align-items:center;gap:7px;padding:8px 16px;font-size:var(--rpt-font-size-sm, .75rem);font-weight:600;border-radius:var(--rpt-border-radius, 6px);cursor:pointer;transition:all var(--rpt-transition-speed, .15s) ease;font-family:inherit}.empty-btn-primary{background:var(--rpt-color-primary, #2563eb);color:#fff;border:1px solid var(--rpt-color-primary, #2563eb);box-shadow:0 1px 3px #2563eb33}.empty-btn-primary:hover{background:var(--rpt-color-primary-hover, #1d4ed8);border-color:var(--rpt-color-primary-hover, #1d4ed8);transform:translateY(-1px)}.empty-btn-secondary{background:#f8fafc;color:#334155;border:1px solid var(--rpt-color-border, #cbd5e1)}.empty-btn-secondary:hover{background:#f1f5f9;color:#0f172a;border-color:#94a3b8;transform:translateY(-1px)}.empty-btn-outline{background:#fff;color:var(--rpt-color-primary, #2563eb);border:1px solid #bfdbfe}.empty-btn-outline:hover{background:#eff6ff;border-color:var(--rpt-color-primary, #2563eb);transform:translateY(-1px)}.canvas-active-input{width:100%;height:32px;padding:4px 8px;font-size:.8125rem;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:4px;outline:none;box-sizing:border-box;background:#fff;color:#0f172a;transition:border-color .15s ease}.canvas-active-input:focus{border-color:var(--rpt-color-primary, #2563eb)}.canvas-active-checkbox-lbl{display:inline-flex;align-items:center;gap:6px;cursor:pointer}.canvas-live-control-wrapper{width:100%;max-width:100%;min-width:0;position:relative;z-index:1;box-sizing:border-box}.canvas-grid-box{margin-top:24px;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:8px;background:#fff;box-shadow:0 1px 3px #0000000d;overflow:hidden}.grid-preview-header{display:flex;align-items:center;justify-content:space-between;padding:10px 16px;background:var(--rpt-color-surface, #f8fafc);border-bottom:1px solid var(--rpt-color-border, #cbd5e1);font-size:.8rem;font-weight:600;color:var(--rpt-color-text, #0f172a)}.grid-title{font-weight:700}.grid-exec-badge{font-size:.6875rem;font-weight:600;padding:2px 8px;border-radius:10px}.grid-exec-badge.is-loading{background:#eff6ff;color:#2563eb}.grid-exec-badge.is-error{background:#fee2e2;color:#ef4444}.grid-exec-badge.is-success{background:#ecfdf5;color:#059669}.btn-canvas-run-grid{display:inline-flex;align-items:center;gap:5px;padding:4px 10px;font-size:.72rem;font-weight:600;color:#fff;background:var(--rpt-color-primary, #2563eb);border:none;border-radius:4px;cursor:pointer;transition:background .15s ease}.btn-canvas-run-grid:hover:not(:disabled){background:#1d4ed8}.btn-canvas-run-grid:disabled{opacity:.6;cursor:not-allowed}.btn-canvas-edit-grid{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;font-size:.72rem;font-weight:500;color:#334155;background:#fff;border:1px solid #cbd5e1;border-radius:4px;cursor:pointer;transition:all .12s ease}.btn-canvas-edit-grid:hover{background:#f1f5f9}.canvas-live-grid-container{width:100%;min-height:480px;background:#fff}.live-mode-badge{display:inline-flex;align-items:center;gap:4px;font-size:.6875rem;font-weight:700;color:#047857;background:#ecfdf5;border:1px solid #a7f3d0;padding:1px 8px;border-radius:9999px}.canvas-component-card.is-field-highlighted{border-color:#f59e0b!important;box-shadow:0 0 0 3px #f59e0b59!important;animation:rpt-pulse-border 1s ease-in-out infinite alternate}@keyframes rpt-pulse-border{0%{box-shadow:0 0 0 2px #f59e0b40}to{box-shadow:0 0 0 5px #f59e0b80}}.grid-layout-status-strip{display:flex;align-items:center;justify-content:space-between;padding:6px 16px;background:#f1f5f9;border-bottom:1px solid var(--rpt-color-border, #cbd5e1);font-size:.6875rem;color:#475569}.grid-status-info{display:inline-flex;align-items:center;gap:6px;font-weight:600}.grid-status-sync{display:inline-flex;align-items:center;gap:4px;color:#059669;font-weight:600;transition:transform .2s ease}.grid-status-sync.is-pulsing{transform:scale(1.08);color:#047857}.canvas-actions{display:flex;align-items:center;gap:8px}.btn-grid-add{background:#f0fdf4;border-color:#bbf7d0;color:#16a34a}.btn-grid-add:hover{background:#dcfce7;border-color:#86efac;color:#15803d}.btn-remove-grid{display:inline-flex;align-items:center;justify-content:center;padding:4px;border:none;background:transparent;color:#94a3b8;border-radius:4px;cursor:pointer;transition:all .15s ease}.btn-remove-grid:hover{background:#fee2e2;color:#ef4444}.canvas-add-grid-banner{margin-top:24px;border:2px dashed #cbd5e1;border-radius:8px;background:#fff;padding:16px 20px;display:flex;align-items:center;gap:16px;cursor:pointer;transition:all .15s ease}.canvas-add-grid-banner:hover{border-color:#2563eb;background:#f8fafc;box-shadow:0 2px 8px #2563eb14}.add-grid-icon{width:40px;height:40px;border-radius:8px;background:#eff6ff;color:#2563eb;display:flex;align-items:center;justify-content:center;flex-shrink:0}.add-grid-text{display:flex;flex-direction:column;gap:2px;flex:1}.add-grid-title{font-size:.875rem;font-weight:600;color:#0f172a}.add-grid-sub{font-size:.75rem;color:#64748b}.btn-banner-add{padding:6px 14px;font-size:.75rem;font-weight:600;color:#2563eb;background:#eff6ff;border:1px solid #bfdbfe;border-radius:6px;cursor:pointer;white-space:nowrap;transition:all .15s ease}.canvas-add-grid-banner:hover .btn-banner-add{background:#2563eb;color:#fff;border-color:#2563eb}.canvas-grid-loading-overlay{position:absolute;inset:0;background:#ffffffe6;backdrop-filter:blur(2px);z-index:10;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;font-family:inherit;border-radius:6px}.grid-loading-title{font-weight:600;font-size:.95rem;color:#1e293b}.grid-loading-sub{font-size:.8rem;color:#64748b}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button,.canvas-active-input::-webkit-inner-spin-button,.canvas-active-input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}input[type=number],.canvas-active-input{-moz-appearance:textfield;appearance:textfield}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i1$2.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i1$2.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i1$2.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: i1$2.CdkDragPreview, selector: "ng-template[cdkDragPreview]", inputs: ["data", "matchSize"] }, { kind: "directive", type: i1$2.CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: RptIconComponent, selector: "rpt-icon", inputs: ["name", "size", "color", "strokeWidth"] }, { kind: "component", type: RptDropdownComponent, selector: "rpt-dropdown", inputs: ["config", "control", "parentForm", "showStatus"], outputs: ["dropdownClick"] }, { kind: "component", type: RptDateRangeComponent, selector: "rpt-date-range", inputs: ["config", "control"] }, { kind: "component", type: RptRadioComponent, selector: "rpt-radio", inputs: ["config", "control", "parentForm"] }, { kind: "component", type: ReportGridBridgeComponent, selector: "rpt-grid-bridge", inputs: ["gridDefinition", "data"], outputs: ["gridStateChange", "columnDefsChange", "rowClick", "selectionChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
14270
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: DesignCanvasComponent, isStandalone: true, selector: "rpt-design-canvas", host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: " <div class=\"canvas-wrapper\" (click)=\"onCanvasBackgroundClick($event)\">\n <!-- Canvas Status / Quick Controls -->\n <div class=\"canvas-toolbar\">\n <div class=\"canvas-info\">\n <span class=\"info-label\">Grid Layout:</span>\n <span class=\"info-value\">{{ totalColumns() }} Columns</span>\n <span class=\"info-divider\">\u2022</span>\n <span class=\"info-label\">Components:</span>\n <span class=\"info-value\">{{ components().length }}</span>\n <span class=\"info-divider\">\u2022</span>\n <span class=\"live-mode-badge\" title=\"Interactive canvas: Values typed here run live queries and test dropdowns.\">\n \u26A1 Live Interactive Mode\n </span>\n </div>\n\n <div class=\"canvas-actions\">\n @if (!definition().grid) {\n <button\n type=\"button\"\n class=\"canvas-action-btn btn-grid-add\"\n (click)=\"addResultGrid($event)\"\n title=\"Add a Result Grid to this report\"\n >\n <rpt-icon name=\"table\" [size]=\"14\" />\n <span>+ Add Grid</span>\n </button>\n } @else {\n <button\n type=\"button\"\n class=\"canvas-action-btn btn-grid-configured\"\n (click)=\"selectGridSettings($event)\"\n title=\"Configure Result Data Grid\"\n >\n <rpt-icon name=\"table\" [size]=\"14\" />\n <span>Grid ({{ definition().grid?.columns?.length ?? 0 }})</span>\n </button>\n }\n\n <button\n type=\"button\"\n class=\"canvas-action-btn\"\n (click)=\"selectReportSettings($event)\"\n title=\"Configure Page Layout & Grid Settings\"\n >\n <rpt-icon name=\"settings\" [size]=\"14\" />\n <span>Report Settings</span>\n </button>\n </div>\n </div>\n\n <!-- Canvas Drop Area -->\n <div class=\"canvas-scroll-area\" cdkScrollable>\n <div\n class=\"canvas-artboard\"\n [style.padding.px]=\"definition().layout.padding ?? 24\"\n >\n <!-- Report Header Preview -->\n <div class=\"canvas-report-header\">\n <h2 class=\"canvas-report-title\">{{ definition().name || 'Untitled Report' }}</h2>\n @if (definition().description) {\n <p class=\"canvas-report-desc\">{{ definition().description }}</p>\n }\n </div>\n\n <!-- Component Drop List Grid with 2D mixed orientation and data binding -->\n <div\n cdkDropList\n id=\"designer-canvas-drop-list\"\n class=\"canvas-grid\"\n cdkDropListOrientation=\"mixed\"\n [cdkDropListData]=\"sortedComponents()\"\n [style.grid-template-columns]=\"gridTemplateColumns()\"\n [style.gap.px]=\"definition().layout.gap ?? 16\"\n (cdkDropListDropped)=\"onDrop($event)\"\n >\n @for (comp of sortedComponents(); track comp.id; let idx = $index) {\n <div\n cdkDrag\n [cdkDragData]=\"comp\"\n [cdkDragDisabled]=\"isAnyResizing()\"\n class=\"canvas-component-card\"\n [class.is-selected]=\"isSelected(comp.id)\"\n [class.is-resizing]=\"resizingCompId() === comp.id\"\n [class.is-field-highlighted]=\"isFieldHighlighted(comp)\"\n [class.is-narrow]=\"getSpan(comp) <= 4\"\n [class.is-compact]=\"getSpan(comp) <= 2\"\n [style.grid-column]=\"getGridColumn(comp)\"\n [style.grid-row]=\"getGridRow(comp)\"\n [style.width]=\"comp.layout?.width || 'auto'\"\n [style.height]=\"getComponentHeight(comp)\"\n [style.min-height]=\"getComponentMinHeight(comp)\"\n (click)=\"onSelect(comp.id, $event)\"\n >\n <!-- Card Header with Drag Handle & Actions -->\n <div class=\"card-header\">\n <div class=\"card-title-group\" cdkDragHandle title=\"Drag to reorder\">\n <span class=\"drag-handle-dots\">\u22EE\u22EE</span>\n <rpt-icon [name]=\"getComponentIconName(comp.type)\" [size]=\"14\" />\n <span class=\"comp-title\" [title]=\"comp.label || comp.key || comp.type\">\n {{ comp.label || comp.key || comp.type }}\n </span>\n </div>\n\n <div class=\"card-actions\">\n <!-- Static span pill & new row / start col pill when not hovered / not selected -->\n @if (comp.layout?.column; as col) {\n <span class=\"new-row-pill-static\" title=\"Starts at Column {{ col }}\">Col {{ col }}</span>\n } @else if (comp.layout?.newRow) {\n <span class=\"new-row-pill-static\" title=\"Starts on separate row\">\u21B3 Row</span>\n }\n <span\n class=\"span-pill-static\"\n title=\"Column span: {{ getSpan(comp) }} of {{ totalColumns() }} columns\"\n >\n {{ getSpan(comp) }}/{{ totalColumns() }}\n </span>\n\n <!-- Quick Action Stepper & Buttons (revealed on hover or selection) -->\n <div class=\"card-quick-controls\">\n <button\n type=\"button\"\n class=\"card-action-btn\"\n [class.btn-active-toggle]=\"comp.layout?.newRow\"\n (click)=\"toggleNewRow(comp, $event)\"\n title=\"{{ comp.layout?.newRow ? 'Starts on new row (Click to place inline)' : 'Start on separate row (Row Break)' }}\"\n >\n <rpt-icon name=\"corner-down-right\" [size]=\"11\" />\n </button>\n\n <div class=\"span-control-group\" title=\"Adjust Column Span\">\n <button\n type=\"button\"\n class=\"span-btn\"\n [disabled]=\"getSpan(comp) <= 1\"\n (click)=\"adjustSpan(comp, -1, $event)\"\n title=\"Shrink Column Span\"\n >\n <rpt-icon name=\"minus\" [size]=\"9\" />\n </button>\n <span class=\"span-badge\" [title]=\"'Column span: ' + getSpan(comp) + ' of ' + totalColumns()\">\n <span class=\"span-num\">{{ getSpan(comp) }}</span><span class=\"span-total\">/{{ totalColumns() }}</span>\n </span>\n <button\n type=\"button\"\n class=\"span-btn\"\n [disabled]=\"getSpan(comp) >= totalColumns()\"\n (click)=\"adjustSpan(comp, 1, $event)\"\n title=\"Expand Column Span\"\n >\n <rpt-icon name=\"plus\" [size]=\"9\" />\n </button>\n </div>\n\n <button\n type=\"button\"\n class=\"card-action-btn\"\n (click)=\"onDuplicate(comp.id, $event)\"\n title=\"Duplicate component\"\n >\n <rpt-icon name=\"copy\" [size]=\"12\" />\n </button>\n <button\n type=\"button\"\n class=\"card-action-btn btn-delete\"\n (click)=\"onDelete(comp.id, $event)\"\n title=\"Remove component\"\n >\n <rpt-icon name=\"trash\" [size]=\"12\" />\n </button>\n </div>\n </div>\n </div>\n\n <!-- Card Body Visual Preview -->\n <div class=\"card-body\">\n\n @switch (comp.type) {\n @case ('input') {\n <div class=\"mock-input-field\" (click)=\"onControlInteract(comp, $event)\">\n <span class=\"mock-label\">{{ comp.label || 'Input' }}</span>\n @if (getCanvasControl(comp); as ctrl) {\n <input\n type=\"text\"\n class=\"canvas-active-input\"\n [placeholder]=\"comp.properties?.['placeholder'] || 'Enter text...'\"\n [formControl]=\"ctrl\"\n (focus)=\"onControlInteract(comp, $event)\"\n />\n } @else {\n <div class=\"mock-input-box\">{{ comp.properties?.['placeholder'] || 'Enter text...' }}</div>\n }\n </div>\n }\n @case ('dropdown') {\n <div class=\"canvas-live-control-wrapper\" (click)=\"onControlInteract(comp, $event)\">\n <rpt-dropdown\n [config]=\"comp\"\n [showStatus]=\"true\"\n [parentForm]=\"canvasForm()\"\n [control]=\"getCanvasControl(comp)\"\n (dropdownClick)=\"onControlInteract(comp)\"\n />\n </div>\n }\n @case ('date') {\n <div class=\"mock-input-field\" (click)=\"onControlInteract(comp, $event)\">\n <span class=\"mock-label\">{{ comp.label || 'Date' }}</span>\n @if (getCanvasControl(comp); as ctrl) {\n <input\n type=\"date\"\n class=\"canvas-active-input\"\n [formControl]=\"ctrl\"\n (focus)=\"onControlInteract(comp, $event)\"\n />\n } @else {\n <div class=\"mock-select-box\">\n <span>YYYY-MM-DD</span>\n <rpt-icon name=\"calendar\" [size]=\"12\" />\n </div>\n }\n </div>\n }\n @case ('date-range') {\n <div class=\"canvas-live-control-wrapper\" (click)=\"onControlInteract(comp, $event)\">\n <rpt-date-range\n [config]=\"comp\"\n [control]=\"getCanvasControl(comp)\"\n />\n </div>\n }\n @case ('number') {\n <div class=\"mock-input-field\" (click)=\"onControlInteract(comp, $event)\">\n <span class=\"mock-label\">{{ comp.label || 'Number' }}</span>\n @if (getCanvasControl(comp); as ctrl) {\n <input\n type=\"number\"\n class=\"canvas-active-input\"\n [formControl]=\"ctrl\"\n (focus)=\"onControlInteract(comp, $event)\"\n />\n } @else {\n <div class=\"mock-input-box\">\n {{ comp.properties?.['prefix'] || '' }} 0.00 {{ comp.properties?.['suffix'] || '' }}\n </div>\n }\n </div>\n }\n @case ('checkbox') {\n <div class=\"mock-checkbox-field\" (click)=\"onControlInteract(comp, $event)\">\n @if (getCanvasControl(comp); as ctrl) {\n <label class=\"canvas-active-checkbox-lbl\">\n <input type=\"checkbox\" [formControl]=\"ctrl\" (focus)=\"onControlInteract(comp, $event)\" />\n <span class=\"mock-label\">{{ comp.label || 'Checkbox' }}</span>\n </label>\n } @else {\n <span class=\"mock-check-box\">\n <rpt-icon name=\"check\" [size]=\"10\" />\n </span>\n <span class=\"mock-label\">{{ comp.label || 'Checkbox' }}</span>\n }\n </div>\n }\n @case ('radio') {\n <div class=\"canvas-live-control-wrapper\" (click)=\"onControlInteract(comp, $event)\">\n <rpt-radio\n [config]=\"comp\"\n [control]=\"getCanvasControl(comp)\"\n [parentForm]=\"canvasForm()\"\n />\n </div>\n }\n @case ('button') {\n <div class=\"mock-btn-wrapper\">\n <button\n type=\"button\"\n class=\"mock-btn\"\n [class]=\"'btn-' + (comp.properties?.['variant'] || 'primary')\"\n [class.mock-btn-full]=\"comp.properties?.['fullWidth'] === true || comp.layout?.width === '100%'\"\n [style.height]=\"getCustomBtnHeight(comp)\"\n [style.width]=\"getCustomBtnWidth(comp)\"\n (click)=\"onCanvasButtonClick(comp, $event)\"\n >\n @if (getCustomBtnIcon(comp); as btnIcon) {\n <rpt-icon [name]=\"btnIcon\" [size]=\"15\" />\n }\n <span>{{ comp.label || 'Button' }}</span>\n </button>\n </div>\n }\n @case ('kpi-card') {\n <div class=\"mock-kpi-field\">\n <div class=\"mock-kpi-header\">\n <span class=\"mock-label\">{{ comp.label || 'KPI Metric' }}</span>\n <rpt-icon name=\"kpi\" [size]=\"14\" />\n </div>\n <div class=\"mock-kpi-value\">{{ comp.properties?.['value'] || '$128,450' }}</div>\n <div class=\"mock-kpi-footer\">\n <span class=\"mock-kpi-trend\">+12.5%</span>\n <span class=\"mock-kpi-sub\">vs last period</span>\n </div>\n </div>\n }\n @case ('chart') {\n <div class=\"mock-chart-field\">\n <div class=\"mock-chart-header\">\n <span class=\"mock-label\">{{ comp.label || 'Quarterly Sales Revenue ($000)' }}</span>\n </div>\n <div class=\"mock-chart-bars\">\n <div class=\"mock-chart-col\">\n <div class=\"mock-chart-bar\" style=\"height: 44%\"></div>\n <span class=\"mock-bar-lbl\">Q1 Jan-Mar</span>\n </div>\n <div class=\"mock-chart-col\">\n <div class=\"mock-chart-bar\" style=\"height: 61%\"></div>\n <span class=\"mock-bar-lbl\">Q2 Apr-Jun</span>\n </div>\n <div class=\"mock-chart-col\">\n <div class=\"mock-chart-bar\" style=\"height: 79%\"></div>\n <span class=\"mock-bar-lbl\">Q3 Jul-Sep</span>\n </div>\n <div class=\"mock-chart-col\">\n <div class=\"mock-chart-bar\" style=\"height: 100%\"></div>\n <span class=\"mock-bar-lbl\">Q4 Oct-Dec</span>\n </div>\n </div>\n </div>\n }\n @default {\n <div class=\"mock-generic-box\">\n <span>{{ comp.type }}</span>\n </div>\n }\n }\n </div>\n\n <!-- Resizing Handles (East, South, South-East) -->\n <div\n class=\"resize-handle handle-e\"\n (pointerdown)=\"startResize($event, comp, 'e')\"\n (dblclick)=\"resetDimensions(comp, $event)\"\n title=\"Drag to resize column width \u2022 Double-click to reset\"\n ></div>\n <div\n class=\"resize-handle handle-s\"\n (pointerdown)=\"startResize($event, comp, 's')\"\n (dblclick)=\"resetDimensions(comp, $event)\"\n title=\"Drag to resize height \u2022 Double-click to reset\"\n ></div>\n <div\n class=\"resize-handle handle-se\"\n (pointerdown)=\"startResize($event, comp, 'se')\"\n (dblclick)=\"resetDimensions(comp, $event)\"\n title=\"Drag to resize width and height \u2022 Double-click to reset\"\n ></div>\n\n <!-- Resizing Dimension Ghost / Badge -->\n @if (resizingCompId() === comp.id) {\n <div class=\"resize-dimension-tooltip\">\n {{ resizingTooltip(comp) }}\n </div>\n }\n\n <!-- Drag placeholder with matched grid column span -->\n <div\n *cdkDragPlaceholder\n class=\"canvas-drag-placeholder\"\n [style.grid-column]=\"getGridColumn(comp)\"\n ></div>\n\n <!-- Drag preview -->\n <div *cdkDragPreview class=\"canvas-drag-preview\">\n <span class=\"drag-handle-dots\">\u22EE\u22EE</span>\n <rpt-icon [name]=\"getComponentIconName(comp.type)\" [size]=\"14\" />\n <span class=\"preview-title\">{{ comp.label || comp.key || comp.type }}</span>\n <span class=\"preview-span\">{{ getSpan(comp) }}/{{ totalColumns() }}</span>\n </div>\n </div>\n }\n\n @if (components().length === 0) {\n <div class=\"canvas-empty-state\" role=\"region\" aria-label=\"Empty canvas starter options\">\n <div class=\"empty-hero-badge\">\n <rpt-icon name=\"design\" [size]=\"28\" />\n </div>\n <h3 class=\"empty-hero-title\">Start Designing Your Custom Report</h3>\n <p class=\"empty-hero-desc\">\n Drag controls from the left component palette, or jumpstart your design with pre-configured enterprise blueprints.\n </p>\n\n <div class=\"empty-hero-actions\">\n <button\n type=\"button\"\n class=\"empty-btn empty-btn-primary\"\n (click)=\"applyBlueprint('blueprint-audit-register')\"\n title=\"Load complete Audit Register blueprint with parameters and result grid\"\n >\n <rpt-icon name=\"table\" [size]=\"15\" />\n <span>Audit Register Blueprint</span>\n </button>\n <button\n type=\"button\"\n class=\"empty-btn empty-btn-secondary\"\n (click)=\"applyBlueprint('blueprint-kpi-summary')\"\n title=\"Load Executive KPI summary blueprint\"\n >\n <rpt-icon name=\"chart-bar\" [size]=\"15\" />\n <span>KPI Summary Blueprint</span>\n </button>\n <button\n type=\"button\"\n class=\"empty-btn empty-btn-outline\"\n (click)=\"addStarterComponent('date-range')\"\n title=\"Add a Date Range filter component\"\n >\n <rpt-icon name=\"calendar\" [size]=\"14\" />\n <span>+ Add Date Filter</span>\n </button>\n <button\n type=\"button\"\n class=\"empty-btn empty-btn-outline\"\n (click)=\"addStarterComponent('button')\"\n title=\"Add an Execute Action button\"\n >\n <rpt-icon name=\"play\" [size]=\"14\" />\n <span>+ Add Run Button</span>\n </button>\n </div>\n </div>\n }\n </div>\n\n <!-- Live Result Grid Preview with Bways-Grid Features -->\n @if (definition().grid) {\n <div class=\"canvas-grid-box\">\n <div class=\"grid-preview-header\">\n <div style=\"display: flex; align-items: center; gap: 8px;\">\n <rpt-icon name=\"table\" [size]=\"15\" />\n <span class=\"grid-title\">Bways Grid ({{ definition().grid?.columns?.length ?? 0 }} columns)</span>\n @if (isExecutingGrid()) {\n <span class=\"grid-exec-badge is-loading\">Loading data from database...</span>\n } @else if (gridExecError()) {\n <span class=\"grid-exec-badge is-error\">{{ gridExecError() }}</span>\n } @else if (canvasGridData().length > 0) {\n <span class=\"grid-exec-badge is-success\">{{ canvasGridData().length }} rows loaded</span>\n }\n </div>\n <div style=\"display: flex; align-items: center; gap: 8px;\">\n <button\n type=\"button\"\n class=\"btn-canvas-run-grid\"\n (click)=\"executeCanvasReport($event)\"\n [disabled]=\"isExecutingGrid()\"\n title=\"Execute query and load data into Bways Grid\"\n >\n <rpt-icon name=\"play\" [size]=\"11\" />\n <span>{{ isExecutingGrid() ? 'Running...' : 'Run / Refresh Grid' }}</span>\n </button>\n <button\n type=\"button\"\n class=\"btn-canvas-edit-grid\"\n (click)=\"selectGridSettings($event)\"\n title=\"Configure columns and grid options\"\n >\n <rpt-icon name=\"settings\" [size]=\"11\" />\n <span>Configure</span>\n </button>\n <button\n type=\"button\"\n class=\"btn-remove-grid\"\n (click)=\"removeGrid($event)\"\n title=\"Remove Result Grid from this report\"\n >\n <rpt-icon name=\"trash\" [size]=\"13\" />\n </button>\n </div>\n </div>\n\n @if (getGridSummaryText(); as summary) {\n <div class=\"grid-layout-status-strip\">\n <div class=\"grid-status-info\">\n <rpt-icon name=\"table\" [size]=\"12\" />\n <span>{{ summary }}</span>\n </div>\n <div class=\"grid-status-sync\" [class.is-pulsing]=\"layoutJustUpdated()\">\n <rpt-icon name=\"check\" [size]=\"11\" />\n <span>Layout auto-persisted</span>\n </div>\n </div>\n }\n\n <!-- Live Bways Grid bridge -->\n <div class=\"canvas-live-grid-container\" (click)=\"$event.stopPropagation()\" style=\"position: relative; min-height: 250px;\">\n @if (isExecutingGrid()) {\n <div class=\"canvas-grid-loading-overlay\">\n <span class=\"status-dot dot-pulse\" style=\"width: 14px; height: 14px; color: #2563eb; background: #2563eb; border-radius: 50%;\"></span>\n <span class=\"grid-loading-title\">Executing stored procedure on SQL Server...</span>\n <span class=\"grid-loading-sub\">Calculating outstanding balances and ledger amounts. Please wait...</span>\n </div>\n }\n <rpt-grid-bridge\n [gridDefinition]=\"definition().grid!\"\n [data]=\"canvasGridData()\"\n (gridStateChange)=\"onCanvasGridStateChange($event)\"\n (columnDefsChange)=\"onCanvasColumnDefsChange($event)\"\n />\n </div>\n </div>\n } @else {\n <div class=\"canvas-add-grid-banner\" (click)=\"addResultGrid($event)\">\n <div class=\"add-grid-icon\">\n <rpt-icon name=\"table\" [size]=\"20\" />\n </div>\n <div class=\"add-grid-text\">\n <span class=\"add-grid-title\">+ Add Result Data Grid</span>\n <span class=\"add-grid-sub\">Attach an interactive data table or UltraGrid to display report execution output</span>\n </div>\n <button type=\"button\" class=\"btn-banner-add\">Add Grid</button>\n </div>\n }\n </div>\n </div>\n </div>", styles: [":host{display:block;flex:1;min-height:0;min-width:0;height:100%;background-color:#f1f5f9;font-family:var(--rpt-font-family, inherit);box-sizing:border-box}.canvas-wrapper{min-height:0;display:flex;flex-direction:column;height:100%}.canvas-toolbar{display:flex;align-items:center;justify-content:space-between;min-height:42px;height:42px;padding:0 24px;background:var(--rpt-color-surface, #ffffff);border-bottom:1px solid var(--rpt-color-border, #cbd5e1);box-sizing:border-box;gap:12px;overflow-x:auto;scrollbar-width:none;font-family:var(--rpt-font-family, inherit);flex-shrink:0}.canvas-toolbar::-webkit-scrollbar{display:none}.canvas-info{display:flex;align-items:center;gap:8px;font-family:var(--rpt-font-family, inherit);font-size:var(--rpt-font-size-sm, .75rem);color:var(--rpt-color-text-muted, #64748b);white-space:nowrap;line-height:1}.info-label{color:var(--rpt-color-text-muted, #64748b);font-weight:500;font-size:var(--rpt-font-size-sm, .75rem)}.info-value{font-weight:600;color:var(--rpt-color-text, #0f172a);font-size:var(--rpt-font-size-sm, .75rem)}.info-divider{color:var(--rpt-color-border, #cbd5e1);margin:0 2px}.live-mode-badge{display:inline-flex;align-items:center;gap:4px;font-family:var(--rpt-font-family, inherit);font-size:var(--rpt-font-size-xs, .7rem);font-weight:600;color:#047857;background:#ecfdf5;border:1px solid #a7f3d0;padding:2px 8px;border-radius:9999px;line-height:1}.canvas-actions{display:flex;align-items:center;gap:8px;white-space:nowrap}.canvas-action-btn{display:inline-flex;align-items:center;gap:6px;height:28px;padding:0 10px;font-family:var(--rpt-font-family, inherit);font-size:var(--rpt-font-size-sm, .75rem);font-weight:500;letter-spacing:-.01em;color:var(--rpt-color-text-secondary, #334155);background:var(--rpt-color-surface, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);cursor:pointer;line-height:1;white-space:nowrap;box-shadow:0 1px 2px #0000000a;transition:all var(--rpt-transition-speed, .15s) ease}.canvas-action-btn:hover{background:var(--rpt-color-surface-hover, #f8fafc);border-color:var(--rpt-color-border-hover, #94a3b8);color:var(--rpt-color-text, #0f172a);box-shadow:0 2px 4px #0000000f}.canvas-action-btn.btn-grid-configured{background:#eff6ff;border-color:#bfdbfe;color:#2563eb;font-weight:600}.canvas-action-btn.btn-grid-configured:hover{background:#dbeafe;color:#1d4ed8;border-color:#93c5fd}.canvas-action-btn.btn-grid-add{background:#f0fdf4;border-color:#bbf7d0;color:#16a34a;font-weight:600}.canvas-action-btn.btn-grid-add:hover{background:#dcfce7;color:#15803d;border-color:#86efac}.canvas-action-btn:active{transform:translateY(1px)}.canvas-scroll-area{flex:1;min-height:0;overflow-y:auto;padding:16px}.canvas-artboard{width:100%;max-width:1400px;margin:0 auto;background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-lg, 8px);box-shadow:0 4px 6px -1px #0000000d;min-height:480px;box-sizing:border-box}@media (max-width: 900px){.canvas-toolbar{min-height:36px;height:36px;padding:0 10px;gap:6px}.live-mode-badge{display:none}.canvas-info{font-size:.75rem;gap:5px}.canvas-action-btn{padding:0 8px;font-size:.725rem;height:28px;gap:4px}.canvas-scroll-area{padding:10px}.canvas-artboard{min-height:360px}}@media (max-width: 600px){.canvas-toolbar{padding:0 8px;gap:4px}.canvas-info .info-label,.canvas-action-btn span{display:none}.canvas-action-btn{padding:0 6px;min-width:28px;justify-content:center}}.canvas-report-header{margin-bottom:24px;padding-bottom:16px;border-bottom:1px solid var(--rpt-color-border, #e2e8f0)}.canvas-report-title{font-size:var(--rpt-font-size-xl, 1.5rem);font-weight:700;color:var(--rpt-color-text, #0f172a);margin:0 0 6px}.canvas-report-desc{font-size:var(--rpt-font-size-base, .875rem);color:var(--rpt-color-text-muted, #64748b);margin:0}.canvas-grid{display:grid;width:100%;min-height:180px;align-items:start;border-radius:6px;transition:all var(--rpt-transition-speed, .15s) ease}.canvas-component-card{container-type:inline-size;background:#fff;border:1.5px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);box-shadow:var(--rpt-box-shadow, 0 1px 2px rgba(0, 0, 0, .05));box-sizing:border-box;transition:all var(--rpt-transition-speed, .15s) ease;cursor:pointer;position:relative;overflow:visible;display:flex;flex-direction:column;min-height:104px;z-index:1}.canvas-component-card:hover{border-color:var(--rpt-color-primary, #2563eb);box-shadow:0 4px 6px -1px #2563eb1a;z-index:2}.canvas-component-card.is-selected{border-color:var(--rpt-color-primary, #2563eb);box-shadow:0 0 0 2px #2563eb40;z-index:10}.canvas-component-card:focus-within,.canvas-component-card:has(.rpt-combobox-menu),.canvas-component-card:has(.is-open){z-index:100}.canvas-component-card.is-resizing{-webkit-user-select:none;user-select:none;border-color:var(--rpt-color-primary, #2563eb);box-shadow:0 0 0 2px #2563eb59;z-index:30;transition:none!important}.resize-handle{position:absolute;z-index:20;touch-action:none;opacity:0;pointer-events:none;transition:opacity .15s ease}.canvas-component-card.is-selected .resize-handle{opacity:1;pointer-events:auto}.resize-handle.handle-e{top:0;right:0;width:6px;height:100%;cursor:ew-resize;display:flex;align-items:center;justify-content:center}.resize-handle.handle-e:after{content:\"\";width:3px;height:24px;background:#cbd5e1;border-radius:2px;transition:background .15s ease}.resize-handle.handle-e:hover:after,.canvas-component-card.is-selected .resize-handle.handle-e:after{background:var(--rpt-color-primary, #2563eb)}.resize-handle.handle-s{bottom:0;left:0;height:6px;width:100%;cursor:ns-resize;display:flex;align-items:center;justify-content:center}.resize-handle.handle-s:after{content:\"\";height:3px;width:24px;background:#cbd5e1;border-radius:2px;transition:background .15s ease}.resize-handle.handle-s:hover:after,.canvas-component-card.is-selected .resize-handle.handle-s:after{background:var(--rpt-color-primary, #2563eb)}.resize-handle.handle-se{bottom:0;right:0;width:12px;height:12px;cursor:nwse-resize;display:flex;align-items:flex-end;justify-content:flex-end;padding:2px}.resize-handle.handle-se:after{content:\"\";width:5px;height:5px;border-right:2px solid #cbd5e1;border-bottom:2px solid #cbd5e1;transition:border-color .15s ease}.resize-handle.handle-se:hover:after,.canvas-component-card.is-selected .resize-handle.handle-se:after{border-color:var(--rpt-color-primary, #2563eb)}.resize-dimension-tooltip{position:absolute;top:38px;right:8px;background:#0f172ae6;color:#fff;font-size:.7rem;font-weight:600;padding:3px 8px;border-radius:4px;white-space:nowrap;pointer-events:none;z-index:50;box-shadow:0 2px 4px #0003}.card-header{display:flex;align-items:center;justify-content:space-between;gap:6px;padding:5px 8px;background:var(--rpt-color-surface, #f8fafc);border-bottom:1px solid var(--rpt-color-border, #e2e8f0);border-top-left-radius:var(--rpt-border-radius, 6px);border-top-right-radius:var(--rpt-border-radius, 6px);min-height:32px;box-sizing:border-box;overflow:hidden;position:relative}.canvas-component-card.is-selected .card-header{overflow:visible}.card-title-group{display:flex;align-items:center;gap:5px;cursor:grab;min-width:0;flex:1;overflow:hidden}.drag-handle-dots{color:var(--rpt-color-text-muted, #94a3b8);font-size:.85rem;letter-spacing:-2px;flex-shrink:0;-webkit-user-select:none;user-select:none}.comp-title{font-size:.75rem;font-weight:600;color:var(--rpt-color-text, #0f172a);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;-webkit-user-select:none;user-select:none}.card-actions{display:flex;align-items:center;gap:4px;flex-shrink:0;margin-left:auto}.new-row-pill-static{font-size:.65rem;font-weight:600;color:#7c3aed;background:#f5f3ff;border:1px solid #ddd6fe;padding:1px 5px;border-radius:9999px;white-space:nowrap;line-height:1;flex-shrink:0}.span-pill-static{display:inline-flex;align-items:center;justify-content:center;font-size:.65rem;font-weight:600;color:var(--rpt-color-text-muted, #64748b);background:var(--rpt-color-surface, #f1f5f9);border:1px solid var(--rpt-color-border, #e2e8f0);padding:1px 6px;border-radius:9999px;line-height:1.2;white-space:nowrap;-webkit-user-select:none;user-select:none}.card-quick-controls{display:none;align-items:center;gap:3px;flex-shrink:0}.canvas-component-card:hover .span-pill-static,.canvas-component-card.is-selected .span-pill-static,.canvas-component-card:hover .new-row-pill-static,.canvas-component-card.is-selected .new-row-pill-static{display:none}.canvas-component-card:hover .card-quick-controls,.canvas-component-card.is-selected .card-quick-controls{display:inline-flex}.canvas-component-card.is-narrow .card-header{padding:4px 6px;gap:3px}.canvas-component-card.is-narrow:hover .comp-title,.canvas-component-card.is-narrow.is-selected .comp-title,.canvas-component-card.is-narrow:hover .drag-handle-dots,.canvas-component-card.is-narrow.is-selected .drag-handle-dots{display:none}.canvas-component-card.is-narrow .card-quick-controls{gap:2px}.canvas-component-card.is-narrow .span-control-group{height:18px}.canvas-component-card.is-narrow .span-btn{width:14px;height:16px}.canvas-component-card.is-narrow .span-badge{font-size:.6rem;padding:0 2px}.canvas-component-card.is-narrow .span-total{display:none}.canvas-component-card.is-narrow .card-action-btn{width:18px;height:18px}@container (max-width: 250px){.card-header{padding:4px 6px;gap:3px}.canvas-component-card:hover .comp-title,.canvas-component-card.is-selected .comp-title,.canvas-component-card:hover .drag-handle-dots,.canvas-component-card.is-selected .drag-handle-dots,.span-total{display:none}.card-quick-controls{gap:2px}.card-action-btn{width:18px;height:18px}.span-control-group{height:18px}.span-btn{width:14px;height:16px}.span-badge{font-size:.6rem;padding:0 2px}}@container (max-width: 160px){.canvas-component-card.is-selected .card-quick-controls{position:absolute;top:-30px;right:0;background:#fff;border:1.5px solid var(--rpt-color-primary, #2563eb);border-radius:6px;padding:2px 4px;gap:3px;box-shadow:0 4px 12px #2563eb2e,0 2px 4px #00000014;z-index:120;white-space:nowrap}}@media (max-width: 900px){.canvas-component-card:hover .comp-title,.canvas-component-card.is-selected .comp-title,.canvas-component-card:hover .drag-handle-dots,.canvas-component-card.is-selected .drag-handle-dots,.canvas-component-card .span-total{display:none}.canvas-component-card.is-selected .card-header{overflow:visible}.canvas-component-card.is-selected .card-quick-controls{position:absolute;top:-30px;right:0;background:#fff;border:1.5px solid var(--rpt-color-primary, #2563eb);border-radius:6px;padding:2px 4px;gap:3px;box-shadow:0 4px 12px #2563eb2e,0 2px 4px #00000014;z-index:120;white-space:nowrap}}.span-control-group{display:inline-flex;align-items:center;background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:4px;height:20px;padding:0 1px;white-space:nowrap;flex-shrink:0}.span-btn{width:16px;height:18px;display:inline-flex;align-items:center;justify-content:center;border:none;background:transparent;padding:0;color:var(--rpt-color-text-muted, #64748b);cursor:pointer;border-radius:2px;transition:all .12s ease}.span-btn:hover:not(:disabled){background:var(--rpt-color-surface-hover, #f1f5f9);color:var(--rpt-color-primary, #2563eb)}.span-btn:disabled{opacity:.3;cursor:not-allowed}.span-badge{font-size:.65rem;font-weight:600;color:var(--rpt-color-text, #0f172a);padding:0 3px;white-space:nowrap;-webkit-user-select:none;user-select:none;line-height:1}.span-total{font-size:.6rem;color:var(--rpt-color-text-muted, #64748b);font-weight:500}.card-action-btn{width:20px;height:20px;border:none;background:transparent;border-radius:4px;display:inline-flex;align-items:center;justify-content:center;padding:0;cursor:pointer;color:var(--rpt-color-text-muted, #64748b);transition:all .12s ease;flex-shrink:0}.card-action-btn:hover{background:#e2e8f0;color:var(--rpt-color-text, #0f172a)}.card-action-btn.btn-active-toggle{background:#7c3aed;color:#fff;border-color:#7c3aed}.card-action-btn.btn-active-toggle:hover{background:#6d28d9;color:#fff}.btn-delete:hover{background:#fee2e2;color:#ef4444}.card-body{padding:8px 10px 14px;flex:1;display:flex;flex-direction:column;min-height:0;min-width:0;max-width:100%;box-sizing:border-box;overflow:visible}.mock-input-field{display:flex;flex-direction:column;gap:4px;flex:1;height:100%;min-height:0}.mock-label{font-size:.7rem;font-weight:600;color:var(--rpt-color-text-muted, #475569);flex-shrink:0}.mock-input-box,.mock-select-box{border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:6px;padding:6px 12px;font-size:var(--rpt-font-size-base, .875rem);color:var(--rpt-color-text-muted, #94a3b8);background:#fff;flex:1;min-height:38px;height:100%;display:flex;align-items:center;justify-content:space-between;box-sizing:border-box}.mock-checkbox-field{display:flex;align-items:center;gap:6px;flex:1;height:100%;min-height:0;padding-top:14px}.mock-check-box{width:18px;height:18px;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:.65rem;color:var(--rpt-color-primary, #2563eb);flex-shrink:0}.mock-btn-wrapper{display:flex;flex:1;height:100%;min-height:38px;align-items:center;justify-content:flex-start;width:100%;min-width:0;box-sizing:border-box}.mock-btn{border-radius:var(--rpt-border-radius, 6px);padding:0 16px;height:36px;font-size:var(--rpt-font-size-base, .875rem);font-weight:500;border:1px solid transparent;cursor:pointer;width:auto;max-width:100%;display:inline-flex;align-items:center;justify-content:center;gap:6px;box-sizing:border-box;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:all .15s ease}.mock-btn.mock-btn-full{width:100%}.canvas-component-card.is-narrow .mock-btn{padding:0 6px;font-size:.775rem;width:100%;height:34px}.mock-kpi-field{display:flex;flex-direction:column;justify-content:space-between;flex:1;height:100%;min-height:0}.mock-kpi-header{display:flex;align-items:center;justify-content:space-between}.mock-kpi-value{font-size:1.25rem;font-weight:700;color:var(--rpt-color-text, #0f172a);margin:2px 0}.mock-kpi-footer{display:flex;align-items:center;gap:6px;font-size:.65rem}.mock-kpi-trend{color:#16a34a;font-weight:600}.mock-kpi-sub{color:#64748b}.mock-chart-field{display:flex;flex-direction:column;flex:1;height:100%;min-height:140px}.mock-chart-bars{display:flex;align-items:flex-end;justify-content:space-around;gap:16px;flex:1;min-height:100px;padding-top:12px;box-sizing:border-box}.mock-chart-col{flex:1;display:flex;flex-direction:column;align-items:center;height:100%;min-width:60px;max-width:140px}.mock-chart-bar{width:38px;max-width:44px;margin:0 auto;background:var(--rpt-color-primary, #2563eb);border-radius:6px 6px 0 0;opacity:.85;min-height:10px;transition:height .2s ease}.mock-bar-lbl{font-size:.6875rem;color:#64748b;margin-top:6px;text-align:center;white-space:normal;line-height:1.2}.btn-primary{background:var(--rpt-color-primary, #2563eb);color:#fff}.btn-secondary{background:#f1f5f9;color:#0f172a;border:1px solid #cbd5e1}.btn-outline{background:transparent;color:#2563eb;border:1px solid #2563eb}.btn-danger{background:#ef4444;color:#fff}.mock-generic-box{border:1px dashed #cbd5e1;border-radius:4px;padding:8px;font-size:.75rem;text-align:center;color:#64748b}.canvas-drag-placeholder{background:#2563eb0f;border:2px dashed var(--rpt-color-primary, #2563eb);border-radius:6px;min-height:80px;box-sizing:border-box;transition:transform .15s ease}.canvas-drag-preview,::ng-deep .canvas-drag-preview{background:#fff;border:2px solid var(--rpt-color-primary, #2563eb);border-radius:6px;padding:8px 14px;display:inline-flex;align-items:center;gap:8px;font-family:var(--rpt-font-family, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif)!important;font-size:.8rem;font-weight:600;color:var(--rpt-color-text, #0f172a);box-shadow:0 12px 24px -4px #00000040;z-index:10000}.preview-title,::ng-deep .preview-title{font-family:inherit!important;max-width:160px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.preview-span,::ng-deep .preview-span{font-family:inherit!important;font-size:.65rem;font-weight:600;color:var(--rpt-color-primary, #2563eb);background:#eff6ff;border:1px solid #bfdbfe;padding:1px 6px;border-radius:9999px}.canvas-empty-state{grid-column:1 / -1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:56px 28px;border:2px dashed var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-xl, 12px);background:var(--rpt-color-surface, #ffffff);text-align:center;box-shadow:var(--rpt-box-shadow-xs, 0 1px 2px 0 rgba(0, 0, 0, .04));transition:border-color var(--rpt-transition-speed, .15s) ease}.canvas-empty-state:hover{border-color:var(--rpt-color-primary, #2563eb)}.empty-hero-badge{width:56px;height:56px;border-radius:var(--rpt-border-radius-xl, 12px);background:var(--rpt-color-primary-light, #eff6ff);color:var(--rpt-color-primary, #2563eb);display:flex;align-items:center;justify-content:center;margin-bottom:16px;box-shadow:0 2px 8px #2563eb1f}.empty-hero-title{font-size:var(--rpt-font-size-lg, 1.125rem);font-weight:700;color:var(--rpt-color-text, #0f172a);margin:0 0 8px;letter-spacing:-.01em}.empty-hero-desc{font-size:var(--rpt-font-size-base, .875rem);color:var(--rpt-color-text-muted, #64748b);max-width:520px;line-height:1.5;margin:0 0 24px}.empty-hero-actions{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:10px}.empty-btn{display:inline-flex;align-items:center;gap:7px;padding:8px 16px;font-size:var(--rpt-font-size-sm, .75rem);font-weight:600;border-radius:var(--rpt-border-radius, 6px);cursor:pointer;transition:all var(--rpt-transition-speed, .15s) ease;font-family:inherit}.empty-btn-primary{background:var(--rpt-color-primary, #2563eb);color:#fff;border:1px solid var(--rpt-color-primary, #2563eb);box-shadow:0 1px 3px #2563eb33}.empty-btn-primary:hover{background:var(--rpt-color-primary-hover, #1d4ed8);border-color:var(--rpt-color-primary-hover, #1d4ed8);transform:translateY(-1px)}.empty-btn-secondary{background:#f8fafc;color:#334155;border:1px solid var(--rpt-color-border, #cbd5e1)}.empty-btn-secondary:hover{background:#f1f5f9;color:#0f172a;border-color:#94a3b8;transform:translateY(-1px)}.empty-btn-outline{background:#fff;color:var(--rpt-color-primary, #2563eb);border:1px solid #bfdbfe}.empty-btn-outline:hover{background:#eff6ff;border-color:var(--rpt-color-primary, #2563eb);transform:translateY(-1px)}.canvas-active-input{width:100%;height:32px;padding:4px 8px;font-size:.8125rem;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:4px;outline:none;box-sizing:border-box;background:#fff;color:#0f172a;transition:border-color .15s ease}.canvas-active-input:focus{border-color:var(--rpt-color-primary, #2563eb)}.canvas-active-checkbox-lbl{display:inline-flex;align-items:center;gap:6px;cursor:pointer}.canvas-live-control-wrapper{width:100%;max-width:100%;min-width:0;position:relative;z-index:1;box-sizing:border-box}.canvas-grid-box{margin-top:24px;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:8px;background:#fff;box-shadow:0 1px 3px #0000000d;overflow:hidden}.grid-preview-header{display:flex;align-items:center;justify-content:space-between;padding:10px 16px;background:var(--rpt-color-surface, #f8fafc);border-bottom:1px solid var(--rpt-color-border, #cbd5e1);font-size:.8rem;font-weight:600;color:var(--rpt-color-text, #0f172a)}.grid-title{font-weight:700}.grid-exec-badge{font-size:.6875rem;font-weight:600;padding:2px 8px;border-radius:10px}.grid-exec-badge.is-loading{background:#eff6ff;color:#2563eb}.grid-exec-badge.is-error{background:#fee2e2;color:#ef4444}.grid-exec-badge.is-success{background:#ecfdf5;color:#059669}.btn-canvas-run-grid{display:inline-flex;align-items:center;gap:5px;padding:4px 10px;font-size:.72rem;font-weight:600;color:#fff;background:var(--rpt-color-primary, #2563eb);border:none;border-radius:4px;cursor:pointer;transition:background .15s ease}.btn-canvas-run-grid:hover:not(:disabled){background:#1d4ed8}.btn-canvas-run-grid:disabled{opacity:.6;cursor:not-allowed}.btn-canvas-edit-grid{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;font-size:.72rem;font-weight:500;color:#334155;background:#fff;border:1px solid #cbd5e1;border-radius:4px;cursor:pointer;transition:all .12s ease}.btn-canvas-edit-grid:hover{background:#f1f5f9}.canvas-live-grid-container{width:100%;min-height:480px;background:#fff}.live-mode-badge{display:inline-flex;align-items:center;gap:4px;font-size:.6875rem;font-weight:700;color:#047857;background:#ecfdf5;border:1px solid #a7f3d0;padding:1px 8px;border-radius:9999px}.canvas-component-card.is-field-highlighted{border-color:#f59e0b!important;box-shadow:0 0 0 3px #f59e0b59!important;animation:rpt-pulse-border 1s ease-in-out infinite alternate}@keyframes rpt-pulse-border{0%{box-shadow:0 0 0 2px #f59e0b40}to{box-shadow:0 0 0 5px #f59e0b80}}.grid-layout-status-strip{display:flex;align-items:center;justify-content:space-between;padding:6px 16px;background:#f1f5f9;border-bottom:1px solid var(--rpt-color-border, #cbd5e1);font-size:.6875rem;color:#475569}.grid-status-info{display:inline-flex;align-items:center;gap:6px;font-weight:600}.grid-status-sync{display:inline-flex;align-items:center;gap:4px;color:#059669;font-weight:600;transition:transform .2s ease}.grid-status-sync.is-pulsing{transform:scale(1.08);color:#047857}.canvas-actions{display:flex;align-items:center;gap:8px}.btn-grid-add{background:#f0fdf4;border-color:#bbf7d0;color:#16a34a}.btn-grid-add:hover{background:#dcfce7;border-color:#86efac;color:#15803d}.btn-remove-grid{display:inline-flex;align-items:center;justify-content:center;padding:4px;border:none;background:transparent;color:#94a3b8;border-radius:4px;cursor:pointer;transition:all .15s ease}.btn-remove-grid:hover{background:#fee2e2;color:#ef4444}.canvas-add-grid-banner{margin-top:24px;border:2px dashed #cbd5e1;border-radius:8px;background:#fff;padding:16px 20px;display:flex;align-items:center;gap:16px;cursor:pointer;transition:all .15s ease}.canvas-add-grid-banner:hover{border-color:#2563eb;background:#f8fafc;box-shadow:0 2px 8px #2563eb14}.add-grid-icon{width:40px;height:40px;border-radius:8px;background:#eff6ff;color:#2563eb;display:flex;align-items:center;justify-content:center;flex-shrink:0}.add-grid-text{display:flex;flex-direction:column;gap:2px;flex:1}.add-grid-title{font-size:.875rem;font-weight:600;color:#0f172a}.add-grid-sub{font-size:.75rem;color:#64748b}.btn-banner-add{padding:6px 14px;font-size:.75rem;font-weight:600;color:#2563eb;background:#eff6ff;border:1px solid #bfdbfe;border-radius:6px;cursor:pointer;white-space:nowrap;transition:all .15s ease}.canvas-add-grid-banner:hover .btn-banner-add{background:#2563eb;color:#fff;border-color:#2563eb}.canvas-grid-loading-overlay{position:absolute;inset:0;background:#ffffffe6;backdrop-filter:blur(2px);z-index:10;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;font-family:inherit;border-radius:6px}.grid-loading-title{font-weight:600;font-size:.95rem;color:#1e293b}.grid-loading-sub{font-size:.8rem;color:#64748b}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button,.canvas-active-input::-webkit-inner-spin-button,.canvas-active-input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}input[type=number],.canvas-active-input{-moz-appearance:textfield;appearance:textfield}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: CdkScrollable, selector: "[cdk-scrollable], [cdkScrollable]" }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i1$2.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i1$2.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i1$2.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: i1$2.CdkDragPreview, selector: "ng-template[cdkDragPreview]", inputs: ["data", "matchSize"] }, { kind: "directive", type: i1$2.CdkDragPlaceholder, selector: "ng-template[cdkDragPlaceholder]", inputs: ["data"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: RptIconComponent, selector: "rpt-icon", inputs: ["name", "size", "color", "strokeWidth"] }, { kind: "component", type: RptDropdownComponent, selector: "rpt-dropdown", inputs: ["config", "control", "parentForm", "showStatus"], outputs: ["dropdownClick"] }, { kind: "component", type: RptDateRangeComponent, selector: "rpt-date-range", inputs: ["config", "control"] }, { kind: "component", type: RptRadioComponent, selector: "rpt-radio", inputs: ["config", "control", "parentForm"] }, { kind: "component", type: ReportGridBridgeComponent, selector: "rpt-grid-bridge", inputs: ["gridDefinition", "data"], outputs: ["gridStateChange", "columnDefsChange", "rowClick", "selectionChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
14103
14271
|
}
|
|
14104
14272
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DesignCanvasComponent, decorators: [{
|
|
14105
14273
|
type: Component,
|
|
14106
14274
|
args: [{ selector: 'rpt-design-canvas', standalone: true, imports: [
|
|
14107
14275
|
CommonModule,
|
|
14276
|
+
CdkScrollable,
|
|
14108
14277
|
DragDropModule,
|
|
14109
14278
|
ReactiveFormsModule,
|
|
14110
14279
|
RptIconComponent,
|
|
@@ -14112,7 +14281,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
14112
14281
|
RptDateRangeComponent,
|
|
14113
14282
|
RptRadioComponent,
|
|
14114
14283
|
ReportGridBridgeComponent,
|
|
14115
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: " <div class=\"canvas-wrapper\" (click)=\"onCanvasBackgroundClick($event)\">\n <!-- Canvas Status / Quick Controls -->\n <div class=\"canvas-toolbar\">\n <div class=\"canvas-info\">\n <span class=\"info-label\">Grid Layout:</span>\n <span class=\"info-value\">{{ totalColumns() }} Columns</span>\n <span class=\"info-divider\">\u2022</span>\n <span class=\"info-label\">Components:</span>\n <span class=\"info-value\">{{ components().length }}</span>\n <span class=\"info-divider\">\u2022</span>\n <span class=\"live-mode-badge\" title=\"Interactive canvas: Values typed here run live queries and test dropdowns.\">\n \u26A1 Live Interactive Mode\n </span>\n </div>\n\n <div class=\"canvas-actions\">\n @if (!definition().grid) {\n <button\n type=\"button\"\n class=\"canvas-action-btn btn-grid-add\"\n (click)=\"addResultGrid($event)\"\n title=\"Add a Result Grid to this report\"\n >\n <rpt-icon name=\"table\" [size]=\"14\" />\n <span>+ Add Grid</span>\n </button>\n } @else {\n <button\n type=\"button\"\n class=\"canvas-action-btn btn-grid-configured\"\n (click)=\"selectGridSettings($event)\"\n title=\"Configure Result Data Grid\"\n >\n <rpt-icon name=\"table\" [size]=\"14\" />\n <span>Grid ({{ definition().grid?.columns?.length ?? 0 }})</span>\n </button>\n }\n\n <button\n type=\"button\"\n class=\"canvas-action-btn\"\n (click)=\"selectReportSettings($event)\"\n title=\"Configure Page Layout & Grid Settings\"\n >\n <rpt-icon name=\"settings\" [size]=\"14\" />\n <span>Report Settings</span>\n </button>\n </div>\n </div>\n\n <!-- Canvas Drop Area -->\n <div class=\"canvas-scroll-area\">\n <div\n class=\"canvas-artboard\"\n [style.padding.px]=\"definition().layout.padding ?? 24\"\n >\n <!-- Report Header Preview -->\n <div class=\"canvas-report-header\">\n <h2 class=\"canvas-report-title\">{{ definition().name || 'Untitled Report' }}</h2>\n @if (definition().description) {\n <p class=\"canvas-report-desc\">{{ definition().description }}</p>\n }\n </div>\n\n <!-- Component Drop List Grid with 2D mixed orientation and data binding -->\n <div\n cdkDropList\n id=\"designer-canvas-drop-list\"\n class=\"canvas-grid\"\n cdkDropListOrientation=\"mixed\"\n [cdkDropListData]=\"sortedComponents()\"\n [style.grid-template-columns]=\"gridTemplateColumns()\"\n [style.gap.px]=\"definition().layout.gap ?? 16\"\n (cdkDropListDropped)=\"onDrop($event)\"\n >\n @for (comp of sortedComponents(); track comp.id; let idx = $index) {\n <div\n cdkDrag\n [cdkDragData]=\"comp\"\n [cdkDragDisabled]=\"isAnyResizing()\"\n class=\"canvas-component-card\"\n [class.is-selected]=\"isSelected(comp.id)\"\n [class.is-resizing]=\"resizingCompId() === comp.id\"\n [class.is-field-highlighted]=\"isFieldHighlighted(comp)\"\n [class.is-narrow]=\"getSpan(comp) <= 4\"\n [class.is-compact]=\"getSpan(comp) <= 2\"\n [style.grid-column]=\"getGridColumn(comp)\"\n [style.grid-row]=\"getGridRow(comp)\"\n [style.width]=\"comp.layout?.width || 'auto'\"\n [style.height]=\"getComponentHeight(comp)\"\n [style.min-height]=\"getComponentMinHeight(comp)\"\n (click)=\"onSelect(comp.id, $event)\"\n >\n <!-- Card Header with Drag Handle & Actions -->\n <div class=\"card-header\">\n <div class=\"card-title-group\" cdkDragHandle title=\"Drag to reorder\">\n <span class=\"drag-handle-dots\">\u22EE\u22EE</span>\n <rpt-icon [name]=\"getComponentIconName(comp.type)\" [size]=\"14\" />\n <span class=\"comp-title\" [title]=\"comp.label || comp.key || comp.type\">\n {{ comp.label || comp.key || comp.type }}\n </span>\n </div>\n\n <div class=\"card-actions\">\n <!-- Static span pill & new row / start col pill when not hovered / not selected -->\n @if (comp.layout?.column; as col) {\n <span class=\"new-row-pill-static\" title=\"Starts at Column {{ col }}\">Col {{ col }}</span>\n } @else if (comp.layout?.newRow) {\n <span class=\"new-row-pill-static\" title=\"Starts on separate row\">\u21B3 Row</span>\n }\n <span\n class=\"span-pill-static\"\n title=\"Column span: {{ getSpan(comp) }} of {{ totalColumns() }} columns\"\n >\n {{ getSpan(comp) }}/{{ totalColumns() }}\n </span>\n\n <!-- Quick Action Stepper & Buttons (revealed on hover or selection) -->\n <div class=\"card-quick-controls\">\n <button\n type=\"button\"\n class=\"card-action-btn\"\n [class.btn-active-toggle]=\"comp.layout?.newRow\"\n (click)=\"toggleNewRow(comp, $event)\"\n title=\"{{ comp.layout?.newRow ? 'Starts on new row (Click to place inline)' : 'Start on separate row (Row Break)' }}\"\n >\n <rpt-icon name=\"corner-down-right\" [size]=\"11\" />\n </button>\n\n <div class=\"span-control-group\" title=\"Adjust Column Span\">\n <button\n type=\"button\"\n class=\"span-btn\"\n [disabled]=\"getSpan(comp) <= 1\"\n (click)=\"adjustSpan(comp, -1, $event)\"\n title=\"Shrink Column Span\"\n >\n <rpt-icon name=\"minus\" [size]=\"9\" />\n </button>\n <span class=\"span-badge\" [title]=\"'Column span: ' + getSpan(comp) + ' of ' + totalColumns()\">\n <span class=\"span-num\">{{ getSpan(comp) }}</span><span class=\"span-total\">/{{ totalColumns() }}</span>\n </span>\n <button\n type=\"button\"\n class=\"span-btn\"\n [disabled]=\"getSpan(comp) >= totalColumns()\"\n (click)=\"adjustSpan(comp, 1, $event)\"\n title=\"Expand Column Span\"\n >\n <rpt-icon name=\"plus\" [size]=\"9\" />\n </button>\n </div>\n\n <button\n type=\"button\"\n class=\"card-action-btn\"\n (click)=\"onDuplicate(comp.id, $event)\"\n title=\"Duplicate component\"\n >\n <rpt-icon name=\"copy\" [size]=\"12\" />\n </button>\n <button\n type=\"button\"\n class=\"card-action-btn btn-delete\"\n (click)=\"onDelete(comp.id, $event)\"\n title=\"Remove component\"\n >\n <rpt-icon name=\"trash\" [size]=\"12\" />\n </button>\n </div>\n </div>\n </div>\n\n <!-- Card Body Visual Preview -->\n <div class=\"card-body\">\n\n @switch (comp.type) {\n @case ('input') {\n <div class=\"mock-input-field\" (click)=\"onControlInteract(comp, $event)\">\n <span class=\"mock-label\">{{ comp.label || 'Input' }}</span>\n @if (getCanvasControl(comp); as ctrl) {\n <input\n type=\"text\"\n class=\"canvas-active-input\"\n [placeholder]=\"comp.properties?.['placeholder'] || 'Enter text...'\"\n [formControl]=\"ctrl\"\n (focus)=\"onControlInteract(comp, $event)\"\n />\n } @else {\n <div class=\"mock-input-box\">{{ comp.properties?.['placeholder'] || 'Enter text...' }}</div>\n }\n </div>\n }\n @case ('dropdown') {\n <div class=\"canvas-live-control-wrapper\" (click)=\"onControlInteract(comp, $event)\">\n <rpt-dropdown\n [config]=\"comp\"\n [showStatus]=\"true\"\n [parentForm]=\"canvasForm()\"\n [control]=\"getCanvasControl(comp)\"\n (dropdownClick)=\"onControlInteract(comp)\"\n />\n </div>\n }\n @case ('date') {\n <div class=\"mock-input-field\" (click)=\"onControlInteract(comp, $event)\">\n <span class=\"mock-label\">{{ comp.label || 'Date' }}</span>\n @if (getCanvasControl(comp); as ctrl) {\n <input\n type=\"date\"\n class=\"canvas-active-input\"\n [formControl]=\"ctrl\"\n (focus)=\"onControlInteract(comp, $event)\"\n />\n } @else {\n <div class=\"mock-select-box\">\n <span>YYYY-MM-DD</span>\n <rpt-icon name=\"calendar\" [size]=\"12\" />\n </div>\n }\n </div>\n }\n @case ('date-range') {\n <div class=\"canvas-live-control-wrapper\" (click)=\"onControlInteract(comp, $event)\">\n <rpt-date-range\n [config]=\"comp\"\n [control]=\"getCanvasControl(comp)\"\n />\n </div>\n }\n @case ('number') {\n <div class=\"mock-input-field\" (click)=\"onControlInteract(comp, $event)\">\n <span class=\"mock-label\">{{ comp.label || 'Number' }}</span>\n @if (getCanvasControl(comp); as ctrl) {\n <input\n type=\"number\"\n class=\"canvas-active-input\"\n [formControl]=\"ctrl\"\n (focus)=\"onControlInteract(comp, $event)\"\n />\n } @else {\n <div class=\"mock-input-box\">\n {{ comp.properties?.['prefix'] || '' }} 0.00 {{ comp.properties?.['suffix'] || '' }}\n </div>\n }\n </div>\n }\n @case ('checkbox') {\n <div class=\"mock-checkbox-field\" (click)=\"onControlInteract(comp, $event)\">\n @if (getCanvasControl(comp); as ctrl) {\n <label class=\"canvas-active-checkbox-lbl\">\n <input type=\"checkbox\" [formControl]=\"ctrl\" (focus)=\"onControlInteract(comp, $event)\" />\n <span class=\"mock-label\">{{ comp.label || 'Checkbox' }}</span>\n </label>\n } @else {\n <span class=\"mock-check-box\">\n <rpt-icon name=\"check\" [size]=\"10\" />\n </span>\n <span class=\"mock-label\">{{ comp.label || 'Checkbox' }}</span>\n }\n </div>\n }\n @case ('radio') {\n <div class=\"canvas-live-control-wrapper\" (click)=\"onControlInteract(comp, $event)\">\n <rpt-radio\n [config]=\"comp\"\n [control]=\"getCanvasControl(comp)\"\n [parentForm]=\"canvasForm()\"\n />\n </div>\n }\n @case ('button') {\n <div class=\"mock-btn-wrapper\">\n <button\n type=\"button\"\n class=\"mock-btn\"\n [class]=\"'btn-' + (comp.properties?.['variant'] || 'primary')\"\n [class.mock-btn-full]=\"comp.properties?.['fullWidth'] === true || comp.layout?.width === '100%'\"\n [style.height]=\"getCustomBtnHeight(comp)\"\n [style.width]=\"getCustomBtnWidth(comp)\"\n (click)=\"onCanvasButtonClick(comp, $event)\"\n >\n @if (getCustomBtnIcon(comp); as btnIcon) {\n <rpt-icon [name]=\"btnIcon\" [size]=\"15\" />\n }\n <span>{{ comp.label || 'Button' }}</span>\n </button>\n </div>\n }\n @case ('kpi-card') {\n <div class=\"mock-kpi-field\">\n <div class=\"mock-kpi-header\">\n <span class=\"mock-label\">{{ comp.label || 'KPI Metric' }}</span>\n <rpt-icon name=\"kpi\" [size]=\"14\" />\n </div>\n <div class=\"mock-kpi-value\">{{ comp.properties?.['value'] || '$128,450' }}</div>\n <div class=\"mock-kpi-footer\">\n <span class=\"mock-kpi-trend\">+12.5%</span>\n <span class=\"mock-kpi-sub\">vs last period</span>\n </div>\n </div>\n }\n @case ('chart') {\n <div class=\"mock-chart-field\">\n <div class=\"mock-chart-header\">\n <span class=\"mock-label\">{{ comp.label || 'Quarterly Sales Revenue ($000)' }}</span>\n </div>\n <div class=\"mock-chart-bars\">\n <div class=\"mock-chart-col\">\n <div class=\"mock-chart-bar\" style=\"height: 44%\"></div>\n <span class=\"mock-bar-lbl\">Q1 Jan-Mar</span>\n </div>\n <div class=\"mock-chart-col\">\n <div class=\"mock-chart-bar\" style=\"height: 61%\"></div>\n <span class=\"mock-bar-lbl\">Q2 Apr-Jun</span>\n </div>\n <div class=\"mock-chart-col\">\n <div class=\"mock-chart-bar\" style=\"height: 79%\"></div>\n <span class=\"mock-bar-lbl\">Q3 Jul-Sep</span>\n </div>\n <div class=\"mock-chart-col\">\n <div class=\"mock-chart-bar\" style=\"height: 100%\"></div>\n <span class=\"mock-bar-lbl\">Q4 Oct-Dec</span>\n </div>\n </div>\n </div>\n }\n @default {\n <div class=\"mock-generic-box\">\n <span>{{ comp.type }}</span>\n </div>\n }\n }\n </div>\n\n <!-- Resizing Handles (East, South, South-East) -->\n <div\n class=\"resize-handle handle-e\"\n (pointerdown)=\"startResize($event, comp, 'e')\"\n (dblclick)=\"resetDimensions(comp, $event)\"\n title=\"Drag to resize column width \u2022 Double-click to reset\"\n ></div>\n <div\n class=\"resize-handle handle-s\"\n (pointerdown)=\"startResize($event, comp, 's')\"\n (dblclick)=\"resetDimensions(comp, $event)\"\n title=\"Drag to resize height \u2022 Double-click to reset\"\n ></div>\n <div\n class=\"resize-handle handle-se\"\n (pointerdown)=\"startResize($event, comp, 'se')\"\n (dblclick)=\"resetDimensions(comp, $event)\"\n title=\"Drag to resize width and height \u2022 Double-click to reset\"\n ></div>\n\n <!-- Resizing Dimension Ghost / Badge -->\n @if (resizingCompId() === comp.id) {\n <div class=\"resize-dimension-tooltip\">\n {{ resizingTooltip(comp) }}\n </div>\n }\n\n <!-- Drag placeholder with matched grid column span -->\n <div\n *cdkDragPlaceholder\n class=\"canvas-drag-placeholder\"\n [style.grid-column]=\"getGridColumn(comp)\"\n ></div>\n\n <!-- Drag preview -->\n <div *cdkDragPreview class=\"canvas-drag-preview\">\n <span class=\"drag-handle-dots\">\u22EE\u22EE</span>\n <rpt-icon [name]=\"getComponentIconName(comp.type)\" [size]=\"14\" />\n <span class=\"preview-title\">{{ comp.label || comp.key || comp.type }}</span>\n <span class=\"preview-span\">{{ getSpan(comp) }}/{{ totalColumns() }}</span>\n </div>\n </div>\n }\n\n @if (components().length === 0) {\n <div class=\"canvas-empty-state\" role=\"region\" aria-label=\"Empty canvas starter options\">\n <div class=\"empty-hero-badge\">\n <rpt-icon name=\"design\" [size]=\"28\" />\n </div>\n <h3 class=\"empty-hero-title\">Start Designing Your Custom Report</h3>\n <p class=\"empty-hero-desc\">\n Drag controls from the left component palette, or jumpstart your design with pre-configured enterprise blueprints.\n </p>\n\n <div class=\"empty-hero-actions\">\n <button\n type=\"button\"\n class=\"empty-btn empty-btn-primary\"\n (click)=\"applyBlueprint('blueprint-audit-register')\"\n title=\"Load complete Audit Register blueprint with parameters and result grid\"\n >\n <rpt-icon name=\"table\" [size]=\"15\" />\n <span>Audit Register Blueprint</span>\n </button>\n <button\n type=\"button\"\n class=\"empty-btn empty-btn-secondary\"\n (click)=\"applyBlueprint('blueprint-kpi-summary')\"\n title=\"Load Executive KPI summary blueprint\"\n >\n <rpt-icon name=\"chart-bar\" [size]=\"15\" />\n <span>KPI Summary Blueprint</span>\n </button>\n <button\n type=\"button\"\n class=\"empty-btn empty-btn-outline\"\n (click)=\"addStarterComponent('date-range')\"\n title=\"Add a Date Range filter component\"\n >\n <rpt-icon name=\"calendar\" [size]=\"14\" />\n <span>+ Add Date Filter</span>\n </button>\n <button\n type=\"button\"\n class=\"empty-btn empty-btn-outline\"\n (click)=\"addStarterComponent('button')\"\n title=\"Add an Execute Action button\"\n >\n <rpt-icon name=\"play\" [size]=\"14\" />\n <span>+ Add Run Button</span>\n </button>\n </div>\n </div>\n }\n </div>\n\n <!-- Live Result Grid Preview with Bways-Grid Features -->\n @if (definition().grid) {\n <div class=\"canvas-grid-box\">\n <div class=\"grid-preview-header\">\n <div style=\"display: flex; align-items: center; gap: 8px;\">\n <rpt-icon name=\"table\" [size]=\"15\" />\n <span class=\"grid-title\">Bways Grid ({{ definition().grid?.columns?.length ?? 0 }} columns)</span>\n @if (isExecutingGrid()) {\n <span class=\"grid-exec-badge is-loading\">Loading data from database...</span>\n } @else if (gridExecError()) {\n <span class=\"grid-exec-badge is-error\">{{ gridExecError() }}</span>\n } @else if (canvasGridData().length > 0) {\n <span class=\"grid-exec-badge is-success\">{{ canvasGridData().length }} rows loaded</span>\n }\n </div>\n <div style=\"display: flex; align-items: center; gap: 8px;\">\n <button\n type=\"button\"\n class=\"btn-canvas-run-grid\"\n (click)=\"executeCanvasReport($event)\"\n [disabled]=\"isExecutingGrid()\"\n title=\"Execute query and load data into Bways Grid\"\n >\n <rpt-icon name=\"play\" [size]=\"11\" />\n <span>{{ isExecutingGrid() ? 'Running...' : 'Run / Refresh Grid' }}</span>\n </button>\n <button\n type=\"button\"\n class=\"btn-canvas-edit-grid\"\n (click)=\"selectGridSettings($event)\"\n title=\"Configure columns and grid options\"\n >\n <rpt-icon name=\"settings\" [size]=\"11\" />\n <span>Configure</span>\n </button>\n <button\n type=\"button\"\n class=\"btn-remove-grid\"\n (click)=\"removeGrid($event)\"\n title=\"Remove Result Grid from this report\"\n >\n <rpt-icon name=\"trash\" [size]=\"13\" />\n </button>\n </div>\n </div>\n\n @if (getGridSummaryText(); as summary) {\n <div class=\"grid-layout-status-strip\">\n <div class=\"grid-status-info\">\n <rpt-icon name=\"table\" [size]=\"12\" />\n <span>{{ summary }}</span>\n </div>\n <div class=\"grid-status-sync\" [class.is-pulsing]=\"layoutJustUpdated()\">\n <rpt-icon name=\"check\" [size]=\"11\" />\n <span>Layout auto-persisted</span>\n </div>\n </div>\n }\n\n <!-- Live Bways Grid bridge -->\n <div class=\"canvas-live-grid-container\" (click)=\"$event.stopPropagation()\" style=\"position: relative; min-height: 250px;\">\n @if (isExecutingGrid()) {\n <div class=\"canvas-grid-loading-overlay\">\n <span class=\"status-dot dot-pulse\" style=\"width: 14px; height: 14px; color: #2563eb; background: #2563eb; border-radius: 50%;\"></span>\n <span class=\"grid-loading-title\">Executing stored procedure on SQL Server...</span>\n <span class=\"grid-loading-sub\">Calculating outstanding balances and ledger amounts. Please wait...</span>\n </div>\n }\n <rpt-grid-bridge\n [gridDefinition]=\"definition().grid!\"\n [data]=\"canvasGridData()\"\n (gridStateChange)=\"onCanvasGridStateChange($event)\"\n (columnDefsChange)=\"onCanvasColumnDefsChange($event)\"\n />\n </div>\n </div>\n } @else {\n <div class=\"canvas-add-grid-banner\" (click)=\"addResultGrid($event)\">\n <div class=\"add-grid-icon\">\n <rpt-icon name=\"table\" [size]=\"20\" />\n </div>\n <div class=\"add-grid-text\">\n <span class=\"add-grid-title\">+ Add Result Data Grid</span>\n <span class=\"add-grid-sub\">Attach an interactive data table or UltraGrid to display report execution output</span>\n </div>\n <button type=\"button\" class=\"btn-banner-add\">Add Grid</button>\n </div>\n }\n </div>\n </div>\n </div>", styles: [":host{display:block;flex:1;min-height:0;min-width:0;height:100%;background-color:#f1f5f9;font-family:var(--rpt-font-family, inherit);box-sizing:border-box}.canvas-wrapper{min-height:0;display:flex;flex-direction:column;height:100%}.canvas-toolbar{display:flex;align-items:center;justify-content:space-between;min-height:42px;height:42px;padding:0 24px;background:var(--rpt-color-surface, #ffffff);border-bottom:1px solid var(--rpt-color-border, #cbd5e1);box-sizing:border-box;gap:12px;overflow-x:auto;scrollbar-width:none;font-family:var(--rpt-font-family, inherit);flex-shrink:0}.canvas-toolbar::-webkit-scrollbar{display:none}.canvas-info{display:flex;align-items:center;gap:8px;font-family:var(--rpt-font-family, inherit);font-size:var(--rpt-font-size-sm, .75rem);color:var(--rpt-color-text-muted, #64748b);white-space:nowrap;line-height:1}.info-label{color:var(--rpt-color-text-muted, #64748b);font-weight:500;font-size:var(--rpt-font-size-sm, .75rem)}.info-value{font-weight:600;color:var(--rpt-color-text, #0f172a);font-size:var(--rpt-font-size-sm, .75rem)}.info-divider{color:var(--rpt-color-border, #cbd5e1);margin:0 2px}.live-mode-badge{display:inline-flex;align-items:center;gap:4px;font-family:var(--rpt-font-family, inherit);font-size:var(--rpt-font-size-xs, .7rem);font-weight:600;color:#047857;background:#ecfdf5;border:1px solid #a7f3d0;padding:2px 8px;border-radius:9999px;line-height:1}.canvas-actions{display:flex;align-items:center;gap:8px;white-space:nowrap}.canvas-action-btn{display:inline-flex;align-items:center;gap:6px;height:28px;padding:0 10px;font-family:var(--rpt-font-family, inherit);font-size:var(--rpt-font-size-sm, .75rem);font-weight:500;letter-spacing:-.01em;color:var(--rpt-color-text-secondary, #334155);background:var(--rpt-color-surface, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);cursor:pointer;line-height:1;white-space:nowrap;box-shadow:0 1px 2px #0000000a;transition:all var(--rpt-transition-speed, .15s) ease}.canvas-action-btn:hover{background:var(--rpt-color-surface-hover, #f8fafc);border-color:var(--rpt-color-border-hover, #94a3b8);color:var(--rpt-color-text, #0f172a);box-shadow:0 2px 4px #0000000f}.canvas-action-btn.btn-grid-configured{background:#eff6ff;border-color:#bfdbfe;color:#2563eb;font-weight:600}.canvas-action-btn.btn-grid-configured:hover{background:#dbeafe;color:#1d4ed8;border-color:#93c5fd}.canvas-action-btn.btn-grid-add{background:#f0fdf4;border-color:#bbf7d0;color:#16a34a;font-weight:600}.canvas-action-btn.btn-grid-add:hover{background:#dcfce7;color:#15803d;border-color:#86efac}.canvas-action-btn:active{transform:translateY(1px)}.canvas-scroll-area{flex:1;min-height:0;overflow-y:auto;padding:16px}.canvas-artboard{width:100%;max-width:1400px;margin:0 auto;background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-lg, 8px);box-shadow:0 4px 6px -1px #0000000d;min-height:480px;box-sizing:border-box}@media (max-width: 900px){.canvas-toolbar{min-height:36px;height:36px;padding:0 10px;gap:6px}.live-mode-badge{display:none}.canvas-info{font-size:.75rem;gap:5px}.canvas-action-btn{padding:0 8px;font-size:.725rem;height:28px;gap:4px}.canvas-scroll-area{padding:10px}.canvas-artboard{min-height:360px}}@media (max-width: 600px){.canvas-toolbar{padding:0 8px;gap:4px}.canvas-info .info-label,.canvas-action-btn span{display:none}.canvas-action-btn{padding:0 6px;min-width:28px;justify-content:center}}.canvas-report-header{margin-bottom:24px;padding-bottom:16px;border-bottom:1px solid var(--rpt-color-border, #e2e8f0)}.canvas-report-title{font-size:var(--rpt-font-size-xl, 1.5rem);font-weight:700;color:var(--rpt-color-text, #0f172a);margin:0 0 6px}.canvas-report-desc{font-size:var(--rpt-font-size-base, .875rem);color:var(--rpt-color-text-muted, #64748b);margin:0}.canvas-grid{display:grid;width:100%;min-height:180px;align-items:start;border-radius:6px;transition:all var(--rpt-transition-speed, .15s) ease}.canvas-component-card{container-type:inline-size;background:#fff;border:1.5px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);box-shadow:var(--rpt-box-shadow, 0 1px 2px rgba(0, 0, 0, .05));box-sizing:border-box;transition:all var(--rpt-transition-speed, .15s) ease;cursor:pointer;position:relative;overflow:visible;display:flex;flex-direction:column;min-height:104px;z-index:1}.canvas-component-card:hover{border-color:var(--rpt-color-primary, #2563eb);box-shadow:0 4px 6px -1px #2563eb1a;z-index:2}.canvas-component-card.is-selected{border-color:var(--rpt-color-primary, #2563eb);box-shadow:0 0 0 2px #2563eb40;z-index:10}.canvas-component-card:focus-within,.canvas-component-card:has(.rpt-combobox-menu),.canvas-component-card:has(.is-open){z-index:100}.canvas-component-card.is-resizing{-webkit-user-select:none;user-select:none;border-color:var(--rpt-color-primary, #2563eb);box-shadow:0 0 0 2px #2563eb59;z-index:30;transition:none!important}.resize-handle{position:absolute;z-index:20;touch-action:none;opacity:0;pointer-events:none;transition:opacity .15s ease}.canvas-component-card.is-selected .resize-handle{opacity:1;pointer-events:auto}.resize-handle.handle-e{top:0;right:0;width:6px;height:100%;cursor:ew-resize;display:flex;align-items:center;justify-content:center}.resize-handle.handle-e:after{content:\"\";width:3px;height:24px;background:#cbd5e1;border-radius:2px;transition:background .15s ease}.resize-handle.handle-e:hover:after,.canvas-component-card.is-selected .resize-handle.handle-e:after{background:var(--rpt-color-primary, #2563eb)}.resize-handle.handle-s{bottom:0;left:0;height:6px;width:100%;cursor:ns-resize;display:flex;align-items:center;justify-content:center}.resize-handle.handle-s:after{content:\"\";height:3px;width:24px;background:#cbd5e1;border-radius:2px;transition:background .15s ease}.resize-handle.handle-s:hover:after,.canvas-component-card.is-selected .resize-handle.handle-s:after{background:var(--rpt-color-primary, #2563eb)}.resize-handle.handle-se{bottom:0;right:0;width:12px;height:12px;cursor:nwse-resize;display:flex;align-items:flex-end;justify-content:flex-end;padding:2px}.resize-handle.handle-se:after{content:\"\";width:5px;height:5px;border-right:2px solid #cbd5e1;border-bottom:2px solid #cbd5e1;transition:border-color .15s ease}.resize-handle.handle-se:hover:after,.canvas-component-card.is-selected .resize-handle.handle-se:after{border-color:var(--rpt-color-primary, #2563eb)}.resize-dimension-tooltip{position:absolute;top:38px;right:8px;background:#0f172ae6;color:#fff;font-size:.7rem;font-weight:600;padding:3px 8px;border-radius:4px;white-space:nowrap;pointer-events:none;z-index:50;box-shadow:0 2px 4px #0003}.card-header{display:flex;align-items:center;justify-content:space-between;gap:6px;padding:5px 8px;background:var(--rpt-color-surface, #f8fafc);border-bottom:1px solid var(--rpt-color-border, #e2e8f0);border-top-left-radius:var(--rpt-border-radius, 6px);border-top-right-radius:var(--rpt-border-radius, 6px);min-height:32px;box-sizing:border-box;overflow:hidden;position:relative}.canvas-component-card.is-selected .card-header{overflow:visible}.card-title-group{display:flex;align-items:center;gap:5px;cursor:grab;min-width:0;flex:1;overflow:hidden}.drag-handle-dots{color:var(--rpt-color-text-muted, #94a3b8);font-size:.85rem;letter-spacing:-2px;flex-shrink:0;-webkit-user-select:none;user-select:none}.comp-title{font-size:.75rem;font-weight:600;color:var(--rpt-color-text, #0f172a);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;-webkit-user-select:none;user-select:none}.card-actions{display:flex;align-items:center;gap:4px;flex-shrink:0;margin-left:auto}.new-row-pill-static{font-size:.65rem;font-weight:600;color:#7c3aed;background:#f5f3ff;border:1px solid #ddd6fe;padding:1px 5px;border-radius:9999px;white-space:nowrap;line-height:1;flex-shrink:0}.span-pill-static{display:inline-flex;align-items:center;justify-content:center;font-size:.65rem;font-weight:600;color:var(--rpt-color-text-muted, #64748b);background:var(--rpt-color-surface, #f1f5f9);border:1px solid var(--rpt-color-border, #e2e8f0);padding:1px 6px;border-radius:9999px;line-height:1.2;white-space:nowrap;-webkit-user-select:none;user-select:none}.card-quick-controls{display:none;align-items:center;gap:3px;flex-shrink:0}.canvas-component-card:hover .span-pill-static,.canvas-component-card.is-selected .span-pill-static,.canvas-component-card:hover .new-row-pill-static,.canvas-component-card.is-selected .new-row-pill-static{display:none}.canvas-component-card:hover .card-quick-controls,.canvas-component-card.is-selected .card-quick-controls{display:inline-flex}.canvas-component-card.is-narrow .card-header{padding:4px 6px;gap:3px}.canvas-component-card.is-narrow:hover .comp-title,.canvas-component-card.is-narrow.is-selected .comp-title,.canvas-component-card.is-narrow:hover .drag-handle-dots,.canvas-component-card.is-narrow.is-selected .drag-handle-dots{display:none}.canvas-component-card.is-narrow .card-quick-controls{gap:2px}.canvas-component-card.is-narrow .span-control-group{height:18px}.canvas-component-card.is-narrow .span-btn{width:14px;height:16px}.canvas-component-card.is-narrow .span-badge{font-size:.6rem;padding:0 2px}.canvas-component-card.is-narrow .span-total{display:none}.canvas-component-card.is-narrow .card-action-btn{width:18px;height:18px}@container (max-width: 250px){.card-header{padding:4px 6px;gap:3px}.canvas-component-card:hover .comp-title,.canvas-component-card.is-selected .comp-title,.canvas-component-card:hover .drag-handle-dots,.canvas-component-card.is-selected .drag-handle-dots,.span-total{display:none}.card-quick-controls{gap:2px}.card-action-btn{width:18px;height:18px}.span-control-group{height:18px}.span-btn{width:14px;height:16px}.span-badge{font-size:.6rem;padding:0 2px}}@container (max-width: 160px){.canvas-component-card.is-selected .card-quick-controls{position:absolute;top:-30px;right:0;background:#fff;border:1.5px solid var(--rpt-color-primary, #2563eb);border-radius:6px;padding:2px 4px;gap:3px;box-shadow:0 4px 12px #2563eb2e,0 2px 4px #00000014;z-index:120;white-space:nowrap}}@media (max-width: 900px){.canvas-component-card:hover .comp-title,.canvas-component-card.is-selected .comp-title,.canvas-component-card:hover .drag-handle-dots,.canvas-component-card.is-selected .drag-handle-dots,.canvas-component-card .span-total{display:none}.canvas-component-card.is-selected .card-header{overflow:visible}.canvas-component-card.is-selected .card-quick-controls{position:absolute;top:-30px;right:0;background:#fff;border:1.5px solid var(--rpt-color-primary, #2563eb);border-radius:6px;padding:2px 4px;gap:3px;box-shadow:0 4px 12px #2563eb2e,0 2px 4px #00000014;z-index:120;white-space:nowrap}}.span-control-group{display:inline-flex;align-items:center;background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:4px;height:20px;padding:0 1px;white-space:nowrap;flex-shrink:0}.span-btn{width:16px;height:18px;display:inline-flex;align-items:center;justify-content:center;border:none;background:transparent;padding:0;color:var(--rpt-color-text-muted, #64748b);cursor:pointer;border-radius:2px;transition:all .12s ease}.span-btn:hover:not(:disabled){background:var(--rpt-color-surface-hover, #f1f5f9);color:var(--rpt-color-primary, #2563eb)}.span-btn:disabled{opacity:.3;cursor:not-allowed}.span-badge{font-size:.65rem;font-weight:600;color:var(--rpt-color-text, #0f172a);padding:0 3px;white-space:nowrap;-webkit-user-select:none;user-select:none;line-height:1}.span-total{font-size:.6rem;color:var(--rpt-color-text-muted, #64748b);font-weight:500}.card-action-btn{width:20px;height:20px;border:none;background:transparent;border-radius:4px;display:inline-flex;align-items:center;justify-content:center;padding:0;cursor:pointer;color:var(--rpt-color-text-muted, #64748b);transition:all .12s ease;flex-shrink:0}.card-action-btn:hover{background:#e2e8f0;color:var(--rpt-color-text, #0f172a)}.card-action-btn.btn-active-toggle{background:#7c3aed;color:#fff;border-color:#7c3aed}.card-action-btn.btn-active-toggle:hover{background:#6d28d9;color:#fff}.btn-delete:hover{background:#fee2e2;color:#ef4444}.card-body{padding:8px 10px 14px;flex:1;display:flex;flex-direction:column;min-height:0;min-width:0;max-width:100%;box-sizing:border-box;overflow:visible}.mock-input-field{display:flex;flex-direction:column;gap:4px;flex:1;height:100%;min-height:0}.mock-label{font-size:.7rem;font-weight:600;color:var(--rpt-color-text-muted, #475569);flex-shrink:0}.mock-input-box,.mock-select-box{border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:6px;padding:6px 12px;font-size:var(--rpt-font-size-base, .875rem);color:var(--rpt-color-text-muted, #94a3b8);background:#fff;flex:1;min-height:38px;height:100%;display:flex;align-items:center;justify-content:space-between;box-sizing:border-box}.mock-checkbox-field{display:flex;align-items:center;gap:6px;flex:1;height:100%;min-height:0;padding-top:14px}.mock-check-box{width:18px;height:18px;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:.65rem;color:var(--rpt-color-primary, #2563eb);flex-shrink:0}.mock-btn-wrapper{display:flex;flex:1;height:100%;min-height:38px;align-items:center;justify-content:flex-start;width:100%;min-width:0;box-sizing:border-box}.mock-btn{border-radius:var(--rpt-border-radius, 6px);padding:0 16px;height:36px;font-size:var(--rpt-font-size-base, .875rem);font-weight:500;border:1px solid transparent;cursor:pointer;width:auto;max-width:100%;display:inline-flex;align-items:center;justify-content:center;gap:6px;box-sizing:border-box;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:all .15s ease}.mock-btn.mock-btn-full{width:100%}.canvas-component-card.is-narrow .mock-btn{padding:0 6px;font-size:.775rem;width:100%;height:34px}.mock-kpi-field{display:flex;flex-direction:column;justify-content:space-between;flex:1;height:100%;min-height:0}.mock-kpi-header{display:flex;align-items:center;justify-content:space-between}.mock-kpi-value{font-size:1.25rem;font-weight:700;color:var(--rpt-color-text, #0f172a);margin:2px 0}.mock-kpi-footer{display:flex;align-items:center;gap:6px;font-size:.65rem}.mock-kpi-trend{color:#16a34a;font-weight:600}.mock-kpi-sub{color:#64748b}.mock-chart-field{display:flex;flex-direction:column;flex:1;height:100%;min-height:140px}.mock-chart-bars{display:flex;align-items:flex-end;justify-content:space-around;gap:16px;flex:1;min-height:100px;padding-top:12px;box-sizing:border-box}.mock-chart-col{flex:1;display:flex;flex-direction:column;align-items:center;height:100%;min-width:60px;max-width:140px}.mock-chart-bar{width:38px;max-width:44px;margin:0 auto;background:var(--rpt-color-primary, #2563eb);border-radius:6px 6px 0 0;opacity:.85;min-height:10px;transition:height .2s ease}.mock-bar-lbl{font-size:.6875rem;color:#64748b;margin-top:6px;text-align:center;white-space:normal;line-height:1.2}.btn-primary{background:var(--rpt-color-primary, #2563eb);color:#fff}.btn-secondary{background:#f1f5f9;color:#0f172a;border:1px solid #cbd5e1}.btn-outline{background:transparent;color:#2563eb;border:1px solid #2563eb}.btn-danger{background:#ef4444;color:#fff}.mock-generic-box{border:1px dashed #cbd5e1;border-radius:4px;padding:8px;font-size:.75rem;text-align:center;color:#64748b}.canvas-drag-placeholder{background:#2563eb0f;border:2px dashed var(--rpt-color-primary, #2563eb);border-radius:6px;min-height:80px;box-sizing:border-box;transition:transform .15s ease}.canvas-drag-preview,::ng-deep .canvas-drag-preview{background:#fff;border:2px solid var(--rpt-color-primary, #2563eb);border-radius:6px;padding:8px 14px;display:inline-flex;align-items:center;gap:8px;font-family:var(--rpt-font-family, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif)!important;font-size:.8rem;font-weight:600;color:var(--rpt-color-text, #0f172a);box-shadow:0 12px 24px -4px #00000040;z-index:10000}.preview-title,::ng-deep .preview-title{font-family:inherit!important;max-width:160px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.preview-span,::ng-deep .preview-span{font-family:inherit!important;font-size:.65rem;font-weight:600;color:var(--rpt-color-primary, #2563eb);background:#eff6ff;border:1px solid #bfdbfe;padding:1px 6px;border-radius:9999px}.canvas-empty-state{grid-column:1 / -1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:56px 28px;border:2px dashed var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-xl, 12px);background:var(--rpt-color-surface, #ffffff);text-align:center;box-shadow:var(--rpt-box-shadow-xs, 0 1px 2px 0 rgba(0, 0, 0, .04));transition:border-color var(--rpt-transition-speed, .15s) ease}.canvas-empty-state:hover{border-color:var(--rpt-color-primary, #2563eb)}.empty-hero-badge{width:56px;height:56px;border-radius:var(--rpt-border-radius-xl, 12px);background:var(--rpt-color-primary-light, #eff6ff);color:var(--rpt-color-primary, #2563eb);display:flex;align-items:center;justify-content:center;margin-bottom:16px;box-shadow:0 2px 8px #2563eb1f}.empty-hero-title{font-size:var(--rpt-font-size-lg, 1.125rem);font-weight:700;color:var(--rpt-color-text, #0f172a);margin:0 0 8px;letter-spacing:-.01em}.empty-hero-desc{font-size:var(--rpt-font-size-base, .875rem);color:var(--rpt-color-text-muted, #64748b);max-width:520px;line-height:1.5;margin:0 0 24px}.empty-hero-actions{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:10px}.empty-btn{display:inline-flex;align-items:center;gap:7px;padding:8px 16px;font-size:var(--rpt-font-size-sm, .75rem);font-weight:600;border-radius:var(--rpt-border-radius, 6px);cursor:pointer;transition:all var(--rpt-transition-speed, .15s) ease;font-family:inherit}.empty-btn-primary{background:var(--rpt-color-primary, #2563eb);color:#fff;border:1px solid var(--rpt-color-primary, #2563eb);box-shadow:0 1px 3px #2563eb33}.empty-btn-primary:hover{background:var(--rpt-color-primary-hover, #1d4ed8);border-color:var(--rpt-color-primary-hover, #1d4ed8);transform:translateY(-1px)}.empty-btn-secondary{background:#f8fafc;color:#334155;border:1px solid var(--rpt-color-border, #cbd5e1)}.empty-btn-secondary:hover{background:#f1f5f9;color:#0f172a;border-color:#94a3b8;transform:translateY(-1px)}.empty-btn-outline{background:#fff;color:var(--rpt-color-primary, #2563eb);border:1px solid #bfdbfe}.empty-btn-outline:hover{background:#eff6ff;border-color:var(--rpt-color-primary, #2563eb);transform:translateY(-1px)}.canvas-active-input{width:100%;height:32px;padding:4px 8px;font-size:.8125rem;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:4px;outline:none;box-sizing:border-box;background:#fff;color:#0f172a;transition:border-color .15s ease}.canvas-active-input:focus{border-color:var(--rpt-color-primary, #2563eb)}.canvas-active-checkbox-lbl{display:inline-flex;align-items:center;gap:6px;cursor:pointer}.canvas-live-control-wrapper{width:100%;max-width:100%;min-width:0;position:relative;z-index:1;box-sizing:border-box}.canvas-grid-box{margin-top:24px;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:8px;background:#fff;box-shadow:0 1px 3px #0000000d;overflow:hidden}.grid-preview-header{display:flex;align-items:center;justify-content:space-between;padding:10px 16px;background:var(--rpt-color-surface, #f8fafc);border-bottom:1px solid var(--rpt-color-border, #cbd5e1);font-size:.8rem;font-weight:600;color:var(--rpt-color-text, #0f172a)}.grid-title{font-weight:700}.grid-exec-badge{font-size:.6875rem;font-weight:600;padding:2px 8px;border-radius:10px}.grid-exec-badge.is-loading{background:#eff6ff;color:#2563eb}.grid-exec-badge.is-error{background:#fee2e2;color:#ef4444}.grid-exec-badge.is-success{background:#ecfdf5;color:#059669}.btn-canvas-run-grid{display:inline-flex;align-items:center;gap:5px;padding:4px 10px;font-size:.72rem;font-weight:600;color:#fff;background:var(--rpt-color-primary, #2563eb);border:none;border-radius:4px;cursor:pointer;transition:background .15s ease}.btn-canvas-run-grid:hover:not(:disabled){background:#1d4ed8}.btn-canvas-run-grid:disabled{opacity:.6;cursor:not-allowed}.btn-canvas-edit-grid{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;font-size:.72rem;font-weight:500;color:#334155;background:#fff;border:1px solid #cbd5e1;border-radius:4px;cursor:pointer;transition:all .12s ease}.btn-canvas-edit-grid:hover{background:#f1f5f9}.canvas-live-grid-container{width:100%;min-height:480px;background:#fff}.live-mode-badge{display:inline-flex;align-items:center;gap:4px;font-size:.6875rem;font-weight:700;color:#047857;background:#ecfdf5;border:1px solid #a7f3d0;padding:1px 8px;border-radius:9999px}.canvas-component-card.is-field-highlighted{border-color:#f59e0b!important;box-shadow:0 0 0 3px #f59e0b59!important;animation:rpt-pulse-border 1s ease-in-out infinite alternate}@keyframes rpt-pulse-border{0%{box-shadow:0 0 0 2px #f59e0b40}to{box-shadow:0 0 0 5px #f59e0b80}}.grid-layout-status-strip{display:flex;align-items:center;justify-content:space-between;padding:6px 16px;background:#f1f5f9;border-bottom:1px solid var(--rpt-color-border, #cbd5e1);font-size:.6875rem;color:#475569}.grid-status-info{display:inline-flex;align-items:center;gap:6px;font-weight:600}.grid-status-sync{display:inline-flex;align-items:center;gap:4px;color:#059669;font-weight:600;transition:transform .2s ease}.grid-status-sync.is-pulsing{transform:scale(1.08);color:#047857}.canvas-actions{display:flex;align-items:center;gap:8px}.btn-grid-add{background:#f0fdf4;border-color:#bbf7d0;color:#16a34a}.btn-grid-add:hover{background:#dcfce7;border-color:#86efac;color:#15803d}.btn-remove-grid{display:inline-flex;align-items:center;justify-content:center;padding:4px;border:none;background:transparent;color:#94a3b8;border-radius:4px;cursor:pointer;transition:all .15s ease}.btn-remove-grid:hover{background:#fee2e2;color:#ef4444}.canvas-add-grid-banner{margin-top:24px;border:2px dashed #cbd5e1;border-radius:8px;background:#fff;padding:16px 20px;display:flex;align-items:center;gap:16px;cursor:pointer;transition:all .15s ease}.canvas-add-grid-banner:hover{border-color:#2563eb;background:#f8fafc;box-shadow:0 2px 8px #2563eb14}.add-grid-icon{width:40px;height:40px;border-radius:8px;background:#eff6ff;color:#2563eb;display:flex;align-items:center;justify-content:center;flex-shrink:0}.add-grid-text{display:flex;flex-direction:column;gap:2px;flex:1}.add-grid-title{font-size:.875rem;font-weight:600;color:#0f172a}.add-grid-sub{font-size:.75rem;color:#64748b}.btn-banner-add{padding:6px 14px;font-size:.75rem;font-weight:600;color:#2563eb;background:#eff6ff;border:1px solid #bfdbfe;border-radius:6px;cursor:pointer;white-space:nowrap;transition:all .15s ease}.canvas-add-grid-banner:hover .btn-banner-add{background:#2563eb;color:#fff;border-color:#2563eb}.canvas-grid-loading-overlay{position:absolute;inset:0;background:#ffffffe6;backdrop-filter:blur(2px);z-index:10;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;font-family:inherit;border-radius:6px}.grid-loading-title{font-weight:600;font-size:.95rem;color:#1e293b}.grid-loading-sub{font-size:.8rem;color:#64748b}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button,.canvas-active-input::-webkit-inner-spin-button,.canvas-active-input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}input[type=number],.canvas-active-input{-moz-appearance:textfield;appearance:textfield}\n"] }]
|
|
14284
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: " <div class=\"canvas-wrapper\" (click)=\"onCanvasBackgroundClick($event)\">\n <!-- Canvas Status / Quick Controls -->\n <div class=\"canvas-toolbar\">\n <div class=\"canvas-info\">\n <span class=\"info-label\">Grid Layout:</span>\n <span class=\"info-value\">{{ totalColumns() }} Columns</span>\n <span class=\"info-divider\">\u2022</span>\n <span class=\"info-label\">Components:</span>\n <span class=\"info-value\">{{ components().length }}</span>\n <span class=\"info-divider\">\u2022</span>\n <span class=\"live-mode-badge\" title=\"Interactive canvas: Values typed here run live queries and test dropdowns.\">\n \u26A1 Live Interactive Mode\n </span>\n </div>\n\n <div class=\"canvas-actions\">\n @if (!definition().grid) {\n <button\n type=\"button\"\n class=\"canvas-action-btn btn-grid-add\"\n (click)=\"addResultGrid($event)\"\n title=\"Add a Result Grid to this report\"\n >\n <rpt-icon name=\"table\" [size]=\"14\" />\n <span>+ Add Grid</span>\n </button>\n } @else {\n <button\n type=\"button\"\n class=\"canvas-action-btn btn-grid-configured\"\n (click)=\"selectGridSettings($event)\"\n title=\"Configure Result Data Grid\"\n >\n <rpt-icon name=\"table\" [size]=\"14\" />\n <span>Grid ({{ definition().grid?.columns?.length ?? 0 }})</span>\n </button>\n }\n\n <button\n type=\"button\"\n class=\"canvas-action-btn\"\n (click)=\"selectReportSettings($event)\"\n title=\"Configure Page Layout & Grid Settings\"\n >\n <rpt-icon name=\"settings\" [size]=\"14\" />\n <span>Report Settings</span>\n </button>\n </div>\n </div>\n\n <!-- Canvas Drop Area -->\n <div class=\"canvas-scroll-area\" cdkScrollable>\n <div\n class=\"canvas-artboard\"\n [style.padding.px]=\"definition().layout.padding ?? 24\"\n >\n <!-- Report Header Preview -->\n <div class=\"canvas-report-header\">\n <h2 class=\"canvas-report-title\">{{ definition().name || 'Untitled Report' }}</h2>\n @if (definition().description) {\n <p class=\"canvas-report-desc\">{{ definition().description }}</p>\n }\n </div>\n\n <!-- Component Drop List Grid with 2D mixed orientation and data binding -->\n <div\n cdkDropList\n id=\"designer-canvas-drop-list\"\n class=\"canvas-grid\"\n cdkDropListOrientation=\"mixed\"\n [cdkDropListData]=\"sortedComponents()\"\n [style.grid-template-columns]=\"gridTemplateColumns()\"\n [style.gap.px]=\"definition().layout.gap ?? 16\"\n (cdkDropListDropped)=\"onDrop($event)\"\n >\n @for (comp of sortedComponents(); track comp.id; let idx = $index) {\n <div\n cdkDrag\n [cdkDragData]=\"comp\"\n [cdkDragDisabled]=\"isAnyResizing()\"\n class=\"canvas-component-card\"\n [class.is-selected]=\"isSelected(comp.id)\"\n [class.is-resizing]=\"resizingCompId() === comp.id\"\n [class.is-field-highlighted]=\"isFieldHighlighted(comp)\"\n [class.is-narrow]=\"getSpan(comp) <= 4\"\n [class.is-compact]=\"getSpan(comp) <= 2\"\n [style.grid-column]=\"getGridColumn(comp)\"\n [style.grid-row]=\"getGridRow(comp)\"\n [style.width]=\"comp.layout?.width || 'auto'\"\n [style.height]=\"getComponentHeight(comp)\"\n [style.min-height]=\"getComponentMinHeight(comp)\"\n (click)=\"onSelect(comp.id, $event)\"\n >\n <!-- Card Header with Drag Handle & Actions -->\n <div class=\"card-header\">\n <div class=\"card-title-group\" cdkDragHandle title=\"Drag to reorder\">\n <span class=\"drag-handle-dots\">\u22EE\u22EE</span>\n <rpt-icon [name]=\"getComponentIconName(comp.type)\" [size]=\"14\" />\n <span class=\"comp-title\" [title]=\"comp.label || comp.key || comp.type\">\n {{ comp.label || comp.key || comp.type }}\n </span>\n </div>\n\n <div class=\"card-actions\">\n <!-- Static span pill & new row / start col pill when not hovered / not selected -->\n @if (comp.layout?.column; as col) {\n <span class=\"new-row-pill-static\" title=\"Starts at Column {{ col }}\">Col {{ col }}</span>\n } @else if (comp.layout?.newRow) {\n <span class=\"new-row-pill-static\" title=\"Starts on separate row\">\u21B3 Row</span>\n }\n <span\n class=\"span-pill-static\"\n title=\"Column span: {{ getSpan(comp) }} of {{ totalColumns() }} columns\"\n >\n {{ getSpan(comp) }}/{{ totalColumns() }}\n </span>\n\n <!-- Quick Action Stepper & Buttons (revealed on hover or selection) -->\n <div class=\"card-quick-controls\">\n <button\n type=\"button\"\n class=\"card-action-btn\"\n [class.btn-active-toggle]=\"comp.layout?.newRow\"\n (click)=\"toggleNewRow(comp, $event)\"\n title=\"{{ comp.layout?.newRow ? 'Starts on new row (Click to place inline)' : 'Start on separate row (Row Break)' }}\"\n >\n <rpt-icon name=\"corner-down-right\" [size]=\"11\" />\n </button>\n\n <div class=\"span-control-group\" title=\"Adjust Column Span\">\n <button\n type=\"button\"\n class=\"span-btn\"\n [disabled]=\"getSpan(comp) <= 1\"\n (click)=\"adjustSpan(comp, -1, $event)\"\n title=\"Shrink Column Span\"\n >\n <rpt-icon name=\"minus\" [size]=\"9\" />\n </button>\n <span class=\"span-badge\" [title]=\"'Column span: ' + getSpan(comp) + ' of ' + totalColumns()\">\n <span class=\"span-num\">{{ getSpan(comp) }}</span><span class=\"span-total\">/{{ totalColumns() }}</span>\n </span>\n <button\n type=\"button\"\n class=\"span-btn\"\n [disabled]=\"getSpan(comp) >= totalColumns()\"\n (click)=\"adjustSpan(comp, 1, $event)\"\n title=\"Expand Column Span\"\n >\n <rpt-icon name=\"plus\" [size]=\"9\" />\n </button>\n </div>\n\n <button\n type=\"button\"\n class=\"card-action-btn\"\n (click)=\"onDuplicate(comp.id, $event)\"\n title=\"Duplicate component\"\n >\n <rpt-icon name=\"copy\" [size]=\"12\" />\n </button>\n <button\n type=\"button\"\n class=\"card-action-btn btn-delete\"\n (click)=\"onDelete(comp.id, $event)\"\n title=\"Remove component\"\n >\n <rpt-icon name=\"trash\" [size]=\"12\" />\n </button>\n </div>\n </div>\n </div>\n\n <!-- Card Body Visual Preview -->\n <div class=\"card-body\">\n\n @switch (comp.type) {\n @case ('input') {\n <div class=\"mock-input-field\" (click)=\"onControlInteract(comp, $event)\">\n <span class=\"mock-label\">{{ comp.label || 'Input' }}</span>\n @if (getCanvasControl(comp); as ctrl) {\n <input\n type=\"text\"\n class=\"canvas-active-input\"\n [placeholder]=\"comp.properties?.['placeholder'] || 'Enter text...'\"\n [formControl]=\"ctrl\"\n (focus)=\"onControlInteract(comp, $event)\"\n />\n } @else {\n <div class=\"mock-input-box\">{{ comp.properties?.['placeholder'] || 'Enter text...' }}</div>\n }\n </div>\n }\n @case ('dropdown') {\n <div class=\"canvas-live-control-wrapper\" (click)=\"onControlInteract(comp, $event)\">\n <rpt-dropdown\n [config]=\"comp\"\n [showStatus]=\"true\"\n [parentForm]=\"canvasForm()\"\n [control]=\"getCanvasControl(comp)\"\n (dropdownClick)=\"onControlInteract(comp)\"\n />\n </div>\n }\n @case ('date') {\n <div class=\"mock-input-field\" (click)=\"onControlInteract(comp, $event)\">\n <span class=\"mock-label\">{{ comp.label || 'Date' }}</span>\n @if (getCanvasControl(comp); as ctrl) {\n <input\n type=\"date\"\n class=\"canvas-active-input\"\n [formControl]=\"ctrl\"\n (focus)=\"onControlInteract(comp, $event)\"\n />\n } @else {\n <div class=\"mock-select-box\">\n <span>YYYY-MM-DD</span>\n <rpt-icon name=\"calendar\" [size]=\"12\" />\n </div>\n }\n </div>\n }\n @case ('date-range') {\n <div class=\"canvas-live-control-wrapper\" (click)=\"onControlInteract(comp, $event)\">\n <rpt-date-range\n [config]=\"comp\"\n [control]=\"getCanvasControl(comp)\"\n />\n </div>\n }\n @case ('number') {\n <div class=\"mock-input-field\" (click)=\"onControlInteract(comp, $event)\">\n <span class=\"mock-label\">{{ comp.label || 'Number' }}</span>\n @if (getCanvasControl(comp); as ctrl) {\n <input\n type=\"number\"\n class=\"canvas-active-input\"\n [formControl]=\"ctrl\"\n (focus)=\"onControlInteract(comp, $event)\"\n />\n } @else {\n <div class=\"mock-input-box\">\n {{ comp.properties?.['prefix'] || '' }} 0.00 {{ comp.properties?.['suffix'] || '' }}\n </div>\n }\n </div>\n }\n @case ('checkbox') {\n <div class=\"mock-checkbox-field\" (click)=\"onControlInteract(comp, $event)\">\n @if (getCanvasControl(comp); as ctrl) {\n <label class=\"canvas-active-checkbox-lbl\">\n <input type=\"checkbox\" [formControl]=\"ctrl\" (focus)=\"onControlInteract(comp, $event)\" />\n <span class=\"mock-label\">{{ comp.label || 'Checkbox' }}</span>\n </label>\n } @else {\n <span class=\"mock-check-box\">\n <rpt-icon name=\"check\" [size]=\"10\" />\n </span>\n <span class=\"mock-label\">{{ comp.label || 'Checkbox' }}</span>\n }\n </div>\n }\n @case ('radio') {\n <div class=\"canvas-live-control-wrapper\" (click)=\"onControlInteract(comp, $event)\">\n <rpt-radio\n [config]=\"comp\"\n [control]=\"getCanvasControl(comp)\"\n [parentForm]=\"canvasForm()\"\n />\n </div>\n }\n @case ('button') {\n <div class=\"mock-btn-wrapper\">\n <button\n type=\"button\"\n class=\"mock-btn\"\n [class]=\"'btn-' + (comp.properties?.['variant'] || 'primary')\"\n [class.mock-btn-full]=\"comp.properties?.['fullWidth'] === true || comp.layout?.width === '100%'\"\n [style.height]=\"getCustomBtnHeight(comp)\"\n [style.width]=\"getCustomBtnWidth(comp)\"\n (click)=\"onCanvasButtonClick(comp, $event)\"\n >\n @if (getCustomBtnIcon(comp); as btnIcon) {\n <rpt-icon [name]=\"btnIcon\" [size]=\"15\" />\n }\n <span>{{ comp.label || 'Button' }}</span>\n </button>\n </div>\n }\n @case ('kpi-card') {\n <div class=\"mock-kpi-field\">\n <div class=\"mock-kpi-header\">\n <span class=\"mock-label\">{{ comp.label || 'KPI Metric' }}</span>\n <rpt-icon name=\"kpi\" [size]=\"14\" />\n </div>\n <div class=\"mock-kpi-value\">{{ comp.properties?.['value'] || '$128,450' }}</div>\n <div class=\"mock-kpi-footer\">\n <span class=\"mock-kpi-trend\">+12.5%</span>\n <span class=\"mock-kpi-sub\">vs last period</span>\n </div>\n </div>\n }\n @case ('chart') {\n <div class=\"mock-chart-field\">\n <div class=\"mock-chart-header\">\n <span class=\"mock-label\">{{ comp.label || 'Quarterly Sales Revenue ($000)' }}</span>\n </div>\n <div class=\"mock-chart-bars\">\n <div class=\"mock-chart-col\">\n <div class=\"mock-chart-bar\" style=\"height: 44%\"></div>\n <span class=\"mock-bar-lbl\">Q1 Jan-Mar</span>\n </div>\n <div class=\"mock-chart-col\">\n <div class=\"mock-chart-bar\" style=\"height: 61%\"></div>\n <span class=\"mock-bar-lbl\">Q2 Apr-Jun</span>\n </div>\n <div class=\"mock-chart-col\">\n <div class=\"mock-chart-bar\" style=\"height: 79%\"></div>\n <span class=\"mock-bar-lbl\">Q3 Jul-Sep</span>\n </div>\n <div class=\"mock-chart-col\">\n <div class=\"mock-chart-bar\" style=\"height: 100%\"></div>\n <span class=\"mock-bar-lbl\">Q4 Oct-Dec</span>\n </div>\n </div>\n </div>\n }\n @default {\n <div class=\"mock-generic-box\">\n <span>{{ comp.type }}</span>\n </div>\n }\n }\n </div>\n\n <!-- Resizing Handles (East, South, South-East) -->\n <div\n class=\"resize-handle handle-e\"\n (pointerdown)=\"startResize($event, comp, 'e')\"\n (dblclick)=\"resetDimensions(comp, $event)\"\n title=\"Drag to resize column width \u2022 Double-click to reset\"\n ></div>\n <div\n class=\"resize-handle handle-s\"\n (pointerdown)=\"startResize($event, comp, 's')\"\n (dblclick)=\"resetDimensions(comp, $event)\"\n title=\"Drag to resize height \u2022 Double-click to reset\"\n ></div>\n <div\n class=\"resize-handle handle-se\"\n (pointerdown)=\"startResize($event, comp, 'se')\"\n (dblclick)=\"resetDimensions(comp, $event)\"\n title=\"Drag to resize width and height \u2022 Double-click to reset\"\n ></div>\n\n <!-- Resizing Dimension Ghost / Badge -->\n @if (resizingCompId() === comp.id) {\n <div class=\"resize-dimension-tooltip\">\n {{ resizingTooltip(comp) }}\n </div>\n }\n\n <!-- Drag placeholder with matched grid column span -->\n <div\n *cdkDragPlaceholder\n class=\"canvas-drag-placeholder\"\n [style.grid-column]=\"getGridColumn(comp)\"\n ></div>\n\n <!-- Drag preview -->\n <div *cdkDragPreview class=\"canvas-drag-preview\">\n <span class=\"drag-handle-dots\">\u22EE\u22EE</span>\n <rpt-icon [name]=\"getComponentIconName(comp.type)\" [size]=\"14\" />\n <span class=\"preview-title\">{{ comp.label || comp.key || comp.type }}</span>\n <span class=\"preview-span\">{{ getSpan(comp) }}/{{ totalColumns() }}</span>\n </div>\n </div>\n }\n\n @if (components().length === 0) {\n <div class=\"canvas-empty-state\" role=\"region\" aria-label=\"Empty canvas starter options\">\n <div class=\"empty-hero-badge\">\n <rpt-icon name=\"design\" [size]=\"28\" />\n </div>\n <h3 class=\"empty-hero-title\">Start Designing Your Custom Report</h3>\n <p class=\"empty-hero-desc\">\n Drag controls from the left component palette, or jumpstart your design with pre-configured enterprise blueprints.\n </p>\n\n <div class=\"empty-hero-actions\">\n <button\n type=\"button\"\n class=\"empty-btn empty-btn-primary\"\n (click)=\"applyBlueprint('blueprint-audit-register')\"\n title=\"Load complete Audit Register blueprint with parameters and result grid\"\n >\n <rpt-icon name=\"table\" [size]=\"15\" />\n <span>Audit Register Blueprint</span>\n </button>\n <button\n type=\"button\"\n class=\"empty-btn empty-btn-secondary\"\n (click)=\"applyBlueprint('blueprint-kpi-summary')\"\n title=\"Load Executive KPI summary blueprint\"\n >\n <rpt-icon name=\"chart-bar\" [size]=\"15\" />\n <span>KPI Summary Blueprint</span>\n </button>\n <button\n type=\"button\"\n class=\"empty-btn empty-btn-outline\"\n (click)=\"addStarterComponent('date-range')\"\n title=\"Add a Date Range filter component\"\n >\n <rpt-icon name=\"calendar\" [size]=\"14\" />\n <span>+ Add Date Filter</span>\n </button>\n <button\n type=\"button\"\n class=\"empty-btn empty-btn-outline\"\n (click)=\"addStarterComponent('button')\"\n title=\"Add an Execute Action button\"\n >\n <rpt-icon name=\"play\" [size]=\"14\" />\n <span>+ Add Run Button</span>\n </button>\n </div>\n </div>\n }\n </div>\n\n <!-- Live Result Grid Preview with Bways-Grid Features -->\n @if (definition().grid) {\n <div class=\"canvas-grid-box\">\n <div class=\"grid-preview-header\">\n <div style=\"display: flex; align-items: center; gap: 8px;\">\n <rpt-icon name=\"table\" [size]=\"15\" />\n <span class=\"grid-title\">Bways Grid ({{ definition().grid?.columns?.length ?? 0 }} columns)</span>\n @if (isExecutingGrid()) {\n <span class=\"grid-exec-badge is-loading\">Loading data from database...</span>\n } @else if (gridExecError()) {\n <span class=\"grid-exec-badge is-error\">{{ gridExecError() }}</span>\n } @else if (canvasGridData().length > 0) {\n <span class=\"grid-exec-badge is-success\">{{ canvasGridData().length }} rows loaded</span>\n }\n </div>\n <div style=\"display: flex; align-items: center; gap: 8px;\">\n <button\n type=\"button\"\n class=\"btn-canvas-run-grid\"\n (click)=\"executeCanvasReport($event)\"\n [disabled]=\"isExecutingGrid()\"\n title=\"Execute query and load data into Bways Grid\"\n >\n <rpt-icon name=\"play\" [size]=\"11\" />\n <span>{{ isExecutingGrid() ? 'Running...' : 'Run / Refresh Grid' }}</span>\n </button>\n <button\n type=\"button\"\n class=\"btn-canvas-edit-grid\"\n (click)=\"selectGridSettings($event)\"\n title=\"Configure columns and grid options\"\n >\n <rpt-icon name=\"settings\" [size]=\"11\" />\n <span>Configure</span>\n </button>\n <button\n type=\"button\"\n class=\"btn-remove-grid\"\n (click)=\"removeGrid($event)\"\n title=\"Remove Result Grid from this report\"\n >\n <rpt-icon name=\"trash\" [size]=\"13\" />\n </button>\n </div>\n </div>\n\n @if (getGridSummaryText(); as summary) {\n <div class=\"grid-layout-status-strip\">\n <div class=\"grid-status-info\">\n <rpt-icon name=\"table\" [size]=\"12\" />\n <span>{{ summary }}</span>\n </div>\n <div class=\"grid-status-sync\" [class.is-pulsing]=\"layoutJustUpdated()\">\n <rpt-icon name=\"check\" [size]=\"11\" />\n <span>Layout auto-persisted</span>\n </div>\n </div>\n }\n\n <!-- Live Bways Grid bridge -->\n <div class=\"canvas-live-grid-container\" (click)=\"$event.stopPropagation()\" style=\"position: relative; min-height: 250px;\">\n @if (isExecutingGrid()) {\n <div class=\"canvas-grid-loading-overlay\">\n <span class=\"status-dot dot-pulse\" style=\"width: 14px; height: 14px; color: #2563eb; background: #2563eb; border-radius: 50%;\"></span>\n <span class=\"grid-loading-title\">Executing stored procedure on SQL Server...</span>\n <span class=\"grid-loading-sub\">Calculating outstanding balances and ledger amounts. Please wait...</span>\n </div>\n }\n <rpt-grid-bridge\n [gridDefinition]=\"definition().grid!\"\n [data]=\"canvasGridData()\"\n (gridStateChange)=\"onCanvasGridStateChange($event)\"\n (columnDefsChange)=\"onCanvasColumnDefsChange($event)\"\n />\n </div>\n </div>\n } @else {\n <div class=\"canvas-add-grid-banner\" (click)=\"addResultGrid($event)\">\n <div class=\"add-grid-icon\">\n <rpt-icon name=\"table\" [size]=\"20\" />\n </div>\n <div class=\"add-grid-text\">\n <span class=\"add-grid-title\">+ Add Result Data Grid</span>\n <span class=\"add-grid-sub\">Attach an interactive data table or UltraGrid to display report execution output</span>\n </div>\n <button type=\"button\" class=\"btn-banner-add\">Add Grid</button>\n </div>\n }\n </div>\n </div>\n </div>", styles: [":host{display:block;flex:1;min-height:0;min-width:0;height:100%;background-color:#f1f5f9;font-family:var(--rpt-font-family, inherit);box-sizing:border-box}.canvas-wrapper{min-height:0;display:flex;flex-direction:column;height:100%}.canvas-toolbar{display:flex;align-items:center;justify-content:space-between;min-height:42px;height:42px;padding:0 24px;background:var(--rpt-color-surface, #ffffff);border-bottom:1px solid var(--rpt-color-border, #cbd5e1);box-sizing:border-box;gap:12px;overflow-x:auto;scrollbar-width:none;font-family:var(--rpt-font-family, inherit);flex-shrink:0}.canvas-toolbar::-webkit-scrollbar{display:none}.canvas-info{display:flex;align-items:center;gap:8px;font-family:var(--rpt-font-family, inherit);font-size:var(--rpt-font-size-sm, .75rem);color:var(--rpt-color-text-muted, #64748b);white-space:nowrap;line-height:1}.info-label{color:var(--rpt-color-text-muted, #64748b);font-weight:500;font-size:var(--rpt-font-size-sm, .75rem)}.info-value{font-weight:600;color:var(--rpt-color-text, #0f172a);font-size:var(--rpt-font-size-sm, .75rem)}.info-divider{color:var(--rpt-color-border, #cbd5e1);margin:0 2px}.live-mode-badge{display:inline-flex;align-items:center;gap:4px;font-family:var(--rpt-font-family, inherit);font-size:var(--rpt-font-size-xs, .7rem);font-weight:600;color:#047857;background:#ecfdf5;border:1px solid #a7f3d0;padding:2px 8px;border-radius:9999px;line-height:1}.canvas-actions{display:flex;align-items:center;gap:8px;white-space:nowrap}.canvas-action-btn{display:inline-flex;align-items:center;gap:6px;height:28px;padding:0 10px;font-family:var(--rpt-font-family, inherit);font-size:var(--rpt-font-size-sm, .75rem);font-weight:500;letter-spacing:-.01em;color:var(--rpt-color-text-secondary, #334155);background:var(--rpt-color-surface, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);cursor:pointer;line-height:1;white-space:nowrap;box-shadow:0 1px 2px #0000000a;transition:all var(--rpt-transition-speed, .15s) ease}.canvas-action-btn:hover{background:var(--rpt-color-surface-hover, #f8fafc);border-color:var(--rpt-color-border-hover, #94a3b8);color:var(--rpt-color-text, #0f172a);box-shadow:0 2px 4px #0000000f}.canvas-action-btn.btn-grid-configured{background:#eff6ff;border-color:#bfdbfe;color:#2563eb;font-weight:600}.canvas-action-btn.btn-grid-configured:hover{background:#dbeafe;color:#1d4ed8;border-color:#93c5fd}.canvas-action-btn.btn-grid-add{background:#f0fdf4;border-color:#bbf7d0;color:#16a34a;font-weight:600}.canvas-action-btn.btn-grid-add:hover{background:#dcfce7;color:#15803d;border-color:#86efac}.canvas-action-btn:active{transform:translateY(1px)}.canvas-scroll-area{flex:1;min-height:0;overflow-y:auto;padding:16px}.canvas-artboard{width:100%;max-width:1400px;margin:0 auto;background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-lg, 8px);box-shadow:0 4px 6px -1px #0000000d;min-height:480px;box-sizing:border-box}@media (max-width: 900px){.canvas-toolbar{min-height:36px;height:36px;padding:0 10px;gap:6px}.live-mode-badge{display:none}.canvas-info{font-size:.75rem;gap:5px}.canvas-action-btn{padding:0 8px;font-size:.725rem;height:28px;gap:4px}.canvas-scroll-area{padding:10px}.canvas-artboard{min-height:360px}}@media (max-width: 600px){.canvas-toolbar{padding:0 8px;gap:4px}.canvas-info .info-label,.canvas-action-btn span{display:none}.canvas-action-btn{padding:0 6px;min-width:28px;justify-content:center}}.canvas-report-header{margin-bottom:24px;padding-bottom:16px;border-bottom:1px solid var(--rpt-color-border, #e2e8f0)}.canvas-report-title{font-size:var(--rpt-font-size-xl, 1.5rem);font-weight:700;color:var(--rpt-color-text, #0f172a);margin:0 0 6px}.canvas-report-desc{font-size:var(--rpt-font-size-base, .875rem);color:var(--rpt-color-text-muted, #64748b);margin:0}.canvas-grid{display:grid;width:100%;min-height:180px;align-items:start;border-radius:6px;transition:all var(--rpt-transition-speed, .15s) ease}.canvas-component-card{container-type:inline-size;background:#fff;border:1.5px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);box-shadow:var(--rpt-box-shadow, 0 1px 2px rgba(0, 0, 0, .05));box-sizing:border-box;transition:all var(--rpt-transition-speed, .15s) ease;cursor:pointer;position:relative;overflow:visible;display:flex;flex-direction:column;min-height:104px;z-index:1}.canvas-component-card:hover{border-color:var(--rpt-color-primary, #2563eb);box-shadow:0 4px 6px -1px #2563eb1a;z-index:2}.canvas-component-card.is-selected{border-color:var(--rpt-color-primary, #2563eb);box-shadow:0 0 0 2px #2563eb40;z-index:10}.canvas-component-card:focus-within,.canvas-component-card:has(.rpt-combobox-menu),.canvas-component-card:has(.is-open){z-index:100}.canvas-component-card.is-resizing{-webkit-user-select:none;user-select:none;border-color:var(--rpt-color-primary, #2563eb);box-shadow:0 0 0 2px #2563eb59;z-index:30;transition:none!important}.resize-handle{position:absolute;z-index:20;touch-action:none;opacity:0;pointer-events:none;transition:opacity .15s ease}.canvas-component-card.is-selected .resize-handle{opacity:1;pointer-events:auto}.resize-handle.handle-e{top:0;right:0;width:6px;height:100%;cursor:ew-resize;display:flex;align-items:center;justify-content:center}.resize-handle.handle-e:after{content:\"\";width:3px;height:24px;background:#cbd5e1;border-radius:2px;transition:background .15s ease}.resize-handle.handle-e:hover:after,.canvas-component-card.is-selected .resize-handle.handle-e:after{background:var(--rpt-color-primary, #2563eb)}.resize-handle.handle-s{bottom:0;left:0;height:6px;width:100%;cursor:ns-resize;display:flex;align-items:center;justify-content:center}.resize-handle.handle-s:after{content:\"\";height:3px;width:24px;background:#cbd5e1;border-radius:2px;transition:background .15s ease}.resize-handle.handle-s:hover:after,.canvas-component-card.is-selected .resize-handle.handle-s:after{background:var(--rpt-color-primary, #2563eb)}.resize-handle.handle-se{bottom:0;right:0;width:12px;height:12px;cursor:nwse-resize;display:flex;align-items:flex-end;justify-content:flex-end;padding:2px}.resize-handle.handle-se:after{content:\"\";width:5px;height:5px;border-right:2px solid #cbd5e1;border-bottom:2px solid #cbd5e1;transition:border-color .15s ease}.resize-handle.handle-se:hover:after,.canvas-component-card.is-selected .resize-handle.handle-se:after{border-color:var(--rpt-color-primary, #2563eb)}.resize-dimension-tooltip{position:absolute;top:38px;right:8px;background:#0f172ae6;color:#fff;font-size:.7rem;font-weight:600;padding:3px 8px;border-radius:4px;white-space:nowrap;pointer-events:none;z-index:50;box-shadow:0 2px 4px #0003}.card-header{display:flex;align-items:center;justify-content:space-between;gap:6px;padding:5px 8px;background:var(--rpt-color-surface, #f8fafc);border-bottom:1px solid var(--rpt-color-border, #e2e8f0);border-top-left-radius:var(--rpt-border-radius, 6px);border-top-right-radius:var(--rpt-border-radius, 6px);min-height:32px;box-sizing:border-box;overflow:hidden;position:relative}.canvas-component-card.is-selected .card-header{overflow:visible}.card-title-group{display:flex;align-items:center;gap:5px;cursor:grab;min-width:0;flex:1;overflow:hidden}.drag-handle-dots{color:var(--rpt-color-text-muted, #94a3b8);font-size:.85rem;letter-spacing:-2px;flex-shrink:0;-webkit-user-select:none;user-select:none}.comp-title{font-size:.75rem;font-weight:600;color:var(--rpt-color-text, #0f172a);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;-webkit-user-select:none;user-select:none}.card-actions{display:flex;align-items:center;gap:4px;flex-shrink:0;margin-left:auto}.new-row-pill-static{font-size:.65rem;font-weight:600;color:#7c3aed;background:#f5f3ff;border:1px solid #ddd6fe;padding:1px 5px;border-radius:9999px;white-space:nowrap;line-height:1;flex-shrink:0}.span-pill-static{display:inline-flex;align-items:center;justify-content:center;font-size:.65rem;font-weight:600;color:var(--rpt-color-text-muted, #64748b);background:var(--rpt-color-surface, #f1f5f9);border:1px solid var(--rpt-color-border, #e2e8f0);padding:1px 6px;border-radius:9999px;line-height:1.2;white-space:nowrap;-webkit-user-select:none;user-select:none}.card-quick-controls{display:none;align-items:center;gap:3px;flex-shrink:0}.canvas-component-card:hover .span-pill-static,.canvas-component-card.is-selected .span-pill-static,.canvas-component-card:hover .new-row-pill-static,.canvas-component-card.is-selected .new-row-pill-static{display:none}.canvas-component-card:hover .card-quick-controls,.canvas-component-card.is-selected .card-quick-controls{display:inline-flex}.canvas-component-card.is-narrow .card-header{padding:4px 6px;gap:3px}.canvas-component-card.is-narrow:hover .comp-title,.canvas-component-card.is-narrow.is-selected .comp-title,.canvas-component-card.is-narrow:hover .drag-handle-dots,.canvas-component-card.is-narrow.is-selected .drag-handle-dots{display:none}.canvas-component-card.is-narrow .card-quick-controls{gap:2px}.canvas-component-card.is-narrow .span-control-group{height:18px}.canvas-component-card.is-narrow .span-btn{width:14px;height:16px}.canvas-component-card.is-narrow .span-badge{font-size:.6rem;padding:0 2px}.canvas-component-card.is-narrow .span-total{display:none}.canvas-component-card.is-narrow .card-action-btn{width:18px;height:18px}@container (max-width: 250px){.card-header{padding:4px 6px;gap:3px}.canvas-component-card:hover .comp-title,.canvas-component-card.is-selected .comp-title,.canvas-component-card:hover .drag-handle-dots,.canvas-component-card.is-selected .drag-handle-dots,.span-total{display:none}.card-quick-controls{gap:2px}.card-action-btn{width:18px;height:18px}.span-control-group{height:18px}.span-btn{width:14px;height:16px}.span-badge{font-size:.6rem;padding:0 2px}}@container (max-width: 160px){.canvas-component-card.is-selected .card-quick-controls{position:absolute;top:-30px;right:0;background:#fff;border:1.5px solid var(--rpt-color-primary, #2563eb);border-radius:6px;padding:2px 4px;gap:3px;box-shadow:0 4px 12px #2563eb2e,0 2px 4px #00000014;z-index:120;white-space:nowrap}}@media (max-width: 900px){.canvas-component-card:hover .comp-title,.canvas-component-card.is-selected .comp-title,.canvas-component-card:hover .drag-handle-dots,.canvas-component-card.is-selected .drag-handle-dots,.canvas-component-card .span-total{display:none}.canvas-component-card.is-selected .card-header{overflow:visible}.canvas-component-card.is-selected .card-quick-controls{position:absolute;top:-30px;right:0;background:#fff;border:1.5px solid var(--rpt-color-primary, #2563eb);border-radius:6px;padding:2px 4px;gap:3px;box-shadow:0 4px 12px #2563eb2e,0 2px 4px #00000014;z-index:120;white-space:nowrap}}.span-control-group{display:inline-flex;align-items:center;background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:4px;height:20px;padding:0 1px;white-space:nowrap;flex-shrink:0}.span-btn{width:16px;height:18px;display:inline-flex;align-items:center;justify-content:center;border:none;background:transparent;padding:0;color:var(--rpt-color-text-muted, #64748b);cursor:pointer;border-radius:2px;transition:all .12s ease}.span-btn:hover:not(:disabled){background:var(--rpt-color-surface-hover, #f1f5f9);color:var(--rpt-color-primary, #2563eb)}.span-btn:disabled{opacity:.3;cursor:not-allowed}.span-badge{font-size:.65rem;font-weight:600;color:var(--rpt-color-text, #0f172a);padding:0 3px;white-space:nowrap;-webkit-user-select:none;user-select:none;line-height:1}.span-total{font-size:.6rem;color:var(--rpt-color-text-muted, #64748b);font-weight:500}.card-action-btn{width:20px;height:20px;border:none;background:transparent;border-radius:4px;display:inline-flex;align-items:center;justify-content:center;padding:0;cursor:pointer;color:var(--rpt-color-text-muted, #64748b);transition:all .12s ease;flex-shrink:0}.card-action-btn:hover{background:#e2e8f0;color:var(--rpt-color-text, #0f172a)}.card-action-btn.btn-active-toggle{background:#7c3aed;color:#fff;border-color:#7c3aed}.card-action-btn.btn-active-toggle:hover{background:#6d28d9;color:#fff}.btn-delete:hover{background:#fee2e2;color:#ef4444}.card-body{padding:8px 10px 14px;flex:1;display:flex;flex-direction:column;min-height:0;min-width:0;max-width:100%;box-sizing:border-box;overflow:visible}.mock-input-field{display:flex;flex-direction:column;gap:4px;flex:1;height:100%;min-height:0}.mock-label{font-size:.7rem;font-weight:600;color:var(--rpt-color-text-muted, #475569);flex-shrink:0}.mock-input-box,.mock-select-box{border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:6px;padding:6px 12px;font-size:var(--rpt-font-size-base, .875rem);color:var(--rpt-color-text-muted, #94a3b8);background:#fff;flex:1;min-height:38px;height:100%;display:flex;align-items:center;justify-content:space-between;box-sizing:border-box}.mock-checkbox-field{display:flex;align-items:center;gap:6px;flex:1;height:100%;min-height:0;padding-top:14px}.mock-check-box{width:18px;height:18px;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:.65rem;color:var(--rpt-color-primary, #2563eb);flex-shrink:0}.mock-btn-wrapper{display:flex;flex:1;height:100%;min-height:38px;align-items:center;justify-content:flex-start;width:100%;min-width:0;box-sizing:border-box}.mock-btn{border-radius:var(--rpt-border-radius, 6px);padding:0 16px;height:36px;font-size:var(--rpt-font-size-base, .875rem);font-weight:500;border:1px solid transparent;cursor:pointer;width:auto;max-width:100%;display:inline-flex;align-items:center;justify-content:center;gap:6px;box-sizing:border-box;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:all .15s ease}.mock-btn.mock-btn-full{width:100%}.canvas-component-card.is-narrow .mock-btn{padding:0 6px;font-size:.775rem;width:100%;height:34px}.mock-kpi-field{display:flex;flex-direction:column;justify-content:space-between;flex:1;height:100%;min-height:0}.mock-kpi-header{display:flex;align-items:center;justify-content:space-between}.mock-kpi-value{font-size:1.25rem;font-weight:700;color:var(--rpt-color-text, #0f172a);margin:2px 0}.mock-kpi-footer{display:flex;align-items:center;gap:6px;font-size:.65rem}.mock-kpi-trend{color:#16a34a;font-weight:600}.mock-kpi-sub{color:#64748b}.mock-chart-field{display:flex;flex-direction:column;flex:1;height:100%;min-height:140px}.mock-chart-bars{display:flex;align-items:flex-end;justify-content:space-around;gap:16px;flex:1;min-height:100px;padding-top:12px;box-sizing:border-box}.mock-chart-col{flex:1;display:flex;flex-direction:column;align-items:center;height:100%;min-width:60px;max-width:140px}.mock-chart-bar{width:38px;max-width:44px;margin:0 auto;background:var(--rpt-color-primary, #2563eb);border-radius:6px 6px 0 0;opacity:.85;min-height:10px;transition:height .2s ease}.mock-bar-lbl{font-size:.6875rem;color:#64748b;margin-top:6px;text-align:center;white-space:normal;line-height:1.2}.btn-primary{background:var(--rpt-color-primary, #2563eb);color:#fff}.btn-secondary{background:#f1f5f9;color:#0f172a;border:1px solid #cbd5e1}.btn-outline{background:transparent;color:#2563eb;border:1px solid #2563eb}.btn-danger{background:#ef4444;color:#fff}.mock-generic-box{border:1px dashed #cbd5e1;border-radius:4px;padding:8px;font-size:.75rem;text-align:center;color:#64748b}.canvas-drag-placeholder{background:#2563eb0f;border:2px dashed var(--rpt-color-primary, #2563eb);border-radius:6px;min-height:80px;box-sizing:border-box;transition:transform .15s ease}.canvas-drag-preview,::ng-deep .canvas-drag-preview{background:#fff;border:2px solid var(--rpt-color-primary, #2563eb);border-radius:6px;padding:8px 14px;display:inline-flex;align-items:center;gap:8px;font-family:var(--rpt-font-family, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif)!important;font-size:.8rem;font-weight:600;color:var(--rpt-color-text, #0f172a);box-shadow:0 12px 24px -4px #00000040;z-index:10000}.preview-title,::ng-deep .preview-title{font-family:inherit!important;max-width:160px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.preview-span,::ng-deep .preview-span{font-family:inherit!important;font-size:.65rem;font-weight:600;color:var(--rpt-color-primary, #2563eb);background:#eff6ff;border:1px solid #bfdbfe;padding:1px 6px;border-radius:9999px}.canvas-empty-state{grid-column:1 / -1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:56px 28px;border:2px dashed var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-xl, 12px);background:var(--rpt-color-surface, #ffffff);text-align:center;box-shadow:var(--rpt-box-shadow-xs, 0 1px 2px 0 rgba(0, 0, 0, .04));transition:border-color var(--rpt-transition-speed, .15s) ease}.canvas-empty-state:hover{border-color:var(--rpt-color-primary, #2563eb)}.empty-hero-badge{width:56px;height:56px;border-radius:var(--rpt-border-radius-xl, 12px);background:var(--rpt-color-primary-light, #eff6ff);color:var(--rpt-color-primary, #2563eb);display:flex;align-items:center;justify-content:center;margin-bottom:16px;box-shadow:0 2px 8px #2563eb1f}.empty-hero-title{font-size:var(--rpt-font-size-lg, 1.125rem);font-weight:700;color:var(--rpt-color-text, #0f172a);margin:0 0 8px;letter-spacing:-.01em}.empty-hero-desc{font-size:var(--rpt-font-size-base, .875rem);color:var(--rpt-color-text-muted, #64748b);max-width:520px;line-height:1.5;margin:0 0 24px}.empty-hero-actions{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:10px}.empty-btn{display:inline-flex;align-items:center;gap:7px;padding:8px 16px;font-size:var(--rpt-font-size-sm, .75rem);font-weight:600;border-radius:var(--rpt-border-radius, 6px);cursor:pointer;transition:all var(--rpt-transition-speed, .15s) ease;font-family:inherit}.empty-btn-primary{background:var(--rpt-color-primary, #2563eb);color:#fff;border:1px solid var(--rpt-color-primary, #2563eb);box-shadow:0 1px 3px #2563eb33}.empty-btn-primary:hover{background:var(--rpt-color-primary-hover, #1d4ed8);border-color:var(--rpt-color-primary-hover, #1d4ed8);transform:translateY(-1px)}.empty-btn-secondary{background:#f8fafc;color:#334155;border:1px solid var(--rpt-color-border, #cbd5e1)}.empty-btn-secondary:hover{background:#f1f5f9;color:#0f172a;border-color:#94a3b8;transform:translateY(-1px)}.empty-btn-outline{background:#fff;color:var(--rpt-color-primary, #2563eb);border:1px solid #bfdbfe}.empty-btn-outline:hover{background:#eff6ff;border-color:var(--rpt-color-primary, #2563eb);transform:translateY(-1px)}.canvas-active-input{width:100%;height:32px;padding:4px 8px;font-size:.8125rem;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:4px;outline:none;box-sizing:border-box;background:#fff;color:#0f172a;transition:border-color .15s ease}.canvas-active-input:focus{border-color:var(--rpt-color-primary, #2563eb)}.canvas-active-checkbox-lbl{display:inline-flex;align-items:center;gap:6px;cursor:pointer}.canvas-live-control-wrapper{width:100%;max-width:100%;min-width:0;position:relative;z-index:1;box-sizing:border-box}.canvas-grid-box{margin-top:24px;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:8px;background:#fff;box-shadow:0 1px 3px #0000000d;overflow:hidden}.grid-preview-header{display:flex;align-items:center;justify-content:space-between;padding:10px 16px;background:var(--rpt-color-surface, #f8fafc);border-bottom:1px solid var(--rpt-color-border, #cbd5e1);font-size:.8rem;font-weight:600;color:var(--rpt-color-text, #0f172a)}.grid-title{font-weight:700}.grid-exec-badge{font-size:.6875rem;font-weight:600;padding:2px 8px;border-radius:10px}.grid-exec-badge.is-loading{background:#eff6ff;color:#2563eb}.grid-exec-badge.is-error{background:#fee2e2;color:#ef4444}.grid-exec-badge.is-success{background:#ecfdf5;color:#059669}.btn-canvas-run-grid{display:inline-flex;align-items:center;gap:5px;padding:4px 10px;font-size:.72rem;font-weight:600;color:#fff;background:var(--rpt-color-primary, #2563eb);border:none;border-radius:4px;cursor:pointer;transition:background .15s ease}.btn-canvas-run-grid:hover:not(:disabled){background:#1d4ed8}.btn-canvas-run-grid:disabled{opacity:.6;cursor:not-allowed}.btn-canvas-edit-grid{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;font-size:.72rem;font-weight:500;color:#334155;background:#fff;border:1px solid #cbd5e1;border-radius:4px;cursor:pointer;transition:all .12s ease}.btn-canvas-edit-grid:hover{background:#f1f5f9}.canvas-live-grid-container{width:100%;min-height:480px;background:#fff}.live-mode-badge{display:inline-flex;align-items:center;gap:4px;font-size:.6875rem;font-weight:700;color:#047857;background:#ecfdf5;border:1px solid #a7f3d0;padding:1px 8px;border-radius:9999px}.canvas-component-card.is-field-highlighted{border-color:#f59e0b!important;box-shadow:0 0 0 3px #f59e0b59!important;animation:rpt-pulse-border 1s ease-in-out infinite alternate}@keyframes rpt-pulse-border{0%{box-shadow:0 0 0 2px #f59e0b40}to{box-shadow:0 0 0 5px #f59e0b80}}.grid-layout-status-strip{display:flex;align-items:center;justify-content:space-between;padding:6px 16px;background:#f1f5f9;border-bottom:1px solid var(--rpt-color-border, #cbd5e1);font-size:.6875rem;color:#475569}.grid-status-info{display:inline-flex;align-items:center;gap:6px;font-weight:600}.grid-status-sync{display:inline-flex;align-items:center;gap:4px;color:#059669;font-weight:600;transition:transform .2s ease}.grid-status-sync.is-pulsing{transform:scale(1.08);color:#047857}.canvas-actions{display:flex;align-items:center;gap:8px}.btn-grid-add{background:#f0fdf4;border-color:#bbf7d0;color:#16a34a}.btn-grid-add:hover{background:#dcfce7;border-color:#86efac;color:#15803d}.btn-remove-grid{display:inline-flex;align-items:center;justify-content:center;padding:4px;border:none;background:transparent;color:#94a3b8;border-radius:4px;cursor:pointer;transition:all .15s ease}.btn-remove-grid:hover{background:#fee2e2;color:#ef4444}.canvas-add-grid-banner{margin-top:24px;border:2px dashed #cbd5e1;border-radius:8px;background:#fff;padding:16px 20px;display:flex;align-items:center;gap:16px;cursor:pointer;transition:all .15s ease}.canvas-add-grid-banner:hover{border-color:#2563eb;background:#f8fafc;box-shadow:0 2px 8px #2563eb14}.add-grid-icon{width:40px;height:40px;border-radius:8px;background:#eff6ff;color:#2563eb;display:flex;align-items:center;justify-content:center;flex-shrink:0}.add-grid-text{display:flex;flex-direction:column;gap:2px;flex:1}.add-grid-title{font-size:.875rem;font-weight:600;color:#0f172a}.add-grid-sub{font-size:.75rem;color:#64748b}.btn-banner-add{padding:6px 14px;font-size:.75rem;font-weight:600;color:#2563eb;background:#eff6ff;border:1px solid #bfdbfe;border-radius:6px;cursor:pointer;white-space:nowrap;transition:all .15s ease}.canvas-add-grid-banner:hover .btn-banner-add{background:#2563eb;color:#fff;border-color:#2563eb}.canvas-grid-loading-overlay{position:absolute;inset:0;background:#ffffffe6;backdrop-filter:blur(2px);z-index:10;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;font-family:inherit;border-radius:6px}.grid-loading-title{font-weight:600;font-size:.95rem;color:#1e293b}.grid-loading-sub{font-size:.8rem;color:#64748b}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button,.canvas-active-input::-webkit-inner-spin-button,.canvas-active-input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}input[type=number],.canvas-active-input{-moz-appearance:textfield;appearance:textfield}\n"] }]
|
|
14116
14285
|
}], ctorParameters: () => [], propDecorators: { onEscape: [{
|
|
14117
14286
|
type: HostListener,
|
|
14118
14287
|
args: ['document:keydown.escape']
|
|
@@ -14795,6 +14964,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
14795
14964
|
|
|
14796
14965
|
class ParameterBindingEditorComponent {
|
|
14797
14966
|
sessionService = inject(ReportSessionContextService);
|
|
14967
|
+
reportingConfig = inject(REPORTING_CONFIG, { optional: true });
|
|
14798
14968
|
param = input.required();
|
|
14799
14969
|
currentValue = input('');
|
|
14800
14970
|
allComponents = input([]);
|
|
@@ -14803,7 +14973,11 @@ class ParameterBindingEditorComponent {
|
|
|
14803
14973
|
valueChange = output();
|
|
14804
14974
|
linkedFieldChange = output();
|
|
14805
14975
|
internalMode = signal(null);
|
|
14806
|
-
sessionMatch = computed(() => this.sessionService.matchParameter(this.param().name));
|
|
14976
|
+
sessionMatch = computed(() => this.reportingConfig?.autoParameter === false ? null : this.sessionService.matchParameter(this.param().name));
|
|
14977
|
+
unavailableSessionBinding = computed(() => {
|
|
14978
|
+
const token = parseParameterToken(String(this.currentValue() ?? ''));
|
|
14979
|
+
return token?.field.startsWith('session.') && getFieldCaseInsensitive(this.sessionService.getCurrentSessionContext?.() ?? {}, token.field.slice(8)) == null;
|
|
14980
|
+
});
|
|
14807
14981
|
friendlyLabel = computed(() => {
|
|
14808
14982
|
return this.param().friendlyLabel || humanizeIdentifier(this.param().name);
|
|
14809
14983
|
});
|
|
@@ -14868,8 +15042,27 @@ class ParameterBindingEditorComponent {
|
|
|
14868
15042
|
return [];
|
|
14869
15043
|
});
|
|
14870
15044
|
compatibleControls = computed(() => {
|
|
14871
|
-
return this.allComponents().filter(
|
|
15045
|
+
return this.allComponents().filter(c => c.id !== this.currentComponentId() && !!c.key && ['input', 'number', 'date', 'date-range', 'dropdown', 'radio', 'checkbox'].includes(c.type))
|
|
15046
|
+
.flatMap(c => c.type === 'date-range'
|
|
15047
|
+
? ['from', 'to'].map(part => ({ ...c, key: (c.key || c.id) + '.' + part, label: (c.label || c.key || c.id) + (part === 'from' ? ' ? From' : ' ? To') }))
|
|
15048
|
+
: [c]);
|
|
14872
15049
|
});
|
|
15050
|
+
selectedDropdownField = computed(() => this.compatibleControls().find(c => (c.key || c.id) === this.selectedControlKey() && c.type === 'dropdown'));
|
|
15051
|
+
dropdownExample = computed(() => {
|
|
15052
|
+
const options = this.selectedDropdownField()?.properties?.['options'];
|
|
15053
|
+
if (!Array.isArray(options))
|
|
15054
|
+
return null;
|
|
15055
|
+
const first = options.find(option => option && typeof option === 'object' && 'key' in option);
|
|
15056
|
+
return first ? { label: String(first.label ?? first.key), value: String(first.key) } : null;
|
|
15057
|
+
});
|
|
15058
|
+
dateFormats = PARAMETER_DATE_FORMATS;
|
|
15059
|
+
selectedDateField = computed(() => this.compatibleControls().find(c => (c.key || c.id) === this.selectedControlKey() && (c.type === 'date' || c.type === 'date-range')));
|
|
15060
|
+
selectedDateFormat = computed(() => parseParameterToken(String(this.currentValue()))?.dateFormat ?? 'yyyy-MM-dd');
|
|
15061
|
+
onDateFormatSelected(format) {
|
|
15062
|
+
if (!PARAMETER_DATE_FORMATS.includes(format) || !this.selectedDateField())
|
|
15063
|
+
return;
|
|
15064
|
+
this.valueChange.emit('{{' + this.selectedControlKey() + ' | date:' + format + '}}');
|
|
15065
|
+
}
|
|
14873
15066
|
hasValue = computed(() => {
|
|
14874
15067
|
const v = this.currentValue();
|
|
14875
15068
|
return v !== undefined && v !== null && v !== '';
|
|
@@ -14890,7 +15083,7 @@ class ParameterBindingEditorComponent {
|
|
|
14890
15083
|
return 'session';
|
|
14891
15084
|
}
|
|
14892
15085
|
if (val.startsWith('{{') && val.endsWith('}}')) {
|
|
14893
|
-
const inner = val
|
|
15086
|
+
const inner = parseParameterToken(val)?.field;
|
|
14894
15087
|
const match = this.compatibleControls().find((c) => (c.key || c.id) === inner);
|
|
14895
15088
|
if (match) {
|
|
14896
15089
|
return 'control';
|
|
@@ -14922,7 +15115,7 @@ class ParameterBindingEditorComponent {
|
|
|
14922
15115
|
selectedControlKey = computed(() => {
|
|
14923
15116
|
const val = String(this.currentValue() ?? '');
|
|
14924
15117
|
if (val.startsWith('{{') && val.endsWith('}}')) {
|
|
14925
|
-
return val
|
|
15118
|
+
return parseParameterToken(val)?.field ?? '';
|
|
14926
15119
|
}
|
|
14927
15120
|
return val;
|
|
14928
15121
|
});
|
|
@@ -14985,7 +15178,9 @@ class ParameterBindingEditorComponent {
|
|
|
14985
15178
|
}
|
|
14986
15179
|
onControlSelected(key) {
|
|
14987
15180
|
if (key) {
|
|
14988
|
-
const
|
|
15181
|
+
const field = this.compatibleControls().find(c => (c.key || c.id) === key);
|
|
15182
|
+
const isDate = field?.type === 'date' || field?.type === 'date-range';
|
|
15183
|
+
const expr = isDate ? '{{' + key + ' | date:yyyy-MM-dd}}' : '{{' + key + '}}';
|
|
14989
15184
|
this.valueChange.emit(expr);
|
|
14990
15185
|
this.linkedFieldChange.emit({
|
|
14991
15186
|
paramName: this.param().name,
|
|
@@ -15108,6 +15303,9 @@ class ParameterBindingEditorComponent {
|
|
|
15108
15303
|
|
|
15109
15304
|
<!-- Mode-specific Input Controls -->
|
|
15110
15305
|
<div class="param-body">
|
|
15306
|
+
@if (unavailableSessionBinding()) {
|
|
15307
|
+
<div class="param-desc" role="status">This session value is unavailable and will not be sent. Configure a session source or choose Field / Fixed.</div>
|
|
15308
|
+
}
|
|
15111
15309
|
<!-- 0. AUTO SESSION DERIVED -->
|
|
15112
15310
|
@if (activeMode() === 'session' && sessionMatch()) {
|
|
15113
15311
|
<div class="session-box">
|
|
@@ -15150,6 +15348,32 @@ class ParameterBindingEditorComponent {
|
|
|
15150
15348
|
}
|
|
15151
15349
|
</select>
|
|
15152
15350
|
</div>
|
|
15351
|
+
@if (selectedDropdownField(); as field) {
|
|
15352
|
+
<div class="param-desc" role="note">
|
|
15353
|
+
Sends the selected option's <strong>{{ field.dataSource?.valueField || 'key' }}</strong> value.
|
|
15354
|
+
@if (field.dataSource?.displayField) {
|
|
15355
|
+
<span>{{ field.dataSource?.displayField }} is the display label.</span>
|
|
15356
|
+
}
|
|
15357
|
+
@if (field.dataSource?.valueField || field.dataSource?.entityName || field.dataSource?.apiEndpoint || field.dataSource?.masterName) {
|
|
15358
|
+
<span>Choose Value Field in the dropdown settings to change what is sent.</span>
|
|
15359
|
+
} @else {
|
|
15360
|
+
<span>Edit option keys in the dropdown settings to change what is sent.</span>
|
|
15361
|
+
}
|
|
15362
|
+
</div>
|
|
15363
|
+
@if (dropdownExample(); as example) {
|
|
15364
|
+
<div class="param-desc">Example: {{ example.label }} ? {{ example.value }}</div>
|
|
15365
|
+
}
|
|
15366
|
+
}
|
|
15367
|
+
@if (selectedDateField()) {
|
|
15368
|
+
<label class="param-desc" [for]="'date-format-' + currentComponentId() + '-' + param().name">Execution date format</label>
|
|
15369
|
+
<select class="field-select" [id]="'date-format-' + currentComponentId() + '-' + param().name"
|
|
15370
|
+
[ngModel]="selectedDateFormat()" (ngModelChange)="onDateFormatSelected($event)">
|
|
15371
|
+
@for (format of dateFormats; track format) {
|
|
15372
|
+
<option [value]="format">{{ format }}{{ format === 'yyyyMMdd' ? ' (e.g. 20261104)' : '' }}</option>
|
|
15373
|
+
}
|
|
15374
|
+
</select>
|
|
15375
|
+
<div class="param-desc">Applied when executing; the date picker keeps its normal display.</div>
|
|
15376
|
+
}
|
|
15153
15377
|
@if (!selectedControlKey() && compatibleControls().length === 0) {
|
|
15154
15378
|
<div class="empty-hint">No input fields on canvas. Add a dropdown, date picker, or input first.</div>
|
|
15155
15379
|
}
|
|
@@ -15355,6 +15579,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
15355
15579
|
|
|
15356
15580
|
<!-- Mode-specific Input Controls -->
|
|
15357
15581
|
<div class="param-body">
|
|
15582
|
+
@if (unavailableSessionBinding()) {
|
|
15583
|
+
<div class="param-desc" role="status">This session value is unavailable and will not be sent. Configure a session source or choose Field / Fixed.</div>
|
|
15584
|
+
}
|
|
15358
15585
|
<!-- 0. AUTO SESSION DERIVED -->
|
|
15359
15586
|
@if (activeMode() === 'session' && sessionMatch()) {
|
|
15360
15587
|
<div class="session-box">
|
|
@@ -15397,6 +15624,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
15397
15624
|
}
|
|
15398
15625
|
</select>
|
|
15399
15626
|
</div>
|
|
15627
|
+
@if (selectedDropdownField(); as field) {
|
|
15628
|
+
<div class="param-desc" role="note">
|
|
15629
|
+
Sends the selected option's <strong>{{ field.dataSource?.valueField || 'key' }}</strong> value.
|
|
15630
|
+
@if (field.dataSource?.displayField) {
|
|
15631
|
+
<span>{{ field.dataSource?.displayField }} is the display label.</span>
|
|
15632
|
+
}
|
|
15633
|
+
@if (field.dataSource?.valueField || field.dataSource?.entityName || field.dataSource?.apiEndpoint || field.dataSource?.masterName) {
|
|
15634
|
+
<span>Choose Value Field in the dropdown settings to change what is sent.</span>
|
|
15635
|
+
} @else {
|
|
15636
|
+
<span>Edit option keys in the dropdown settings to change what is sent.</span>
|
|
15637
|
+
}
|
|
15638
|
+
</div>
|
|
15639
|
+
@if (dropdownExample(); as example) {
|
|
15640
|
+
<div class="param-desc">Example: {{ example.label }} ? {{ example.value }}</div>
|
|
15641
|
+
}
|
|
15642
|
+
}
|
|
15643
|
+
@if (selectedDateField()) {
|
|
15644
|
+
<label class="param-desc" [for]="'date-format-' + currentComponentId() + '-' + param().name">Execution date format</label>
|
|
15645
|
+
<select class="field-select" [id]="'date-format-' + currentComponentId() + '-' + param().name"
|
|
15646
|
+
[ngModel]="selectedDateFormat()" (ngModelChange)="onDateFormatSelected($event)">
|
|
15647
|
+
@for (format of dateFormats; track format) {
|
|
15648
|
+
<option [value]="format">{{ format }}{{ format === 'yyyyMMdd' ? ' (e.g. 20261104)' : '' }}</option>
|
|
15649
|
+
}
|
|
15650
|
+
</select>
|
|
15651
|
+
<div class="param-desc">Applied when executing; the date picker keeps its normal display.</div>
|
|
15652
|
+
}
|
|
15400
15653
|
@if (!selectedControlKey() && compatibleControls().length === 0) {
|
|
15401
15654
|
<div class="empty-hint">No input fields on canvas. Add a dropdown, date picker, or input first.</div>
|
|
15402
15655
|
}
|
|
@@ -15632,13 +15885,10 @@ class PropertyEditorGridComponent {
|
|
|
15632
15885
|
if (parsed.procedureName && parsed.procedureName.toLowerCase() !== this.activeEntityName().toLowerCase()) {
|
|
15633
15886
|
this.onSelectEntity(parsed.procedureName);
|
|
15634
15887
|
}
|
|
15635
|
-
|
|
15888
|
+
let currentMappings = { ...(this.dataSource()?.parameterMappings || {}) };
|
|
15636
15889
|
let count = 0;
|
|
15637
15890
|
for (const [key, val] of Object.entries(parsed.parameters)) {
|
|
15638
|
-
|
|
15639
|
-
currentMappings[key] = val;
|
|
15640
|
-
currentMappings[cleanKey] = val;
|
|
15641
|
-
currentMappings[`@${cleanKey}`] = val;
|
|
15891
|
+
currentMappings = setParameterMapping(currentMappings, key, val);
|
|
15642
15892
|
count++;
|
|
15643
15893
|
}
|
|
15644
15894
|
this.updateDataSourceParameters.emit(currentMappings);
|
|
@@ -15769,19 +16019,14 @@ class PropertyEditorGridComponent {
|
|
|
15769
16019
|
}
|
|
15770
16020
|
getParameterValue(paramName) {
|
|
15771
16021
|
const mappings = this.dataSource()?.parameterMappings || {};
|
|
15772
|
-
const
|
|
15773
|
-
const val = mappings[paramName] ?? mappings[`@${cleanName}`] ?? mappings[cleanName];
|
|
16022
|
+
const val = getFieldCaseInsensitive(mappings, paramName);
|
|
15774
16023
|
if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean') {
|
|
15775
16024
|
return val;
|
|
15776
16025
|
}
|
|
15777
16026
|
return '';
|
|
15778
16027
|
}
|
|
15779
16028
|
onParameterChange(paramName, value) {
|
|
15780
|
-
const current =
|
|
15781
|
-
const cleanName = paramName.replace(/^@/, '');
|
|
15782
|
-
current[paramName] = value;
|
|
15783
|
-
current[`@${cleanName}`] = value;
|
|
15784
|
-
current[cleanName] = value;
|
|
16029
|
+
const current = setParameterMapping(this.dataSource()?.parameterMappings, paramName, value);
|
|
15785
16030
|
this.updateDataSourceParameters.emit(current);
|
|
15786
16031
|
}
|
|
15787
16032
|
onUpdateCol(index, field, value) {
|
|
@@ -15856,7 +16101,7 @@ class PropertyEditorGridComponent {
|
|
|
15856
16101
|
this.updateRowClickAction.emit({ ...current, parameterMapping: nextMapping });
|
|
15857
16102
|
}
|
|
15858
16103
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PropertyEditorGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
15859
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: PropertyEditorGridComponent, isStandalone: true, selector: "rpt-property-editor-grid", inputs: { grid: { classPropertyName: "grid", publicName: "grid", isSignal: true, isRequired: false, transformFunction: null }, dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: false, transformFunction: null }, availableEntities: { classPropertyName: "availableEntities", publicName: "availableEntities", isSignal: true, isRequired: false, transformFunction: null }, procedureParameters: { classPropertyName: "procedureParameters", publicName: "procedureParameters", isSignal: true, isRequired: false, transformFunction: null }, allComponents: { classPropertyName: "allComponents", publicName: "allComponents", isSignal: true, isRequired: false, transformFunction: null }, isDetecting: { classPropertyName: "isDetecting", publicName: "isDetecting", isSignal: true, isRequired: false, transformFunction: null }, statusMessage: { classPropertyName: "statusMessage", publicName: "statusMessage", isSignal: true, isRequired: false, transformFunction: null }, statusKind: { classPropertyName: "statusKind", publicName: "statusKind", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { addGrid: "addGrid", removeGrid: "removeGrid", addColumn: "addColumn", autoDetectColumns: "autoDetectColumns", removeColumn: "removeColumn", updateColumn: "updateColumn", updateOption: "updateOption", updateRowClickAction: "updateRowClickAction", setDataSource: "setDataSource", updateDataSourceParameters: "updateDataSourceParameters", entityChanged: "entityChanged" }, ngImport: i0, template: " @if (!grid()) {\n <div class=\"empty-grid-card\">\n <div class=\"empty-grid-icon\">\n <rpt-icon name=\"table\" [size]=\"28\" />\n </div>\n <h4 class=\"empty-grid-title\">No Result Grid Attached</h4>\n <p class=\"empty-grid-desc\">Attach an interactive Result Data Grid (or UltraGrid) to this report definition to render queries and stored procedure results.</p>\n <button type=\"button\" class=\"btn-create-grid-action\" (click)=\"addGrid.emit()\">\n <rpt-icon name=\"plus\" [size]=\"14\" />\n <span>+ Add Result Grid</span>\n </button>\n </div>\n } @else {\n <div class=\"props-form\">\n <!-- 1. Report Data Source & Stored Procedure Configuration -->\n <div class=\"property-group\">\n <div class=\"group-header-row\">\n <div class=\"group-title-wrap\">\n <span class=\"group-title\">Report Data Source</span>\n @if (activeEntityName()) {\n <span class=\"group-badge active-ds-badge\">{{ activeEntityName() }}</span>\n }\n </div>\n </div>\n\n <!-- Source Type Switcher (Stored Procedure vs Table vs REST API) -->\n <div class=\"source-type-switcher\">\n <button\n type=\"button\"\n class=\"source-tab-btn\"\n [class.active]=\"currentSourceType() === 'stored_procedure'\"\n (click)=\"onSourceTypeChange('stored_procedure')\"\n >\n <rpt-icon name=\"code\" [size]=\"12\" />\n <span>Stored Procedure</span>\n </button>\n <button\n type=\"button\"\n class=\"source-tab-btn\"\n [class.active]=\"currentSourceType() === 'table'\"\n (click)=\"onSourceTypeChange('table')\"\n >\n <rpt-icon name=\"table\" [size]=\"12\" />\n <span>Table / View</span>\n </button>\n <button\n type=\"button\"\n class=\"source-tab-btn\"\n [class.active]=\"currentSourceType() === 'api'\"\n (click)=\"onSourceTypeChange('api')\"\n >\n <rpt-icon name=\"globe\" [size]=\"12\" />\n <span>REST API</span>\n </button>\n </div>\n\n @if (currentSourceType() === 'api') {\n <!-- API Endpoint Selector / Input -->\n <div class=\"field-row\">\n <label class=\"field-label\">API Endpoint URL</label>\n @if (registeredApis().length > 0) {\n <div style=\"margin-bottom: 6px;\">\n <rpt-searchable-select\n [options]=\"registeredApis()\"\n [ngModel]=\"apiEndpoint()\"\n (ngModelChange)=\"onApiEndpointChange($event)\"\n placeholder=\"-- Select Registered Endpoint or type below --\"\n searchPlaceholder=\"Search registered APIs...\"\n />\n </div>\n }\n <input\n type=\"text\"\n class=\"form-control\"\n placeholder=\"e.g. /api/v1/orders or https://api.service.com/data\"\n [ngModel]=\"apiEndpoint()\"\n (ngModelChange)=\"onApiEndpointChange($event)\"\n />\n <span class=\"field-hint\" style=\"font-size: 11px; color: var(--rpt-color-text-muted, #64748b); margin-top: 4px; display: block;\">\n Direct HTTP call or backend proxy. Use <code>:param</code> for route tokens.\n </span>\n </div>\n\n <!-- HTTP Method & Data Path Grid -->\n <div class=\"field-grid-2\" style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 8px;\">\n <div class=\"field-row\">\n <label class=\"field-label\">HTTP Method</label>\n <select\n class=\"form-control\"\n [ngModel]=\"apiMethod()\"\n (ngModelChange)=\"onApiMethodChange($event)\"\n >\n <option value=\"GET\">GET (Query String)</option>\n <option value=\"POST\">POST (Body Payload)</option>\n </select>\n </div>\n <div class=\"field-row\">\n <label class=\"field-label\">Response Data Path</label>\n <input\n type=\"text\"\n class=\"form-control\"\n placeholder=\"e.g. data.items, rows\"\n [ngModel]=\"apiDataPath()\"\n (ngModelChange)=\"onApiDataPathChange($event)\"\n />\n </div>\n </div>\n\n <!-- Use Proxy Toggle -->\n <div class=\"field-row checkbox-field-row\" style=\"margin-top: 4px;\">\n <label class=\"checkbox-label\" style=\"display: flex; align-items: center; gap: 6px; font-size: 12px; cursor: pointer;\">\n <input\n type=\"checkbox\"\n [checked]=\"apiUseProxy()\"\n (change)=\"onApiUseProxyChange($any($event.target).checked)\"\n />\n <span class=\"checkbox-text\">Route through backend proxy (bypasses CORS / hides credentials)</span>\n </label>\n </div>\n } @else {\n <!-- Entity Selector Dropdown -->\n <div class=\"field-row\">\n <label class=\"field-label\">\n {{ currentSourceType() === 'stored_procedure' ? 'Stored Procedure' : 'Table / View' }}\n </label>\n <rpt-searchable-select\n [options]=\"gridEntitySelectOptions()\"\n [ngModel]=\"activeEntityName()\"\n (ngModelChange)=\"onSelectEntity($event)\"\n placeholder=\"-- Choose Data Source --\"\n [searchPlaceholder]=\"currentSourceType() === 'stored_procedure' ? 'Search procedures...' : 'Search tables & views...'\"\n />\n </div>\n }\n\n <!-- Stored Procedure Parameter Mappings -->\n @if (currentSourceType() === 'stored_procedure' && activeEntityName()) {\n <div class=\"procedure-params-section\">\n <div class=\"params-section-header\">\n <div class=\"params-header-left\">\n <span class=\"params-title\">Procedure Parameters</span>\n <span class=\"params-count-badge\">{{ effectiveParameters().length }}</span>\n </div>\n <button\n type=\"button\"\n class=\"btn-paste-exec\"\n (click)=\"toggleExecSnippet()\"\n title=\"Paste full SSMS 'EXEC proc @param=val' snippet to fill parameters automatically\"\n >\n <rpt-icon name=\"code\" [size]=\"12\" />\n <span>{{ isExecSnippetOpen() ? 'Close SQL' : 'Paste SQL Exec' }}</span>\n </button>\n </div>\n\n @if (isExecSnippetOpen()) {\n <div class=\"exec-snippet-panel\">\n <div class=\"exec-snippet-header\">\n <span class=\"snippet-hint\">Paste full <code>EXEC ProcedureName @param=val...</code> statement from SSMS:</span>\n </div>\n <textarea\n class=\"exec-snippet-textarea\"\n rows=\"3\"\n placeholder=\"e.g. exec Get_HelpList @chrCOMPANYID=N'101',@chrTableName=N'MSTACCTGLSUB'...\"\n [ngModel]=\"execSnippetText()\"\n (ngModelChange)=\"execSnippetText.set($event)\"\n ></textarea>\n <div class=\"exec-snippet-actions\">\n <button type=\"button\" class=\"btn-snippet-apply\" (click)=\"applyExecSnippet()\">\n <rpt-icon name=\"check\" [size]=\"12\" />\n <span>Apply Parameters</span>\n </button>\n <button type=\"button\" class=\"btn-snippet-cancel\" (click)=\"toggleExecSnippet()\">\n Cancel\n </button>\n </div>\n @if (execParseStatus()) {\n <div class=\"snippet-status\" [class.is-error]=\"execParseStatus()?.kind === 'error'\" [class.is-success]=\"execParseStatus()?.kind === 'success'\">\n {{ execParseStatus()?.message }}\n </div>\n }\n </div>\n }\n\n @if (effectiveParameters().length > 0) {\n <div class=\"param-cards-list\">\n @for (param of effectiveParameters(); track param.name) {\n <rpt-parameter-binding-editor\n [param]=\"param\"\n [currentValue]=\"getParameterValue(param.name)\"\n [allComponents]=\"allComponents()\"\n (valueChange)=\"onParameterChange(param.name, $event)\"\n />\n }\n </div>\n } @else {\n <div class=\"empty-params-hint\">\n <span>No required parameters for this procedure.</span>\n </div>\n }\n </div>\n }\n </div>\n\n <!-- 2. Result Columns Configuration -->\n <div class=\"property-group\">\n <div class=\"group-header-row\">\n <div class=\"group-title-wrap\">\n <span class=\"group-title\">Columns</span>\n <span class=\"group-badge\">{{ columns().length }}</span>\n </div>\n </div>\n\n <!-- Dedicated Full-Width Action Buttons (No cramping) -->\n <div class=\"columns-action-bar\">\n <button\n type=\"button\"\n class=\"btn-col-action btn-auto-detect\"\n (click)=\"autoDetectColumns.emit()\"\n [disabled]=\"isDetecting()\"\n title=\"Auto-detect all columns from procedure or data table\"\n >\n <rpt-icon name=\"refresh\" [size]=\"12\" [class.rpt-spin]=\"isDetecting()\" />\n <span>{{ isDetecting() ? 'Detecting...' : 'Auto-Detect' }}</span>\n </button>\n <button\n type=\"button\"\n class=\"btn-col-action btn-add-col\"\n (click)=\"addColumn.emit()\"\n title=\"Add a new column manually\"\n >\n <rpt-icon name=\"plus\" [size]=\"12\" />\n <span>+ Add Column</span>\n </button>\n </div>\n\n @if (statusMessage()) {\n <div class=\"col-status-banner\" [class.is-error]=\"statusKind() === 'error'\" [class.is-success]=\"statusKind() === 'success'\">\n <rpt-icon [name]=\"statusKind() === 'error' ? 'close' : 'check'\" [size]=\"12\" />\n <span>{{ statusMessage() }}</span>\n </div>\n }\n\n <div class=\"columns-editor-list\">\n @for (col of columns(); track col.field; let idx = $index) {\n <div class=\"col-edit-card\">\n <div class=\"col-card-header\">\n <div class=\"col-header-title-group\">\n <span class=\"col-drag-dots\">\u22EE\u22EE</span>\n <span class=\"col-idx-badge\">#{{ idx + 1 }}</span>\n <input\n type=\"text\"\n class=\"col-title-input\"\n placeholder=\"Column Label\"\n [ngModel]=\"col.label\"\n (ngModelChange)=\"onUpdateCol(idx, 'label', $event)\"\n />\n </div>\n <div class=\"col-card-actions\">\n <span class=\"col-type-pill\">{{ col.dataType || 'string' }}</span>\n <button\n type=\"button\"\n class=\"col-action-btn btn-delete\"\n (click)=\"removeColumn.emit(idx)\"\n title=\"Remove Column\"\n >\n <rpt-icon name=\"trash\" [size]=\"12\" />\n </button>\n </div>\n </div>\n\n <div class=\"col-card-body\">\n <div class=\"col-edit-fields\">\n <div class=\"col-field-item\">\n <span class=\"mini-label\">Field</span>\n <input\n type=\"text\"\n class=\"col-mini-input\"\n placeholder=\"field\"\n [ngModel]=\"col.field\"\n (ngModelChange)=\"onUpdateCol(idx, 'field', $event)\"\n />\n </div>\n <div class=\"col-field-item\">\n <span class=\"mini-label\">Type</span>\n <select\n class=\"col-mini-select\"\n [ngModel]=\"col.dataType || 'string'\"\n (ngModelChange)=\"onUpdateCol(idx, 'dataType', $event)\"\n >\n <option value=\"string\">String</option>\n <option value=\"number\">Number</option>\n <option value=\"currency\">Currency</option>\n <option value=\"date\">Date</option>\n <option value=\"boolean\">Boolean</option>\n </select>\n </div>\n <div class=\"col-field-item\">\n <span class=\"mini-label\">Format</span>\n <input\n type=\"text\"\n class=\"col-mini-input\"\n placeholder=\"e.g. dd/MM/yyyy\"\n [ngModel]=\"col.format\"\n (ngModelChange)=\"onUpdateCol(idx, 'format', $event)\"\n />\n </div>\n <div class=\"col-field-item\">\n <span class=\"mini-label\">Align</span>\n <select\n class=\"col-mini-select\"\n [ngModel]=\"col.align || 'left'\"\n (ngModelChange)=\"onUpdateCol(idx, 'align', $event)\"\n >\n <option value=\"left\">Left</option>\n <option value=\"center\">Center</option>\n <option value=\"right\">Right</option>\n </select>\n </div>\n <div class=\"col-field-item\">\n <span class=\"mini-label\">Aggregate</span>\n <select\n class=\"col-mini-select\"\n [ngModel]=\"col.aggregate || 'none'\"\n (ngModelChange)=\"onUpdateCol(idx, 'aggregate', $event === 'none' ? undefined : $event)\"\n >\n <option value=\"none\">None</option>\n <option value=\"sum\">Sum</option>\n <option value=\"avg\">Average</option>\n <option value=\"count\">Count</option>\n <option value=\"min\">Min</option>\n <option value=\"max\">Max</option>\n </select>\n </div>\n <div class=\"col-field-item\">\n <span class=\"mini-label\">Width</span>\n <input\n type=\"text\"\n class=\"col-mini-input\"\n placeholder=\"auto\"\n [ngModel]=\"col.width || ''\"\n (ngModelChange)=\"onUpdateCol(idx, 'width', $event || undefined)\"\n />\n </div>\n </div>\n\n <div class=\"col-card-footer\">\n <label class=\"col-checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"col.visible !== false\"\n (ngModelChange)=\"onUpdateCol(idx, 'visible', $event)\"\n />\n <span>Visible</span>\n </label>\n <label class=\"col-checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"col.checkboxSelection ?? false\"\n (ngModelChange)=\"onUpdateCol(idx, 'checkboxSelection', $event)\"\n />\n <span>Checkbox</span>\n </label>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n\n <!-- Grid Table Options Group -->\n <div class=\"property-group\">\n <div class=\"group-title\">Table Options</div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.sideBar !== false\"\n (ngModelChange)=\"updateOption.emit({ field: 'sideBar', value: $event })\"\n />\n <span>Enable Side Panel (Tool Panels)</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.globalSearch !== false\"\n (ngModelChange)=\"updateOption.emit({ field: 'globalSearch', value: $event })\"\n />\n <span>Global Search Box</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.statusBar !== false\"\n (ngModelChange)=\"updateOption.emit({ field: 'statusBar', value: $event })\"\n />\n <span>Status Bar</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.pagination ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'pagination', value: $event })\"\n />\n <span>Enable Pagination</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.selectable ?? false\"\n (ngModelChange)=\"updateOption.emit({ field: 'selectable', value: $event })\"\n />\n <span>Row Selection</span>\n </label>\n </div>\n\n @if (grid()?.options?.selectable) {\n <div class=\"field-row\">\n <label class=\"field-label\">Selection Mode</label>\n <select\n class=\"field-input\"\n [ngModel]=\"grid()?.options?.selectionMode ?? 'multiple'\"\n (ngModelChange)=\"updateOption.emit({ field: 'selectionMode', value: $event })\"\n >\n <option value=\"multiple\">Multiple Rows</option>\n <option value=\"single\">Single Row</option>\n </select>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.checkboxSelection ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'checkboxSelection', value: $event })\"\n />\n <span>Checkbox Selection</span>\n </label>\n </div>\n\n @if ((grid()?.options?.selectionMode ?? 'multiple') === 'multiple') {\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.headerCheckboxSelection ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'headerCheckboxSelection', value: $event })\"\n />\n <span>Header Select All Checkbox</span>\n </label>\n </div>\n }\n }\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.allowColumnReorder ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'allowColumnReorder', value: $event })\"\n />\n <span>Allow Column Reorder</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.allowExport ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'allowExport', value: $event })\"\n />\n <span>Allow Export</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"field-label\">Page Size</label>\n <input\n type=\"number\"\n class=\"field-input\"\n min=\"1\"\n max=\"1000\"\n (wheel)=\"$any($event.target).blur()\"\n (keydown.arrowup)=\"$event.preventDefault()\"\n (keydown.arrowdown)=\"$event.preventDefault()\"\n [ngModel]=\"grid()?.options?.pageSize ?? 10\"\n (ngModelChange)=\"updateOption.emit({ field: 'pageSize', value: +$event })\"\n />\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.showRowNumbers ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'showRowNumbers', value: $event })\"\n />\n <span>Show Row Numbers</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.showFooter ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'showFooter', value: $event })\"\n />\n <span>Show Footer Totals</span>\n </label>\n </div>\n </div>\n\n <!-- Row Click & Drill-Down Action Group -->\n <div class=\"property-group\">\n <div class=\"group-title\">Row Click & Drill-Down</div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"hasRowClickAction()\"\n (ngModelChange)=\"toggleRowClickAction($event)\"\n />\n <span>Enable Row Click Action</span>\n </label>\n </div>\n\n @if (hasRowClickAction()) {\n <div class=\"field-row\">\n <label class=\"field-label\">Action Type</label>\n <select\n class=\"field-input field-select\"\n [ngModel]=\"rowClickAction()?.type || 'open-drawer'\"\n (ngModelChange)=\"updateRowActionField('type', $event)\"\n >\n <option value=\"open-drawer\">Slide-Over Detail Drawer</option>\n <option value=\"drilldown-report\">Drill-Down Report (Host Event)</option>\n <option value=\"navigate\">Navigate to URL</option>\n <option value=\"emit-custom\">Emit Custom Action</option>\n </select>\n </div>\n\n @if (rowClickAction()?.type === 'open-drawer' || rowClickAction()?.type === 'drilldown-report') {\n <div class=\"field-row\">\n <label class=\"field-label\">Target Report ID</label>\n <input\n type=\"text\"\n class=\"field-input\"\n placeholder=\"e.g. order-details\"\n [ngModel]=\"rowClickAction()?.targetReportId || ''\"\n (ngModelChange)=\"updateRowActionField('targetReportId', $event)\"\n />\n </div>\n\n <div class=\"field-row\">\n <label class=\"field-label\">Drawer Title</label>\n <input\n type=\"text\"\n class=\"field-input\"\n placeholder=\"e.g. Order Details\"\n [ngModel]=\"rowClickAction()?.drawerTitle || ''\"\n (ngModelChange)=\"updateRowActionField('drawerTitle', $event)\"\n />\n </div>\n\n <div class=\"field-row\">\n <label class=\"field-label\">Drawer Width</label>\n <input\n type=\"text\"\n class=\"field-input\"\n placeholder=\"e.g. 760px, 80%, 900px\"\n [ngModel]=\"rowClickAction()?.drawerWidth || '760px'\"\n (ngModelChange)=\"updateRowActionField('drawerWidth', $event)\"\n />\n </div>\n }\n\n @if (rowClickAction()?.type === 'navigate') {\n <div class=\"field-row\">\n <label class=\"field-label\">Target URL</label>\n <input\n type=\"text\"\n class=\"field-input\"\n [placeholder]=\"'/orders/{{row.orderId}} or https://...'\"\n [ngModel]=\"rowClickAction()?.targetUrl || ''\"\n (ngModelChange)=\"updateRowActionField('targetUrl', $event)\"\n />\n </div>\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"rowClickAction()?.openNewTab ?? false\"\n (ngModelChange)=\"updateRowActionField('openNewTab', $event)\"\n />\n <span>Open in New Tab</span>\n </label>\n </div>\n }\n\n @if (rowClickAction()?.type === 'emit-custom') {\n <div class=\"field-row\">\n <label class=\"field-label\">Custom Action Name</label>\n <input\n type=\"text\"\n class=\"field-input\"\n placeholder=\"e.g. view-audit-trail\"\n [ngModel]=\"rowClickAction()?.customActionName || ''\"\n (ngModelChange)=\"updateRowActionField('customActionName', $event)\"\n />\n </div>\n }\n\n <div class=\"field-row\">\n <div class=\"field-label-row\">\n <label class=\"field-label\">Parameter Mapping</label>\n <button\n type=\"button\"\n class=\"btn-add-mapping\"\n (click)=\"addParameterMapping()\"\n >\n + Add Mapping\n </button>\n </div>\n\n @for (mapping of parameterMappings(); track mapping.key; let mIdx = $index) {\n <div class=\"mapping-pair-row\">\n <input\n type=\"text\"\n class=\"col-mini-input\"\n style=\"flex: 1;\"\n placeholder=\"Target (e.g. customerId)\"\n [ngModel]=\"mapping.key\"\n (ngModelChange)=\"updateMappingKey(mIdx, $event)\"\n />\n <span style=\"font-size: 0.75rem; color: #94a3b8;\">\u2190</span>\n <input\n type=\"text\"\n class=\"col-mini-input\"\n style=\"flex: 1;\"\n placeholder=\"Source (e.g. row.id)\"\n [ngModel]=\"mapping.value\"\n (ngModelChange)=\"updateMappingValue(mIdx, $event)\"\n />\n <button\n type=\"button\"\n class=\"col-action-btn btn-delete\"\n (click)=\"removeMapping(mIdx)\"\n title=\"Remove mapping\"\n >\n <rpt-icon name=\"trash\" [size]=\"11\" />\n </button>\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Remove Grid Action -->\n <div class=\"grid-danger-zone\">\n <button type=\"button\" class=\"btn-remove-grid-action\" (click)=\"removeGrid.emit()\">\n <rpt-icon name=\"trash\" [size]=\"13\" />\n <span>Remove Grid from Report</span>\n </button>\n </div>\n </div>\n }", styles: [".property-group{display:flex;flex-direction:column;gap:12px}.group-title{font-size:.7rem;font-weight:700;color:var(--rpt-color-text-muted, #64748b);text-transform:uppercase;letter-spacing:.05em;line-height:1.2}.field-row{display:flex;flex-direction:column;gap:4px}.field-label-row{display:flex;align-items:center;justify-content:space-between}.field-badge-val{font-size:.65rem;font-weight:600;color:var(--rpt-color-primary, #2563eb);background:#eff6ff;border:1px solid #bfdbfe;padding:1px 6px;border-radius:9999px}.field-label{font-size:.7rem;font-weight:600;color:var(--rpt-color-text, #0f172a)}.field-input,.field-select,.field-textarea{width:100%;box-sizing:border-box;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-sm, 4px);padding:6px 10px;font-size:var(--rpt-font-size-sm, .75rem);color:var(--rpt-color-text, #0f172a);background:#fff;outline:none;transition:all var(--rpt-transition-speed, .15s) ease}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button,.field-input[type=number]::-webkit-inner-spin-button,.field-input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}input[type=number],.field-input[type=number]{-moz-appearance:textfield;appearance:textfield}.field-select{appearance:none;-webkit-appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right 10px center;background-size:13px 13px;padding-right:28px;cursor:pointer}.field-input:focus,.field-select:focus,.field-textarea:focus{border-color:var(--rpt-color-primary, #2563eb);box-shadow:var(--rpt-box-shadow-focus, 0 0 0 3px rgba(37, 99, 235, .25))}.checkbox-toggle{display:inline-flex;align-items:center;gap:8px;font-size:.75rem;font-weight:500;color:var(--rpt-color-text, #0f172a);cursor:pointer}.preset-pill-group{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}.preset-pill{background:#f1f5f9;border:1px solid #e2e8f0;border-radius:4px;padding:2px 8px;font-size:.65rem;font-weight:600;color:#475569;cursor:pointer;transition:all .12s ease}.preset-pill:hover{background:#e2e8f0;color:#0f172a;border-color:#cbd5e1}.preset-pill-active{background:var(--rpt-color-primary, #2563eb)!important;color:#fff!important;border-color:var(--rpt-color-primary, #2563eb)!important}.preset-pill-reset{color:var(--rpt-color-primary, #2563eb);background:#eff6ff;border-color:#bfdbfe}.preset-pill-reset:hover{background:#dbeafe;color:#1d4ed8;border-color:#93c5fd}\n", ".empty-grid-card{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 16px;text-align:center;border:2px dashed #cbd5e1;border-radius:8px;background:#fff;margin:10px 0}.empty-grid-icon{width:48px;height:48px;border-radius:10px;background:#eff6ff;color:#2563eb;display:flex;align-items:center;justify-content:center;margin-bottom:12px}.empty-grid-title{font-size:.95rem;font-weight:700;color:#0f172a;margin:0 0 6px}.empty-grid-desc{font-size:.775rem;color:#64748b;margin:0 0 18px;line-height:1.4;max-width:280px}.btn-create-grid-action{display:inline-flex;align-items:center;gap:6px;padding:8px 16px;background:#2563eb;color:#fff;font-size:.775rem;font-weight:600;border:none;border-radius:6px;cursor:pointer;box-shadow:0 2px 4px #2563eb33;transition:all .15s ease}.btn-create-grid-action:hover{background:#1d4ed8;box-shadow:0 4px 8px #2563eb40}.grid-danger-zone{margin-top:10px;padding-top:16px;border-top:1px dashed #e2e8f0;display:flex;justify-content:center}.btn-remove-grid-action{display:inline-flex;align-items:center;gap:6px;background:#fee2e2;border:1px solid #fecaca;color:#dc2626;font-size:.75rem;font-weight:600;padding:6px 12px;border-radius:6px;cursor:pointer;transition:all .15s ease}.btn-remove-grid-action:hover{background:#fecaca;border-color:#f87171;color:#b91c1c}.props-form{display:flex;flex-direction:column;gap:20px}.group-header-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.group-title-wrap{display:flex;align-items:center;gap:6px}.group-badge{font-size:.65rem;font-weight:700;color:#64748b;background:#f1f5f9;border:1px solid #e2e8f0;border-radius:10px;padding:1px 6px}.columns-action-bar{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:12px}.btn-col-action{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:7px 8px;font-size:.725rem;font-weight:600;border-radius:6px;cursor:pointer;white-space:nowrap;transition:all .12s ease;box-sizing:border-box}.btn-col-action:disabled{opacity:.6;cursor:not-allowed}.btn-auto-detect{background:#eff6ff;border:1px solid #bfdbfe;color:#2563eb}.btn-auto-detect:hover:not(:disabled){background:#2563eb;color:#fff;border-color:#2563eb}.btn-add-col{background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);color:#334155}.btn-add-col:hover{background:#f8fafc;border-color:#94a3b8;color:#0f172a}.col-status-banner{display:flex;align-items:center;gap:6px;padding:6px 10px;border-radius:6px;font-size:.72rem;line-height:1.35;margin-bottom:10px;background:#f1f5f9;color:#334155;border:1px solid #cbd5e1}.col-status-banner.is-success{background:#f0fdf4;color:#166534;border-color:#bbf7d0}.col-status-banner.is-error{background:#fef2f2;color:#991b1b;border-color:#fecaca}@keyframes rpt-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.rpt-spin{animation:rpt-spin 1s linear infinite}.columns-editor-list{display:flex;flex-direction:column;gap:10px}.col-edit-card{background:#fff;border:1.5px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);box-shadow:var(--rpt-box-shadow, 0 1px 2px rgba(0, 0, 0, .05));box-sizing:border-box;transition:all var(--rpt-transition-speed, .15s) ease;position:relative;overflow:hidden;display:flex;flex-direction:column}.col-edit-card:hover{border-color:var(--rpt-color-primary, #2563eb);box-shadow:0 4px 6px -1px #2563eb1a}.col-card-header{display:flex;align-items:center;justify-content:space-between;gap:6px;padding:5px 8px;background:var(--rpt-color-surface, #f8fafc);border-bottom:1px solid var(--rpt-color-border, #e2e8f0);min-height:32px;box-sizing:border-box}.col-header-title-group{display:flex;align-items:center;gap:5px;min-width:0;flex:1}.col-drag-dots{color:#94a3b8;font-size:.8rem;cursor:grab;-webkit-user-select:none;user-select:none;line-height:1;padding-left:2px}.col-idx-badge{font-size:.65rem;font-weight:700;color:#64748b;background:#e2e8f0;padding:1px 5px;border-radius:3px;letter-spacing:.02em}.col-title-input{flex:1;border:1px solid transparent;background:transparent;font-size:.75rem;font-weight:600;color:var(--rpt-color-text, #0f172a);padding:2px 6px;border-radius:4px;outline:none;transition:all .15s ease}.col-title-input:hover{border-color:#cbd5e1;background:#fff}.col-title-input:focus{border-color:var(--rpt-color-primary, #2563eb);background:#fff;box-shadow:0 0 0 2px #2563eb26}.col-card-actions{display:flex;align-items:center;gap:4px}.col-type-pill{font-size:.625rem;font-weight:700;color:#1e40af;background:#dbeafe;border:1px solid #bfdbfe;padding:1px 5px;border-radius:3px;text-transform:uppercase;letter-spacing:.04em}.col-action-btn{display:inline-flex;align-items:center;justify-content:center;background:none;border:none;color:var(--rpt-color-text-muted, #64748b);cursor:pointer;padding:3px;border-radius:4px;transition:all .15s ease}.col-action-btn.btn-delete:hover{color:var(--rpt-color-danger, #ef4444);background:#fee2e2}.col-card-body{padding:8px 10px;display:flex;flex-direction:column;gap:6px;background:#fff}.col-edit-fields{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px 8px}.col-field-item{display:flex;flex-direction:column;gap:2px;min-width:0}.mini-label{font-size:.625rem;font-weight:600;color:#64748b;letter-spacing:.02em}.col-mini-input,.col-mini-select{width:100%;box-sizing:border-box;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:3px;padding:3px 5px;font-size:.7rem;color:var(--rpt-color-text, #0f172a);background:#fff;outline:none;transition:all .12s ease}.col-mini-select{appearance:none;-webkit-appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right 4px center;background-size:10px 10px;padding-right:16px;cursor:pointer}.col-mini-input:focus,.col-mini-select:focus{border-color:var(--rpt-color-primary, #2563eb);box-shadow:0 0 0 2px #2563eb26}.col-card-footer{display:flex;align-items:center;justify-content:flex-end;gap:12px;padding-top:4px;border-top:1px dashed #e2e8f0;margin-top:2px}.col-checkbox-toggle{display:inline-flex;align-items:center;gap:5px;font-size:.675rem;font-weight:500;color:#475569;cursor:pointer;-webkit-user-select:none;user-select:none}.btn-add-mapping{display:inline-flex;align-items:center;background:#eff6ff;border:1px solid #bfdbfe;color:#2563eb;font-size:.65rem;font-weight:600;padding:2px 6px;border-radius:4px;cursor:pointer;transition:all .12s ease}.btn-add-mapping:hover{background:#dbeafe}.mapping-pair-row{display:flex;align-items:center;gap:6px;margin-top:4px}.active-ds-badge{background:#eff6ff;color:#2563eb;font-size:.65rem;font-weight:700;padding:1px 6px;border-radius:4px;border:1px solid #bfdbfe;max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.source-type-switcher{display:flex;background:#f1f5f9;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:6px;padding:2px;gap:2px;margin-bottom:12px}.source-tab-btn{flex:1;display:inline-flex;align-items:center;justify-content:center;gap:5px;padding:6px 4px;font-size:.725rem;font-weight:600;color:var(--rpt-color-text-muted, #64748b);background:transparent;border:none;border-radius:4px;cursor:pointer;transition:all .12s ease}.source-tab-btn:hover{color:var(--rpt-color-text, #0f172a)}.source-tab-btn.active{background:#fff;color:var(--rpt-color-primary, #2563eb);box-shadow:0 1px 2px #0000000f}.procedure-params-section{margin-top:14px;padding:0;background:transparent;border:none}.params-section-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.btn-paste-exec{display:inline-flex;align-items:center;gap:4px;font-size:.675rem;font-weight:600;color:#2563eb;background:#eff6ff;border:1px solid #bfdbfe;border-radius:4px;padding:3px 8px;cursor:pointer;transition:all .12s ease}.btn-paste-exec:hover{background:#2563eb;color:#fff}.exec-snippet-panel{display:flex;flex-direction:column;gap:6px;background:#f8fafc;border:1px solid #cbd5e1;border-radius:6px;padding:8px 10px;margin-bottom:10px}.snippet-hint{font-size:.675rem;color:#475569}.snippet-hint code{background:#e2e8f0;padding:1px 4px;border-radius:3px;font-family:monospace}.exec-snippet-textarea{width:100%;box-sizing:border-box;font-family:monospace;font-size:.7rem;padding:6px 8px;border:1px solid #cbd5e1;border-radius:4px;background:#fff;resize:vertical;min-height:54px}.exec-snippet-actions{display:flex;align-items:center;gap:6px}.btn-snippet-apply{display:inline-flex;align-items:center;gap:4px;font-size:.7rem;font-weight:600;padding:4px 10px;background:#16a34a;color:#fff;border:none;border-radius:4px;cursor:pointer}.btn-snippet-apply:hover{background:#15803d}.btn-snippet-cancel{font-size:.7rem;padding:4px 8px;background:#f1f5f9;color:#475569;border:1px solid #cbd5e1;border-radius:4px;cursor:pointer}.snippet-status{font-size:.675rem;padding:4px 6px;border-radius:4px}.snippet-status.is-success{background:#f0fdf4;color:#166534;border:1px solid #bbf7d0}.snippet-status.is-error{background:#fef2f2;color:#991b1b;border:1px solid #fecaca}.param-cards-list{display:flex;flex-direction:column;gap:10px;width:100%;box-sizing:border-box}.empty-params-hint{font-size:.725rem;color:#64748b;font-style:italic;padding:12px;text-align:center;background:#f8fafc;border:1px dashed #cbd5e1;border-radius:6px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: RptIconComponent, selector: "rpt-icon", inputs: ["name", "size", "color", "strokeWidth"] }, { kind: "component", type: ParameterBindingEditorComponent, selector: "rpt-parameter-binding-editor", inputs: ["param", "currentValue", "allComponents", "currentComponentId", "isAdvancedMode"], outputs: ["valueChange", "linkedFieldChange"] }, { kind: "component", type: RptSearchableSelectComponent, selector: "rpt-searchable-select", inputs: ["options", "value", "placeholder", "searchPlaceholder", "disabled", "loading", "loadingText", "emptyText", "clearable", "searchable", "size", "id", "ariaLabel", "valueKey", "labelKey", "sublabelKey", "badgeKey", "panelMaxHeight", "openAbove"], outputs: ["valueChange", "selectionChange", "opened", "closed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
16104
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: PropertyEditorGridComponent, isStandalone: true, selector: "rpt-property-editor-grid", inputs: { grid: { classPropertyName: "grid", publicName: "grid", isSignal: true, isRequired: false, transformFunction: null }, dataSource: { classPropertyName: "dataSource", publicName: "dataSource", isSignal: true, isRequired: false, transformFunction: null }, availableEntities: { classPropertyName: "availableEntities", publicName: "availableEntities", isSignal: true, isRequired: false, transformFunction: null }, procedureParameters: { classPropertyName: "procedureParameters", publicName: "procedureParameters", isSignal: true, isRequired: false, transformFunction: null }, allComponents: { classPropertyName: "allComponents", publicName: "allComponents", isSignal: true, isRequired: false, transformFunction: null }, isDetecting: { classPropertyName: "isDetecting", publicName: "isDetecting", isSignal: true, isRequired: false, transformFunction: null }, statusMessage: { classPropertyName: "statusMessage", publicName: "statusMessage", isSignal: true, isRequired: false, transformFunction: null }, statusKind: { classPropertyName: "statusKind", publicName: "statusKind", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { addGrid: "addGrid", removeGrid: "removeGrid", addColumn: "addColumn", autoDetectColumns: "autoDetectColumns", removeColumn: "removeColumn", updateColumn: "updateColumn", updateOption: "updateOption", updateRowClickAction: "updateRowClickAction", setDataSource: "setDataSource", updateDataSourceParameters: "updateDataSourceParameters", entityChanged: "entityChanged" }, ngImport: i0, template: " @if (!grid()) {\n <div class=\"empty-grid-card\">\n <div class=\"empty-grid-icon\">\n <rpt-icon name=\"table\" [size]=\"28\" />\n </div>\n <h4 class=\"empty-grid-title\">No Result Grid Attached</h4>\n <p class=\"empty-grid-desc\">Attach an interactive Result Data Grid (or UltraGrid) to this report definition to render queries and stored procedure results.</p>\n <button type=\"button\" class=\"btn-create-grid-action\" (click)=\"addGrid.emit()\">\n <rpt-icon name=\"plus\" [size]=\"14\" />\n <span>+ Add Result Grid</span>\n </button>\n </div>\n } @else {\n <div class=\"props-form\">\n <!-- 1. Report Data Source & Stored Procedure Configuration -->\n <div class=\"property-group\">\n <div class=\"group-header-row\">\n <div class=\"group-title-wrap\">\n <span class=\"group-title\">Report Data Source</span>\n @if (activeEntityName()) {\n <span class=\"group-badge active-ds-badge\">{{ activeEntityName() }}</span>\n }\n </div>\n </div>\n\n <!-- Source Type Switcher (Stored Procedure vs Table vs REST API) -->\n <div class=\"source-type-switcher\">\n <button\n type=\"button\"\n class=\"source-tab-btn\"\n [class.active]=\"currentSourceType() === 'stored_procedure'\"\n (click)=\"onSourceTypeChange('stored_procedure')\"\n >\n <rpt-icon name=\"code\" [size]=\"12\" />\n <span>Stored Procedure</span>\n </button>\n <button\n type=\"button\"\n class=\"source-tab-btn\"\n [class.active]=\"currentSourceType() === 'table'\"\n (click)=\"onSourceTypeChange('table')\"\n >\n <rpt-icon name=\"table\" [size]=\"12\" />\n <span>Table / View</span>\n </button>\n <button\n type=\"button\"\n class=\"source-tab-btn\"\n [class.active]=\"currentSourceType() === 'api'\"\n (click)=\"onSourceTypeChange('api')\"\n >\n <rpt-icon name=\"globe\" [size]=\"12\" />\n <span>REST API</span>\n </button>\n </div>\n\n @if (currentSourceType() === 'api') {\n <!-- API Endpoint Selector / Input -->\n <div class=\"field-row\">\n <label class=\"field-label\">API Endpoint URL</label>\n @if (registeredApis().length > 0) {\n <div style=\"margin-bottom: 6px;\">\n <rpt-searchable-select\n [options]=\"registeredApis()\"\n [ngModel]=\"apiEndpoint()\"\n (ngModelChange)=\"onApiEndpointChange($event)\"\n placeholder=\"-- Select Registered Endpoint or type below --\"\n searchPlaceholder=\"Search registered APIs...\"\n />\n </div>\n }\n <input\n type=\"text\"\n class=\"form-control\"\n placeholder=\"e.g. /api/v1/orders or https://api.service.com/data\"\n [ngModel]=\"apiEndpoint()\"\n (ngModelChange)=\"onApiEndpointChange($event)\"\n />\n <span class=\"field-hint\" style=\"font-size: 11px; color: var(--rpt-color-text-muted, #64748b); margin-top: 4px; display: block;\">\n Direct HTTP call or backend proxy. Use <code>:param</code> for route tokens.\n </span>\n </div>\n\n <!-- HTTP Method & Data Path Grid -->\n <div class=\"field-grid-2\" style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 8px;\">\n <div class=\"field-row\">\n <label class=\"field-label\">HTTP Method</label>\n <select\n class=\"form-control\"\n [ngModel]=\"apiMethod()\"\n (ngModelChange)=\"onApiMethodChange($event)\"\n >\n <option value=\"GET\">GET (Query String)</option>\n <option value=\"POST\">POST (Body Payload)</option>\n </select>\n </div>\n <div class=\"field-row\">\n <label class=\"field-label\">Response Data Path</label>\n <input\n type=\"text\"\n class=\"form-control\"\n placeholder=\"e.g. data.items, rows\"\n [ngModel]=\"apiDataPath()\"\n (ngModelChange)=\"onApiDataPathChange($event)\"\n />\n </div>\n </div>\n\n <!-- Use Proxy Toggle -->\n <div class=\"field-row checkbox-field-row\" style=\"margin-top: 4px;\">\n <label class=\"checkbox-label\" style=\"display: flex; align-items: center; gap: 6px; font-size: 12px; cursor: pointer;\">\n <input\n type=\"checkbox\"\n [checked]=\"apiUseProxy()\"\n (change)=\"onApiUseProxyChange($any($event.target).checked)\"\n />\n <span class=\"checkbox-text\">Route through backend proxy (bypasses CORS / hides credentials)</span>\n </label>\n </div>\n } @else {\n <!-- Entity Selector Dropdown -->\n <div class=\"field-row\">\n <label class=\"field-label\">\n {{ currentSourceType() === 'stored_procedure' ? 'Stored Procedure' : 'Table / View' }}\n </label>\n <rpt-searchable-select\n [options]=\"gridEntitySelectOptions()\"\n [ngModel]=\"activeEntityName()\"\n (ngModelChange)=\"onSelectEntity($event)\"\n placeholder=\"-- Choose Data Source --\"\n [searchPlaceholder]=\"currentSourceType() === 'stored_procedure' ? 'Search procedures...' : 'Search tables & views...'\"\n />\n </div>\n }\n\n <!-- Stored Procedure Parameter Mappings -->\n @if (currentSourceType() === 'stored_procedure' && activeEntityName()) {\n <div class=\"procedure-params-section\">\n <div class=\"params-section-header\">\n <div class=\"params-header-left\">\n <span class=\"params-title\">Procedure Parameters</span>\n <span class=\"params-count-badge\">{{ effectiveParameters().length }}</span>\n </div>\n <button\n type=\"button\"\n class=\"btn-paste-exec\"\n (click)=\"toggleExecSnippet()\"\n title=\"Paste full SSMS 'EXEC proc @param=val' snippet to fill parameters automatically\"\n >\n <rpt-icon name=\"code\" [size]=\"12\" />\n <span>{{ isExecSnippetOpen() ? 'Close SQL' : 'Paste SQL Exec' }}</span>\n </button>\n </div>\n\n @if (isExecSnippetOpen()) {\n <div class=\"exec-snippet-panel\">\n <div class=\"exec-snippet-header\">\n <span class=\"snippet-hint\">Paste full <code>EXEC ProcedureName @param=val...</code> statement from SSMS:</span>\n </div>\n <textarea\n class=\"exec-snippet-textarea\"\n rows=\"3\"\n placeholder=\"e.g. exec Get_HelpList @chrCOMPANYID=N'101',@chrTableName=N'MSTACCTGLSUB'...\"\n [ngModel]=\"execSnippetText()\"\n (ngModelChange)=\"execSnippetText.set($event)\"\n ></textarea>\n <div class=\"exec-snippet-actions\">\n <button type=\"button\" class=\"btn-snippet-apply\" (click)=\"applyExecSnippet()\">\n <rpt-icon name=\"check\" [size]=\"12\" />\n <span>Apply Parameters</span>\n </button>\n <button type=\"button\" class=\"btn-snippet-cancel\" (click)=\"toggleExecSnippet()\">\n Cancel\n </button>\n </div>\n @if (execParseStatus()) {\n <div class=\"snippet-status\" [class.is-error]=\"execParseStatus()?.kind === 'error'\" [class.is-success]=\"execParseStatus()?.kind === 'success'\">\n {{ execParseStatus()?.message }}\n </div>\n }\n </div>\n }\n\n @if (effectiveParameters().length > 0) {\n <div class=\"param-cards-list\">\n @for (param of effectiveParameters(); track param.name) {\n <rpt-parameter-binding-editor\n [param]=\"param\"\n [currentValue]=\"getParameterValue(param.name)\"\n [allComponents]=\"allComponents()\"\n (valueChange)=\"onParameterChange(param.name, $event)\"\n />\n }\n </div>\n } @else {\n <div class=\"empty-params-hint\">\n <span>No required parameters for this procedure.</span>\n </div>\n }\n </div>\n }\n </div>\n\n <!-- 2. Result Columns Configuration -->\n <div class=\"property-group\">\n <div class=\"group-header-row\">\n <div class=\"group-title-wrap\">\n <span class=\"group-title\">Columns</span>\n <span class=\"group-badge\">{{ columns().length }}</span>\n </div>\n </div>\n\n <!-- Dedicated Full-Width Action Buttons (No cramping) -->\n <div class=\"columns-action-bar\">\n <button\n type=\"button\"\n class=\"btn-col-action btn-auto-detect\"\n (click)=\"autoDetectColumns.emit()\"\n [disabled]=\"isDetecting()\"\n title=\"Auto-detect all columns from procedure or data table\"\n >\n <rpt-icon name=\"refresh\" [size]=\"12\" [class.rpt-spin]=\"isDetecting()\" />\n <span>{{ isDetecting() ? 'Detecting...' : 'Auto-Detect' }}</span>\n </button>\n <button\n type=\"button\"\n class=\"btn-col-action btn-add-col\"\n (click)=\"addColumn.emit()\"\n title=\"Add a new column manually\"\n >\n <rpt-icon name=\"plus\" [size]=\"12\" />\n <span>+ Add Column</span>\n </button>\n </div>\n\n @if (statusMessage()) {\n <div class=\"col-status-banner\" [class.is-error]=\"statusKind() === 'error'\" [class.is-success]=\"statusKind() === 'success'\">\n <rpt-icon [name]=\"statusKind() === 'error' ? 'close' : 'check'\" [size]=\"12\" />\n <span>{{ statusMessage() }}</span>\n </div>\n }\n\n <div class=\"columns-editor-list\">\n @for (col of columns(); track col.field; let idx = $index) {\n <div class=\"col-edit-card\">\n <div class=\"col-card-header\">\n <div class=\"col-header-title-group\">\n <span class=\"col-drag-dots\">\u22EE\u22EE</span>\n <span class=\"col-idx-badge\">#{{ idx + 1 }}</span>\n <input\n type=\"text\"\n class=\"col-title-input\"\n placeholder=\"Column Label\"\n [ngModel]=\"col.label\"\n (ngModelChange)=\"onUpdateCol(idx, 'label', $event)\"\n />\n </div>\n <div class=\"col-card-actions\">\n <span class=\"col-type-pill\">{{ col.dataType || 'string' }}</span>\n <button\n type=\"button\"\n class=\"col-action-btn btn-delete\"\n (click)=\"removeColumn.emit(idx)\"\n title=\"Remove Column\"\n >\n <rpt-icon name=\"trash\" [size]=\"12\" />\n </button>\n </div>\n </div>\n\n <div class=\"col-card-body\">\n <div class=\"col-edit-fields\">\n <div class=\"col-field-item\">\n <span class=\"mini-label\">Field</span>\n <input\n type=\"text\"\n class=\"col-mini-input\"\n placeholder=\"field\"\n [ngModel]=\"col.field\"\n (ngModelChange)=\"onUpdateCol(idx, 'field', $event)\"\n />\n </div>\n <div class=\"col-field-item\">\n <span class=\"mini-label\">Type</span>\n <select\n class=\"col-mini-select\"\n [ngModel]=\"col.dataType || 'string'\"\n (ngModelChange)=\"onUpdateCol(idx, 'dataType', $event)\"\n >\n <option value=\"string\">String</option>\n <option value=\"number\">Number</option>\n <option value=\"currency\">Currency</option>\n <option value=\"date\">Date</option>\n <option value=\"boolean\">Boolean</option>\n </select>\n </div>\n <div class=\"col-field-item\">\n <span class=\"mini-label\">Format</span>\n <input\n type=\"text\"\n class=\"col-mini-input\"\n placeholder=\"e.g. dd/MM/yyyy\"\n [ngModel]=\"col.format\"\n (ngModelChange)=\"onUpdateCol(idx, 'format', $event)\"\n />\n </div>\n <div class=\"col-field-item\">\n <span class=\"mini-label\">Align</span>\n <select\n class=\"col-mini-select\"\n [ngModel]=\"col.align || 'left'\"\n (ngModelChange)=\"onUpdateCol(idx, 'align', $event)\"\n >\n <option value=\"left\">Left</option>\n <option value=\"center\">Center</option>\n <option value=\"right\">Right</option>\n </select>\n </div>\n <div class=\"col-field-item\">\n <span class=\"mini-label\">Aggregate</span>\n <select\n class=\"col-mini-select\"\n [ngModel]=\"col.aggregate || 'none'\"\n (ngModelChange)=\"onUpdateCol(idx, 'aggregate', $event === 'none' ? undefined : $event)\"\n >\n <option value=\"none\">None</option>\n <option value=\"sum\">Sum</option>\n <option value=\"avg\">Average</option>\n <option value=\"count\">Count</option>\n <option value=\"min\">Min</option>\n <option value=\"max\">Max</option>\n </select>\n </div>\n <div class=\"col-field-item\">\n <span class=\"mini-label\">Width</span>\n <input\n type=\"text\"\n class=\"col-mini-input\"\n placeholder=\"auto\"\n [ngModel]=\"col.width || ''\"\n (ngModelChange)=\"onUpdateCol(idx, 'width', $event || undefined)\"\n />\n </div>\n </div>\n\n <div class=\"col-card-footer\">\n <label class=\"col-checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"col.visible !== false\"\n (ngModelChange)=\"onUpdateCol(idx, 'visible', $event)\"\n />\n <span>Visible</span>\n </label>\n <label class=\"col-checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"col.checkboxSelection ?? false\"\n (ngModelChange)=\"onUpdateCol(idx, 'checkboxSelection', $event)\"\n />\n <span>Checkbox</span>\n </label>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n\n <!-- Grid Table Options Group -->\n <div class=\"property-group\">\n <div class=\"group-title\">Table Options</div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.sideBar !== false\"\n (ngModelChange)=\"updateOption.emit({ field: 'sideBar', value: $event })\"\n />\n <span>Enable Side Panel (Tool Panels)</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.globalSearch !== false\"\n (ngModelChange)=\"updateOption.emit({ field: 'globalSearch', value: $event })\"\n />\n <span>Global Search Box</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.statusBar !== false\"\n (ngModelChange)=\"updateOption.emit({ field: 'statusBar', value: $event })\"\n />\n <span>Status Bar</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.pagination ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'pagination', value: $event })\"\n />\n <span>Enable Pagination</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.selectable ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'selectable', value: $event })\"\n />\n <span>Row Selection</span>\n </label>\n </div>\n\n @if (grid()?.options?.selectable ?? true) {\n <div class=\"field-row\">\n <label class=\"field-label\">Selection Mode</label>\n <select\n class=\"field-input\"\n [ngModel]=\"grid()?.options?.selectionMode ?? grid()?.options?.rowSelection ?? 'multiple'\"\n (ngModelChange)=\"updateOption.emit({ field: 'selectionMode', value: $event })\"\n >\n <option value=\"multiple\">Multiple Rows</option>\n <option value=\"single\">Single Row</option>\n </select>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.checkboxSelection ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'checkboxSelection', value: $event })\"\n />\n <span>Checkbox Selection</span>\n </label>\n </div>\n\n @if ((grid()?.options?.selectionMode ?? 'multiple') === 'multiple') {\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.headerCheckboxSelection ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'headerCheckboxSelection', value: $event })\"\n />\n <span>Header Select All Checkbox</span>\n </label>\n </div>\n }\n }\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.allowColumnReorder ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'allowColumnReorder', value: $event })\"\n />\n <span>Allow Column Reorder</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.allowExport ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'allowExport', value: $event })\"\n />\n <span>Allow Export</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"field-label\">Page Size</label>\n <input\n type=\"number\"\n class=\"field-input\"\n min=\"1\"\n max=\"1000\"\n (wheel)=\"$any($event.target).blur()\"\n (keydown.arrowup)=\"$event.preventDefault()\"\n (keydown.arrowdown)=\"$event.preventDefault()\"\n [ngModel]=\"grid()?.options?.pageSize ?? 10\"\n (ngModelChange)=\"updateOption.emit({ field: 'pageSize', value: +$event })\"\n />\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.showRowNumbers ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'showRowNumbers', value: $event })\"\n />\n <span>Show Row Numbers</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.showFooter ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'showFooter', value: $event })\"\n />\n <span>Show Footer Totals</span>\n </label>\n </div>\n </div>\n\n <!-- Row Click & Drill-Down Action Group -->\n <div class=\"property-group\">\n <div class=\"group-title\">Row Click & Drill-Down</div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"hasRowClickAction()\"\n (ngModelChange)=\"toggleRowClickAction($event)\"\n />\n <span>Enable Row Click Action</span>\n </label>\n </div>\n\n @if (hasRowClickAction()) {\n <div class=\"field-row\">\n <label class=\"field-label\">Action Type</label>\n <select\n class=\"field-input field-select\"\n [ngModel]=\"rowClickAction()?.type || 'open-drawer'\"\n (ngModelChange)=\"updateRowActionField('type', $event)\"\n >\n <option value=\"open-drawer\">Slide-Over Detail Drawer</option>\n <option value=\"drilldown-report\">Drill-Down Report (Host Event)</option>\n <option value=\"navigate\">Navigate to URL</option>\n <option value=\"emit-custom\">Emit Custom Action</option>\n </select>\n </div>\n\n @if (rowClickAction()?.type === 'open-drawer' || rowClickAction()?.type === 'drilldown-report') {\n <div class=\"field-row\">\n <label class=\"field-label\">Target Report ID</label>\n <input\n type=\"text\"\n class=\"field-input\"\n placeholder=\"e.g. order-details\"\n [ngModel]=\"rowClickAction()?.targetReportId || ''\"\n (ngModelChange)=\"updateRowActionField('targetReportId', $event)\"\n />\n </div>\n\n <div class=\"field-row\">\n <label class=\"field-label\">Drawer Title</label>\n <input\n type=\"text\"\n class=\"field-input\"\n placeholder=\"e.g. Order Details\"\n [ngModel]=\"rowClickAction()?.drawerTitle || ''\"\n (ngModelChange)=\"updateRowActionField('drawerTitle', $event)\"\n />\n </div>\n\n <div class=\"field-row\">\n <label class=\"field-label\">Drawer Width</label>\n <input\n type=\"text\"\n class=\"field-input\"\n placeholder=\"e.g. 760px, 80%, 900px\"\n [ngModel]=\"rowClickAction()?.drawerWidth || '760px'\"\n (ngModelChange)=\"updateRowActionField('drawerWidth', $event)\"\n />\n </div>\n }\n\n @if (rowClickAction()?.type === 'navigate') {\n <div class=\"field-row\">\n <label class=\"field-label\">Target URL</label>\n <input\n type=\"text\"\n class=\"field-input\"\n [placeholder]=\"'/orders/{{row.orderId}} or https://...'\"\n [ngModel]=\"rowClickAction()?.targetUrl || ''\"\n (ngModelChange)=\"updateRowActionField('targetUrl', $event)\"\n />\n </div>\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"rowClickAction()?.openNewTab ?? false\"\n (ngModelChange)=\"updateRowActionField('openNewTab', $event)\"\n />\n <span>Open in New Tab</span>\n </label>\n </div>\n }\n\n @if (rowClickAction()?.type === 'emit-custom') {\n <div class=\"field-row\">\n <label class=\"field-label\">Custom Action Name</label>\n <input\n type=\"text\"\n class=\"field-input\"\n placeholder=\"e.g. view-audit-trail\"\n [ngModel]=\"rowClickAction()?.customActionName || ''\"\n (ngModelChange)=\"updateRowActionField('customActionName', $event)\"\n />\n </div>\n }\n\n <div class=\"field-row\">\n <div class=\"field-label-row\">\n <label class=\"field-label\">Parameter Mapping</label>\n <button\n type=\"button\"\n class=\"btn-add-mapping\"\n (click)=\"addParameterMapping()\"\n >\n + Add Mapping\n </button>\n </div>\n\n @for (mapping of parameterMappings(); track mapping.key; let mIdx = $index) {\n <div class=\"mapping-pair-row\">\n <input\n type=\"text\"\n class=\"col-mini-input\"\n style=\"flex: 1;\"\n placeholder=\"Target (e.g. customerId)\"\n [ngModel]=\"mapping.key\"\n (ngModelChange)=\"updateMappingKey(mIdx, $event)\"\n />\n <span style=\"font-size: 0.75rem; color: #94a3b8;\">\u2190</span>\n <input\n type=\"text\"\n class=\"col-mini-input\"\n style=\"flex: 1;\"\n placeholder=\"Source (e.g. row.id)\"\n [ngModel]=\"mapping.value\"\n (ngModelChange)=\"updateMappingValue(mIdx, $event)\"\n />\n <button\n type=\"button\"\n class=\"col-action-btn btn-delete\"\n (click)=\"removeMapping(mIdx)\"\n title=\"Remove mapping\"\n >\n <rpt-icon name=\"trash\" [size]=\"11\" />\n </button>\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Remove Grid Action -->\n <div class=\"grid-danger-zone\">\n <button type=\"button\" class=\"btn-remove-grid-action\" (click)=\"removeGrid.emit()\">\n <rpt-icon name=\"trash\" [size]=\"13\" />\n <span>Remove Grid from Report</span>\n </button>\n </div>\n </div>\n }", styles: [".property-group{display:flex;flex-direction:column;gap:12px}.group-title{font-size:.7rem;font-weight:700;color:var(--rpt-color-text-muted, #64748b);text-transform:uppercase;letter-spacing:.05em;line-height:1.2}.field-row{display:flex;flex-direction:column;gap:4px}.field-label-row{display:flex;align-items:center;justify-content:space-between}.field-badge-val{font-size:.65rem;font-weight:600;color:var(--rpt-color-primary, #2563eb);background:#eff6ff;border:1px solid #bfdbfe;padding:1px 6px;border-radius:9999px}.field-label{font-size:.7rem;font-weight:600;color:var(--rpt-color-text, #0f172a)}.field-input,.field-select,.field-textarea{width:100%;box-sizing:border-box;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-sm, 4px);padding:6px 10px;font-size:var(--rpt-font-size-sm, .75rem);color:var(--rpt-color-text, #0f172a);background:#fff;outline:none;transition:all var(--rpt-transition-speed, .15s) ease}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button,.field-input[type=number]::-webkit-inner-spin-button,.field-input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}input[type=number],.field-input[type=number]{-moz-appearance:textfield;appearance:textfield}.field-select{appearance:none;-webkit-appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right 10px center;background-size:13px 13px;padding-right:28px;cursor:pointer}.field-input:focus,.field-select:focus,.field-textarea:focus{border-color:var(--rpt-color-primary, #2563eb);box-shadow:var(--rpt-box-shadow-focus, 0 0 0 3px rgba(37, 99, 235, .25))}.checkbox-toggle{display:inline-flex;align-items:center;gap:8px;font-size:.75rem;font-weight:500;color:var(--rpt-color-text, #0f172a);cursor:pointer}.preset-pill-group{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}.preset-pill{background:#f1f5f9;border:1px solid #e2e8f0;border-radius:4px;padding:2px 8px;font-size:.65rem;font-weight:600;color:#475569;cursor:pointer;transition:all .12s ease}.preset-pill:hover{background:#e2e8f0;color:#0f172a;border-color:#cbd5e1}.preset-pill-active{background:var(--rpt-color-primary, #2563eb)!important;color:#fff!important;border-color:var(--rpt-color-primary, #2563eb)!important}.preset-pill-reset{color:var(--rpt-color-primary, #2563eb);background:#eff6ff;border-color:#bfdbfe}.preset-pill-reset:hover{background:#dbeafe;color:#1d4ed8;border-color:#93c5fd}\n", ".empty-grid-card{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 16px;text-align:center;border:2px dashed #cbd5e1;border-radius:8px;background:#fff;margin:10px 0}.empty-grid-icon{width:48px;height:48px;border-radius:10px;background:#eff6ff;color:#2563eb;display:flex;align-items:center;justify-content:center;margin-bottom:12px}.empty-grid-title{font-size:.95rem;font-weight:700;color:#0f172a;margin:0 0 6px}.empty-grid-desc{font-size:.775rem;color:#64748b;margin:0 0 18px;line-height:1.4;max-width:280px}.btn-create-grid-action{display:inline-flex;align-items:center;gap:6px;padding:8px 16px;background:#2563eb;color:#fff;font-size:.775rem;font-weight:600;border:none;border-radius:6px;cursor:pointer;box-shadow:0 2px 4px #2563eb33;transition:all .15s ease}.btn-create-grid-action:hover{background:#1d4ed8;box-shadow:0 4px 8px #2563eb40}.grid-danger-zone{margin-top:10px;padding-top:16px;border-top:1px dashed #e2e8f0;display:flex;justify-content:center}.btn-remove-grid-action{display:inline-flex;align-items:center;gap:6px;background:#fee2e2;border:1px solid #fecaca;color:#dc2626;font-size:.75rem;font-weight:600;padding:6px 12px;border-radius:6px;cursor:pointer;transition:all .15s ease}.btn-remove-grid-action:hover{background:#fecaca;border-color:#f87171;color:#b91c1c}.props-form{display:flex;flex-direction:column;gap:20px}.group-header-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.group-title-wrap{display:flex;align-items:center;gap:6px}.group-badge{font-size:.65rem;font-weight:700;color:#64748b;background:#f1f5f9;border:1px solid #e2e8f0;border-radius:10px;padding:1px 6px}.columns-action-bar{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:12px}.btn-col-action{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:7px 8px;font-size:.725rem;font-weight:600;border-radius:6px;cursor:pointer;white-space:nowrap;transition:all .12s ease;box-sizing:border-box}.btn-col-action:disabled{opacity:.6;cursor:not-allowed}.btn-auto-detect{background:#eff6ff;border:1px solid #bfdbfe;color:#2563eb}.btn-auto-detect:hover:not(:disabled){background:#2563eb;color:#fff;border-color:#2563eb}.btn-add-col{background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);color:#334155}.btn-add-col:hover{background:#f8fafc;border-color:#94a3b8;color:#0f172a}.col-status-banner{display:flex;align-items:center;gap:6px;padding:6px 10px;border-radius:6px;font-size:.72rem;line-height:1.35;margin-bottom:10px;background:#f1f5f9;color:#334155;border:1px solid #cbd5e1}.col-status-banner.is-success{background:#f0fdf4;color:#166534;border-color:#bbf7d0}.col-status-banner.is-error{background:#fef2f2;color:#991b1b;border-color:#fecaca}@keyframes rpt-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.rpt-spin{animation:rpt-spin 1s linear infinite}.columns-editor-list{display:flex;flex-direction:column;gap:10px}.col-edit-card{background:#fff;border:1.5px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);box-shadow:var(--rpt-box-shadow, 0 1px 2px rgba(0, 0, 0, .05));box-sizing:border-box;transition:all var(--rpt-transition-speed, .15s) ease;position:relative;overflow:hidden;display:flex;flex-direction:column}.col-edit-card:hover{border-color:var(--rpt-color-primary, #2563eb);box-shadow:0 4px 6px -1px #2563eb1a}.col-card-header{display:flex;align-items:center;justify-content:space-between;gap:6px;padding:5px 8px;background:var(--rpt-color-surface, #f8fafc);border-bottom:1px solid var(--rpt-color-border, #e2e8f0);min-height:32px;box-sizing:border-box}.col-header-title-group{display:flex;align-items:center;gap:5px;min-width:0;flex:1}.col-drag-dots{color:#94a3b8;font-size:.8rem;cursor:grab;-webkit-user-select:none;user-select:none;line-height:1;padding-left:2px}.col-idx-badge{font-size:.65rem;font-weight:700;color:#64748b;background:#e2e8f0;padding:1px 5px;border-radius:3px;letter-spacing:.02em}.col-title-input{flex:1;border:1px solid transparent;background:transparent;font-size:.75rem;font-weight:600;color:var(--rpt-color-text, #0f172a);padding:2px 6px;border-radius:4px;outline:none;transition:all .15s ease}.col-title-input:hover{border-color:#cbd5e1;background:#fff}.col-title-input:focus{border-color:var(--rpt-color-primary, #2563eb);background:#fff;box-shadow:0 0 0 2px #2563eb26}.col-card-actions{display:flex;align-items:center;gap:4px}.col-type-pill{font-size:.625rem;font-weight:700;color:#1e40af;background:#dbeafe;border:1px solid #bfdbfe;padding:1px 5px;border-radius:3px;text-transform:uppercase;letter-spacing:.04em}.col-action-btn{display:inline-flex;align-items:center;justify-content:center;background:none;border:none;color:var(--rpt-color-text-muted, #64748b);cursor:pointer;padding:3px;border-radius:4px;transition:all .15s ease}.col-action-btn.btn-delete:hover{color:var(--rpt-color-danger, #ef4444);background:#fee2e2}.col-card-body{padding:8px 10px;display:flex;flex-direction:column;gap:6px;background:#fff}.col-edit-fields{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px 8px}.col-field-item{display:flex;flex-direction:column;gap:2px;min-width:0}.mini-label{font-size:.625rem;font-weight:600;color:#64748b;letter-spacing:.02em}.col-mini-input,.col-mini-select{width:100%;box-sizing:border-box;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:3px;padding:3px 5px;font-size:.7rem;color:var(--rpt-color-text, #0f172a);background:#fff;outline:none;transition:all .12s ease}.col-mini-select{appearance:none;-webkit-appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right 4px center;background-size:10px 10px;padding-right:16px;cursor:pointer}.col-mini-input:focus,.col-mini-select:focus{border-color:var(--rpt-color-primary, #2563eb);box-shadow:0 0 0 2px #2563eb26}.col-card-footer{display:flex;align-items:center;justify-content:flex-end;gap:12px;padding-top:4px;border-top:1px dashed #e2e8f0;margin-top:2px}.col-checkbox-toggle{display:inline-flex;align-items:center;gap:5px;font-size:.675rem;font-weight:500;color:#475569;cursor:pointer;-webkit-user-select:none;user-select:none}.btn-add-mapping{display:inline-flex;align-items:center;background:#eff6ff;border:1px solid #bfdbfe;color:#2563eb;font-size:.65rem;font-weight:600;padding:2px 6px;border-radius:4px;cursor:pointer;transition:all .12s ease}.btn-add-mapping:hover{background:#dbeafe}.mapping-pair-row{display:flex;align-items:center;gap:6px;margin-top:4px}.active-ds-badge{background:#eff6ff;color:#2563eb;font-size:.65rem;font-weight:700;padding:1px 6px;border-radius:4px;border:1px solid #bfdbfe;max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.source-type-switcher{display:flex;background:#f1f5f9;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:6px;padding:2px;gap:2px;margin-bottom:12px}.source-tab-btn{flex:1;display:inline-flex;align-items:center;justify-content:center;gap:5px;padding:6px 4px;font-size:.725rem;font-weight:600;color:var(--rpt-color-text-muted, #64748b);background:transparent;border:none;border-radius:4px;cursor:pointer;transition:all .12s ease}.source-tab-btn:hover{color:var(--rpt-color-text, #0f172a)}.source-tab-btn.active{background:#fff;color:var(--rpt-color-primary, #2563eb);box-shadow:0 1px 2px #0000000f}.procedure-params-section{margin-top:14px;padding:0;background:transparent;border:none}.params-section-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.btn-paste-exec{display:inline-flex;align-items:center;gap:4px;font-size:.675rem;font-weight:600;color:#2563eb;background:#eff6ff;border:1px solid #bfdbfe;border-radius:4px;padding:3px 8px;cursor:pointer;transition:all .12s ease}.btn-paste-exec:hover{background:#2563eb;color:#fff}.exec-snippet-panel{display:flex;flex-direction:column;gap:6px;background:#f8fafc;border:1px solid #cbd5e1;border-radius:6px;padding:8px 10px;margin-bottom:10px}.snippet-hint{font-size:.675rem;color:#475569}.snippet-hint code{background:#e2e8f0;padding:1px 4px;border-radius:3px;font-family:monospace}.exec-snippet-textarea{width:100%;box-sizing:border-box;font-family:monospace;font-size:.7rem;padding:6px 8px;border:1px solid #cbd5e1;border-radius:4px;background:#fff;resize:vertical;min-height:54px}.exec-snippet-actions{display:flex;align-items:center;gap:6px}.btn-snippet-apply{display:inline-flex;align-items:center;gap:4px;font-size:.7rem;font-weight:600;padding:4px 10px;background:#16a34a;color:#fff;border:none;border-radius:4px;cursor:pointer}.btn-snippet-apply:hover{background:#15803d}.btn-snippet-cancel{font-size:.7rem;padding:4px 8px;background:#f1f5f9;color:#475569;border:1px solid #cbd5e1;border-radius:4px;cursor:pointer}.snippet-status{font-size:.675rem;padding:4px 6px;border-radius:4px}.snippet-status.is-success{background:#f0fdf4;color:#166534;border:1px solid #bbf7d0}.snippet-status.is-error{background:#fef2f2;color:#991b1b;border:1px solid #fecaca}.param-cards-list{display:flex;flex-direction:column;gap:10px;width:100%;box-sizing:border-box}.empty-params-hint{font-size:.725rem;color:#64748b;font-style:italic;padding:12px;text-align:center;background:#f8fafc;border:1px dashed #cbd5e1;border-radius:6px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: RptIconComponent, selector: "rpt-icon", inputs: ["name", "size", "color", "strokeWidth"] }, { kind: "component", type: ParameterBindingEditorComponent, selector: "rpt-parameter-binding-editor", inputs: ["param", "currentValue", "allComponents", "currentComponentId", "isAdvancedMode"], outputs: ["valueChange", "linkedFieldChange"] }, { kind: "component", type: RptSearchableSelectComponent, selector: "rpt-searchable-select", inputs: ["options", "value", "placeholder", "searchPlaceholder", "disabled", "loading", "loadingText", "emptyText", "clearable", "searchable", "size", "id", "ariaLabel", "valueKey", "labelKey", "sublabelKey", "badgeKey", "panelMaxHeight", "openAbove"], outputs: ["valueChange", "selectionChange", "opened", "closed"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
15860
16105
|
}
|
|
15861
16106
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PropertyEditorGridComponent, decorators: [{
|
|
15862
16107
|
type: Component,
|
|
@@ -15866,7 +16111,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
15866
16111
|
RptIconComponent,
|
|
15867
16112
|
ParameterBindingEditorComponent,
|
|
15868
16113
|
RptSearchableSelectComponent,
|
|
15869
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: " @if (!grid()) {\n <div class=\"empty-grid-card\">\n <div class=\"empty-grid-icon\">\n <rpt-icon name=\"table\" [size]=\"28\" />\n </div>\n <h4 class=\"empty-grid-title\">No Result Grid Attached</h4>\n <p class=\"empty-grid-desc\">Attach an interactive Result Data Grid (or UltraGrid) to this report definition to render queries and stored procedure results.</p>\n <button type=\"button\" class=\"btn-create-grid-action\" (click)=\"addGrid.emit()\">\n <rpt-icon name=\"plus\" [size]=\"14\" />\n <span>+ Add Result Grid</span>\n </button>\n </div>\n } @else {\n <div class=\"props-form\">\n <!-- 1. Report Data Source & Stored Procedure Configuration -->\n <div class=\"property-group\">\n <div class=\"group-header-row\">\n <div class=\"group-title-wrap\">\n <span class=\"group-title\">Report Data Source</span>\n @if (activeEntityName()) {\n <span class=\"group-badge active-ds-badge\">{{ activeEntityName() }}</span>\n }\n </div>\n </div>\n\n <!-- Source Type Switcher (Stored Procedure vs Table vs REST API) -->\n <div class=\"source-type-switcher\">\n <button\n type=\"button\"\n class=\"source-tab-btn\"\n [class.active]=\"currentSourceType() === 'stored_procedure'\"\n (click)=\"onSourceTypeChange('stored_procedure')\"\n >\n <rpt-icon name=\"code\" [size]=\"12\" />\n <span>Stored Procedure</span>\n </button>\n <button\n type=\"button\"\n class=\"source-tab-btn\"\n [class.active]=\"currentSourceType() === 'table'\"\n (click)=\"onSourceTypeChange('table')\"\n >\n <rpt-icon name=\"table\" [size]=\"12\" />\n <span>Table / View</span>\n </button>\n <button\n type=\"button\"\n class=\"source-tab-btn\"\n [class.active]=\"currentSourceType() === 'api'\"\n (click)=\"onSourceTypeChange('api')\"\n >\n <rpt-icon name=\"globe\" [size]=\"12\" />\n <span>REST API</span>\n </button>\n </div>\n\n @if (currentSourceType() === 'api') {\n <!-- API Endpoint Selector / Input -->\n <div class=\"field-row\">\n <label class=\"field-label\">API Endpoint URL</label>\n @if (registeredApis().length > 0) {\n <div style=\"margin-bottom: 6px;\">\n <rpt-searchable-select\n [options]=\"registeredApis()\"\n [ngModel]=\"apiEndpoint()\"\n (ngModelChange)=\"onApiEndpointChange($event)\"\n placeholder=\"-- Select Registered Endpoint or type below --\"\n searchPlaceholder=\"Search registered APIs...\"\n />\n </div>\n }\n <input\n type=\"text\"\n class=\"form-control\"\n placeholder=\"e.g. /api/v1/orders or https://api.service.com/data\"\n [ngModel]=\"apiEndpoint()\"\n (ngModelChange)=\"onApiEndpointChange($event)\"\n />\n <span class=\"field-hint\" style=\"font-size: 11px; color: var(--rpt-color-text-muted, #64748b); margin-top: 4px; display: block;\">\n Direct HTTP call or backend proxy. Use <code>:param</code> for route tokens.\n </span>\n </div>\n\n <!-- HTTP Method & Data Path Grid -->\n <div class=\"field-grid-2\" style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 8px;\">\n <div class=\"field-row\">\n <label class=\"field-label\">HTTP Method</label>\n <select\n class=\"form-control\"\n [ngModel]=\"apiMethod()\"\n (ngModelChange)=\"onApiMethodChange($event)\"\n >\n <option value=\"GET\">GET (Query String)</option>\n <option value=\"POST\">POST (Body Payload)</option>\n </select>\n </div>\n <div class=\"field-row\">\n <label class=\"field-label\">Response Data Path</label>\n <input\n type=\"text\"\n class=\"form-control\"\n placeholder=\"e.g. data.items, rows\"\n [ngModel]=\"apiDataPath()\"\n (ngModelChange)=\"onApiDataPathChange($event)\"\n />\n </div>\n </div>\n\n <!-- Use Proxy Toggle -->\n <div class=\"field-row checkbox-field-row\" style=\"margin-top: 4px;\">\n <label class=\"checkbox-label\" style=\"display: flex; align-items: center; gap: 6px; font-size: 12px; cursor: pointer;\">\n <input\n type=\"checkbox\"\n [checked]=\"apiUseProxy()\"\n (change)=\"onApiUseProxyChange($any($event.target).checked)\"\n />\n <span class=\"checkbox-text\">Route through backend proxy (bypasses CORS / hides credentials)</span>\n </label>\n </div>\n } @else {\n <!-- Entity Selector Dropdown -->\n <div class=\"field-row\">\n <label class=\"field-label\">\n {{ currentSourceType() === 'stored_procedure' ? 'Stored Procedure' : 'Table / View' }}\n </label>\n <rpt-searchable-select\n [options]=\"gridEntitySelectOptions()\"\n [ngModel]=\"activeEntityName()\"\n (ngModelChange)=\"onSelectEntity($event)\"\n placeholder=\"-- Choose Data Source --\"\n [searchPlaceholder]=\"currentSourceType() === 'stored_procedure' ? 'Search procedures...' : 'Search tables & views...'\"\n />\n </div>\n }\n\n <!-- Stored Procedure Parameter Mappings -->\n @if (currentSourceType() === 'stored_procedure' && activeEntityName()) {\n <div class=\"procedure-params-section\">\n <div class=\"params-section-header\">\n <div class=\"params-header-left\">\n <span class=\"params-title\">Procedure Parameters</span>\n <span class=\"params-count-badge\">{{ effectiveParameters().length }}</span>\n </div>\n <button\n type=\"button\"\n class=\"btn-paste-exec\"\n (click)=\"toggleExecSnippet()\"\n title=\"Paste full SSMS 'EXEC proc @param=val' snippet to fill parameters automatically\"\n >\n <rpt-icon name=\"code\" [size]=\"12\" />\n <span>{{ isExecSnippetOpen() ? 'Close SQL' : 'Paste SQL Exec' }}</span>\n </button>\n </div>\n\n @if (isExecSnippetOpen()) {\n <div class=\"exec-snippet-panel\">\n <div class=\"exec-snippet-header\">\n <span class=\"snippet-hint\">Paste full <code>EXEC ProcedureName @param=val...</code> statement from SSMS:</span>\n </div>\n <textarea\n class=\"exec-snippet-textarea\"\n rows=\"3\"\n placeholder=\"e.g. exec Get_HelpList @chrCOMPANYID=N'101',@chrTableName=N'MSTACCTGLSUB'...\"\n [ngModel]=\"execSnippetText()\"\n (ngModelChange)=\"execSnippetText.set($event)\"\n ></textarea>\n <div class=\"exec-snippet-actions\">\n <button type=\"button\" class=\"btn-snippet-apply\" (click)=\"applyExecSnippet()\">\n <rpt-icon name=\"check\" [size]=\"12\" />\n <span>Apply Parameters</span>\n </button>\n <button type=\"button\" class=\"btn-snippet-cancel\" (click)=\"toggleExecSnippet()\">\n Cancel\n </button>\n </div>\n @if (execParseStatus()) {\n <div class=\"snippet-status\" [class.is-error]=\"execParseStatus()?.kind === 'error'\" [class.is-success]=\"execParseStatus()?.kind === 'success'\">\n {{ execParseStatus()?.message }}\n </div>\n }\n </div>\n }\n\n @if (effectiveParameters().length > 0) {\n <div class=\"param-cards-list\">\n @for (param of effectiveParameters(); track param.name) {\n <rpt-parameter-binding-editor\n [param]=\"param\"\n [currentValue]=\"getParameterValue(param.name)\"\n [allComponents]=\"allComponents()\"\n (valueChange)=\"onParameterChange(param.name, $event)\"\n />\n }\n </div>\n } @else {\n <div class=\"empty-params-hint\">\n <span>No required parameters for this procedure.</span>\n </div>\n }\n </div>\n }\n </div>\n\n <!-- 2. Result Columns Configuration -->\n <div class=\"property-group\">\n <div class=\"group-header-row\">\n <div class=\"group-title-wrap\">\n <span class=\"group-title\">Columns</span>\n <span class=\"group-badge\">{{ columns().length }}</span>\n </div>\n </div>\n\n <!-- Dedicated Full-Width Action Buttons (No cramping) -->\n <div class=\"columns-action-bar\">\n <button\n type=\"button\"\n class=\"btn-col-action btn-auto-detect\"\n (click)=\"autoDetectColumns.emit()\"\n [disabled]=\"isDetecting()\"\n title=\"Auto-detect all columns from procedure or data table\"\n >\n <rpt-icon name=\"refresh\" [size]=\"12\" [class.rpt-spin]=\"isDetecting()\" />\n <span>{{ isDetecting() ? 'Detecting...' : 'Auto-Detect' }}</span>\n </button>\n <button\n type=\"button\"\n class=\"btn-col-action btn-add-col\"\n (click)=\"addColumn.emit()\"\n title=\"Add a new column manually\"\n >\n <rpt-icon name=\"plus\" [size]=\"12\" />\n <span>+ Add Column</span>\n </button>\n </div>\n\n @if (statusMessage()) {\n <div class=\"col-status-banner\" [class.is-error]=\"statusKind() === 'error'\" [class.is-success]=\"statusKind() === 'success'\">\n <rpt-icon [name]=\"statusKind() === 'error' ? 'close' : 'check'\" [size]=\"12\" />\n <span>{{ statusMessage() }}</span>\n </div>\n }\n\n <div class=\"columns-editor-list\">\n @for (col of columns(); track col.field; let idx = $index) {\n <div class=\"col-edit-card\">\n <div class=\"col-card-header\">\n <div class=\"col-header-title-group\">\n <span class=\"col-drag-dots\">\u22EE\u22EE</span>\n <span class=\"col-idx-badge\">#{{ idx + 1 }}</span>\n <input\n type=\"text\"\n class=\"col-title-input\"\n placeholder=\"Column Label\"\n [ngModel]=\"col.label\"\n (ngModelChange)=\"onUpdateCol(idx, 'label', $event)\"\n />\n </div>\n <div class=\"col-card-actions\">\n <span class=\"col-type-pill\">{{ col.dataType || 'string' }}</span>\n <button\n type=\"button\"\n class=\"col-action-btn btn-delete\"\n (click)=\"removeColumn.emit(idx)\"\n title=\"Remove Column\"\n >\n <rpt-icon name=\"trash\" [size]=\"12\" />\n </button>\n </div>\n </div>\n\n <div class=\"col-card-body\">\n <div class=\"col-edit-fields\">\n <div class=\"col-field-item\">\n <span class=\"mini-label\">Field</span>\n <input\n type=\"text\"\n class=\"col-mini-input\"\n placeholder=\"field\"\n [ngModel]=\"col.field\"\n (ngModelChange)=\"onUpdateCol(idx, 'field', $event)\"\n />\n </div>\n <div class=\"col-field-item\">\n <span class=\"mini-label\">Type</span>\n <select\n class=\"col-mini-select\"\n [ngModel]=\"col.dataType || 'string'\"\n (ngModelChange)=\"onUpdateCol(idx, 'dataType', $event)\"\n >\n <option value=\"string\">String</option>\n <option value=\"number\">Number</option>\n <option value=\"currency\">Currency</option>\n <option value=\"date\">Date</option>\n <option value=\"boolean\">Boolean</option>\n </select>\n </div>\n <div class=\"col-field-item\">\n <span class=\"mini-label\">Format</span>\n <input\n type=\"text\"\n class=\"col-mini-input\"\n placeholder=\"e.g. dd/MM/yyyy\"\n [ngModel]=\"col.format\"\n (ngModelChange)=\"onUpdateCol(idx, 'format', $event)\"\n />\n </div>\n <div class=\"col-field-item\">\n <span class=\"mini-label\">Align</span>\n <select\n class=\"col-mini-select\"\n [ngModel]=\"col.align || 'left'\"\n (ngModelChange)=\"onUpdateCol(idx, 'align', $event)\"\n >\n <option value=\"left\">Left</option>\n <option value=\"center\">Center</option>\n <option value=\"right\">Right</option>\n </select>\n </div>\n <div class=\"col-field-item\">\n <span class=\"mini-label\">Aggregate</span>\n <select\n class=\"col-mini-select\"\n [ngModel]=\"col.aggregate || 'none'\"\n (ngModelChange)=\"onUpdateCol(idx, 'aggregate', $event === 'none' ? undefined : $event)\"\n >\n <option value=\"none\">None</option>\n <option value=\"sum\">Sum</option>\n <option value=\"avg\">Average</option>\n <option value=\"count\">Count</option>\n <option value=\"min\">Min</option>\n <option value=\"max\">Max</option>\n </select>\n </div>\n <div class=\"col-field-item\">\n <span class=\"mini-label\">Width</span>\n <input\n type=\"text\"\n class=\"col-mini-input\"\n placeholder=\"auto\"\n [ngModel]=\"col.width || ''\"\n (ngModelChange)=\"onUpdateCol(idx, 'width', $event || undefined)\"\n />\n </div>\n </div>\n\n <div class=\"col-card-footer\">\n <label class=\"col-checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"col.visible !== false\"\n (ngModelChange)=\"onUpdateCol(idx, 'visible', $event)\"\n />\n <span>Visible</span>\n </label>\n <label class=\"col-checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"col.checkboxSelection ?? false\"\n (ngModelChange)=\"onUpdateCol(idx, 'checkboxSelection', $event)\"\n />\n <span>Checkbox</span>\n </label>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n\n <!-- Grid Table Options Group -->\n <div class=\"property-group\">\n <div class=\"group-title\">Table Options</div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.sideBar !== false\"\n (ngModelChange)=\"updateOption.emit({ field: 'sideBar', value: $event })\"\n />\n <span>Enable Side Panel (Tool Panels)</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.globalSearch !== false\"\n (ngModelChange)=\"updateOption.emit({ field: 'globalSearch', value: $event })\"\n />\n <span>Global Search Box</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.statusBar !== false\"\n (ngModelChange)=\"updateOption.emit({ field: 'statusBar', value: $event })\"\n />\n <span>Status Bar</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.pagination ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'pagination', value: $event })\"\n />\n <span>Enable Pagination</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.selectable ?? false\"\n (ngModelChange)=\"updateOption.emit({ field: 'selectable', value: $event })\"\n />\n <span>Row Selection</span>\n </label>\n </div>\n\n @if (grid()?.options?.selectable) {\n <div class=\"field-row\">\n <label class=\"field-label\">Selection Mode</label>\n <select\n class=\"field-input\"\n [ngModel]=\"grid()?.options?.selectionMode ?? 'multiple'\"\n (ngModelChange)=\"updateOption.emit({ field: 'selectionMode', value: $event })\"\n >\n <option value=\"multiple\">Multiple Rows</option>\n <option value=\"single\">Single Row</option>\n </select>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.checkboxSelection ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'checkboxSelection', value: $event })\"\n />\n <span>Checkbox Selection</span>\n </label>\n </div>\n\n @if ((grid()?.options?.selectionMode ?? 'multiple') === 'multiple') {\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.headerCheckboxSelection ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'headerCheckboxSelection', value: $event })\"\n />\n <span>Header Select All Checkbox</span>\n </label>\n </div>\n }\n }\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.allowColumnReorder ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'allowColumnReorder', value: $event })\"\n />\n <span>Allow Column Reorder</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.allowExport ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'allowExport', value: $event })\"\n />\n <span>Allow Export</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"field-label\">Page Size</label>\n <input\n type=\"number\"\n class=\"field-input\"\n min=\"1\"\n max=\"1000\"\n (wheel)=\"$any($event.target).blur()\"\n (keydown.arrowup)=\"$event.preventDefault()\"\n (keydown.arrowdown)=\"$event.preventDefault()\"\n [ngModel]=\"grid()?.options?.pageSize ?? 10\"\n (ngModelChange)=\"updateOption.emit({ field: 'pageSize', value: +$event })\"\n />\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.showRowNumbers ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'showRowNumbers', value: $event })\"\n />\n <span>Show Row Numbers</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.showFooter ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'showFooter', value: $event })\"\n />\n <span>Show Footer Totals</span>\n </label>\n </div>\n </div>\n\n <!-- Row Click & Drill-Down Action Group -->\n <div class=\"property-group\">\n <div class=\"group-title\">Row Click & Drill-Down</div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"hasRowClickAction()\"\n (ngModelChange)=\"toggleRowClickAction($event)\"\n />\n <span>Enable Row Click Action</span>\n </label>\n </div>\n\n @if (hasRowClickAction()) {\n <div class=\"field-row\">\n <label class=\"field-label\">Action Type</label>\n <select\n class=\"field-input field-select\"\n [ngModel]=\"rowClickAction()?.type || 'open-drawer'\"\n (ngModelChange)=\"updateRowActionField('type', $event)\"\n >\n <option value=\"open-drawer\">Slide-Over Detail Drawer</option>\n <option value=\"drilldown-report\">Drill-Down Report (Host Event)</option>\n <option value=\"navigate\">Navigate to URL</option>\n <option value=\"emit-custom\">Emit Custom Action</option>\n </select>\n </div>\n\n @if (rowClickAction()?.type === 'open-drawer' || rowClickAction()?.type === 'drilldown-report') {\n <div class=\"field-row\">\n <label class=\"field-label\">Target Report ID</label>\n <input\n type=\"text\"\n class=\"field-input\"\n placeholder=\"e.g. order-details\"\n [ngModel]=\"rowClickAction()?.targetReportId || ''\"\n (ngModelChange)=\"updateRowActionField('targetReportId', $event)\"\n />\n </div>\n\n <div class=\"field-row\">\n <label class=\"field-label\">Drawer Title</label>\n <input\n type=\"text\"\n class=\"field-input\"\n placeholder=\"e.g. Order Details\"\n [ngModel]=\"rowClickAction()?.drawerTitle || ''\"\n (ngModelChange)=\"updateRowActionField('drawerTitle', $event)\"\n />\n </div>\n\n <div class=\"field-row\">\n <label class=\"field-label\">Drawer Width</label>\n <input\n type=\"text\"\n class=\"field-input\"\n placeholder=\"e.g. 760px, 80%, 900px\"\n [ngModel]=\"rowClickAction()?.drawerWidth || '760px'\"\n (ngModelChange)=\"updateRowActionField('drawerWidth', $event)\"\n />\n </div>\n }\n\n @if (rowClickAction()?.type === 'navigate') {\n <div class=\"field-row\">\n <label class=\"field-label\">Target URL</label>\n <input\n type=\"text\"\n class=\"field-input\"\n [placeholder]=\"'/orders/{{row.orderId}} or https://...'\"\n [ngModel]=\"rowClickAction()?.targetUrl || ''\"\n (ngModelChange)=\"updateRowActionField('targetUrl', $event)\"\n />\n </div>\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"rowClickAction()?.openNewTab ?? false\"\n (ngModelChange)=\"updateRowActionField('openNewTab', $event)\"\n />\n <span>Open in New Tab</span>\n </label>\n </div>\n }\n\n @if (rowClickAction()?.type === 'emit-custom') {\n <div class=\"field-row\">\n <label class=\"field-label\">Custom Action Name</label>\n <input\n type=\"text\"\n class=\"field-input\"\n placeholder=\"e.g. view-audit-trail\"\n [ngModel]=\"rowClickAction()?.customActionName || ''\"\n (ngModelChange)=\"updateRowActionField('customActionName', $event)\"\n />\n </div>\n }\n\n <div class=\"field-row\">\n <div class=\"field-label-row\">\n <label class=\"field-label\">Parameter Mapping</label>\n <button\n type=\"button\"\n class=\"btn-add-mapping\"\n (click)=\"addParameterMapping()\"\n >\n + Add Mapping\n </button>\n </div>\n\n @for (mapping of parameterMappings(); track mapping.key; let mIdx = $index) {\n <div class=\"mapping-pair-row\">\n <input\n type=\"text\"\n class=\"col-mini-input\"\n style=\"flex: 1;\"\n placeholder=\"Target (e.g. customerId)\"\n [ngModel]=\"mapping.key\"\n (ngModelChange)=\"updateMappingKey(mIdx, $event)\"\n />\n <span style=\"font-size: 0.75rem; color: #94a3b8;\">\u2190</span>\n <input\n type=\"text\"\n class=\"col-mini-input\"\n style=\"flex: 1;\"\n placeholder=\"Source (e.g. row.id)\"\n [ngModel]=\"mapping.value\"\n (ngModelChange)=\"updateMappingValue(mIdx, $event)\"\n />\n <button\n type=\"button\"\n class=\"col-action-btn btn-delete\"\n (click)=\"removeMapping(mIdx)\"\n title=\"Remove mapping\"\n >\n <rpt-icon name=\"trash\" [size]=\"11\" />\n </button>\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Remove Grid Action -->\n <div class=\"grid-danger-zone\">\n <button type=\"button\" class=\"btn-remove-grid-action\" (click)=\"removeGrid.emit()\">\n <rpt-icon name=\"trash\" [size]=\"13\" />\n <span>Remove Grid from Report</span>\n </button>\n </div>\n </div>\n }", styles: [".property-group{display:flex;flex-direction:column;gap:12px}.group-title{font-size:.7rem;font-weight:700;color:var(--rpt-color-text-muted, #64748b);text-transform:uppercase;letter-spacing:.05em;line-height:1.2}.field-row{display:flex;flex-direction:column;gap:4px}.field-label-row{display:flex;align-items:center;justify-content:space-between}.field-badge-val{font-size:.65rem;font-weight:600;color:var(--rpt-color-primary, #2563eb);background:#eff6ff;border:1px solid #bfdbfe;padding:1px 6px;border-radius:9999px}.field-label{font-size:.7rem;font-weight:600;color:var(--rpt-color-text, #0f172a)}.field-input,.field-select,.field-textarea{width:100%;box-sizing:border-box;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-sm, 4px);padding:6px 10px;font-size:var(--rpt-font-size-sm, .75rem);color:var(--rpt-color-text, #0f172a);background:#fff;outline:none;transition:all var(--rpt-transition-speed, .15s) ease}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button,.field-input[type=number]::-webkit-inner-spin-button,.field-input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}input[type=number],.field-input[type=number]{-moz-appearance:textfield;appearance:textfield}.field-select{appearance:none;-webkit-appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right 10px center;background-size:13px 13px;padding-right:28px;cursor:pointer}.field-input:focus,.field-select:focus,.field-textarea:focus{border-color:var(--rpt-color-primary, #2563eb);box-shadow:var(--rpt-box-shadow-focus, 0 0 0 3px rgba(37, 99, 235, .25))}.checkbox-toggle{display:inline-flex;align-items:center;gap:8px;font-size:.75rem;font-weight:500;color:var(--rpt-color-text, #0f172a);cursor:pointer}.preset-pill-group{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}.preset-pill{background:#f1f5f9;border:1px solid #e2e8f0;border-radius:4px;padding:2px 8px;font-size:.65rem;font-weight:600;color:#475569;cursor:pointer;transition:all .12s ease}.preset-pill:hover{background:#e2e8f0;color:#0f172a;border-color:#cbd5e1}.preset-pill-active{background:var(--rpt-color-primary, #2563eb)!important;color:#fff!important;border-color:var(--rpt-color-primary, #2563eb)!important}.preset-pill-reset{color:var(--rpt-color-primary, #2563eb);background:#eff6ff;border-color:#bfdbfe}.preset-pill-reset:hover{background:#dbeafe;color:#1d4ed8;border-color:#93c5fd}\n", ".empty-grid-card{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 16px;text-align:center;border:2px dashed #cbd5e1;border-radius:8px;background:#fff;margin:10px 0}.empty-grid-icon{width:48px;height:48px;border-radius:10px;background:#eff6ff;color:#2563eb;display:flex;align-items:center;justify-content:center;margin-bottom:12px}.empty-grid-title{font-size:.95rem;font-weight:700;color:#0f172a;margin:0 0 6px}.empty-grid-desc{font-size:.775rem;color:#64748b;margin:0 0 18px;line-height:1.4;max-width:280px}.btn-create-grid-action{display:inline-flex;align-items:center;gap:6px;padding:8px 16px;background:#2563eb;color:#fff;font-size:.775rem;font-weight:600;border:none;border-radius:6px;cursor:pointer;box-shadow:0 2px 4px #2563eb33;transition:all .15s ease}.btn-create-grid-action:hover{background:#1d4ed8;box-shadow:0 4px 8px #2563eb40}.grid-danger-zone{margin-top:10px;padding-top:16px;border-top:1px dashed #e2e8f0;display:flex;justify-content:center}.btn-remove-grid-action{display:inline-flex;align-items:center;gap:6px;background:#fee2e2;border:1px solid #fecaca;color:#dc2626;font-size:.75rem;font-weight:600;padding:6px 12px;border-radius:6px;cursor:pointer;transition:all .15s ease}.btn-remove-grid-action:hover{background:#fecaca;border-color:#f87171;color:#b91c1c}.props-form{display:flex;flex-direction:column;gap:20px}.group-header-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.group-title-wrap{display:flex;align-items:center;gap:6px}.group-badge{font-size:.65rem;font-weight:700;color:#64748b;background:#f1f5f9;border:1px solid #e2e8f0;border-radius:10px;padding:1px 6px}.columns-action-bar{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:12px}.btn-col-action{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:7px 8px;font-size:.725rem;font-weight:600;border-radius:6px;cursor:pointer;white-space:nowrap;transition:all .12s ease;box-sizing:border-box}.btn-col-action:disabled{opacity:.6;cursor:not-allowed}.btn-auto-detect{background:#eff6ff;border:1px solid #bfdbfe;color:#2563eb}.btn-auto-detect:hover:not(:disabled){background:#2563eb;color:#fff;border-color:#2563eb}.btn-add-col{background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);color:#334155}.btn-add-col:hover{background:#f8fafc;border-color:#94a3b8;color:#0f172a}.col-status-banner{display:flex;align-items:center;gap:6px;padding:6px 10px;border-radius:6px;font-size:.72rem;line-height:1.35;margin-bottom:10px;background:#f1f5f9;color:#334155;border:1px solid #cbd5e1}.col-status-banner.is-success{background:#f0fdf4;color:#166534;border-color:#bbf7d0}.col-status-banner.is-error{background:#fef2f2;color:#991b1b;border-color:#fecaca}@keyframes rpt-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.rpt-spin{animation:rpt-spin 1s linear infinite}.columns-editor-list{display:flex;flex-direction:column;gap:10px}.col-edit-card{background:#fff;border:1.5px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);box-shadow:var(--rpt-box-shadow, 0 1px 2px rgba(0, 0, 0, .05));box-sizing:border-box;transition:all var(--rpt-transition-speed, .15s) ease;position:relative;overflow:hidden;display:flex;flex-direction:column}.col-edit-card:hover{border-color:var(--rpt-color-primary, #2563eb);box-shadow:0 4px 6px -1px #2563eb1a}.col-card-header{display:flex;align-items:center;justify-content:space-between;gap:6px;padding:5px 8px;background:var(--rpt-color-surface, #f8fafc);border-bottom:1px solid var(--rpt-color-border, #e2e8f0);min-height:32px;box-sizing:border-box}.col-header-title-group{display:flex;align-items:center;gap:5px;min-width:0;flex:1}.col-drag-dots{color:#94a3b8;font-size:.8rem;cursor:grab;-webkit-user-select:none;user-select:none;line-height:1;padding-left:2px}.col-idx-badge{font-size:.65rem;font-weight:700;color:#64748b;background:#e2e8f0;padding:1px 5px;border-radius:3px;letter-spacing:.02em}.col-title-input{flex:1;border:1px solid transparent;background:transparent;font-size:.75rem;font-weight:600;color:var(--rpt-color-text, #0f172a);padding:2px 6px;border-radius:4px;outline:none;transition:all .15s ease}.col-title-input:hover{border-color:#cbd5e1;background:#fff}.col-title-input:focus{border-color:var(--rpt-color-primary, #2563eb);background:#fff;box-shadow:0 0 0 2px #2563eb26}.col-card-actions{display:flex;align-items:center;gap:4px}.col-type-pill{font-size:.625rem;font-weight:700;color:#1e40af;background:#dbeafe;border:1px solid #bfdbfe;padding:1px 5px;border-radius:3px;text-transform:uppercase;letter-spacing:.04em}.col-action-btn{display:inline-flex;align-items:center;justify-content:center;background:none;border:none;color:var(--rpt-color-text-muted, #64748b);cursor:pointer;padding:3px;border-radius:4px;transition:all .15s ease}.col-action-btn.btn-delete:hover{color:var(--rpt-color-danger, #ef4444);background:#fee2e2}.col-card-body{padding:8px 10px;display:flex;flex-direction:column;gap:6px;background:#fff}.col-edit-fields{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px 8px}.col-field-item{display:flex;flex-direction:column;gap:2px;min-width:0}.mini-label{font-size:.625rem;font-weight:600;color:#64748b;letter-spacing:.02em}.col-mini-input,.col-mini-select{width:100%;box-sizing:border-box;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:3px;padding:3px 5px;font-size:.7rem;color:var(--rpt-color-text, #0f172a);background:#fff;outline:none;transition:all .12s ease}.col-mini-select{appearance:none;-webkit-appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right 4px center;background-size:10px 10px;padding-right:16px;cursor:pointer}.col-mini-input:focus,.col-mini-select:focus{border-color:var(--rpt-color-primary, #2563eb);box-shadow:0 0 0 2px #2563eb26}.col-card-footer{display:flex;align-items:center;justify-content:flex-end;gap:12px;padding-top:4px;border-top:1px dashed #e2e8f0;margin-top:2px}.col-checkbox-toggle{display:inline-flex;align-items:center;gap:5px;font-size:.675rem;font-weight:500;color:#475569;cursor:pointer;-webkit-user-select:none;user-select:none}.btn-add-mapping{display:inline-flex;align-items:center;background:#eff6ff;border:1px solid #bfdbfe;color:#2563eb;font-size:.65rem;font-weight:600;padding:2px 6px;border-radius:4px;cursor:pointer;transition:all .12s ease}.btn-add-mapping:hover{background:#dbeafe}.mapping-pair-row{display:flex;align-items:center;gap:6px;margin-top:4px}.active-ds-badge{background:#eff6ff;color:#2563eb;font-size:.65rem;font-weight:700;padding:1px 6px;border-radius:4px;border:1px solid #bfdbfe;max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.source-type-switcher{display:flex;background:#f1f5f9;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:6px;padding:2px;gap:2px;margin-bottom:12px}.source-tab-btn{flex:1;display:inline-flex;align-items:center;justify-content:center;gap:5px;padding:6px 4px;font-size:.725rem;font-weight:600;color:var(--rpt-color-text-muted, #64748b);background:transparent;border:none;border-radius:4px;cursor:pointer;transition:all .12s ease}.source-tab-btn:hover{color:var(--rpt-color-text, #0f172a)}.source-tab-btn.active{background:#fff;color:var(--rpt-color-primary, #2563eb);box-shadow:0 1px 2px #0000000f}.procedure-params-section{margin-top:14px;padding:0;background:transparent;border:none}.params-section-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.btn-paste-exec{display:inline-flex;align-items:center;gap:4px;font-size:.675rem;font-weight:600;color:#2563eb;background:#eff6ff;border:1px solid #bfdbfe;border-radius:4px;padding:3px 8px;cursor:pointer;transition:all .12s ease}.btn-paste-exec:hover{background:#2563eb;color:#fff}.exec-snippet-panel{display:flex;flex-direction:column;gap:6px;background:#f8fafc;border:1px solid #cbd5e1;border-radius:6px;padding:8px 10px;margin-bottom:10px}.snippet-hint{font-size:.675rem;color:#475569}.snippet-hint code{background:#e2e8f0;padding:1px 4px;border-radius:3px;font-family:monospace}.exec-snippet-textarea{width:100%;box-sizing:border-box;font-family:monospace;font-size:.7rem;padding:6px 8px;border:1px solid #cbd5e1;border-radius:4px;background:#fff;resize:vertical;min-height:54px}.exec-snippet-actions{display:flex;align-items:center;gap:6px}.btn-snippet-apply{display:inline-flex;align-items:center;gap:4px;font-size:.7rem;font-weight:600;padding:4px 10px;background:#16a34a;color:#fff;border:none;border-radius:4px;cursor:pointer}.btn-snippet-apply:hover{background:#15803d}.btn-snippet-cancel{font-size:.7rem;padding:4px 8px;background:#f1f5f9;color:#475569;border:1px solid #cbd5e1;border-radius:4px;cursor:pointer}.snippet-status{font-size:.675rem;padding:4px 6px;border-radius:4px}.snippet-status.is-success{background:#f0fdf4;color:#166534;border:1px solid #bbf7d0}.snippet-status.is-error{background:#fef2f2;color:#991b1b;border:1px solid #fecaca}.param-cards-list{display:flex;flex-direction:column;gap:10px;width:100%;box-sizing:border-box}.empty-params-hint{font-size:.725rem;color:#64748b;font-style:italic;padding:12px;text-align:center;background:#f8fafc;border:1px dashed #cbd5e1;border-radius:6px}\n"] }]
|
|
16114
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: " @if (!grid()) {\n <div class=\"empty-grid-card\">\n <div class=\"empty-grid-icon\">\n <rpt-icon name=\"table\" [size]=\"28\" />\n </div>\n <h4 class=\"empty-grid-title\">No Result Grid Attached</h4>\n <p class=\"empty-grid-desc\">Attach an interactive Result Data Grid (or UltraGrid) to this report definition to render queries and stored procedure results.</p>\n <button type=\"button\" class=\"btn-create-grid-action\" (click)=\"addGrid.emit()\">\n <rpt-icon name=\"plus\" [size]=\"14\" />\n <span>+ Add Result Grid</span>\n </button>\n </div>\n } @else {\n <div class=\"props-form\">\n <!-- 1. Report Data Source & Stored Procedure Configuration -->\n <div class=\"property-group\">\n <div class=\"group-header-row\">\n <div class=\"group-title-wrap\">\n <span class=\"group-title\">Report Data Source</span>\n @if (activeEntityName()) {\n <span class=\"group-badge active-ds-badge\">{{ activeEntityName() }}</span>\n }\n </div>\n </div>\n\n <!-- Source Type Switcher (Stored Procedure vs Table vs REST API) -->\n <div class=\"source-type-switcher\">\n <button\n type=\"button\"\n class=\"source-tab-btn\"\n [class.active]=\"currentSourceType() === 'stored_procedure'\"\n (click)=\"onSourceTypeChange('stored_procedure')\"\n >\n <rpt-icon name=\"code\" [size]=\"12\" />\n <span>Stored Procedure</span>\n </button>\n <button\n type=\"button\"\n class=\"source-tab-btn\"\n [class.active]=\"currentSourceType() === 'table'\"\n (click)=\"onSourceTypeChange('table')\"\n >\n <rpt-icon name=\"table\" [size]=\"12\" />\n <span>Table / View</span>\n </button>\n <button\n type=\"button\"\n class=\"source-tab-btn\"\n [class.active]=\"currentSourceType() === 'api'\"\n (click)=\"onSourceTypeChange('api')\"\n >\n <rpt-icon name=\"globe\" [size]=\"12\" />\n <span>REST API</span>\n </button>\n </div>\n\n @if (currentSourceType() === 'api') {\n <!-- API Endpoint Selector / Input -->\n <div class=\"field-row\">\n <label class=\"field-label\">API Endpoint URL</label>\n @if (registeredApis().length > 0) {\n <div style=\"margin-bottom: 6px;\">\n <rpt-searchable-select\n [options]=\"registeredApis()\"\n [ngModel]=\"apiEndpoint()\"\n (ngModelChange)=\"onApiEndpointChange($event)\"\n placeholder=\"-- Select Registered Endpoint or type below --\"\n searchPlaceholder=\"Search registered APIs...\"\n />\n </div>\n }\n <input\n type=\"text\"\n class=\"form-control\"\n placeholder=\"e.g. /api/v1/orders or https://api.service.com/data\"\n [ngModel]=\"apiEndpoint()\"\n (ngModelChange)=\"onApiEndpointChange($event)\"\n />\n <span class=\"field-hint\" style=\"font-size: 11px; color: var(--rpt-color-text-muted, #64748b); margin-top: 4px; display: block;\">\n Direct HTTP call or backend proxy. Use <code>:param</code> for route tokens.\n </span>\n </div>\n\n <!-- HTTP Method & Data Path Grid -->\n <div class=\"field-grid-2\" style=\"display: grid; grid-template-columns: 1fr 1fr; gap: 8px;\">\n <div class=\"field-row\">\n <label class=\"field-label\">HTTP Method</label>\n <select\n class=\"form-control\"\n [ngModel]=\"apiMethod()\"\n (ngModelChange)=\"onApiMethodChange($event)\"\n >\n <option value=\"GET\">GET (Query String)</option>\n <option value=\"POST\">POST (Body Payload)</option>\n </select>\n </div>\n <div class=\"field-row\">\n <label class=\"field-label\">Response Data Path</label>\n <input\n type=\"text\"\n class=\"form-control\"\n placeholder=\"e.g. data.items, rows\"\n [ngModel]=\"apiDataPath()\"\n (ngModelChange)=\"onApiDataPathChange($event)\"\n />\n </div>\n </div>\n\n <!-- Use Proxy Toggle -->\n <div class=\"field-row checkbox-field-row\" style=\"margin-top: 4px;\">\n <label class=\"checkbox-label\" style=\"display: flex; align-items: center; gap: 6px; font-size: 12px; cursor: pointer;\">\n <input\n type=\"checkbox\"\n [checked]=\"apiUseProxy()\"\n (change)=\"onApiUseProxyChange($any($event.target).checked)\"\n />\n <span class=\"checkbox-text\">Route through backend proxy (bypasses CORS / hides credentials)</span>\n </label>\n </div>\n } @else {\n <!-- Entity Selector Dropdown -->\n <div class=\"field-row\">\n <label class=\"field-label\">\n {{ currentSourceType() === 'stored_procedure' ? 'Stored Procedure' : 'Table / View' }}\n </label>\n <rpt-searchable-select\n [options]=\"gridEntitySelectOptions()\"\n [ngModel]=\"activeEntityName()\"\n (ngModelChange)=\"onSelectEntity($event)\"\n placeholder=\"-- Choose Data Source --\"\n [searchPlaceholder]=\"currentSourceType() === 'stored_procedure' ? 'Search procedures...' : 'Search tables & views...'\"\n />\n </div>\n }\n\n <!-- Stored Procedure Parameter Mappings -->\n @if (currentSourceType() === 'stored_procedure' && activeEntityName()) {\n <div class=\"procedure-params-section\">\n <div class=\"params-section-header\">\n <div class=\"params-header-left\">\n <span class=\"params-title\">Procedure Parameters</span>\n <span class=\"params-count-badge\">{{ effectiveParameters().length }}</span>\n </div>\n <button\n type=\"button\"\n class=\"btn-paste-exec\"\n (click)=\"toggleExecSnippet()\"\n title=\"Paste full SSMS 'EXEC proc @param=val' snippet to fill parameters automatically\"\n >\n <rpt-icon name=\"code\" [size]=\"12\" />\n <span>{{ isExecSnippetOpen() ? 'Close SQL' : 'Paste SQL Exec' }}</span>\n </button>\n </div>\n\n @if (isExecSnippetOpen()) {\n <div class=\"exec-snippet-panel\">\n <div class=\"exec-snippet-header\">\n <span class=\"snippet-hint\">Paste full <code>EXEC ProcedureName @param=val...</code> statement from SSMS:</span>\n </div>\n <textarea\n class=\"exec-snippet-textarea\"\n rows=\"3\"\n placeholder=\"e.g. exec Get_HelpList @chrCOMPANYID=N'101',@chrTableName=N'MSTACCTGLSUB'...\"\n [ngModel]=\"execSnippetText()\"\n (ngModelChange)=\"execSnippetText.set($event)\"\n ></textarea>\n <div class=\"exec-snippet-actions\">\n <button type=\"button\" class=\"btn-snippet-apply\" (click)=\"applyExecSnippet()\">\n <rpt-icon name=\"check\" [size]=\"12\" />\n <span>Apply Parameters</span>\n </button>\n <button type=\"button\" class=\"btn-snippet-cancel\" (click)=\"toggleExecSnippet()\">\n Cancel\n </button>\n </div>\n @if (execParseStatus()) {\n <div class=\"snippet-status\" [class.is-error]=\"execParseStatus()?.kind === 'error'\" [class.is-success]=\"execParseStatus()?.kind === 'success'\">\n {{ execParseStatus()?.message }}\n </div>\n }\n </div>\n }\n\n @if (effectiveParameters().length > 0) {\n <div class=\"param-cards-list\">\n @for (param of effectiveParameters(); track param.name) {\n <rpt-parameter-binding-editor\n [param]=\"param\"\n [currentValue]=\"getParameterValue(param.name)\"\n [allComponents]=\"allComponents()\"\n (valueChange)=\"onParameterChange(param.name, $event)\"\n />\n }\n </div>\n } @else {\n <div class=\"empty-params-hint\">\n <span>No required parameters for this procedure.</span>\n </div>\n }\n </div>\n }\n </div>\n\n <!-- 2. Result Columns Configuration -->\n <div class=\"property-group\">\n <div class=\"group-header-row\">\n <div class=\"group-title-wrap\">\n <span class=\"group-title\">Columns</span>\n <span class=\"group-badge\">{{ columns().length }}</span>\n </div>\n </div>\n\n <!-- Dedicated Full-Width Action Buttons (No cramping) -->\n <div class=\"columns-action-bar\">\n <button\n type=\"button\"\n class=\"btn-col-action btn-auto-detect\"\n (click)=\"autoDetectColumns.emit()\"\n [disabled]=\"isDetecting()\"\n title=\"Auto-detect all columns from procedure or data table\"\n >\n <rpt-icon name=\"refresh\" [size]=\"12\" [class.rpt-spin]=\"isDetecting()\" />\n <span>{{ isDetecting() ? 'Detecting...' : 'Auto-Detect' }}</span>\n </button>\n <button\n type=\"button\"\n class=\"btn-col-action btn-add-col\"\n (click)=\"addColumn.emit()\"\n title=\"Add a new column manually\"\n >\n <rpt-icon name=\"plus\" [size]=\"12\" />\n <span>+ Add Column</span>\n </button>\n </div>\n\n @if (statusMessage()) {\n <div class=\"col-status-banner\" [class.is-error]=\"statusKind() === 'error'\" [class.is-success]=\"statusKind() === 'success'\">\n <rpt-icon [name]=\"statusKind() === 'error' ? 'close' : 'check'\" [size]=\"12\" />\n <span>{{ statusMessage() }}</span>\n </div>\n }\n\n <div class=\"columns-editor-list\">\n @for (col of columns(); track col.field; let idx = $index) {\n <div class=\"col-edit-card\">\n <div class=\"col-card-header\">\n <div class=\"col-header-title-group\">\n <span class=\"col-drag-dots\">\u22EE\u22EE</span>\n <span class=\"col-idx-badge\">#{{ idx + 1 }}</span>\n <input\n type=\"text\"\n class=\"col-title-input\"\n placeholder=\"Column Label\"\n [ngModel]=\"col.label\"\n (ngModelChange)=\"onUpdateCol(idx, 'label', $event)\"\n />\n </div>\n <div class=\"col-card-actions\">\n <span class=\"col-type-pill\">{{ col.dataType || 'string' }}</span>\n <button\n type=\"button\"\n class=\"col-action-btn btn-delete\"\n (click)=\"removeColumn.emit(idx)\"\n title=\"Remove Column\"\n >\n <rpt-icon name=\"trash\" [size]=\"12\" />\n </button>\n </div>\n </div>\n\n <div class=\"col-card-body\">\n <div class=\"col-edit-fields\">\n <div class=\"col-field-item\">\n <span class=\"mini-label\">Field</span>\n <input\n type=\"text\"\n class=\"col-mini-input\"\n placeholder=\"field\"\n [ngModel]=\"col.field\"\n (ngModelChange)=\"onUpdateCol(idx, 'field', $event)\"\n />\n </div>\n <div class=\"col-field-item\">\n <span class=\"mini-label\">Type</span>\n <select\n class=\"col-mini-select\"\n [ngModel]=\"col.dataType || 'string'\"\n (ngModelChange)=\"onUpdateCol(idx, 'dataType', $event)\"\n >\n <option value=\"string\">String</option>\n <option value=\"number\">Number</option>\n <option value=\"currency\">Currency</option>\n <option value=\"date\">Date</option>\n <option value=\"boolean\">Boolean</option>\n </select>\n </div>\n <div class=\"col-field-item\">\n <span class=\"mini-label\">Format</span>\n <input\n type=\"text\"\n class=\"col-mini-input\"\n placeholder=\"e.g. dd/MM/yyyy\"\n [ngModel]=\"col.format\"\n (ngModelChange)=\"onUpdateCol(idx, 'format', $event)\"\n />\n </div>\n <div class=\"col-field-item\">\n <span class=\"mini-label\">Align</span>\n <select\n class=\"col-mini-select\"\n [ngModel]=\"col.align || 'left'\"\n (ngModelChange)=\"onUpdateCol(idx, 'align', $event)\"\n >\n <option value=\"left\">Left</option>\n <option value=\"center\">Center</option>\n <option value=\"right\">Right</option>\n </select>\n </div>\n <div class=\"col-field-item\">\n <span class=\"mini-label\">Aggregate</span>\n <select\n class=\"col-mini-select\"\n [ngModel]=\"col.aggregate || 'none'\"\n (ngModelChange)=\"onUpdateCol(idx, 'aggregate', $event === 'none' ? undefined : $event)\"\n >\n <option value=\"none\">None</option>\n <option value=\"sum\">Sum</option>\n <option value=\"avg\">Average</option>\n <option value=\"count\">Count</option>\n <option value=\"min\">Min</option>\n <option value=\"max\">Max</option>\n </select>\n </div>\n <div class=\"col-field-item\">\n <span class=\"mini-label\">Width</span>\n <input\n type=\"text\"\n class=\"col-mini-input\"\n placeholder=\"auto\"\n [ngModel]=\"col.width || ''\"\n (ngModelChange)=\"onUpdateCol(idx, 'width', $event || undefined)\"\n />\n </div>\n </div>\n\n <div class=\"col-card-footer\">\n <label class=\"col-checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"col.visible !== false\"\n (ngModelChange)=\"onUpdateCol(idx, 'visible', $event)\"\n />\n <span>Visible</span>\n </label>\n <label class=\"col-checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"col.checkboxSelection ?? false\"\n (ngModelChange)=\"onUpdateCol(idx, 'checkboxSelection', $event)\"\n />\n <span>Checkbox</span>\n </label>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n\n <!-- Grid Table Options Group -->\n <div class=\"property-group\">\n <div class=\"group-title\">Table Options</div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.sideBar !== false\"\n (ngModelChange)=\"updateOption.emit({ field: 'sideBar', value: $event })\"\n />\n <span>Enable Side Panel (Tool Panels)</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.globalSearch !== false\"\n (ngModelChange)=\"updateOption.emit({ field: 'globalSearch', value: $event })\"\n />\n <span>Global Search Box</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.statusBar !== false\"\n (ngModelChange)=\"updateOption.emit({ field: 'statusBar', value: $event })\"\n />\n <span>Status Bar</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.pagination ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'pagination', value: $event })\"\n />\n <span>Enable Pagination</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.selectable ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'selectable', value: $event })\"\n />\n <span>Row Selection</span>\n </label>\n </div>\n\n @if (grid()?.options?.selectable ?? true) {\n <div class=\"field-row\">\n <label class=\"field-label\">Selection Mode</label>\n <select\n class=\"field-input\"\n [ngModel]=\"grid()?.options?.selectionMode ?? grid()?.options?.rowSelection ?? 'multiple'\"\n (ngModelChange)=\"updateOption.emit({ field: 'selectionMode', value: $event })\"\n >\n <option value=\"multiple\">Multiple Rows</option>\n <option value=\"single\">Single Row</option>\n </select>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.checkboxSelection ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'checkboxSelection', value: $event })\"\n />\n <span>Checkbox Selection</span>\n </label>\n </div>\n\n @if ((grid()?.options?.selectionMode ?? 'multiple') === 'multiple') {\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.headerCheckboxSelection ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'headerCheckboxSelection', value: $event })\"\n />\n <span>Header Select All Checkbox</span>\n </label>\n </div>\n }\n }\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.allowColumnReorder ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'allowColumnReorder', value: $event })\"\n />\n <span>Allow Column Reorder</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.allowExport ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'allowExport', value: $event })\"\n />\n <span>Allow Export</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"field-label\">Page Size</label>\n <input\n type=\"number\"\n class=\"field-input\"\n min=\"1\"\n max=\"1000\"\n (wheel)=\"$any($event.target).blur()\"\n (keydown.arrowup)=\"$event.preventDefault()\"\n (keydown.arrowdown)=\"$event.preventDefault()\"\n [ngModel]=\"grid()?.options?.pageSize ?? 10\"\n (ngModelChange)=\"updateOption.emit({ field: 'pageSize', value: +$event })\"\n />\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.showRowNumbers ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'showRowNumbers', value: $event })\"\n />\n <span>Show Row Numbers</span>\n </label>\n </div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"grid()?.options?.showFooter ?? true\"\n (ngModelChange)=\"updateOption.emit({ field: 'showFooter', value: $event })\"\n />\n <span>Show Footer Totals</span>\n </label>\n </div>\n </div>\n\n <!-- Row Click & Drill-Down Action Group -->\n <div class=\"property-group\">\n <div class=\"group-title\">Row Click & Drill-Down</div>\n\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"hasRowClickAction()\"\n (ngModelChange)=\"toggleRowClickAction($event)\"\n />\n <span>Enable Row Click Action</span>\n </label>\n </div>\n\n @if (hasRowClickAction()) {\n <div class=\"field-row\">\n <label class=\"field-label\">Action Type</label>\n <select\n class=\"field-input field-select\"\n [ngModel]=\"rowClickAction()?.type || 'open-drawer'\"\n (ngModelChange)=\"updateRowActionField('type', $event)\"\n >\n <option value=\"open-drawer\">Slide-Over Detail Drawer</option>\n <option value=\"drilldown-report\">Drill-Down Report (Host Event)</option>\n <option value=\"navigate\">Navigate to URL</option>\n <option value=\"emit-custom\">Emit Custom Action</option>\n </select>\n </div>\n\n @if (rowClickAction()?.type === 'open-drawer' || rowClickAction()?.type === 'drilldown-report') {\n <div class=\"field-row\">\n <label class=\"field-label\">Target Report ID</label>\n <input\n type=\"text\"\n class=\"field-input\"\n placeholder=\"e.g. order-details\"\n [ngModel]=\"rowClickAction()?.targetReportId || ''\"\n (ngModelChange)=\"updateRowActionField('targetReportId', $event)\"\n />\n </div>\n\n <div class=\"field-row\">\n <label class=\"field-label\">Drawer Title</label>\n <input\n type=\"text\"\n class=\"field-input\"\n placeholder=\"e.g. Order Details\"\n [ngModel]=\"rowClickAction()?.drawerTitle || ''\"\n (ngModelChange)=\"updateRowActionField('drawerTitle', $event)\"\n />\n </div>\n\n <div class=\"field-row\">\n <label class=\"field-label\">Drawer Width</label>\n <input\n type=\"text\"\n class=\"field-input\"\n placeholder=\"e.g. 760px, 80%, 900px\"\n [ngModel]=\"rowClickAction()?.drawerWidth || '760px'\"\n (ngModelChange)=\"updateRowActionField('drawerWidth', $event)\"\n />\n </div>\n }\n\n @if (rowClickAction()?.type === 'navigate') {\n <div class=\"field-row\">\n <label class=\"field-label\">Target URL</label>\n <input\n type=\"text\"\n class=\"field-input\"\n [placeholder]=\"'/orders/{{row.orderId}} or https://...'\"\n [ngModel]=\"rowClickAction()?.targetUrl || ''\"\n (ngModelChange)=\"updateRowActionField('targetUrl', $event)\"\n />\n </div>\n <div class=\"field-row\">\n <label class=\"checkbox-toggle\">\n <input\n type=\"checkbox\"\n [ngModel]=\"rowClickAction()?.openNewTab ?? false\"\n (ngModelChange)=\"updateRowActionField('openNewTab', $event)\"\n />\n <span>Open in New Tab</span>\n </label>\n </div>\n }\n\n @if (rowClickAction()?.type === 'emit-custom') {\n <div class=\"field-row\">\n <label class=\"field-label\">Custom Action Name</label>\n <input\n type=\"text\"\n class=\"field-input\"\n placeholder=\"e.g. view-audit-trail\"\n [ngModel]=\"rowClickAction()?.customActionName || ''\"\n (ngModelChange)=\"updateRowActionField('customActionName', $event)\"\n />\n </div>\n }\n\n <div class=\"field-row\">\n <div class=\"field-label-row\">\n <label class=\"field-label\">Parameter Mapping</label>\n <button\n type=\"button\"\n class=\"btn-add-mapping\"\n (click)=\"addParameterMapping()\"\n >\n + Add Mapping\n </button>\n </div>\n\n @for (mapping of parameterMappings(); track mapping.key; let mIdx = $index) {\n <div class=\"mapping-pair-row\">\n <input\n type=\"text\"\n class=\"col-mini-input\"\n style=\"flex: 1;\"\n placeholder=\"Target (e.g. customerId)\"\n [ngModel]=\"mapping.key\"\n (ngModelChange)=\"updateMappingKey(mIdx, $event)\"\n />\n <span style=\"font-size: 0.75rem; color: #94a3b8;\">\u2190</span>\n <input\n type=\"text\"\n class=\"col-mini-input\"\n style=\"flex: 1;\"\n placeholder=\"Source (e.g. row.id)\"\n [ngModel]=\"mapping.value\"\n (ngModelChange)=\"updateMappingValue(mIdx, $event)\"\n />\n <button\n type=\"button\"\n class=\"col-action-btn btn-delete\"\n (click)=\"removeMapping(mIdx)\"\n title=\"Remove mapping\"\n >\n <rpt-icon name=\"trash\" [size]=\"11\" />\n </button>\n </div>\n }\n </div>\n }\n </div>\n\n <!-- Remove Grid Action -->\n <div class=\"grid-danger-zone\">\n <button type=\"button\" class=\"btn-remove-grid-action\" (click)=\"removeGrid.emit()\">\n <rpt-icon name=\"trash\" [size]=\"13\" />\n <span>Remove Grid from Report</span>\n </button>\n </div>\n </div>\n }", styles: [".property-group{display:flex;flex-direction:column;gap:12px}.group-title{font-size:.7rem;font-weight:700;color:var(--rpt-color-text-muted, #64748b);text-transform:uppercase;letter-spacing:.05em;line-height:1.2}.field-row{display:flex;flex-direction:column;gap:4px}.field-label-row{display:flex;align-items:center;justify-content:space-between}.field-badge-val{font-size:.65rem;font-weight:600;color:var(--rpt-color-primary, #2563eb);background:#eff6ff;border:1px solid #bfdbfe;padding:1px 6px;border-radius:9999px}.field-label{font-size:.7rem;font-weight:600;color:var(--rpt-color-text, #0f172a)}.field-input,.field-select,.field-textarea{width:100%;box-sizing:border-box;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius-sm, 4px);padding:6px 10px;font-size:var(--rpt-font-size-sm, .75rem);color:var(--rpt-color-text, #0f172a);background:#fff;outline:none;transition:all var(--rpt-transition-speed, .15s) ease}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button,.field-input[type=number]::-webkit-inner-spin-button,.field-input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}input[type=number],.field-input[type=number]{-moz-appearance:textfield;appearance:textfield}.field-select{appearance:none;-webkit-appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right 10px center;background-size:13px 13px;padding-right:28px;cursor:pointer}.field-input:focus,.field-select:focus,.field-textarea:focus{border-color:var(--rpt-color-primary, #2563eb);box-shadow:var(--rpt-box-shadow-focus, 0 0 0 3px rgba(37, 99, 235, .25))}.checkbox-toggle{display:inline-flex;align-items:center;gap:8px;font-size:.75rem;font-weight:500;color:var(--rpt-color-text, #0f172a);cursor:pointer}.preset-pill-group{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}.preset-pill{background:#f1f5f9;border:1px solid #e2e8f0;border-radius:4px;padding:2px 8px;font-size:.65rem;font-weight:600;color:#475569;cursor:pointer;transition:all .12s ease}.preset-pill:hover{background:#e2e8f0;color:#0f172a;border-color:#cbd5e1}.preset-pill-active{background:var(--rpt-color-primary, #2563eb)!important;color:#fff!important;border-color:var(--rpt-color-primary, #2563eb)!important}.preset-pill-reset{color:var(--rpt-color-primary, #2563eb);background:#eff6ff;border-color:#bfdbfe}.preset-pill-reset:hover{background:#dbeafe;color:#1d4ed8;border-color:#93c5fd}\n", ".empty-grid-card{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 16px;text-align:center;border:2px dashed #cbd5e1;border-radius:8px;background:#fff;margin:10px 0}.empty-grid-icon{width:48px;height:48px;border-radius:10px;background:#eff6ff;color:#2563eb;display:flex;align-items:center;justify-content:center;margin-bottom:12px}.empty-grid-title{font-size:.95rem;font-weight:700;color:#0f172a;margin:0 0 6px}.empty-grid-desc{font-size:.775rem;color:#64748b;margin:0 0 18px;line-height:1.4;max-width:280px}.btn-create-grid-action{display:inline-flex;align-items:center;gap:6px;padding:8px 16px;background:#2563eb;color:#fff;font-size:.775rem;font-weight:600;border:none;border-radius:6px;cursor:pointer;box-shadow:0 2px 4px #2563eb33;transition:all .15s ease}.btn-create-grid-action:hover{background:#1d4ed8;box-shadow:0 4px 8px #2563eb40}.grid-danger-zone{margin-top:10px;padding-top:16px;border-top:1px dashed #e2e8f0;display:flex;justify-content:center}.btn-remove-grid-action{display:inline-flex;align-items:center;gap:6px;background:#fee2e2;border:1px solid #fecaca;color:#dc2626;font-size:.75rem;font-weight:600;padding:6px 12px;border-radius:6px;cursor:pointer;transition:all .15s ease}.btn-remove-grid-action:hover{background:#fecaca;border-color:#f87171;color:#b91c1c}.props-form{display:flex;flex-direction:column;gap:20px}.group-header-row{display:flex;align-items:center;justify-content:space-between;margin-bottom:6px}.group-title-wrap{display:flex;align-items:center;gap:6px}.group-badge{font-size:.65rem;font-weight:700;color:#64748b;background:#f1f5f9;border:1px solid #e2e8f0;border-radius:10px;padding:1px 6px}.columns-action-bar{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:12px}.btn-col-action{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:7px 8px;font-size:.725rem;font-weight:600;border-radius:6px;cursor:pointer;white-space:nowrap;transition:all .12s ease;box-sizing:border-box}.btn-col-action:disabled{opacity:.6;cursor:not-allowed}.btn-auto-detect{background:#eff6ff;border:1px solid #bfdbfe;color:#2563eb}.btn-auto-detect:hover:not(:disabled){background:#2563eb;color:#fff;border-color:#2563eb}.btn-add-col{background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);color:#334155}.btn-add-col:hover{background:#f8fafc;border-color:#94a3b8;color:#0f172a}.col-status-banner{display:flex;align-items:center;gap:6px;padding:6px 10px;border-radius:6px;font-size:.72rem;line-height:1.35;margin-bottom:10px;background:#f1f5f9;color:#334155;border:1px solid #cbd5e1}.col-status-banner.is-success{background:#f0fdf4;color:#166534;border-color:#bbf7d0}.col-status-banner.is-error{background:#fef2f2;color:#991b1b;border-color:#fecaca}@keyframes rpt-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.rpt-spin{animation:rpt-spin 1s linear infinite}.columns-editor-list{display:flex;flex-direction:column;gap:10px}.col-edit-card{background:#fff;border:1.5px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);box-shadow:var(--rpt-box-shadow, 0 1px 2px rgba(0, 0, 0, .05));box-sizing:border-box;transition:all var(--rpt-transition-speed, .15s) ease;position:relative;overflow:hidden;display:flex;flex-direction:column}.col-edit-card:hover{border-color:var(--rpt-color-primary, #2563eb);box-shadow:0 4px 6px -1px #2563eb1a}.col-card-header{display:flex;align-items:center;justify-content:space-between;gap:6px;padding:5px 8px;background:var(--rpt-color-surface, #f8fafc);border-bottom:1px solid var(--rpt-color-border, #e2e8f0);min-height:32px;box-sizing:border-box}.col-header-title-group{display:flex;align-items:center;gap:5px;min-width:0;flex:1}.col-drag-dots{color:#94a3b8;font-size:.8rem;cursor:grab;-webkit-user-select:none;user-select:none;line-height:1;padding-left:2px}.col-idx-badge{font-size:.65rem;font-weight:700;color:#64748b;background:#e2e8f0;padding:1px 5px;border-radius:3px;letter-spacing:.02em}.col-title-input{flex:1;border:1px solid transparent;background:transparent;font-size:.75rem;font-weight:600;color:var(--rpt-color-text, #0f172a);padding:2px 6px;border-radius:4px;outline:none;transition:all .15s ease}.col-title-input:hover{border-color:#cbd5e1;background:#fff}.col-title-input:focus{border-color:var(--rpt-color-primary, #2563eb);background:#fff;box-shadow:0 0 0 2px #2563eb26}.col-card-actions{display:flex;align-items:center;gap:4px}.col-type-pill{font-size:.625rem;font-weight:700;color:#1e40af;background:#dbeafe;border:1px solid #bfdbfe;padding:1px 5px;border-radius:3px;text-transform:uppercase;letter-spacing:.04em}.col-action-btn{display:inline-flex;align-items:center;justify-content:center;background:none;border:none;color:var(--rpt-color-text-muted, #64748b);cursor:pointer;padding:3px;border-radius:4px;transition:all .15s ease}.col-action-btn.btn-delete:hover{color:var(--rpt-color-danger, #ef4444);background:#fee2e2}.col-card-body{padding:8px 10px;display:flex;flex-direction:column;gap:6px;background:#fff}.col-edit-fields{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px 8px}.col-field-item{display:flex;flex-direction:column;gap:2px;min-width:0}.mini-label{font-size:.625rem;font-weight:600;color:#64748b;letter-spacing:.02em}.col-mini-input,.col-mini-select{width:100%;box-sizing:border-box;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:3px;padding:3px 5px;font-size:.7rem;color:var(--rpt-color-text, #0f172a);background:#fff;outline:none;transition:all .12s ease}.col-mini-select{appearance:none;-webkit-appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right 4px center;background-size:10px 10px;padding-right:16px;cursor:pointer}.col-mini-input:focus,.col-mini-select:focus{border-color:var(--rpt-color-primary, #2563eb);box-shadow:0 0 0 2px #2563eb26}.col-card-footer{display:flex;align-items:center;justify-content:flex-end;gap:12px;padding-top:4px;border-top:1px dashed #e2e8f0;margin-top:2px}.col-checkbox-toggle{display:inline-flex;align-items:center;gap:5px;font-size:.675rem;font-weight:500;color:#475569;cursor:pointer;-webkit-user-select:none;user-select:none}.btn-add-mapping{display:inline-flex;align-items:center;background:#eff6ff;border:1px solid #bfdbfe;color:#2563eb;font-size:.65rem;font-weight:600;padding:2px 6px;border-radius:4px;cursor:pointer;transition:all .12s ease}.btn-add-mapping:hover{background:#dbeafe}.mapping-pair-row{display:flex;align-items:center;gap:6px;margin-top:4px}.active-ds-badge{background:#eff6ff;color:#2563eb;font-size:.65rem;font-weight:700;padding:1px 6px;border-radius:4px;border:1px solid #bfdbfe;max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.source-type-switcher{display:flex;background:#f1f5f9;border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:6px;padding:2px;gap:2px;margin-bottom:12px}.source-tab-btn{flex:1;display:inline-flex;align-items:center;justify-content:center;gap:5px;padding:6px 4px;font-size:.725rem;font-weight:600;color:var(--rpt-color-text-muted, #64748b);background:transparent;border:none;border-radius:4px;cursor:pointer;transition:all .12s ease}.source-tab-btn:hover{color:var(--rpt-color-text, #0f172a)}.source-tab-btn.active{background:#fff;color:var(--rpt-color-primary, #2563eb);box-shadow:0 1px 2px #0000000f}.procedure-params-section{margin-top:14px;padding:0;background:transparent;border:none}.params-section-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.btn-paste-exec{display:inline-flex;align-items:center;gap:4px;font-size:.675rem;font-weight:600;color:#2563eb;background:#eff6ff;border:1px solid #bfdbfe;border-radius:4px;padding:3px 8px;cursor:pointer;transition:all .12s ease}.btn-paste-exec:hover{background:#2563eb;color:#fff}.exec-snippet-panel{display:flex;flex-direction:column;gap:6px;background:#f8fafc;border:1px solid #cbd5e1;border-radius:6px;padding:8px 10px;margin-bottom:10px}.snippet-hint{font-size:.675rem;color:#475569}.snippet-hint code{background:#e2e8f0;padding:1px 4px;border-radius:3px;font-family:monospace}.exec-snippet-textarea{width:100%;box-sizing:border-box;font-family:monospace;font-size:.7rem;padding:6px 8px;border:1px solid #cbd5e1;border-radius:4px;background:#fff;resize:vertical;min-height:54px}.exec-snippet-actions{display:flex;align-items:center;gap:6px}.btn-snippet-apply{display:inline-flex;align-items:center;gap:4px;font-size:.7rem;font-weight:600;padding:4px 10px;background:#16a34a;color:#fff;border:none;border-radius:4px;cursor:pointer}.btn-snippet-apply:hover{background:#15803d}.btn-snippet-cancel{font-size:.7rem;padding:4px 8px;background:#f1f5f9;color:#475569;border:1px solid #cbd5e1;border-radius:4px;cursor:pointer}.snippet-status{font-size:.675rem;padding:4px 6px;border-radius:4px}.snippet-status.is-success{background:#f0fdf4;color:#166534;border:1px solid #bbf7d0}.snippet-status.is-error{background:#fef2f2;color:#991b1b;border:1px solid #fecaca}.param-cards-list{display:flex;flex-direction:column;gap:10px;width:100%;box-sizing:border-box}.empty-params-hint{font-size:.725rem;color:#64748b;font-style:italic;padding:12px;text-align:center;background:#f8fafc;border:1px dashed #cbd5e1;border-radius:6px}\n"] }]
|
|
15870
16115
|
}], ctorParameters: () => [] });
|
|
15871
16116
|
|
|
15872
16117
|
class PropertyEditorReportComponent {
|
|
@@ -16044,6 +16289,7 @@ class PropertyEditorDatasourceComponent {
|
|
|
16044
16289
|
entitiesSub;
|
|
16045
16290
|
discoveryAdapter = inject(ReportSchemaDiscoveryAdapter, { optional: true });
|
|
16046
16291
|
sessionService = inject(ReportSessionContextService, { optional: true });
|
|
16292
|
+
reportingConfig = inject(REPORTING_CONFIG, { optional: true });
|
|
16047
16293
|
designerState = inject(DesignerStateService, { optional: true });
|
|
16048
16294
|
dataDiscovery = inject(ReportDataDiscoveryService, { optional: true });
|
|
16049
16295
|
endpoints = inject(BWAYS_REPORT_ENDPOINTS, { optional: true });
|
|
@@ -16290,7 +16536,8 @@ class PropertyEditorDatasourceComponent {
|
|
|
16290
16536
|
continue;
|
|
16291
16537
|
}
|
|
16292
16538
|
// 1. Session match (e.g. companyId, branchId, userId)
|
|
16293
|
-
const sessionMatch = this.
|
|
16539
|
+
const sessionMatch = this.reportingConfig?.autoParameter === false ? null :
|
|
16540
|
+
this.sessionService?.matchParameter(clean) ?? this.sessionService?.matchParameter(param.name);
|
|
16294
16541
|
if (sessionMatch) {
|
|
16295
16542
|
currentMappings[param.name] = `{{session.${sessionMatch.sessionKey}}}`;
|
|
16296
16543
|
currentMappings[clean] = `{{session.${sessionMatch.sessionKey}}}`;
|
|
@@ -16357,7 +16604,8 @@ class PropertyEditorDatasourceComponent {
|
|
|
16357
16604
|
continue;
|
|
16358
16605
|
}
|
|
16359
16606
|
// 1. Session match (e.g. companyId, branchId, userId)
|
|
16360
|
-
const sessionMatch = this.
|
|
16607
|
+
const sessionMatch = this.reportingConfig?.autoParameter === false ? null :
|
|
16608
|
+
this.sessionService?.matchParameter(clean) ?? this.sessionService?.matchParameter(param.name);
|
|
16361
16609
|
if (sessionMatch) {
|
|
16362
16610
|
currentMappings[param.name] = `{{session.${sessionMatch.sessionKey}}}`;
|
|
16363
16611
|
currentMappings[clean] = `{{session.${sessionMatch.sessionKey}}}`;
|
|
@@ -16596,10 +16844,7 @@ class PropertyEditorDatasourceComponent {
|
|
|
16596
16844
|
'');
|
|
16597
16845
|
}
|
|
16598
16846
|
setParamValue(paramName, value) {
|
|
16599
|
-
const
|
|
16600
|
-
const existing = { ...(this.comp().dataSource?.parameterMappings ?? {}) };
|
|
16601
|
-
existing[paramName] = value;
|
|
16602
|
-
existing[clean] = value;
|
|
16847
|
+
const existing = setParameterMapping(this.comp().dataSource?.parameterMappings, paramName, value);
|
|
16603
16848
|
this.updateDataSource.emit({
|
|
16604
16849
|
...(this.comp().dataSource ?? { displayField: '', valueField: '' }),
|
|
16605
16850
|
parameterMappings: existing,
|
|
@@ -17602,12 +17847,13 @@ class PropertyEditorComponent {
|
|
|
17602
17847
|
return comp.validation?.required ? 'Required' : 'Optional';
|
|
17603
17848
|
}
|
|
17604
17849
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PropertyEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
17605
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: PropertyEditorComponent, isStandalone: true, selector: "rpt-property-editor", outputs: { collapseRequested: "collapseRequested" }, ngImport: i0, template: " <div class=\"editor-container\">\n <!-- Editor Header & Tabs -->\n <div class=\"editor-tabs\">\n <button\n type=\"button\"\n class=\"tab-btn\"\n [class.active]=\"activeTab() === 'properties'\"\n (click)=\"setTab('properties')\"\n [disabled]=\"!selectedComponent()\"\n >\n <rpt-icon name=\"edit\" [size]=\"13\" />\n <span>Properties</span>\n </button>\n <button\n type=\"button\"\n class=\"tab-btn\"\n [class.active]=\"activeTab() === 'report'\"\n (click)=\"setTab('report')\"\n >\n <rpt-icon name=\"design\" [size]=\"13\" />\n <span>Layout</span>\n </button>\n <button\n type=\"button\"\n class=\"tab-btn\"\n [class.active]=\"activeTab() === 'grid'\"\n (click)=\"setTab('grid')\"\n >\n <rpt-icon name=\"table\" [size]=\"13\" />\n <span>Grid</span>\n </button>\n\n <div style=\"flex: 1;\"></div>\n\n <button\n type=\"button\"\n class=\"tab-btn btn-collapse-inspector\"\n (click)=\"collapseRequested.emit()\"\n title=\"Collapse Property Editor\"\n aria-label=\"Collapse Property Editor\"\n >\n <rpt-icon name=\"chevron-right\" [size]=\"13\" />\n </button>\n </div>\n\n <!-- Editor Body -->\n <div class=\"editor-body\">\n <!-- 1. COMPONENT PROPERTIES TAB -->\n @if (activeTab() === 'properties') {\n @if (selectedComponent(); as comp) {\n <div class=\"component-sections-wrapper\">\n <!-- Component Identifier Header (Pinned at Top) -->\n <div class=\"comp-badge-header\">\n <div class=\"badge-title\">\n <span class=\"badge-type\">{{ comp.type }}</span>\n <span class=\"badge-id\">{{ comp.id }}</span>\n </div>\n <div class=\"badge-header-actions\">\n <button\n type=\"button\"\n class=\"btn-badge-action\"\n (click)=\"duplicateComponent(comp.id)\"\n title=\"Duplicate component\"\n >\n <rpt-icon name=\"copy\" [size]=\"13\" />\n </button>\n <button\n type=\"button\"\n class=\"btn-badge-action btn-badge-delete\"\n (click)=\"deleteComponent(comp.id)\"\n title=\"Delete component\"\n >\n <rpt-icon name=\"trash\" [size]=\"13\" />\n </button>\n <button\n type=\"button\"\n class=\"btn-deselect\"\n (click)=\"deselect()\"\n title=\"Deselect component\"\n >\n <rpt-icon name=\"close\" [size]=\"13\" />\n </button>\n </div>\n </div>\n\n <!-- Accordion Container -->\n <div class=\"property-accordion\">\n <!-- 1. OPTIONS DATA SOURCE ACCORDION (for Dropdown & Radio) -->\n @if (comp.type === 'dropdown' || comp.type === 'radio') {\n <div class=\"accordion-item\" [class.is-expanded]=\"isSectionExpanded('datasource')\">\n <button\n type=\"button\"\n class=\"accordion-header\"\n (click)=\"toggleSection('datasource')\"\n title=\"Toggle Options Data Source section\"\n >\n <div class=\"header-left\">\n <rpt-icon name=\"database\" [size]=\"14\" />\n <span class=\"header-title\">Options Data Source</span>\n <span class=\"header-badge\">{{ getDatasourceBadge(comp) }}</span>\n </div>\n <div class=\"header-right\">\n <rpt-icon name=\"chevron-down\" [size]=\"13\" class=\"accordion-chevron\" />\n </div>\n </button>\n @if (isSectionExpanded('datasource')) {\n <div class=\"accordion-content\">\n <rpt-property-editor-datasource\n [comp]=\"comp\"\n [allComponents]=\"allComponents()\"\n (updateDataSource)=\"updateCompDataSource(comp.id, $event)\"\n (updateProperty)=\"updateProp(comp.id, $event.key, $event.value)\"\n />\n </div>\n }\n </div>\n }\n\n <!-- BUTTON ACTION ACCORDION (FIRST for Button) -->\n @if (comp.type === 'button') {\n <div class=\"accordion-item\" [class.is-expanded]=\"isSectionExpanded('action')\">\n <button\n type=\"button\"\n class=\"accordion-header\"\n (click)=\"toggleSection('action')\"\n title=\"Toggle Button Action section\"\n >\n <div class=\"header-left\">\n <rpt-icon name=\"lightning\" [size]=\"14\" />\n <span class=\"header-title\">Button Action</span>\n <span class=\"header-badge\">{{ getActionBadge(comp) }}</span>\n </div>\n <div class=\"header-right\">\n <rpt-icon name=\"chevron-down\" [size]=\"13\" class=\"accordion-chevron\" />\n </div>\n </button>\n @if (isSectionExpanded('action')) {\n <div class=\"accordion-content\">\n <rpt-property-editor-action\n [comp]=\"comp\"\n (updateProperty)=\"updateProp(comp.id, $event.key, $event.value)\"\n />\n </div>\n }\n </div>\n }\n\n <!-- 2. GENERAL PROPERTIES ACCORDION -->\n <div class=\"accordion-item\" [class.is-expanded]=\"isSectionExpanded('general')\">\n <button\n type=\"button\"\n class=\"accordion-header\"\n (click)=\"toggleSection('general')\"\n title=\"Toggle General Properties section\"\n >\n <div class=\"header-left\">\n <rpt-icon name=\"edit\" [size]=\"14\" />\n <span class=\"header-title\">General Properties</span>\n @if (comp.label) {\n <span class=\"header-badge\">{{ comp.label }}</span>\n }\n </div>\n <div class=\"header-right\">\n <rpt-icon name=\"chevron-down\" [size]=\"13\" class=\"accordion-chevron\" />\n </div>\n </button>\n @if (isSectionExpanded('general')) {\n <div class=\"accordion-content\">\n <rpt-property-editor-general\n [comp]=\"comp\"\n [propertySchema]=\"propertySchema()\"\n (update)=\"updateComp($event.field, $event.value)\"\n (updateProperty)=\"updateProp(comp.id, $event.key, $event.value)\"\n (deselect)=\"deselect()\"\n />\n </div>\n }\n </div>\n\n <!-- 3. LAYOUT & GRID ACCORDION -->\n <div class=\"accordion-item\" [class.is-expanded]=\"isSectionExpanded('layout')\">\n <button\n type=\"button\"\n class=\"accordion-header\"\n (click)=\"toggleSection('layout')\"\n title=\"Toggle Layout & Dimensions section\"\n >\n <div class=\"header-left\">\n <rpt-icon name=\"design\" [size]=\"14\" />\n <span class=\"header-title\">Layout & Grid</span>\n <span class=\"header-badge\">{{ getLayoutBadge(comp) }}</span>\n </div>\n <div class=\"header-right\">\n <rpt-icon name=\"chevron-down\" [size]=\"13\" class=\"accordion-chevron\" />\n </div>\n </button>\n @if (isSectionExpanded('layout')) {\n <div class=\"accordion-content\">\n <rpt-property-editor-layout\n [comp]=\"comp\"\n [maxColumns]=\"maxColumns()\"\n (updateLayout)=\"updateLayout($event.key, $event.value)\"\n (updateHeight)=\"updateComponentHeight($event)\"\n (resetDimensions)=\"resetComponentDimensions(comp.id, comp.type)\"\n />\n </div>\n }\n </div>\n\n <!-- 4. VALIDATION ACCORDION -->\n <div class=\"accordion-item\" [class.is-expanded]=\"isSectionExpanded('validation')\">\n <button\n type=\"button\"\n class=\"accordion-header\"\n (click)=\"toggleSection('validation')\"\n title=\"Toggle Validation Rules section\"\n >\n <div class=\"header-left\">\n <rpt-icon name=\"check\" [size]=\"14\" />\n <span class=\"header-title\">Validation Rules</span>\n <span class=\"header-badge\">{{ getValidationBadge(comp) }}</span>\n </div>\n <div class=\"header-right\">\n <rpt-icon name=\"chevron-down\" [size]=\"13\" class=\"accordion-chevron\" />\n </div>\n </button>\n @if (isSectionExpanded('validation')) {\n <div class=\"accordion-content\">\n <rpt-property-editor-validation\n [comp]=\"comp\"\n (updateValidation)=\"updateValidation($event.key, $event.value)\"\n />\n </div>\n }\n </div>\n </div>\n </div>\n } @else {\n <div class=\"empty-selection\">\n <div class=\"empty-icon-wrap\">\n <rpt-icon name=\"cursor\" [size]=\"24\" color=\"#64748b\" />\n </div>\n <p class=\"empty-text\">Select a component on the canvas to configure its properties.</p>\n </div>\n }\n }\n\n <!-- 2. REPORT SETTINGS TAB -->\n @if (activeTab() === 'report') {\n <rpt-property-editor-report\n [definition]=\"definition()\"\n (updateReport)=\"updateReport($event.field, $event.value)\"\n (updateLayout)=\"updateLayoutProp($event.field, $event.value)\"\n />\n }\n\n <!-- 3. RESULT GRID SETTINGS TAB -->\n @if (activeTab() === 'grid') {\n <rpt-property-editor-grid\n [grid]=\"definition().grid\"\n [dataSource]=\"gridDataSource()\"\n [availableEntities]=\"availableEntities()\"\n [procedureParameters]=\"gridProcedureParameters()\"\n [allComponents]=\"allComponents()\"\n [isDetecting]=\"isDetectingColumns()\"\n [statusMessage]=\"gridDetectionStatus()?.message ?? null\"\n [statusKind]=\"gridDetectionStatus()?.kind ?? null\"\n (entityChanged)=\"onGridEntityChanged($event)\"\n (updateDataSourceParameters)=\"onGridDataSourceParametersChanged($event)\"\n (addGrid)=\"addResultGrid()\"\n (removeGrid)=\"removeResultGrid()\"\n (addColumn)=\"addColumn()\"\n (autoDetectColumns)=\"autoDetectGridColumns()\"\n (removeColumn)=\"removeColumn($event)\"\n (updateColumn)=\"updateColumn($event.index, $event.field, $event.value)\"\n (updateOption)=\"updateGridOption($event.field, $event.value)\"\n (updateRowClickAction)=\"updateGridRowClickAction($event)\"\n />\n }\n </div>\n </div>", styles: [":host{display:block;height:100%;background:#fff;border-left:1px solid var(--rpt-color-border, #cbd5e1);box-sizing:border-box;font-family:var(--rpt-font-family, inherit)}.editor-container{display:flex;flex-direction:column;height:100%}.editor-tabs{display:flex;flex-shrink:0;border-bottom:1px solid var(--rpt-color-border, #cbd5e1);background:var(--rpt-color-surface, #f8fafc)}.tab-btn{flex:1;display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:12px 8px;font-size:var(--rpt-font-size-sm, .75rem);font-weight:600;color:var(--rpt-color-text-muted, #64748b);background:transparent;border:none;border-bottom:2px solid transparent;cursor:pointer;transition:all var(--rpt-transition-speed, .15s) ease}.tab-btn:hover:not(:disabled){color:var(--rpt-color-text, #0f172a)}.tab-btn.active{color:var(--rpt-color-primary, #2563eb);border-bottom-color:var(--rpt-color-primary, #2563eb);background:#fff}.tab-btn:disabled{opacity:.4;cursor:not-allowed}.editor-body{flex:1;overflow-y:auto;padding:12px 14px}.component-sections-wrapper{display:flex;flex-direction:column;gap:12px}.comp-badge-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:var(--rpt-color-surface, #f8fafc);border-radius:6px;border:1px solid var(--rpt-color-border, #e2e8f0)}.badge-title{display:flex;align-items:center}.badge-type{font-size:.75rem;font-weight:700;color:var(--rpt-color-primary, #2563eb);text-transform:uppercase;margin-right:8px}.badge-id{font-size:.7rem;color:var(--rpt-color-text-muted, #94a3b8);font-family:monospace}.badge-header-actions{display:flex;align-items:center;gap:4px}.btn-badge-action{background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);display:inline-flex;align-items:center;justify-content:center;color:var(--rpt-color-text-muted, #64748b);cursor:pointer;width:24px;height:24px;border-radius:4px;padding:0;transition:all .12s ease}.btn-badge-action:hover{background:var(--rpt-color-surface-hover, #f1f5f9);color:var(--rpt-color-primary, #2563eb);border-color:var(--rpt-color-primary, #2563eb)}.btn-badge-action.btn-badge-delete:hover{background:#fee2e2;color:#ef4444;border-color:#fca5a5}.btn-deselect{background:none;border:1px solid transparent;display:inline-flex;align-items:center;justify-content:center;color:var(--rpt-color-text-muted, #64748b);cursor:pointer;width:24px;height:24px;border-radius:4px;padding:0;transition:all .15s ease}.btn-deselect:hover{color:var(--rpt-color-danger, #ef4444);background:#fee2e2;border-color:#fca5a5}.property-accordion{display:flex;flex-direction:column;gap:10px}.accordion-item{border:1px solid var(--rpt-color-border, #e2e8f0);border-radius:6px;background:#fff;overflow:hidden;transition:all .15s ease}.accordion-item.is-expanded{border-color:#cbd5e1;box-shadow:0 1px 3px #0000000a}.accordion-header{width:100%;display:flex;align-items:center;justify-content:space-between;padding:10px 12px;background:var(--rpt-color-surface, #f8fafc);border:none;cursor:pointer;text-align:left;-webkit-user-select:none;user-select:none;transition:background .15s ease}.accordion-header:hover{background:#f1f5f9}.accordion-item.is-expanded .accordion-header{background:#f8fafc;border-bottom:1px solid var(--rpt-color-border, #e2e8f0)}.header-left{display:flex;align-items:center;gap:8px;min-width:0;flex:1}.header-title{font-size:.72rem;font-weight:700;color:var(--rpt-color-text, #0f172a);text-transform:uppercase;letter-spacing:.03em;white-space:nowrap}.header-badge{font-size:.65rem;font-weight:600;color:var(--rpt-color-primary, #2563eb);background:#eff6ff;border:1px solid #bfdbfe;padding:1px 6px;border-radius:9999px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:120px}.header-right{display:flex;align-items:center;color:var(--rpt-color-text-muted, #64748b);margin-left:8px}.accordion-chevron{transition:transform .2s ease}.accordion-item.is-expanded .accordion-chevron{transform:rotate(180deg)}.accordion-content{padding:12px;display:flex;flex-direction:column;gap:12px}::ng-deep .accordion-content .group-title{display:none}::ng-deep .accordion-content .comp-badge-header{display:none}.empty-selection{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center;box-sizing:border-box}.empty-icon-wrap{display:inline-flex;align-items:center;justify-content:center;width:52px;height:52px;border-radius:50%;background:#f1f5f9;margin-bottom:16px;box-shadow:0 1px 3px #0000000a}.empty-text{font-size:.8125rem;line-height:1.5;max-width:220px;margin:0;color:var(--rpt-color-text-muted, #64748b)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: RptIconComponent, selector: "rpt-icon", inputs: ["name", "size", "color", "strokeWidth"] }, { kind: "component", type: PropertyEditorGeneralComponent, selector: "rpt-property-editor-general", inputs: ["comp", "propertySchema"], outputs: ["update", "updateProperty", "deselect"] }, { kind: "component", type: PropertyEditorLayoutComponent, selector: "rpt-property-editor-layout", inputs: ["comp", "maxColumns"], outputs: ["updateLayout", "updateHeight", "resetDimensions"] }, { kind: "component", type: PropertyEditorValidationComponent, selector: "rpt-property-editor-validation", inputs: ["comp"], outputs: ["updateValidation"] }, { kind: "component", type: PropertyEditorGridComponent, selector: "rpt-property-editor-grid", inputs: ["grid", "dataSource", "availableEntities", "procedureParameters", "allComponents", "isDetecting", "statusMessage", "statusKind"], outputs: ["addGrid", "removeGrid", "addColumn", "autoDetectColumns", "removeColumn", "updateColumn", "updateOption", "updateRowClickAction", "setDataSource", "updateDataSourceParameters", "entityChanged"] }, { kind: "component", type: PropertyEditorReportComponent, selector: "rpt-property-editor-report", inputs: ["definition"], outputs: ["updateReport", "updateLayout"] }, { kind: "component", type: PropertyEditorDatasourceComponent, selector: "rpt-property-editor-datasource", inputs: ["comp", "allComponents"], outputs: ["updateDataSource", "updateProperty"] }, { kind: "component", type: PropertyEditorActionComponent, selector: "rpt-property-editor-action", inputs: ["comp"], outputs: ["updateProperty"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
17850
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "18.2.14", type: PropertyEditorComponent, isStandalone: true, selector: "rpt-property-editor", outputs: { collapseRequested: "collapseRequested" }, ngImport: i0, template: " <div class=\"editor-container\">\n <!-- Editor Header & Tabs -->\n <div class=\"editor-tabs\">\n <button\n type=\"button\"\n class=\"tab-btn\"\n [class.active]=\"activeTab() === 'properties'\"\n (click)=\"setTab('properties')\"\n [disabled]=\"!selectedComponent()\"\n >\n <rpt-icon name=\"edit\" [size]=\"13\" />\n <span>Properties</span>\n </button>\n <button\n type=\"button\"\n class=\"tab-btn\"\n [class.active]=\"activeTab() === 'report'\"\n (click)=\"setTab('report')\"\n >\n <rpt-icon name=\"design\" [size]=\"13\" />\n <span>Layout</span>\n </button>\n <button\n type=\"button\"\n class=\"tab-btn\"\n [class.active]=\"activeTab() === 'grid'\"\n (click)=\"setTab('grid')\"\n >\n <rpt-icon name=\"table\" [size]=\"13\" />\n <span>Grid</span>\n </button>\n\n <div style=\"flex: 1;\"></div>\n\n <button\n type=\"button\"\n class=\"tab-btn btn-collapse-inspector\"\n (click)=\"collapseRequested.emit()\"\n title=\"Collapse Property Editor\"\n aria-label=\"Collapse Property Editor\"\n >\n <rpt-icon name=\"chevron-right\" [size]=\"13\" />\n </button>\n </div>\n\n <!-- Editor Body -->\n <div class=\"editor-body\" cdkScrollable>\n <!-- 1. COMPONENT PROPERTIES TAB -->\n @if (activeTab() === 'properties') {\n @if (selectedComponent(); as comp) {\n <div class=\"component-sections-wrapper\">\n <!-- Component Identifier Header (Pinned at Top) -->\n <div class=\"comp-badge-header\">\n <div class=\"badge-title\">\n <span class=\"badge-type\">{{ comp.type }}</span>\n <span class=\"badge-id\">{{ comp.id }}</span>\n </div>\n <div class=\"badge-header-actions\">\n <button\n type=\"button\"\n class=\"btn-badge-action\"\n (click)=\"duplicateComponent(comp.id)\"\n title=\"Duplicate component\"\n >\n <rpt-icon name=\"copy\" [size]=\"13\" />\n </button>\n <button\n type=\"button\"\n class=\"btn-badge-action btn-badge-delete\"\n (click)=\"deleteComponent(comp.id)\"\n title=\"Delete component\"\n >\n <rpt-icon name=\"trash\" [size]=\"13\" />\n </button>\n <button\n type=\"button\"\n class=\"btn-deselect\"\n (click)=\"deselect()\"\n title=\"Deselect component\"\n >\n <rpt-icon name=\"close\" [size]=\"13\" />\n </button>\n </div>\n </div>\n\n <!-- Accordion Container -->\n <div class=\"property-accordion\">\n <!-- 1. OPTIONS DATA SOURCE ACCORDION (for Dropdown & Radio) -->\n @if (comp.type === 'dropdown' || comp.type === 'radio') {\n <div class=\"accordion-item\" [class.is-expanded]=\"isSectionExpanded('datasource')\">\n <button\n type=\"button\"\n class=\"accordion-header\"\n (click)=\"toggleSection('datasource')\"\n title=\"Toggle Options Data Source section\"\n >\n <div class=\"header-left\">\n <rpt-icon name=\"database\" [size]=\"14\" />\n <span class=\"header-title\">Options Data Source</span>\n <span class=\"header-badge\">{{ getDatasourceBadge(comp) }}</span>\n </div>\n <div class=\"header-right\">\n <rpt-icon name=\"chevron-down\" [size]=\"13\" class=\"accordion-chevron\" />\n </div>\n </button>\n @if (isSectionExpanded('datasource')) {\n <div class=\"accordion-content\">\n <rpt-property-editor-datasource\n [comp]=\"comp\"\n [allComponents]=\"allComponents()\"\n (updateDataSource)=\"updateCompDataSource(comp.id, $event)\"\n (updateProperty)=\"updateProp(comp.id, $event.key, $event.value)\"\n />\n </div>\n }\n </div>\n }\n\n <!-- BUTTON ACTION ACCORDION (FIRST for Button) -->\n @if (comp.type === 'button') {\n <div class=\"accordion-item\" [class.is-expanded]=\"isSectionExpanded('action')\">\n <button\n type=\"button\"\n class=\"accordion-header\"\n (click)=\"toggleSection('action')\"\n title=\"Toggle Button Action section\"\n >\n <div class=\"header-left\">\n <rpt-icon name=\"lightning\" [size]=\"14\" />\n <span class=\"header-title\">Button Action</span>\n <span class=\"header-badge\">{{ getActionBadge(comp) }}</span>\n </div>\n <div class=\"header-right\">\n <rpt-icon name=\"chevron-down\" [size]=\"13\" class=\"accordion-chevron\" />\n </div>\n </button>\n @if (isSectionExpanded('action')) {\n <div class=\"accordion-content\">\n <rpt-property-editor-action\n [comp]=\"comp\"\n (updateProperty)=\"updateProp(comp.id, $event.key, $event.value)\"\n />\n </div>\n }\n </div>\n }\n\n <!-- 2. GENERAL PROPERTIES ACCORDION -->\n <div class=\"accordion-item\" [class.is-expanded]=\"isSectionExpanded('general')\">\n <button\n type=\"button\"\n class=\"accordion-header\"\n (click)=\"toggleSection('general')\"\n title=\"Toggle General Properties section\"\n >\n <div class=\"header-left\">\n <rpt-icon name=\"edit\" [size]=\"14\" />\n <span class=\"header-title\">General Properties</span>\n @if (comp.label) {\n <span class=\"header-badge\">{{ comp.label }}</span>\n }\n </div>\n <div class=\"header-right\">\n <rpt-icon name=\"chevron-down\" [size]=\"13\" class=\"accordion-chevron\" />\n </div>\n </button>\n @if (isSectionExpanded('general')) {\n <div class=\"accordion-content\">\n <rpt-property-editor-general\n [comp]=\"comp\"\n [propertySchema]=\"propertySchema()\"\n (update)=\"updateComp($event.field, $event.value)\"\n (updateProperty)=\"updateProp(comp.id, $event.key, $event.value)\"\n (deselect)=\"deselect()\"\n />\n </div>\n }\n </div>\n\n <!-- 3. LAYOUT & GRID ACCORDION -->\n <div class=\"accordion-item\" [class.is-expanded]=\"isSectionExpanded('layout')\">\n <button\n type=\"button\"\n class=\"accordion-header\"\n (click)=\"toggleSection('layout')\"\n title=\"Toggle Layout & Dimensions section\"\n >\n <div class=\"header-left\">\n <rpt-icon name=\"design\" [size]=\"14\" />\n <span class=\"header-title\">Layout & Grid</span>\n <span class=\"header-badge\">{{ getLayoutBadge(comp) }}</span>\n </div>\n <div class=\"header-right\">\n <rpt-icon name=\"chevron-down\" [size]=\"13\" class=\"accordion-chevron\" />\n </div>\n </button>\n @if (isSectionExpanded('layout')) {\n <div class=\"accordion-content\">\n <rpt-property-editor-layout\n [comp]=\"comp\"\n [maxColumns]=\"maxColumns()\"\n (updateLayout)=\"updateLayout($event.key, $event.value)\"\n (updateHeight)=\"updateComponentHeight($event)\"\n (resetDimensions)=\"resetComponentDimensions(comp.id, comp.type)\"\n />\n </div>\n }\n </div>\n\n <!-- 4. VALIDATION ACCORDION -->\n <div class=\"accordion-item\" [class.is-expanded]=\"isSectionExpanded('validation')\">\n <button\n type=\"button\"\n class=\"accordion-header\"\n (click)=\"toggleSection('validation')\"\n title=\"Toggle Validation Rules section\"\n >\n <div class=\"header-left\">\n <rpt-icon name=\"check\" [size]=\"14\" />\n <span class=\"header-title\">Validation Rules</span>\n <span class=\"header-badge\">{{ getValidationBadge(comp) }}</span>\n </div>\n <div class=\"header-right\">\n <rpt-icon name=\"chevron-down\" [size]=\"13\" class=\"accordion-chevron\" />\n </div>\n </button>\n @if (isSectionExpanded('validation')) {\n <div class=\"accordion-content\">\n <rpt-property-editor-validation\n [comp]=\"comp\"\n (updateValidation)=\"updateValidation($event.key, $event.value)\"\n />\n </div>\n }\n </div>\n </div>\n </div>\n } @else {\n <div class=\"empty-selection\">\n <div class=\"empty-icon-wrap\">\n <rpt-icon name=\"cursor\" [size]=\"24\" color=\"#64748b\" />\n </div>\n <p class=\"empty-text\">Select a component on the canvas to configure its properties.</p>\n </div>\n }\n }\n\n <!-- 2. REPORT SETTINGS TAB -->\n @if (activeTab() === 'report') {\n <rpt-property-editor-report\n [definition]=\"definition()\"\n (updateReport)=\"updateReport($event.field, $event.value)\"\n (updateLayout)=\"updateLayoutProp($event.field, $event.value)\"\n />\n }\n\n <!-- 3. RESULT GRID SETTINGS TAB -->\n @if (activeTab() === 'grid') {\n <rpt-property-editor-grid\n [grid]=\"definition().grid\"\n [dataSource]=\"gridDataSource()\"\n [availableEntities]=\"availableEntities()\"\n [procedureParameters]=\"gridProcedureParameters()\"\n [allComponents]=\"allComponents()\"\n [isDetecting]=\"isDetectingColumns()\"\n [statusMessage]=\"gridDetectionStatus()?.message ?? null\"\n [statusKind]=\"gridDetectionStatus()?.kind ?? null\"\n (entityChanged)=\"onGridEntityChanged($event)\"\n (updateDataSourceParameters)=\"onGridDataSourceParametersChanged($event)\"\n (addGrid)=\"addResultGrid()\"\n (removeGrid)=\"removeResultGrid()\"\n (addColumn)=\"addColumn()\"\n (autoDetectColumns)=\"autoDetectGridColumns()\"\n (removeColumn)=\"removeColumn($event)\"\n (updateColumn)=\"updateColumn($event.index, $event.field, $event.value)\"\n (updateOption)=\"updateGridOption($event.field, $event.value)\"\n (updateRowClickAction)=\"updateGridRowClickAction($event)\"\n />\n }\n </div>\n </div>", styles: [":host{display:block;height:100%;background:#fff;border-left:1px solid var(--rpt-color-border, #cbd5e1);box-sizing:border-box;font-family:var(--rpt-font-family, inherit)}.editor-container{display:flex;flex-direction:column;height:100%}.editor-tabs{display:flex;flex-shrink:0;border-bottom:1px solid var(--rpt-color-border, #cbd5e1);background:var(--rpt-color-surface, #f8fafc)}.tab-btn{flex:1;display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:12px 8px;font-size:var(--rpt-font-size-sm, .75rem);font-weight:600;color:var(--rpt-color-text-muted, #64748b);background:transparent;border:none;border-bottom:2px solid transparent;cursor:pointer;transition:all var(--rpt-transition-speed, .15s) ease}.tab-btn:hover:not(:disabled){color:var(--rpt-color-text, #0f172a)}.tab-btn.active{color:var(--rpt-color-primary, #2563eb);border-bottom-color:var(--rpt-color-primary, #2563eb);background:#fff}.tab-btn:disabled{opacity:.4;cursor:not-allowed}.editor-body{flex:1;overflow-y:auto;padding:12px 14px}.component-sections-wrapper{display:flex;flex-direction:column;gap:12px}.comp-badge-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:var(--rpt-color-surface, #f8fafc);border-radius:6px;border:1px solid var(--rpt-color-border, #e2e8f0)}.badge-title{display:flex;align-items:center}.badge-type{font-size:.75rem;font-weight:700;color:var(--rpt-color-primary, #2563eb);text-transform:uppercase;margin-right:8px}.badge-id{font-size:.7rem;color:var(--rpt-color-text-muted, #94a3b8);font-family:monospace}.badge-header-actions{display:flex;align-items:center;gap:4px}.btn-badge-action{background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);display:inline-flex;align-items:center;justify-content:center;color:var(--rpt-color-text-muted, #64748b);cursor:pointer;width:24px;height:24px;border-radius:4px;padding:0;transition:all .12s ease}.btn-badge-action:hover{background:var(--rpt-color-surface-hover, #f1f5f9);color:var(--rpt-color-primary, #2563eb);border-color:var(--rpt-color-primary, #2563eb)}.btn-badge-action.btn-badge-delete:hover{background:#fee2e2;color:#ef4444;border-color:#fca5a5}.btn-deselect{background:none;border:1px solid transparent;display:inline-flex;align-items:center;justify-content:center;color:var(--rpt-color-text-muted, #64748b);cursor:pointer;width:24px;height:24px;border-radius:4px;padding:0;transition:all .15s ease}.btn-deselect:hover{color:var(--rpt-color-danger, #ef4444);background:#fee2e2;border-color:#fca5a5}.property-accordion{display:flex;flex-direction:column;gap:10px}.accordion-item{border:1px solid var(--rpt-color-border, #e2e8f0);border-radius:6px;background:#fff;overflow:hidden;transition:all .15s ease}.accordion-item.is-expanded{border-color:#cbd5e1;box-shadow:0 1px 3px #0000000a}.accordion-header{width:100%;display:flex;align-items:center;justify-content:space-between;padding:10px 12px;background:var(--rpt-color-surface, #f8fafc);border:none;cursor:pointer;text-align:left;-webkit-user-select:none;user-select:none;transition:background .15s ease}.accordion-header:hover{background:#f1f5f9}.accordion-item.is-expanded .accordion-header{background:#f8fafc;border-bottom:1px solid var(--rpt-color-border, #e2e8f0)}.header-left{display:flex;align-items:center;gap:8px;min-width:0;flex:1}.header-title{font-size:.72rem;font-weight:700;color:var(--rpt-color-text, #0f172a);text-transform:uppercase;letter-spacing:.03em;white-space:nowrap}.header-badge{font-size:.65rem;font-weight:600;color:var(--rpt-color-primary, #2563eb);background:#eff6ff;border:1px solid #bfdbfe;padding:1px 6px;border-radius:9999px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:120px}.header-right{display:flex;align-items:center;color:var(--rpt-color-text-muted, #64748b);margin-left:8px}.accordion-chevron{transition:transform .2s ease}.accordion-item.is-expanded .accordion-chevron{transform:rotate(180deg)}.accordion-content{padding:12px;display:flex;flex-direction:column;gap:12px}::ng-deep .accordion-content .group-title{display:none}::ng-deep .accordion-content .comp-badge-header{display:none}.empty-selection{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center;box-sizing:border-box}.empty-icon-wrap{display:inline-flex;align-items:center;justify-content:center;width:52px;height:52px;border-radius:50%;background:#f1f5f9;margin-bottom:16px;box-shadow:0 1px 3px #0000000a}.empty-text{font-size:.8125rem;line-height:1.5;max-width:220px;margin:0;color:var(--rpt-color-text-muted, #64748b)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: CdkScrollable, selector: "[cdk-scrollable], [cdkScrollable]" }, { kind: "component", type: RptIconComponent, selector: "rpt-icon", inputs: ["name", "size", "color", "strokeWidth"] }, { kind: "component", type: PropertyEditorGeneralComponent, selector: "rpt-property-editor-general", inputs: ["comp", "propertySchema"], outputs: ["update", "updateProperty", "deselect"] }, { kind: "component", type: PropertyEditorLayoutComponent, selector: "rpt-property-editor-layout", inputs: ["comp", "maxColumns"], outputs: ["updateLayout", "updateHeight", "resetDimensions"] }, { kind: "component", type: PropertyEditorValidationComponent, selector: "rpt-property-editor-validation", inputs: ["comp"], outputs: ["updateValidation"] }, { kind: "component", type: PropertyEditorGridComponent, selector: "rpt-property-editor-grid", inputs: ["grid", "dataSource", "availableEntities", "procedureParameters", "allComponents", "isDetecting", "statusMessage", "statusKind"], outputs: ["addGrid", "removeGrid", "addColumn", "autoDetectColumns", "removeColumn", "updateColumn", "updateOption", "updateRowClickAction", "setDataSource", "updateDataSourceParameters", "entityChanged"] }, { kind: "component", type: PropertyEditorReportComponent, selector: "rpt-property-editor-report", inputs: ["definition"], outputs: ["updateReport", "updateLayout"] }, { kind: "component", type: PropertyEditorDatasourceComponent, selector: "rpt-property-editor-datasource", inputs: ["comp", "allComponents"], outputs: ["updateDataSource", "updateProperty"] }, { kind: "component", type: PropertyEditorActionComponent, selector: "rpt-property-editor-action", inputs: ["comp"], outputs: ["updateProperty"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
17606
17851
|
}
|
|
17607
17852
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PropertyEditorComponent, decorators: [{
|
|
17608
17853
|
type: Component,
|
|
17609
17854
|
args: [{ selector: 'rpt-property-editor', standalone: true, imports: [
|
|
17610
17855
|
CommonModule,
|
|
17856
|
+
CdkScrollable,
|
|
17611
17857
|
RptIconComponent,
|
|
17612
17858
|
PropertyEditorGeneralComponent,
|
|
17613
17859
|
PropertyEditorLayoutComponent,
|
|
@@ -17616,7 +17862,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
17616
17862
|
PropertyEditorReportComponent,
|
|
17617
17863
|
PropertyEditorDatasourceComponent,
|
|
17618
17864
|
PropertyEditorActionComponent,
|
|
17619
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: " <div class=\"editor-container\">\n <!-- Editor Header & Tabs -->\n <div class=\"editor-tabs\">\n <button\n type=\"button\"\n class=\"tab-btn\"\n [class.active]=\"activeTab() === 'properties'\"\n (click)=\"setTab('properties')\"\n [disabled]=\"!selectedComponent()\"\n >\n <rpt-icon name=\"edit\" [size]=\"13\" />\n <span>Properties</span>\n </button>\n <button\n type=\"button\"\n class=\"tab-btn\"\n [class.active]=\"activeTab() === 'report'\"\n (click)=\"setTab('report')\"\n >\n <rpt-icon name=\"design\" [size]=\"13\" />\n <span>Layout</span>\n </button>\n <button\n type=\"button\"\n class=\"tab-btn\"\n [class.active]=\"activeTab() === 'grid'\"\n (click)=\"setTab('grid')\"\n >\n <rpt-icon name=\"table\" [size]=\"13\" />\n <span>Grid</span>\n </button>\n\n <div style=\"flex: 1;\"></div>\n\n <button\n type=\"button\"\n class=\"tab-btn btn-collapse-inspector\"\n (click)=\"collapseRequested.emit()\"\n title=\"Collapse Property Editor\"\n aria-label=\"Collapse Property Editor\"\n >\n <rpt-icon name=\"chevron-right\" [size]=\"13\" />\n </button>\n </div>\n\n <!-- Editor Body -->\n <div class=\"editor-body\">\n <!-- 1. COMPONENT PROPERTIES TAB -->\n @if (activeTab() === 'properties') {\n @if (selectedComponent(); as comp) {\n <div class=\"component-sections-wrapper\">\n <!-- Component Identifier Header (Pinned at Top) -->\n <div class=\"comp-badge-header\">\n <div class=\"badge-title\">\n <span class=\"badge-type\">{{ comp.type }}</span>\n <span class=\"badge-id\">{{ comp.id }}</span>\n </div>\n <div class=\"badge-header-actions\">\n <button\n type=\"button\"\n class=\"btn-badge-action\"\n (click)=\"duplicateComponent(comp.id)\"\n title=\"Duplicate component\"\n >\n <rpt-icon name=\"copy\" [size]=\"13\" />\n </button>\n <button\n type=\"button\"\n class=\"btn-badge-action btn-badge-delete\"\n (click)=\"deleteComponent(comp.id)\"\n title=\"Delete component\"\n >\n <rpt-icon name=\"trash\" [size]=\"13\" />\n </button>\n <button\n type=\"button\"\n class=\"btn-deselect\"\n (click)=\"deselect()\"\n title=\"Deselect component\"\n >\n <rpt-icon name=\"close\" [size]=\"13\" />\n </button>\n </div>\n </div>\n\n <!-- Accordion Container -->\n <div class=\"property-accordion\">\n <!-- 1. OPTIONS DATA SOURCE ACCORDION (for Dropdown & Radio) -->\n @if (comp.type === 'dropdown' || comp.type === 'radio') {\n <div class=\"accordion-item\" [class.is-expanded]=\"isSectionExpanded('datasource')\">\n <button\n type=\"button\"\n class=\"accordion-header\"\n (click)=\"toggleSection('datasource')\"\n title=\"Toggle Options Data Source section\"\n >\n <div class=\"header-left\">\n <rpt-icon name=\"database\" [size]=\"14\" />\n <span class=\"header-title\">Options Data Source</span>\n <span class=\"header-badge\">{{ getDatasourceBadge(comp) }}</span>\n </div>\n <div class=\"header-right\">\n <rpt-icon name=\"chevron-down\" [size]=\"13\" class=\"accordion-chevron\" />\n </div>\n </button>\n @if (isSectionExpanded('datasource')) {\n <div class=\"accordion-content\">\n <rpt-property-editor-datasource\n [comp]=\"comp\"\n [allComponents]=\"allComponents()\"\n (updateDataSource)=\"updateCompDataSource(comp.id, $event)\"\n (updateProperty)=\"updateProp(comp.id, $event.key, $event.value)\"\n />\n </div>\n }\n </div>\n }\n\n <!-- BUTTON ACTION ACCORDION (FIRST for Button) -->\n @if (comp.type === 'button') {\n <div class=\"accordion-item\" [class.is-expanded]=\"isSectionExpanded('action')\">\n <button\n type=\"button\"\n class=\"accordion-header\"\n (click)=\"toggleSection('action')\"\n title=\"Toggle Button Action section\"\n >\n <div class=\"header-left\">\n <rpt-icon name=\"lightning\" [size]=\"14\" />\n <span class=\"header-title\">Button Action</span>\n <span class=\"header-badge\">{{ getActionBadge(comp) }}</span>\n </div>\n <div class=\"header-right\">\n <rpt-icon name=\"chevron-down\" [size]=\"13\" class=\"accordion-chevron\" />\n </div>\n </button>\n @if (isSectionExpanded('action')) {\n <div class=\"accordion-content\">\n <rpt-property-editor-action\n [comp]=\"comp\"\n (updateProperty)=\"updateProp(comp.id, $event.key, $event.value)\"\n />\n </div>\n }\n </div>\n }\n\n <!-- 2. GENERAL PROPERTIES ACCORDION -->\n <div class=\"accordion-item\" [class.is-expanded]=\"isSectionExpanded('general')\">\n <button\n type=\"button\"\n class=\"accordion-header\"\n (click)=\"toggleSection('general')\"\n title=\"Toggle General Properties section\"\n >\n <div class=\"header-left\">\n <rpt-icon name=\"edit\" [size]=\"14\" />\n <span class=\"header-title\">General Properties</span>\n @if (comp.label) {\n <span class=\"header-badge\">{{ comp.label }}</span>\n }\n </div>\n <div class=\"header-right\">\n <rpt-icon name=\"chevron-down\" [size]=\"13\" class=\"accordion-chevron\" />\n </div>\n </button>\n @if (isSectionExpanded('general')) {\n <div class=\"accordion-content\">\n <rpt-property-editor-general\n [comp]=\"comp\"\n [propertySchema]=\"propertySchema()\"\n (update)=\"updateComp($event.field, $event.value)\"\n (updateProperty)=\"updateProp(comp.id, $event.key, $event.value)\"\n (deselect)=\"deselect()\"\n />\n </div>\n }\n </div>\n\n <!-- 3. LAYOUT & GRID ACCORDION -->\n <div class=\"accordion-item\" [class.is-expanded]=\"isSectionExpanded('layout')\">\n <button\n type=\"button\"\n class=\"accordion-header\"\n (click)=\"toggleSection('layout')\"\n title=\"Toggle Layout & Dimensions section\"\n >\n <div class=\"header-left\">\n <rpt-icon name=\"design\" [size]=\"14\" />\n <span class=\"header-title\">Layout & Grid</span>\n <span class=\"header-badge\">{{ getLayoutBadge(comp) }}</span>\n </div>\n <div class=\"header-right\">\n <rpt-icon name=\"chevron-down\" [size]=\"13\" class=\"accordion-chevron\" />\n </div>\n </button>\n @if (isSectionExpanded('layout')) {\n <div class=\"accordion-content\">\n <rpt-property-editor-layout\n [comp]=\"comp\"\n [maxColumns]=\"maxColumns()\"\n (updateLayout)=\"updateLayout($event.key, $event.value)\"\n (updateHeight)=\"updateComponentHeight($event)\"\n (resetDimensions)=\"resetComponentDimensions(comp.id, comp.type)\"\n />\n </div>\n }\n </div>\n\n <!-- 4. VALIDATION ACCORDION -->\n <div class=\"accordion-item\" [class.is-expanded]=\"isSectionExpanded('validation')\">\n <button\n type=\"button\"\n class=\"accordion-header\"\n (click)=\"toggleSection('validation')\"\n title=\"Toggle Validation Rules section\"\n >\n <div class=\"header-left\">\n <rpt-icon name=\"check\" [size]=\"14\" />\n <span class=\"header-title\">Validation Rules</span>\n <span class=\"header-badge\">{{ getValidationBadge(comp) }}</span>\n </div>\n <div class=\"header-right\">\n <rpt-icon name=\"chevron-down\" [size]=\"13\" class=\"accordion-chevron\" />\n </div>\n </button>\n @if (isSectionExpanded('validation')) {\n <div class=\"accordion-content\">\n <rpt-property-editor-validation\n [comp]=\"comp\"\n (updateValidation)=\"updateValidation($event.key, $event.value)\"\n />\n </div>\n }\n </div>\n </div>\n </div>\n } @else {\n <div class=\"empty-selection\">\n <div class=\"empty-icon-wrap\">\n <rpt-icon name=\"cursor\" [size]=\"24\" color=\"#64748b\" />\n </div>\n <p class=\"empty-text\">Select a component on the canvas to configure its properties.</p>\n </div>\n }\n }\n\n <!-- 2. REPORT SETTINGS TAB -->\n @if (activeTab() === 'report') {\n <rpt-property-editor-report\n [definition]=\"definition()\"\n (updateReport)=\"updateReport($event.field, $event.value)\"\n (updateLayout)=\"updateLayoutProp($event.field, $event.value)\"\n />\n }\n\n <!-- 3. RESULT GRID SETTINGS TAB -->\n @if (activeTab() === 'grid') {\n <rpt-property-editor-grid\n [grid]=\"definition().grid\"\n [dataSource]=\"gridDataSource()\"\n [availableEntities]=\"availableEntities()\"\n [procedureParameters]=\"gridProcedureParameters()\"\n [allComponents]=\"allComponents()\"\n [isDetecting]=\"isDetectingColumns()\"\n [statusMessage]=\"gridDetectionStatus()?.message ?? null\"\n [statusKind]=\"gridDetectionStatus()?.kind ?? null\"\n (entityChanged)=\"onGridEntityChanged($event)\"\n (updateDataSourceParameters)=\"onGridDataSourceParametersChanged($event)\"\n (addGrid)=\"addResultGrid()\"\n (removeGrid)=\"removeResultGrid()\"\n (addColumn)=\"addColumn()\"\n (autoDetectColumns)=\"autoDetectGridColumns()\"\n (removeColumn)=\"removeColumn($event)\"\n (updateColumn)=\"updateColumn($event.index, $event.field, $event.value)\"\n (updateOption)=\"updateGridOption($event.field, $event.value)\"\n (updateRowClickAction)=\"updateGridRowClickAction($event)\"\n />\n }\n </div>\n </div>", styles: [":host{display:block;height:100%;background:#fff;border-left:1px solid var(--rpt-color-border, #cbd5e1);box-sizing:border-box;font-family:var(--rpt-font-family, inherit)}.editor-container{display:flex;flex-direction:column;height:100%}.editor-tabs{display:flex;flex-shrink:0;border-bottom:1px solid var(--rpt-color-border, #cbd5e1);background:var(--rpt-color-surface, #f8fafc)}.tab-btn{flex:1;display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:12px 8px;font-size:var(--rpt-font-size-sm, .75rem);font-weight:600;color:var(--rpt-color-text-muted, #64748b);background:transparent;border:none;border-bottom:2px solid transparent;cursor:pointer;transition:all var(--rpt-transition-speed, .15s) ease}.tab-btn:hover:not(:disabled){color:var(--rpt-color-text, #0f172a)}.tab-btn.active{color:var(--rpt-color-primary, #2563eb);border-bottom-color:var(--rpt-color-primary, #2563eb);background:#fff}.tab-btn:disabled{opacity:.4;cursor:not-allowed}.editor-body{flex:1;overflow-y:auto;padding:12px 14px}.component-sections-wrapper{display:flex;flex-direction:column;gap:12px}.comp-badge-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:var(--rpt-color-surface, #f8fafc);border-radius:6px;border:1px solid var(--rpt-color-border, #e2e8f0)}.badge-title{display:flex;align-items:center}.badge-type{font-size:.75rem;font-weight:700;color:var(--rpt-color-primary, #2563eb);text-transform:uppercase;margin-right:8px}.badge-id{font-size:.7rem;color:var(--rpt-color-text-muted, #94a3b8);font-family:monospace}.badge-header-actions{display:flex;align-items:center;gap:4px}.btn-badge-action{background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);display:inline-flex;align-items:center;justify-content:center;color:var(--rpt-color-text-muted, #64748b);cursor:pointer;width:24px;height:24px;border-radius:4px;padding:0;transition:all .12s ease}.btn-badge-action:hover{background:var(--rpt-color-surface-hover, #f1f5f9);color:var(--rpt-color-primary, #2563eb);border-color:var(--rpt-color-primary, #2563eb)}.btn-badge-action.btn-badge-delete:hover{background:#fee2e2;color:#ef4444;border-color:#fca5a5}.btn-deselect{background:none;border:1px solid transparent;display:inline-flex;align-items:center;justify-content:center;color:var(--rpt-color-text-muted, #64748b);cursor:pointer;width:24px;height:24px;border-radius:4px;padding:0;transition:all .15s ease}.btn-deselect:hover{color:var(--rpt-color-danger, #ef4444);background:#fee2e2;border-color:#fca5a5}.property-accordion{display:flex;flex-direction:column;gap:10px}.accordion-item{border:1px solid var(--rpt-color-border, #e2e8f0);border-radius:6px;background:#fff;overflow:hidden;transition:all .15s ease}.accordion-item.is-expanded{border-color:#cbd5e1;box-shadow:0 1px 3px #0000000a}.accordion-header{width:100%;display:flex;align-items:center;justify-content:space-between;padding:10px 12px;background:var(--rpt-color-surface, #f8fafc);border:none;cursor:pointer;text-align:left;-webkit-user-select:none;user-select:none;transition:background .15s ease}.accordion-header:hover{background:#f1f5f9}.accordion-item.is-expanded .accordion-header{background:#f8fafc;border-bottom:1px solid var(--rpt-color-border, #e2e8f0)}.header-left{display:flex;align-items:center;gap:8px;min-width:0;flex:1}.header-title{font-size:.72rem;font-weight:700;color:var(--rpt-color-text, #0f172a);text-transform:uppercase;letter-spacing:.03em;white-space:nowrap}.header-badge{font-size:.65rem;font-weight:600;color:var(--rpt-color-primary, #2563eb);background:#eff6ff;border:1px solid #bfdbfe;padding:1px 6px;border-radius:9999px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:120px}.header-right{display:flex;align-items:center;color:var(--rpt-color-text-muted, #64748b);margin-left:8px}.accordion-chevron{transition:transform .2s ease}.accordion-item.is-expanded .accordion-chevron{transform:rotate(180deg)}.accordion-content{padding:12px;display:flex;flex-direction:column;gap:12px}::ng-deep .accordion-content .group-title{display:none}::ng-deep .accordion-content .comp-badge-header{display:none}.empty-selection{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center;box-sizing:border-box}.empty-icon-wrap{display:inline-flex;align-items:center;justify-content:center;width:52px;height:52px;border-radius:50%;background:#f1f5f9;margin-bottom:16px;box-shadow:0 1px 3px #0000000a}.empty-text{font-size:.8125rem;line-height:1.5;max-width:220px;margin:0;color:var(--rpt-color-text-muted, #64748b)}\n"] }]
|
|
17865
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: " <div class=\"editor-container\">\n <!-- Editor Header & Tabs -->\n <div class=\"editor-tabs\">\n <button\n type=\"button\"\n class=\"tab-btn\"\n [class.active]=\"activeTab() === 'properties'\"\n (click)=\"setTab('properties')\"\n [disabled]=\"!selectedComponent()\"\n >\n <rpt-icon name=\"edit\" [size]=\"13\" />\n <span>Properties</span>\n </button>\n <button\n type=\"button\"\n class=\"tab-btn\"\n [class.active]=\"activeTab() === 'report'\"\n (click)=\"setTab('report')\"\n >\n <rpt-icon name=\"design\" [size]=\"13\" />\n <span>Layout</span>\n </button>\n <button\n type=\"button\"\n class=\"tab-btn\"\n [class.active]=\"activeTab() === 'grid'\"\n (click)=\"setTab('grid')\"\n >\n <rpt-icon name=\"table\" [size]=\"13\" />\n <span>Grid</span>\n </button>\n\n <div style=\"flex: 1;\"></div>\n\n <button\n type=\"button\"\n class=\"tab-btn btn-collapse-inspector\"\n (click)=\"collapseRequested.emit()\"\n title=\"Collapse Property Editor\"\n aria-label=\"Collapse Property Editor\"\n >\n <rpt-icon name=\"chevron-right\" [size]=\"13\" />\n </button>\n </div>\n\n <!-- Editor Body -->\n <div class=\"editor-body\" cdkScrollable>\n <!-- 1. COMPONENT PROPERTIES TAB -->\n @if (activeTab() === 'properties') {\n @if (selectedComponent(); as comp) {\n <div class=\"component-sections-wrapper\">\n <!-- Component Identifier Header (Pinned at Top) -->\n <div class=\"comp-badge-header\">\n <div class=\"badge-title\">\n <span class=\"badge-type\">{{ comp.type }}</span>\n <span class=\"badge-id\">{{ comp.id }}</span>\n </div>\n <div class=\"badge-header-actions\">\n <button\n type=\"button\"\n class=\"btn-badge-action\"\n (click)=\"duplicateComponent(comp.id)\"\n title=\"Duplicate component\"\n >\n <rpt-icon name=\"copy\" [size]=\"13\" />\n </button>\n <button\n type=\"button\"\n class=\"btn-badge-action btn-badge-delete\"\n (click)=\"deleteComponent(comp.id)\"\n title=\"Delete component\"\n >\n <rpt-icon name=\"trash\" [size]=\"13\" />\n </button>\n <button\n type=\"button\"\n class=\"btn-deselect\"\n (click)=\"deselect()\"\n title=\"Deselect component\"\n >\n <rpt-icon name=\"close\" [size]=\"13\" />\n </button>\n </div>\n </div>\n\n <!-- Accordion Container -->\n <div class=\"property-accordion\">\n <!-- 1. OPTIONS DATA SOURCE ACCORDION (for Dropdown & Radio) -->\n @if (comp.type === 'dropdown' || comp.type === 'radio') {\n <div class=\"accordion-item\" [class.is-expanded]=\"isSectionExpanded('datasource')\">\n <button\n type=\"button\"\n class=\"accordion-header\"\n (click)=\"toggleSection('datasource')\"\n title=\"Toggle Options Data Source section\"\n >\n <div class=\"header-left\">\n <rpt-icon name=\"database\" [size]=\"14\" />\n <span class=\"header-title\">Options Data Source</span>\n <span class=\"header-badge\">{{ getDatasourceBadge(comp) }}</span>\n </div>\n <div class=\"header-right\">\n <rpt-icon name=\"chevron-down\" [size]=\"13\" class=\"accordion-chevron\" />\n </div>\n </button>\n @if (isSectionExpanded('datasource')) {\n <div class=\"accordion-content\">\n <rpt-property-editor-datasource\n [comp]=\"comp\"\n [allComponents]=\"allComponents()\"\n (updateDataSource)=\"updateCompDataSource(comp.id, $event)\"\n (updateProperty)=\"updateProp(comp.id, $event.key, $event.value)\"\n />\n </div>\n }\n </div>\n }\n\n <!-- BUTTON ACTION ACCORDION (FIRST for Button) -->\n @if (comp.type === 'button') {\n <div class=\"accordion-item\" [class.is-expanded]=\"isSectionExpanded('action')\">\n <button\n type=\"button\"\n class=\"accordion-header\"\n (click)=\"toggleSection('action')\"\n title=\"Toggle Button Action section\"\n >\n <div class=\"header-left\">\n <rpt-icon name=\"lightning\" [size]=\"14\" />\n <span class=\"header-title\">Button Action</span>\n <span class=\"header-badge\">{{ getActionBadge(comp) }}</span>\n </div>\n <div class=\"header-right\">\n <rpt-icon name=\"chevron-down\" [size]=\"13\" class=\"accordion-chevron\" />\n </div>\n </button>\n @if (isSectionExpanded('action')) {\n <div class=\"accordion-content\">\n <rpt-property-editor-action\n [comp]=\"comp\"\n (updateProperty)=\"updateProp(comp.id, $event.key, $event.value)\"\n />\n </div>\n }\n </div>\n }\n\n <!-- 2. GENERAL PROPERTIES ACCORDION -->\n <div class=\"accordion-item\" [class.is-expanded]=\"isSectionExpanded('general')\">\n <button\n type=\"button\"\n class=\"accordion-header\"\n (click)=\"toggleSection('general')\"\n title=\"Toggle General Properties section\"\n >\n <div class=\"header-left\">\n <rpt-icon name=\"edit\" [size]=\"14\" />\n <span class=\"header-title\">General Properties</span>\n @if (comp.label) {\n <span class=\"header-badge\">{{ comp.label }}</span>\n }\n </div>\n <div class=\"header-right\">\n <rpt-icon name=\"chevron-down\" [size]=\"13\" class=\"accordion-chevron\" />\n </div>\n </button>\n @if (isSectionExpanded('general')) {\n <div class=\"accordion-content\">\n <rpt-property-editor-general\n [comp]=\"comp\"\n [propertySchema]=\"propertySchema()\"\n (update)=\"updateComp($event.field, $event.value)\"\n (updateProperty)=\"updateProp(comp.id, $event.key, $event.value)\"\n (deselect)=\"deselect()\"\n />\n </div>\n }\n </div>\n\n <!-- 3. LAYOUT & GRID ACCORDION -->\n <div class=\"accordion-item\" [class.is-expanded]=\"isSectionExpanded('layout')\">\n <button\n type=\"button\"\n class=\"accordion-header\"\n (click)=\"toggleSection('layout')\"\n title=\"Toggle Layout & Dimensions section\"\n >\n <div class=\"header-left\">\n <rpt-icon name=\"design\" [size]=\"14\" />\n <span class=\"header-title\">Layout & Grid</span>\n <span class=\"header-badge\">{{ getLayoutBadge(comp) }}</span>\n </div>\n <div class=\"header-right\">\n <rpt-icon name=\"chevron-down\" [size]=\"13\" class=\"accordion-chevron\" />\n </div>\n </button>\n @if (isSectionExpanded('layout')) {\n <div class=\"accordion-content\">\n <rpt-property-editor-layout\n [comp]=\"comp\"\n [maxColumns]=\"maxColumns()\"\n (updateLayout)=\"updateLayout($event.key, $event.value)\"\n (updateHeight)=\"updateComponentHeight($event)\"\n (resetDimensions)=\"resetComponentDimensions(comp.id, comp.type)\"\n />\n </div>\n }\n </div>\n\n <!-- 4. VALIDATION ACCORDION -->\n <div class=\"accordion-item\" [class.is-expanded]=\"isSectionExpanded('validation')\">\n <button\n type=\"button\"\n class=\"accordion-header\"\n (click)=\"toggleSection('validation')\"\n title=\"Toggle Validation Rules section\"\n >\n <div class=\"header-left\">\n <rpt-icon name=\"check\" [size]=\"14\" />\n <span class=\"header-title\">Validation Rules</span>\n <span class=\"header-badge\">{{ getValidationBadge(comp) }}</span>\n </div>\n <div class=\"header-right\">\n <rpt-icon name=\"chevron-down\" [size]=\"13\" class=\"accordion-chevron\" />\n </div>\n </button>\n @if (isSectionExpanded('validation')) {\n <div class=\"accordion-content\">\n <rpt-property-editor-validation\n [comp]=\"comp\"\n (updateValidation)=\"updateValidation($event.key, $event.value)\"\n />\n </div>\n }\n </div>\n </div>\n </div>\n } @else {\n <div class=\"empty-selection\">\n <div class=\"empty-icon-wrap\">\n <rpt-icon name=\"cursor\" [size]=\"24\" color=\"#64748b\" />\n </div>\n <p class=\"empty-text\">Select a component on the canvas to configure its properties.</p>\n </div>\n }\n }\n\n <!-- 2. REPORT SETTINGS TAB -->\n @if (activeTab() === 'report') {\n <rpt-property-editor-report\n [definition]=\"definition()\"\n (updateReport)=\"updateReport($event.field, $event.value)\"\n (updateLayout)=\"updateLayoutProp($event.field, $event.value)\"\n />\n }\n\n <!-- 3. RESULT GRID SETTINGS TAB -->\n @if (activeTab() === 'grid') {\n <rpt-property-editor-grid\n [grid]=\"definition().grid\"\n [dataSource]=\"gridDataSource()\"\n [availableEntities]=\"availableEntities()\"\n [procedureParameters]=\"gridProcedureParameters()\"\n [allComponents]=\"allComponents()\"\n [isDetecting]=\"isDetectingColumns()\"\n [statusMessage]=\"gridDetectionStatus()?.message ?? null\"\n [statusKind]=\"gridDetectionStatus()?.kind ?? null\"\n (entityChanged)=\"onGridEntityChanged($event)\"\n (updateDataSourceParameters)=\"onGridDataSourceParametersChanged($event)\"\n (addGrid)=\"addResultGrid()\"\n (removeGrid)=\"removeResultGrid()\"\n (addColumn)=\"addColumn()\"\n (autoDetectColumns)=\"autoDetectGridColumns()\"\n (removeColumn)=\"removeColumn($event)\"\n (updateColumn)=\"updateColumn($event.index, $event.field, $event.value)\"\n (updateOption)=\"updateGridOption($event.field, $event.value)\"\n (updateRowClickAction)=\"updateGridRowClickAction($event)\"\n />\n }\n </div>\n </div>", styles: [":host{display:block;height:100%;background:#fff;border-left:1px solid var(--rpt-color-border, #cbd5e1);box-sizing:border-box;font-family:var(--rpt-font-family, inherit)}.editor-container{display:flex;flex-direction:column;height:100%}.editor-tabs{display:flex;flex-shrink:0;border-bottom:1px solid var(--rpt-color-border, #cbd5e1);background:var(--rpt-color-surface, #f8fafc)}.tab-btn{flex:1;display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:12px 8px;font-size:var(--rpt-font-size-sm, .75rem);font-weight:600;color:var(--rpt-color-text-muted, #64748b);background:transparent;border:none;border-bottom:2px solid transparent;cursor:pointer;transition:all var(--rpt-transition-speed, .15s) ease}.tab-btn:hover:not(:disabled){color:var(--rpt-color-text, #0f172a)}.tab-btn.active{color:var(--rpt-color-primary, #2563eb);border-bottom-color:var(--rpt-color-primary, #2563eb);background:#fff}.tab-btn:disabled{opacity:.4;cursor:not-allowed}.editor-body{flex:1;overflow-y:auto;padding:12px 14px}.component-sections-wrapper{display:flex;flex-direction:column;gap:12px}.comp-badge-header{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:var(--rpt-color-surface, #f8fafc);border-radius:6px;border:1px solid var(--rpt-color-border, #e2e8f0)}.badge-title{display:flex;align-items:center}.badge-type{font-size:.75rem;font-weight:700;color:var(--rpt-color-primary, #2563eb);text-transform:uppercase;margin-right:8px}.badge-id{font-size:.7rem;color:var(--rpt-color-text-muted, #94a3b8);font-family:monospace}.badge-header-actions{display:flex;align-items:center;gap:4px}.btn-badge-action{background:#fff;border:1px solid var(--rpt-color-border, #cbd5e1);display:inline-flex;align-items:center;justify-content:center;color:var(--rpt-color-text-muted, #64748b);cursor:pointer;width:24px;height:24px;border-radius:4px;padding:0;transition:all .12s ease}.btn-badge-action:hover{background:var(--rpt-color-surface-hover, #f1f5f9);color:var(--rpt-color-primary, #2563eb);border-color:var(--rpt-color-primary, #2563eb)}.btn-badge-action.btn-badge-delete:hover{background:#fee2e2;color:#ef4444;border-color:#fca5a5}.btn-deselect{background:none;border:1px solid transparent;display:inline-flex;align-items:center;justify-content:center;color:var(--rpt-color-text-muted, #64748b);cursor:pointer;width:24px;height:24px;border-radius:4px;padding:0;transition:all .15s ease}.btn-deselect:hover{color:var(--rpt-color-danger, #ef4444);background:#fee2e2;border-color:#fca5a5}.property-accordion{display:flex;flex-direction:column;gap:10px}.accordion-item{border:1px solid var(--rpt-color-border, #e2e8f0);border-radius:6px;background:#fff;overflow:hidden;transition:all .15s ease}.accordion-item.is-expanded{border-color:#cbd5e1;box-shadow:0 1px 3px #0000000a}.accordion-header{width:100%;display:flex;align-items:center;justify-content:space-between;padding:10px 12px;background:var(--rpt-color-surface, #f8fafc);border:none;cursor:pointer;text-align:left;-webkit-user-select:none;user-select:none;transition:background .15s ease}.accordion-header:hover{background:#f1f5f9}.accordion-item.is-expanded .accordion-header{background:#f8fafc;border-bottom:1px solid var(--rpt-color-border, #e2e8f0)}.header-left{display:flex;align-items:center;gap:8px;min-width:0;flex:1}.header-title{font-size:.72rem;font-weight:700;color:var(--rpt-color-text, #0f172a);text-transform:uppercase;letter-spacing:.03em;white-space:nowrap}.header-badge{font-size:.65rem;font-weight:600;color:var(--rpt-color-primary, #2563eb);background:#eff6ff;border:1px solid #bfdbfe;padding:1px 6px;border-radius:9999px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:120px}.header-right{display:flex;align-items:center;color:var(--rpt-color-text-muted, #64748b);margin-left:8px}.accordion-chevron{transition:transform .2s ease}.accordion-item.is-expanded .accordion-chevron{transform:rotate(180deg)}.accordion-content{padding:12px;display:flex;flex-direction:column;gap:12px}::ng-deep .accordion-content .group-title{display:none}::ng-deep .accordion-content .comp-badge-header{display:none}.empty-selection{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:60px 20px;text-align:center;box-sizing:border-box}.empty-icon-wrap{display:inline-flex;align-items:center;justify-content:center;width:52px;height:52px;border-radius:50%;background:#f1f5f9;margin-bottom:16px;box-shadow:0 1px 3px #0000000a}.empty-text{font-size:.8125rem;line-height:1.5;max-width:220px;margin:0;color:var(--rpt-color-text-muted, #64748b)}\n"] }]
|
|
17620
17866
|
}], ctorParameters: () => [] });
|
|
17621
17867
|
|
|
17622
17868
|
class DesignerComponent {
|
|
@@ -17643,6 +17889,7 @@ class DesignerComponent {
|
|
|
17643
17889
|
sessionService = inject(ReportSessionContextService, { optional: true });
|
|
17644
17890
|
executionService = inject(ReportExecutionService, { optional: true });
|
|
17645
17891
|
dialog = inject(Dialog, { optional: true });
|
|
17892
|
+
overlaySupport = inject(ReportOverlayService);
|
|
17646
17893
|
router = inject(Router, { optional: true });
|
|
17647
17894
|
reportingConfig = inject(REPORTING_CONFIG, { optional: true });
|
|
17648
17895
|
/** Optional reportId to load from persistence adapter */
|
|
@@ -17823,12 +18070,13 @@ class DesignerComponent {
|
|
|
17823
18070
|
this.showToast('The report review dialog is unavailable.');
|
|
17824
18071
|
return;
|
|
17825
18072
|
}
|
|
18073
|
+
this.overlaySupport.ensureStyles();
|
|
17826
18074
|
const routeBase = this.reportingConfig?.routeBase || 'reports';
|
|
17827
18075
|
const dialogRef = this.dialog.open(ReportSavedDialogComponent, {
|
|
17828
18076
|
injector: this.injector,
|
|
17829
18077
|
data: { report: this.currentDefinition(), routeBase },
|
|
17830
|
-
width: 'min(860px,
|
|
17831
|
-
panelClass: 'rpt-dialog-panel', disableClose: false,
|
|
18078
|
+
width: 'min(860px, calc(100vw - 32px))', maxWidth: '860px', maxHeight: 'calc(100dvh - 32px)',
|
|
18079
|
+
panelClass: 'rpt-dialog-panel', backdropClass: 'rpt-dialog-backdrop', disableClose: false,
|
|
17832
18080
|
ariaLabelledBy: 'dialog-title', autoFocus: '#rpt-dlg-id',
|
|
17833
18081
|
});
|
|
17834
18082
|
this.saveDialogOpen = true;
|
|
@@ -17993,7 +18241,7 @@ class DesignerComponent {
|
|
|
17993
18241
|
<rpt-design-canvas />
|
|
17994
18242
|
}
|
|
17995
18243
|
@case ('preview') {
|
|
17996
|
-
<div class="preview-mode-container">
|
|
18244
|
+
<div class="preview-mode-container" cdkScrollable>
|
|
17997
18245
|
<div class="preview-banner">
|
|
17998
18246
|
<rpt-icon name="eye" [size]="15" />
|
|
17999
18247
|
<span>Live Preview Mode — Running with actual Reactive Forms & Dynamic Grid</span>
|
|
@@ -18155,12 +18403,13 @@ class DesignerComponent {
|
|
|
18155
18403
|
</div>
|
|
18156
18404
|
}
|
|
18157
18405
|
</div>
|
|
18158
|
-
`, isInline: true, styles: [":host{display:flex;flex-direction:column;flex:1 1 0%;min-width:0;width:100%;height:100%;min-height:0;font-family:var(--rpt-font-family, inherit);box-sizing:border-box}.designer-shell{flex:1;min-height:0;display:flex;flex-direction:column;height:100%;background-color:#f8fafc;position:relative;overflow:hidden}.designer-workbench{display:flex;flex:1;overflow:hidden;position:relative;min-height:0}.workbench-left-container{display:flex;position:relative;width:268px;flex-shrink:0;height:100%;transition:width var(--rpt-transition-speed-smooth, .25s) cubic-bezier(.16,1,.3,1);z-index:20}.workbench-left-container.is-collapsed{width:0!important}.workbench-left-panel{width:100%;height:100%;overflow:hidden;border-right:1px solid var(--rpt-color-border, #cbd5e1);background:#fff;box-sizing:border-box}.workbench-left-container.is-collapsed .workbench-left-panel{visibility:hidden;border-right:none}.workbench-center-panel{flex:1;min-height:0;height:100%;overflow:hidden;display:flex;flex-direction:column;min-width:0;position:relative}.workbench-right-container{display:flex;position:relative;width:420px;flex-shrink:0;height:100%;transition:width var(--rpt-transition-speed-smooth, .25s) cubic-bezier(.16,1,.3,1);z-index:20}.workbench-right-container.is-collapsed{width:0!important}.workbench-right-panel{width:100%;height:100%;overflow:hidden;border-left:1px solid var(--rpt-color-border, #cbd5e1);background:#fff;box-sizing:border-box}.workbench-right-container.is-collapsed .workbench-right-panel{visibility:hidden;border-left:none}.compact-workbench-bar,.workbench-drawer-backdrop{display:none}@media (max-width: 900px){.compact-workbench-bar{display:flex;align-items:center;justify-content:space-around;height:48px;background:var(--rpt-color-surface, #ffffff);border-top:1px solid var(--rpt-color-border, #cbd5e1);padding:0 12px;gap:8px;z-index:45;flex-shrink:0;box-shadow:0 -2px 8px #0000000a;font-family:var(--rpt-font-family, inherit)}.compact-nav-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:6px 14px;border-radius:var(--rpt-border-radius, 6px);border:1px solid transparent;background:transparent;font-family:var(--rpt-font-family, inherit);font-size:var(--rpt-font-size-sm, .75rem);font-weight:var(--rpt-font-weight-medium, 500);letter-spacing:-.01em;line-height:1.4;color:var(--rpt-color-text-secondary, #334155);cursor:pointer;-webkit-user-select:none;user-select:none;transition:all var(--rpt-transition-speed, .15s) ease;white-space:nowrap}.compact-nav-btn rpt-icon{display:inline-flex;align-items:center;justify-content:center;color:var(--rpt-color-text-muted, #64748b);transition:color var(--rpt-transition-speed, .15s) ease}.compact-nav-btn:hover{background:var(--rpt-color-surface-hover, #f1f5f9);color:var(--rpt-color-text, #0f172a);border-color:var(--rpt-color-border-subtle, #e2e8f0)}.compact-nav-btn:hover rpt-icon{color:var(--rpt-color-text, #0f172a)}.compact-nav-btn.active{background:var(--rpt-color-primary-light, #eff6ff);color:var(--rpt-color-primary, #2563eb);border-color:#2563eb40;font-weight:var(--rpt-font-weight-semibold, 600);box-shadow:0 1px 2px #2563eb14}.compact-nav-btn.active rpt-icon{color:var(--rpt-color-primary, #2563eb)}.compact-nav-btn:focus-visible{outline:2px solid var(--rpt-color-primary, #2563eb);outline-offset:1px}.workbench-drawer-backdrop{display:block;position:absolute;inset:0;background:#0f172a59;backdrop-filter:blur(1px);z-index:50}.workbench-left-container{position:absolute;left:0;top:0;bottom:0;width:280px;max-width:85vw;z-index:60;box-shadow:4px 0 24px #00000026;transform:translate(0);transition:transform .25s cubic-bezier(.16,1,.3,1)}.workbench-left-container.is-collapsed{transform:translate(-100%);width:280px!important}.workbench-right-container{position:absolute;right:0;top:0;bottom:0;width:380px;max-width:95vw;z-index:60;box-shadow:-4px 0 24px #00000026;transform:translate(0);transition:transform .25s cubic-bezier(.16,1,.3,1)}.workbench-right-container.is-collapsed{transform:translate(100%);width:380px!important}}.preview-mode-container{flex:1;min-height:0;overflow-y:auto;padding:16px;background-color:#f1f5f9;box-sizing:border-box}.preview-banner{max-width:1400px;margin:0 auto 16px;background:#eff6ff;color:#1e40af;border:1px solid #bfdbfe;padding:10px 18px;border-radius:var(--rpt-border-radius-lg, 8px);font-size:var(--rpt-font-size-sm, .8rem);font-weight:600;display:flex;align-items:center;gap:8px;box-shadow:var(--rpt-box-shadow-xs, 0 1px 2px rgba(0, 0, 0, .04));box-sizing:border-box}.preview-content{max-width:1400px;margin:0 auto;background:#fff;border-radius:var(--rpt-border-radius-lg, 8px);border:1px solid var(--rpt-color-border, #cbd5e1);box-shadow:0 4px 6px -1px #0000000d;padding:0;min-height:480px;box-sizing:border-box}.json-mode-container{flex:1;min-height:0;padding:20px 24px;background-color:var(--rpt-color-bg, #f8fafc);display:flex;flex-direction:column;box-sizing:border-box;overflow:hidden}.json-editor-card{flex:1;min-height:0;display:flex;flex-direction:column;background:var(--rpt-color-surface, #ffffff);border:1px solid var(--rpt-color-border-subtle, #e2e8f0);border-radius:var(--rpt-border-radius-lg, 8px);box-shadow:0 1px 3px #0000000d,0 1px 2px -1px #00000008;overflow:hidden;transition:background-color .2s ease,border-color .2s ease}.json-editor-card.theme-dark{background:#0f172a;border-color:#1e293b;box-shadow:0 4px 12px #00000040}.json-banner{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:12px 18px;background:var(--rpt-color-surface, #ffffff);border-bottom:1px solid var(--rpt-color-border-subtle, #e2e8f0);flex-shrink:0;transition:background-color .2s ease,border-color .2s ease}.theme-dark .json-banner{background:#0b1120;border-bottom-color:#1e293b}.json-banner-left{display:flex;align-items:center;gap:12px;min-width:0}.json-badge-icon{width:32px;height:32px;border-radius:var(--rpt-border-radius, 6px);background:var(--rpt-color-primary-light, #eff6ff);color:var(--rpt-color-primary, #2563eb);border:1px solid rgba(37,99,235,.18);display:flex;align-items:center;justify-content:center;flex-shrink:0}.theme-dark .json-badge-icon{background:#2563eb33;color:#60a5fa;border-color:#60a5fa4d}.json-title-group{display:flex;flex-direction:column;gap:2px;min-width:0}.json-title-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.json-title{font-size:.875rem;font-weight:600;letter-spacing:-.01em;color:var(--rpt-color-text, #0f172a)}.theme-dark .json-title{color:#f1f5f9}.json-schema-tag{font-size:.6875rem;font-weight:600;letter-spacing:.02em;padding:1px 7px;border-radius:9999px;background:#f1f5f9;color:#475569;border:1px solid #e2e8f0;text-transform:uppercase}.theme-dark .json-schema-tag{background:#1e293b;color:#94a3b8;border-color:#334155}.json-meta-row{display:flex;align-items:center;gap:6px;font-size:.75rem;color:var(--rpt-color-text-muted, #64748b)}.theme-dark .json-meta-row{color:#64748b}.json-stat-pill strong{color:var(--rpt-color-text, #0f172a);font-weight:600}.theme-dark .json-stat-pill strong,.json-stat-divider{color:#cbd5e1}.theme-dark .json-stat-divider{color:#334155}.json-banner-actions{display:flex;align-items:center;gap:8px;flex-shrink:0}.btn-json-action{display:inline-flex;align-items:center;gap:6px;padding:5px 11px;font-size:.75rem;font-weight:500;color:var(--rpt-color-text-secondary, #334155);background:var(--rpt-color-surface, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);cursor:pointer;transition:all .15s ease}.btn-json-action:hover{background:var(--rpt-color-surface-hover, #f1f5f9);border-color:var(--rpt-color-border-hover, #94a3b8);color:var(--rpt-color-text, #0f172a)}.btn-json-action.active{background:var(--rpt-color-primary-light, #eff6ff);color:var(--rpt-color-primary, #2563eb);border-color:var(--rpt-color-primary, #2563eb);font-weight:600}.theme-dark .btn-json-action{background:#1e293b;border-color:#334155;color:#cbd5e1}.theme-dark .btn-json-action:hover{background:#334155;border-color:#475569;color:#fff}.theme-dark .btn-json-action.active{background:#2563eb40;color:#60a5fa;border-color:#3b82f6}.btn-copy-json{display:inline-flex;align-items:center;gap:6px;padding:5px 13px;font-size:.75rem;font-weight:600;color:#fff;background:var(--rpt-color-primary, #2563eb);border:1px solid var(--rpt-color-primary, #2563eb);border-radius:var(--rpt-border-radius, 6px);cursor:pointer;box-shadow:0 1px 2px #2563eb33;transition:all .15s ease}.btn-copy-json:hover{background:var(--rpt-color-primary-hover, #1d4ed8);border-color:var(--rpt-color-primary-hover, #1d4ed8)}.btn-copy-json.copied{background:#10b981;border-color:#10b981;box-shadow:0 1px 2px #10b98133}.json-viewport{flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column;background:#fff;transition:background-color .2s ease}.theme-dark .json-viewport{background:#0f172a}.json-scroll-container{flex:1;min-height:0;overflow:auto}.json-code-table{width:100%;border-collapse:collapse;font-family:var(--rpt-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace);font-size:.8125rem;line-height:1.6}.json-code-row{transition:background-color .1s ease}.json-code-row:hover{background:#f8fafc}.theme-dark .json-code-row:hover{background:#1e293b99}.json-line-number{width:44px;min-width:44px;max-width:56px;padding:1px 12px 1px 8px;text-align:right;color:#94a3b8;background:#f8fafc;border-right:1px solid #e2e8f0;-webkit-user-select:none;user-select:none;font-variant-numeric:tabular-nums;vertical-align:top;font-size:.75rem}.theme-dark .json-line-number{background:#0b1120;color:#475569;border-right-color:#1e293b}.json-line-text{padding:1px 16px;white-space:pre;vertical-align:top;color:#1e293b}.wrap-enabled .json-line-text{white-space:pre-wrap;word-break:break-all}.theme-dark .json-line-text{color:#e2e8f0}.json-token-key{color:#1e40af;font-weight:600}.json-token-string{color:#047857}.json-token-number{color:#7c3aed;font-weight:500}.json-token-boolean{color:#d97706;font-weight:600}.json-token-null{color:#dc2626;font-weight:600}.theme-dark .json-token-key{color:#60a5fa;font-weight:600}.theme-dark .json-token-string{color:#4ade80}.theme-dark .json-token-number{color:#c084fc;font-weight:500}.theme-dark .json-token-boolean{color:#fbbf24;font-weight:600}.theme-dark .json-token-null{color:#f87171;font-weight:600}.save-toast{position:absolute;bottom:24px;right:24px;background:#10b981;color:#fff;padding:10px 20px;border-radius:6px;font-size:.85rem;font-weight:600;box-shadow:0 10px 15px -3px #0000001a;z-index:100;animation:rpt-fade-in .2s ease}@keyframes rpt-fade-in{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i1$2.CdkDropListGroup, selector: "[cdkDropListGroup]", inputs: ["cdkDropListGroupDisabled"], exportAs: ["cdkDropListGroup"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: DesignerToolbarComponent, selector: "rpt-designer-toolbar", inputs: ["isLeftPanelCollapsed", "isRightPanelCollapsed"], outputs: ["toggleLeftPanel", "toggleRightPanel", "saveRequested"] }, { kind: "component", type: ComponentPaletteComponent, selector: "rpt-component-palette", outputs: ["collapseRequested"] }, { kind: "component", type: DesignCanvasComponent, selector: "rpt-design-canvas" }, { kind: "component", type: PropertyEditorComponent, selector: "rpt-property-editor", outputs: ["collapseRequested"] }, { kind: "component", type: ReportRuntimeComponent, selector: "rpt-report-runtime, rpt-runtime", inputs: ["reportId", "report", "resultData"], outputs: ["resetRequested", "execute", "executed", "exportRequested", "printRequested", "customActionTriggered", "gridStateChange", "columnDefsChange", "selectionChange", "selectionChanged", "rowActionTriggered", "drilldownRequested"] }, { kind: "component", type: RptIconComponent, selector: "rpt-icon", inputs: ["name", "size", "color", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
18406
|
+
`, isInline: true, styles: [":host{display:flex;flex-direction:column;flex:1 1 0%;min-width:0;width:100%;height:100%;min-height:0;font-family:var(--rpt-font-family, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Arial, sans-serif);box-sizing:border-box}.designer-shell{flex:1;min-height:0;display:flex;flex-direction:column;height:100%;background-color:#f8fafc;position:relative;overflow:hidden}.designer-workbench{display:flex;flex:1;overflow:hidden;position:relative;min-height:0}.workbench-left-container{display:flex;position:relative;width:268px;flex-shrink:0;height:100%;transition:width var(--rpt-transition-speed-smooth, .25s) cubic-bezier(.16,1,.3,1);z-index:20}.workbench-left-container.is-collapsed{width:0!important}.workbench-left-panel{width:100%;height:100%;overflow:hidden;border-right:1px solid var(--rpt-color-border, #cbd5e1);background:#fff;box-sizing:border-box}.workbench-left-container.is-collapsed .workbench-left-panel{visibility:hidden;border-right:none}.workbench-center-panel{flex:1;min-height:0;height:100%;overflow:hidden;display:flex;flex-direction:column;min-width:0;position:relative}.workbench-right-container{display:flex;position:relative;width:420px;flex-shrink:0;height:100%;transition:width var(--rpt-transition-speed-smooth, .25s) cubic-bezier(.16,1,.3,1);z-index:20}.workbench-right-container.is-collapsed{width:0!important}.workbench-right-panel{width:100%;height:100%;overflow:hidden;border-left:1px solid var(--rpt-color-border, #cbd5e1);background:#fff;box-sizing:border-box}.workbench-right-container.is-collapsed .workbench-right-panel{visibility:hidden;border-left:none}.compact-workbench-bar,.workbench-drawer-backdrop{display:none}@media (max-width: 900px){.compact-workbench-bar{display:flex;align-items:center;justify-content:space-around;height:48px;background:var(--rpt-color-surface, #ffffff);border-top:1px solid var(--rpt-color-border, #cbd5e1);padding:0 12px;gap:8px;z-index:45;flex-shrink:0;box-shadow:0 -2px 8px #0000000a;font-family:var(--rpt-font-family, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Arial, sans-serif)}.compact-nav-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:6px 14px;border-radius:var(--rpt-border-radius, 6px);border:1px solid transparent;background:transparent;font-family:var(--rpt-font-family, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Arial, sans-serif);font-size:var(--rpt-font-size-sm, .75rem);font-weight:var(--rpt-font-weight-medium, 500);letter-spacing:-.01em;line-height:1.4;color:var(--rpt-color-text-secondary, #334155);cursor:pointer;-webkit-user-select:none;user-select:none;transition:all var(--rpt-transition-speed, .15s) ease;white-space:nowrap}.compact-nav-btn rpt-icon{display:inline-flex;align-items:center;justify-content:center;color:var(--rpt-color-text-muted, #64748b);transition:color var(--rpt-transition-speed, .15s) ease}.compact-nav-btn:hover{background:var(--rpt-color-surface-hover, #f1f5f9);color:var(--rpt-color-text, #0f172a);border-color:var(--rpt-color-border-subtle, #e2e8f0)}.compact-nav-btn:hover rpt-icon{color:var(--rpt-color-text, #0f172a)}.compact-nav-btn.active{background:var(--rpt-color-primary-light, #eff6ff);color:var(--rpt-color-primary, #2563eb);border-color:#2563eb40;font-weight:var(--rpt-font-weight-semibold, 600);box-shadow:0 1px 2px #2563eb14}.compact-nav-btn.active rpt-icon{color:var(--rpt-color-primary, #2563eb)}.compact-nav-btn:focus-visible{outline:2px solid var(--rpt-color-primary, #2563eb);outline-offset:1px}.workbench-drawer-backdrop{display:block;position:absolute;inset:0;background:#0f172a59;backdrop-filter:blur(1px);z-index:50}.workbench-left-container{position:absolute;left:0;top:0;bottom:0;width:280px;max-width:85vw;z-index:60;box-shadow:4px 0 24px #00000026;transform:translate(0);transition:transform .25s cubic-bezier(.16,1,.3,1)}.workbench-left-container.is-collapsed{transform:translate(-100%);width:280px!important}.workbench-right-container{position:absolute;right:0;top:0;bottom:0;width:380px;max-width:95vw;z-index:60;box-shadow:-4px 0 24px #00000026;transform:translate(0);transition:transform .25s cubic-bezier(.16,1,.3,1)}.workbench-right-container.is-collapsed{transform:translate(100%);width:380px!important}}.preview-mode-container{flex:1;min-height:0;overflow-y:auto;padding:16px;background-color:#f1f5f9;box-sizing:border-box}.preview-banner{max-width:1400px;margin:0 auto 16px;background:#eff6ff;color:#1e40af;border:1px solid #bfdbfe;padding:10px 18px;border-radius:var(--rpt-border-radius-lg, 8px);font-size:var(--rpt-font-size-sm, .8rem);font-weight:600;display:flex;align-items:center;gap:8px;box-shadow:var(--rpt-box-shadow-xs, 0 1px 2px rgba(0, 0, 0, .04));box-sizing:border-box}.preview-content{max-width:1400px;margin:0 auto;background:#fff;border-radius:var(--rpt-border-radius-lg, 8px);border:1px solid var(--rpt-color-border, #cbd5e1);box-shadow:0 4px 6px -1px #0000000d;padding:0;min-height:480px;box-sizing:border-box}.json-mode-container{flex:1;min-height:0;padding:20px 24px;background-color:var(--rpt-color-bg, #f8fafc);display:flex;flex-direction:column;box-sizing:border-box;overflow:hidden}.json-editor-card{flex:1;min-height:0;display:flex;flex-direction:column;background:var(--rpt-color-surface, #ffffff);border:1px solid var(--rpt-color-border-subtle, #e2e8f0);border-radius:var(--rpt-border-radius-lg, 8px);box-shadow:0 1px 3px #0000000d,0 1px 2px -1px #00000008;overflow:hidden;transition:background-color .2s ease,border-color .2s ease}.json-editor-card.theme-dark{background:#0f172a;border-color:#1e293b;box-shadow:0 4px 12px #00000040}.json-banner{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:12px 18px;background:var(--rpt-color-surface, #ffffff);border-bottom:1px solid var(--rpt-color-border-subtle, #e2e8f0);flex-shrink:0;transition:background-color .2s ease,border-color .2s ease}.theme-dark .json-banner{background:#0b1120;border-bottom-color:#1e293b}.json-banner-left{display:flex;align-items:center;gap:12px;min-width:0}.json-badge-icon{width:32px;height:32px;border-radius:var(--rpt-border-radius, 6px);background:var(--rpt-color-primary-light, #eff6ff);color:var(--rpt-color-primary, #2563eb);border:1px solid rgba(37,99,235,.18);display:flex;align-items:center;justify-content:center;flex-shrink:0}.theme-dark .json-badge-icon{background:#2563eb33;color:#60a5fa;border-color:#60a5fa4d}.json-title-group{display:flex;flex-direction:column;gap:2px;min-width:0}.json-title-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.json-title{font-size:.875rem;font-weight:600;letter-spacing:-.01em;color:var(--rpt-color-text, #0f172a)}.theme-dark .json-title{color:#f1f5f9}.json-schema-tag{font-size:.6875rem;font-weight:600;letter-spacing:.02em;padding:1px 7px;border-radius:9999px;background:#f1f5f9;color:#475569;border:1px solid #e2e8f0;text-transform:uppercase}.theme-dark .json-schema-tag{background:#1e293b;color:#94a3b8;border-color:#334155}.json-meta-row{display:flex;align-items:center;gap:6px;font-size:.75rem;color:var(--rpt-color-text-muted, #64748b)}.theme-dark .json-meta-row{color:#64748b}.json-stat-pill strong{color:var(--rpt-color-text, #0f172a);font-weight:600}.theme-dark .json-stat-pill strong,.json-stat-divider{color:#cbd5e1}.theme-dark .json-stat-divider{color:#334155}.json-banner-actions{display:flex;align-items:center;gap:8px;flex-shrink:0}.btn-json-action{display:inline-flex;align-items:center;gap:6px;padding:5px 11px;font-size:.75rem;font-weight:500;color:var(--rpt-color-text-secondary, #334155);background:var(--rpt-color-surface, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);cursor:pointer;transition:all .15s ease}.btn-json-action:hover{background:var(--rpt-color-surface-hover, #f1f5f9);border-color:var(--rpt-color-border-hover, #94a3b8);color:var(--rpt-color-text, #0f172a)}.btn-json-action.active{background:var(--rpt-color-primary-light, #eff6ff);color:var(--rpt-color-primary, #2563eb);border-color:var(--rpt-color-primary, #2563eb);font-weight:600}.theme-dark .btn-json-action{background:#1e293b;border-color:#334155;color:#cbd5e1}.theme-dark .btn-json-action:hover{background:#334155;border-color:#475569;color:#fff}.theme-dark .btn-json-action.active{background:#2563eb40;color:#60a5fa;border-color:#3b82f6}.btn-copy-json{display:inline-flex;align-items:center;gap:6px;padding:5px 13px;font-size:.75rem;font-weight:600;color:#fff;background:var(--rpt-color-primary, #2563eb);border:1px solid var(--rpt-color-primary, #2563eb);border-radius:var(--rpt-border-radius, 6px);cursor:pointer;box-shadow:0 1px 2px #2563eb33;transition:all .15s ease}.btn-copy-json:hover{background:var(--rpt-color-primary-hover, #1d4ed8);border-color:var(--rpt-color-primary-hover, #1d4ed8)}.btn-copy-json.copied{background:#10b981;border-color:#10b981;box-shadow:0 1px 2px #10b98133}.json-viewport{flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column;background:#fff;transition:background-color .2s ease}.theme-dark .json-viewport{background:#0f172a}.json-scroll-container{flex:1;min-height:0;overflow:auto}.json-code-table{width:100%;border-collapse:collapse;font-family:var(--rpt-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace);font-size:.8125rem;line-height:1.6}.json-code-row{transition:background-color .1s ease}.json-code-row:hover{background:#f8fafc}.theme-dark .json-code-row:hover{background:#1e293b99}.json-line-number{width:44px;min-width:44px;max-width:56px;padding:1px 12px 1px 8px;text-align:right;color:#94a3b8;background:#f8fafc;border-right:1px solid #e2e8f0;-webkit-user-select:none;user-select:none;font-variant-numeric:tabular-nums;vertical-align:top;font-size:.75rem}.theme-dark .json-line-number{background:#0b1120;color:#475569;border-right-color:#1e293b}.json-line-text{padding:1px 16px;white-space:pre;vertical-align:top;color:#1e293b}.wrap-enabled .json-line-text{white-space:pre-wrap;word-break:break-all}.theme-dark .json-line-text{color:#e2e8f0}.json-token-key{color:#1e40af;font-weight:600}.json-token-string{color:#047857}.json-token-number{color:#7c3aed;font-weight:500}.json-token-boolean{color:#d97706;font-weight:600}.json-token-null{color:#dc2626;font-weight:600}.theme-dark .json-token-key{color:#60a5fa;font-weight:600}.theme-dark .json-token-string{color:#4ade80}.theme-dark .json-token-number{color:#c084fc;font-weight:500}.theme-dark .json-token-boolean{color:#fbbf24;font-weight:600}.theme-dark .json-token-null{color:#f87171;font-weight:600}.save-toast{position:absolute;bottom:24px;right:24px;background:#10b981;color:#fff;padding:10px 20px;border-radius:6px;font-size:.85rem;font-weight:600;box-shadow:0 10px 15px -3px #0000001a;z-index:100;animation:rpt-fade-in .2s ease}@keyframes rpt-fade-in{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: CdkScrollable, selector: "[cdk-scrollable], [cdkScrollable]" }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i1$2.CdkDropListGroup, selector: "[cdkDropListGroup]", inputs: ["cdkDropListGroupDisabled"], exportAs: ["cdkDropListGroup"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: DesignerToolbarComponent, selector: "rpt-designer-toolbar", inputs: ["isLeftPanelCollapsed", "isRightPanelCollapsed"], outputs: ["toggleLeftPanel", "toggleRightPanel", "saveRequested"] }, { kind: "component", type: ComponentPaletteComponent, selector: "rpt-component-palette", outputs: ["collapseRequested"] }, { kind: "component", type: DesignCanvasComponent, selector: "rpt-design-canvas" }, { kind: "component", type: PropertyEditorComponent, selector: "rpt-property-editor", outputs: ["collapseRequested"] }, { kind: "component", type: ReportRuntimeComponent, selector: "rpt-report-runtime, rpt-runtime", inputs: ["reportId", "report", "resultData"], outputs: ["resetRequested", "execute", "executed", "exportRequested", "printRequested", "customActionTriggered", "gridStateChange", "columnDefsChange", "selectionChange", "selectionChanged", "rowActionTriggered", "drilldownRequested"] }, { kind: "component", type: RptIconComponent, selector: "rpt-icon", inputs: ["name", "size", "color", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
18159
18407
|
}
|
|
18160
18408
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DesignerComponent, decorators: [{
|
|
18161
18409
|
type: Component,
|
|
18162
18410
|
args: [{ selector: 'rpt-designer', standalone: true, imports: [
|
|
18163
18411
|
CommonModule,
|
|
18412
|
+
CdkScrollable,
|
|
18164
18413
|
FormsModule,
|
|
18165
18414
|
DragDropModule,
|
|
18166
18415
|
DialogModule,
|
|
@@ -18209,7 +18458,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
18209
18458
|
<rpt-design-canvas />
|
|
18210
18459
|
}
|
|
18211
18460
|
@case ('preview') {
|
|
18212
|
-
<div class="preview-mode-container">
|
|
18461
|
+
<div class="preview-mode-container" cdkScrollable>
|
|
18213
18462
|
<div class="preview-banner">
|
|
18214
18463
|
<rpt-icon name="eye" [size]="15" />
|
|
18215
18464
|
<span>Live Preview Mode — Running with actual Reactive Forms & Dynamic Grid</span>
|
|
@@ -18371,7 +18620,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
|
|
|
18371
18620
|
</div>
|
|
18372
18621
|
}
|
|
18373
18622
|
</div>
|
|
18374
|
-
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:flex;flex-direction:column;flex:1 1 0%;min-width:0;width:100%;height:100%;min-height:0;font-family:var(--rpt-font-family, inherit);box-sizing:border-box}.designer-shell{flex:1;min-height:0;display:flex;flex-direction:column;height:100%;background-color:#f8fafc;position:relative;overflow:hidden}.designer-workbench{display:flex;flex:1;overflow:hidden;position:relative;min-height:0}.workbench-left-container{display:flex;position:relative;width:268px;flex-shrink:0;height:100%;transition:width var(--rpt-transition-speed-smooth, .25s) cubic-bezier(.16,1,.3,1);z-index:20}.workbench-left-container.is-collapsed{width:0!important}.workbench-left-panel{width:100%;height:100%;overflow:hidden;border-right:1px solid var(--rpt-color-border, #cbd5e1);background:#fff;box-sizing:border-box}.workbench-left-container.is-collapsed .workbench-left-panel{visibility:hidden;border-right:none}.workbench-center-panel{flex:1;min-height:0;height:100%;overflow:hidden;display:flex;flex-direction:column;min-width:0;position:relative}.workbench-right-container{display:flex;position:relative;width:420px;flex-shrink:0;height:100%;transition:width var(--rpt-transition-speed-smooth, .25s) cubic-bezier(.16,1,.3,1);z-index:20}.workbench-right-container.is-collapsed{width:0!important}.workbench-right-panel{width:100%;height:100%;overflow:hidden;border-left:1px solid var(--rpt-color-border, #cbd5e1);background:#fff;box-sizing:border-box}.workbench-right-container.is-collapsed .workbench-right-panel{visibility:hidden;border-left:none}.compact-workbench-bar,.workbench-drawer-backdrop{display:none}@media (max-width: 900px){.compact-workbench-bar{display:flex;align-items:center;justify-content:space-around;height:48px;background:var(--rpt-color-surface, #ffffff);border-top:1px solid var(--rpt-color-border, #cbd5e1);padding:0 12px;gap:8px;z-index:45;flex-shrink:0;box-shadow:0 -2px 8px #0000000a;font-family:var(--rpt-font-family, inherit)}.compact-nav-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:6px 14px;border-radius:var(--rpt-border-radius, 6px);border:1px solid transparent;background:transparent;font-family:var(--rpt-font-family, inherit);font-size:var(--rpt-font-size-sm, .75rem);font-weight:var(--rpt-font-weight-medium, 500);letter-spacing:-.01em;line-height:1.4;color:var(--rpt-color-text-secondary, #334155);cursor:pointer;-webkit-user-select:none;user-select:none;transition:all var(--rpt-transition-speed, .15s) ease;white-space:nowrap}.compact-nav-btn rpt-icon{display:inline-flex;align-items:center;justify-content:center;color:var(--rpt-color-text-muted, #64748b);transition:color var(--rpt-transition-speed, .15s) ease}.compact-nav-btn:hover{background:var(--rpt-color-surface-hover, #f1f5f9);color:var(--rpt-color-text, #0f172a);border-color:var(--rpt-color-border-subtle, #e2e8f0)}.compact-nav-btn:hover rpt-icon{color:var(--rpt-color-text, #0f172a)}.compact-nav-btn.active{background:var(--rpt-color-primary-light, #eff6ff);color:var(--rpt-color-primary, #2563eb);border-color:#2563eb40;font-weight:var(--rpt-font-weight-semibold, 600);box-shadow:0 1px 2px #2563eb14}.compact-nav-btn.active rpt-icon{color:var(--rpt-color-primary, #2563eb)}.compact-nav-btn:focus-visible{outline:2px solid var(--rpt-color-primary, #2563eb);outline-offset:1px}.workbench-drawer-backdrop{display:block;position:absolute;inset:0;background:#0f172a59;backdrop-filter:blur(1px);z-index:50}.workbench-left-container{position:absolute;left:0;top:0;bottom:0;width:280px;max-width:85vw;z-index:60;box-shadow:4px 0 24px #00000026;transform:translate(0);transition:transform .25s cubic-bezier(.16,1,.3,1)}.workbench-left-container.is-collapsed{transform:translate(-100%);width:280px!important}.workbench-right-container{position:absolute;right:0;top:0;bottom:0;width:380px;max-width:95vw;z-index:60;box-shadow:-4px 0 24px #00000026;transform:translate(0);transition:transform .25s cubic-bezier(.16,1,.3,1)}.workbench-right-container.is-collapsed{transform:translate(100%);width:380px!important}}.preview-mode-container{flex:1;min-height:0;overflow-y:auto;padding:16px;background-color:#f1f5f9;box-sizing:border-box}.preview-banner{max-width:1400px;margin:0 auto 16px;background:#eff6ff;color:#1e40af;border:1px solid #bfdbfe;padding:10px 18px;border-radius:var(--rpt-border-radius-lg, 8px);font-size:var(--rpt-font-size-sm, .8rem);font-weight:600;display:flex;align-items:center;gap:8px;box-shadow:var(--rpt-box-shadow-xs, 0 1px 2px rgba(0, 0, 0, .04));box-sizing:border-box}.preview-content{max-width:1400px;margin:0 auto;background:#fff;border-radius:var(--rpt-border-radius-lg, 8px);border:1px solid var(--rpt-color-border, #cbd5e1);box-shadow:0 4px 6px -1px #0000000d;padding:0;min-height:480px;box-sizing:border-box}.json-mode-container{flex:1;min-height:0;padding:20px 24px;background-color:var(--rpt-color-bg, #f8fafc);display:flex;flex-direction:column;box-sizing:border-box;overflow:hidden}.json-editor-card{flex:1;min-height:0;display:flex;flex-direction:column;background:var(--rpt-color-surface, #ffffff);border:1px solid var(--rpt-color-border-subtle, #e2e8f0);border-radius:var(--rpt-border-radius-lg, 8px);box-shadow:0 1px 3px #0000000d,0 1px 2px -1px #00000008;overflow:hidden;transition:background-color .2s ease,border-color .2s ease}.json-editor-card.theme-dark{background:#0f172a;border-color:#1e293b;box-shadow:0 4px 12px #00000040}.json-banner{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:12px 18px;background:var(--rpt-color-surface, #ffffff);border-bottom:1px solid var(--rpt-color-border-subtle, #e2e8f0);flex-shrink:0;transition:background-color .2s ease,border-color .2s ease}.theme-dark .json-banner{background:#0b1120;border-bottom-color:#1e293b}.json-banner-left{display:flex;align-items:center;gap:12px;min-width:0}.json-badge-icon{width:32px;height:32px;border-radius:var(--rpt-border-radius, 6px);background:var(--rpt-color-primary-light, #eff6ff);color:var(--rpt-color-primary, #2563eb);border:1px solid rgba(37,99,235,.18);display:flex;align-items:center;justify-content:center;flex-shrink:0}.theme-dark .json-badge-icon{background:#2563eb33;color:#60a5fa;border-color:#60a5fa4d}.json-title-group{display:flex;flex-direction:column;gap:2px;min-width:0}.json-title-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.json-title{font-size:.875rem;font-weight:600;letter-spacing:-.01em;color:var(--rpt-color-text, #0f172a)}.theme-dark .json-title{color:#f1f5f9}.json-schema-tag{font-size:.6875rem;font-weight:600;letter-spacing:.02em;padding:1px 7px;border-radius:9999px;background:#f1f5f9;color:#475569;border:1px solid #e2e8f0;text-transform:uppercase}.theme-dark .json-schema-tag{background:#1e293b;color:#94a3b8;border-color:#334155}.json-meta-row{display:flex;align-items:center;gap:6px;font-size:.75rem;color:var(--rpt-color-text-muted, #64748b)}.theme-dark .json-meta-row{color:#64748b}.json-stat-pill strong{color:var(--rpt-color-text, #0f172a);font-weight:600}.theme-dark .json-stat-pill strong,.json-stat-divider{color:#cbd5e1}.theme-dark .json-stat-divider{color:#334155}.json-banner-actions{display:flex;align-items:center;gap:8px;flex-shrink:0}.btn-json-action{display:inline-flex;align-items:center;gap:6px;padding:5px 11px;font-size:.75rem;font-weight:500;color:var(--rpt-color-text-secondary, #334155);background:var(--rpt-color-surface, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);cursor:pointer;transition:all .15s ease}.btn-json-action:hover{background:var(--rpt-color-surface-hover, #f1f5f9);border-color:var(--rpt-color-border-hover, #94a3b8);color:var(--rpt-color-text, #0f172a)}.btn-json-action.active{background:var(--rpt-color-primary-light, #eff6ff);color:var(--rpt-color-primary, #2563eb);border-color:var(--rpt-color-primary, #2563eb);font-weight:600}.theme-dark .btn-json-action{background:#1e293b;border-color:#334155;color:#cbd5e1}.theme-dark .btn-json-action:hover{background:#334155;border-color:#475569;color:#fff}.theme-dark .btn-json-action.active{background:#2563eb40;color:#60a5fa;border-color:#3b82f6}.btn-copy-json{display:inline-flex;align-items:center;gap:6px;padding:5px 13px;font-size:.75rem;font-weight:600;color:#fff;background:var(--rpt-color-primary, #2563eb);border:1px solid var(--rpt-color-primary, #2563eb);border-radius:var(--rpt-border-radius, 6px);cursor:pointer;box-shadow:0 1px 2px #2563eb33;transition:all .15s ease}.btn-copy-json:hover{background:var(--rpt-color-primary-hover, #1d4ed8);border-color:var(--rpt-color-primary-hover, #1d4ed8)}.btn-copy-json.copied{background:#10b981;border-color:#10b981;box-shadow:0 1px 2px #10b98133}.json-viewport{flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column;background:#fff;transition:background-color .2s ease}.theme-dark .json-viewport{background:#0f172a}.json-scroll-container{flex:1;min-height:0;overflow:auto}.json-code-table{width:100%;border-collapse:collapse;font-family:var(--rpt-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace);font-size:.8125rem;line-height:1.6}.json-code-row{transition:background-color .1s ease}.json-code-row:hover{background:#f8fafc}.theme-dark .json-code-row:hover{background:#1e293b99}.json-line-number{width:44px;min-width:44px;max-width:56px;padding:1px 12px 1px 8px;text-align:right;color:#94a3b8;background:#f8fafc;border-right:1px solid #e2e8f0;-webkit-user-select:none;user-select:none;font-variant-numeric:tabular-nums;vertical-align:top;font-size:.75rem}.theme-dark .json-line-number{background:#0b1120;color:#475569;border-right-color:#1e293b}.json-line-text{padding:1px 16px;white-space:pre;vertical-align:top;color:#1e293b}.wrap-enabled .json-line-text{white-space:pre-wrap;word-break:break-all}.theme-dark .json-line-text{color:#e2e8f0}.json-token-key{color:#1e40af;font-weight:600}.json-token-string{color:#047857}.json-token-number{color:#7c3aed;font-weight:500}.json-token-boolean{color:#d97706;font-weight:600}.json-token-null{color:#dc2626;font-weight:600}.theme-dark .json-token-key{color:#60a5fa;font-weight:600}.theme-dark .json-token-string{color:#4ade80}.theme-dark .json-token-number{color:#c084fc;font-weight:500}.theme-dark .json-token-boolean{color:#fbbf24;font-weight:600}.theme-dark .json-token-null{color:#f87171;font-weight:600}.save-toast{position:absolute;bottom:24px;right:24px;background:#10b981;color:#fff;padding:10px 20px;border-radius:6px;font-size:.85rem;font-weight:600;box-shadow:0 10px 15px -3px #0000001a;z-index:100;animation:rpt-fade-in .2s ease}@keyframes rpt-fade-in{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}\n"] }]
|
|
18623
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:flex;flex-direction:column;flex:1 1 0%;min-width:0;width:100%;height:100%;min-height:0;font-family:var(--rpt-font-family, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Arial, sans-serif);box-sizing:border-box}.designer-shell{flex:1;min-height:0;display:flex;flex-direction:column;height:100%;background-color:#f8fafc;position:relative;overflow:hidden}.designer-workbench{display:flex;flex:1;overflow:hidden;position:relative;min-height:0}.workbench-left-container{display:flex;position:relative;width:268px;flex-shrink:0;height:100%;transition:width var(--rpt-transition-speed-smooth, .25s) cubic-bezier(.16,1,.3,1);z-index:20}.workbench-left-container.is-collapsed{width:0!important}.workbench-left-panel{width:100%;height:100%;overflow:hidden;border-right:1px solid var(--rpt-color-border, #cbd5e1);background:#fff;box-sizing:border-box}.workbench-left-container.is-collapsed .workbench-left-panel{visibility:hidden;border-right:none}.workbench-center-panel{flex:1;min-height:0;height:100%;overflow:hidden;display:flex;flex-direction:column;min-width:0;position:relative}.workbench-right-container{display:flex;position:relative;width:420px;flex-shrink:0;height:100%;transition:width var(--rpt-transition-speed-smooth, .25s) cubic-bezier(.16,1,.3,1);z-index:20}.workbench-right-container.is-collapsed{width:0!important}.workbench-right-panel{width:100%;height:100%;overflow:hidden;border-left:1px solid var(--rpt-color-border, #cbd5e1);background:#fff;box-sizing:border-box}.workbench-right-container.is-collapsed .workbench-right-panel{visibility:hidden;border-left:none}.compact-workbench-bar,.workbench-drawer-backdrop{display:none}@media (max-width: 900px){.compact-workbench-bar{display:flex;align-items:center;justify-content:space-around;height:48px;background:var(--rpt-color-surface, #ffffff);border-top:1px solid var(--rpt-color-border, #cbd5e1);padding:0 12px;gap:8px;z-index:45;flex-shrink:0;box-shadow:0 -2px 8px #0000000a;font-family:var(--rpt-font-family, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Arial, sans-serif)}.compact-nav-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:6px 14px;border-radius:var(--rpt-border-radius, 6px);border:1px solid transparent;background:transparent;font-family:var(--rpt-font-family, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Arial, sans-serif);font-size:var(--rpt-font-size-sm, .75rem);font-weight:var(--rpt-font-weight-medium, 500);letter-spacing:-.01em;line-height:1.4;color:var(--rpt-color-text-secondary, #334155);cursor:pointer;-webkit-user-select:none;user-select:none;transition:all var(--rpt-transition-speed, .15s) ease;white-space:nowrap}.compact-nav-btn rpt-icon{display:inline-flex;align-items:center;justify-content:center;color:var(--rpt-color-text-muted, #64748b);transition:color var(--rpt-transition-speed, .15s) ease}.compact-nav-btn:hover{background:var(--rpt-color-surface-hover, #f1f5f9);color:var(--rpt-color-text, #0f172a);border-color:var(--rpt-color-border-subtle, #e2e8f0)}.compact-nav-btn:hover rpt-icon{color:var(--rpt-color-text, #0f172a)}.compact-nav-btn.active{background:var(--rpt-color-primary-light, #eff6ff);color:var(--rpt-color-primary, #2563eb);border-color:#2563eb40;font-weight:var(--rpt-font-weight-semibold, 600);box-shadow:0 1px 2px #2563eb14}.compact-nav-btn.active rpt-icon{color:var(--rpt-color-primary, #2563eb)}.compact-nav-btn:focus-visible{outline:2px solid var(--rpt-color-primary, #2563eb);outline-offset:1px}.workbench-drawer-backdrop{display:block;position:absolute;inset:0;background:#0f172a59;backdrop-filter:blur(1px);z-index:50}.workbench-left-container{position:absolute;left:0;top:0;bottom:0;width:280px;max-width:85vw;z-index:60;box-shadow:4px 0 24px #00000026;transform:translate(0);transition:transform .25s cubic-bezier(.16,1,.3,1)}.workbench-left-container.is-collapsed{transform:translate(-100%);width:280px!important}.workbench-right-container{position:absolute;right:0;top:0;bottom:0;width:380px;max-width:95vw;z-index:60;box-shadow:-4px 0 24px #00000026;transform:translate(0);transition:transform .25s cubic-bezier(.16,1,.3,1)}.workbench-right-container.is-collapsed{transform:translate(100%);width:380px!important}}.preview-mode-container{flex:1;min-height:0;overflow-y:auto;padding:16px;background-color:#f1f5f9;box-sizing:border-box}.preview-banner{max-width:1400px;margin:0 auto 16px;background:#eff6ff;color:#1e40af;border:1px solid #bfdbfe;padding:10px 18px;border-radius:var(--rpt-border-radius-lg, 8px);font-size:var(--rpt-font-size-sm, .8rem);font-weight:600;display:flex;align-items:center;gap:8px;box-shadow:var(--rpt-box-shadow-xs, 0 1px 2px rgba(0, 0, 0, .04));box-sizing:border-box}.preview-content{max-width:1400px;margin:0 auto;background:#fff;border-radius:var(--rpt-border-radius-lg, 8px);border:1px solid var(--rpt-color-border, #cbd5e1);box-shadow:0 4px 6px -1px #0000000d;padding:0;min-height:480px;box-sizing:border-box}.json-mode-container{flex:1;min-height:0;padding:20px 24px;background-color:var(--rpt-color-bg, #f8fafc);display:flex;flex-direction:column;box-sizing:border-box;overflow:hidden}.json-editor-card{flex:1;min-height:0;display:flex;flex-direction:column;background:var(--rpt-color-surface, #ffffff);border:1px solid var(--rpt-color-border-subtle, #e2e8f0);border-radius:var(--rpt-border-radius-lg, 8px);box-shadow:0 1px 3px #0000000d,0 1px 2px -1px #00000008;overflow:hidden;transition:background-color .2s ease,border-color .2s ease}.json-editor-card.theme-dark{background:#0f172a;border-color:#1e293b;box-shadow:0 4px 12px #00000040}.json-banner{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:12px 18px;background:var(--rpt-color-surface, #ffffff);border-bottom:1px solid var(--rpt-color-border-subtle, #e2e8f0);flex-shrink:0;transition:background-color .2s ease,border-color .2s ease}.theme-dark .json-banner{background:#0b1120;border-bottom-color:#1e293b}.json-banner-left{display:flex;align-items:center;gap:12px;min-width:0}.json-badge-icon{width:32px;height:32px;border-radius:var(--rpt-border-radius, 6px);background:var(--rpt-color-primary-light, #eff6ff);color:var(--rpt-color-primary, #2563eb);border:1px solid rgba(37,99,235,.18);display:flex;align-items:center;justify-content:center;flex-shrink:0}.theme-dark .json-badge-icon{background:#2563eb33;color:#60a5fa;border-color:#60a5fa4d}.json-title-group{display:flex;flex-direction:column;gap:2px;min-width:0}.json-title-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.json-title{font-size:.875rem;font-weight:600;letter-spacing:-.01em;color:var(--rpt-color-text, #0f172a)}.theme-dark .json-title{color:#f1f5f9}.json-schema-tag{font-size:.6875rem;font-weight:600;letter-spacing:.02em;padding:1px 7px;border-radius:9999px;background:#f1f5f9;color:#475569;border:1px solid #e2e8f0;text-transform:uppercase}.theme-dark .json-schema-tag{background:#1e293b;color:#94a3b8;border-color:#334155}.json-meta-row{display:flex;align-items:center;gap:6px;font-size:.75rem;color:var(--rpt-color-text-muted, #64748b)}.theme-dark .json-meta-row{color:#64748b}.json-stat-pill strong{color:var(--rpt-color-text, #0f172a);font-weight:600}.theme-dark .json-stat-pill strong,.json-stat-divider{color:#cbd5e1}.theme-dark .json-stat-divider{color:#334155}.json-banner-actions{display:flex;align-items:center;gap:8px;flex-shrink:0}.btn-json-action{display:inline-flex;align-items:center;gap:6px;padding:5px 11px;font-size:.75rem;font-weight:500;color:var(--rpt-color-text-secondary, #334155);background:var(--rpt-color-surface, #ffffff);border:1px solid var(--rpt-color-border, #cbd5e1);border-radius:var(--rpt-border-radius, 6px);cursor:pointer;transition:all .15s ease}.btn-json-action:hover{background:var(--rpt-color-surface-hover, #f1f5f9);border-color:var(--rpt-color-border-hover, #94a3b8);color:var(--rpt-color-text, #0f172a)}.btn-json-action.active{background:var(--rpt-color-primary-light, #eff6ff);color:var(--rpt-color-primary, #2563eb);border-color:var(--rpt-color-primary, #2563eb);font-weight:600}.theme-dark .btn-json-action{background:#1e293b;border-color:#334155;color:#cbd5e1}.theme-dark .btn-json-action:hover{background:#334155;border-color:#475569;color:#fff}.theme-dark .btn-json-action.active{background:#2563eb40;color:#60a5fa;border-color:#3b82f6}.btn-copy-json{display:inline-flex;align-items:center;gap:6px;padding:5px 13px;font-size:.75rem;font-weight:600;color:#fff;background:var(--rpt-color-primary, #2563eb);border:1px solid var(--rpt-color-primary, #2563eb);border-radius:var(--rpt-border-radius, 6px);cursor:pointer;box-shadow:0 1px 2px #2563eb33;transition:all .15s ease}.btn-copy-json:hover{background:var(--rpt-color-primary-hover, #1d4ed8);border-color:var(--rpt-color-primary-hover, #1d4ed8)}.btn-copy-json.copied{background:#10b981;border-color:#10b981;box-shadow:0 1px 2px #10b98133}.json-viewport{flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column;background:#fff;transition:background-color .2s ease}.theme-dark .json-viewport{background:#0f172a}.json-scroll-container{flex:1;min-height:0;overflow:auto}.json-code-table{width:100%;border-collapse:collapse;font-family:var(--rpt-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace);font-size:.8125rem;line-height:1.6}.json-code-row{transition:background-color .1s ease}.json-code-row:hover{background:#f8fafc}.theme-dark .json-code-row:hover{background:#1e293b99}.json-line-number{width:44px;min-width:44px;max-width:56px;padding:1px 12px 1px 8px;text-align:right;color:#94a3b8;background:#f8fafc;border-right:1px solid #e2e8f0;-webkit-user-select:none;user-select:none;font-variant-numeric:tabular-nums;vertical-align:top;font-size:.75rem}.theme-dark .json-line-number{background:#0b1120;color:#475569;border-right-color:#1e293b}.json-line-text{padding:1px 16px;white-space:pre;vertical-align:top;color:#1e293b}.wrap-enabled .json-line-text{white-space:pre-wrap;word-break:break-all}.theme-dark .json-line-text{color:#e2e8f0}.json-token-key{color:#1e40af;font-weight:600}.json-token-string{color:#047857}.json-token-number{color:#7c3aed;font-weight:500}.json-token-boolean{color:#d97706;font-weight:600}.json-token-null{color:#dc2626;font-weight:600}.theme-dark .json-token-key{color:#60a5fa;font-weight:600}.theme-dark .json-token-string{color:#4ade80}.theme-dark .json-token-number{color:#c084fc;font-weight:500}.theme-dark .json-token-boolean{color:#fbbf24;font-weight:600}.theme-dark .json-token-null{color:#f87171;font-weight:600}.save-toast{position:absolute;bottom:24px;right:24px;background:#10b981;color:#fff;padding:10px 20px;border-radius:6px;font-size:.85rem;font-weight:600;box-shadow:0 10px 15px -3px #0000001a;z-index:100;animation:rpt-fade-in .2s ease}@keyframes rpt-fade-in{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}\n"] }]
|
|
18375
18624
|
}], ctorParameters: () => [], propDecorators: { config: [{
|
|
18376
18625
|
type: Input
|
|
18377
18626
|
}], apiUrl: [{
|
|
@@ -20305,5 +20554,5 @@ const bwaysReportProviders = provideBwaysReport;
|
|
|
20305
20554
|
* Generated bundle index. Do not edit.
|
|
20306
20555
|
*/
|
|
20307
20556
|
|
|
20308
|
-
export { AdapterNotProvidedError, BWAYS_REPORT_ENDPOINTS, BrowserReportStorage, BwaysReportBuilderComponent, BwaysReportMenuComponent, BwaysReportPageComponent, CHART_COMPONENT, ComponentNotRegisteredError, ComponentPaletteComponent, ComponentRegistry, DataSourceError, DesignCanvasComponent, DesignerComponent, DesignerStateService, DesignerToolbarComponent, DynamicFormBuilder, DynamicRendererComponent, EndpointDataSourceAdapter, EndpointLookupAdapter, EndpointMasterDataAdapter, EndpointPersistenceAdapter, EndpointSchemaDiscoveryAdapter, GRID_COMPONENT, HttpDataSourceAdapter, HttpLookupAdapter, HttpMasterDataAdapter, HttpPersistenceAdapter, HttpReportDataSourceAdapter, HttpReportLookupAdapter, HttpReportPersistenceAdapter, HttpReportSchemaDiscoveryAdapter, HttpSchemaDiscoveryAdapter, LocalStorageReportPersistenceAdapter, MasterDataProvider, MemoryReportStorage, MockReportDataSourceAdapter, PALETTE_CATEGORY_MAP, PropertyEditorActionComponent, PropertyEditorComponent, REPORTING_CONFIG, REPORT_SESSION_CONTEXT, REPORT_SESSION_PROVIDER, REPORT_STORAGE, ReportApiExecutionService, ReportChartBridgeComponent, ReportDataDiscoveryService, ReportDataSourceAdapter, ReportDefinitionError, ReportExportService, ReportGridBridgeComponent, ReportLookupAdapter, ReportMenuService, ReportPersistenceAdapter, ReportRuntimeComponent, ReportSchemaDiscoveryAdapter, ReportSessionContextService, ReportStorageService, ReportStoreService, ReportTemplateService, ReportTranslationAdapter, ReportVersioningService, ReportingFeatureKind, RoutedReportDesignerComponent, RoutedReportRuntimeComponent, RptButtonComponent, RptCheckboxComponent, RptDateComponent, RptDateRangeComponent, RptDropdownComponent, RptIconComponent, RptInputComponent, RptKpiCardComponent, RptNumberComponent, RptRadioComponent, RptReportComponent, RptReportDesignerComponent, RptSearchableSelectComponent, SchemaValidatorService, SchemaVersionError, SessionContextProvider, bwaysReportProviders, bwaysReportRoutes, createReportRoutes, createValidators, extractLookupOptionItem, extractRowArray, findMissingCanvasFields, formatFilterSummary, generateCsvContent, generateExcelXml, generateHtmlContent, generateId, getFieldCaseInsensitive, getValidationMessage, groupReportsByCategory, injectReportMenu, injectReportMenuGroups, migrateDefinition, normalizeBwaysConfig, parseSqlExecStatement, provideBwaysReport, provideBwaysReporting, provideReporting, registerBuiltinComponents, reportResolver, resolveCanvasFieldValue, resolveContextPreset, resolveExecutionDataSource, resolveParameterMappings, resolveParameterValue, sanitizeCsvCell, sanitizeSqlParamLiteral, toFieldKey, validateReportSchema, withHttpAdapters, withLocalStorage, withMockAdapters };
|
|
20557
|
+
export { AdapterNotProvidedError, BWAYS_REPORT_ENDPOINTS, BrowserReportStorage, BwaysReportBuilderComponent, BwaysReportMenuComponent, BwaysReportPageComponent, CHART_COMPONENT, ComponentNotRegisteredError, ComponentPaletteComponent, ComponentRegistry, DataSourceError, DesignCanvasComponent, DesignerComponent, DesignerStateService, DesignerToolbarComponent, DynamicFormBuilder, DynamicRendererComponent, EndpointDataSourceAdapter, EndpointLookupAdapter, EndpointMasterDataAdapter, EndpointPersistenceAdapter, EndpointSchemaDiscoveryAdapter, GRID_COMPONENT, HttpDataSourceAdapter, HttpLookupAdapter, HttpMasterDataAdapter, HttpPersistenceAdapter, HttpReportDataSourceAdapter, HttpReportLookupAdapter, HttpReportPersistenceAdapter, HttpReportSchemaDiscoveryAdapter, HttpSchemaDiscoveryAdapter, LocalStorageReportPersistenceAdapter, MasterDataProvider, MemoryReportStorage, MockReportDataSourceAdapter, PALETTE_CATEGORY_MAP, PARAMETER_DATE_FORMATS, PropertyEditorActionComponent, PropertyEditorComponent, REPORTING_CONFIG, REPORT_SESSION_CONTEXT, REPORT_SESSION_PROVIDER, REPORT_STORAGE, ReportApiExecutionService, ReportChartBridgeComponent, ReportDataDiscoveryService, ReportDataSourceAdapter, ReportDefinitionError, ReportExportService, ReportGridBridgeComponent, ReportLookupAdapter, ReportMenuService, ReportPersistenceAdapter, ReportRuntimeComponent, ReportSchemaDiscoveryAdapter, ReportSessionContextService, ReportStorageService, ReportStoreService, ReportTemplateService, ReportTranslationAdapter, ReportVersioningService, ReportingFeatureKind, RoutedReportDesignerComponent, RoutedReportRuntimeComponent, RptButtonComponent, RptCheckboxComponent, RptDateComponent, RptDateRangeComponent, RptDropdownComponent, RptIconComponent, RptInputComponent, RptKpiCardComponent, RptNumberComponent, RptRadioComponent, RptReportComponent, RptReportDesignerComponent, RptSearchableSelectComponent, SchemaValidatorService, SchemaVersionError, SessionContextProvider, bwaysReportProviders, bwaysReportRoutes, createReportRoutes, createValidators, extractLookupOptionItem, extractRowArray, findMissingCanvasFields, formatFilterSummary, formatParameterDate, generateCsvContent, generateExcelXml, generateHtmlContent, generateId, getFieldCaseInsensitive, getValidationMessage, groupReportsByCategory, injectReportMenu, injectReportMenuGroups, migrateDefinition, normalizeBwaysConfig, normalizeParameterNames, parseParameterToken, parseSqlExecStatement, provideBwaysReport, provideBwaysReporting, provideReporting, registerBuiltinComponents, reportResolver, resolveCanvasFieldValue, resolveContextPreset, resolveExecutionDataSource, resolveParameterMappings, resolveParameterValue, sanitizeCsvCell, sanitizeSqlParamLiteral, setParameterMapping, toFieldKey, validateReportSchema, withHttpAdapters, withLocalStorage, withMockAdapters };
|
|
20309
20558
|
//# sourceMappingURL=bways-report.mjs.map
|