@memberjunction/ng-entity-viewer 6.1.4 → 6.2.0-edge.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 (81) hide show
  1. package/dist/lib/aggregate-panel/aggregate-panel.component.d.ts +18 -2
  2. package/dist/lib/aggregate-panel/aggregate-panel.component.d.ts.map +1 -1
  3. package/dist/lib/aggregate-panel/aggregate-panel.component.js +47 -18
  4. package/dist/lib/aggregate-panel/aggregate-panel.component.js.map +1 -1
  5. package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.d.ts +22 -0
  6. package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.d.ts.map +1 -1
  7. package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.js +57 -13
  8. package/dist/lib/aggregate-setup-dialog/aggregate-setup-dialog.component.js.map +1 -1
  9. package/dist/lib/entity-cards/entity-cards.component.d.ts +68 -5
  10. package/dist/lib/entity-cards/entity-cards.component.d.ts.map +1 -1
  11. package/dist/lib/entity-cards/entity-cards.component.js +213 -73
  12. package/dist/lib/entity-cards/entity-cards.component.js.map +1 -1
  13. package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts +156 -14
  14. package/dist/lib/entity-data-grid/entity-data-grid.component.d.ts.map +1 -1
  15. package/dist/lib/entity-data-grid/entity-data-grid.component.js +432 -138
  16. package/dist/lib/entity-data-grid/entity-data-grid.component.js.map +1 -1
  17. package/dist/lib/entity-data-grid/events/grid-events.js +67 -67
  18. package/dist/lib/entity-data-grid/events/grid-events.js.map +1 -1
  19. package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.d.ts +112 -8
  20. package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.d.ts.map +1 -1
  21. package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.js +255 -69
  22. package/dist/lib/entity-record-detail-panel/entity-record-detail-panel.component.js.map +1 -1
  23. package/dist/lib/entity-viewer/entity-viewer.component.d.ts +11 -0
  24. package/dist/lib/entity-viewer/entity-viewer.component.d.ts.map +1 -1
  25. package/dist/lib/entity-viewer/entity-viewer.component.js +45 -8
  26. package/dist/lib/entity-viewer/entity-viewer.component.js.map +1 -1
  27. package/dist/lib/pill/pill.component.d.ts +13 -3
  28. package/dist/lib/pill/pill.component.d.ts.map +1 -1
  29. package/dist/lib/pill/pill.component.js +34 -10
  30. package/dist/lib/pill/pill.component.js.map +1 -1
  31. package/dist/lib/quick-save-dialog/quick-save-dialog.component.d.ts +29 -3
  32. package/dist/lib/quick-save-dialog/quick-save-dialog.component.d.ts.map +1 -1
  33. package/dist/lib/quick-save-dialog/quick-save-dialog.component.js +173 -114
  34. package/dist/lib/quick-save-dialog/quick-save-dialog.component.js.map +1 -1
  35. package/dist/lib/recycle-bin/recycle-bin.component.d.ts +4 -0
  36. package/dist/lib/recycle-bin/recycle-bin.component.d.ts.map +1 -1
  37. package/dist/lib/recycle-bin/recycle-bin.component.js +10 -2
  38. package/dist/lib/recycle-bin/recycle-bin.component.js.map +1 -1
  39. package/dist/lib/utils/aggregate-field.util.d.ts +21 -0
  40. package/dist/lib/utils/aggregate-field.util.d.ts.map +1 -0
  41. package/dist/lib/utils/aggregate-field.util.js +40 -0
  42. package/dist/lib/utils/aggregate-field.util.js.map +1 -0
  43. package/dist/lib/utils/highlight.util.d.ts +6 -0
  44. package/dist/lib/utils/highlight.util.d.ts.map +1 -1
  45. package/dist/lib/utils/highlight.util.js +23 -11
  46. package/dist/lib/utils/highlight.util.js.map +1 -1
  47. package/dist/lib/utils/record.util.d.ts +10 -0
  48. package/dist/lib/utils/record.util.d.ts.map +1 -1
  49. package/dist/lib/utils/record.util.js +20 -4
  50. package/dist/lib/utils/record.util.js.map +1 -1
  51. package/dist/lib/view-config-panel/view-config-panel.component.d.ts +22 -2
  52. package/dist/lib/view-config-panel/view-config-panel.component.d.ts.map +1 -1
  53. package/dist/lib/view-config-panel/view-config-panel.component.js +54 -14
  54. package/dist/lib/view-config-panel/view-config-panel.component.js.map +1 -1
  55. package/dist/lib/view-selector/view-selector.component.js +2 -2
  56. package/dist/lib/view-types/renderers/cards-view-renderer.component.d.ts +15 -3
  57. package/dist/lib/view-types/renderers/cards-view-renderer.component.d.ts.map +1 -1
  58. package/dist/lib/view-types/renderers/cards-view-renderer.component.js +33 -5
  59. package/dist/lib/view-types/renderers/cards-view-renderer.component.js.map +1 -1
  60. package/dist/lib/view-types/renderers/grid-view-renderer.component.d.ts +160 -5
  61. package/dist/lib/view-types/renderers/grid-view-renderer.component.d.ts.map +1 -1
  62. package/dist/lib/view-types/renderers/grid-view-renderer.component.js +576 -36
  63. package/dist/lib/view-types/renderers/grid-view-renderer.component.js.map +1 -1
  64. package/dist/lib/view-types/renderers/map-view-renderer.component.d.ts +16 -2
  65. package/dist/lib/view-types/renderers/map-view-renderer.component.d.ts.map +1 -1
  66. package/dist/lib/view-types/renderers/map-view-renderer.component.js +37 -9
  67. package/dist/lib/view-types/renderers/map-view-renderer.component.js.map +1 -1
  68. package/dist/lib/view-types/view-type.contracts.js.map +1 -1
  69. package/dist/lib/view-workspace/view-workspace.component.d.ts +235 -24
  70. package/dist/lib/view-workspace/view-workspace.component.d.ts.map +1 -1
  71. package/dist/lib/view-workspace/view-workspace.component.js +600 -174
  72. package/dist/lib/view-workspace/view-workspace.component.js.map +1 -1
  73. package/dist/module.d.ts +5 -4
  74. package/dist/module.d.ts.map +1 -1
  75. package/dist/module.js +8 -1
  76. package/dist/module.js.map +1 -1
  77. package/dist/public-api.d.ts +1 -0
  78. package/dist/public-api.d.ts.map +1 -1
  79. package/dist/public-api.js +1 -0
  80. package/dist/public-api.js.map +1 -1
  81. package/package.json +21 -20
@@ -161,12 +161,12 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
161
161
  * the workspace emits {@link SelectedViewChange} whenever the selection changes internally.
162
162
  */
163
163
  get SelectedView() {
164
- return this.currentViewEntity;
164
+ return this.CurrentViewEntity;
165
165
  }
166
166
  set SelectedView(value) {
167
- this.currentViewEntity = value;
168
- this.currentGridState = value ? this.parseViewGridState(value) : this.loadUserDefaultGridState();
169
- this.viewModified = false;
167
+ this.CurrentViewEntity = value;
168
+ this.CurrentGridState = value ? this.parseViewGridState(value) : this.loadUserDefaultGridState();
169
+ this.ViewModified = false;
170
170
  }
171
171
  // ----- Host-driven passthrough inputs (forwarded to the inner entity-viewer) -----
172
172
  //
@@ -299,63 +299,257 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
299
299
  // INTERNAL STATE (plain fields — no state service)
300
300
  // ========================================
301
301
  /** The currently selected view entity (null = the default/unsaved view). */
302
- currentViewEntity = null;
302
+ CurrentViewEntity = null;
303
+ /** @deprecated Use {@link CurrentViewEntity}. */
304
+ get currentViewEntity() {
305
+ return this.CurrentViewEntity;
306
+ }
307
+ /** @deprecated Use {@link CurrentViewEntity}. */
308
+ set currentViewEntity(value) {
309
+ this.CurrentViewEntity = value;
310
+ }
303
311
  /** The currently selected view ID (null = default view). */
304
- selectedViewId = null;
312
+ SelectedViewId = null;
313
+ /** @deprecated Use {@link SelectedViewId}. */
314
+ get selectedViewId() {
315
+ return this.SelectedViewId;
316
+ }
317
+ /** @deprecated Use {@link SelectedViewId}. */
318
+ set selectedViewId(value) {
319
+ this.SelectedViewId = value;
320
+ }
305
321
  /** Live grid state (column widths / order / sort / aggregates) reflecting user interaction. */
306
- currentGridState = null;
322
+ CurrentGridState = null;
323
+ /** @deprecated Use {@link CurrentGridState}. */
324
+ get currentGridState() {
325
+ return this.CurrentGridState;
326
+ }
327
+ /** @deprecated Use {@link CurrentGridState}. */
328
+ set currentGridState(value) {
329
+ this.CurrentGridState = value;
330
+ }
307
331
  /** Whether the current view has unsaved modifications. */
308
- viewModified = false;
332
+ ViewModified = false;
333
+ /** @deprecated Use {@link ViewModified}. */
334
+ get viewModified() {
335
+ return this.ViewModified;
336
+ }
337
+ /** @deprecated Use {@link ViewModified}. */
338
+ set viewModified(value) {
339
+ this.ViewModified = value;
340
+ }
309
341
  /** The current free-text filter. */
310
342
  filterText = '';
311
343
  /** The currently selected record's composite-key string (for grid highlight). */
312
344
  selectedRecordId = null;
313
345
  /** Records loaded by the viewer, kept for config-panel sample data. */
314
- loadedRecords = [];
346
+ LoadedRecords = [];
347
+ /** @deprecated Use {@link LoadedRecords}. */
348
+ get loadedRecords() {
349
+ return this.LoadedRecords;
350
+ }
351
+ /** @deprecated Use {@link LoadedRecords}. */
352
+ set loadedRecords(value) {
353
+ this.LoadedRecords = value;
354
+ }
315
355
  /** Whether a save operation is in progress (drives the saving spinners). */
316
- isSavingView = false;
356
+ IsSavingView = false;
357
+ /** @deprecated Use {@link IsSavingView}. */
358
+ get isSavingView() {
359
+ return this.IsSavingView;
360
+ }
361
+ /** @deprecated Use {@link IsSavingView}. */
362
+ set isSavingView(value) {
363
+ this.IsSavingView = value;
364
+ }
317
365
  // ----- Dialog / panel open flags -----
318
366
  /** Whether the slide-in config panel is open. */
319
- isConfigPanelOpen = false;
367
+ IsConfigPanelOpen = false;
368
+ /** @deprecated Use {@link IsConfigPanelOpen}. */
369
+ get isConfigPanelOpen() {
370
+ return this.IsConfigPanelOpen;
371
+ }
372
+ /** @deprecated Use {@link IsConfigPanelOpen}. */
373
+ set isConfigPanelOpen(value) {
374
+ this.IsConfigPanelOpen = value;
375
+ }
320
376
  /** Whether the quick-save dialog is open. */
321
- showQuickSaveDialog = false;
377
+ ShowQuickSaveDialog = false;
378
+ /** @deprecated Use {@link ShowQuickSaveDialog}. */
379
+ get showQuickSaveDialog() {
380
+ return this.ShowQuickSaveDialog;
381
+ }
382
+ /** @deprecated Use {@link ShowQuickSaveDialog}. */
383
+ set showQuickSaveDialog(value) {
384
+ this.ShowQuickSaveDialog = value;
385
+ }
322
386
  /** Whether the duplicate-view dialog is open. */
323
- showDuplicateDialog = false;
387
+ ShowDuplicateDialog = false;
388
+ /** @deprecated Use {@link ShowDuplicateDialog}. */
389
+ get showDuplicateDialog() {
390
+ return this.ShowDuplicateDialog;
391
+ }
392
+ /** @deprecated Use {@link ShowDuplicateDialog}. */
393
+ set showDuplicateDialog(value) {
394
+ this.ShowDuplicateDialog = value;
395
+ }
324
396
  /** Whether the shared-view warning dialog is open. */
325
- showSharedViewWarning = false;
397
+ ShowSharedViewWarning = false;
398
+ /** @deprecated Use {@link ShowSharedViewWarning}. */
399
+ get showSharedViewWarning() {
400
+ return this.ShowSharedViewWarning;
401
+ }
402
+ /** @deprecated Use {@link ShowSharedViewWarning}. */
403
+ set showSharedViewWarning(value) {
404
+ this.ShowSharedViewWarning = value;
405
+ }
326
406
  /** Whether the config panel opens in "save as new" mode. */
327
- defaultSaveAsNew = false;
407
+ DefaultSaveAsNew = false;
408
+ /** @deprecated Use {@link DefaultSaveAsNew}. */
409
+ get defaultSaveAsNew() {
410
+ return this.DefaultSaveAsNew;
411
+ }
412
+ /** @deprecated Use {@link DefaultSaveAsNew}. */
413
+ set defaultSaveAsNew(value) {
414
+ this.DefaultSaveAsNew = value;
415
+ }
328
416
  // ----- Pending state carried across dialogs -----
329
417
  /** Summary shown in the quick-save dialog. */
330
- quickSaveSummary = null;
418
+ QuickSaveSummary = null;
419
+ /** @deprecated Use {@link QuickSaveSummary}. */
420
+ get quickSaveSummary() {
421
+ return this.QuickSaveSummary;
422
+ }
423
+ /** @deprecated Use {@link QuickSaveSummary}. */
424
+ set quickSaveSummary(value) {
425
+ this.QuickSaveSummary = value;
426
+ }
427
+ /**
428
+ * Name pre-filled in the quick-save dialog when it opens as a name prompt for a default-view
429
+ * Save that carries a filter (see {@link OnSaveDefaultViewSettings}). Empty for every other
430
+ * quick-save, which keeps the field blank as before.
431
+ */
432
+ QuickSaveSuggestedName = '';
433
+ /**
434
+ * The config-panel save staged while the user names the view it is being promoted into.
435
+ * Set only when a Save on the default (unsaved) view carries a filter; consumed by
436
+ * {@link executeQuickSave}, which merges it with the name the dialog collects.
437
+ */
438
+ pendingDefaultViewSave = null;
331
439
  /** Summary shown in the duplicate-view dialog. */
332
- duplicateSummary = null;
440
+ DuplicateSummary = null;
441
+ /** @deprecated Use {@link DuplicateSummary}. */
442
+ get duplicateSummary() {
443
+ return this.DuplicateSummary;
444
+ }
445
+ /** @deprecated Use {@link DuplicateSummary}. */
446
+ set duplicateSummary(value) {
447
+ this.DuplicateSummary = value;
448
+ }
333
449
  /** Source view name shown in the duplicate-view dialog. */
334
- duplicateSourceViewName = '';
450
+ DuplicateSourceViewName = '';
451
+ /** @deprecated Use {@link DuplicateSourceViewName}. */
452
+ get duplicateSourceViewName() {
453
+ return this.DuplicateSourceViewName;
454
+ }
455
+ /** @deprecated Use {@link DuplicateSourceViewName}. */
456
+ set duplicateSourceViewName(value) {
457
+ this.DuplicateSourceViewName = value;
458
+ }
335
459
  /** The view ID being duplicated. */
336
460
  duplicateTargetViewId = null;
337
461
  /** A quick-save event held while the shared-view warning is shown. */
338
462
  pendingQuickSaveEvent = null;
339
463
  /** Pre-populated name carried from quick-save dialog into the config panel. */
340
- pendingNewViewName = '';
464
+ PendingNewViewName = '';
465
+ /** @deprecated Use {@link PendingNewViewName}. */
466
+ get pendingNewViewName() {
467
+ return this.PendingNewViewName;
468
+ }
469
+ /** @deprecated Use {@link PendingNewViewName}. */
470
+ set pendingNewViewName(value) {
471
+ this.PendingNewViewName = value;
472
+ }
341
473
  /** Pre-populated description carried from quick-save dialog into the config panel. */
342
- pendingNewViewDescription = '';
474
+ PendingNewViewDescription = '';
475
+ /** @deprecated Use {@link PendingNewViewDescription}. */
476
+ get pendingNewViewDescription() {
477
+ return this.PendingNewViewDescription;
478
+ }
479
+ /** @deprecated Use {@link PendingNewViewDescription}. */
480
+ set pendingNewViewDescription(value) {
481
+ this.PendingNewViewDescription = value;
482
+ }
343
483
  /** Pre-populated sharing preference carried from quick-save dialog into the config panel. */
344
- pendingNewViewIsShared = false;
484
+ PendingNewViewIsShared = false;
485
+ /** @deprecated Use {@link PendingNewViewIsShared}. */
486
+ get pendingNewViewIsShared() {
487
+ return this.PendingNewViewIsShared;
488
+ }
489
+ /** @deprecated Use {@link PendingNewViewIsShared}. */
490
+ set pendingNewViewIsShared(value) {
491
+ this.PendingNewViewIsShared = value;
492
+ }
493
+ /** Traditional filter carried from quick-save dialog back into the config panel. */
494
+ PendingNewViewFilterState = null;
495
+ /** Smart-filter toggle carried from quick-save dialog back into the config panel. */
496
+ PendingNewViewSmartFilterEnabled = false;
497
+ /** Smart-filter prompt carried from quick-save dialog back into the config panel. */
498
+ PendingNewViewSmartFilterPrompt = '';
345
499
  // ----- Filter dialog (rendered at workspace level for full width) -----
346
500
  /** Whether the full-width filter dialog is open. */
347
- isFilterDialogOpen = false;
501
+ IsFilterDialogOpen = false;
502
+ /** @deprecated Use {@link IsFilterDialogOpen}. */
503
+ get isFilterDialogOpen() {
504
+ return this.IsFilterDialogOpen;
505
+ }
506
+ /** @deprecated Use {@link IsFilterDialogOpen}. */
507
+ set isFilterDialogOpen(value) {
508
+ this.IsFilterDialogOpen = value;
509
+ }
348
510
  /** The filter state currently being edited in the filter dialog. */
349
- filterDialogState = null;
511
+ FilterDialogState = null;
512
+ /** @deprecated Use {@link FilterDialogState}. */
513
+ get filterDialogState() {
514
+ return this.FilterDialogState;
515
+ }
516
+ /** @deprecated Use {@link FilterDialogState}. */
517
+ set filterDialogState(value) {
518
+ this.FilterDialogState = value;
519
+ }
350
520
  /** The filter fields available in the filter dialog. */
351
- filterDialogFields = [];
521
+ FilterDialogFields = [];
522
+ /** @deprecated Use {@link FilterDialogFields}. */
523
+ get filterDialogFields() {
524
+ return this.FilterDialogFields;
525
+ }
526
+ /** @deprecated Use {@link FilterDialogFields}. */
527
+ set filterDialogFields(value) {
528
+ this.FilterDialogFields = value;
529
+ }
352
530
  constructor(cdr) {
353
531
  super();
354
532
  this.cdr = cdr;
355
533
  }
356
534
  /** Controls the workspace-level export dialog (the same generic dialog the grid uses). */
357
- showExportDialog = false;
358
- exportDialogConfig = null;
535
+ ShowExportDialog = false;
536
+ /** @deprecated Use {@link ShowExportDialog}. */
537
+ get showExportDialog() {
538
+ return this.ShowExportDialog;
539
+ }
540
+ /** @deprecated Use {@link ShowExportDialog}. */
541
+ set showExportDialog(value) {
542
+ this.ShowExportDialog = value;
543
+ }
544
+ ExportDialogConfig = null;
545
+ /** @deprecated Use {@link ExportDialogConfig}. */
546
+ get exportDialogConfig() {
547
+ return this.ExportDialogConfig;
548
+ }
549
+ /** @deprecated Use {@link ExportDialogConfig}. */
550
+ set exportDialogConfig(value) {
551
+ this.ExportDialogConfig = value;
552
+ }
359
553
  /**
360
554
  * Handles the toolbar's Export button (the `<mj-view-selector>` `ExportRequested` output). This is
361
555
  * the always-visible, view-type-agnostic export for the workspace — previously the event wasn't
@@ -365,7 +559,7 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
365
559
  * options. Because the dialog only needs data + columns (it has no view-type dependency), this works
366
560
  * identically for Grid, Cards, Map and Timeline. Never fails silently — every path notifies.
367
561
  */
368
- async onExportRequested() {
562
+ async OnExportRequested() {
369
563
  const viewer = this.entityViewerRef;
370
564
  if (!viewer || !this._entity) {
371
565
  console.error('[ViewWorkspace] Export: viewer not ready', { hasViewer: !!viewer, hasEntity: !!this._entity });
@@ -379,7 +573,7 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
379
573
  MJNotificationService.Instance.CreateSimpleNotification('Nothing to export — the view returned no records.', 'warning', 5000);
380
574
  return;
381
575
  }
382
- this.exportDialogConfig = {
576
+ this.ExportDialogConfig = {
383
577
  data: rows,
384
578
  columns: this.buildExportColumns(),
385
579
  defaultFileName: this.buildExportFileName(),
@@ -389,7 +583,7 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
389
583
  defaultSamplingMode: 'all',
390
584
  dialogTitle: `Export ${this._entity.Name}`
391
585
  };
392
- this.showExportDialog = true;
586
+ this.ShowExportDialog = true;
393
587
  this.cdr.detectChanges();
394
588
  }
395
589
  catch (e) {
@@ -397,12 +591,20 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
397
591
  console.error('[ViewWorkspace] Export error:', e);
398
592
  }
399
593
  }
594
+ /** @deprecated Use {@link OnExportRequested}. */
595
+ async onExportRequested() {
596
+ return this.OnExportRequested();
597
+ }
400
598
  /** Closes the workspace export dialog (the dialog performs the export + download itself). */
401
- onExportDialogClosed(_result) {
402
- this.showExportDialog = false;
403
- this.exportDialogConfig = null;
599
+ OnExportDialogClosed(_result) {
600
+ this.ShowExportDialog = false;
601
+ this.ExportDialogConfig = null;
404
602
  this.cdr.detectChanges();
405
603
  }
604
+ /** @deprecated Use {@link OnExportDialogClosed}. */
605
+ onExportDialogClosed(_result) {
606
+ return this.OnExportDialogClosed(_result);
607
+ }
406
608
  /**
407
609
  * Columns to export: what the active renderer shows on screen (the grid), else the view's saved
408
610
  * columns, else the entity fields. The fallbacks apply the grid's own rules, so Cards, Map and
@@ -423,7 +625,7 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
423
625
  const user = this.ProviderToUse?.CurrentUser;
424
626
  const denied = user ? entity.GetDeniedReadFields(user) : new Set();
425
627
  const readable = (f) => !denied.has(f.Name.trim().toLowerCase());
426
- const gridCols = this.currentGridState?.columnSettings;
628
+ const gridCols = this.CurrentGridState?.columnSettings;
427
629
  if (gridCols && gridCols.length > 0) {
428
630
  const columns = [];
429
631
  const sorted = [...gridCols]
@@ -444,7 +646,7 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
444
646
  .map(f => ({ name: f.Name, displayName: f.DisplayNameOrName }));
445
647
  }
446
648
  buildExportFileName() {
447
- const viewName = this.currentViewEntity?.Name || 'Data';
649
+ const viewName = this.CurrentViewEntity?.Name || 'Data';
448
650
  return `${this._entity.Name}_${viewName}_${new Date().toISOString().split('T')[0]}`;
449
651
  }
450
652
  // ========================================
@@ -453,27 +655,30 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
453
655
  /** Initializes the workspace and loads the per-user default grid state for the entity. */
454
656
  ngOnInit() {
455
657
  this._initialized = true;
456
- if (this._entity && !this.currentViewEntity) {
457
- this.currentGridState = this.loadUserDefaultGridState();
658
+ if (this._entity && !this.CurrentViewEntity) {
659
+ this.CurrentGridState = this.loadUserDefaultGridState();
458
660
  }
459
661
  }
460
662
  /** Resets all view/selection state when the bound entity changes to a different entity. */
461
663
  resetForEntityChange() {
462
- this.currentViewEntity = null;
463
- this.selectedViewId = null;
464
- this.viewModified = false;
664
+ this.CurrentViewEntity = null;
665
+ this.SelectedViewId = null;
666
+ this.ViewModified = false;
465
667
  this.selectedRecordId = null;
466
- this.currentGridState = this.loadUserDefaultGridState();
668
+ this.CurrentGridState = this.loadUserDefaultGridState();
467
669
  this.closeAllDialogs();
468
670
  this.cdr.detectChanges();
469
671
  }
470
672
  /** Closes every dialog/panel — used on entity change. */
471
673
  closeAllDialogs() {
472
- this.isConfigPanelOpen = false;
473
- this.showQuickSaveDialog = false;
474
- this.showDuplicateDialog = false;
475
- this.showSharedViewWarning = false;
476
- this.isFilterDialogOpen = false;
674
+ this.IsConfigPanelOpen = false;
675
+ this.ShowQuickSaveDialog = false;
676
+ this.ShowDuplicateDialog = false;
677
+ this.ShowSharedViewWarning = false;
678
+ this.IsFilterDialogOpen = false;
679
+ // A save staged for the previous entity references that entity's fields — it must not
680
+ // surface on the next quick-save against the new one.
681
+ this.clearPendingNewViewState();
477
682
  }
478
683
  // ========================================
479
684
  // VIEW-TYPE SWITCHER (lifted into the toolbar)
@@ -485,26 +690,38 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
485
690
  * + persistence). The viewer's own header switcher is suppressed via Config, so this is the
486
691
  * single switcher in the workspace.
487
692
  */
488
- onToolbarViewTypeSelected(event) {
693
+ OnToolbarViewTypeSelected(event) {
489
694
  this.entityViewerRef?.SelectViewTypeById(event.viewTypeId);
490
695
  this.cdr.detectChanges();
491
696
  }
697
+ /** @deprecated Use {@link OnToolbarViewTypeSelected}. */
698
+ onToolbarViewTypeSelected(event) {
699
+ return this.OnToolbarViewTypeSelected(event);
700
+ }
492
701
  /**
493
702
  * The active view type's `MJ: View Types` row ID, read from the inner viewer for the toolbar
494
703
  * switcher's active highlight. Safe to read in the template — it reflects the viewer's own
495
704
  * resolved state rather than driving it.
496
705
  */
497
- get activeViewTypeId() {
706
+ get ActiveViewTypeId() {
498
707
  return this.entityViewerRef?.ActiveViewTypeId ?? null;
499
708
  }
709
+ /** @deprecated Use {@link ActiveViewTypeId}. */
710
+ get activeViewTypeId() {
711
+ return this.ActiveViewTypeId;
712
+ }
500
713
  /**
501
714
  * The Config forwarded to the inner viewer. Merges the host-supplied {@link ViewerConfig} but
502
715
  * always forces `showViewModeToggle: false` so the view-type switcher appears only once — in the
503
716
  * workspace toolbar, not duplicated in the viewer's own header.
504
717
  */
505
- get innerViewerConfig() {
718
+ get InnerViewerConfig() {
506
719
  return { ...(this.ViewerConfig ?? {}), showViewModeToggle: false };
507
720
  }
721
+ /** @deprecated Use {@link InnerViewerConfig}. */
722
+ get innerViewerConfig() {
723
+ return this.InnerViewerConfig;
724
+ }
508
725
  // ========================================
509
726
  // VIEW SELECTION
510
727
  // ========================================
@@ -512,82 +729,126 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
512
729
  * Handle a view selection from the selector dropdown. Applies the view's grid state and filter,
513
730
  * resets the modified flag, and notifies the host. (Generalized from DataExplorer.onViewSelected.)
514
731
  */
515
- onViewSelected(event) {
732
+ OnViewSelected(event) {
516
733
  this.entityViewerRef?.EnsurePendingChangesSaved();
517
- this.currentViewEntity = event.View;
518
- this.selectedViewId = event.ViewID;
519
- this.viewModified = false;
734
+ this.CurrentViewEntity = event.View;
735
+ this.SelectedViewId = event.ViewID;
736
+ this.ViewModified = false;
520
737
  this.filterText = '';
521
- this.currentGridState = event.View
738
+ this.CurrentGridState = event.View
522
739
  ? this.parseViewGridState(event.View)
523
740
  : this.loadUserDefaultGridState();
524
741
  this.ViewSelected.emit(event.View);
525
742
  this.SelectedViewChange.emit(event.View);
526
743
  this.cdr.detectChanges();
527
744
  }
745
+ /** @deprecated Use {@link OnViewSelected}. */
746
+ onViewSelected(event) {
747
+ return this.OnViewSelected(event);
748
+ }
528
749
  /** Handle filter text change from the viewer — re-emit so the host can sync its filter box / URL. */
529
- onFilterTextChanged(filterText) {
750
+ OnFilterTextChanged(filterText) {
530
751
  this.filterText = filterText;
531
752
  this.FilterTextChanged.emit(filterText);
532
753
  }
754
+ /** @deprecated Use {@link OnFilterTextChanged}. */
755
+ onFilterTextChanged(filterText) {
756
+ return this.OnFilterTextChanged(filterText);
757
+ }
533
758
  /** Track loaded records so the config panel has sample data, and re-emit the full event. */
534
- onDataLoaded(event) {
535
- this.loadedRecords = event.records;
759
+ OnDataLoaded(event) {
760
+ this.LoadedRecords = event.records;
536
761
  this.DataLoaded.emit(event);
537
762
  }
763
+ /** @deprecated Use {@link OnDataLoaded}. */
764
+ onDataLoaded(event) {
765
+ return this.OnDataLoaded(event);
766
+ }
538
767
  /** Re-emit the inner viewer's filtered-count change for the host. */
539
- onFilteredCountChanged(event) {
768
+ OnFilteredCountChanged(event) {
540
769
  this.FilteredCountChanged.emit(event);
541
770
  }
771
+ /** @deprecated Use {@link OnFilteredCountChanged}. */
772
+ onFilteredCountChanged(event) {
773
+ return this.OnFilteredCountChanged(event);
774
+ }
542
775
  // ========================================
543
776
  // RECORD SELECTION / OPENING / NAVIGATION (emit for host)
544
777
  // ========================================
545
778
  /** Handle a record single-click — track selection and re-emit for the host. */
546
- onRecordSelected(event) {
779
+ OnRecordSelected(event) {
547
780
  this.selectedRecordId = event.compositeKey.ToConcatenatedString();
548
781
  this.RecordSelected.emit(event);
549
782
  }
783
+ /** @deprecated Use {@link OnRecordSelected}. */
784
+ onRecordSelected(event) {
785
+ return this.OnRecordSelected(event);
786
+ }
550
787
  /** Handle a record open (double-click) — emit for the host to route to the record. */
551
- onRecordOpened(event) {
788
+ OnRecordOpened(event) {
552
789
  if (event.record) {
553
790
  this.OpenRecordRequested.emit({ entity: event.entity, record: event.record });
554
791
  }
555
792
  }
793
+ /** @deprecated Use {@link OnRecordOpened}. */
794
+ onRecordOpened(event) {
795
+ return this.OnRecordOpened(event);
796
+ }
556
797
  /**
557
798
  * Handle a plug-in renderer's request (via the inner viewer) to open a related record on a
558
799
  * (possibly different) entity — re-emit for the host to route.
559
800
  */
560
- onOpenRelatedRecordRequested(nav) {
801
+ OnOpenRelatedRecordRequested(nav) {
561
802
  this.OpenRelatedRecordRequested.emit(nav);
562
803
  }
804
+ /** @deprecated Use {@link OnOpenRelatedRecordRequested}. */
805
+ onOpenRelatedRecordRequested(nav) {
806
+ return this.OnOpenRelatedRecordRequested(nav);
807
+ }
563
808
  /**
564
809
  * Handle a plug-in renderer's request (via the inner viewer) to create a new record of the
565
810
  * current entity (e.g. a grid's "New" button) — re-emit for the host to open the form.
566
811
  */
567
- onCreateRecordRequested() {
812
+ OnCreateRecordRequested() {
568
813
  if (this._entity) {
569
814
  this.CreateNewRecordRequested.emit(this._entity);
570
815
  }
571
816
  }
817
+ /** @deprecated Use {@link OnCreateRecordRequested}. */
818
+ onCreateRecordRequested() {
819
+ return this.OnCreateRecordRequested();
820
+ }
572
821
  // ========================================
573
822
  // VIEW SELECTOR ACTIONS
574
823
  // ========================================
575
824
  /** Handle the selector's "open in tab" request — emit for the host to route. */
576
- onOpenInTabRequested(viewId) {
825
+ OnOpenInTabRequested(viewId) {
577
826
  this.OpenViewInTabRequested.emit(viewId);
578
827
  }
828
+ /** @deprecated Use {@link OnOpenInTabRequested}. */
829
+ onOpenInTabRequested(viewId) {
830
+ return this.OnOpenInTabRequested(viewId);
831
+ }
579
832
  /** Handle the selector's "create new record" request — emit for the host. */
580
- onCreateNewRecordRequested() {
833
+ OnCreateNewRecordRequested() {
581
834
  if (this._entity) {
582
835
  this.CreateNewRecordRequested.emit(this._entity);
583
836
  }
584
837
  }
838
+ /** @deprecated Use {@link OnCreateNewRecordRequested}. */
839
+ onCreateNewRecordRequested() {
840
+ return this.OnCreateNewRecordRequested();
841
+ }
585
842
  /** Handle the selector's "configure view" request — open the config panel. */
586
- onConfigureViewRequested() {
587
- this.defaultSaveAsNew = false;
588
- this.isConfigPanelOpen = true;
843
+ OnConfigureViewRequested() {
844
+ this.DefaultSaveAsNew = false;
845
+ this.IsConfigPanelOpen = true;
589
846
  this.cdr.detectChanges();
590
847
  }
848
+ /** @deprecated Use {@link OnConfigureViewRequested}. */
849
+ onConfigureViewRequested() {
850
+ return this.OnConfigureViewRequested();
851
+ }
591
852
  // ========================================
592
853
  // PROGRAMMATIC GRID CONTROL (passthrough to the inner entity viewer)
593
854
  // ========================================
@@ -647,46 +908,71 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
647
908
  return this.entityViewerRef ? this.entityViewerRef.ExportRecords(format) : false;
648
909
  }
649
910
  /** Handle the selector's "save view" request — open the config panel in the requested mode. */
650
- onSaveViewRequested(event) {
651
- this.defaultSaveAsNew = event.SaveAsNew || false;
652
- this.isConfigPanelOpen = true;
911
+ OnSaveViewRequested(event) {
912
+ this.DefaultSaveAsNew = event.SaveAsNew || false;
913
+ this.IsConfigPanelOpen = true;
653
914
  this.cdr.detectChanges();
654
915
  }
916
+ /** @deprecated Use {@link OnSaveViewRequested}. */
917
+ onSaveViewRequested(event) {
918
+ return this.OnSaveViewRequested(event);
919
+ }
655
920
  // ========================================
656
921
  // CONFIG PANEL
657
922
  // ========================================
658
923
  /** Close the config panel and clear any pending new-view carry-over state. */
659
- onCloseConfigPanel() {
660
- this.isConfigPanelOpen = false;
924
+ OnCloseConfigPanel() {
925
+ this.IsConfigPanelOpen = false;
661
926
  this.clearPendingNewViewState();
662
927
  this.cdr.detectChanges();
663
928
  }
929
+ /** @deprecated Use {@link OnCloseConfigPanel}. */
930
+ onCloseConfigPanel() {
931
+ return this.OnCloseConfigPanel();
932
+ }
664
933
  /** Reset the carry-over state used when continuing a new-view flow into the config panel. */
665
934
  clearPendingNewViewState() {
666
- this.pendingNewViewName = '';
667
- this.pendingNewViewDescription = '';
668
- this.pendingNewViewIsShared = false;
669
- this.defaultSaveAsNew = false;
935
+ this.PendingNewViewName = '';
936
+ this.PendingNewViewDescription = '';
937
+ this.PendingNewViewIsShared = false;
938
+ this.PendingNewViewFilterState = null;
939
+ this.PendingNewViewSmartFilterEnabled = false;
940
+ this.PendingNewViewSmartFilterPrompt = '';
941
+ this.DefaultSaveAsNew = false;
942
+ this.pendingDefaultViewSave = null;
943
+ this.QuickSaveSuggestedName = '';
670
944
  }
671
945
  // ----- Filter dialog -----
672
946
  /** Open the full-width filter dialog from the config panel's request. */
673
- onOpenFilterDialogRequest(event) {
674
- this.filterDialogState = event.filterState;
675
- this.filterDialogFields = event.filterFields;
676
- this.isFilterDialogOpen = true;
947
+ OnOpenFilterDialogRequest(event) {
948
+ this.FilterDialogState = event.filterState;
949
+ this.FilterDialogFields = event.filterFields;
950
+ this.IsFilterDialogOpen = true;
677
951
  this.cdr.detectChanges();
678
952
  }
953
+ /** @deprecated Use {@link OnOpenFilterDialogRequest}. */
954
+ onOpenFilterDialogRequest(event) {
955
+ return this.OnOpenFilterDialogRequest(event);
956
+ }
679
957
  /** Close the filter dialog. */
680
- onCloseFilterDialog() {
681
- this.isFilterDialogOpen = false;
958
+ OnCloseFilterDialog() {
959
+ this.IsFilterDialogOpen = false;
682
960
  this.cdr.detectChanges();
683
961
  }
962
+ /** @deprecated Use {@link OnCloseFilterDialog}. */
963
+ onCloseFilterDialog() {
964
+ return this.OnCloseFilterDialog();
965
+ }
684
966
  /** Apply a filter from the dialog — the config panel picks it up via `externalFilterState`. */
685
- onFilterApplied(filter) {
686
- this.filterDialogState = filter;
687
- this.isFilterDialogOpen = false;
967
+ OnFilterApplied(filter) {
968
+ this.FilterDialogState = filter;
969
+ this.IsFilterDialogOpen = false;
688
970
  this.cdr.detectChanges();
689
971
  }
972
+ /** @deprecated Use {@link OnFilterApplied}. */
973
+ onFilterApplied(filter) {
974
+ return this.OnFilterApplied(filter);
975
+ }
690
976
  // ========================================
691
977
  // SAVE VIEW
692
978
  // ========================================
@@ -694,33 +980,39 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
694
980
  * Handle a save from the config panel. When {@link AutoSaveView}, persists the view itself
695
981
  * (create-new or update) via the BaseEntity; otherwise emits {@link SaveViewRequested} for the host.
696
982
  * (Faithful generalization of DataExplorer.onSaveView, minus routing/state-service/notifications.)
983
+ * Resolves `true` once the view is saved (or handed to the host), `false` if it was not.
697
984
  */
698
- async onSaveView(event) {
985
+ async OnSaveView(event) {
699
986
  if (!this._entity) {
700
- return;
987
+ return false;
701
988
  }
702
989
  if (!this.AutoSaveView) {
703
990
  this.SaveViewRequested.emit(event);
704
- this.isConfigPanelOpen = false;
991
+ this.IsConfigPanelOpen = false;
705
992
  this.clearPendingNewViewState();
706
993
  this.cdr.detectChanges();
707
- return;
994
+ return true;
708
995
  }
709
- this.isSavingView = true;
996
+ this.IsSavingView = true;
710
997
  this.cdr.detectChanges();
711
- const isNew = event.SaveAsNew || !this.currentViewEntity;
998
+ const isNew = event.SaveAsNew || !this.CurrentViewEntity;
712
999
  const success = isNew
713
1000
  ? await this.persistNewView(event)
714
1001
  : await this.persistExistingView(event);
715
1002
  if (success) {
716
- this.isConfigPanelOpen = false;
1003
+ this.IsConfigPanelOpen = false;
717
1004
  this.clearPendingNewViewState();
718
1005
  // The view selector re-derives its lists reactively from UserViewEngine's cache
719
1006
  // (BaseEntity save auto-invalidates the engine); we only need to reload the grid data.
720
1007
  await this.entityViewerRef?.LoadData();
721
1008
  }
722
- this.isSavingView = false;
1009
+ this.IsSavingView = false;
723
1010
  this.cdr.detectChanges();
1011
+ return success;
1012
+ }
1013
+ /** @deprecated Use {@link OnSaveView}. */
1014
+ async onSaveView(event) {
1015
+ return this.OnSaveView(event);
724
1016
  }
725
1017
  /**
726
1018
  * Create and persist a brand-new view from the save event. Fires the cancelable
@@ -756,10 +1048,10 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
756
1048
  LogError(`[ViewWorkspace] Failed to create view: ${newView.LatestResult?.CompleteMessage ?? 'unknown error'}`);
757
1049
  return false;
758
1050
  }
759
- this.currentViewEntity = newView;
760
- this.selectedViewId = newView.ID;
761
- this.viewModified = false;
762
- this.currentGridState = this.parseViewGridState(newView);
1051
+ this.CurrentViewEntity = newView;
1052
+ this.SelectedViewId = newView.ID;
1053
+ this.ViewModified = false;
1054
+ this.CurrentGridState = this.parseViewGridState(newView);
763
1055
  this.ViewSelected.emit(newView);
764
1056
  this.SelectedViewChange.emit(newView);
765
1057
  this.AfterViewSave.emit({ View: newView, IsNew: true });
@@ -770,7 +1062,7 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
770
1062
  * {@link BeforeViewSave} before saving and {@link AfterViewSave} after success.
771
1063
  */
772
1064
  async persistExistingView(event) {
773
- const view = this.currentViewEntity;
1065
+ const view = this.CurrentViewEntity;
774
1066
  view.Name = event.Name;
775
1067
  view.Description = event.Description;
776
1068
  view.IsShared = event.IsShared;
@@ -795,8 +1087,8 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
795
1087
  LogError(`[ViewWorkspace] Failed to update view: ${view.LatestResult?.CompleteMessage ?? 'unknown error'}`);
796
1088
  return false;
797
1089
  }
798
- this.viewModified = false;
799
- this.currentGridState = this.parseViewGridState(view);
1090
+ this.ViewModified = false;
1091
+ this.CurrentGridState = this.parseViewGridState(view);
800
1092
  this.AfterViewSave.emit({ View: view, IsNew: false });
801
1093
  return true;
802
1094
  }
@@ -805,17 +1097,32 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
805
1097
  * persists to `UserInfoEngine`; otherwise emits {@link SaveDefaultsRequested}.
806
1098
  * (Generalized from DataExplorer.onSaveDefaultViewSettings.)
807
1099
  */
808
- async onSaveDefaultViewSettings(event) {
1100
+ async OnSaveDefaultViewSettings(event) {
809
1101
  if (!this._entity) {
810
1102
  return;
811
1103
  }
1104
+ // A default-view Save that carries a filter is not a preferences save at all — the
1105
+ // `default-view-setting/<Entity>` payload is an IGridState with nowhere to put a filter, so
1106
+ // persisting here would silently drop it (#4220). It is really a view creation missing a
1107
+ // name, so stage it and open the quick-save dialog to collect one. Confirming there routes
1108
+ // through `OnSaveView`, which honours `AutoSaveView` for both host modes.
1109
+ if (this.eventHasFilter(event)) {
1110
+ this.pendingDefaultViewSave = event;
1111
+ this.QuickSaveSuggestedName = this.buildSuggestedViewName(event);
1112
+ this.QuickSaveSummary = this.buildConfigSummary(event);
1113
+ this.DefaultSaveAsNew = true;
1114
+ this.IsConfigPanelOpen = false;
1115
+ this.ShowQuickSaveDialog = true;
1116
+ this.cdr.detectChanges();
1117
+ return;
1118
+ }
812
1119
  if (!this.AutoSaveView) {
813
1120
  this.SaveDefaultsRequested.emit(event);
814
- this.isConfigPanelOpen = false;
1121
+ this.IsConfigPanelOpen = false;
815
1122
  this.cdr.detectChanges();
816
1123
  return;
817
1124
  }
818
- this.isSavingView = true;
1125
+ this.IsSavingView = true;
819
1126
  this.cdr.detectChanges();
820
1127
  const gridState = this.buildGridState(event);
821
1128
  if (gridState) {
@@ -823,7 +1130,7 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
823
1130
  const settingKey = `default-view-setting/${this._entity.Name}`;
824
1131
  const saved = await UserInfoEngine.Instance.SetSetting(settingKey, JSON.stringify(gridState));
825
1132
  if (saved) {
826
- this.currentGridState = {
1133
+ this.CurrentGridState = {
827
1134
  columnSettings: gridState.columnSettings,
828
1135
  sortSettings: gridState.sortSettings,
829
1136
  aggregates: gridState.aggregates
@@ -835,10 +1142,14 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
835
1142
  LogError('[ViewWorkspace] Failed to save default view settings');
836
1143
  }
837
1144
  }
838
- this.isConfigPanelOpen = false;
839
- this.isSavingView = false;
1145
+ this.IsConfigPanelOpen = false;
1146
+ this.IsSavingView = false;
840
1147
  this.cdr.detectChanges();
841
1148
  }
1149
+ /** @deprecated Use {@link OnSaveDefaultViewSettings}. */
1150
+ async onSaveDefaultViewSettings(event) {
1151
+ return this.OnSaveDefaultViewSettings(event);
1152
+ }
842
1153
  // ========================================
843
1154
  // DELETE VIEW
844
1155
  // ========================================
@@ -847,17 +1158,17 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
847
1158
  * (firing cancelable {@link BeforeViewDelete} / notification {@link AfterViewDelete}); otherwise
848
1159
  * emits {@link DeleteViewRequested}. (Generalized from DataExplorer.onDeleteView.)
849
1160
  */
850
- async onDeleteView() {
851
- if (!this.currentViewEntity) {
1161
+ async OnDeleteView() {
1162
+ if (!this.CurrentViewEntity) {
852
1163
  return;
853
1164
  }
854
1165
  if (!this.AutoSaveView) {
855
- this.DeleteViewRequested.emit(this.currentViewEntity);
856
- this.isConfigPanelOpen = false;
1166
+ this.DeleteViewRequested.emit(this.CurrentViewEntity);
1167
+ this.IsConfigPanelOpen = false;
857
1168
  this.cdr.detectChanges();
858
1169
  return;
859
1170
  }
860
- const view = this.currentViewEntity;
1171
+ const view = this.CurrentViewEntity;
861
1172
  const viewId = view.ID;
862
1173
  const viewName = view.Name;
863
1174
  const before = { Data: view, Cancel: false };
@@ -870,11 +1181,11 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
870
1181
  LogError(`[ViewWorkspace] Failed to delete view: ${view.LatestResult?.CompleteMessage ?? 'unknown error'}`);
871
1182
  return;
872
1183
  }
873
- this.currentViewEntity = null;
874
- this.selectedViewId = null;
875
- this.viewModified = false;
876
- this.isConfigPanelOpen = false;
877
- this.currentGridState = this.loadUserDefaultGridState();
1184
+ this.CurrentViewEntity = null;
1185
+ this.SelectedViewId = null;
1186
+ this.ViewModified = false;
1187
+ this.IsConfigPanelOpen = false;
1188
+ this.CurrentGridState = this.loadUserDefaultGridState();
878
1189
  // The view selector re-derives its lists reactively from UserViewEngine's cache
879
1190
  // (BaseEntity delete auto-invalidates the engine), so no explicit reload is needed here.
880
1191
  this.ViewSelected.emit(null);
@@ -882,6 +1193,10 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
882
1193
  this.AfterViewDelete.emit({ ViewID: viewId, ViewName: viewName });
883
1194
  this.cdr.detectChanges();
884
1195
  }
1196
+ /** @deprecated Use {@link OnDeleteView}. */
1197
+ async onDeleteView() {
1198
+ return this.OnDeleteView();
1199
+ }
885
1200
  // ========================================
886
1201
  // QUICK SAVE
887
1202
  // ========================================
@@ -889,47 +1204,73 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
889
1204
  * Handle a quick-save request from the selector (F-001). Builds a summary from the config panel
890
1205
  * and opens the quick-save dialog. (Generalized from DataExplorer.onQuickSaveRequested.)
891
1206
  */
892
- onQuickSaveRequested(saveAsNew) {
893
- this.defaultSaveAsNew = saveAsNew;
894
- this.quickSaveSummary = this.viewConfigPanelRef?.BuildSummary() ?? null;
895
- this.showQuickSaveDialog = true;
1207
+ OnQuickSaveRequested(saveAsNew) {
1208
+ this.DefaultSaveAsNew = saveAsNew;
1209
+ this.QuickSaveSummary = this.viewConfigPanelRef?.BuildSummary() ?? null;
1210
+ this.ShowQuickSaveDialog = true;
896
1211
  this.cdr.detectChanges();
897
1212
  }
1213
+ /** @deprecated Use {@link OnQuickSaveRequested}. */
1214
+ onQuickSaveRequested(saveAsNew) {
1215
+ return this.OnQuickSaveRequested(saveAsNew);
1216
+ }
898
1217
  /**
899
1218
  * Handle the quick-save dialog's save. Intercepts updates to a shared view with the shared-view
900
1219
  * warning; otherwise executes the save. (Generalized from DataExplorer.onQuickSave.)
901
1220
  */
902
- async onQuickSave(event) {
903
- this.showQuickSaveDialog = false;
904
- if (!event.SaveAsNew && this.currentViewEntity?.IsShared) {
1221
+ async OnQuickSave(event) {
1222
+ this.ShowQuickSaveDialog = false;
1223
+ if (!event.SaveAsNew && this.CurrentViewEntity?.IsShared) {
905
1224
  this.pendingQuickSaveEvent = event;
906
- this.showSharedViewWarning = true;
1225
+ this.ShowSharedViewWarning = true;
907
1226
  this.cdr.detectChanges();
908
1227
  return;
909
1228
  }
910
1229
  await this.executeQuickSave(event);
911
1230
  }
912
- /** Build a `ViewSaveEvent` from a `QuickSaveEvent` and delegate to {@link onSaveView}. */
1231
+ /** @deprecated Use {@link OnQuickSave}. */
1232
+ async onQuickSave(event) {
1233
+ return this.OnQuickSave(event);
1234
+ }
1235
+ /**
1236
+ * Build a `ViewSaveEvent` from a `QuickSaveEvent` and delegate to {@link OnSaveView}.
1237
+ *
1238
+ * When a config-panel save is staged — a default-view Save carrying a filter, waiting only on a
1239
+ * name (see {@link OnSaveDefaultViewSettings}) — its columns, sort, filter and aggregates are
1240
+ * merged in and the dialog supplies just the name/description/sharing. The smart-filter fields
1241
+ * used to be hard-coded to `false`/`''` here, which dropped a smart filter on this path too
1242
+ * (#4220).
1243
+ *
1244
+ * The staged save is left in place until the save succeeds ({@link OnSaveView} clears it). If
1245
+ * the save fails or a host cancels it, the name prompt reopens so the user can retry or cancel
1246
+ * rather than losing the filter with nothing on screen.
1247
+ */
913
1248
  async executeQuickSave(event) {
1249
+ const staged = this.pendingDefaultViewSave;
914
1250
  const viewSaveEvent = {
915
1251
  Name: event.Name,
916
1252
  Description: event.Description,
917
1253
  IsShared: event.IsShared,
918
1254
  SaveAsNew: event.SaveAsNew,
919
- Columns: [],
920
- SortField: null,
921
- SortDirection: 'asc',
922
- SortItems: [],
923
- SmartFilterEnabled: false,
924
- SmartFilterPrompt: '',
925
- FilterState: this.filterDialogState ?? null,
926
- AggregatesConfig: null
1255
+ Columns: staged?.Columns ?? [],
1256
+ SortField: staged?.SortField ?? null,
1257
+ SortDirection: staged?.SortDirection ?? 'asc',
1258
+ SortItems: staged?.SortItems ?? [],
1259
+ SmartFilterEnabled: staged?.SmartFilterEnabled ?? false,
1260
+ SmartFilterPrompt: staged?.SmartFilterPrompt ?? '',
1261
+ FilterState: staged?.FilterState ?? this.FilterDialogState ?? null,
1262
+ AggregatesConfig: staged?.AggregatesConfig ?? null
927
1263
  };
928
- await this.onSaveView(viewSaveEvent);
1264
+ const saved = await this.OnSaveView(viewSaveEvent);
1265
+ if (!saved && staged) {
1266
+ this.QuickSaveSuggestedName = event.Name;
1267
+ this.ShowQuickSaveDialog = true;
1268
+ this.cdr.detectChanges();
1269
+ }
929
1270
  }
930
1271
  /** Handle the shared-view warning action (update / save-as-copy / cancel). */
931
- async onSharedViewAction(action) {
932
- this.showSharedViewWarning = false;
1272
+ async OnSharedViewAction(action) {
1273
+ this.ShowSharedViewWarning = false;
933
1274
  const event = this.pendingQuickSaveEvent;
934
1275
  this.pendingQuickSaveEvent = null;
935
1276
  if (!event) {
@@ -943,27 +1284,54 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
943
1284
  }
944
1285
  this.cdr.detectChanges();
945
1286
  }
1287
+ /** @deprecated Use {@link OnSharedViewAction}. */
1288
+ async onSharedViewAction(action) {
1289
+ return this.OnSharedViewAction(action);
1290
+ }
946
1291
  /** Handle the shared-view warning cancel. */
947
- onSharedViewWarningCancel() {
948
- this.showSharedViewWarning = false;
1292
+ OnSharedViewWarningCancel() {
1293
+ this.ShowSharedViewWarning = false;
949
1294
  this.pendingQuickSaveEvent = null;
950
1295
  this.cdr.detectChanges();
951
1296
  }
1297
+ /** @deprecated Use {@link OnSharedViewWarningCancel}. */
1298
+ onSharedViewWarningCancel() {
1299
+ return this.OnSharedViewWarningCancel();
1300
+ }
952
1301
  /** Handle the quick-save dialog close. */
953
- onQuickSaveClose() {
954
- this.showQuickSaveDialog = false;
1302
+ OnQuickSaveClose() {
1303
+ this.ShowQuickSaveDialog = false;
1304
+ // Abandoning the name prompt abandons the promoted save with it — otherwise the staged
1305
+ // filter would surface on whatever the user saved next.
1306
+ this.clearPendingNewViewState();
955
1307
  this.cdr.detectChanges();
956
1308
  }
1309
+ /** @deprecated Use {@link OnQuickSaveClose}. */
1310
+ onQuickSaveClose() {
1311
+ return this.OnQuickSaveClose();
1312
+ }
957
1313
  /** Handle "open advanced" from the quick-save dialog — carry data into the config panel. */
958
- onQuickSaveOpenAdvanced(event) {
959
- this.pendingNewViewName = event.Name;
960
- this.pendingNewViewDescription = event.Description;
961
- this.pendingNewViewIsShared = event.IsShared;
962
- this.defaultSaveAsNew = true;
963
- this.showQuickSaveDialog = false;
964
- this.isConfigPanelOpen = true;
1314
+ OnQuickSaveOpenAdvanced(event) {
1315
+ this.PendingNewViewName = event.Name;
1316
+ this.PendingNewViewDescription = event.Description;
1317
+ this.PendingNewViewIsShared = event.IsShared;
1318
+ // Hand any staged filter back to the panel, which resets its own filter state on open.
1319
+ // Without this the escape hatch out of the name prompt would silently drop the filter.
1320
+ const staged = this.pendingDefaultViewSave;
1321
+ if (staged) {
1322
+ this.PendingNewViewFilterState = staged.FilterState;
1323
+ this.PendingNewViewSmartFilterEnabled = staged.SmartFilterEnabled;
1324
+ this.PendingNewViewSmartFilterPrompt = staged.SmartFilterPrompt;
1325
+ }
1326
+ this.DefaultSaveAsNew = true;
1327
+ this.ShowQuickSaveDialog = false;
1328
+ this.IsConfigPanelOpen = true;
965
1329
  this.cdr.detectChanges();
966
1330
  }
1331
+ /** @deprecated Use {@link OnQuickSaveOpenAdvanced}. */
1332
+ onQuickSaveOpenAdvanced(event) {
1333
+ return this.OnQuickSaveOpenAdvanced(event);
1334
+ }
967
1335
  // ========================================
968
1336
  // DUPLICATE VIEW
969
1337
  // ========================================
@@ -971,19 +1339,23 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
971
1339
  * Handle a duplicate request (F-005). Opens the duplicate dialog so the user names the copy.
972
1340
  * (Generalized from DataExplorer.onDuplicateView.)
973
1341
  */
974
- onDuplicateViewRequested(viewId) {
975
- const targetId = viewId || this.currentViewEntity?.ID;
1342
+ OnDuplicateViewRequested(viewId) {
1343
+ const targetId = viewId || this.CurrentViewEntity?.ID;
976
1344
  if (!targetId || !this._entity) {
977
1345
  return;
978
1346
  }
979
1347
  const allViews = [...(this.viewSelectorRef?.MyViews ?? []), ...(this.viewSelectorRef?.SharedViews ?? [])];
980
1348
  const viewItem = allViews.find(v => v.id === targetId);
981
1349
  this.duplicateTargetViewId = targetId;
982
- this.duplicateSourceViewName = viewItem?.name || this.currentViewEntity?.Name || 'View';
983
- this.duplicateSummary = this.buildDuplicateSummary(viewItem?.entity ?? this.currentViewEntity);
984
- this.showDuplicateDialog = true;
1350
+ this.DuplicateSourceViewName = viewItem?.name || this.CurrentViewEntity?.Name || 'View';
1351
+ this.DuplicateSummary = this.buildDuplicateSummary(viewItem?.entity ?? this.CurrentViewEntity);
1352
+ this.ShowDuplicateDialog = true;
985
1353
  this.cdr.detectChanges();
986
1354
  }
1355
+ /** @deprecated Use {@link OnDuplicateViewRequested}. */
1356
+ onDuplicateViewRequested(viewId) {
1357
+ return this.OnDuplicateViewRequested(viewId);
1358
+ }
987
1359
  /** Build a {@link ViewConfigSummary} from a view entity for the duplicate dialog. */
988
1360
  buildDuplicateSummary(view) {
989
1361
  if (!view) {
@@ -1033,8 +1405,8 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
1033
1405
  * copies its config into a new personal view, and persists it; otherwise emits
1034
1406
  * {@link DuplicateViewRequested}. (Generalized from DataExplorer.onDuplicateConfirmed.)
1035
1407
  */
1036
- async onDuplicateConfirmed(event) {
1037
- this.showDuplicateDialog = false;
1408
+ async OnDuplicateConfirmed(event) {
1409
+ this.ShowDuplicateDialog = false;
1038
1410
  const targetId = this.duplicateTargetViewId;
1039
1411
  this.duplicateTargetViewId = null;
1040
1412
  if (!targetId || !this._entity) {
@@ -1046,6 +1418,10 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
1046
1418
  }
1047
1419
  await this.persistDuplicate(targetId, event.Name);
1048
1420
  }
1421
+ /** @deprecated Use {@link OnDuplicateConfirmed}. */
1422
+ async onDuplicateConfirmed(event) {
1423
+ return this.OnDuplicateConfirmed(event);
1424
+ }
1049
1425
  /** Load the source view, copy its config into a new personal view, and persist it. */
1050
1426
  async persistDuplicate(sourceViewId, newName) {
1051
1427
  const provider = this.ProviderToUse;
@@ -1077,18 +1453,26 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
1077
1453
  this.AfterViewSave.emit({ View: newView, IsNew: true });
1078
1454
  }
1079
1455
  /** Handle the duplicate dialog cancel. */
1080
- onDuplicateCancel() {
1081
- this.showDuplicateDialog = false;
1456
+ OnDuplicateCancel() {
1457
+ this.ShowDuplicateDialog = false;
1082
1458
  this.duplicateTargetViewId = null;
1083
1459
  this.cdr.detectChanges();
1084
1460
  }
1461
+ /** @deprecated Use {@link OnDuplicateCancel}. */
1462
+ onDuplicateCancel() {
1463
+ return this.OnDuplicateCancel();
1464
+ }
1085
1465
  /** Handle duplicate triggered from the config panel — duplicate the selected view. */
1086
- onDuplicateFromPanel() {
1087
- if (this.currentViewEntity?.ID) {
1088
- this.isConfigPanelOpen = false;
1089
- this.onDuplicateViewRequested(this.currentViewEntity.ID);
1466
+ OnDuplicateFromPanel() {
1467
+ if (this.CurrentViewEntity?.ID) {
1468
+ this.IsConfigPanelOpen = false;
1469
+ this.OnDuplicateViewRequested(this.CurrentViewEntity.ID);
1090
1470
  }
1091
1471
  }
1472
+ /** @deprecated Use {@link OnDuplicateFromPanel}. */
1473
+ onDuplicateFromPanel() {
1474
+ return this.OnDuplicateFromPanel();
1475
+ }
1092
1476
  // ========================================
1093
1477
  // REVERT
1094
1478
  // ========================================
@@ -1096,18 +1480,22 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
1096
1480
  * Handle a revert request (F-007). Re-parses the saved view's grid state, clears the modified
1097
1481
  * flag, and reloads. (Generalized from DataExplorer.onRevertView.)
1098
1482
  */
1099
- async onRevertView() {
1100
- if (!this.currentViewEntity) {
1483
+ async OnRevertView() {
1484
+ if (!this.CurrentViewEntity) {
1101
1485
  return;
1102
1486
  }
1103
- const gridState = this.parseViewGridState(this.currentViewEntity);
1487
+ const gridState = this.parseViewGridState(this.CurrentViewEntity);
1104
1488
  if (gridState) {
1105
- this.currentGridState = gridState;
1489
+ this.CurrentGridState = gridState;
1106
1490
  }
1107
- this.viewModified = false;
1491
+ this.ViewModified = false;
1108
1492
  await this.entityViewerRef?.LoadData();
1109
1493
  this.cdr.detectChanges();
1110
1494
  }
1495
+ /** @deprecated Use {@link OnRevertView}. */
1496
+ async onRevertView() {
1497
+ return this.OnRevertView();
1498
+ }
1111
1499
  // ========================================
1112
1500
  // GRID/SORT/FILTER STATE BUILDERS
1113
1501
  // (Generalized from DataExplorer — no state-service / selectedEntity coupling.)
@@ -1130,8 +1518,8 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
1130
1518
  format: col.format
1131
1519
  }));
1132
1520
  }
1133
- else if (this.currentGridState?.columnSettings && this.currentGridState.columnSettings.length > 0) {
1134
- columnSettings = this.currentGridState.columnSettings;
1521
+ else if (this.CurrentGridState?.columnSettings && this.CurrentGridState.columnSettings.length > 0) {
1522
+ columnSettings = this.CurrentGridState.columnSettings;
1135
1523
  }
1136
1524
  else if (this._entity) {
1137
1525
  columnSettings = this._entity.Fields
@@ -1153,8 +1541,8 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
1153
1541
  }
1154
1542
  const sortSettings = this.buildGridSortSettings(event);
1155
1543
  let aggregates = event.AggregatesConfig ?? undefined;
1156
- if (!aggregates && this.currentGridState?.aggregates) {
1157
- aggregates = this.currentGridState.aggregates;
1544
+ if (!aggregates && this.CurrentGridState?.aggregates) {
1545
+ aggregates = this.CurrentGridState.aggregates;
1158
1546
  }
1159
1547
  return { columnSettings, sortSettings, aggregates };
1160
1548
  }
@@ -1166,8 +1554,8 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
1166
1554
  if (event.SortField) {
1167
1555
  return [{ field: event.SortField, dir: event.SortDirection }];
1168
1556
  }
1169
- if (this.currentGridState?.sortSettings && this.currentGridState.sortSettings.length > 0) {
1170
- return this.currentGridState.sortSettings;
1557
+ if (this.CurrentGridState?.sortSettings && this.CurrentGridState.sortSettings.length > 0) {
1558
+ return this.CurrentGridState.sortSettings;
1171
1559
  }
1172
1560
  return undefined;
1173
1561
  }
@@ -1187,6 +1575,44 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
1187
1575
  ? JSON.stringify(event.FilterState)
1188
1576
  : JSON.stringify({ logic: 'and', filters: [] });
1189
1577
  }
1578
+ /**
1579
+ * Whether a save event carries a filter the user would expect to be kept — a smart prompt with
1580
+ * actual text, or at least one traditional filter. Deliberately stricter than the config panel's
1581
+ * own `hasActiveFilters()`, which only looks at the traditional list.
1582
+ */
1583
+ eventHasFilter(event) {
1584
+ const hasSmartFilter = event.SmartFilterEnabled && !!event.SmartFilterPrompt?.trim();
1585
+ const hasTraditionalFilter = (event.FilterState?.filters?.length ?? 0) > 0;
1586
+ return hasSmartFilter || hasTraditionalFilter;
1587
+ }
1588
+ /** Longest suggested name we will build before eliding the smart prompt. */
1589
+ static SUGGESTED_NAME_MAX_LENGTH = 60;
1590
+ /**
1591
+ * Propose a name for a view being promoted out of the default view, so the user can accept it
1592
+ * with a single click. A smart prompt describes the view better than anything we could invent,
1593
+ * so it becomes the name (elided if long); a traditional filter gets a plain marker.
1594
+ */
1595
+ buildSuggestedViewName(event) {
1596
+ const entityName = this._entity?.DisplayNameOrName ?? this._entity?.Name ?? 'View';
1597
+ const prompt = event.SmartFilterEnabled ? event.SmartFilterPrompt?.trim() : '';
1598
+ if (!prompt) {
1599
+ return `${entityName} — Filtered`;
1600
+ }
1601
+ const budget = ViewWorkspaceComponent.SUGGESTED_NAME_MAX_LENGTH - entityName.length - ' — '.length;
1602
+ const suffix = prompt.length > budget ? `${prompt.slice(0, Math.max(budget - 1, 1)).trimEnd()}…` : prompt;
1603
+ return `${entityName} — ${suffix}`;
1604
+ }
1605
+ /** Build a {@link ViewConfigSummary} from a pending save event, for the quick-save dialog. */
1606
+ buildConfigSummary(event) {
1607
+ return {
1608
+ ColumnCount: event.Columns.length,
1609
+ FilterCount: event.FilterState?.filters?.length ?? 0,
1610
+ SortCount: event.SortItems.length,
1611
+ SmartFilterActive: event.SmartFilterEnabled && !!event.SmartFilterPrompt?.trim(),
1612
+ SmartFilterPrompt: event.SmartFilterPrompt,
1613
+ AggregateCount: event.AggregatesConfig?.expressions?.filter(a => a.enabled !== false).length ?? 0
1614
+ };
1615
+ }
1190
1616
  // ========================================
1191
1617
  // GRID STATE PARSE / DEFAULTS
1192
1618
  // ========================================
@@ -1257,7 +1683,7 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
1257
1683
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.viewSelectorRef = _t.first);
1258
1684
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.entityViewerRef = _t.first);
1259
1685
  i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.viewConfigPanelRef = _t.first);
1260
- } }, inputs: { Entity: "Entity", EntityName: "EntityName", EntityID: "EntityID", AutoSaveView: "AutoSaveView", SelectedView: "SelectedView", FilterText: "FilterText", SelectedRecordId: "SelectedRecordId", ViewerConfig: "ViewerConfig" }, outputs: { OpenRecordRequested: "OpenRecordRequested", OpenViewInTabRequested: "OpenViewInTabRequested", CreateNewRecordRequested: "CreateNewRecordRequested", OpenRelatedRecordRequested: "OpenRelatedRecordRequested", RecordSelected: "RecordSelected", ViewSelected: "ViewSelected", SelectedViewChange: "SelectedViewChange", FilterTextChanged: "FilterTextChanged", DataLoaded: "DataLoaded", FilteredCountChanged: "FilteredCountChanged", BeforeViewSave: "BeforeViewSave", AfterViewSave: "AfterViewSave", BeforeViewDelete: "BeforeViewDelete", AfterViewDelete: "AfterViewDelete", SaveViewRequested: "SaveViewRequested", DeleteViewRequested: "DeleteViewRequested", DuplicateViewRequested: "DuplicateViewRequested", SaveDefaultsRequested: "SaveDefaultsRequested" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 13, vars: 34, consts: [[1, "view-workspace"], [1, "workspace-toolbar"], [3, "ViewSelected", "SaveViewRequested", "OpenInTabRequested", "ConfigureViewRequested", "CreateNewRecordRequested", "ExportRequested", "DuplicateViewRequested", "QuickSaveRequested", "RevertRequested", "Provider", "Entity", "SelectedViewID", "ViewModified"], [1, "workspace-toolbar-spacer"], [3, "ViewTypeSelected", "Provider", "Entity", "ActiveViewTypeID"], [1, "workspace-body"], [3, "Provider", "Entity", "ViewEntity", "FilterText", "SelectedRecordID", "GridState", "AutoSaveView", "Config"], [3, "Close", "Save", "SaveDefaults", "Delete", "Duplicate", "OpenFilterDialogRequest", "Provider", "Entity", "ViewEntity", "IsOpen", "CurrentGridState", "SampleData", "ExternalFilterState", "IsSaving", "DefaultSaveAsNew", "PendingNewViewName", "PendingNewViewDescription", "PendingNewViewIsShared"], [3, "Save", "Close", "OpenAdvanced", "IsOpen", "ViewEntity", "EntityName", "Summary", "IsSaving", "DefaultSaveAsNew"], [3, "Duplicate", "Cancel", "IsOpen", "SourceViewName", "Summary"], [3, "Action", "Cancel", "IsOpen", "ViewName"], [3, "closed", "visible", "config"], [3, "FilterTextChange", "RecordSelected", "RecordOpened", "DataLoaded", "FilteredCountChanged", "OpenRelatedRecordRequested", "CreateRecordRequested", "ConfigureRequested", "Provider", "Entity", "ViewEntity", "FilterText", "SelectedRecordID", "GridState", "AutoSaveView", "Config"], [1, "filter-dialog-backdrop", 3, "click"], ["role", "dialog", "aria-modal", "true", 1, "filter-dialog"], [1, "filter-dialog-header"], [1, "filter-dialog-title"], [1, "fa-solid", "fa-filter"], ["type", "button", "title", "Close", 1, "filter-dialog-close", 3, "click"], [1, "fa-solid", "fa-times"], [1, "filter-dialog-content"], [3, "apply", "filterChange", "fields", "filter", "showSummary"], [1, "filter-dialog-footer"], ["mjButton", "", "variant", "primary", "size", "sm", "type", "button", 3, "click"], [1, "fa-solid", "fa-check"], ["mjButton", "", "variant", "flat", "size", "sm", "type", "button", 3, "click"]], template: function ViewWorkspaceComponent_Template(rf, ctx) { if (rf & 1) {
1686
+ } }, inputs: { Entity: "Entity", EntityName: "EntityName", EntityID: "EntityID", AutoSaveView: "AutoSaveView", SelectedView: "SelectedView", FilterText: "FilterText", SelectedRecordId: "SelectedRecordId", ViewerConfig: "ViewerConfig" }, outputs: { OpenRecordRequested: "OpenRecordRequested", OpenViewInTabRequested: "OpenViewInTabRequested", CreateNewRecordRequested: "CreateNewRecordRequested", OpenRelatedRecordRequested: "OpenRelatedRecordRequested", RecordSelected: "RecordSelected", ViewSelected: "ViewSelected", SelectedViewChange: "SelectedViewChange", FilterTextChanged: "FilterTextChanged", DataLoaded: "DataLoaded", FilteredCountChanged: "FilteredCountChanged", BeforeViewSave: "BeforeViewSave", AfterViewSave: "AfterViewSave", BeforeViewDelete: "BeforeViewDelete", AfterViewDelete: "AfterViewDelete", SaveViewRequested: "SaveViewRequested", DeleteViewRequested: "DeleteViewRequested", DuplicateViewRequested: "DuplicateViewRequested", SaveDefaultsRequested: "SaveDefaultsRequested" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 13, vars: 38, consts: [[1, "view-workspace"], [1, "workspace-toolbar"], [3, "ViewSelected", "SaveViewRequested", "OpenInTabRequested", "ConfigureViewRequested", "CreateNewRecordRequested", "ExportRequested", "DuplicateViewRequested", "QuickSaveRequested", "RevertRequested", "Provider", "Entity", "SelectedViewID", "ViewModified"], [1, "workspace-toolbar-spacer"], [3, "ViewTypeSelected", "Provider", "Entity", "ActiveViewTypeID"], [1, "workspace-body"], [3, "Provider", "Entity", "ViewEntity", "FilterText", "SelectedRecordID", "GridState", "AutoSaveView", "Config"], [3, "Close", "Save", "SaveDefaults", "Delete", "Duplicate", "OpenFilterDialogRequest", "Provider", "Entity", "ViewEntity", "IsOpen", "CurrentGridState", "SampleData", "ExternalFilterState", "IsSaving", "DefaultSaveAsNew", "PendingNewViewName", "PendingNewViewDescription", "PendingNewViewIsShared", "PendingNewViewFilterState", "PendingNewViewSmartFilterEnabled", "PendingNewViewSmartFilterPrompt"], [3, "Save", "Close", "OpenAdvanced", "IsOpen", "ViewEntity", "EntityName", "Summary", "IsSaving", "DefaultSaveAsNew", "SuggestedName"], [3, "Duplicate", "Cancel", "IsOpen", "SourceViewName", "Summary"], [3, "Action", "Cancel", "IsOpen", "ViewName"], [3, "closed", "visible", "config"], [3, "FilterTextChange", "RecordSelected", "RecordOpened", "DataLoaded", "FilteredCountChanged", "OpenRelatedRecordRequested", "CreateRecordRequested", "ConfigureRequested", "Provider", "Entity", "ViewEntity", "FilterText", "SelectedRecordID", "GridState", "AutoSaveView", "Config"], [1, "filter-dialog-backdrop", 3, "click"], ["role", "dialog", "aria-modal", "true", 1, "filter-dialog"], [1, "filter-dialog-header"], [1, "filter-dialog-title"], [1, "fa-solid", "fa-filter"], ["type", "button", "title", "Close", 1, "filter-dialog-close", 3, "click"], [1, "fa-solid", "fa-times"], [1, "filter-dialog-content"], [3, "apply", "filterChange", "fields", "filter", "showSummary"], [1, "filter-dialog-footer"], ["mjButton", "", "variant", "primary", "size", "sm", "type", "button", 3, "click"], [1, "fa-solid", "fa-check"], ["mjButton", "", "variant", "flat", "size", "sm", "type", "button", 3, "click"]], template: function ViewWorkspaceComponent_Template(rf, ctx) { if (rf & 1) {
1261
1687
  i0.ɵɵelementStart(0, "div", 0)(1, "div", 1)(2, "mj-view-selector", 2);
1262
1688
  i0.ɵɵlistener("ViewSelected", function ViewWorkspaceComponent_Template_mj_view_selector_ViewSelected_2_listener($event) { return ctx.onViewSelected($event); })("SaveViewRequested", function ViewWorkspaceComponent_Template_mj_view_selector_SaveViewRequested_2_listener($event) { return ctx.onSaveViewRequested($event); })("OpenInTabRequested", function ViewWorkspaceComponent_Template_mj_view_selector_OpenInTabRequested_2_listener($event) { return ctx.onOpenInTabRequested($event); })("ConfigureViewRequested", function ViewWorkspaceComponent_Template_mj_view_selector_ConfigureViewRequested_2_listener() { return ctx.onConfigureViewRequested(); })("CreateNewRecordRequested", function ViewWorkspaceComponent_Template_mj_view_selector_CreateNewRecordRequested_2_listener() { return ctx.onCreateNewRecordRequested(); })("ExportRequested", function ViewWorkspaceComponent_Template_mj_view_selector_ExportRequested_2_listener() { return ctx.onExportRequested(); })("DuplicateViewRequested", function ViewWorkspaceComponent_Template_mj_view_selector_DuplicateViewRequested_2_listener($event) { return ctx.onDuplicateViewRequested($event); })("QuickSaveRequested", function ViewWorkspaceComponent_Template_mj_view_selector_QuickSaveRequested_2_listener($event) { return ctx.onQuickSaveRequested($event); })("RevertRequested", function ViewWorkspaceComponent_Template_mj_view_selector_RevertRequested_2_listener() { return ctx.onRevertView(); });
1263
1689
  i0.ɵɵelementEnd();
@@ -1292,11 +1718,11 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
1292
1718
  i0.ɵɵadvance(2);
1293
1719
  i0.ɵɵconditional(ctx.Entity ? 6 : -1);
1294
1720
  i0.ɵɵadvance();
1295
- i0.ɵɵproperty("Provider", ctx.Provider)("Entity", ctx.Entity)("ViewEntity", ctx.currentViewEntity)("IsOpen", ctx.isConfigPanelOpen)("CurrentGridState", ctx.currentGridState)("SampleData", ctx.loadedRecords)("ExternalFilterState", ctx.filterDialogState)("IsSaving", ctx.isSavingView)("DefaultSaveAsNew", ctx.defaultSaveAsNew)("PendingNewViewName", ctx.pendingNewViewName)("PendingNewViewDescription", ctx.pendingNewViewDescription)("PendingNewViewIsShared", ctx.pendingNewViewIsShared);
1721
+ i0.ɵɵproperty("Provider", ctx.Provider)("Entity", ctx.Entity)("ViewEntity", ctx.currentViewEntity)("IsOpen", ctx.isConfigPanelOpen)("CurrentGridState", ctx.currentGridState)("SampleData", ctx.loadedRecords)("ExternalFilterState", ctx.filterDialogState)("IsSaving", ctx.isSavingView)("DefaultSaveAsNew", ctx.defaultSaveAsNew)("PendingNewViewName", ctx.pendingNewViewName)("PendingNewViewDescription", ctx.pendingNewViewDescription)("PendingNewViewIsShared", ctx.pendingNewViewIsShared)("PendingNewViewFilterState", ctx.PendingNewViewFilterState)("PendingNewViewSmartFilterEnabled", ctx.PendingNewViewSmartFilterEnabled)("PendingNewViewSmartFilterPrompt", ctx.PendingNewViewSmartFilterPrompt);
1296
1722
  i0.ɵɵadvance();
1297
1723
  i0.ɵɵconditional(ctx.isFilterDialogOpen ? 8 : -1);
1298
1724
  i0.ɵɵadvance();
1299
- i0.ɵɵproperty("IsOpen", ctx.showQuickSaveDialog)("ViewEntity", ctx.currentViewEntity)("EntityName", (ctx.Entity == null ? null : ctx.Entity.DisplayNameOrName) ?? "")("Summary", ctx.quickSaveSummary)("IsSaving", ctx.isSavingView)("DefaultSaveAsNew", ctx.defaultSaveAsNew);
1725
+ i0.ɵɵproperty("IsOpen", ctx.showQuickSaveDialog)("ViewEntity", ctx.currentViewEntity)("EntityName", (ctx.Entity == null ? null : ctx.Entity.DisplayNameOrName) ?? "")("Summary", ctx.quickSaveSummary)("IsSaving", ctx.isSavingView)("DefaultSaveAsNew", ctx.defaultSaveAsNew)("SuggestedName", ctx.QuickSaveSuggestedName);
1300
1726
  i0.ɵɵadvance();
1301
1727
  i0.ɵɵproperty("IsOpen", ctx.showDuplicateDialog)("SourceViewName", ctx.duplicateSourceViewName)("Summary", ctx.duplicateSummary);
1302
1728
  i0.ɵɵadvance();
@@ -1307,7 +1733,7 @@ export class ViewWorkspaceComponent extends BaseAngularComponent {
1307
1733
  }
1308
1734
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ViewWorkspaceComponent, [{
1309
1735
  type: Component,
1310
- args: [{ standalone: false, selector: 'mj-view-workspace', template: "<div class=\"view-workspace\">\n <!-- Toolbar: view selector + view-type switcher (single row) -->\n <div class=\"workspace-toolbar\">\n <mj-view-selector\n [Provider]=\"Provider\"\n [Entity]=\"Entity\"\n [SelectedViewID]=\"selectedViewId\"\n [ViewModified]=\"viewModified\"\n (ViewSelected)=\"onViewSelected($event)\"\n (SaveViewRequested)=\"onSaveViewRequested($event)\"\n (OpenInTabRequested)=\"onOpenInTabRequested($event)\"\n (ConfigureViewRequested)=\"onConfigureViewRequested()\"\n (CreateNewRecordRequested)=\"onCreateNewRecordRequested()\"\n (ExportRequested)=\"onExportRequested()\"\n (DuplicateViewRequested)=\"onDuplicateViewRequested($event)\"\n (QuickSaveRequested)=\"onQuickSaveRequested($event)\"\n (RevertRequested)=\"onRevertView()\">\n </mj-view-selector>\n\n <span class=\"workspace-toolbar-spacer\"></span>\n\n <!-- View-type switcher lifted into the top toolbar (same row as the selector). The inner\n entity-viewer's own header switcher is suppressed via innerViewerConfig so it appears\n exactly once. Switching routes to the viewer via SelectViewTypeById. -->\n <mj-view-type-switcher\n [Provider]=\"Provider\"\n [Entity]=\"Entity\"\n [ActiveViewTypeID]=\"activeViewTypeId\"\n (ViewTypeSelected)=\"onToolbarViewTypeSelected($event)\">\n </mj-view-type-switcher>\n </div>\n\n <!-- Data renderer -->\n <div class=\"workspace-body\">\n @if (Entity) {\n <mj-entity-viewer\n [Provider]=\"Provider\"\n [Entity]=\"Entity\"\n [ViewEntity]=\"currentViewEntity\"\n [FilterText]=\"filterText\"\n [SelectedRecordID]=\"selectedRecordId\"\n [GridState]=\"currentGridState\"\n [AutoSaveView]=\"AutoSaveView\"\n [Config]=\"innerViewerConfig\"\n (FilterTextChange)=\"onFilterTextChanged($event)\"\n (RecordSelected)=\"onRecordSelected($event)\"\n (RecordOpened)=\"onRecordOpened($event)\"\n (DataLoaded)=\"onDataLoaded($event)\"\n (FilteredCountChanged)=\"onFilteredCountChanged($event)\"\n (OpenRelatedRecordRequested)=\"onOpenRelatedRecordRequested($event)\"\n (CreateRecordRequested)=\"onCreateRecordRequested()\"\n (ConfigureRequested)=\"onConfigureViewRequested()\">\n </mj-entity-viewer>\n }\n </div>\n\n <!-- View configuration panel (slide-in) -->\n <mj-view-config-panel\n [Provider]=\"Provider\"\n [Entity]=\"Entity\"\n [ViewEntity]=\"currentViewEntity\"\n [IsOpen]=\"isConfigPanelOpen\"\n [CurrentGridState]=\"currentGridState\"\n [SampleData]=\"loadedRecords\"\n [ExternalFilterState]=\"filterDialogState\"\n [IsSaving]=\"isSavingView\"\n [DefaultSaveAsNew]=\"defaultSaveAsNew\"\n [PendingNewViewName]=\"pendingNewViewName\"\n [PendingNewViewDescription]=\"pendingNewViewDescription\"\n [PendingNewViewIsShared]=\"pendingNewViewIsShared\"\n (Close)=\"onCloseConfigPanel()\"\n (Save)=\"onSaveView($event)\"\n (SaveDefaults)=\"onSaveDefaultViewSettings($event)\"\n (Delete)=\"onDeleteView()\"\n (Duplicate)=\"onDuplicateFromPanel()\"\n (OpenFilterDialogRequest)=\"onOpenFilterDialogRequest($event)\">\n </mj-view-config-panel>\n\n <!-- Full-width filter dialog -->\n @if (isFilterDialogOpen) {\n <div class=\"filter-dialog-backdrop\" (click)=\"onCloseFilterDialog()\"></div>\n <div class=\"filter-dialog\" role=\"dialog\" aria-modal=\"true\">\n <div class=\"filter-dialog-header\">\n <div class=\"filter-dialog-title\">\n <i class=\"fa-solid fa-filter\"></i>\n <span>Edit Filters</span>\n </div>\n <button class=\"filter-dialog-close\" type=\"button\" (click)=\"onCloseFilterDialog()\" title=\"Close\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n <div class=\"filter-dialog-content\">\n <mj-filter-builder\n [fields]=\"filterDialogFields\"\n [filter]=\"filterDialogState\"\n [showSummary]=\"true\"\n (apply)=\"onFilterApplied($event)\"\n (filterChange)=\"filterDialogState = $event\">\n </mj-filter-builder>\n </div>\n <div class=\"filter-dialog-footer\">\n <button mjButton variant=\"primary\" size=\"sm\" type=\"button\"\n (click)=\"filterDialogState && onFilterApplied(filterDialogState)\">\n <i class=\"fa-solid fa-check\"></i> Apply Filters\n </button>\n <button mjButton variant=\"flat\" size=\"sm\" type=\"button\" (click)=\"onCloseFilterDialog()\">\n Cancel\n </button>\n </div>\n </div>\n }\n\n <!-- Quick save dialog -->\n <mj-quick-save-dialog\n [IsOpen]=\"showQuickSaveDialog\"\n [ViewEntity]=\"currentViewEntity\"\n [EntityName]=\"Entity?.DisplayNameOrName ?? ''\"\n [Summary]=\"quickSaveSummary\"\n [IsSaving]=\"isSavingView\"\n [DefaultSaveAsNew]=\"defaultSaveAsNew\"\n (Save)=\"onQuickSave($event)\"\n (Close)=\"onQuickSaveClose()\"\n (OpenAdvanced)=\"onQuickSaveOpenAdvanced($event)\">\n </mj-quick-save-dialog>\n\n <!-- Duplicate view dialog -->\n <mj-duplicate-view-dialog\n [IsOpen]=\"showDuplicateDialog\"\n [SourceViewName]=\"duplicateSourceViewName\"\n [Summary]=\"duplicateSummary\"\n (Duplicate)=\"onDuplicateConfirmed($event)\"\n (Cancel)=\"onDuplicateCancel()\">\n </mj-duplicate-view-dialog>\n\n <!-- Shared view warning dialog -->\n <mj-shared-view-warning-dialog\n [IsOpen]=\"showSharedViewWarning\"\n [ViewName]=\"currentViewEntity?.Name ?? ''\"\n (Action)=\"onSharedViewAction($event)\"\n (Cancel)=\"onSharedViewWarningCancel()\">\n </mj-shared-view-warning-dialog>\n\n <!-- Export dialog \u2014 opened by the toolbar Export button for ANY view type. The dialog is generic\n (data + columns only), so Excel/CSV/JSON + sampling work identically on Grid/Cards/Map/Timeline. -->\n <mj-export-dialog\n [visible]=\"showExportDialog\"\n [config]=\"exportDialogConfig\"\n (closed)=\"onExportDialogClosed($event)\">\n </mj-export-dialog>\n</div>\n", styles: [":host {\n display: flex; /* keep block if you prefer; width:100% still works */\n flex: 1 1 auto; /* grow to fill the new flex row in .content-body */\n width: 100%;\n height: 100%;\n min-width: 0; /* lets it shrink too, prevents overflow when narrow */\n}\n\n.view-workspace {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n flex: 1 1 auto; /* add: grow to fill the host's flex row */\n width: 100%; /* add */\n min-width: 0; /* add: mirror min-height so it can shrink too */\n background: var(--mj-bg-page);\n}\n\n/* Toolbar row: view selector + view-type switcher */\n.workspace-toolbar {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n padding: 10px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n flex-shrink: 0;\n}\n\n/* Pushes the view-type switcher to the right end of the toolbar row */\n.workspace-toolbar-spacer {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n/* Data renderer fills remaining space */\n.workspace-body {\n flex: 1 1 auto;\n min-height: 0;\n position: relative;\n overflow: hidden;\n}\n\n.workspace-body mj-entity-viewer {\n display: block;\n height: 100%;\n}\n\n/* ============================\n Full-width filter dialog\n ============================ */\n.filter-dialog-backdrop {\n position: fixed;\n inset: 0;\n background: var(--mj-bg-overlay);\n z-index: 2000;\n animation: vw-fade-in 0.15s ease;\n}\n\n.filter-dialog {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 90%;\n max-width: 800px;\n max-height: 85vh;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n box-shadow: 0 20px 60px color-mix(in srgb, var(--mj-text-primary) 25%, transparent);\n z-index: 2001;\n animation: vw-slide-in 0.2s ease;\n}\n\n.filter-dialog-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.filter-dialog-title {\n display: flex;\n align-items: center;\n gap: 10px;\n font-size: 18px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.filter-dialog-title i {\n color: var(--mj-brand-primary);\n}\n\n.filter-dialog-close {\n width: 36px;\n height: 36px;\n border: none;\n background: transparent;\n border-radius: 6px;\n cursor: pointer;\n color: var(--mj-text-muted);\n font-size: 16px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.15s ease, color 0.15s ease;\n}\n\n.filter-dialog-close:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n.filter-dialog-content {\n flex: 1;\n overflow-y: auto;\n padding: 20px;\n min-height: 200px;\n}\n\n.filter-dialog-footer {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 16px 20px;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n border-radius: 0 0 12px 12px;\n flex-shrink: 0;\n}\n\n@keyframes vw-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@keyframes vw-slide-in {\n from {\n opacity: 0;\n transform: translate(-50%, -48%);\n }\n to {\n opacity: 1;\n transform: translate(-50%, -50%);\n }\n}\n\n@media (max-width: 600px) {\n .filter-dialog {\n width: 95%;\n max-height: 90vh;\n border-radius: 8px;\n }\n\n .filter-dialog-footer {\n flex-direction: column;\n align-items: stretch;\n }\n}\n"] }]
1736
+ args: [{ standalone: false, selector: 'mj-view-workspace', template: "<div class=\"view-workspace\">\n <!-- Toolbar: view selector + view-type switcher (single row) -->\n <div class=\"workspace-toolbar\">\n <mj-view-selector\n [Provider]=\"Provider\"\n [Entity]=\"Entity\"\n [SelectedViewID]=\"selectedViewId\"\n [ViewModified]=\"viewModified\"\n (ViewSelected)=\"onViewSelected($event)\"\n (SaveViewRequested)=\"onSaveViewRequested($event)\"\n (OpenInTabRequested)=\"onOpenInTabRequested($event)\"\n (ConfigureViewRequested)=\"onConfigureViewRequested()\"\n (CreateNewRecordRequested)=\"onCreateNewRecordRequested()\"\n (ExportRequested)=\"onExportRequested()\"\n (DuplicateViewRequested)=\"onDuplicateViewRequested($event)\"\n (QuickSaveRequested)=\"onQuickSaveRequested($event)\"\n (RevertRequested)=\"onRevertView()\">\n </mj-view-selector>\n\n <span class=\"workspace-toolbar-spacer\"></span>\n\n <!-- View-type switcher lifted into the top toolbar (same row as the selector). The inner\n entity-viewer's own header switcher is suppressed via innerViewerConfig so it appears\n exactly once. Switching routes to the viewer via SelectViewTypeById. -->\n <mj-view-type-switcher\n [Provider]=\"Provider\"\n [Entity]=\"Entity\"\n [ActiveViewTypeID]=\"activeViewTypeId\"\n (ViewTypeSelected)=\"onToolbarViewTypeSelected($event)\">\n </mj-view-type-switcher>\n </div>\n\n <!-- Data renderer -->\n <div class=\"workspace-body\">\n @if (Entity) {\n <mj-entity-viewer\n [Provider]=\"Provider\"\n [Entity]=\"Entity\"\n [ViewEntity]=\"currentViewEntity\"\n [FilterText]=\"filterText\"\n [SelectedRecordID]=\"selectedRecordId\"\n [GridState]=\"currentGridState\"\n [AutoSaveView]=\"AutoSaveView\"\n [Config]=\"innerViewerConfig\"\n (FilterTextChange)=\"onFilterTextChanged($event)\"\n (RecordSelected)=\"onRecordSelected($event)\"\n (RecordOpened)=\"onRecordOpened($event)\"\n (DataLoaded)=\"onDataLoaded($event)\"\n (FilteredCountChanged)=\"onFilteredCountChanged($event)\"\n (OpenRelatedRecordRequested)=\"onOpenRelatedRecordRequested($event)\"\n (CreateRecordRequested)=\"onCreateRecordRequested()\"\n (ConfigureRequested)=\"onConfigureViewRequested()\">\n </mj-entity-viewer>\n }\n </div>\n\n <!-- View configuration panel (slide-in) -->\n <mj-view-config-panel\n [Provider]=\"Provider\"\n [Entity]=\"Entity\"\n [ViewEntity]=\"currentViewEntity\"\n [IsOpen]=\"isConfigPanelOpen\"\n [CurrentGridState]=\"currentGridState\"\n [SampleData]=\"loadedRecords\"\n [ExternalFilterState]=\"filterDialogState\"\n [IsSaving]=\"isSavingView\"\n [DefaultSaveAsNew]=\"defaultSaveAsNew\"\n [PendingNewViewName]=\"pendingNewViewName\"\n [PendingNewViewDescription]=\"pendingNewViewDescription\"\n [PendingNewViewIsShared]=\"pendingNewViewIsShared\"\n [PendingNewViewFilterState]=\"PendingNewViewFilterState\"\n [PendingNewViewSmartFilterEnabled]=\"PendingNewViewSmartFilterEnabled\"\n [PendingNewViewSmartFilterPrompt]=\"PendingNewViewSmartFilterPrompt\"\n (Close)=\"onCloseConfigPanel()\"\n (Save)=\"onSaveView($event)\"\n (SaveDefaults)=\"onSaveDefaultViewSettings($event)\"\n (Delete)=\"onDeleteView()\"\n (Duplicate)=\"onDuplicateFromPanel()\"\n (OpenFilterDialogRequest)=\"onOpenFilterDialogRequest($event)\">\n </mj-view-config-panel>\n\n <!-- Full-width filter dialog -->\n @if (isFilterDialogOpen) {\n <div class=\"filter-dialog-backdrop\" (click)=\"onCloseFilterDialog()\"></div>\n <div class=\"filter-dialog\" role=\"dialog\" aria-modal=\"true\">\n <div class=\"filter-dialog-header\">\n <div class=\"filter-dialog-title\">\n <i class=\"fa-solid fa-filter\"></i>\n <span>Edit Filters</span>\n </div>\n <button class=\"filter-dialog-close\" type=\"button\" (click)=\"onCloseFilterDialog()\" title=\"Close\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n <div class=\"filter-dialog-content\">\n <mj-filter-builder\n [fields]=\"filterDialogFields\"\n [filter]=\"filterDialogState\"\n [showSummary]=\"true\"\n (apply)=\"onFilterApplied($event)\"\n (filterChange)=\"filterDialogState = $event\">\n </mj-filter-builder>\n </div>\n <div class=\"filter-dialog-footer\">\n <button mjButton variant=\"primary\" size=\"sm\" type=\"button\"\n (click)=\"filterDialogState && onFilterApplied(filterDialogState)\">\n <i class=\"fa-solid fa-check\"></i> Apply Filters\n </button>\n <button mjButton variant=\"flat\" size=\"sm\" type=\"button\" (click)=\"onCloseFilterDialog()\">\n Cancel\n </button>\n </div>\n </div>\n }\n\n <!-- Quick save dialog -->\n <mj-quick-save-dialog\n [IsOpen]=\"showQuickSaveDialog\"\n [ViewEntity]=\"currentViewEntity\"\n [EntityName]=\"Entity?.DisplayNameOrName ?? ''\"\n [Summary]=\"quickSaveSummary\"\n [IsSaving]=\"isSavingView\"\n [DefaultSaveAsNew]=\"defaultSaveAsNew\"\n [SuggestedName]=\"QuickSaveSuggestedName\"\n (Save)=\"onQuickSave($event)\"\n (Close)=\"onQuickSaveClose()\"\n (OpenAdvanced)=\"onQuickSaveOpenAdvanced($event)\">\n </mj-quick-save-dialog>\n\n <!-- Duplicate view dialog -->\n <mj-duplicate-view-dialog\n [IsOpen]=\"showDuplicateDialog\"\n [SourceViewName]=\"duplicateSourceViewName\"\n [Summary]=\"duplicateSummary\"\n (Duplicate)=\"onDuplicateConfirmed($event)\"\n (Cancel)=\"onDuplicateCancel()\">\n </mj-duplicate-view-dialog>\n\n <!-- Shared view warning dialog -->\n <mj-shared-view-warning-dialog\n [IsOpen]=\"showSharedViewWarning\"\n [ViewName]=\"currentViewEntity?.Name ?? ''\"\n (Action)=\"onSharedViewAction($event)\"\n (Cancel)=\"onSharedViewWarningCancel()\">\n </mj-shared-view-warning-dialog>\n\n <!-- Export dialog \u2014 opened by the toolbar Export button for ANY view type. The dialog is generic\n (data + columns only), so Excel/CSV/JSON + sampling work identically on Grid/Cards/Map/Timeline. -->\n <mj-export-dialog\n [visible]=\"showExportDialog\"\n [config]=\"exportDialogConfig\"\n (closed)=\"onExportDialogClosed($event)\">\n </mj-export-dialog>\n</div>\n", styles: [":host {\n display: flex; /* keep block if you prefer; width:100% still works */\n flex: 1 1 auto; /* grow to fill the new flex row in .content-body */\n width: 100%;\n height: 100%;\n min-width: 0; /* lets it shrink too, prevents overflow when narrow */\n}\n\n.view-workspace {\n display: flex;\n flex-direction: column;\n height: 100%;\n min-height: 0;\n flex: 1 1 auto; /* add: grow to fill the host's flex row */\n width: 100%; /* add */\n min-width: 0; /* add: mirror min-height so it can shrink too */\n background: var(--mj-bg-page);\n}\n\n/* Toolbar row: view selector + view-type switcher */\n.workspace-toolbar {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n padding: 10px 16px;\n border-bottom: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface);\n flex-shrink: 0;\n}\n\n/* Pushes the view-type switcher to the right end of the toolbar row */\n.workspace-toolbar-spacer {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n/* Data renderer fills remaining space */\n.workspace-body {\n flex: 1 1 auto;\n min-height: 0;\n position: relative;\n overflow: hidden;\n}\n\n.workspace-body mj-entity-viewer {\n display: block;\n height: 100%;\n}\n\n/* ============================\n Full-width filter dialog\n ============================ */\n.filter-dialog-backdrop {\n position: fixed;\n inset: 0;\n background: var(--mj-bg-overlay);\n z-index: 2000;\n animation: vw-fade-in 0.15s ease;\n}\n\n.filter-dialog {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 90%;\n max-width: 800px;\n max-height: 85vh;\n display: flex;\n flex-direction: column;\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default);\n border-radius: 12px;\n box-shadow: 0 20px 60px color-mix(in srgb, var(--mj-text-primary) 25%, transparent);\n z-index: 2001;\n animation: vw-slide-in 0.2s ease;\n}\n\n.filter-dialog-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px 20px;\n border-bottom: 1px solid var(--mj-border-default);\n flex-shrink: 0;\n}\n\n.filter-dialog-title {\n display: flex;\n align-items: center;\n gap: 10px;\n font-size: 18px;\n font-weight: 600;\n color: var(--mj-text-primary);\n}\n\n.filter-dialog-title i {\n color: var(--mj-brand-primary);\n}\n\n.filter-dialog-close {\n width: 36px;\n height: 36px;\n border: none;\n background: transparent;\n border-radius: 6px;\n cursor: pointer;\n color: var(--mj-text-muted);\n font-size: 16px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background 0.15s ease, color 0.15s ease;\n}\n\n.filter-dialog-close:hover {\n background: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\n\n.filter-dialog-content {\n flex: 1;\n overflow-y: auto;\n padding: 20px;\n min-height: 200px;\n}\n\n.filter-dialog-footer {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 16px 20px;\n border-top: 1px solid var(--mj-border-default);\n background: var(--mj-bg-surface-card);\n border-radius: 0 0 12px 12px;\n flex-shrink: 0;\n}\n\n@keyframes vw-fade-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n@keyframes vw-slide-in {\n from {\n opacity: 0;\n transform: translate(-50%, -48%);\n }\n to {\n opacity: 1;\n transform: translate(-50%, -50%);\n }\n}\n\n@media (max-width: 600px) {\n .filter-dialog {\n width: 95%;\n max-height: 90vh;\n border-radius: 8px;\n }\n\n .filter-dialog-footer {\n flex-direction: column;\n align-items: stretch;\n }\n}\n"] }]
1311
1737
  }], () => [{ type: i0.ChangeDetectorRef }], { Entity: [{
1312
1738
  type: Input
1313
1739
  }], EntityName: [{