bways-report 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +46 -0
  2. package/esm2022/lib/core/models/report-grid.model.mjs +1 -1
  3. package/esm2022/lib/core/services/report-overlay.service.mjs +79 -0
  4. package/esm2022/lib/core/utils/report-parameter.util.mjs +68 -15
  5. package/esm2022/lib/data-access/adapters/endpoint-datasource.adapter.mjs +10 -2
  6. package/esm2022/lib/data-access/adapters/endpoint-lookup.adapter.mjs +10 -1
  7. package/esm2022/lib/data-access/adapters/http-datasource.adapter.mjs +10 -1
  8. package/esm2022/lib/data-access/adapters/http-lookup.adapter.mjs +10 -1
  9. package/esm2022/lib/features/designer/components/design-canvas/design-canvas.component.mjs +6 -4
  10. package/esm2022/lib/features/designer/components/dialogs/report-saved-dialog.component.mjs +11 -10
  11. package/esm2022/lib/features/designer/components/property-editor/parameter-binding-editor.component.mjs +39 -6
  12. package/esm2022/lib/features/designer/components/property-editor/property-editor-grid.component.mjs +3 -3
  13. package/esm2022/lib/features/designer/components/property-editor/property-editor.component.mjs +5 -3
  14. package/esm2022/lib/features/designer/designer.component.mjs +11 -6
  15. package/esm2022/lib/features/runtime/dynamic-renderer.component.mjs +2 -2
  16. package/esm2022/lib/features/runtime/report-execution.service.mjs +35 -4
  17. package/esm2022/lib/features/runtime/report-grid-bridge.component.mjs +15 -20
  18. package/esm2022/lib/primitives/components/dropdown/rpt-dropdown.component.mjs +21 -10
  19. package/esm2022/lib/primitives/components/radio/rpt-radio.component.mjs +15 -5
  20. package/esm2022/lib/primitives/components/searchable-select/rpt-searchable-select.component.mjs +6 -6
  21. package/fesm2022/bways-report.mjs +324 -88
  22. package/fesm2022/bways-report.mjs.map +1 -1
  23. package/lib/core/models/report-grid.model.d.ts +1 -1
  24. package/lib/core/services/report-overlay.service.d.ts +14 -0
  25. package/lib/core/utils/report-parameter.util.d.ts +13 -1
  26. package/lib/features/designer/components/property-editor/parameter-binding-editor.component.d.ts +5 -0
  27. package/lib/features/designer/designer.component.d.ts +1 -0
  28. package/lib/features/runtime/report-grid-bridge.component.d.ts +2 -2
  29. package/lib/primitives/components/dropdown/rpt-dropdown.component.d.ts +1 -2
  30. package/lib/primitives/components/searchable-select/rpt-searchable-select.component.d.ts +0 -1
  31. package/package.json +8 -5
  32. package/styles/theme.css +184 -0
  33. 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 * as i1$1 from '@angular/common';
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';
@@ -1520,6 +1521,40 @@ function extractLookupOptionItem(row, valueField, displayField) {
1520
1521
  }
1521
1522
 
1522
1523
  const TOKEN_RE = /^\{\{\s*(.+?)\s*\}\}$/;
1524
+ /** Supported wire formats for date-bound procedure/API parameters. */
1525
+ const PARAMETER_DATE_FORMATS = ['yyyy-MM-dd', 'yyyyMMdd', 'dd/MM/yyyy', 'MM/dd/yyyy', 'dd-MM-yyyy'];
1526
+ /** The optional date pipe is persisted in existing string mappings. */
1527
+ function parseParameterToken(raw) {
1528
+ const match = TOKEN_RE.exec(raw);
1529
+ if (!match)
1530
+ return null;
1531
+ const parts = match[1].split(/\s*\|\s*date\s*:\s*/);
1532
+ if (parts.length === 1)
1533
+ return { field: parts[0].trim() };
1534
+ const format = parts[1];
1535
+ return { field: parts[0].trim(), dateFormat: parts.length === 2 ? format : parts.slice(1).join(' | date:') };
1536
+ }
1537
+ /** Format calendar parts directly, avoiding UTC/time-zone shifts and locale parsing. */
1538
+ function formatParameterDate(value, format) {
1539
+ if (!PARAMETER_DATE_FORMATS.includes(format))
1540
+ throw new Error('Unsupported parameter date format. Choose a date format in the Field binding.');
1541
+ if (value === '' || value === null || value === undefined)
1542
+ return '';
1543
+ const raw = value instanceof Date
1544
+ ? value.getFullYear() + '-' + String(value.getMonth() + 1).padStart(2, '0') + '-' + String(value.getDate()).padStart(2, '0')
1545
+ : String(value);
1546
+ const match = /^(\d{4})-?(\d{2})-?(\d{2})(?:T.*)?$/.exec(raw);
1547
+ if (!match)
1548
+ throw new Error('Invalid date. Select a valid date in the linked field.');
1549
+ const [, year, month, day] = match;
1550
+ const probe = new Date(0);
1551
+ probe.setFullYear(+year, +month - 1, +day);
1552
+ probe.setHours(0, 0, 0, 0);
1553
+ if (probe.getFullYear() !== +year || probe.getMonth() + 1 !== +month || probe.getDate() !== +day) {
1554
+ throw new Error('Invalid date. Select a valid date in the linked field.');
1555
+ }
1556
+ return format.replace('yyyy', year).replace('MM', month).replace('dd', day);
1557
+ }
1523
1558
  const pad$1 = (n) => String(n).padStart(2, '0');
1524
1559
  const formatYMD$1 = (d) => `${d.getFullYear()}-${pad$1(d.getMonth() + 1)}-${pad$1(d.getDate())}`;
1525
1560
  /** Resolves `{{context.xxx}}` presets such as today / startOfMonth. */
@@ -1722,31 +1757,50 @@ function parseSqlExecStatement(sql) {
1722
1757
  function resolveParameterValue(raw, session, formValues) {
1723
1758
  if (typeof raw !== 'string')
1724
1759
  return raw;
1725
- const m = TOKEN_RE.exec(raw);
1726
- if (!m)
1760
+ const token = parseParameterToken(raw);
1761
+ if (!token)
1727
1762
  return sanitizeSqlParamLiteral(raw);
1728
- const inner = m[1];
1763
+ const inner = token.field;
1764
+ const format = (value) => token.dateFormat ? formatParameterDate(value, token.dateFormat) : value;
1729
1765
  if (inner.startsWith('session.')) {
1730
1766
  const sessionKey = inner.slice(8);
1731
1767
  const val = getFieldCaseInsensitive(session, sessionKey);
1732
- return val !== undefined && val !== null ? val : '';
1768
+ return format(val !== undefined && val !== null ? val : '');
1733
1769
  }
1734
1770
  if (inner.startsWith('context.')) {
1735
- return resolveContextPreset(inner.slice(8));
1771
+ return format(resolveContextPreset(inner.slice(8)));
1736
1772
  }
1737
1773
  const v = resolveCanvasFieldValue(formValues, inner);
1738
- return v !== undefined && v !== null ? v : '';
1774
+ return format(v !== undefined && v !== null ? v : '');
1775
+ }
1776
+ /** Canonical SQL argument names. Equal aliases collapse; conflicting values fail explicitly. */
1777
+ function normalizeParameterNames(parameters) {
1778
+ const out = {};
1779
+ const names = new Map();
1780
+ for (const [rawKey, value] of Object.entries(parameters)) {
1781
+ const key = rawKey.replace(/^@+/, '');
1782
+ if (!key)
1783
+ throw new Error('A parameter name cannot be empty.');
1784
+ const existing = names.get(key.toLowerCase());
1785
+ if (existing !== undefined) {
1786
+ if (!Object.is(out[existing], value) && JSON.stringify(out[existing]) !== JSON.stringify(value)) {
1787
+ throw new Error('Conflicting values for parameter "' + existing + '". Keep one mapping for this parameter.');
1788
+ }
1789
+ continue;
1790
+ }
1791
+ names.set(key.toLowerCase(), key);
1792
+ Object.defineProperty(out, key, { value, enumerable: true, configurable: true, writable: true });
1793
+ }
1794
+ return out;
1739
1795
  }
1740
- /** Resolves every mapping into concrete values (de-duplicating `@name` / `name`). */
1796
+ /** Resolves mappings into one concrete value per parameter, without @ aliases. */
1741
1797
  function resolveParameterMappings(mappings, session, formValues) {
1742
1798
  const out = {};
1743
1799
  for (const [key, raw] of Object.entries(mappings ?? {})) {
1744
1800
  const val = resolveParameterValue(raw, session, formValues);
1745
- const cleanKey = key.replace(/^@/, '');
1746
- out[cleanKey] = val;
1747
- out[`@${cleanKey}`] = val;
1801
+ Object.defineProperty(out, key, { value: val, enumerable: true });
1748
1802
  }
1749
- return out;
1803
+ return normalizeParameterNames(out);
1750
1804
  }
1751
1805
  /**
1752
1806
  * Returns canvas field keys referenced by mappings that currently have no value.
@@ -1757,10 +1811,10 @@ function findMissingCanvasFields(mappings, formValues) {
1757
1811
  for (const raw of Object.values(mappings ?? {})) {
1758
1812
  if (typeof raw !== 'string')
1759
1813
  continue;
1760
- const m = TOKEN_RE.exec(raw);
1761
- if (!m)
1814
+ const token = parseParameterToken(raw);
1815
+ if (!token)
1762
1816
  continue;
1763
- const inner = m[1];
1817
+ const inner = token.field;
1764
1818
  if (inner.startsWith('session.') || inner.startsWith('context.'))
1765
1819
  continue;
1766
1820
  const v = resolveCanvasFieldValue(formValues, inner);
@@ -2256,6 +2310,14 @@ class HttpReportLookupAdapter extends ReportLookupAdapter {
2256
2310
  cache = new Map();
2257
2311
  inFlight = new Map();
2258
2312
  fetchOptions(request) {
2313
+ if (request.sourceType === 'procedure') {
2314
+ try {
2315
+ request = { ...request, parameters: normalizeParameterNames(request.parameters ?? {}) };
2316
+ }
2317
+ catch (error) {
2318
+ return throwError(() => error);
2319
+ }
2320
+ }
2259
2321
  const cacheKey = this.generateCacheKey(request);
2260
2322
  const cached = this.cache.get(cacheKey);
2261
2323
  const now = Date.now();
@@ -2343,6 +2405,14 @@ class HttpReportDataSourceAdapter extends ReportDataSourceAdapter {
2343
2405
  config = inject(REPORTING_CONFIG, { optional: true });
2344
2406
  sessionService = inject(ReportSessionContextService, { optional: true });
2345
2407
  execute(dataSource, parameters) {
2408
+ if (dataSource.method === 'STORED_PROCEDURE' || dataSource.type === 'stored_procedure' || dataSource.type === 'procedure' || !dataSource.type) {
2409
+ try {
2410
+ parameters = normalizeParameterNames(parameters);
2411
+ }
2412
+ catch (error) {
2413
+ return throwError(() => error);
2414
+ }
2415
+ }
2346
2416
  const baseUrl = this.config?.apiBaseUrl;
2347
2417
  const session = this.sessionService?.sessionContext() ?? {};
2348
2418
  if (baseUrl && this.http) {
@@ -3173,6 +3243,14 @@ class EndpointDataSourceAdapter extends ReportDataSourceAdapter {
3173
3243
  headers = headers.set('X-Branch-Id', String(session['branchId']));
3174
3244
  }
3175
3245
  const kind = this.resolveKind(dataSource);
3246
+ if (kind === 'procedure') {
3247
+ try {
3248
+ parameters = normalizeParameterNames(parameters);
3249
+ }
3250
+ catch (error) {
3251
+ return throwError(() => error);
3252
+ }
3253
+ }
3176
3254
  const payload = {
3177
3255
  kind,
3178
3256
  name: dataSource.endpoint || dataSource.name,
@@ -3227,13 +3305,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
3227
3305
  type: Injectable
3228
3306
  }] });
3229
3307
 
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
3308
  class EndpointLookupAdapter extends ReportLookupAdapter {
3238
3309
  http = inject(HttpClient);
3239
3310
  endpoints = inject(BWAYS_REPORT_ENDPOINTS);
@@ -3242,6 +3313,14 @@ class EndpointLookupAdapter extends ReportLookupAdapter {
3242
3313
  cache = new Map();
3243
3314
  inFlight = new Map();
3244
3315
  fetchOptions(request) {
3316
+ if (request.sourceType === 'procedure') {
3317
+ try {
3318
+ request = { ...request, parameters: normalizeParameterNames(request.parameters ?? {}) };
3319
+ }
3320
+ catch (error) {
3321
+ return throwError(() => error);
3322
+ }
3323
+ }
3245
3324
  const cacheKey = this.generateCacheKey(request);
3246
3325
  const cached = this.cache.get(cacheKey);
3247
3326
  const now = Date.now();
@@ -3558,14 +3637,87 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
3558
3637
  `, 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
3638
  }] });
3560
3639
 
3640
+ /** CDK structural styles, embedded so standalone components need no host CSS import.
3641
+ * Compatible with CDK's overlay-prebuilt.css; no theme or !important overrides.
3642
+ * CDK structural CSS is MIT licensed; see third-party-notices/angular-cdk-LICENSE.txt.
3643
+ */
3644
+ 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}";
3645
+ // Library modal appearance must also work without the optional theme asset.
3646
+ const DIALOG_STYLES = '.rpt-dialog-backdrop{background-color:rgba(15,23,42,.48);backdrop-filter:blur(3px)}';
3647
+ const STYLE_ID = 'rpt-overlay-base-styles';
3648
+ class ReportOverlayService {
3649
+ document = inject(DOCUMENT);
3650
+ nonce = inject(CSP_NONCE, { optional: true });
3651
+ overlay = inject(Overlay);
3652
+ scrollDispatcher = inject(ScrollDispatcher);
3653
+ zone = inject(NgZone);
3654
+ ensureStyles() {
3655
+ if (this.document.getElementById(STYLE_ID))
3656
+ return;
3657
+ const style = this.document.createElement('style');
3658
+ style.id = STYLE_ID;
3659
+ if (this.nonce)
3660
+ style.setAttribute('nonce', this.nonce);
3661
+ style.textContent = OVERLAY_STYLES + DIALOG_STYLES;
3662
+ (this.document.head ?? this.document.documentElement).appendChild(style);
3663
+ }
3664
+ /** Retain CDK's scroll strategy; cover native, unregistered host ancestors too. */
3665
+ scrollStrategy(origin) {
3666
+ this.ensureStyles();
3667
+ const delegate = this.overlay.scrollStrategies.reposition({ scrollThrottle: 16 });
3668
+ const view = this.document.defaultView;
3669
+ let overlayRef;
3670
+ let frame;
3671
+ let enabled = false;
3672
+ const onScroll = (event) => {
3673
+ const target = event.target;
3674
+ if (!target || target.nodeType !== 1 || !target.contains(origin()))
3675
+ return;
3676
+ // Registered ancestors already notify the delegate. Menu-list scrolling
3677
+ // is excluded because the menu does not contain its trigger.
3678
+ if (this.scrollDispatcher.getAncestorScrollContainers(origin()).some(container => container.getElementRef().nativeElement === target))
3679
+ return;
3680
+ if (frame === undefined && view)
3681
+ frame = view.requestAnimationFrame(() => {
3682
+ frame = undefined;
3683
+ if (enabled && overlayRef?.hasAttached())
3684
+ overlayRef.updatePosition();
3685
+ });
3686
+ };
3687
+ return {
3688
+ attach: ref => { overlayRef = ref; delegate.attach(ref); },
3689
+ enable: () => {
3690
+ if (enabled)
3691
+ return;
3692
+ enabled = true;
3693
+ delegate.enable();
3694
+ this.zone.runOutsideAngular(() => this.document.addEventListener('scroll', onScroll, true));
3695
+ },
3696
+ disable: () => {
3697
+ enabled = false;
3698
+ delegate.disable();
3699
+ this.document.removeEventListener('scroll', onScroll, true);
3700
+ if (frame !== undefined && view)
3701
+ view.cancelAnimationFrame(frame);
3702
+ frame = undefined;
3703
+ },
3704
+ };
3705
+ }
3706
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ReportOverlayService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
3707
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ReportOverlayService, providedIn: 'root' });
3708
+ }
3709
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ReportOverlayService, decorators: [{
3710
+ type: Injectable,
3711
+ args: [{ providedIn: 'root' }]
3712
+ }] });
3713
+
3561
3714
  class RptDropdownComponent {
3562
3715
  masterDataProvider = inject(MasterDataProvider, { optional: true });
3563
3716
  lookupAdapter = inject(ReportLookupAdapter, { optional: true });
3564
3717
  sessionService = inject(ReportSessionContextService, { optional: true });
3565
3718
  elementRef = inject(ElementRef);
3566
3719
  reportingConfig = inject(REPORTING_CONFIG, { optional: true });
3567
- overlay = inject(Overlay);
3568
- scrollStrategy = this.overlay.scrollStrategies.reposition();
3720
+ scrollStrategy = inject(ReportOverlayService).scrollStrategy(() => this.elementRef.nativeElement);
3569
3721
  triggerWidth = signal('auto');
3570
3722
  overlayPositions = [
3571
3723
  {
@@ -3985,10 +4137,21 @@ class RptDropdownComponent {
3985
4137
  return;
3986
4138
  }
3987
4139
  // Stored procedures only receive explicit mappings so SQL Server doesn't reject unknown arguments.
3988
- const resolvedParams = {
3989
- ...(ds.sourceType === 'table' ? session : {}),
3990
- ...resolveParameterMappings(ds.parameterMappings, session, formValues),
3991
- };
4140
+ let resolvedParams;
4141
+ try {
4142
+ resolvedParams = {
4143
+ ...(ds.sourceType === 'table' ? session : {}),
4144
+ ...resolveParameterMappings(ds.parameterMappings, session, formValues),
4145
+ };
4146
+ }
4147
+ catch (error) {
4148
+ this.lookupSub?.unsubscribe();
4149
+ this.isLoading.set(false);
4150
+ this.dynamicOptions.set([]);
4151
+ this.loadError.set(error instanceof Error ? error.message : 'Invalid parameter value.');
4152
+ this.lastRequestKey = '';
4153
+ return;
4154
+ }
3992
4155
  const request = {
3993
4156
  sourceType: ds.sourceType ?? (isApi ? 'api' : 'table'),
3994
4157
  entityName: ds.entityName ?? (isApi ? ds.apiEndpoint : ''),
@@ -4070,14 +4233,14 @@ class RptDropdownComponent {
4070
4233
  }
4071
4234
  }
4072
4235
  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 });
4236
+ 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 [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: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
4237
  }
4075
4238
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RptDropdownComponent, decorators: [{
4076
4239
  type: Component,
4077
4240
  args: [{ selector: 'rpt-dropdown', standalone: true, imports: [ReactiveFormsModule, OverlayModule], host: {
4078
4241
  '[class.is-open]': 'isOpen()',
4079
4242
  '[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"] }]
4243
+ }, 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 [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: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
4244
  }], ctorParameters: () => [], propDecorators: { onDocumentClick: [{
4082
4245
  type: HostListener,
4083
4246
  args: ['document:click', ['$event']]
@@ -5961,9 +6124,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
5961
6124
 
5962
6125
  class RptSearchableSelectComponent {
5963
6126
  elementRef;
5964
- overlay = inject(Overlay);
5965
6127
  panelId = 'rpt-select-panel-' + Math.random().toString(36).substring(2, 9);
5966
- scrollStrategy = this.overlay.scrollStrategies.reposition();
6128
+ scrollStrategy = inject(ReportOverlayService).scrollStrategy(() => this.elementRef.nativeElement);
5967
6129
  triggerWidth = signal('auto');
5968
6130
  overlayPositions = [
5969
6131
  {
@@ -6313,7 +6475,7 @@ class RptSearchableSelectComponent {
6313
6475
  useExisting: forwardRef(() => RptSearchableSelectComponent),
6314
6476
  multi: true,
6315
6477
  },
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 });
6478
+ ], 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
6479
  }
6318
6480
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: RptSearchableSelectComponent, decorators: [{
6319
6481
  type: Component,
@@ -6330,7 +6492,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
6330
6492
  '[class.size-sm]': 'size() === "sm"',
6331
6493
  '[class.size-md]': 'size() === "md"',
6332
6494
  '[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"] }]
6495
+ }, 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
6496
  }], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { onDocumentClick: [{
6335
6497
  type: HostListener,
6336
6498
  args: ['document:click', ['$event']]
@@ -6551,10 +6713,20 @@ class RptRadioComponent {
6551
6713
  return;
6552
6714
  }
6553
6715
  const session = this.sessionService?.sessionContext() ?? {};
6554
- const resolvedParams = {
6555
- ...(ds.sourceType === 'table' ? session : {}),
6556
- ...resolveParameterMappings(ds.parameterMappings, session, formValues),
6557
- };
6716
+ let resolvedParams;
6717
+ try {
6718
+ resolvedParams = {
6719
+ ...(ds.sourceType === 'table' ? session : {}),
6720
+ ...resolveParameterMappings(ds.parameterMappings, session, formValues),
6721
+ };
6722
+ }
6723
+ catch (error) {
6724
+ this.lookupSub?.unsubscribe();
6725
+ this.isLoading.set(false);
6726
+ this.dynamicOptions.set([]);
6727
+ this.loadError.set(error instanceof Error ? error.message : 'Invalid parameter value.');
6728
+ return;
6729
+ }
6558
6730
  const request = {
6559
6731
  sourceType: (ds.sourceType ?? (isApi ? 'api' : 'table')),
6560
6732
  entityName: ds.entityName ?? (isApi ? ds.apiEndpoint : ''),
@@ -8141,20 +8313,14 @@ class ReportGridBridgeComponent {
8141
8313
  });
8142
8314
  isCheckboxSelectionEnabled = computed(() => {
8143
8315
  const opts = this.gridDefinition()?.options;
8144
- if (opts?.checkboxSelection !== undefined) {
8145
- return opts.checkboxSelection;
8146
- }
8147
- if (opts?.selectable) {
8148
- return true;
8149
- }
8150
- return this.visibleColumns().some((c) => c.checkboxSelection);
8316
+ return this.isSelectionEnabled() && (opts?.checkboxSelection ?? true);
8151
8317
  });
8152
8318
  effectiveRowSelection = computed(() => {
8153
8319
  const opts = this.gridDefinition()?.options;
8154
8320
  return opts?.selectionMode || opts?.rowSelection || 'multiple';
8155
8321
  });
8156
8322
  isHeaderCheckboxSelectionEnabled = computed(() => {
8157
- if (this.effectiveRowSelection() === 'single') {
8323
+ if (!this.isCheckboxSelectionEnabled() || this.effectiveRowSelection() === 'single') {
8158
8324
  return false;
8159
8325
  }
8160
8326
  const opts = this.gridDefinition()?.options;
@@ -8165,7 +8331,7 @@ class ReportGridBridgeComponent {
8165
8331
  });
8166
8332
  isSelectionEnabled = computed(() => {
8167
8333
  const opts = this.gridDefinition()?.options;
8168
- return !!(opts?.selectable || opts?.checkboxSelection || this.visibleColumns().some((c) => c.checkboxSelection));
8334
+ return opts?.selectable ?? true;
8169
8335
  });
8170
8336
  sortColumn = signal(null);
8171
8337
  sortDirection = signal('asc');
@@ -8204,7 +8370,7 @@ class ReportGridBridgeComponent {
8204
8370
  }
8205
8371
  let colCheckboxSelection = col.checkboxSelection;
8206
8372
  let colHeaderCheckboxSelection = col.headerCheckboxSelection;
8207
- if (!hasExplicitColCheckbox && isGridCheckbox && idx === 0) {
8373
+ if (!hasExplicitColCheckbox && isGridCheckbox && idx === 0 && col.checkboxSelection !== false) {
8208
8374
  colCheckboxSelection = true;
8209
8375
  colHeaderCheckboxSelection = isGridHeaderCheckbox;
8210
8376
  }
@@ -8216,8 +8382,8 @@ class ReportGridBridgeComponent {
8216
8382
  hide: col.visible === false,
8217
8383
  width: widthNum,
8218
8384
  aggFunc: col.aggregate && col.aggregate !== 'none' ? col.aggregate : undefined,
8219
- checkboxSelection: colCheckboxSelection,
8220
- headerCheckboxSelection: colHeaderCheckboxSelection,
8385
+ checkboxSelection: isGridCheckbox ? colCheckboxSelection : false,
8386
+ headerCheckboxSelection: isGridHeaderCheckbox ? colHeaderCheckboxSelection : false,
8221
8387
  };
8222
8388
  });
8223
8389
  });
@@ -8243,6 +8409,7 @@ class ReportGridBridgeComponent {
8243
8409
  statusBar: opts.statusBar !== undefined ? opts.statusBar : true,
8244
8410
  globalSearch: opts.globalSearch !== undefined ? opts.globalSearch : true,
8245
8411
  ...opts,
8412
+ selectable: this.isSelectionEnabled(),
8246
8413
  rowSelection: rowSel,
8247
8414
  selectionMode: rowSel,
8248
8415
  checkboxSelection: hasCheckbox,
@@ -8318,7 +8485,7 @@ class ReportGridBridgeComponent {
8318
8485
  return this.gridDefinition().options?.pageSize ?? 10;
8319
8486
  });
8320
8487
  colSpan = computed(() => {
8321
- return this.visibleColumns().length + (this.showRowNumbers() ? 1 : 0) + (this.isSelectionEnabled() ? 1 : 0);
8488
+ return this.visibleColumns().length + (this.showRowNumbers() ? 1 : 0) + (this.isCheckboxSelectionEnabled() ? 1 : 0);
8322
8489
  });
8323
8490
  hasAggregates = computed(() => {
8324
8491
  return (this.gridDefinition().options?.showFooter &&
@@ -8543,7 +8710,7 @@ class ReportGridBridgeComponent {
8543
8710
  @if (showRowNumbers()) {
8544
8711
  <th class="rpt-th rpt-th-num">#</th>
8545
8712
  }
8546
- @if (isSelectionEnabled()) {
8713
+ @if (isCheckboxSelectionEnabled()) {
8547
8714
  <th class="rpt-th rpt-th-cb">
8548
8715
  @if (effectiveRowSelection() !== 'single' && isHeaderCheckboxSelectionEnabled()) {
8549
8716
  <input
@@ -8595,7 +8762,7 @@ class ReportGridBridgeComponent {
8595
8762
  @if (showRowNumbers()) {
8596
8763
  <td class="rpt-td rpt-td-num">{{ (currentPage() - 1) * pageSize() + $index + 1 }}</td>
8597
8764
  }
8598
- @if (isSelectionEnabled()) {
8765
+ @if (isCheckboxSelectionEnabled()) {
8599
8766
  <td class="rpt-td rpt-td-cb" (click)="$event.stopPropagation()">
8600
8767
  <input
8601
8768
  [type]="effectiveRowSelection() === 'single' ? 'radio' : 'checkbox'"
@@ -8630,7 +8797,7 @@ class ReportGridBridgeComponent {
8630
8797
  @if (showRowNumbers()) {
8631
8798
  <td class="rpt-td-num"></td>
8632
8799
  }
8633
- @if (isSelectionEnabled()) {
8800
+ @if (isCheckboxSelectionEnabled()) {
8634
8801
  <td class="rpt-td-cb"></td>
8635
8802
  }
8636
8803
  @for (col of visibleColumns(); track col.field) {
@@ -8710,7 +8877,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
8710
8877
  @if (showRowNumbers()) {
8711
8878
  <th class="rpt-th rpt-th-num">#</th>
8712
8879
  }
8713
- @if (isSelectionEnabled()) {
8880
+ @if (isCheckboxSelectionEnabled()) {
8714
8881
  <th class="rpt-th rpt-th-cb">
8715
8882
  @if (effectiveRowSelection() !== 'single' && isHeaderCheckboxSelectionEnabled()) {
8716
8883
  <input
@@ -8762,7 +8929,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
8762
8929
  @if (showRowNumbers()) {
8763
8930
  <td class="rpt-td rpt-td-num">{{ (currentPage() - 1) * pageSize() + $index + 1 }}</td>
8764
8931
  }
8765
- @if (isSelectionEnabled()) {
8932
+ @if (isCheckboxSelectionEnabled()) {
8766
8933
  <td class="rpt-td rpt-td-cb" (click)="$event.stopPropagation()">
8767
8934
  <input
8768
8935
  [type]="effectiveRowSelection() === 'single' ? 'radio' : 'checkbox'"
@@ -8797,7 +8964,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
8797
8964
  @if (showRowNumbers()) {
8798
8965
  <td class="rpt-td-num"></td>
8799
8966
  }
8800
- @if (isSelectionEnabled()) {
8967
+ @if (isCheckboxSelectionEnabled()) {
8801
8968
  <td class="rpt-td-cb"></td>
8802
8969
  }
8803
8970
  @for (col of visibleColumns(); track col.field) {
@@ -9050,7 +9217,7 @@ class DynamicRendererComponent {
9050
9217
  inputs['config'] = comp;
9051
9218
  }
9052
9219
  if (comp.key && comp.type !== 'button' && comp.type !== 'kpi-card' && comp.type !== 'chart') {
9053
- inputs['control'] = this.form().get(comp.key);
9220
+ inputs['control'] = this.form().controls[comp.key];
9054
9221
  }
9055
9222
  if (comp.type === 'dropdown' || comp.type === 'radio') {
9056
9223
  inputs['parentForm'] = this.form();
@@ -9488,7 +9655,13 @@ class ReportExecutionService {
9488
9655
  return throwError(() => new Error('No data source configured. Select the Run Report button and choose a stored procedure in its Report Data section.'));
9489
9656
  }
9490
9657
  const session = this.session?.getCurrentSessionContext() ?? {};
9491
- const resolved = resolveParameterMappings(ds.parameterMappings, session, formValues);
9658
+ let resolved;
9659
+ try {
9660
+ resolved = resolveParameterMappings(ds.parameterMappings, session, formValues);
9661
+ }
9662
+ catch (error) {
9663
+ return throwError(() => error);
9664
+ }
9492
9665
  // Expand compound objects (e.g. dateRange: { from, to }) into top-level parameters
9493
9666
  const flattenedFormValues = { ...formValues };
9494
9667
  for (const [k, v] of Object.entries(formValues)) {
@@ -9506,7 +9679,6 @@ class ReportExecutionService {
9506
9679
  }
9507
9680
  }
9508
9681
  }
9509
- const params = { ...flattenedFormValues, ...resolved };
9510
9682
  // Delegate to ReportApiExecutionService if data source is an API endpoint
9511
9683
  const isApiSource = ds.type === 'api' ||
9512
9684
  ds.endpoint.startsWith('http://') ||
@@ -9525,6 +9697,32 @@ class ReportExecutionService {
9525
9697
  if (!this.adapter) {
9526
9698
  return throwError(() => new Error('No ReportDataSourceAdapter provided to execute the data source.'));
9527
9699
  }
9700
+ const isProcedure = ds.method === 'STORED_PROCEDURE' || ds.type === 'stored_procedure' || ds.type === 'procedure' || !ds.type;
9701
+ let params = { ...flattenedFormValues, ...resolved };
9702
+ if (isProcedure) {
9703
+ // Form controls are expression inputs, never an implicit SQL argument list.
9704
+ const declared = {};
9705
+ const mappedNames = new Set(Object.keys(resolved).map(key => key.toLowerCase()));
9706
+ const keys = ds.parameterKeys ?? (ds.parameterMappings === undefined ? [
9707
+ ...def.parameters.map(parameter => parameter.key),
9708
+ ...def.components.flatMap(component => component.binding?.parameterKey ? [component.binding.parameterKey] : []),
9709
+ ] : []);
9710
+ for (const key of keys) {
9711
+ if (mappedNames.has(key.replace(/^@+/, '').toLowerCase()))
9712
+ continue;
9713
+ const control = def.components.find(component => component.binding?.parameterKey === key);
9714
+ const parameter = def.parameters.find(item => item.key === key);
9715
+ const value = resolveCanvasFieldValue(formValues, control?.key || key) ?? resolveCanvasFieldValue(formValues, key.replace(/^@+/, '')) ?? parameter?.defaultValue;
9716
+ if (value !== undefined)
9717
+ Object.defineProperty(declared, key, { value, enumerable: true, configurable: true });
9718
+ }
9719
+ try {
9720
+ params = normalizeParameterNames({ ...declared, ...resolved });
9721
+ }
9722
+ catch (error) {
9723
+ return throwError(() => error);
9724
+ }
9725
+ }
9528
9726
  return this.guard(this.adapter.execute({ ...ds, name: ds.name || ds.endpoint, type: ds.type ?? 'stored_procedure' }, params));
9529
9727
  }
9530
9728
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ReportExecutionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
@@ -11535,9 +11733,9 @@ class ReportSavedDialogComponent {
11535
11733
  </div>
11536
11734
 
11537
11735
  <!-- Modal Body (Two-Column Split) -->
11538
- <div class="rpt-dialog-body" [attr.inert]="updateConfirmation() || isPreparingSave() ? '' : null">
11736
+ <div class="rpt-dialog-body" cdkScrollable [attr.inert]="updateConfirmation() || isPreparingSave() ? '' : null">
11539
11737
  <!-- Left Column: Settings & Publishing -->
11540
- <div class="rpt-settings-column">
11738
+ <div class="rpt-settings-column" cdkScrollable>
11541
11739
  <div class="rpt-section-header">Page Navigation & Settings</div>
11542
11740
 
11543
11741
  <div class="rpt-form-group">
@@ -11720,7 +11918,7 @@ class ReportSavedDialogComponent {
11720
11918
  <span class="rpt-preview-hint">Interactive preview matching runtime page</span>
11721
11919
  </div>
11722
11920
 
11723
- <div class="rpt-preview-runtime-wrapper">
11921
+ <div class="rpt-preview-runtime-wrapper" cdkScrollable>
11724
11922
  <div class="rpt-preview-card-frame">
11725
11923
  <rpt-runtime [report]="currentPreviewDef()" />
11726
11924
  </div>
@@ -11807,11 +12005,11 @@ class ReportSavedDialogComponent {
11807
12005
  </div>
11808
12006
  }
11809
12007
  </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 });
12008
+ `, 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
12009
  }
11812
12010
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: ReportSavedDialogComponent, decorators: [{
11813
12011
  type: Component,
11814
- args: [{ selector: 'rpt-report-saved-dialog', standalone: true, imports: [CommonModule, FormsModule, A11yModule, ReportRuntimeComponent, RptIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
12012
+ args: [{ selector: 'rpt-report-saved-dialog', standalone: true, imports: [CommonModule, FormsModule, A11yModule, CdkScrollable, ReportRuntimeComponent, RptIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: `
11815
12013
  <div class="rpt-dialog-shell" [attr.aria-busy]="isSaving() || isCheckingId()">
11816
12014
  <!-- Review before the only persistence request -->
11817
12015
  <div class="rpt-dialog-header" [attr.inert]="updateConfirmation() ? '' : null">
@@ -11836,9 +12034,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
11836
12034
  </div>
11837
12035
 
11838
12036
  <!-- Modal Body (Two-Column Split) -->
11839
- <div class="rpt-dialog-body" [attr.inert]="updateConfirmation() || isPreparingSave() ? '' : null">
12037
+ <div class="rpt-dialog-body" cdkScrollable [attr.inert]="updateConfirmation() || isPreparingSave() ? '' : null">
11840
12038
  <!-- Left Column: Settings & Publishing -->
11841
- <div class="rpt-settings-column">
12039
+ <div class="rpt-settings-column" cdkScrollable>
11842
12040
  <div class="rpt-section-header">Page Navigation & Settings</div>
11843
12041
 
11844
12042
  <div class="rpt-form-group">
@@ -12021,7 +12219,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
12021
12219
  <span class="rpt-preview-hint">Interactive preview matching runtime page</span>
12022
12220
  </div>
12023
12221
 
12024
- <div class="rpt-preview-runtime-wrapper">
12222
+ <div class="rpt-preview-runtime-wrapper" cdkScrollable>
12025
12223
  <div class="rpt-preview-card-frame">
12026
12224
  <rpt-runtime [report]="currentPreviewDef()" />
12027
12225
  </div>
@@ -12108,7 +12306,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
12108
12306
  </div>
12109
12307
  }
12110
12308
  </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"] }]
12309
+ `, 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
12310
  }] });
12113
12311
 
12114
12312
  /** Private state owned by one designer; never mutates application providers. */
@@ -13672,7 +13870,7 @@ class DesignCanvasComponent {
13672
13870
  getCanvasControl(comp) {
13673
13871
  if (!comp.key)
13674
13872
  return null;
13675
- return this.canvasForm().get(comp.key) ?? null;
13873
+ return this.canvasForm().controls[comp.key] ?? null;
13676
13874
  }
13677
13875
  highlightedFieldKeys = signal(new Set());
13678
13876
  layoutJustUpdated = signal(false);
@@ -14099,12 +14297,13 @@ class DesignCanvasComponent {
14099
14297
  this.designerState.addComponent(type);
14100
14298
  }
14101
14299
  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 });
14300
+ 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
14301
  }
14104
14302
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DesignCanvasComponent, decorators: [{
14105
14303
  type: Component,
14106
14304
  args: [{ selector: 'rpt-design-canvas', standalone: true, imports: [
14107
14305
  CommonModule,
14306
+ CdkScrollable,
14108
14307
  DragDropModule,
14109
14308
  ReactiveFormsModule,
14110
14309
  RptIconComponent,
@@ -14112,7 +14311,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
14112
14311
  RptDateRangeComponent,
14113
14312
  RptRadioComponent,
14114
14313
  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"] }]
14314
+ ], 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
14315
  }], ctorParameters: () => [], propDecorators: { onEscape: [{
14117
14316
  type: HostListener,
14118
14317
  args: ['document:keydown.escape']
@@ -14868,8 +15067,19 @@ class ParameterBindingEditorComponent {
14868
15067
  return [];
14869
15068
  });
14870
15069
  compatibleControls = computed(() => {
14871
- return this.allComponents().filter((c) => c.id !== this.currentComponentId());
15070
+ return this.allComponents().filter(c => c.id !== this.currentComponentId() && !!c.key && ['input', 'number', 'date', 'date-range', 'dropdown', 'radio', 'checkbox'].includes(c.type))
15071
+ .flatMap(c => c.type === 'date-range'
15072
+ ? ['from', 'to'].map(part => ({ ...c, key: (c.key || c.id) + '.' + part, label: (c.label || c.key || c.id) + (part === 'from' ? ' ? From' : ' ? To') }))
15073
+ : [c]);
14872
15074
  });
15075
+ dateFormats = PARAMETER_DATE_FORMATS;
15076
+ selectedDateField = computed(() => this.compatibleControls().find(c => (c.key || c.id) === this.selectedControlKey() && (c.type === 'date' || c.type === 'date-range')));
15077
+ selectedDateFormat = computed(() => parseParameterToken(String(this.currentValue()))?.dateFormat ?? 'yyyy-MM-dd');
15078
+ onDateFormatSelected(format) {
15079
+ if (!PARAMETER_DATE_FORMATS.includes(format) || !this.selectedDateField())
15080
+ return;
15081
+ this.valueChange.emit('{{' + this.selectedControlKey() + ' | date:' + format + '}}');
15082
+ }
14873
15083
  hasValue = computed(() => {
14874
15084
  const v = this.currentValue();
14875
15085
  return v !== undefined && v !== null && v !== '';
@@ -14890,7 +15100,7 @@ class ParameterBindingEditorComponent {
14890
15100
  return 'session';
14891
15101
  }
14892
15102
  if (val.startsWith('{{') && val.endsWith('}}')) {
14893
- const inner = val.slice(2, -2).trim();
15103
+ const inner = parseParameterToken(val)?.field;
14894
15104
  const match = this.compatibleControls().find((c) => (c.key || c.id) === inner);
14895
15105
  if (match) {
14896
15106
  return 'control';
@@ -14922,7 +15132,7 @@ class ParameterBindingEditorComponent {
14922
15132
  selectedControlKey = computed(() => {
14923
15133
  const val = String(this.currentValue() ?? '');
14924
15134
  if (val.startsWith('{{') && val.endsWith('}}')) {
14925
- return val.slice(2, -2).trim();
15135
+ return parseParameterToken(val)?.field ?? '';
14926
15136
  }
14927
15137
  return val;
14928
15138
  });
@@ -14985,7 +15195,9 @@ class ParameterBindingEditorComponent {
14985
15195
  }
14986
15196
  onControlSelected(key) {
14987
15197
  if (key) {
14988
- const expr = `{{${key}}}`;
15198
+ const field = this.compatibleControls().find(c => (c.key || c.id) === key);
15199
+ const isDate = field?.type === 'date' || field?.type === 'date-range';
15200
+ const expr = isDate ? '{{' + key + ' | date:yyyy-MM-dd}}' : '{{' + key + '}}';
14989
15201
  this.valueChange.emit(expr);
14990
15202
  this.linkedFieldChange.emit({
14991
15203
  paramName: this.param().name,
@@ -15150,6 +15362,16 @@ class ParameterBindingEditorComponent {
15150
15362
  }
15151
15363
  </select>
15152
15364
  </div>
15365
+ @if (selectedDateField()) {
15366
+ <label class="param-desc" [for]="'date-format-' + currentComponentId() + '-' + param().name">Execution date format</label>
15367
+ <select class="field-select" [id]="'date-format-' + currentComponentId() + '-' + param().name"
15368
+ [ngModel]="selectedDateFormat()" (ngModelChange)="onDateFormatSelected($event)">
15369
+ @for (format of dateFormats; track format) {
15370
+ <option [value]="format">{{ format }}{{ format === 'yyyyMMdd' ? ' (e.g. 20261104)' : '' }}</option>
15371
+ }
15372
+ </select>
15373
+ <div class="param-desc">Applied when executing; the date picker keeps its normal display.</div>
15374
+ }
15153
15375
  @if (!selectedControlKey() && compatibleControls().length === 0) {
15154
15376
  <div class="empty-hint">No input fields on canvas. Add a dropdown, date picker, or input first.</div>
15155
15377
  }
@@ -15397,6 +15619,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
15397
15619
  }
15398
15620
  </select>
15399
15621
  </div>
15622
+ @if (selectedDateField()) {
15623
+ <label class="param-desc" [for]="'date-format-' + currentComponentId() + '-' + param().name">Execution date format</label>
15624
+ <select class="field-select" [id]="'date-format-' + currentComponentId() + '-' + param().name"
15625
+ [ngModel]="selectedDateFormat()" (ngModelChange)="onDateFormatSelected($event)">
15626
+ @for (format of dateFormats; track format) {
15627
+ <option [value]="format">{{ format }}{{ format === 'yyyyMMdd' ? ' (e.g. 20261104)' : '' }}</option>
15628
+ }
15629
+ </select>
15630
+ <div class="param-desc">Applied when executing; the date picker keeps its normal display.</div>
15631
+ }
15400
15632
  @if (!selectedControlKey() && compatibleControls().length === 0) {
15401
15633
  <div class="empty-hint">No input fields on canvas. Add a dropdown, date picker, or input first.</div>
15402
15634
  }
@@ -15856,7 +16088,7 @@ class PropertyEditorGridComponent {
15856
16088
  this.updateRowClickAction.emit({ ...current, parameterMapping: nextMapping });
15857
16089
  }
15858
16090
  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 &#64;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 });
16091
+ 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 &#64;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
16092
  }
15861
16093
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PropertyEditorGridComponent, decorators: [{
15862
16094
  type: Component,
@@ -15866,7 +16098,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
15866
16098
  RptIconComponent,
15867
16099
  ParameterBindingEditorComponent,
15868
16100
  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 &#64;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"] }]
16101
+ ], 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 &#64;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
16102
  }], ctorParameters: () => [] });
15871
16103
 
15872
16104
  class PropertyEditorReportComponent {
@@ -17602,12 +17834,13 @@ class PropertyEditorComponent {
17602
17834
  return comp.validation?.required ? 'Required' : 'Optional';
17603
17835
  }
17604
17836
  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 });
17837
+ 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
17838
  }
17607
17839
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: PropertyEditorComponent, decorators: [{
17608
17840
  type: Component,
17609
17841
  args: [{ selector: 'rpt-property-editor', standalone: true, imports: [
17610
17842
  CommonModule,
17843
+ CdkScrollable,
17611
17844
  RptIconComponent,
17612
17845
  PropertyEditorGeneralComponent,
17613
17846
  PropertyEditorLayoutComponent,
@@ -17616,7 +17849,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
17616
17849
  PropertyEditorReportComponent,
17617
17850
  PropertyEditorDatasourceComponent,
17618
17851
  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"] }]
17852
+ ], 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
17853
  }], ctorParameters: () => [] });
17621
17854
 
17622
17855
  class DesignerComponent {
@@ -17643,6 +17876,7 @@ class DesignerComponent {
17643
17876
  sessionService = inject(ReportSessionContextService, { optional: true });
17644
17877
  executionService = inject(ReportExecutionService, { optional: true });
17645
17878
  dialog = inject(Dialog, { optional: true });
17879
+ overlaySupport = inject(ReportOverlayService);
17646
17880
  router = inject(Router, { optional: true });
17647
17881
  reportingConfig = inject(REPORTING_CONFIG, { optional: true });
17648
17882
  /** Optional reportId to load from persistence adapter */
@@ -17823,12 +18057,13 @@ class DesignerComponent {
17823
18057
  this.showToast('The report review dialog is unavailable.');
17824
18058
  return;
17825
18059
  }
18060
+ this.overlaySupport.ensureStyles();
17826
18061
  const routeBase = this.reportingConfig?.routeBase || 'reports';
17827
18062
  const dialogRef = this.dialog.open(ReportSavedDialogComponent, {
17828
18063
  injector: this.injector,
17829
18064
  data: { report: this.currentDefinition(), routeBase },
17830
- width: 'min(860px, 92vw)', maxWidth: '860px', maxHeight: '84vh',
17831
- panelClass: 'rpt-dialog-panel', disableClose: false,
18065
+ width: 'min(860px, calc(100vw - 32px))', maxWidth: '860px', maxHeight: 'calc(100dvh - 32px)',
18066
+ panelClass: 'rpt-dialog-panel', backdropClass: 'rpt-dialog-backdrop', disableClose: false,
17832
18067
  ariaLabelledBy: 'dialog-title', autoFocus: '#rpt-dlg-id',
17833
18068
  });
17834
18069
  this.saveDialogOpen = true;
@@ -17993,7 +18228,7 @@ class DesignerComponent {
17993
18228
  <rpt-design-canvas />
17994
18229
  }
17995
18230
  @case ('preview') {
17996
- <div class="preview-mode-container">
18231
+ <div class="preview-mode-container" cdkScrollable>
17997
18232
  <div class="preview-banner">
17998
18233
  <rpt-icon name="eye" [size]="15" />
17999
18234
  <span>Live Preview Mode — Running with actual Reactive Forms & Dynamic Grid</span>
@@ -18155,12 +18390,13 @@ class DesignerComponent {
18155
18390
  </div>
18156
18391
  }
18157
18392
  </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 });
18393
+ `, 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: "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
18394
  }
18160
18395
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImport: i0, type: DesignerComponent, decorators: [{
18161
18396
  type: Component,
18162
18397
  args: [{ selector: 'rpt-designer', standalone: true, imports: [
18163
18398
  CommonModule,
18399
+ CdkScrollable,
18164
18400
  FormsModule,
18165
18401
  DragDropModule,
18166
18402
  DialogModule,
@@ -18209,7 +18445,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.14", ngImpo
18209
18445
  <rpt-design-canvas />
18210
18446
  }
18211
18447
  @case ('preview') {
18212
- <div class="preview-mode-container">
18448
+ <div class="preview-mode-container" cdkScrollable>
18213
18449
  <div class="preview-banner">
18214
18450
  <rpt-icon name="eye" [size]="15" />
18215
18451
  <span>Live Preview Mode — Running with actual Reactive Forms & Dynamic Grid</span>
@@ -20305,5 +20541,5 @@ const bwaysReportProviders = provideBwaysReport;
20305
20541
  * Generated bundle index. Do not edit.
20306
20542
  */
20307
20543
 
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 };
20544
+ 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, toFieldKey, validateReportSchema, withHttpAdapters, withLocalStorage, withMockAdapters };
20309
20545
  //# sourceMappingURL=bways-report.mjs.map