@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.
@@ -3889,15 +3889,128 @@ class NileSelectEditor {
3889
3889
  // Clear container and append select
3890
3890
  context.container.innerHTML = '';
3891
3891
  context.container.appendChild(this.select);
3892
- // Focus the select after render
3892
+ // Focus after mount; open dropdown only once nile-popup exists (avoids null this.popup in nile-select)
3893
+ const openOnEdit = this.options.openDropdownOnEdit !== false;
3893
3894
  setTimeout(() => {
3895
+ void this.activateSelectAfterMount(openOnEdit);
3896
+ }, 50);
3897
+ }
3898
+ async activateSelectAfterMount(openOnEdit) {
3899
+ try {
3900
+ this.select?.focus();
3901
+ }
3902
+ catch {
3903
+ // ignore
3904
+ }
3905
+ if (!openOnEdit || !this.select || this.options.disabled) {
3906
+ return;
3907
+ }
3908
+ await this.openNileSelectDropdownSafely();
3909
+ }
3910
+ /** Lit @query('nile-popup') / .select — must exist before handleOpenChange touches this.popup.popup */
3911
+ isNileSelectPopupMounted(select) {
3912
+ return !!select.shadowRoot?.querySelector('nile-popup');
3913
+ }
3914
+ async invokeShowHandlingPromise(host) {
3915
+ if (typeof host.show !== 'function') {
3916
+ return;
3917
+ }
3918
+ try {
3919
+ const out = host.show();
3920
+ if (out != null && typeof out.then === 'function') {
3921
+ await out.catch(() => undefined);
3922
+ }
3923
+ }
3924
+ catch {
3925
+ // sync throw from show()
3926
+ }
3927
+ }
3928
+ async openNileSelectDropdownSafely() {
3929
+ const select = this.select;
3930
+ if (!select) {
3931
+ return;
3932
+ }
3933
+ try {
3934
+ await customElements.whenDefined('nile-select');
3935
+ }
3936
+ catch {
3937
+ // ignore
3938
+ }
3939
+ if (this.options.enableVirtualScroll) {
3894
3940
  try {
3895
- this.select?.focus();
3941
+ await customElements.whenDefined('nile-virtual-select');
3896
3942
  }
3897
- catch (e) {
3898
- // Ignore errors
3943
+ catch {
3944
+ // ignore — not registered in all environments
3945
+ }
3946
+ // Same idea as nile-popup: inner host may not exist until Lit finishes a pass (or several).
3947
+ const maxVirtualWaitPasses = 50;
3948
+ for (let i = 0; i < maxVirtualWaitPasses; i++) {
3949
+ const vs = select.shadowRoot?.querySelector('nile-virtual-select');
3950
+ if (vs) {
3951
+ await this.invokeShowHandlingPromise(vs);
3952
+ return;
3953
+ }
3954
+ const lit = select;
3955
+ if (lit.updateComplete?.then) {
3956
+ try {
3957
+ await lit.updateComplete;
3958
+ }
3959
+ catch {
3960
+ // ignore
3961
+ }
3962
+ }
3963
+ await new Promise(resolve => requestAnimationFrame(() => resolve()));
3899
3964
  }
3900
- }, 50);
3965
+ return;
3966
+ }
3967
+ const maxWaitPasses = 50;
3968
+ for (let i = 0; i < maxWaitPasses; i++) {
3969
+ if (this.isNileSelectPopupMounted(select)) {
3970
+ break;
3971
+ }
3972
+ const lit = select;
3973
+ if (lit.updateComplete?.then) {
3974
+ try {
3975
+ await lit.updateComplete;
3976
+ }
3977
+ catch {
3978
+ // ignore
3979
+ }
3980
+ }
3981
+ await new Promise(resolve => requestAnimationFrame(() => resolve()));
3982
+ }
3983
+ if (!this.isNileSelectPopupMounted(select)) {
3984
+ return;
3985
+ }
3986
+ const host = select;
3987
+ for (let attempt = 0; attempt < 5; attempt++) {
3988
+ if (!this.isNileSelectPopupMounted(select)) {
3989
+ return;
3990
+ }
3991
+ try {
3992
+ if (typeof host.show === 'function') {
3993
+ await this.invokeShowHandlingPromise(host);
3994
+ }
3995
+ else {
3996
+ host.open = true;
3997
+ }
3998
+ return;
3999
+ }
4000
+ catch {
4001
+ // ignore
4002
+ }
4003
+ await new Promise(resolve => setTimeout(resolve, 40));
4004
+ const lit = select;
4005
+ if (lit.updateComplete?.then) {
4006
+ try {
4007
+ await lit.updateComplete;
4008
+ }
4009
+ catch {
4010
+ // ignore
4011
+ }
4012
+ }
4013
+ }
3901
4014
  }
3902
4015
  /**
3903
4016
  * Set the initial value for the select
@@ -4945,6 +5058,7 @@ class NileChipEditor {
4945
5058
  this.acceptsInitialKeypress = false;
4946
5059
  this.eventListeners = [];
4947
5060
  this.trackedValues = [];
5061
+ this.hasChangeOccurred = false;
4948
5062
  }
4949
5063
  edit(context) {
4950
5064
  if (!context.container) {
@@ -5097,6 +5211,7 @@ class NileChipEditor {
5097
5211
  const detail = e.detail;
5098
5212
  const newValue = Array.isArray(detail?.value) ? [...detail.value] : (this.chip?.value ? [...this.chip.value] : []);
5099
5213
  this.trackedValues = newValue;
5214
+ this.hasChangeOccurred = true;
5100
5215
  context.onChange(newValue);
5101
5216
  });
5102
5217
  // Keyboard handling on the chip element
@@ -5165,6 +5280,7 @@ class NileChipEditor {
5165
5280
  this.chip = undefined;
5166
5281
  this.cellContainer = undefined;
5167
5282
  this.trackedValues = [];
5283
+ this.hasChangeOccurred = false;
5168
5284
  }
5169
5285
  focus() {
5170
5286
  try {
@@ -5173,7 +5289,7 @@ class NileChipEditor {
5173
5289
  catch { /* ignore */ }
5174
5290
  }
5175
5291
  getCurrentValue() {
5176
- if (this.trackedValues.length > 0) {
5292
+ if (this.hasChangeOccurred) {
5177
5293
  return [...this.trackedValues];
5178
5294
  }
5179
5295
  if (!this.chip)
@@ -6381,8 +6497,14 @@ class StCellComponent {
6381
6497
  }
6382
6498
  }
6383
6499
  onCellClick() {
6384
- // Single click now only focuses, doesn't start editing
6385
- // Focus is handled by parent st-table component
6500
+ // First click: parent <td> focuses this cell (bubbles after this handler).
6501
+ // Second click while already focused: enter edit (spreadsheet-style).
6502
+ if (this.isEditable &&
6503
+ !this.cellLoading &&
6504
+ !this.cell.isEditing() &&
6505
+ this.cell.isFocused()) {
6506
+ this.startEdit();
6507
+ }
6386
6508
  }
6387
6509
  onCellDoubleClick() {
6388
6510
  // Double-click starts editing regardless of EditMode (Excel-like behavior)
@@ -8096,7 +8218,7 @@ class StColumnMenuDropdownComponent {
8096
8218
  */
8097
8219
  this.isOpen = false;
8098
8220
  /**
8099
- * Position of the dropdown (x, y coordinates)
8221
+ * Position of the dropdown (x, y coordinates, triggerTop for flip positioning)
8100
8222
  */
8101
8223
  this.position = { x: 0, y: 0 };
8102
8224
  /**
@@ -8209,25 +8331,41 @@ class StColumnMenuDropdownComponent {
8209
8331
  this.dropdownStyle = {};
8210
8332
  return;
8211
8333
  }
8212
- const viewportWidth = window.innerWidth;
8213
- const viewportHeight = window.innerHeight;
8214
- const dropdownWidth = 280; // Approximate dropdown width
8215
- const dropdownHeight = 300; // Approximate max dropdown height
8216
8334
  let { x, y } = this.position;
8217
- // Adjust horizontal position if dropdown would overflow
8218
- if (x + dropdownWidth > viewportWidth) {
8219
- x = Math.max(10, viewportWidth - dropdownWidth - 10);
8220
- }
8221
- // Adjust vertical position if dropdown would overflow
8222
- if (y + dropdownHeight > viewportHeight) {
8223
- y = Math.max(10, viewportHeight - dropdownHeight - 10);
8224
- }
8335
+ // Render at initial position first
8225
8336
  this.dropdownStyle = {
8226
8337
  position: 'fixed',
8227
8338
  left: `${x}px`,
8228
8339
  top: `${y}px`,
8229
- 'z-index': 9999
8340
+ 'z-index': 9999,
8341
+ visibility: 'hidden'
8230
8342
  };
8343
+ // After rendering, measure actual size and adjust if needed
8344
+ requestAnimationFrame(() => {
8345
+ const viewportWidth = window.innerWidth;
8346
+ const viewportHeight = window.innerHeight;
8347
+ const el = this.dropdownPanel?.nativeElement;
8348
+ const dropdownWidth = el?.offsetWidth || 280;
8349
+ const dropdownHeight = el?.offsetHeight || 200;
8350
+ // Adjust horizontal position if dropdown would overflow
8351
+ if (x + dropdownWidth > viewportWidth) {
8352
+ x = Math.max(10, viewportWidth - dropdownWidth - 10);
8353
+ }
8354
+ // Adjust vertical position — flip above the trigger if it overflows bottom
8355
+ if (y + dropdownHeight > viewportHeight) {
8356
+ const triggerTop = this.position.triggerTop ?? this.position.y;
8357
+ y = triggerTop - dropdownHeight - 4;
8358
+ }
8359
+ // Clamp to viewport edges
8360
+ x = Math.max(10, x);
8361
+ y = Math.max(10, y);
8362
+ this.dropdownStyle = {
8363
+ position: 'fixed',
8364
+ left: `${x}px`,
8365
+ top: `${y}px`,
8366
+ 'z-index': 9999
8367
+ };
8368
+ });
8231
8369
  }
8232
8370
  /**
8233
8371
  * Check if an action is disabled
@@ -8294,10 +8432,10 @@ class StColumnMenuDropdownComponent {
8294
8432
  }
8295
8433
  }
8296
8434
  StColumnMenuDropdownComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: StColumnMenuDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8297
- 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"] }] });
8435
+ 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"] }] });
8298
8436
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: StColumnMenuDropdownComponent, decorators: [{
8299
8437
  type: Component,
8300
- 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"] }]
8438
+ 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"] }]
8301
8439
  }], propDecorators: { isOpen: [{
8302
8440
  type: Input
8303
8441
  }], position: [{
@@ -8311,6 +8449,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
8311
8449
  }], filterPopup: [{
8312
8450
  type: ViewChild,
8313
8451
  args: ['filterPopup']
8452
+ }], dropdownPanel: [{
8453
+ type: ViewChild,
8454
+ args: ['dropdownPanel']
8314
8455
  }], onBackdropClick: [{
8315
8456
  type: HostListener,
8316
8457
  args: ['click', ['$event']]
@@ -8323,7 +8464,7 @@ class StRowActionsDropdownComponent {
8323
8464
  */
8324
8465
  this.isOpen = false;
8325
8466
  /**
8326
- * Position of the dropdown (x, y coordinates)
8467
+ * Position of the dropdown (x, y coordinates, triggerTop for flip positioning)
8327
8468
  */
8328
8469
  this.position = { x: 0, y: 0 };
8329
8470
  /**
@@ -8378,35 +8519,39 @@ class StRowActionsDropdownComponent {
8378
8519
  this.dropdownStyle = {};
8379
8520
  return;
8380
8521
  }
8381
- const DROPDOWN_WIDTH = 200; // Approximate width
8382
- const DROPDOWN_HEIGHT = this.visibleActions.length * 40 + 16; // Approximate height
8383
- const viewportWidth = window.innerWidth;
8384
- const viewportHeight = window.innerHeight;
8385
8522
  let left = this.position.x;
8386
8523
  let top = this.position.y;
8387
- // Check if dropdown would overflow right edge
8388
- if (left + DROPDOWN_WIDTH > viewportWidth) {
8389
- left = viewportWidth - DROPDOWN_WIDTH - 10;
8390
- }
8391
- // Check if dropdown would overflow bottom edge
8392
- if (top + DROPDOWN_HEIGHT > viewportHeight) {
8393
- // Position above the trigger
8394
- top = this.position.y - DROPDOWN_HEIGHT;
8395
- }
8396
- // Ensure dropdown doesn't go off-screen on the left
8397
- if (left < 10) {
8398
- left = 10;
8399
- }
8400
- // Ensure dropdown doesn't go off-screen on the top
8401
- if (top < 10) {
8402
- top = 10;
8403
- }
8524
+ // Render at initial position first (hidden until measured)
8404
8525
  this.dropdownStyle = {
8405
8526
  position: 'fixed',
8406
8527
  left: `${left}px`,
8407
8528
  top: `${top}px`,
8408
- zIndex: TableZIndex.ROW_ACTIONS_DROPDOWN
8529
+ zIndex: TableZIndex.ROW_ACTIONS_DROPDOWN,
8530
+ visibility: 'hidden'
8409
8531
  };
8532
+ // After rendering, measure actual size and adjust position directly on the DOM
8533
+ // (OnPush change detection won't pick up property changes inside requestAnimationFrame)
8534
+ requestAnimationFrame(() => {
8535
+ const el = this.dropdownPanel?.nativeElement;
8536
+ if (!el)
8537
+ return;
8538
+ const viewportWidth = window.innerWidth;
8539
+ const viewportHeight = window.innerHeight;
8540
+ const dropdownWidth = el.offsetWidth || 200;
8541
+ const dropdownHeight = el.offsetHeight || (this.visibleActions.length * 40 + 16);
8542
+ if (left + dropdownWidth > viewportWidth) {
8543
+ left = viewportWidth - dropdownWidth - 10;
8544
+ }
8545
+ if (top + dropdownHeight > viewportHeight) {
8546
+ const triggerTop = this.position.triggerTop ?? this.position.y;
8547
+ top = triggerTop - dropdownHeight - 4;
8548
+ }
8549
+ left = Math.max(10, left);
8550
+ top = Math.max(10, top);
8551
+ el.style.left = `${left}px`;
8552
+ el.style.top = `${top}px`;
8553
+ el.style.visibility = 'visible';
8554
+ });
8410
8555
  }
8411
8556
  /**
8412
8557
  * Handle action click
@@ -8460,10 +8605,10 @@ class StRowActionsDropdownComponent {
8460
8605
  }
8461
8606
  }
8462
8607
  StRowActionsDropdownComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: StRowActionsDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8463
- 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 });
8608
+ 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 });
8464
8609
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: StRowActionsDropdownComponent, decorators: [{
8465
8610
  type: Component,
8466
- 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"] }]
8611
+ 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"] }]
8467
8612
  }], propDecorators: { isOpen: [{
8468
8613
  type: Input
8469
8614
  }], position: [{
@@ -8474,6 +8619,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
8474
8619
  type: Output
8475
8620
  }], closed: [{
8476
8621
  type: Output
8622
+ }], dropdownPanel: [{
8623
+ type: ViewChild,
8624
+ args: ['dropdownPanel']
8477
8625
  }], onEscapeKey: [{
8478
8626
  type: HostListener,
8479
8627
  args: ['document:keydown.escape', ['$event']]
@@ -9751,10 +9899,10 @@ class StTableComponent {
9751
9899
  event.stopPropagation();
9752
9900
  const target = event.currentTarget;
9753
9901
  const rect = target.getBoundingClientRect();
9754
- // Calculate position (below the button by default)
9755
9902
  const position = {
9756
9903
  x: rect.left,
9757
- y: rect.bottom + 4 // 4px gap
9904
+ y: rect.bottom + 4,
9905
+ triggerTop: rect.top
9758
9906
  };
9759
9907
  // Create context
9760
9908
  const context = {
@@ -9815,10 +9963,10 @@ class StTableComponent {
9815
9963
  event.stopPropagation();
9816
9964
  const target = event.currentTarget;
9817
9965
  const rect = target.getBoundingClientRect();
9818
- // Calculate position (below the button by default)
9819
9966
  const position = {
9820
9967
  x: rect.left,
9821
- y: rect.bottom + 4 // 4px gap
9968
+ y: rect.bottom + 4,
9969
+ triggerTop: rect.top
9822
9970
  };
9823
9971
  // Create context
9824
9972
  const context = {