Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
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 .changeset/persistent-pinned-menu.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@siemens/ix': patch
---

Keep pinned `ix-menu` components expanded when a menu item is selected.
5 changes: 5 additions & 0 deletions .changeset/reliable-tab-activation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@siemens/ix': patch
---

Fix `ix-tab-set` panel activation and `ix-menu-about` tab activation when child components are still initializing.
5 changes: 5 additions & 0 deletions .changeset/stable-date-picker-focus.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@siemens/ix': patch
---

Fix `ix-date-picker` keyboard navigation so focus consistently moves to the expected day.
5 changes: 5 additions & 0 deletions .changeset/steady-dropdown-triggers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@siemens/ix': patch
---

Fix `ix-dropdown` trigger initialization so dropdowns open reliably immediately after rendering or when triggers are added dynamically.
Original file line number Diff line number Diff line change
Expand Up @@ -345,9 +345,6 @@ regressionTest(
await expect(
chatInput.locator('ix-dropdown-button.attachment-overflow')
).toHaveCount(0);
await expect(chatInput.locator('.attachments')).not.toHaveClass(
/has-attachment-scrollbar/
);
await expect(
page.locator('[data-attachment-overflow-generated]')
).toHaveCount(0);
Expand Down
50 changes: 24 additions & 26 deletions packages/core/src/components/checkbox/tests/checkbox.ct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,7 @@ regressionTest(
const checkbox = page.locator('ix-checkbox');
await expect(checkbox).not.toHaveClass(/label-less/);
await expect(checkbox).toHaveText(/Custom slot label text/);
const width = await checkbox.evaluate((element) =>
Number.parseFloat(getComputedStyle(element).width)
);
expect(width).toBeGreaterThan(24);
await expect(checkbox.locator('ix-typography')).toBeVisible();
}
);

Expand All @@ -121,37 +118,38 @@ regressionTest('label', async ({ mount, page }) => {
await expect(checkboxElement).toHaveText(/some label/);
});

test('Checkbox should not cause layout shift when checked', async ({
mount,
page,
}) => {
await mount(`
regressionTest(
'Checkbox should not cause layout shift when checked',
async ({ mount, page }) => {
await mount(`
<ix-checkbox label="test"></ix-checkbox>
<div id="element-below">This element should not move</div>
`);

await page.waitForSelector('ix-checkbox', { state: 'attached' });
const checkbox = page.locator('ix-checkbox');
const elementBelow = page.locator('#element-below');

const initialBounds = await page.$eval('#element-below', (el) => {
const rect = el.getBoundingClientRect();
return { top: rect.top, left: rect.left };
});
await expect(checkbox).toHaveClass(/hydrated/);
await expect(elementBelow).toBeVisible();
await page.evaluate(() => document.fonts.ready);

await page.click('ix-checkbox');
const initialBounds = await elementBelow.boundingBox();
if (!initialBounds) {
throw new Error('Expected element below checkbox to have a bounding box');
}

await page.waitForFunction(() => {
const checkbox = document.querySelector('ix-checkbox');
return checkbox?.getAttribute('aria-checked') === 'true';
});
await checkbox.click();
await expect(checkbox).toHaveAttribute('aria-checked', 'true');

const newBounds = await page.$eval('#element-below', (el) => {
const rect = el.getBoundingClientRect();
return { top: rect.top, left: rect.left };
});
const newBounds = await elementBelow.boundingBox();
if (!newBounds) {
throw new Error('Expected element below checkbox to remain visible');
}

expect(newBounds.top).toBeCloseTo(initialBounds.top, 0);
expect(newBounds.left).toBeCloseTo(initialBounds.left, 0);
});
expect(newBounds.y).toBeCloseTo(initialBounds.y, 0);
expect(newBounds.x).toBeCloseTo(initialBounds.x, 0);
}
);

test.describe('accessibility', () => {
test('should expose aria-label for accessibility queries', async ({
Expand Down
18 changes: 15 additions & 3 deletions packages/core/src/components/date-picker/date-picker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@
break;
}

event.preventDefault();
return;
}

Expand Down Expand Up @@ -847,7 +848,18 @@
return rows;
}

public changeFocusedDay() {
public changeFocusedDay(focusTarget?: EventTarget) {
const focusedDayElement =
focusTarget instanceof HTMLElement
? focusTarget.closest<HTMLElement>('[data-calendar-day]')
: null;
const focusedDay = focusedDayElement?.dataset.calendarDay;

if (focusedDay) {
this.focusedDay = parseInt(focusedDay, 10);

Check warning on line 859 in packages/core/src/components/date-picker/date-picker.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `Number.parseInt` over `parseInt`.

See more on https://sonarcloud.io/project/issues?id=siemens_ix&issues=AZ-EpF8Q_n7zvn6nGw0d&open=AZ-EpF8Q_n7zvn6nGw0d&pullRequest=2666
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
return;
}

if (this.monthChangedFromFocus) {
return;
}
Expand Down Expand Up @@ -922,9 +934,9 @@
return (
<Host
onKeyDown={(event: KeyboardEvent) => this.onKeyDown(event)}
onFocusin={() => {
onFocusin={(event: FocusEvent) => {
if (hasKeyboardMode()) {
this.changeFocusedDay();
this.changeFocusedDay(event.composedPath()[0]);
}
}}
>
Expand Down
29 changes: 27 additions & 2 deletions packages/core/src/components/dropdown/dropdown-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface DropdownInterface extends IxComponentInterface {

getAssignedSubmenuIds(): string[];
getId(): string;
matchesTrigger(eventTargets: EventTarget[]): boolean;

discoverSubmenu(): void;

Expand Down Expand Up @@ -139,12 +140,36 @@ class DropdownController {
);
}

private getDropdownByTriggerPath(eventTargets: EventTarget[]) {
for (const dropdown of this.stack.values()) {
if (dropdown.matchesTrigger(eventTargets)) {
return dropdown;
}
}

return undefined;
}

private addOverlayListeners() {
this.isWindowListenerActive = true;

window.addEventListener('click', (event: MouseEvent) => {
const hasTrigger = this.pathIncludesTrigger(event.composedPath());
const hasDropdown = this.pathIncludesDropdown(event.composedPath());
const eventTargets = event.composedPath();
const hasTrigger = this.pathIncludesTrigger(eventTargets);
const hasDropdown = this.pathIncludesDropdown(eventTargets);

if (!hasTrigger && !event.defaultPrevented) {
const dropdown = this.getDropdownByTriggerPath(eventTargets);
if (dropdown) {
if (dropdown.isPresent()) {
this.dismiss(dropdown);
} else {
this.present(dropdown);
}
this.dismissOthers(dropdown.getId());
return;
}
}

if (!hasTrigger && !hasDropdown) {
this.dismissAll();
Expand Down
68 changes: 62 additions & 6 deletions packages/core/src/components/dropdown/dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,19 @@ export class Dropdown
return this.dropdownElementId;
}

matchesTrigger(eventTargets: EventTarget[]) {
const trigger =
this.trigger ?? this.hostElement.getAttribute('trigger') ?? undefined;

return eventTargets.some(
(target) =>
target === trigger ||
(typeof trigger === 'string' &&
target instanceof HTMLElement &&
target.id === trigger)
);
}
Comment thread
danielleroux marked this conversation as resolved.

Comment thread
coderabbitai[bot] marked this conversation as resolved.
willDismiss() {
const { defaultPrevented } = this.showChange.emit(false);
return !defaultPrevented;
Expand Down Expand Up @@ -505,6 +518,23 @@ export class Dropdown
}

private async registerListener(element: ElementReference) {
const immediateElement = this.resolveImmediateElement(element);
const canRegisterImmediately =
immediateElement &&
(!hasDropdownItemWrapperImplemented(immediateElement) ||
immediateElement.tagName === 'IX-DROPDOWN-ITEM');

if (canRegisterImmediately) {
this.triggerElement = immediateElement;
if (immediateElement.tagName === 'IX-DROPDOWN-ITEM') {
(immediateElement as HTMLIxDropdownItemElement).isSubMenu = true;
this.hostElement.style.zIndex = `var(--theme-z-index-dropdown)`;
}
this.addEventListenersFor();
this.discoverSubmenu();
return;
}

Comment thread
coderabbitai[bot] marked this conversation as resolved.
this.triggerElement = await this.resolveElement(element);

if (!this.triggerElement) {
Expand Down Expand Up @@ -586,6 +616,32 @@ export class Dropdown
return this.checkForSubmenuAnchor(el);
}

private resolveImmediateElement(
element: ElementReference
): HTMLElement | undefined {
if (element instanceof Promise) {
return undefined;
}

if (element instanceof HTMLElement) {
return element;
}

const documentElement = document.getElementById(element);
if (documentElement) {
return documentElement;
}

const root = this.hostElement.getRootNode();
if (root instanceof ShadowRoot) {
return (
root.querySelector<HTMLElement>(`#${CSS.escape(element)}`) ?? undefined
);
}

return undefined;
}

private async checkForSubmenuAnchor(element?: Element) {
if (!element) {
return undefined;
Expand Down Expand Up @@ -686,7 +742,7 @@ export class Dropdown
}

@Watch('trigger')
changedTrigger(
async changedTrigger(
newTriggerValue: ElementReference,
oldTriggerValue: ElementReference | undefined
) {
Expand All @@ -697,7 +753,7 @@ export class Dropdown
this.disposeKeyListener = undefined;
}

this.registerListener(newTriggerValue);
await this.registerListener(newTriggerValue);
}

private applyFallbackPosition(element: HTMLElement) {
Expand All @@ -706,9 +762,9 @@ export class Dropdown
this.hostElement.parentElement || this.hostElement;
const refRect = referenceElement.getBoundingClientRect();

const transform = `translate(${Math.round(
refRect.left
)}px, ${Math.round(refRect.top)}px)`;
const transform = `translate(${Math.round(refRect.left)}px, ${Math.round(
refRect.top
)}px)`;

Object.assign(element.style, {
top: '0',
Expand Down Expand Up @@ -864,7 +920,7 @@ export class Dropdown
return;
}

this.changedTrigger(this.trigger, undefined);
await this.changedTrigger(this.trigger, undefined);
}

override async componentDidRender() {
Expand Down
Loading
Loading