Skip to content
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ All notable changes for each version of this project will be documented in this
<span #tooltipRef="tooltip" igxTooltip>Hello there, I am a tooltip!</span>
```

- `IgxOverlayService`
- Integrated HTML Popover API into the overlay service for improved z-index management and layering control.
- The overlay service now uses the Popover API to place overlay elements in the top layer, eliminating z-index stacking issues.
- Improved positioning accuracy for container-based overlays with fixed container bounds.

### General

- `IgxGrid`, `IgxTreeGrid`, `IgxHierarchicalGrid`, `IgxPivotGrid`
Expand Down
61 changes: 36 additions & 25 deletions projects/igniteui-angular/combo/src/combo/combo.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1016,13 +1016,14 @@ describe('igxCombo', () => {
fixture.detectChanges();
verifyDropdownItemHeight();
}));
it('should render grouped items properly', (done) => {
it('should render grouped items properly', async () => {
let dropdownContainer;
let dropdownItems;
let scrollIndex = 0;
const headers: Array<string> = Array.from(new Set(combo.data.map(item => item.region)));
combo.toggle();
await wait();
fixture.detectChanges();

const checkGroupedItemsClass = () => {
fixture.detectChanges();
dropdownContainer = fixture.debugElement.query(By.css(`.${CSS_CLASS_CONTAINER}`)).nativeElement;
Expand All @@ -1033,18 +1034,18 @@ describe('igxCombo', () => {
const expectedClass: string = headers.includes(itemText) ? CSS_CLASS_HEADERITEM : CSS_CLASS_DROPDOWNLISTITEM;
expect(itemElement.classList.contains(expectedClass)).toBeTruthy();
});
scrollIndex += 10;
if (scrollIndex < combo.data.length) {
combo.virtualScrollContainer.scrollTo(scrollIndex);
combo.virtualScrollContainer.chunkLoad.pipe(take(1)).subscribe(async () => {
await wait(30);
checkGroupedItemsClass();
});
} else {
done();
}
};

// Check initial state
checkGroupedItemsClass();

// Scroll through the list in chunks and verify items
for (let scrollIndex = 10; scrollIndex < combo.data.length; scrollIndex += 10) {
combo.virtualScrollContainer.scrollTo(scrollIndex);
await firstValueFrom(combo.virtualScrollContainer.chunkLoad);
await wait(30);
checkGroupedItemsClass();
}
});
it('should render selected items properly', () => {
combo.toggle();
Expand Down Expand Up @@ -1195,8 +1196,8 @@ describe('igxCombo', () => {
const comboWrapper = fixture.debugElement.query(By.css(CSS_CLASS_COMBO)).nativeElement;
let containerElementWidth = containerElement.getBoundingClientRect().width;
let wrapperWidth = comboWrapper.getBoundingClientRect().width;
expect(containerElementWidth).toEqual(containerWidth);
expect(containerElementWidth).toEqual(wrapperWidth);
expect(containerElementWidth).toBeCloseTo(containerWidth, 1);
expect(containerElementWidth).toBeCloseTo(wrapperWidth, 1);

combo.toggle();
tick();
Expand Down Expand Up @@ -1764,6 +1765,7 @@ describe('igxCombo', () => {
it('should properly navigate using HOME/END key', (async () => {
let firstVisibleItem: Element;
combo.toggle();
await wait();
fixture.detectChanges();
const dropdownContent = fixture.debugElement.query(By.css(`.${CSS_CLASS_CONTENT}`));
const scrollbar = fixture.debugElement.query(By.css(`.${CSS_CLASS_SCROLLBAR_VERTICAL}`)).nativeElement as HTMLElement;
Expand All @@ -1773,7 +1775,7 @@ describe('igxCombo', () => {
await firstValueFrom(combo.virtualScrollContainer.chunkLoad);
fixture.detectChanges();
// Content was scrolled to bottom
expect(scrollbar.scrollHeight - scrollbar.scrollTop).toEqual(scrollbar.clientHeight);
expect(scrollbar.scrollHeight - scrollbar.scrollTop - scrollbar.clientHeight).toBeLessThan(1);

// Scroll to top
UIInteractions.triggerEventHandlerKeyDown('Home', dropdownContent);
Expand All @@ -1782,7 +1784,7 @@ describe('igxCombo', () => {
const dropdownContainer: HTMLElement = fixture.debugElement.query(By.css(`.${CSS_CLASS_CONTAINER}`)).nativeElement;
firstVisibleItem = dropdownContainer.querySelector(`.${CSS_CLASS_DROPDOWNLISTITEM}` + ':first-child');
// Container is scrolled to top
expect(scrollbar.scrollTop).toEqual(32);
expect(scrollbar.scrollTop - 32).toBeLessThan(1);

// First item is focused
expect(firstVisibleItem.classList.contains(CSS_CLASS_FOCUSED)).toBeTruthy();
Expand All @@ -1791,7 +1793,7 @@ describe('igxCombo', () => {
firstVisibleItem = dropdownContainer.querySelector(`.${CSS_CLASS_DROPDOWNLISTITEM}` + ':first-child');

// Scroll has not change
expect(scrollbar.scrollTop).toEqual(32);
expect(scrollbar.scrollTop - 32).toBeLessThan(1);
// First item is no longer focused
expect(firstVisibleItem.classList.contains(CSS_CLASS_FOCUSED)).toBeFalsy();
UIInteractions.triggerEventHandlerKeyDown('Home', dropdownContent);
Expand Down Expand Up @@ -1895,6 +1897,7 @@ describe('igxCombo', () => {
input = fixture.debugElement.query(By.css(`.${CSS_CLASS_INPUTGROUP}`));
let firstVisibleItem: Element;
combo.toggle();
await wait();
fixture.detectChanges();
const dropdownContent = fixture.debugElement.query(By.css(`.${CSS_CLASS_CONTENT}`));
const scrollbar = fixture.debugElement.query(By.css(`.${CSS_CLASS_SCROLLBAR_VERTICAL}`))
Expand All @@ -1905,7 +1908,7 @@ describe('igxCombo', () => {
await firstValueFrom(combo.virtualScrollContainer.chunkLoad);
fixture.detectChanges();
// Content was scrolled to bottom
expect(scrollbar.scrollHeight - scrollbar.scrollTop).toEqual(scrollbar.clientHeight);
expect(scrollbar.scrollHeight - scrollbar.scrollTop - scrollbar.clientHeight).toBeLessThan(1);

// Scroll to top
UIInteractions.triggerEventHandlerKeyDown('Home', dropdownContent);
Expand Down Expand Up @@ -1983,8 +1986,9 @@ describe('igxCombo', () => {
vContainerScrollHeight = virtDir.getScroll().scrollHeight;
expect(virtDir.getScroll().scrollTop).toEqual(vContainerScrollHeight / 2);
});
it('should display vertical scrollbar properly', () => {
it('should display vertical scrollbar properly', async () => {
combo.toggle();
await wait();
fixture.detectChanges();
const scrollbarContainer = fixture.debugElement
.query(By.css(`.${CSS_CLASS_SCROLLBAR_VERTICAL}`))
Expand All @@ -1995,12 +1999,14 @@ describe('igxCombo', () => {
combo.data = [{ field: 'Mid-Atlantic', region: 'New Jersey' }, { field: 'Mid-Atlantic', region: 'New York' }];
fixture.detectChanges();
combo.toggle();
await wait();
fixture.detectChanges();
hasScrollbar = scrollbarContainer.scrollHeight > scrollbarContainer.clientHeight;
expect(hasScrollbar).toBeFalsy();
});
it('should preserve selection on scrolling', async () => {
combo.toggle();
await wait();
fixture.detectChanges();
const scrollbar = fixture.debugElement.query(By.css(`.${CSS_CLASS_SCROLLBAR_VERTICAL}`)).nativeElement as HTMLElement;
expect(scrollbar.scrollTop).toEqual(0);
Expand All @@ -2019,7 +2025,7 @@ describe('igxCombo', () => {
await firstValueFrom(combo.virtualScrollContainer.chunkLoad);
fixture.detectChanges();
// Content was scrolled to bottom
expect(scrollbar.scrollHeight - scrollbar.scrollTop).toEqual(scrollbar.clientHeight);
expect(scrollbar.scrollHeight - scrollbar.scrollTop - scrollbar.clientHeight).toBeLessThan(1);

combo.virtualScrollContainer.scrollTo(4);
await firstValueFrom(combo.virtualScrollContainer.chunkLoad);
Expand Down Expand Up @@ -2577,7 +2583,7 @@ describe('igxCombo', () => {
fixture.detectChanges();
expect(combo.dropdown.headers[0].element.nativeElement.innerText).toEqual('New England')
});
it('should sort groups with diacritics correctly', () => {
it('should sort groups with diacritics correctly', async() => {
combo.data = [
{ field: "Alaska", region: "Méxícó" },
{ field: "California", region: "Méxícó" },
Expand All @@ -2589,6 +2595,7 @@ describe('igxCombo', () => {
];
combo.groupSortingDirection = SortingDirection.Asc;
combo.toggle();
await wait();
fixture.detectChanges();
let headers = combo.dropdown.headers.map(header => header.element.nativeElement.innerText);
expect(headers).toEqual(['Ángel', 'Boris', 'México']);
Expand Down Expand Up @@ -2745,9 +2752,11 @@ describe('igxCombo', () => {
combo.filterFunction = comboIgnoreDiacriticsFilter;
combo.displayKey = null;
combo.valueKey = null;
combo.groupKey = null;
combo.filteringOptions = { caseSensitive: false, filteringKey: undefined };
combo.data = ['José', 'Óscar', 'Ángel', 'Germán', 'Niño', 'México', 'Méxícó', 'Mexico', 'Köln', 'München'];
combo.toggle();
tick();
fixture.detectChanges();

const searchInput = fixture.debugElement.query(By.css(`input[name="searchInput"]`));
Expand All @@ -2762,8 +2771,8 @@ describe('igxCombo', () => {

verifyFilteredItems('jose', 1);
verifyFilteredItems('mexico', 3);
verifyFilteredItems('o', 6);
verifyFilteredItems('é', 6);
verifyFilteredItems('o', 7);
verifyFilteredItems('é', 7);
}));

it('should filter the dropdown items when typing in the search input', fakeAsync(() => {
Expand All @@ -2779,6 +2788,7 @@ describe('igxCombo', () => {
};

combo.toggle();
tick();
fixture.detectChanges();
const searchInput = fixture.debugElement.query(By.css('input[name=\'searchInput\']'));
const verifyFilteredItems = (inputValue: string, expectedItemsNumber) => {
Expand Down Expand Up @@ -2842,10 +2852,11 @@ describe('igxCombo', () => {
verifyOnSearchInputEventIsFired('Miss');
verifyOnSearchInputEventIsFired('Misso');
});
it('should restore the initial combo dropdown list after clearing the search input', () => {
it('should restore the initial combo dropdown list after clearing the search input', fakeAsync(() => {
let dropdownList;
let dropdownItems;
combo.toggle();
tick();
fixture.detectChanges();
const searchInput = fixture.debugElement.query(By.css(CSS_CLASS_SEARCHINPUT));

Expand All @@ -2864,7 +2875,7 @@ describe('igxCombo', () => {
verifyFilteredItems('Mi', 3, 5);
verifyFilteredItems('M', 4, 15);
combo.filteredData.forEach((item) => expect(combo.data).toContain(item));
});
}));
it('should clear the search input and close the dropdown list on pressing ESC key', fakeAsync(() => {
combo.toggle();
fixture.detectChanges();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,23 @@
background: transparent;
transition: background .25s $in-out-quad;
pointer-events: none;
z-index: 10005;
box-sizing: content-box;

// Override browser's default popover styles to maintain our custom positioning
&[popover] {
// Reset popover defaults to use our positioning with !important to override UA styles
position: fixed !important;
margin: 0 !important;
border: 0 !important;
padding: 0 !important;
width: auto;
height: auto;
overflow: visible !important;

&:not(:popover-open) {
display: initial !important;
}
}
}

%overlay-wrapper--modal {
Expand Down Expand Up @@ -79,6 +94,5 @@
pointer-events: none;
overflow: hidden;
appearance: none;
z-index: -1;
}
}
8 changes: 8 additions & 0 deletions projects/igniteui-angular/core/src/services/overlay/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
The overlay service allows users to show components on overlay div above all other elements in the page.
A walk through of how to get started can be found [here](https://www.infragistics.com/products/ignite-ui-angular/angular/components/overlay-main)

## Key Features

- **Popover API Integration**: Uses the HTML Popover API for improved z-index management and automatic top-layer placement
- **Flexible Positioning**: Multiple position strategies (global, connected, auto, elastic, container)
- **Scroll Strategies**: Handle scroll behavior with NoOp, Block, Close, and Absolute strategies
- **Modal Support**: Optional modal backdrop with configurable close-on-outside-click behavior
- **Animation Support**: Built-in support for open/close animations

## Usage

### With igxToggleDirective
Expand Down
Loading
Loading