@aquera/ngx-smart-table 0.0.17-patch-0.6 → 0.0.17-patch-0.8

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.
@@ -1,5 +1,6 @@
1
1
  import { BehaviorSubject, Subject, isObservable, fromEvent, merge } from 'rxjs';
2
2
  import { distinctUntilChanged, map, throttleTime, takeUntil, tap, debounceTime } from 'rxjs/operators';
3
+ import { __awaiter } from 'tslib';
3
4
  import * as i0 from '@angular/core';
4
5
  import { EventEmitter, ElementRef, Component, Input, Output, ViewChild, HostListener, Injectable, ChangeDetectionStrategy, Directive, NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
5
6
  import * as i1 from '@angular/common';
@@ -7,7 +8,6 @@ import { CommonModule } from '@angular/common';
7
8
  import Ajv from 'ajv';
8
9
  import * as i1$1 from '@angular/forms';
9
10
  import { Validators, ReactiveFormsModule, FormsModule } from '@angular/forms';
10
- import { __awaiter } from 'tslib';
11
11
 
12
12
  /**
13
13
  * Base type definitions for cell configuration model
@@ -3678,10 +3678,6 @@ class NileInputEditor {
3678
3678
  }
3679
3679
  }
3680
3680
 
3681
- /**
3682
- * Custom editor using NileSelect from @aquera/nile-elements
3683
- * This demonstrates how to create dropdown/select editors for ngx-smart-table
3684
- */
3685
3681
  /**
3686
3682
  * Inject global styles for nile-select dropdown height limit
3687
3683
  */
@@ -3814,16 +3810,137 @@ class NileSelectEditor {
3814
3810
  // Clear container and append select
3815
3811
  context.container.innerHTML = '';
3816
3812
  context.container.appendChild(this.select);
3817
- // Focus the select after render
3813
+ // Focus after mount; open dropdown only once nile-popup exists (avoids null this.popup in nile-select)
3814
+ const openOnEdit = this.options.openDropdownOnEdit !== false;
3818
3815
  setTimeout(() => {
3819
- var _a;
3816
+ void this.activateSelectAfterMount(openOnEdit);
3817
+ }, 50);
3818
+ }
3819
+ activateSelectAfterMount(openOnEdit) {
3820
+ var _a;
3821
+ return __awaiter(this, void 0, void 0, function* () {
3820
3822
  try {
3821
3823
  (_a = this.select) === null || _a === void 0 ? void 0 : _a.focus();
3822
3824
  }
3823
- catch (e) {
3824
- // Ignore errors
3825
+ catch (_b) {
3826
+ // ignore
3825
3827
  }
3826
- }, 50);
3828
+ if (!openOnEdit || !this.select || this.options.disabled) {
3829
+ return;
3830
+ }
3831
+ yield this.openNileSelectDropdownSafely();
3832
+ });
3833
+ }
3834
+ /** Lit @query('nile-popup') / .select — must exist before handleOpenChange touches this.popup.popup */
3835
+ isNileSelectPopupMounted(select) {
3836
+ var _a;
3837
+ return !!((_a = select.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('nile-popup'));
3838
+ }
3839
+ invokeShowHandlingPromise(host) {
3840
+ return __awaiter(this, void 0, void 0, function* () {
3841
+ if (typeof host.show !== 'function') {
3842
+ return;
3843
+ }
3844
+ try {
3845
+ const out = host.show();
3846
+ if (out != null && typeof out.then === 'function') {
3847
+ yield out.catch(() => undefined);
3848
+ }
3849
+ }
3850
+ catch (_a) {
3851
+ // sync throw from show()
3852
+ }
3853
+ });
3854
+ }
3855
+ openNileSelectDropdownSafely() {
3856
+ var _a, _b, _c, _d;
3857
+ return __awaiter(this, void 0, void 0, function* () {
3858
+ const select = this.select;
3859
+ if (!select) {
3860
+ return;
3861
+ }
3862
+ try {
3863
+ yield customElements.whenDefined('nile-select');
3864
+ }
3865
+ catch (_e) {
3866
+ // ignore
3867
+ }
3868
+ if (this.options.enableVirtualScroll) {
3869
+ try {
3870
+ yield customElements.whenDefined('nile-virtual-select');
3871
+ }
3872
+ catch (_f) {
3873
+ // ignore — not registered in all environments
3874
+ }
3875
+ // Same idea as nile-popup: inner host may not exist until Lit finishes a pass (or several).
3876
+ const maxVirtualWaitPasses = 50;
3877
+ for (let i = 0; i < maxVirtualWaitPasses; i++) {
3878
+ const vs = (_a = select.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector('nile-virtual-select');
3879
+ if (vs) {
3880
+ yield this.invokeShowHandlingPromise(vs);
3881
+ return;
3882
+ }
3883
+ const lit = select;
3884
+ if ((_b = lit.updateComplete) === null || _b === void 0 ? void 0 : _b.then) {
3885
+ try {
3886
+ yield lit.updateComplete;
3887
+ }
3888
+ catch (_g) {
3889
+ // ignore
3890
+ }
3891
+ }
3892
+ yield new Promise(resolve => requestAnimationFrame(() => resolve()));
3893
+ }
3894
+ return;
3895
+ }
3896
+ const maxWaitPasses = 50;
3897
+ for (let i = 0; i < maxWaitPasses; i++) {
3898
+ if (this.isNileSelectPopupMounted(select)) {
3899
+ break;
3900
+ }
3901
+ const lit = select;
3902
+ if ((_c = lit.updateComplete) === null || _c === void 0 ? void 0 : _c.then) {
3903
+ try {
3904
+ yield lit.updateComplete;
3905
+ }
3906
+ catch (_h) {
3907
+ // ignore
3908
+ }
3909
+ }
3910
+ yield new Promise(resolve => requestAnimationFrame(() => resolve()));
3911
+ }
3912
+ if (!this.isNileSelectPopupMounted(select)) {
3913
+ return;
3914
+ }
3915
+ const host = select;
3916
+ for (let attempt = 0; attempt < 5; attempt++) {
3917
+ if (!this.isNileSelectPopupMounted(select)) {
3918
+ return;
3919
+ }
3920
+ try {
3921
+ if (typeof host.show === 'function') {
3922
+ yield this.invokeShowHandlingPromise(host);
3923
+ }
3924
+ else {
3925
+ host.open = true;
3926
+ }
3927
+ return;
3928
+ }
3929
+ catch (_j) {
3930
+ // ignore
3931
+ }
3932
+ yield new Promise(resolve => setTimeout(resolve, 40));
3933
+ const lit = select;
3934
+ if ((_d = lit.updateComplete) === null || _d === void 0 ? void 0 : _d.then) {
3935
+ try {
3936
+ yield lit.updateComplete;
3937
+ }
3938
+ catch (_k) {
3939
+ // ignore
3940
+ }
3941
+ }
3942
+ }
3943
+ });
3827
3944
  }
3828
3945
  /**
3829
3946
  * Set the initial value for the select
@@ -4898,6 +5015,7 @@ class NileChipEditor {
4898
5015
  this.acceptsInitialKeypress = false;
4899
5016
  this.eventListeners = [];
4900
5017
  this.trackedValues = [];
5018
+ this.hasChangeOccurred = false;
4901
5019
  }
4902
5020
  edit(context) {
4903
5021
  var _a, _b;
@@ -5054,6 +5172,7 @@ class NileChipEditor {
5054
5172
  const detail = e.detail;
5055
5173
  const newValue = Array.isArray(detail === null || detail === void 0 ? void 0 : detail.value) ? [...detail.value] : (((_a = this.chip) === null || _a === void 0 ? void 0 : _a.value) ? [...this.chip.value] : []);
5056
5174
  this.trackedValues = newValue;
5175
+ this.hasChangeOccurred = true;
5057
5176
  context.onChange(newValue);
5058
5177
  });
5059
5178
  // Keyboard handling on the chip element
@@ -5123,6 +5242,7 @@ class NileChipEditor {
5123
5242
  this.chip = undefined;
5124
5243
  this.cellContainer = undefined;
5125
5244
  this.trackedValues = [];
5245
+ this.hasChangeOccurred = false;
5126
5246
  }
5127
5247
  focus() {
5128
5248
  var _a;
@@ -5132,7 +5252,7 @@ class NileChipEditor {
5132
5252
  catch ( /* ignore */_b) { /* ignore */ }
5133
5253
  }
5134
5254
  getCurrentValue() {
5135
- if (this.trackedValues.length > 0) {
5255
+ if (this.hasChangeOccurred) {
5136
5256
  return [...this.trackedValues];
5137
5257
  }
5138
5258
  if (!this.chip)
@@ -6330,8 +6450,14 @@ class StCellComponent {
6330
6450
  }
6331
6451
  }
6332
6452
  onCellClick() {
6333
- // Single click now only focuses, doesn't start editing
6334
- // Focus is handled by parent st-table component
6453
+ // First click: parent <td> focuses this cell (bubbles after this handler).
6454
+ // Second click while already focused: enter edit (spreadsheet-style).
6455
+ if (this.isEditable &&
6456
+ !this.cellLoading &&
6457
+ !this.cell.isEditing() &&
6458
+ this.cell.isFocused()) {
6459
+ this.startEdit();
6460
+ }
6335
6461
  }
6336
6462
  onCellDoubleClick() {
6337
6463
  // Double-click starts editing regardless of EditMode (Excel-like behavior)
@@ -8056,7 +8182,7 @@ class StColumnMenuDropdownComponent {
8056
8182
  */
8057
8183
  this.isOpen = false;
8058
8184
  /**
8059
- * Position of the dropdown (x, y coordinates)
8185
+ * Position of the dropdown (x, y coordinates, triggerTop for flip positioning)
8060
8186
  */
8061
8187
  this.position = { x: 0, y: 0 };
8062
8188
  /**
@@ -8170,25 +8296,42 @@ class StColumnMenuDropdownComponent {
8170
8296
  this.dropdownStyle = {};
8171
8297
  return;
8172
8298
  }
8173
- const viewportWidth = window.innerWidth;
8174
- const viewportHeight = window.innerHeight;
8175
- const dropdownWidth = 280; // Approximate dropdown width
8176
- const dropdownHeight = 300; // Approximate max dropdown height
8177
8299
  let { x, y } = this.position;
8178
- // Adjust horizontal position if dropdown would overflow
8179
- if (x + dropdownWidth > viewportWidth) {
8180
- x = Math.max(10, viewportWidth - dropdownWidth - 10);
8181
- }
8182
- // Adjust vertical position if dropdown would overflow
8183
- if (y + dropdownHeight > viewportHeight) {
8184
- y = Math.max(10, viewportHeight - dropdownHeight - 10);
8185
- }
8300
+ // Render at initial position first
8186
8301
  this.dropdownStyle = {
8187
8302
  position: 'fixed',
8188
8303
  left: `${x}px`,
8189
8304
  top: `${y}px`,
8190
- 'z-index': 9999
8305
+ 'z-index': 9999,
8306
+ visibility: 'hidden'
8191
8307
  };
8308
+ // After rendering, measure actual size and adjust if needed
8309
+ requestAnimationFrame(() => {
8310
+ var _a, _b;
8311
+ const viewportWidth = window.innerWidth;
8312
+ const viewportHeight = window.innerHeight;
8313
+ const el = (_a = this.dropdownPanel) === null || _a === void 0 ? void 0 : _a.nativeElement;
8314
+ const dropdownWidth = (el === null || el === void 0 ? void 0 : el.offsetWidth) || 280;
8315
+ const dropdownHeight = (el === null || el === void 0 ? void 0 : el.offsetHeight) || 200;
8316
+ // Adjust horizontal position if dropdown would overflow
8317
+ if (x + dropdownWidth > viewportWidth) {
8318
+ x = Math.max(10, viewportWidth - dropdownWidth - 10);
8319
+ }
8320
+ // Adjust vertical position — flip above the trigger if it overflows bottom
8321
+ if (y + dropdownHeight > viewportHeight) {
8322
+ const triggerTop = (_b = this.position.triggerTop) !== null && _b !== void 0 ? _b : this.position.y;
8323
+ y = triggerTop - dropdownHeight - 4;
8324
+ }
8325
+ // Clamp to viewport edges
8326
+ x = Math.max(10, x);
8327
+ y = Math.max(10, y);
8328
+ this.dropdownStyle = {
8329
+ position: 'fixed',
8330
+ left: `${x}px`,
8331
+ top: `${y}px`,
8332
+ 'z-index': 9999
8333
+ };
8334
+ });
8192
8335
  }
8193
8336
  /**
8194
8337
  * Check if an action is disabled
@@ -8256,10 +8399,10 @@ class StColumnMenuDropdownComponent {
8256
8399
  }
8257
8400
  }
8258
8401
  StColumnMenuDropdownComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: StColumnMenuDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8259
- StColumnMenuDropdownComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.4.0", type: StColumnMenuDropdownComponent, selector: "st-column-menu-dropdown", inputs: { isOpen: "isOpen", position: "position", context: "context" }, outputs: { actionClicked: "actionClicked", closed: "closed" }, host: { listeners: { "click": "onBackdropClick($event)" } }, viewQueries: [{ propertyName: "filterPopup", first: true, predicate: ["filterPopup"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<!-- Dropdown container with backdrop -->\n<div class=\"dropdown-container\" *ngIf=\"isOpen && context\">\n <!-- Backdrop -->\n <div class=\"dropdown-backdrop\" (click)=\"closed.emit()\"></div>\n \n <!-- Dropdown menu -->\n <div class=\"column-menu-dropdown\" [ngStyle]=\"dropdownStyle\">\n <!-- Main menu with actions -->\n <nile-menu *ngIf=\"!isFilterOpen\">\n <!-- Dynamically render all visible actions -->\n <ng-container *ngFor=\"let action of visibleActions; let i = index; let last = last\">\n <nile-menu-item \n (click)=\"onActionClick(action)\"\n [class.disabled]=\"isActionDisabled(action)\"\n [class.active]=\"isActionActive(action)\">\n <span class=\"checkmark\" *ngIf=\"isActionActive(action)\">\u2713</span>\n <nile-icon slot=\"prefix\" *ngIf=\"action.icon && !isActionActive(action)\" [name]=\"action.icon\"></nile-icon>\n <span class=\"action-label\">{{ action.label }}</span>\n </nile-menu-item>\n \n <!-- Add divider after action groups -->\n <nile-divider *ngIf=\"shouldShowDividerAfter(action, i, last)\"></nile-divider>\n </ng-container>\n \n <!-- Fallback if no actions -->\n <nile-menu-item *ngIf=\"visibleActions.length === 0\">\n No actions available\n </nile-menu-item>\n </nile-menu>\n \n <!-- Filter popup (conditionally rendered) -->\n <st-column-filter\n #filterPopup\n *ngIf=\"isFilterOpen && context\"\n [column]=\"context.column\"\n [tableState]=\"context.tableState\"\n [columnIndex]=\"context.columnIndex\"\n [isFirstColumn]=\"context.isFirstColumn\"\n [isLastColumn]=\"context.isLastColumn\"\n [isOpen]=\"isFilterOpen\"\n (filterApplied)=\"onFilterApplied($event)\"\n (filterCleared)=\"onFilterCleared()\"\n (closed)=\"onFilterClosed()\">\n </st-column-filter>\n </div>\n</div>\n", styles: [".dropdown-container{position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9998}.dropdown-backdrop{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:auto;z-index:9998}.column-menu-dropdown{min-width:200px;max-width:300px;background-color:#fff;border-radius:8px;box-shadow:0 10px 25px #00000026;overflow:hidden;pointer-events:auto;z-index:9999}nile-menu nile-divider::part(divider){margin:0}nile-menu nile-menu-item::part(base){height:2.5rem;min-height:auto}nile-menu nile-menu-item .checkmark{margin-right:8px;color:#4299e1;font-weight:700}\n"], components: [{ type: StColumnFilterComponent, selector: "st-column-filter", inputs: ["column", "tableState", "columnIndex", "isFirstColumn", "isLastColumn", "isOpen", "filterContext"], outputs: ["closed", "filterApplied", "filterCleared"] }], directives: [{ type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] });
8402
+ StColumnMenuDropdownComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.4.0", type: StColumnMenuDropdownComponent, selector: "st-column-menu-dropdown", inputs: { isOpen: "isOpen", position: "position", context: "context" }, outputs: { actionClicked: "actionClicked", closed: "closed" }, host: { listeners: { "click": "onBackdropClick($event)" } }, viewQueries: [{ propertyName: "filterPopup", first: true, predicate: ["filterPopup"], descendants: true }, { propertyName: "dropdownPanel", first: true, predicate: ["dropdownPanel"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<!-- Dropdown container with backdrop -->\n<div class=\"dropdown-container\" *ngIf=\"isOpen && context\">\n <!-- Backdrop -->\n <div class=\"dropdown-backdrop\" (click)=\"closed.emit()\"></div>\n \n <!-- Dropdown menu -->\n <div class=\"column-menu-dropdown\" #dropdownPanel [ngStyle]=\"dropdownStyle\">\n <!-- Main menu with actions -->\n <nile-menu *ngIf=\"!isFilterOpen\">\n <!-- Dynamically render all visible actions -->\n <ng-container *ngFor=\"let action of visibleActions; let i = index; let last = last\">\n <nile-menu-item \n (click)=\"onActionClick(action)\"\n [class.disabled]=\"isActionDisabled(action)\"\n [class.active]=\"isActionActive(action)\">\n <span class=\"checkmark\" *ngIf=\"isActionActive(action)\">\u2713</span>\n <nile-icon slot=\"prefix\" *ngIf=\"action.icon && !isActionActive(action)\" [name]=\"action.icon\"></nile-icon>\n <span class=\"action-label\">{{ action.label }}</span>\n </nile-menu-item>\n \n <!-- Add divider after action groups -->\n <nile-divider *ngIf=\"shouldShowDividerAfter(action, i, last)\"></nile-divider>\n </ng-container>\n \n <!-- Fallback if no actions -->\n <nile-menu-item *ngIf=\"visibleActions.length === 0\">\n No actions available\n </nile-menu-item>\n </nile-menu>\n \n <!-- Filter popup (conditionally rendered) -->\n <st-column-filter\n #filterPopup\n *ngIf=\"isFilterOpen && context\"\n [column]=\"context.column\"\n [tableState]=\"context.tableState\"\n [columnIndex]=\"context.columnIndex\"\n [isFirstColumn]=\"context.isFirstColumn\"\n [isLastColumn]=\"context.isLastColumn\"\n [isOpen]=\"isFilterOpen\"\n (filterApplied)=\"onFilterApplied($event)\"\n (filterCleared)=\"onFilterCleared()\"\n (closed)=\"onFilterClosed()\">\n </st-column-filter>\n </div>\n</div>\n", styles: [".dropdown-container{position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9998}.dropdown-backdrop{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:auto;z-index:9998}.column-menu-dropdown{min-width:200px;max-width:300px;background-color:#fff;border-radius:8px;box-shadow:0 10px 25px #00000026;overflow:hidden;pointer-events:auto;z-index:9999}nile-menu nile-divider::part(divider){margin:0}nile-menu nile-menu-item::part(base){height:2.5rem;min-height:auto}nile-menu nile-menu-item .checkmark{margin-right:8px;color:#4299e1;font-weight:700}\n"], components: [{ type: StColumnFilterComponent, selector: "st-column-filter", inputs: ["column", "tableState", "columnIndex", "isFirstColumn", "isLastColumn", "isOpen", "filterContext"], outputs: ["closed", "filterApplied", "filterCleared"] }], directives: [{ type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] });
8260
8403
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: StColumnMenuDropdownComponent, decorators: [{
8261
8404
  type: Component,
8262
- args: [{ selector: 'st-column-menu-dropdown', template: "<!-- Dropdown container with backdrop -->\n<div class=\"dropdown-container\" *ngIf=\"isOpen && context\">\n <!-- Backdrop -->\n <div class=\"dropdown-backdrop\" (click)=\"closed.emit()\"></div>\n \n <!-- Dropdown menu -->\n <div class=\"column-menu-dropdown\" [ngStyle]=\"dropdownStyle\">\n <!-- Main menu with actions -->\n <nile-menu *ngIf=\"!isFilterOpen\">\n <!-- Dynamically render all visible actions -->\n <ng-container *ngFor=\"let action of visibleActions; let i = index; let last = last\">\n <nile-menu-item \n (click)=\"onActionClick(action)\"\n [class.disabled]=\"isActionDisabled(action)\"\n [class.active]=\"isActionActive(action)\">\n <span class=\"checkmark\" *ngIf=\"isActionActive(action)\">\u2713</span>\n <nile-icon slot=\"prefix\" *ngIf=\"action.icon && !isActionActive(action)\" [name]=\"action.icon\"></nile-icon>\n <span class=\"action-label\">{{ action.label }}</span>\n </nile-menu-item>\n \n <!-- Add divider after action groups -->\n <nile-divider *ngIf=\"shouldShowDividerAfter(action, i, last)\"></nile-divider>\n </ng-container>\n \n <!-- Fallback if no actions -->\n <nile-menu-item *ngIf=\"visibleActions.length === 0\">\n No actions available\n </nile-menu-item>\n </nile-menu>\n \n <!-- Filter popup (conditionally rendered) -->\n <st-column-filter\n #filterPopup\n *ngIf=\"isFilterOpen && context\"\n [column]=\"context.column\"\n [tableState]=\"context.tableState\"\n [columnIndex]=\"context.columnIndex\"\n [isFirstColumn]=\"context.isFirstColumn\"\n [isLastColumn]=\"context.isLastColumn\"\n [isOpen]=\"isFilterOpen\"\n (filterApplied)=\"onFilterApplied($event)\"\n (filterCleared)=\"onFilterCleared()\"\n (closed)=\"onFilterClosed()\">\n </st-column-filter>\n </div>\n</div>\n", styles: [".dropdown-container{position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9998}.dropdown-backdrop{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:auto;z-index:9998}.column-menu-dropdown{min-width:200px;max-width:300px;background-color:#fff;border-radius:8px;box-shadow:0 10px 25px #00000026;overflow:hidden;pointer-events:auto;z-index:9999}nile-menu nile-divider::part(divider){margin:0}nile-menu nile-menu-item::part(base){height:2.5rem;min-height:auto}nile-menu nile-menu-item .checkmark{margin-right:8px;color:#4299e1;font-weight:700}\n"] }]
8405
+ args: [{ selector: 'st-column-menu-dropdown', template: "<!-- Dropdown container with backdrop -->\n<div class=\"dropdown-container\" *ngIf=\"isOpen && context\">\n <!-- Backdrop -->\n <div class=\"dropdown-backdrop\" (click)=\"closed.emit()\"></div>\n \n <!-- Dropdown menu -->\n <div class=\"column-menu-dropdown\" #dropdownPanel [ngStyle]=\"dropdownStyle\">\n <!-- Main menu with actions -->\n <nile-menu *ngIf=\"!isFilterOpen\">\n <!-- Dynamically render all visible actions -->\n <ng-container *ngFor=\"let action of visibleActions; let i = index; let last = last\">\n <nile-menu-item \n (click)=\"onActionClick(action)\"\n [class.disabled]=\"isActionDisabled(action)\"\n [class.active]=\"isActionActive(action)\">\n <span class=\"checkmark\" *ngIf=\"isActionActive(action)\">\u2713</span>\n <nile-icon slot=\"prefix\" *ngIf=\"action.icon && !isActionActive(action)\" [name]=\"action.icon\"></nile-icon>\n <span class=\"action-label\">{{ action.label }}</span>\n </nile-menu-item>\n \n <!-- Add divider after action groups -->\n <nile-divider *ngIf=\"shouldShowDividerAfter(action, i, last)\"></nile-divider>\n </ng-container>\n \n <!-- Fallback if no actions -->\n <nile-menu-item *ngIf=\"visibleActions.length === 0\">\n No actions available\n </nile-menu-item>\n </nile-menu>\n \n <!-- Filter popup (conditionally rendered) -->\n <st-column-filter\n #filterPopup\n *ngIf=\"isFilterOpen && context\"\n [column]=\"context.column\"\n [tableState]=\"context.tableState\"\n [columnIndex]=\"context.columnIndex\"\n [isFirstColumn]=\"context.isFirstColumn\"\n [isLastColumn]=\"context.isLastColumn\"\n [isOpen]=\"isFilterOpen\"\n (filterApplied)=\"onFilterApplied($event)\"\n (filterCleared)=\"onFilterCleared()\"\n (closed)=\"onFilterClosed()\">\n </st-column-filter>\n </div>\n</div>\n", styles: [".dropdown-container{position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9998}.dropdown-backdrop{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:auto;z-index:9998}.column-menu-dropdown{min-width:200px;max-width:300px;background-color:#fff;border-radius:8px;box-shadow:0 10px 25px #00000026;overflow:hidden;pointer-events:auto;z-index:9999}nile-menu nile-divider::part(divider){margin:0}nile-menu nile-menu-item::part(base){height:2.5rem;min-height:auto}nile-menu nile-menu-item .checkmark{margin-right:8px;color:#4299e1;font-weight:700}\n"] }]
8263
8406
  }], propDecorators: { isOpen: [{
8264
8407
  type: Input
8265
8408
  }], position: [{
@@ -8273,6 +8416,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
8273
8416
  }], filterPopup: [{
8274
8417
  type: ViewChild,
8275
8418
  args: ['filterPopup']
8419
+ }], dropdownPanel: [{
8420
+ type: ViewChild,
8421
+ args: ['dropdownPanel']
8276
8422
  }], onBackdropClick: [{
8277
8423
  type: HostListener,
8278
8424
  args: ['click', ['$event']]
@@ -8285,7 +8431,7 @@ class StRowActionsDropdownComponent {
8285
8431
  */
8286
8432
  this.isOpen = false;
8287
8433
  /**
8288
- * Position of the dropdown (x, y coordinates)
8434
+ * Position of the dropdown (x, y coordinates, triggerTop for flip positioning)
8289
8435
  */
8290
8436
  this.position = { x: 0, y: 0 };
8291
8437
  /**
@@ -8340,35 +8486,40 @@ class StRowActionsDropdownComponent {
8340
8486
  this.dropdownStyle = {};
8341
8487
  return;
8342
8488
  }
8343
- const DROPDOWN_WIDTH = 200; // Approximate width
8344
- const DROPDOWN_HEIGHT = this.visibleActions.length * 40 + 16; // Approximate height
8345
- const viewportWidth = window.innerWidth;
8346
- const viewportHeight = window.innerHeight;
8347
8489
  let left = this.position.x;
8348
8490
  let top = this.position.y;
8349
- // Check if dropdown would overflow right edge
8350
- if (left + DROPDOWN_WIDTH > viewportWidth) {
8351
- left = viewportWidth - DROPDOWN_WIDTH - 10;
8352
- }
8353
- // Check if dropdown would overflow bottom edge
8354
- if (top + DROPDOWN_HEIGHT > viewportHeight) {
8355
- // Position above the trigger
8356
- top = this.position.y - DROPDOWN_HEIGHT;
8357
- }
8358
- // Ensure dropdown doesn't go off-screen on the left
8359
- if (left < 10) {
8360
- left = 10;
8361
- }
8362
- // Ensure dropdown doesn't go off-screen on the top
8363
- if (top < 10) {
8364
- top = 10;
8365
- }
8491
+ // Render at initial position first (hidden until measured)
8366
8492
  this.dropdownStyle = {
8367
8493
  position: 'fixed',
8368
8494
  left: `${left}px`,
8369
8495
  top: `${top}px`,
8370
- zIndex: TableZIndex.ROW_ACTIONS_DROPDOWN
8496
+ zIndex: TableZIndex.ROW_ACTIONS_DROPDOWN,
8497
+ visibility: 'hidden'
8371
8498
  };
8499
+ // After rendering, measure actual size and adjust position directly on the DOM
8500
+ // (OnPush change detection won't pick up property changes inside requestAnimationFrame)
8501
+ requestAnimationFrame(() => {
8502
+ var _a, _b;
8503
+ const el = (_a = this.dropdownPanel) === null || _a === void 0 ? void 0 : _a.nativeElement;
8504
+ if (!el)
8505
+ return;
8506
+ const viewportWidth = window.innerWidth;
8507
+ const viewportHeight = window.innerHeight;
8508
+ const dropdownWidth = el.offsetWidth || 200;
8509
+ const dropdownHeight = el.offsetHeight || (this.visibleActions.length * 40 + 16);
8510
+ if (left + dropdownWidth > viewportWidth) {
8511
+ left = viewportWidth - dropdownWidth - 10;
8512
+ }
8513
+ if (top + dropdownHeight > viewportHeight) {
8514
+ const triggerTop = (_b = this.position.triggerTop) !== null && _b !== void 0 ? _b : this.position.y;
8515
+ top = triggerTop - dropdownHeight - 4;
8516
+ }
8517
+ left = Math.max(10, left);
8518
+ top = Math.max(10, top);
8519
+ el.style.left = `${left}px`;
8520
+ el.style.top = `${top}px`;
8521
+ el.style.visibility = 'visible';
8522
+ });
8372
8523
  }
8373
8524
  /**
8374
8525
  * Handle action click
@@ -8422,10 +8573,10 @@ class StRowActionsDropdownComponent {
8422
8573
  }
8423
8574
  }
8424
8575
  StRowActionsDropdownComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: StRowActionsDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8425
- StRowActionsDropdownComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.4.0", type: StRowActionsDropdownComponent, selector: "st-row-actions-dropdown", inputs: { isOpen: "isOpen", position: "position", context: "context" }, outputs: { actionClicked: "actionClicked", closed: "closed" }, host: { listeners: { "document:keydown.escape": "onEscapeKey($event)" } }, usesOnChanges: true, ngImport: i0, template: "<div class=\"dropdown-container\" *ngIf=\"isOpen && context\">\n <!-- Backdrop -->\n <div class=\"dropdown-overlay\" (click)=\"closed.emit()\"></div>\n \n <!-- Dropdown menu -->\n <div class=\"dropdown-menu\" [ngStyle]=\"dropdownStyle\">\n <nile-menu *ngIf=\"isOpen\" class=\"dropdown-menu\" [style.left.px]=\"dropdownStyle.left\" [style.top.px]=\"dropdownStyle.top\">\n <ng-container *ngFor=\"let action of visibleActions\">\n <nile-menu-item [class.disabled]=\"isActionDisabled(action)\" (click)=\"onActionClick(action)\" class=\"action-label\">\n <nile-icon *ngIf=\"action.icon\" size=\"14\" slot=\"prefix\" [name]=\"action.icon\"></nile-icon>\n {{ action.label }}\n </nile-menu-item>\n </ng-container>\n \n <nile-menu-item *ngIf=\"visibleActions.length === 0\">No actions available</nile-menu-item>\n </nile-menu>\n </div>\n\n </div>\n", styles: [".dropdown-container{position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9998}.dropdown-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background-color:transparent;pointer-events:auto;z-index:9998}.dropdown-menu{position:fixed;background-color:#fff;box-shadow:0 5px 15px #0000004d,0 0 0 1px #0000001a;overflow:hidden;pointer-events:auto;z-index:9999}.action-icon{display:flex;align-items:center;justify-content:center;font-size:16px;width:20px;flex-shrink:0}.action-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dropdown-empty{padding:16px;text-align:center;color:#a0aec0;font-size:14px;font-style:italic}nile-menu{height:fit-content}\n"], directives: [{ type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8576
+ StRowActionsDropdownComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.4.0", type: StRowActionsDropdownComponent, selector: "st-row-actions-dropdown", inputs: { isOpen: "isOpen", position: "position", context: "context" }, outputs: { actionClicked: "actionClicked", closed: "closed" }, host: { listeners: { "document:keydown.escape": "onEscapeKey($event)" } }, viewQueries: [{ propertyName: "dropdownPanel", first: true, predicate: ["dropdownPanel"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"dropdown-container\" *ngIf=\"isOpen && context\">\n <!-- Backdrop -->\n <div class=\"dropdown-overlay\" (click)=\"closed.emit()\"></div>\n \n <!-- Dropdown menu -->\n <div class=\"dropdown-menu\" #dropdownPanel [ngStyle]=\"dropdownStyle\">\n <nile-menu *ngIf=\"isOpen\">\n <ng-container *ngFor=\"let action of visibleActions\">\n <nile-menu-item [class.disabled]=\"isActionDisabled(action)\" (click)=\"onActionClick(action)\" class=\"action-label\">\n <nile-icon *ngIf=\"action.icon\" size=\"14\" slot=\"prefix\" [name]=\"action.icon\"></nile-icon>\n {{ action.label }}\n </nile-menu-item>\n </ng-container>\n \n <nile-menu-item *ngIf=\"visibleActions.length === 0\">No actions available</nile-menu-item>\n </nile-menu>\n </div>\n</div>\n", styles: [".dropdown-container{position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9998}.dropdown-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background-color:transparent;pointer-events:auto;z-index:9998}.dropdown-menu{position:fixed;background-color:#fff;box-shadow:0 5px 15px #0000004d,0 0 0 1px #0000001a;overflow:hidden;pointer-events:auto;z-index:9999}.action-icon{display:flex;align-items:center;justify-content:center;font-size:16px;width:20px;flex-shrink:0}.action-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dropdown-empty{padding:16px;text-align:center;color:#a0aec0;font-size:14px;font-style:italic}nile-menu{height:fit-content}\n"], directives: [{ type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8426
8577
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: StRowActionsDropdownComponent, decorators: [{
8427
8578
  type: Component,
8428
- args: [{ selector: 'st-row-actions-dropdown', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"dropdown-container\" *ngIf=\"isOpen && context\">\n <!-- Backdrop -->\n <div class=\"dropdown-overlay\" (click)=\"closed.emit()\"></div>\n \n <!-- Dropdown menu -->\n <div class=\"dropdown-menu\" [ngStyle]=\"dropdownStyle\">\n <nile-menu *ngIf=\"isOpen\" class=\"dropdown-menu\" [style.left.px]=\"dropdownStyle.left\" [style.top.px]=\"dropdownStyle.top\">\n <ng-container *ngFor=\"let action of visibleActions\">\n <nile-menu-item [class.disabled]=\"isActionDisabled(action)\" (click)=\"onActionClick(action)\" class=\"action-label\">\n <nile-icon *ngIf=\"action.icon\" size=\"14\" slot=\"prefix\" [name]=\"action.icon\"></nile-icon>\n {{ action.label }}\n </nile-menu-item>\n </ng-container>\n \n <nile-menu-item *ngIf=\"visibleActions.length === 0\">No actions available</nile-menu-item>\n </nile-menu>\n </div>\n\n </div>\n", styles: [".dropdown-container{position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9998}.dropdown-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background-color:transparent;pointer-events:auto;z-index:9998}.dropdown-menu{position:fixed;background-color:#fff;box-shadow:0 5px 15px #0000004d,0 0 0 1px #0000001a;overflow:hidden;pointer-events:auto;z-index:9999}.action-icon{display:flex;align-items:center;justify-content:center;font-size:16px;width:20px;flex-shrink:0}.action-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dropdown-empty{padding:16px;text-align:center;color:#a0aec0;font-size:14px;font-style:italic}nile-menu{height:fit-content}\n"] }]
8579
+ args: [{ selector: 'st-row-actions-dropdown', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"dropdown-container\" *ngIf=\"isOpen && context\">\n <!-- Backdrop -->\n <div class=\"dropdown-overlay\" (click)=\"closed.emit()\"></div>\n \n <!-- Dropdown menu -->\n <div class=\"dropdown-menu\" #dropdownPanel [ngStyle]=\"dropdownStyle\">\n <nile-menu *ngIf=\"isOpen\">\n <ng-container *ngFor=\"let action of visibleActions\">\n <nile-menu-item [class.disabled]=\"isActionDisabled(action)\" (click)=\"onActionClick(action)\" class=\"action-label\">\n <nile-icon *ngIf=\"action.icon\" size=\"14\" slot=\"prefix\" [name]=\"action.icon\"></nile-icon>\n {{ action.label }}\n </nile-menu-item>\n </ng-container>\n \n <nile-menu-item *ngIf=\"visibleActions.length === 0\">No actions available</nile-menu-item>\n </nile-menu>\n </div>\n</div>\n", styles: [".dropdown-container{position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9998}.dropdown-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background-color:transparent;pointer-events:auto;z-index:9998}.dropdown-menu{position:fixed;background-color:#fff;box-shadow:0 5px 15px #0000004d,0 0 0 1px #0000001a;overflow:hidden;pointer-events:auto;z-index:9999}.action-icon{display:flex;align-items:center;justify-content:center;font-size:16px;width:20px;flex-shrink:0}.action-label{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dropdown-empty{padding:16px;text-align:center;color:#a0aec0;font-size:14px;font-style:italic}nile-menu{height:fit-content}\n"] }]
8429
8580
  }], propDecorators: { isOpen: [{
8430
8581
  type: Input
8431
8582
  }], position: [{
@@ -8436,6 +8587,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
8436
8587
  type: Output
8437
8588
  }], closed: [{
8438
8589
  type: Output
8590
+ }], dropdownPanel: [{
8591
+ type: ViewChild,
8592
+ args: ['dropdownPanel']
8439
8593
  }], onEscapeKey: [{
8440
8594
  type: HostListener,
8441
8595
  args: ['document:keydown.escape', ['$event']]
@@ -9730,10 +9884,10 @@ class StTableComponent {
9730
9884
  event.stopPropagation();
9731
9885
  const target = event.currentTarget;
9732
9886
  const rect = target.getBoundingClientRect();
9733
- // Calculate position (below the button by default)
9734
9887
  const position = {
9735
9888
  x: rect.left,
9736
- y: rect.bottom + 4 // 4px gap
9889
+ y: rect.bottom + 4,
9890
+ triggerTop: rect.top
9737
9891
  };
9738
9892
  // Create context
9739
9893
  const context = {
@@ -9795,10 +9949,10 @@ class StTableComponent {
9795
9949
  event.stopPropagation();
9796
9950
  const target = event.currentTarget;
9797
9951
  const rect = target.getBoundingClientRect();
9798
- // Calculate position (below the button by default)
9799
9952
  const position = {
9800
9953
  x: rect.left,
9801
- y: rect.bottom + 4 // 4px gap
9954
+ y: rect.bottom + 4,
9955
+ triggerTop: rect.top
9802
9956
  };
9803
9957
  // Create context
9804
9958
  const context = {