@aquera/ngx-smart-table 0.0.15-alpha → 0.0.17-alpha

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.
@@ -3523,6 +3523,72 @@ class NileInputEditor {
3523
3523
  * Custom editor using NileSelect from @aquera/nile-elements
3524
3524
  * This demonstrates how to create dropdown/select editors for ngx-smart-table
3525
3525
  */
3526
+ /**
3527
+ * Inject global styles for nile-select dropdown height limit
3528
+ */
3529
+ let dropdownStylesInjected = false;
3530
+ function injectDropdownStyles() {
3531
+ if (dropdownStylesInjected)
3532
+ return;
3533
+ dropdownStylesInjected = true;
3534
+ const styleId = 'nile-select-dropdown-styles';
3535
+ if (document.getElementById(styleId))
3536
+ return;
3537
+ const style = document.createElement('style');
3538
+ style.id = styleId;
3539
+ style.textContent = `
3540
+ /* Limit nile-select dropdown height and enable scrolling */
3541
+ .nile-select-portal-append {
3542
+ max-height: 300px !important;
3543
+ }
3544
+ .nile-select-portal-append .select__listbox {
3545
+ min-width: 220px !important;
3546
+ max-height: 260px !important;
3547
+ overflow-y: auto !important;
3548
+ width: auto !important;
3549
+ }
3550
+ .nile-select-portal-append .select__footer {
3551
+ height: 35px !important;
3552
+ }
3553
+ /* Prevent option text truncation - target the options container */
3554
+ .nile-select-portal-append .select__options {
3555
+ width: auto !important;
3556
+ }
3557
+ .nile-select-portal-append nile-option {
3558
+ width: auto !important;
3559
+ max-width: none !important;
3560
+ }
3561
+ .nile-select-portal-append nile-option::part(base) {
3562
+ white-space: nowrap !important;
3563
+ text-overflow: clip !important;
3564
+ overflow: visible !important;
3565
+ max-width: none !important;
3566
+ }
3567
+ /* Fix option text truncation by changing flex direction */
3568
+ .nile-select-portal-append nile-option::part(option_label_container) {
3569
+ flex-direction: row !important;
3570
+ }
3571
+ /* Combobox styling using ::part() selector */
3572
+ nile-select.st-cell-editor::part(combobox) {
3573
+ background-color: var(--nile-colors-white-base, var(--ng-colors-bg-primary)) !important;
3574
+ border: solid 1px transparent !important;
3575
+ margin: 1px 4px !important;
3576
+ }
3577
+ nile-select.st-cell-editor::part(combobox):hover {
3578
+ border: solid 1px transparent !important;
3579
+ }
3580
+ .st-cell-editor::part(combobox) {
3581
+ background-color: var(--nile-colors-white-base, var(--ng-colors-bg-primary)) !important;
3582
+ border: solid 1px transparent !important;
3583
+ margin: 1px 4px !important;
3584
+ }
3585
+ /* Search input full width */
3586
+ .nile-select-portal-append .select__search {
3587
+ width: 100% !important;
3588
+ }
3589
+ `;
3590
+ document.head.appendChild(style);
3591
+ }
3526
3592
  /**
3527
3593
  * Custom editor that uses NileSelect component
3528
3594
  * @template T The value type (string for single selection, string[] for multiple)
@@ -3533,9 +3599,7 @@ class NileSelectEditor {
3533
3599
  this.acceptsInitialKeypress = false;
3534
3600
  this.eventListeners = [];
3535
3601
  this.currentOptions = [];
3536
- this.isInitializing = false; // Flag to prevent immediate close during initialization
3537
- this.hasSaved = false; // Flag to prevent double-save
3538
- this.lastSelectedValue = ''; // Track the last selected value
3602
+ this.trackedValues = []; // Track selected values for virtual scroll
3539
3603
  // Handle Observable options
3540
3604
  if (isObservable(options.options)) {
3541
3605
  this.optionsSubscription = options.options.subscribe(opts => {
@@ -3554,101 +3618,20 @@ class NileSelectEditor {
3554
3618
  }
3555
3619
  }
3556
3620
  }
3557
- /**
3558
- * Inject global styles to remove border from nile-select combobox
3559
- * Uses ::part selector which works across shadow DOM boundaries
3560
- */
3561
- injectBorderlessStyles() {
3562
- if (NileSelectEditor.stylesInjected)
3563
- return;
3564
- const styleId = 'nile-select-borderless-styles';
3565
- if (document.getElementById(styleId)) {
3566
- NileSelectEditor.stylesInjected = true;
3567
- return;
3568
- }
3569
- const style = document.createElement('style');
3570
- style.id = styleId;
3571
- style.textContent = `
3572
- .st-cell-editor::part(combobox) {
3573
- border: none !important;
3574
- outline: none !important;
3575
- box-shadow: none !important;
3576
- min-height: unset !important;
3577
- height: 100% !important;
3578
- padding: 0 !important;
3579
- }
3580
- .st-cell-editor::part(combobox):hover {
3581
- border: none !important;
3582
- outline: none !important;
3583
- box-shadow: none !important;
3584
- }
3585
- .st-cell-editor::part(combobox):focus {
3586
- border: none !important;
3587
- outline: none !important;
3588
- box-shadow: none !important;
3589
- }
3590
- // .st-cell-editor::part(tag) {
3591
- // margin: 1px 2px !important;
3592
- // border-radius: 3px !important;
3593
- // background-color: #e2e8f0 !important;
3594
- // font-size: 12px !important;
3595
- // height: auto !important;
3596
- // }
3597
- // .st-cell-editor::part(tag__base) {
3598
- // border: none !important;
3599
- // outline: none !important;
3600
- // padding: 0 2px !important;
3601
- // height: auto !important;
3602
- // }
3603
- // .st-cell-editor::part(tag__content) {
3604
- // padding: 0 !important;
3605
- // }
3606
- .st-cell-editor::part(tags) {
3607
- gap: 2px !important;
3608
- align-items: center !important;
3609
- }
3610
- .st-cell-editor::part(tags-count) {
3611
- margin: 0 !important;
3612
- }
3613
- .st-cell-editor::part(footer) {
3614
- height: 35px !important;
3615
- min-height: 35px !important;
3616
- padding: 8px !important;
3617
- padding-bottom: 30px !important;
3618
- box-sizing: border-box !important;
3619
- }
3620
- .st-cell-editor::part(listbox) {
3621
- margin-bottom: 0 !important;
3622
- padding-bottom: 0 !important;
3623
- }
3624
- `;
3625
- document.head.appendChild(style);
3626
- NileSelectEditor.stylesInjected = true;
3627
- }
3628
3621
  edit(context) {
3629
3622
  if (!context.container) {
3630
3623
  console.warn('NileSelectEditor requires a container element');
3631
3624
  return;
3632
3625
  }
3633
- // Set initializing flag to prevent immediate close
3634
- this.isInitializing = true;
3635
- this.hasSaved = false;
3636
- this.lastSelectedValue = '';
3626
+ // Inject dropdown height styles once
3627
+ injectDropdownStyles();
3637
3628
  // Create NileSelect custom element using document.createElement
3638
3629
  this.select = document.createElement('nile-select');
3639
3630
  this.select.className = 'st-cell-editor';
3640
- // Apply inline styles for proper cell fitting - prevent height changes
3641
- this.select.style.width = 'calc(100% - 4px)';
3642
- this.select.style.height = '100%';
3643
- this.select.style.maxHeight = '100%';
3631
+ // Apply inline styles for proper cell fitting
3632
+ this.select.style.width = '100%';
3633
+ this.select.style.height = 'inherit';
3644
3634
  this.select.style.boxSizing = 'border-box';
3645
- this.select.style.margin = '0 2px';
3646
- this.select.style.overflow = 'hidden';
3647
- // Inject styles to remove border using ::part selector
3648
- this.injectBorderlessStyles();
3649
- // Also try removing outline on host element
3650
- this.select.style.outline = 'none';
3651
- this.select.style.border = 'none';
3652
3635
  // Set initial value
3653
3636
  this.setInitialValue(context.value);
3654
3637
  // Apply all configuration options
@@ -3660,23 +3643,16 @@ class NileSelectEditor {
3660
3643
  // Clear container and append select
3661
3644
  context.container.innerHTML = '';
3662
3645
  context.container.appendChild(this.select);
3663
- // Focus and auto-open after render
3646
+ // Focus the select after render
3664
3647
  setTimeout(() => {
3665
3648
  var _a;
3666
- (_a = this.select) === null || _a === void 0 ? void 0 : _a.focus();
3667
- // Auto-open the dropdown
3668
- if (this.select) {
3669
- this.select.open = true;
3649
+ try {
3650
+ (_a = this.select) === null || _a === void 0 ? void 0 : _a.focus();
3670
3651
  }
3671
- // Clear initializing flag after dropdown has opened
3672
- setTimeout(() => {
3673
- this.isInitializing = false;
3674
- // For multi-select, find the portal div for click detection
3675
- if (this.options.multiple) {
3676
- this.portalDiv = document.querySelector('.nile-select-portal-append');
3677
- }
3678
- }, 100);
3679
- }, 0);
3652
+ catch (e) {
3653
+ // Ignore errors
3654
+ }
3655
+ }, 50);
3680
3656
  }
3681
3657
  /**
3682
3658
  * Set the initial value for the select
@@ -3688,17 +3664,21 @@ class NileSelectEditor {
3688
3664
  // Handle multiple selection - value should be array
3689
3665
  if (Array.isArray(value)) {
3690
3666
  this.select.value = value;
3667
+ this.trackedValues = [...value]; // Initialize tracked values
3691
3668
  }
3692
3669
  else if (value) {
3693
3670
  this.select.value = [String(value)];
3671
+ this.trackedValues = [String(value)];
3694
3672
  }
3695
3673
  else {
3696
3674
  this.select.value = [];
3675
+ this.trackedValues = [];
3697
3676
  }
3698
3677
  }
3699
3678
  else {
3700
3679
  // Handle single selection
3701
3680
  this.select.value = value ? String(value) : '';
3681
+ this.trackedValues = value ? [String(value)] : [];
3702
3682
  }
3703
3683
  }
3704
3684
  /**
@@ -3759,13 +3739,9 @@ class NileSelectEditor {
3759
3739
  if (this.options.pill) {
3760
3740
  this.select.pill = this.options.pill;
3761
3741
  }
3762
- if (this.options.hoist !== undefined) {
3742
+ if (this.options.hoist) {
3763
3743
  this.select.hoist = this.options.hoist;
3764
3744
  }
3765
- else {
3766
- // Default hoist to true for all selects to prevent clipping issues in table cells
3767
- this.select.hoist = true;
3768
- }
3769
3745
  if (this.options.placement) {
3770
3746
  this.select.placement = this.options.placement;
3771
3747
  }
@@ -3775,12 +3751,8 @@ class NileSelectEditor {
3775
3751
  if (this.options.disableLocalSearch) {
3776
3752
  this.select.disableLocalSearch = this.options.disableLocalSearch;
3777
3753
  }
3778
- if (this.options.portal !== undefined) {
3779
- this.select.portal = this.options.portal;
3780
- }
3781
- else {
3782
- this.select.portal = true; // Default to portal mode for better positioning
3783
- }
3754
+ // Enable portal mode by default to prevent dropdown clipping in table cells
3755
+ this.select.portal = this.options.portal !== false;
3784
3756
  // Prevent width syncing issues in table cells
3785
3757
  this.select.noWidthSync = true;
3786
3758
  }
@@ -3790,12 +3762,17 @@ class NileSelectEditor {
3790
3762
  updateOptions() {
3791
3763
  if (!this.select)
3792
3764
  return;
3793
- // Clear existing options
3794
- while (this.select.firstChild) {
3795
- this.select.removeChild(this.select.firstChild);
3765
+ // For virtual scroll mode, just update the data property (no DOM children to clear)
3766
+ if (this.options.enableVirtualScroll) {
3767
+ this.createAndAppendOptions(this.currentOptions);
3768
+ }
3769
+ else {
3770
+ // Standard mode: clear existing nile-option elements first
3771
+ while (this.select.firstChild) {
3772
+ this.select.removeChild(this.select.firstChild);
3773
+ }
3774
+ this.createAndAppendOptions(this.currentOptions);
3796
3775
  }
3797
- // Re-append with new options
3798
- this.createAndAppendOptions(this.currentOptions);
3799
3776
  // Trigger update on the web component
3800
3777
  if (this.select.requestUpdate) {
3801
3778
  this.select.requestUpdate();
@@ -3803,11 +3780,22 @@ class NileSelectEditor {
3803
3780
  }
3804
3781
  /**
3805
3782
  * Create NileOption elements and append them to the select
3783
+ * When enableVirtualScroll is true, uses data property instead of DOM elements
3784
+ * @see https://nile.aqueralabs.com/select-virtual?theme=enterprise
3806
3785
  * @param options - The options to render
3807
3786
  */
3808
3787
  createAndAppendOptions(options) {
3809
3788
  if (!this.select)
3810
3789
  return;
3790
+ // Virtual scroll mode: use data property instead of DOM elements
3791
+ if (this.options.enableVirtualScroll) {
3792
+ const virtualData = options.map(opt => typeof opt === 'string'
3793
+ ? { value: opt, label: opt }
3794
+ : { value: opt.value, label: opt.label, disabled: opt.disabled });
3795
+ this.select.data = virtualData;
3796
+ return;
3797
+ }
3798
+ // Standard mode: create nile-option elements
3811
3799
  options.forEach(opt => {
3812
3800
  const nileOption = document.createElement('nile-option');
3813
3801
  // Auto-detect format: string or SelectOption object
@@ -3840,31 +3828,6 @@ class NileSelectEditor {
3840
3828
  if (!this.select)
3841
3829
  return;
3842
3830
  const validateOnSave = this.options.validateOnSave !== false;
3843
- // Prevent click events from bubbling up to the cell container
3844
- const selectClickHandler = (e) => {
3845
- e.stopPropagation();
3846
- };
3847
- this.select.addEventListener('click', selectClickHandler);
3848
- this.eventListeners.push({ event: 'click', handler: selectClickHandler });
3849
- // For single-select with portal, listen to document mousedown to detect option selection
3850
- // Use mousedown because it fires before blur
3851
- if (!this.options.multiple) {
3852
- const documentMouseDownHandler = (e) => {
3853
- var _a, _b;
3854
- const composedPath = ((_a = e.composedPath) === null || _a === void 0 ? void 0 : _a.call(e)) || [];
3855
- // Look for nile-option in the event path
3856
- for (const el of composedPath) {
3857
- if (el instanceof HTMLElement && ((_b = el.tagName) === null || _b === void 0 ? void 0 : _b.toLowerCase()) === 'nile-option') {
3858
- const optionValue = el.getAttribute('value') || el.value || '';
3859
- this.lastSelectedValue = optionValue;
3860
- return;
3861
- }
3862
- }
3863
- };
3864
- // Add listener immediately (mousedown fires before blur, so no need for delay)
3865
- document.addEventListener('mousedown', documentMouseDownHandler, true); // capture phase
3866
- this.eventListeners.push({ event: 'document-mousedown', handler: documentMouseDownHandler });
3867
- }
3868
3831
  // Handle keyboard events (keydown is not replaced by custom events)
3869
3832
  const keydownHandler = (e) => {
3870
3833
  const keyEvent = e;
@@ -3876,46 +3839,102 @@ class NileSelectEditor {
3876
3839
  };
3877
3840
  this.select.addEventListener('keydown', keydownHandler);
3878
3841
  this.eventListeners.push({ event: 'keydown', handler: keydownHandler });
3879
- // Use nile-blur event as save trigger for single-select
3880
- // Add a small delay to allow the value to update before reading
3881
- const singleSelectSaveHandler = (e) => {
3882
- // Don't save again if already saved
3883
- // Ignore events that fire during initialization (e.g. when setting initial value)
3884
- if (this.hasSaved || this.isInitializing) {
3885
- return;
3886
- }
3887
- // Only save for single-select
3888
- if (!this.options.multiple) {
3889
- // Add a small delay to let the value update after selection
3890
- setTimeout(() => {
3891
- var _a, _b, _c;
3892
- // Check again after timeout in case saved by another handler
3893
- if (this.hasSaved) {
3842
+ // Use nile-change custom event as save trigger - ONLY for single-select
3843
+ // Multi-select should stay open until user clicks outside
3844
+ if (!this.options.multiple) {
3845
+ const changeHandler = (e) => {
3846
+ const customEvent = e;
3847
+ try {
3848
+ if (validateOnSave && this.select && !this.select.checkValidity()) {
3849
+ this.select.reportValidity();
3894
3850
  return;
3895
3851
  }
3896
- if (validateOnSave && !((_a = this.select) === null || _a === void 0 ? void 0 : _a.checkValidity())) {
3897
- (_b = this.select) === null || _b === void 0 ? void 0 : _b.reportValidity();
3898
- return;
3852
+ }
3853
+ catch (err) {
3854
+ // Ignore validation errors - nile-select internals may not be ready
3855
+ }
3856
+ context.onSave(this.parseValue(customEvent.detail.value));
3857
+ };
3858
+ this.select.addEventListener('nile-change', changeHandler);
3859
+ this.eventListeners.push({ event: 'nile-change', handler: changeHandler });
3860
+ }
3861
+ // For multi-select, track value changes (needed for virtual scroll where value property doesn't update)
3862
+ if (this.options.multiple) {
3863
+ const trackValueHandler = (e) => {
3864
+ var _a;
3865
+ const customEvent = e;
3866
+ const newValue = (_a = customEvent.detail) === null || _a === void 0 ? void 0 : _a.value;
3867
+ if (newValue !== undefined) {
3868
+ if (Array.isArray(newValue)) {
3869
+ this.trackedValues = [...newValue];
3870
+ }
3871
+ else {
3872
+ this.trackedValues = newValue ? [newValue] : [];
3899
3873
  }
3900
- // Use lastSelectedValue if available, otherwise get from element
3901
- const target = e.target;
3902
- const value = this.lastSelectedValue || (target === null || target === void 0 ? void 0 : target.value) || ((_c = this.select) === null || _c === void 0 ? void 0 : _c.value) || '';
3903
- this.hasSaved = true;
3904
- context.onSave(this.parseValue(value));
3905
- }, 50); // 50ms delay to allow value to update
3874
+ }
3875
+ };
3876
+ this.select.addEventListener('nile-change', trackValueHandler);
3877
+ this.eventListeners.push({ event: 'nile-change', handler: trackValueHandler });
3878
+ }
3879
+ // Track if save was already triggered (to prevent double save)
3880
+ let saveTriggered = false;
3881
+ // Use mousedown on document to detect clicks outside the select and portal
3882
+ const documentMousedownHandler = (e) => {
3883
+ var _a, _b, _c, _d, _e;
3884
+ if (saveTriggered)
3885
+ return;
3886
+ if (!this.select)
3887
+ return;
3888
+ // Use composedPath to traverse shadow DOM boundaries
3889
+ const path = e.composedPath();
3890
+ // Check if click is inside the select element or any nile-select related elements
3891
+ for (const element of path) {
3892
+ if (!(element instanceof HTMLElement))
3893
+ continue;
3894
+ // Check if it's our select element
3895
+ if (element === this.select) {
3896
+ return;
3897
+ }
3898
+ // Check for nile-select portals (various class names)
3899
+ if (((_a = element.classList) === null || _a === void 0 ? void 0 : _a.contains('nile-select-portal-append')) ||
3900
+ ((_b = element.classList) === null || _b === void 0 ? void 0 : _b.contains('select__listbox')) ||
3901
+ ((_c = element.classList) === null || _c === void 0 ? void 0 : _c.contains('select__options')) ||
3902
+ ((_d = element.tagName) === null || _d === void 0 ? void 0 : _d.toLowerCase()) === 'nile-option' ||
3903
+ ((_e = element.tagName) === null || _e === void 0 ? void 0 : _e.toLowerCase()) === 'nile-select') {
3904
+ return;
3905
+ }
3906
+ // Check for any element with nile-select in its class
3907
+ if (element.className && typeof element.className === 'string' &&
3908
+ element.className.includes('nile-select')) {
3909
+ return;
3910
+ }
3911
+ }
3912
+ // Also check portal containers directly
3913
+ const portals = document.querySelectorAll('.nile-select-portal-append, [class*="select__listbox"]');
3914
+ for (let i = 0; i < portals.length; i++) {
3915
+ if (portals[i].contains(e.target)) {
3916
+ return;
3917
+ }
3918
+ }
3919
+ // Click is outside - save and exit
3920
+ saveTriggered = true;
3921
+ try {
3922
+ if (validateOnSave && this.select && !this.select.checkValidity()) {
3923
+ this.select.reportValidity();
3924
+ return;
3925
+ }
3926
+ }
3927
+ catch (err) {
3928
+ // Ignore validation errors - nile-select internals may not be ready
3906
3929
  }
3930
+ context.onSave(this.getCurrentValue());
3907
3931
  };
3908
- // Listen to blur and nile-blur events for focus loss detection
3909
- // Nile web components emit nile-blur custom event instead of native blur
3910
- this.select.addEventListener('blur', singleSelectSaveHandler);
3911
- this.eventListeners.push({ event: 'blur', handler: singleSelectSaveHandler });
3912
- this.select.addEventListener('nile-blur', singleSelectSaveHandler);
3913
- this.eventListeners.push({ event: 'nile-blur', handler: singleSelectSaveHandler });
3914
- // Also listen to change events as fallback
3915
- this.select.addEventListener('nile-change', singleSelectSaveHandler);
3916
- this.eventListeners.push({ event: 'nile-change', handler: singleSelectSaveHandler });
3917
- this.select.addEventListener('change', singleSelectSaveHandler);
3918
- this.eventListeners.push({ event: 'change', handler: singleSelectSaveHandler });
3932
+ // Add listener after a short delay to avoid capturing the initial click that opened the editor
3933
+ setTimeout(() => {
3934
+ document.addEventListener('mousedown', documentMousedownHandler, true);
3935
+ }, 50);
3936
+ // Store for cleanup
3937
+ this._documentMousedownHandler = documentMousedownHandler;
3919
3938
  // Handle clear button if enabled
3920
3939
  if (this.options.clearable) {
3921
3940
  const clearHandler = (e) => {
@@ -3934,44 +3953,6 @@ class NileSelectEditor {
3934
3953
  this.select.addEventListener('nile-search', searchHandler);
3935
3954
  this.eventListeners.push({ event: 'nile-search', handler: searchHandler });
3936
3955
  }
3937
- // For multi-select: detect clicks outside portal and select
3938
- if (this.options.multiple) {
3939
- const documentClickHandler = (e) => {
3940
- var _a, _b, _c, _d;
3941
- const mouseEvent = e;
3942
- const target = mouseEvent.target;
3943
- // Check if portal div exists (might take a moment to render)
3944
- if (!this.portalDiv) {
3945
- this.portalDiv = document.querySelector('.nile-select-portal-append');
3946
- }
3947
- // Determine if click is inside the select or portal
3948
- const clickedInsideSelect = (_a = this.select) === null || _a === void 0 ? void 0 : _a.contains(target);
3949
- const clickedInsidePortal = (_b = this.portalDiv) === null || _b === void 0 ? void 0 : _b.contains(target);
3950
- // If clicked outside both select and portal, save and close
3951
- if (!clickedInsideSelect && !clickedInsidePortal) {
3952
- if (validateOnSave && !((_c = this.select) === null || _c === void 0 ? void 0 : _c.checkValidity())) {
3953
- (_d = this.select) === null || _d === void 0 ? void 0 : _d.reportValidity();
3954
- return;
3955
- }
3956
- context.onSave(this.getCurrentValue());
3957
- }
3958
- // Otherwise, click is inside - do nothing (dropdown stays open)
3959
- };
3960
- // Delay adding the document click handler to prevent the initial click
3961
- // (that opened the editor) from immediately triggering it and closing the dropdown
3962
- // Use 200ms to ensure the dropdown has fully opened
3963
- setTimeout(() => {
3964
- // Only add if select still exists (editor not destroyed)
3965
- if (this.select) {
3966
- // Use bubbling phase (false) instead of capture to not interfere with nile-select's toggle
3967
- document.addEventListener('click', documentClickHandler, false);
3968
- this.eventListeners.push({
3969
- event: 'document-click',
3970
- handler: documentClickHandler
3971
- });
3972
- }
3973
- }, 200);
3974
- }
3975
3956
  }
3976
3957
  /**
3977
3958
  * Parse value based on selection mode
@@ -3991,56 +3972,54 @@ class NileSelectEditor {
3991
3972
  return value;
3992
3973
  }
3993
3974
  destroy() {
3994
- // Reset flags
3995
- this.isInitializing = false;
3996
- this.hasSaved = false;
3997
- this.lastSelectedValue = '';
3998
3975
  // Unsubscribe from options Observable
3999
3976
  if (this.optionsSubscription) {
4000
3977
  this.optionsSubscription.unsubscribe();
4001
3978
  this.optionsSubscription = undefined;
4002
3979
  }
3980
+ // Remove document mousedown handler if exists
3981
+ if (this._documentMousedownHandler) {
3982
+ document.removeEventListener('mousedown', this._documentMousedownHandler, true);
3983
+ delete this._documentMousedownHandler;
3984
+ }
4003
3985
  // Remove all event listeners
4004
3986
  if (this.select) {
4005
3987
  this.eventListeners.forEach(({ event, handler }) => {
4006
3988
  var _a;
4007
- if (event === 'document-click') {
4008
- // Remove from document for multi-select click handler (bubbling phase)
4009
- document.removeEventListener('click', handler, false);
4010
- }
4011
- else if (event === 'document-mousedown') {
4012
- // Remove from document for single-select option mousedown handler (capture phase)
4013
- document.removeEventListener('mousedown', handler, true);
4014
- }
4015
- else {
4016
- // Remove from select element
4017
- (_a = this.select) === null || _a === void 0 ? void 0 : _a.removeEventListener(event, handler);
4018
- }
3989
+ (_a = this.select) === null || _a === void 0 ? void 0 : _a.removeEventListener(event, handler);
4019
3990
  });
4020
3991
  this.eventListeners = [];
4021
3992
  this.select.remove();
4022
3993
  this.select = undefined;
4023
- this.portalDiv = undefined; // Clean up portal reference
4024
3994
  }
4025
3995
  }
4026
3996
  focus() {
4027
3997
  var _a;
4028
- (_a = this.select) === null || _a === void 0 ? void 0 : _a.focus();
3998
+ try {
3999
+ (_a = this.select) === null || _a === void 0 ? void 0 : _a.focus();
4000
+ }
4001
+ catch (e) {
4002
+ // Ignore focus errors - nile-select internals may not be ready
4003
+ }
4029
4004
  }
4030
4005
  getCurrentValue() {
4031
4006
  if (!this.select) {
4032
4007
  return (this.options.multiple ? [] : '');
4033
4008
  }
4034
- const value = this.select.value;
4035
- // Handle multiple selection
4009
+ // For multi-select, prefer tracked values (more reliable for virtual scroll)
4036
4010
  if (this.options.multiple) {
4037
- return (Array.isArray(value) ? value : [value]);
4011
+ // Use tracked values if available, otherwise try component value
4012
+ let values = this.trackedValues.length > 0 ? this.trackedValues : this.select.value;
4013
+ if (Array.isArray(values)) {
4014
+ return values.filter((v) => v !== undefined && v !== null && v !== '');
4015
+ }
4016
+ return (values ? [values] : []);
4038
4017
  }
4039
- // Handle single selection
4040
- return (Array.isArray(value) ? (value.length > 0 ? value[0] : '') : value);
4018
+ // For single-select, use tracked values or component value
4019
+ let value = this.trackedValues.length > 0 ? this.trackedValues[0] : this.select.value;
4020
+ return (Array.isArray(value) ? (value.length > 0 ? value[0] : '') : (value || ''));
4041
4021
  }
4042
4022
  }
4043
- NileSelectEditor.stylesInjected = false;
4044
4023
 
4045
4024
  /**
4046
4025
  * Custom editor using NileAutoComplete from @aquera/nile-elements
@@ -8784,6 +8763,15 @@ class StTableComponent {
8784
8763
  // Use setTimeout to ensure DOM has updated
8785
8764
  setTimeout(() => {
8786
8765
  var _a;
8766
+ // Don't steal focus from editors - if a cell is being edited,
8767
+ // the editor should keep focus
8768
+ const editingPosition = this.getActiveTableState().getEditingPosition();
8769
+ if (editingPosition &&
8770
+ editingPosition.rowIndex === rowIndex &&
8771
+ editingPosition.columnIndex === colIndex) {
8772
+ // Cell is being edited, don't steal focus from the editor
8773
+ return;
8774
+ }
8787
8775
  // Use absolute row index for virtual scroll compatibility
8788
8776
  const absoluteRowIndex = this.getAbsoluteRowIndex(rowIndex);
8789
8777
  // Query within this component's host element to avoid selecting rows from other tables
@@ -9044,11 +9032,27 @@ class StTableComponent {
9044
9032
  * Clear focus when clicking outside table
9045
9033
  */
9046
9034
  onDocumentClick(event) {
9035
+ var _a;
9047
9036
  if (!this.isKeyboardNavigationEnabled())
9048
9037
  return;
9049
9038
  const target = event.target;
9050
9039
  const tableElement = target.closest('.st-table');
9051
- if (!tableElement) {
9040
+ // Also check if click is inside a portal (e.g., nile-select dropdown)
9041
+ // These are rendered outside the table but are part of the editing experience
9042
+ const portalElement = target.closest('.nile-select-portal-append');
9043
+ // Check composedPath for shadow DOM elements (e.g., nile-option, nile-input inside shadow root)
9044
+ const composedPath = ((_a = event.composedPath) === null || _a === void 0 ? void 0 : _a.call(event)) || [];
9045
+ const isInsideNileElement = composedPath.some((el) => {
9046
+ var _a, _b;
9047
+ if (el instanceof HTMLElement) {
9048
+ const tagName = (_a = el.tagName) === null || _a === void 0 ? void 0 : _a.toLowerCase();
9049
+ // Check for any nile-* custom elements used in editors
9050
+ return (tagName === null || tagName === void 0 ? void 0 : tagName.startsWith('nile-')) ||
9051
+ ((_b = el.classList) === null || _b === void 0 ? void 0 : _b.contains('nile-select-portal-append'));
9052
+ }
9053
+ return false;
9054
+ });
9055
+ if (!tableElement && !portalElement && !isInsideNileElement) {
9052
9056
  this.getActiveTableState().clearFocus();
9053
9057
  this.getActiveTableState().clearEditingCell();
9054
9058
  }
@@ -10016,9 +10020,13 @@ class StWorkbookComponent {
10016
10020
  this.workbookActionsOpen = false;
10017
10021
  this.workbookActionsPosition = {};
10018
10022
  /**
10019
- * Visible workbook actions (filtered by hidden)
10023
+ * Visible workbook actions for dropdown (filtered by hidden and showInToolbar)
10020
10024
  */
10021
10025
  this.visibleWorkbookActions = [];
10026
+ /**
10027
+ * Workbook actions to show directly in toolbar
10028
+ */
10029
+ this.toolbarWorkbookActions = [];
10022
10030
  /**
10023
10031
  * Destroy subject for cleanup
10024
10032
  */
@@ -10259,14 +10267,18 @@ class StWorkbookComponent {
10259
10267
  updateVisibleWorkbookActions() {
10260
10268
  if (!this.config.workbookActions) {
10261
10269
  this.visibleWorkbookActions = [];
10270
+ this.toolbarWorkbookActions = [];
10262
10271
  return;
10263
10272
  }
10264
- this.visibleWorkbookActions = this.config.workbookActions.filter(action => {
10273
+ const visibleActions = this.config.workbookActions.filter(action => {
10265
10274
  if (typeof action.hidden === 'function') {
10266
10275
  return !action.hidden();
10267
10276
  }
10268
10277
  return !action.hidden;
10269
10278
  });
10279
+ // Separate toolbar actions from dropdown actions
10280
+ this.toolbarWorkbookActions = visibleActions.filter(action => action.showInToolbar === true);
10281
+ this.visibleWorkbookActions = visibleActions.filter(action => action.showInToolbar !== true);
10270
10282
  }
10271
10283
  /**
10272
10284
  * Check if workbook action is disabled
@@ -10463,10 +10475,10 @@ class StWorkbookComponent {
10463
10475
  }
10464
10476
  }
10465
10477
  StWorkbookComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: StWorkbookComponent, deps: [{ token: AutosaveService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
10466
- StWorkbookComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.4.0", type: StWorkbookComponent, selector: "st-workbook", inputs: { config: "config", sheetsData: "sheetsData", state: "state" }, outputs: { sheetChanged: "sheetChanged", addSheet: "addSheet", sheetTabAction: "sheetTabAction", workbookAction: "workbookAction", cellChange: "cellChange", cellSave: "cellSave", tableStateChange: "tableStateChange", fullscreenToggle: "fullscreenToggle", requestAddRow: "requestAddRow" }, viewQueries: [{ propertyName: "tableComponent", first: true, predicate: StTableComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"workbook-container\" [class.fullscreen]=\"isFullscreen$ | async\">\n <nile-tab-group [activeIndex]=\"activeSheetIndex\">\n \n <!-- Sheet Tabs (one per sheet) -->\n <nile-tab *ngFor=\"let sheet of sheets; let i = index\"\n slot=\"nav\" \n panel=\"shared-panel\"\n [class.active]=\"i === activeSheetIndex\"\n (click)=\"onTabChange(i)\">\n <div class=\"sheet-tab-content\">\n <span class=\"sheet-name\">{{ sheet.name }}</span>\n \n <!-- Tab actions dropdown button -->\n <button class=\"tab-actions-button\"\n (click)=\"openTabActions($event, sheet, i)\"\n *ngIf=\"hasTabActions(sheet)\">\n <nile-icon name=\"arrowdown\" size=\"14\"></nile-icon>\n </button>\n </div>\n </nile-tab>\n \n <!-- Toolbar Tab (for workbook controls) -->\n <nile-tab slot=\"nav\" \n panel=\"shared-panel\"\n class=\"workbook-toolbar-tab\"\n [disabled]=\"true\">\n <div class=\"workbook-toolbar-content\">\n <!-- Autosave Indicator -->\n <div class=\"autosave-indicator\" *ngIf=\"autosaveEnabled\">\n <nile-icon \n *ngIf=\"!isSaving && lastSaveTime\" \n name=\"save\" \n size=\"14\"\n [title]=\"'Saved at ' + (lastSaveTime | date:'HH:mm:ss')\">\n </nile-icon>\n <nile-icon \n *ngIf=\"isSaving\" \n name=\"loader\" \n size=\"14\"\n title=\"Saving...\">\n </nile-icon>\n </div>\n\n <!-- Workbook Actions Dropdown -->\n <button class=\"workbook-actions-button\"\n *ngIf=\"config.workbookActions && config.workbookActions.length > 0\"\n (click)=\"toggleWorkbookActions($event)\"\n title=\"Workbook Actions\">\n <nile-icon name=\"settings\"></nile-icon>\n </button>\n \n <!-- Add Sheet Button -->\n <button class=\"add-sheet-button\"\n *ngIf=\"canAddSheet\"\n (click)=\"onAddSheet()\"\n title=\"Add Sheet\">\n <nile-icon name=\"plus\"></nile-icon>\n </button>\n \n <!-- Fullscreen Button -->\n <button class=\"fullscreen-button\"\n *ngIf=\"config.display?.allowFullscreen !== false\"\n (click)=\"toggleFullscreen()\"\n [title]=\"(isFullscreen$ | async) ? 'Exit Fullscreen' : 'Fullscreen'\">\n <nile-icon [name]=\"(isFullscreen$ | async) ? 'collapse' : 'expand-06'\"></nile-icon>\n </button>\n </div>\n </nile-tab>\n \n <!-- Single Shared Tab Panel -->\n <nile-tab-panel name=\"shared-panel\">\n <!-- Lazy loading strategy: table is destroyed and recreated with new config/state when sheet changes -->\n <!-- Using ngFor with trackBy to force complete reinitialization when tableComponentKey changes -->\n <ng-container *ngFor=\"let key of [tableComponentKey]; trackBy: trackByKey\">\n <st-table *ngIf=\"currentTableConfig && currentTableState\"\n [attr.data-sheet-key]=\"key\"\n [tableConfig]=\"currentTableConfig\"\n [data$]=\"currentTableData$\"\n [tableState]=\"currentTableState\"\n (cellChange)=\"onCellChange($event)\"\n (cellSave)=\"onCellSave($event)\"\n (stateChange)=\"onTableStateChange($event)\"\n (requestAddRow)=\"onRequestAddRow($event)\">\n </st-table>\n </ng-container>\n </nile-tab-panel>\n \n </nile-tab-group>\n</div>\n\n<!-- Tab Actions Dropdown -->\n<div class=\"tab-actions-dropdown\" \n *ngIf=\"tabActionsOpen\"\n [ngStyle]=\"tabActionsPosition\">\n <div class=\"dropdown-backdrop\" (click)=\"closeTabActions()\"></div>\n <div class=\"dropdown-menu\">\n <nile-menu>\n <nile-menu-item *ngFor=\"let action of selectedSheet?.tabActions\" \n (click)=\"onTabActionClick(action, $event)\">\n <nile-icon *ngIf=\"action.icon\" slot=\"prefix\" size=\"14\" [name]=\"action.icon\"></nile-icon>\n {{ action.label }}\n </nile-menu-item>\n </nile-menu>\n </div>\n</div>\n\n<!-- Workbook Actions Dropdown -->\n<div class=\"workbook-actions-dropdown\"\n *ngIf=\"workbookActionsOpen\"\n [ngStyle]=\"workbookActionsPosition\">\n <div class=\"dropdown-backdrop\" (click)=\"closeWorkbookActions()\"></div>\n <div class=\"dropdown-menu\">\n <nile-menu>\n <nile-menu-item *ngFor=\"let action of visibleWorkbookActions\"\n [class.disabled]=\"isActionDisabled(action)\"\n (click)=\"onWorkbookActionClick(action, $event)\">\n <nile-icon *ngIf=\"action.icon\" slot=\"prefix\" size=\"14\" [name]=\"action.icon\"></nile-icon>\n {{ action.label }}\n </nile-menu-item>\n </nile-menu>\n </div>\n</div>\n\n<!-- Fullscreen Backdrop -->\n<div class=\"fullscreen-backdrop\" \n *ngIf=\"isFullscreen$ | async\"\n (click)=\"toggleFullscreen()\">\n</div>\n\n", styles: ["@import\"@aquera/nile/lib/styles/variables.css\";:host{display:block;width:100%;height:100%}.workbook-container{display:flex;flex-direction:column;height:100%;background:white;border:1px solid var(--nile-color-neutral-200);border-radius:4px;overflow:hidden}.workbook-container.fullscreen{position:fixed;inset:0;z-index:2000;border:none;border-radius:0}.workbook-container nile-tab-group{height:100%;display:flex;flex-direction:column}.sheet-tab-content{display:flex;align-items:center;gap:8px;padding:0 4px}.sheet-tab-content .sheet-name{font-size:12px;font-weight:500;font-family:var(--nile-font-family-sans-serif);color:#000}.sheet-tab-content .tab-actions-button{width:20px;height:20px;padding:0;background:transparent;border:none;cursor:pointer}.workbook-toolbar-tab{margin-left:auto!important;pointer-events:auto!important;border-left:1px solid var(--nile-color-neutral-200)}.workbook-toolbar-tab .workbook-toolbar-content{display:flex;gap:4px;align-items:center;padding:0 8px}.workbook-toolbar-tab .workbook-toolbar-content .autosave-indicator{display:flex;align-items:center;margin-right:8px;color:var(--nile-color-success-500)}.workbook-toolbar-tab button{display:flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;background:transparent;border:none;border-radius:4px;cursor:pointer;transition:background-color .2s;color:var(--nile-color-neutral-600)}.workbook-toolbar-tab button:hover{background-color:var(--nile-color-neutral-100);color:var(--nile-color-neutral-900)}.workbook-toolbar-tab button:active{background-color:var(--nile-color-neutral-200)}.workbook-toolbar-tab button nile-icon{font-size:16px}.tab-actions-dropdown{position:fixed;z-index:1001}.tab-actions-dropdown .dropdown-backdrop{position:fixed;top:0;left:0;width:100%;height:100%;background:transparent;z-index:1000}.tab-actions-dropdown .dropdown-menu{position:relative;min-width:180px;background:white;border-radius:8px;box-shadow:0 4px 12px #00000026;z-index:1001;overflow:hidden}.tab-actions-dropdown .dropdown-menu nile-menu{display:block}.workbook-actions-dropdown{position:fixed;z-index:1001}.workbook-actions-dropdown .dropdown-backdrop{position:fixed;top:0;left:0;width:100%;height:100%;background:transparent;z-index:1000}.workbook-actions-dropdown .dropdown-menu{position:relative;min-width:200px;background:white;border-radius:8px;box-shadow:0 4px 12px #00000026;z-index:1001;overflow:hidden}.workbook-actions-dropdown .dropdown-menu nile-menu{display:block}.fullscreen-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:1999;cursor:pointer}nile-tab-group::part(nav){background-color:#fafafa}nile-tab-group::part(active-tab-indicator-path){background:none}nile-tab-group::part(active-tab-indicator){border-bottom:none}nile-tab-group::part(tabs){gap:0}nile-tab-group nile-tab{border:1px solid #e0e0e0;border-top-left-radius:6px;border-top-right-radius:6px}nile-tab-group nile-tab::part(base){padding-left:.5rem;padding-top:.5rem;padding-bottom:.5rem;border-bottom-left-radius:0;border-bottom-right-radius:0}nile-tab-group nile-tab.active{background-color:#fff;color:#000}nile-tab-group nile-tab.active .sheet-name{font-weight:600}nile-tab-group nile-tab-panel::part(base){padding:0;max-height:30rem}\n"], components: [{ type: StTableComponent, selector: "st-table", inputs: ["tableConfig", "data", "data$", "tableState", "enableSorting", "enableFiltering", "validateConfig"], outputs: ["stateChange", "dataChange", "cellEdit", "cellSave", "cellCancel", "cellChange", "columnResized", "columnMoved", "configValidationErrors", "columnAdded", "rowAction", "validationStateChange", "requestAddRow"] }], directives: [{ type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }], pipes: { "async": i1.AsyncPipe, "date": i1.DatePipe } });
10478
+ StWorkbookComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.4.0", type: StWorkbookComponent, selector: "st-workbook", inputs: { config: "config", sheetsData: "sheetsData", state: "state" }, outputs: { sheetChanged: "sheetChanged", addSheet: "addSheet", sheetTabAction: "sheetTabAction", workbookAction: "workbookAction", cellChange: "cellChange", cellSave: "cellSave", tableStateChange: "tableStateChange", fullscreenToggle: "fullscreenToggle", requestAddRow: "requestAddRow" }, viewQueries: [{ propertyName: "tableComponent", first: true, predicate: StTableComponent, descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"workbook-container\" [class.fullscreen]=\"isFullscreen$ | async\">\n <nile-tab-group [activeIndex]=\"activeSheetIndex\">\n \n <!-- Sheet Tabs (one per sheet) -->\n <nile-tab *ngFor=\"let sheet of sheets; let i = index\"\n slot=\"nav\" \n panel=\"shared-panel\"\n [class.active]=\"i === activeSheetIndex\"\n (click)=\"onTabChange(i)\">\n <div class=\"sheet-tab-content\">\n <span class=\"sheet-name\">{{ sheet.name }}</span>\n \n <!-- Tab actions dropdown button -->\n <button class=\"tab-actions-button\"\n (click)=\"openTabActions($event, sheet, i)\"\n *ngIf=\"hasTabActions(sheet)\">\n <nile-icon name=\"arrowdown\" size=\"14\"></nile-icon>\n </button>\n </div>\n </nile-tab>\n \n <!-- Toolbar Tab (for workbook controls) -->\n <nile-tab slot=\"nav\" \n panel=\"shared-panel\"\n class=\"workbook-toolbar-tab\"\n [disabled]=\"true\">\n <div class=\"workbook-toolbar-content\">\n <!-- Autosave Indicator -->\n <div class=\"autosave-indicator\" *ngIf=\"autosaveEnabled\">\n <nile-icon \n *ngIf=\"!isSaving && lastSaveTime\" \n name=\"save\" \n size=\"14\"\n [title]=\"'Saved at ' + (lastSaveTime | date:'HH:mm:ss')\">\n </nile-icon>\n <nile-icon \n *ngIf=\"isSaving\" \n name=\"loader\" \n size=\"14\"\n title=\"Saving...\">\n </nile-icon>\n </div>\n\n <!-- Toolbar Workbook Actions (shown as individual buttons) -->\n <button *ngFor=\"let action of toolbarWorkbookActions\"\n class=\"toolbar-action-button\"\n [class.disabled]=\"isActionDisabled(action)\"\n [title]=\"action.label\"\n (click)=\"onWorkbookActionClick(action, $event)\">\n <nile-icon *ngIf=\"action.icon\" [name]=\"action.icon\"></nile-icon>\n <span *ngIf=\"!action.icon\">{{ action.label }}</span>\n </button>\n\n <!-- Workbook Actions Dropdown -->\n <button class=\"workbook-actions-button\"\n *ngIf=\"visibleWorkbookActions.length > 0\"\n (click)=\"toggleWorkbookActions($event)\"\n title=\"Workbook Actions\">\n <nile-icon name=\"settings\"></nile-icon>\n </button>\n \n <!-- Add Sheet Button -->\n <button class=\"add-sheet-button\"\n *ngIf=\"canAddSheet\"\n (click)=\"onAddSheet()\"\n title=\"Add Sheet\">\n <nile-icon name=\"plus\"></nile-icon>\n </button>\n \n <!-- Fullscreen Button -->\n <button class=\"fullscreen-button\"\n *ngIf=\"config.display?.allowFullscreen !== false\"\n (click)=\"toggleFullscreen()\"\n [title]=\"(isFullscreen$ | async) ? 'Exit Fullscreen' : 'Fullscreen'\">\n <nile-icon [name]=\"(isFullscreen$ | async) ? 'collapse' : 'expand-06'\"></nile-icon>\n </button>\n </div>\n </nile-tab>\n \n <!-- Single Shared Tab Panel -->\n <nile-tab-panel name=\"shared-panel\">\n <!-- Lazy loading strategy: table is destroyed and recreated with new config/state when sheet changes -->\n <!-- Using ngFor with trackBy to force complete reinitialization when tableComponentKey changes -->\n <ng-container *ngFor=\"let key of [tableComponentKey]; trackBy: trackByKey\">\n <st-table *ngIf=\"currentTableConfig && currentTableState\"\n [attr.data-sheet-key]=\"key\"\n [tableConfig]=\"currentTableConfig\"\n [data$]=\"currentTableData$\"\n [tableState]=\"currentTableState\"\n (cellChange)=\"onCellChange($event)\"\n (cellSave)=\"onCellSave($event)\"\n (stateChange)=\"onTableStateChange($event)\"\n (requestAddRow)=\"onRequestAddRow($event)\">\n </st-table>\n </ng-container>\n </nile-tab-panel>\n \n </nile-tab-group>\n</div>\n\n<!-- Tab Actions Dropdown -->\n<div class=\"tab-actions-dropdown\" \n *ngIf=\"tabActionsOpen\"\n [ngStyle]=\"tabActionsPosition\">\n <div class=\"dropdown-backdrop\" (click)=\"closeTabActions()\"></div>\n <div class=\"dropdown-menu\">\n <nile-menu>\n <nile-menu-item *ngFor=\"let action of selectedSheet?.tabActions\" \n (click)=\"onTabActionClick(action, $event)\">\n <nile-icon *ngIf=\"action.icon\" slot=\"prefix\" size=\"14\" [name]=\"action.icon\"></nile-icon>\n {{ action.label }}\n </nile-menu-item>\n </nile-menu>\n </div>\n</div>\n\n<!-- Workbook Actions Dropdown -->\n<div class=\"workbook-actions-dropdown\"\n *ngIf=\"workbookActionsOpen\"\n [ngStyle]=\"workbookActionsPosition\">\n <div class=\"dropdown-backdrop\" (click)=\"closeWorkbookActions()\"></div>\n <div class=\"dropdown-menu\">\n <nile-menu>\n <nile-menu-item *ngFor=\"let action of visibleWorkbookActions\"\n [class.disabled]=\"isActionDisabled(action)\"\n (click)=\"onWorkbookActionClick(action, $event)\">\n <nile-icon *ngIf=\"action.icon\" slot=\"prefix\" size=\"14\" [name]=\"action.icon\"></nile-icon>\n {{ action.label }}\n </nile-menu-item>\n </nile-menu>\n </div>\n</div>\n\n<!-- Fullscreen Backdrop -->\n<div class=\"fullscreen-backdrop\" \n *ngIf=\"isFullscreen$ | async\"\n (click)=\"toggleFullscreen()\">\n</div>\n\n", styles: ["@import\"@aquera/nile/lib/styles/variables.css\";:host{display:block;width:100%;height:100%}.workbook-container{display:flex;flex-direction:column;height:100%;background:white;border:1px solid var(--nile-color-neutral-200);border-radius:4px;overflow:hidden}.workbook-container.fullscreen{position:fixed;inset:0;z-index:2000;border:none;border-radius:0}.workbook-container nile-tab-group{height:100%;display:flex;flex-direction:column}.sheet-tab-content{display:flex;align-items:center;gap:8px;padding:0 4px}.sheet-tab-content .sheet-name{font-size:12px;font-weight:500;font-family:var(--nile-font-family-sans-serif);color:#000}.sheet-tab-content .tab-actions-button{width:20px;height:20px;padding:0;background:transparent;border:none;cursor:pointer}.workbook-toolbar-tab{margin-left:auto!important;pointer-events:auto!important;border-left:1px solid var(--nile-color-neutral-200)}.workbook-toolbar-tab .workbook-toolbar-content{display:flex;gap:4px;align-items:center;padding:0 8px}.workbook-toolbar-tab .workbook-toolbar-content .autosave-indicator{display:flex;align-items:center;margin-right:8px;color:var(--nile-color-success-500)}.workbook-toolbar-tab button{display:flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;background:transparent;border:none;border-radius:4px;cursor:pointer;transition:background-color .2s;color:var(--nile-color-neutral-600)}.workbook-toolbar-tab button:hover{background-color:var(--nile-color-neutral-100);color:var(--nile-color-neutral-900)}.workbook-toolbar-tab button:active{background-color:var(--nile-color-neutral-200)}.workbook-toolbar-tab button nile-icon{font-size:16px}.workbook-toolbar-tab button.disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.tab-actions-dropdown{position:fixed;z-index:1001}.tab-actions-dropdown .dropdown-backdrop{position:fixed;top:0;left:0;width:100%;height:100%;background:transparent;z-index:1000}.tab-actions-dropdown .dropdown-menu{position:relative;min-width:180px;background:white;border-radius:8px;box-shadow:0 4px 12px #00000026;z-index:1001;overflow:hidden}.tab-actions-dropdown .dropdown-menu nile-menu{display:block}.workbook-actions-dropdown{position:fixed;z-index:1001}.workbook-actions-dropdown .dropdown-backdrop{position:fixed;top:0;left:0;width:100%;height:100%;background:transparent;z-index:1000}.workbook-actions-dropdown .dropdown-menu{position:relative;min-width:200px;background:white;border-radius:8px;box-shadow:0 4px 12px #00000026;z-index:1001;overflow:hidden}.workbook-actions-dropdown .dropdown-menu nile-menu{display:block}.fullscreen-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:1999;cursor:pointer}nile-tab-group::part(nav){background-color:#fafafa}nile-tab-group::part(active-tab-indicator-path){background:none}nile-tab-group::part(active-tab-indicator){border-bottom:none}nile-tab-group::part(tabs){gap:0}nile-tab-group nile-tab{border:1px solid #e0e0e0;border-top-left-radius:6px;border-top-right-radius:6px}nile-tab-group nile-tab::part(base){padding-left:.5rem;padding-top:.5rem;padding-bottom:.5rem;border-bottom-left-radius:0;border-bottom-right-radius:0}nile-tab-group nile-tab.active{background-color:#fff;color:#000}nile-tab-group nile-tab.active .sheet-name{font-weight:600}nile-tab-group nile-tab-panel::part(base){padding:0;max-height:30rem}\n"], components: [{ type: StTableComponent, selector: "st-table", inputs: ["tableConfig", "data", "data$", "tableState", "enableSorting", "enableFiltering", "validateConfig"], outputs: ["stateChange", "dataChange", "cellEdit", "cellSave", "cellCancel", "cellChange", "columnResized", "columnMoved", "configValidationErrors", "columnAdded", "rowAction", "validationStateChange", "requestAddRow"] }], directives: [{ type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }], pipes: { "async": i1.AsyncPipe, "date": i1.DatePipe } });
10467
10479
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: StWorkbookComponent, decorators: [{
10468
10480
  type: Component,
10469
- args: [{ selector: 'st-workbook', template: "<div class=\"workbook-container\" [class.fullscreen]=\"isFullscreen$ | async\">\n <nile-tab-group [activeIndex]=\"activeSheetIndex\">\n \n <!-- Sheet Tabs (one per sheet) -->\n <nile-tab *ngFor=\"let sheet of sheets; let i = index\"\n slot=\"nav\" \n panel=\"shared-panel\"\n [class.active]=\"i === activeSheetIndex\"\n (click)=\"onTabChange(i)\">\n <div class=\"sheet-tab-content\">\n <span class=\"sheet-name\">{{ sheet.name }}</span>\n \n <!-- Tab actions dropdown button -->\n <button class=\"tab-actions-button\"\n (click)=\"openTabActions($event, sheet, i)\"\n *ngIf=\"hasTabActions(sheet)\">\n <nile-icon name=\"arrowdown\" size=\"14\"></nile-icon>\n </button>\n </div>\n </nile-tab>\n \n <!-- Toolbar Tab (for workbook controls) -->\n <nile-tab slot=\"nav\" \n panel=\"shared-panel\"\n class=\"workbook-toolbar-tab\"\n [disabled]=\"true\">\n <div class=\"workbook-toolbar-content\">\n <!-- Autosave Indicator -->\n <div class=\"autosave-indicator\" *ngIf=\"autosaveEnabled\">\n <nile-icon \n *ngIf=\"!isSaving && lastSaveTime\" \n name=\"save\" \n size=\"14\"\n [title]=\"'Saved at ' + (lastSaveTime | date:'HH:mm:ss')\">\n </nile-icon>\n <nile-icon \n *ngIf=\"isSaving\" \n name=\"loader\" \n size=\"14\"\n title=\"Saving...\">\n </nile-icon>\n </div>\n\n <!-- Workbook Actions Dropdown -->\n <button class=\"workbook-actions-button\"\n *ngIf=\"config.workbookActions && config.workbookActions.length > 0\"\n (click)=\"toggleWorkbookActions($event)\"\n title=\"Workbook Actions\">\n <nile-icon name=\"settings\"></nile-icon>\n </button>\n \n <!-- Add Sheet Button -->\n <button class=\"add-sheet-button\"\n *ngIf=\"canAddSheet\"\n (click)=\"onAddSheet()\"\n title=\"Add Sheet\">\n <nile-icon name=\"plus\"></nile-icon>\n </button>\n \n <!-- Fullscreen Button -->\n <button class=\"fullscreen-button\"\n *ngIf=\"config.display?.allowFullscreen !== false\"\n (click)=\"toggleFullscreen()\"\n [title]=\"(isFullscreen$ | async) ? 'Exit Fullscreen' : 'Fullscreen'\">\n <nile-icon [name]=\"(isFullscreen$ | async) ? 'collapse' : 'expand-06'\"></nile-icon>\n </button>\n </div>\n </nile-tab>\n \n <!-- Single Shared Tab Panel -->\n <nile-tab-panel name=\"shared-panel\">\n <!-- Lazy loading strategy: table is destroyed and recreated with new config/state when sheet changes -->\n <!-- Using ngFor with trackBy to force complete reinitialization when tableComponentKey changes -->\n <ng-container *ngFor=\"let key of [tableComponentKey]; trackBy: trackByKey\">\n <st-table *ngIf=\"currentTableConfig && currentTableState\"\n [attr.data-sheet-key]=\"key\"\n [tableConfig]=\"currentTableConfig\"\n [data$]=\"currentTableData$\"\n [tableState]=\"currentTableState\"\n (cellChange)=\"onCellChange($event)\"\n (cellSave)=\"onCellSave($event)\"\n (stateChange)=\"onTableStateChange($event)\"\n (requestAddRow)=\"onRequestAddRow($event)\">\n </st-table>\n </ng-container>\n </nile-tab-panel>\n \n </nile-tab-group>\n</div>\n\n<!-- Tab Actions Dropdown -->\n<div class=\"tab-actions-dropdown\" \n *ngIf=\"tabActionsOpen\"\n [ngStyle]=\"tabActionsPosition\">\n <div class=\"dropdown-backdrop\" (click)=\"closeTabActions()\"></div>\n <div class=\"dropdown-menu\">\n <nile-menu>\n <nile-menu-item *ngFor=\"let action of selectedSheet?.tabActions\" \n (click)=\"onTabActionClick(action, $event)\">\n <nile-icon *ngIf=\"action.icon\" slot=\"prefix\" size=\"14\" [name]=\"action.icon\"></nile-icon>\n {{ action.label }}\n </nile-menu-item>\n </nile-menu>\n </div>\n</div>\n\n<!-- Workbook Actions Dropdown -->\n<div class=\"workbook-actions-dropdown\"\n *ngIf=\"workbookActionsOpen\"\n [ngStyle]=\"workbookActionsPosition\">\n <div class=\"dropdown-backdrop\" (click)=\"closeWorkbookActions()\"></div>\n <div class=\"dropdown-menu\">\n <nile-menu>\n <nile-menu-item *ngFor=\"let action of visibleWorkbookActions\"\n [class.disabled]=\"isActionDisabled(action)\"\n (click)=\"onWorkbookActionClick(action, $event)\">\n <nile-icon *ngIf=\"action.icon\" slot=\"prefix\" size=\"14\" [name]=\"action.icon\"></nile-icon>\n {{ action.label }}\n </nile-menu-item>\n </nile-menu>\n </div>\n</div>\n\n<!-- Fullscreen Backdrop -->\n<div class=\"fullscreen-backdrop\" \n *ngIf=\"isFullscreen$ | async\"\n (click)=\"toggleFullscreen()\">\n</div>\n\n", styles: ["@import\"@aquera/nile/lib/styles/variables.css\";:host{display:block;width:100%;height:100%}.workbook-container{display:flex;flex-direction:column;height:100%;background:white;border:1px solid var(--nile-color-neutral-200);border-radius:4px;overflow:hidden}.workbook-container.fullscreen{position:fixed;inset:0;z-index:2000;border:none;border-radius:0}.workbook-container nile-tab-group{height:100%;display:flex;flex-direction:column}.sheet-tab-content{display:flex;align-items:center;gap:8px;padding:0 4px}.sheet-tab-content .sheet-name{font-size:12px;font-weight:500;font-family:var(--nile-font-family-sans-serif);color:#000}.sheet-tab-content .tab-actions-button{width:20px;height:20px;padding:0;background:transparent;border:none;cursor:pointer}.workbook-toolbar-tab{margin-left:auto!important;pointer-events:auto!important;border-left:1px solid var(--nile-color-neutral-200)}.workbook-toolbar-tab .workbook-toolbar-content{display:flex;gap:4px;align-items:center;padding:0 8px}.workbook-toolbar-tab .workbook-toolbar-content .autosave-indicator{display:flex;align-items:center;margin-right:8px;color:var(--nile-color-success-500)}.workbook-toolbar-tab button{display:flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;background:transparent;border:none;border-radius:4px;cursor:pointer;transition:background-color .2s;color:var(--nile-color-neutral-600)}.workbook-toolbar-tab button:hover{background-color:var(--nile-color-neutral-100);color:var(--nile-color-neutral-900)}.workbook-toolbar-tab button:active{background-color:var(--nile-color-neutral-200)}.workbook-toolbar-tab button nile-icon{font-size:16px}.tab-actions-dropdown{position:fixed;z-index:1001}.tab-actions-dropdown .dropdown-backdrop{position:fixed;top:0;left:0;width:100%;height:100%;background:transparent;z-index:1000}.tab-actions-dropdown .dropdown-menu{position:relative;min-width:180px;background:white;border-radius:8px;box-shadow:0 4px 12px #00000026;z-index:1001;overflow:hidden}.tab-actions-dropdown .dropdown-menu nile-menu{display:block}.workbook-actions-dropdown{position:fixed;z-index:1001}.workbook-actions-dropdown .dropdown-backdrop{position:fixed;top:0;left:0;width:100%;height:100%;background:transparent;z-index:1000}.workbook-actions-dropdown .dropdown-menu{position:relative;min-width:200px;background:white;border-radius:8px;box-shadow:0 4px 12px #00000026;z-index:1001;overflow:hidden}.workbook-actions-dropdown .dropdown-menu nile-menu{display:block}.fullscreen-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:1999;cursor:pointer}nile-tab-group::part(nav){background-color:#fafafa}nile-tab-group::part(active-tab-indicator-path){background:none}nile-tab-group::part(active-tab-indicator){border-bottom:none}nile-tab-group::part(tabs){gap:0}nile-tab-group nile-tab{border:1px solid #e0e0e0;border-top-left-radius:6px;border-top-right-radius:6px}nile-tab-group nile-tab::part(base){padding-left:.5rem;padding-top:.5rem;padding-bottom:.5rem;border-bottom-left-radius:0;border-bottom-right-radius:0}nile-tab-group nile-tab.active{background-color:#fff;color:#000}nile-tab-group nile-tab.active .sheet-name{font-weight:600}nile-tab-group nile-tab-panel::part(base){padding:0;max-height:30rem}\n"] }]
10481
+ args: [{ selector: 'st-workbook', template: "<div class=\"workbook-container\" [class.fullscreen]=\"isFullscreen$ | async\">\n <nile-tab-group [activeIndex]=\"activeSheetIndex\">\n \n <!-- Sheet Tabs (one per sheet) -->\n <nile-tab *ngFor=\"let sheet of sheets; let i = index\"\n slot=\"nav\" \n panel=\"shared-panel\"\n [class.active]=\"i === activeSheetIndex\"\n (click)=\"onTabChange(i)\">\n <div class=\"sheet-tab-content\">\n <span class=\"sheet-name\">{{ sheet.name }}</span>\n \n <!-- Tab actions dropdown button -->\n <button class=\"tab-actions-button\"\n (click)=\"openTabActions($event, sheet, i)\"\n *ngIf=\"hasTabActions(sheet)\">\n <nile-icon name=\"arrowdown\" size=\"14\"></nile-icon>\n </button>\n </div>\n </nile-tab>\n \n <!-- Toolbar Tab (for workbook controls) -->\n <nile-tab slot=\"nav\" \n panel=\"shared-panel\"\n class=\"workbook-toolbar-tab\"\n [disabled]=\"true\">\n <div class=\"workbook-toolbar-content\">\n <!-- Autosave Indicator -->\n <div class=\"autosave-indicator\" *ngIf=\"autosaveEnabled\">\n <nile-icon \n *ngIf=\"!isSaving && lastSaveTime\" \n name=\"save\" \n size=\"14\"\n [title]=\"'Saved at ' + (lastSaveTime | date:'HH:mm:ss')\">\n </nile-icon>\n <nile-icon \n *ngIf=\"isSaving\" \n name=\"loader\" \n size=\"14\"\n title=\"Saving...\">\n </nile-icon>\n </div>\n\n <!-- Toolbar Workbook Actions (shown as individual buttons) -->\n <button *ngFor=\"let action of toolbarWorkbookActions\"\n class=\"toolbar-action-button\"\n [class.disabled]=\"isActionDisabled(action)\"\n [title]=\"action.label\"\n (click)=\"onWorkbookActionClick(action, $event)\">\n <nile-icon *ngIf=\"action.icon\" [name]=\"action.icon\"></nile-icon>\n <span *ngIf=\"!action.icon\">{{ action.label }}</span>\n </button>\n\n <!-- Workbook Actions Dropdown -->\n <button class=\"workbook-actions-button\"\n *ngIf=\"visibleWorkbookActions.length > 0\"\n (click)=\"toggleWorkbookActions($event)\"\n title=\"Workbook Actions\">\n <nile-icon name=\"settings\"></nile-icon>\n </button>\n \n <!-- Add Sheet Button -->\n <button class=\"add-sheet-button\"\n *ngIf=\"canAddSheet\"\n (click)=\"onAddSheet()\"\n title=\"Add Sheet\">\n <nile-icon name=\"plus\"></nile-icon>\n </button>\n \n <!-- Fullscreen Button -->\n <button class=\"fullscreen-button\"\n *ngIf=\"config.display?.allowFullscreen !== false\"\n (click)=\"toggleFullscreen()\"\n [title]=\"(isFullscreen$ | async) ? 'Exit Fullscreen' : 'Fullscreen'\">\n <nile-icon [name]=\"(isFullscreen$ | async) ? 'collapse' : 'expand-06'\"></nile-icon>\n </button>\n </div>\n </nile-tab>\n \n <!-- Single Shared Tab Panel -->\n <nile-tab-panel name=\"shared-panel\">\n <!-- Lazy loading strategy: table is destroyed and recreated with new config/state when sheet changes -->\n <!-- Using ngFor with trackBy to force complete reinitialization when tableComponentKey changes -->\n <ng-container *ngFor=\"let key of [tableComponentKey]; trackBy: trackByKey\">\n <st-table *ngIf=\"currentTableConfig && currentTableState\"\n [attr.data-sheet-key]=\"key\"\n [tableConfig]=\"currentTableConfig\"\n [data$]=\"currentTableData$\"\n [tableState]=\"currentTableState\"\n (cellChange)=\"onCellChange($event)\"\n (cellSave)=\"onCellSave($event)\"\n (stateChange)=\"onTableStateChange($event)\"\n (requestAddRow)=\"onRequestAddRow($event)\">\n </st-table>\n </ng-container>\n </nile-tab-panel>\n \n </nile-tab-group>\n</div>\n\n<!-- Tab Actions Dropdown -->\n<div class=\"tab-actions-dropdown\" \n *ngIf=\"tabActionsOpen\"\n [ngStyle]=\"tabActionsPosition\">\n <div class=\"dropdown-backdrop\" (click)=\"closeTabActions()\"></div>\n <div class=\"dropdown-menu\">\n <nile-menu>\n <nile-menu-item *ngFor=\"let action of selectedSheet?.tabActions\" \n (click)=\"onTabActionClick(action, $event)\">\n <nile-icon *ngIf=\"action.icon\" slot=\"prefix\" size=\"14\" [name]=\"action.icon\"></nile-icon>\n {{ action.label }}\n </nile-menu-item>\n </nile-menu>\n </div>\n</div>\n\n<!-- Workbook Actions Dropdown -->\n<div class=\"workbook-actions-dropdown\"\n *ngIf=\"workbookActionsOpen\"\n [ngStyle]=\"workbookActionsPosition\">\n <div class=\"dropdown-backdrop\" (click)=\"closeWorkbookActions()\"></div>\n <div class=\"dropdown-menu\">\n <nile-menu>\n <nile-menu-item *ngFor=\"let action of visibleWorkbookActions\"\n [class.disabled]=\"isActionDisabled(action)\"\n (click)=\"onWorkbookActionClick(action, $event)\">\n <nile-icon *ngIf=\"action.icon\" slot=\"prefix\" size=\"14\" [name]=\"action.icon\"></nile-icon>\n {{ action.label }}\n </nile-menu-item>\n </nile-menu>\n </div>\n</div>\n\n<!-- Fullscreen Backdrop -->\n<div class=\"fullscreen-backdrop\" \n *ngIf=\"isFullscreen$ | async\"\n (click)=\"toggleFullscreen()\">\n</div>\n\n", styles: ["@import\"@aquera/nile/lib/styles/variables.css\";:host{display:block;width:100%;height:100%}.workbook-container{display:flex;flex-direction:column;height:100%;background:white;border:1px solid var(--nile-color-neutral-200);border-radius:4px;overflow:hidden}.workbook-container.fullscreen{position:fixed;inset:0;z-index:2000;border:none;border-radius:0}.workbook-container nile-tab-group{height:100%;display:flex;flex-direction:column}.sheet-tab-content{display:flex;align-items:center;gap:8px;padding:0 4px}.sheet-tab-content .sheet-name{font-size:12px;font-weight:500;font-family:var(--nile-font-family-sans-serif);color:#000}.sheet-tab-content .tab-actions-button{width:20px;height:20px;padding:0;background:transparent;border:none;cursor:pointer}.workbook-toolbar-tab{margin-left:auto!important;pointer-events:auto!important;border-left:1px solid var(--nile-color-neutral-200)}.workbook-toolbar-tab .workbook-toolbar-content{display:flex;gap:4px;align-items:center;padding:0 8px}.workbook-toolbar-tab .workbook-toolbar-content .autosave-indicator{display:flex;align-items:center;margin-right:8px;color:var(--nile-color-success-500)}.workbook-toolbar-tab button{display:flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;background:transparent;border:none;border-radius:4px;cursor:pointer;transition:background-color .2s;color:var(--nile-color-neutral-600)}.workbook-toolbar-tab button:hover{background-color:var(--nile-color-neutral-100);color:var(--nile-color-neutral-900)}.workbook-toolbar-tab button:active{background-color:var(--nile-color-neutral-200)}.workbook-toolbar-tab button nile-icon{font-size:16px}.workbook-toolbar-tab button.disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.tab-actions-dropdown{position:fixed;z-index:1001}.tab-actions-dropdown .dropdown-backdrop{position:fixed;top:0;left:0;width:100%;height:100%;background:transparent;z-index:1000}.tab-actions-dropdown .dropdown-menu{position:relative;min-width:180px;background:white;border-radius:8px;box-shadow:0 4px 12px #00000026;z-index:1001;overflow:hidden}.tab-actions-dropdown .dropdown-menu nile-menu{display:block}.workbook-actions-dropdown{position:fixed;z-index:1001}.workbook-actions-dropdown .dropdown-backdrop{position:fixed;top:0;left:0;width:100%;height:100%;background:transparent;z-index:1000}.workbook-actions-dropdown .dropdown-menu{position:relative;min-width:200px;background:white;border-radius:8px;box-shadow:0 4px 12px #00000026;z-index:1001;overflow:hidden}.workbook-actions-dropdown .dropdown-menu nile-menu{display:block}.fullscreen-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:1999;cursor:pointer}nile-tab-group::part(nav){background-color:#fafafa}nile-tab-group::part(active-tab-indicator-path){background:none}nile-tab-group::part(active-tab-indicator){border-bottom:none}nile-tab-group::part(tabs){gap:0}nile-tab-group nile-tab{border:1px solid #e0e0e0;border-top-left-radius:6px;border-top-right-radius:6px}nile-tab-group nile-tab::part(base){padding-left:.5rem;padding-top:.5rem;padding-bottom:.5rem;border-bottom-left-radius:0;border-bottom-right-radius:0}nile-tab-group nile-tab.active{background-color:#fff;color:#000}nile-tab-group nile-tab.active .sheet-name{font-weight:600}nile-tab-group nile-tab-panel::part(base){padding:0;max-height:30rem}\n"] }]
10470
10482
  }], ctorParameters: function () { return [{ type: AutosaveService }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { config: [{
10471
10483
  type: Input
10472
10484
  }], sheetsData: [{