Skip to content

Commit

Permalink
Merge pull request #492 from MerlinMoos/feat/menu-signal-based
Browse files Browse the repository at this point in the history
refactor(context-menu): migrate to signals (#418)
  • Loading branch information
elite-benni authored Nov 22, 2024
2 parents e5a12e3 + ba7a0b9 commit ab4b6b5
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 128 deletions.
10 changes: 5 additions & 5 deletions apps/ui-storybook-e2e/src/integration/menu/dropdown-menu.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ describe('dropdown-menu', () => {
'Support that has [disabled]="true" should be disabled', () => {
cy.checkA11y('#storybook-root', {
rules: {
'page-has-heading-one': { enabled: false },
'landmark-one-main': { enabled: false },
'page-has-heading-one': {enabled: false},
'landmark-one-main': {enabled: false},
},
});

Expand Down Expand Up @@ -53,7 +53,6 @@ describe('dropdown-menu', () => {
cy.findByText(/open/i).realClick().should('have.attr', 'aria-expanded', 'false');
});

/* TODO: @benpsnyder to fix
it('down on open button should open, up and down should navigate, right over sub menu should open submenu, and left on sub should close it, and escape open should close dropdown completely', () => {
cy.checkA11y('#storybook-root', {
rules: {
Expand All @@ -66,7 +65,9 @@ describe('dropdown-menu', () => {

cy.realPress('Tab');

cy.findByText(/open/i).should('have.focus').realPress('ArrowDown').should('have.attr', 'aria-expanded', 'true');
cy.findByText(/open/i).should('have.focus').realPress('ArrowDown');

cy.findByText(/open/i).should('have.attr', 'aria-expanded', 'true');

cy.findByRole('menu');

Expand All @@ -84,6 +85,5 @@ describe('dropdown-menu', () => {

cy.findByText(/open/i).should('have.focus').should('have.attr', 'aria-expanded', 'false');
});
*/
});
});
42 changes: 9 additions & 33 deletions libs/ui/menu/brain/src/lib/brn-context-menu-trigger.directive.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { CdkContextMenuTrigger } from '@angular/cdk/menu';
import { Directive, Input, type TemplateRef, effect, inject, signal } from '@angular/core';
import { Directive, effect, inject, input, type TemplateRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

export type BrnCtxMenuAlign = 'start' | 'center' | 'end' | undefined;
import { BrnMenuAlign, getBrnMenuAlign } from './brn-menu-align';

@Directive({
selector: '[brnCtxMenuTriggerFor]',
Expand All @@ -11,21 +10,9 @@ export type BrnCtxMenuAlign = 'start' | 'center' | 'end' | undefined;
})
export class BrnContextMenuTriggerDirective {
private readonly _cdkTrigger = inject(CdkContextMenuTrigger, { host: true });
private readonly _align = signal<BrnCtxMenuAlign>(undefined);
@Input()
public set align(value: BrnCtxMenuAlign) {
this._align.set(value);
}

@Input()
public set brnCtxMenuTriggerFor(value: TemplateRef<unknown> | null) {
this._cdkTrigger.menuTemplateRef = value;
}

@Input()
public set brnCtxMenuTriggerData(value: unknown) {
this._cdkTrigger.menuData = value;
}
public brnCtxMenuTriggerFor = input<TemplateRef<unknown> | null>(null);
public brnCtxMenuTriggerData = input<unknown>(undefined);
public readonly align = input<BrnMenuAlign>(undefined);

constructor() {
// once the trigger opens we wait until the next tick and then grab the last position
Expand All @@ -40,23 +27,12 @@ export class BrnContextMenuTriggerDirective {
),
);

effect(() => (this._cdkTrigger.menuTemplateRef = this.brnCtxMenuTriggerFor()));
effect(() => (this._cdkTrigger.menuData = this.brnCtxMenuTriggerData()));
effect(() => {
const align = this._align();
const align = this.align();
if (!align) return;
this._cdkTrigger.menuPosition = [
{
originX: align,
originY: 'bottom',
overlayX: align,
overlayY: 'top',
},
{
originX: align,
originY: 'top',
overlayX: align,
overlayY: 'bottom',
},
];
this._cdkTrigger.menuPosition = getBrnMenuAlign(align);
});
}
}
17 changes: 17 additions & 0 deletions libs/ui/menu/brain/src/lib/brn-menu-align.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ConnectedPosition } from '@angular/cdk/overlay';

export type BrnMenuAlign = 'start' | 'center' | 'end' | undefined;
export const getBrnMenuAlign = (align: Exclude<BrnMenuAlign, undefined>): ConnectedPosition[] => [
{
originX: align,
originY: 'bottom',
overlayX: align,
overlayY: 'top',
},
{
originX: align,
originY: 'top',
overlayX: align,
overlayY: 'bottom',
},
];
37 changes: 11 additions & 26 deletions libs/ui/menu/brain/src/lib/brn-menu-item-checkbox.directive.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,24 @@
import { CdkMenuItemCheckbox } from '@angular/cdk/menu';
import { Directive, Input, Output, booleanAttribute, inject, signal } from '@angular/core';
import { booleanAttribute, Directive, effect, inject, input } from '@angular/core';
import { outputFromObservable } from '@angular/core/rxjs-interop';

@Directive({
selector: '[brnMenuItemCheckbox]',
standalone: true,
hostDirectives: [CdkMenuItemCheckbox],
host: {
'[class.checked]': '_checked()',
'[disabled]': '_disabled()',
'[class.checked]': 'checked()',
'[disabled]': 'disabled()',
},
})
export class BrnMenuItemCheckboxDirective {
private readonly _cdkMenuItem = inject(CdkMenuItemCheckbox, { host: true });
private readonly _cdkMenuItem = inject(CdkMenuItemCheckbox);
public readonly checked = input(this._cdkMenuItem.checked, { transform: booleanAttribute });
public readonly disabled = input(this._cdkMenuItem.disabled, { transform: booleanAttribute });
public readonly triggered = outputFromObservable(this._cdkMenuItem.triggered);

protected readonly _checked = signal(this._cdkMenuItem.checked);
@Input({ transform: booleanAttribute })
public set checked(value: boolean) {
this._cdkMenuItem.checked = value;
this._checked.set(value);
constructor() {
effect(() => (this._cdkMenuItem.disabled = this.disabled()));
effect(() => (this._cdkMenuItem.checked = this.checked()));
}

public get checked() {
return this._checked();
}

protected readonly _disabled = signal(this._cdkMenuItem.disabled);
@Input({ transform: booleanAttribute })
public set disabled(value: boolean) {
this._cdkMenuItem.disabled = value;
this._disabled.set(value);
}
public get disabled() {
return this._disabled();
}

@Output()
public readonly triggered = this._cdkMenuItem.triggered;
}
36 changes: 11 additions & 25 deletions libs/ui/menu/brain/src/lib/brn-menu-item-radio.directive.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,24 @@
import { CdkMenuItemRadio } from '@angular/cdk/menu';
import { Directive, Input, Output, booleanAttribute, inject, signal } from '@angular/core';
import { booleanAttribute, Directive, effect, inject, input } from '@angular/core';
import { outputFromObservable } from '@angular/core/rxjs-interop';

@Directive({
selector: '[brnMenuItemRadio]',
standalone: true,
hostDirectives: [CdkMenuItemRadio],
host: {
'[class.checked]': '_checked()',
'[disabled]': '_disabled()',
'[class.checked]': 'checked()',
'[disabled]': 'disabled()',
},
})
export class BrnMenuItemRadioDirective {
private readonly _cdkMenuItem = inject(CdkMenuItemRadio, { host: true });
private readonly _cdkMenuItem = inject(CdkMenuItemRadio);
public readonly checked = input(this._cdkMenuItem.checked, { transform: booleanAttribute });
public readonly disabled = input(this._cdkMenuItem.disabled, { transform: booleanAttribute });
public readonly triggered = outputFromObservable(this._cdkMenuItem.triggered);

protected readonly _checked = signal(this._cdkMenuItem.checked);
@Input({ transform: booleanAttribute })
public set checked(value: boolean) {
this._cdkMenuItem.checked = value;
this._checked.set(value);
constructor() {
effect(() => (this._cdkMenuItem.disabled = this.disabled()));
effect(() => (this._cdkMenuItem.checked = this.checked()));
}
public get checked() {
return this._checked();
}

protected readonly _disabled = signal(this._cdkMenuItem.disabled);
@Input({ transform: booleanAttribute })
public set disabled(value: boolean) {
this._cdkMenuItem.disabled = value;
this._disabled.set(value);
}
public get disabled() {
return this._disabled();
}

@Output()
public readonly triggered = this._cdkMenuItem.triggered;
}
23 changes: 8 additions & 15 deletions libs/ui/menu/brain/src/lib/brn-menu-item.directive.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,21 @@
import { CdkMenuItem } from '@angular/cdk/menu';
import { Directive, Input, Output, booleanAttribute, inject, signal } from '@angular/core';
import { booleanAttribute, Directive, effect, inject, input } from '@angular/core';
import { outputFromObservable } from '@angular/core/rxjs-interop';

@Directive({
selector: '[brnMenuItem]',
standalone: true,
hostDirectives: [CdkMenuItem],
host: {
'[disabled]': '_disabled()',
'[disabled]': 'disabled()',
},
})
export class BrnMenuItemDirective {
private readonly _cdkMenuItem = inject(CdkMenuItem, { host: true });
private readonly _cdkMenuItem = inject(CdkMenuItem);
public readonly disabled = input(this._cdkMenuItem.disabled, { transform: booleanAttribute });
public readonly triggered = outputFromObservable(this._cdkMenuItem.triggered);

protected readonly _disabled = signal(this._cdkMenuItem.disabled);
@Input({ transform: booleanAttribute })
public set disabled(value: boolean) {
this._cdkMenuItem.disabled = value;
this._disabled.set(value);
constructor() {
effect(() => (this._cdkMenuItem.disabled = this.disabled()));
}

public get disabled() {
return this._disabled();
}

@Output()
public readonly triggered = this._cdkMenuItem.triggered;
}
29 changes: 5 additions & 24 deletions libs/ui/menu/brain/src/lib/brn-menu-trigger.directive.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { CdkMenuTrigger } from '@angular/cdk/menu';
import { Directive, Input, effect, inject, signal } from '@angular/core';
import { Directive, effect, inject, input } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';

export type BrnMenuAlign = 'start' | 'center' | 'end' | undefined;
import { BrnMenuAlign, getBrnMenuAlign } from './brn-menu-align';

@Directive({
selector: '[brnMenuTriggerFor]',
Expand All @@ -11,12 +10,7 @@ export type BrnMenuAlign = 'start' | 'center' | 'end' | undefined;
})
export class BrnMenuTriggerDirective {
private readonly _cdkTrigger = inject(CdkMenuTrigger, { host: true });
private readonly _align = signal<BrnMenuAlign>(undefined);

@Input()
public set align(value: BrnMenuAlign) {
this._align.set(value);
}
public readonly align = input<BrnMenuAlign>(undefined);

constructor() {
// once the trigger opens we wait until the next tick and then grab the last position
Expand All @@ -32,22 +26,9 @@ export class BrnMenuTriggerDirective {
);

effect(() => {
const align = this._align();
const align = this.align();
if (!align) return;
this._cdkTrigger.menuPosition = [
{
originX: align,
originY: 'bottom',
overlayX: align,
overlayY: 'top',
},
{
originX: align,
originY: 'top',
overlayX: align,
overlayY: 'bottom',
},
];
this._cdkTrigger.menuPosition = getBrnMenuAlign(align);
});
}
}

0 comments on commit ab4b6b5

Please sign in to comment.