Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
14 changes: 11 additions & 3 deletions api-generator/api-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -724,7 +724,13 @@ async function main() {
}

for (const key in mergedDocs) {
const typedocJSON = JSON.stringify(mergedDocs[key], null, 4);
const doc = mergedDocs[key];
const isEmpty =
Object.keys(doc).length === 1 &&
doc.components &&
Object.keys(doc.components).length === 0;
if (isEmpty) continue;
const typedocJSON = JSON.stringify(doc, null, 4);
!fs.existsSync(outputPath) && fs.mkdirSync(outputPath);
fs.writeFileSync(path.resolve(outputPath, `${key}.json`), typedocJSON);
}
Expand Down Expand Up @@ -814,7 +820,8 @@ const getTypesValue = (typeobj) => {
// Example: { name: string; age: number } -> { "name": "string", "age": "number" }
if (Array.isArray(children) && children.length) {
const entries = children.map((ch) => ({
[ch.name]: ch.type?.toString?.() ?? 'unknown'
[ch.flags?.isOptional ? ch.name + '?' : ch.name]:
ch.type?.toString?.() ?? 'unknown'
}));
return JSON.stringify(Object.assign({}, ...entries), null, 4);
}
Expand All @@ -824,7 +831,8 @@ const getTypesValue = (typeobj) => {
// Example: { name: string; age: number } -> { "name": "string", "age": "number" }
if (type?.type === 'reflection' && type.declaration?.children?.length) {
const entries = type.declaration.children.map((ch) => ({
[ch.name]: ch.type?.toString?.() ?? 'unknown'
[ch.flags?.isOptional ? ch.name + '?' : ch.name]:
ch.type?.toString?.() ?? 'unknown'
}));
return JSON.stringify(Object.assign({}, ...entries), null, 4);
}
Expand Down
10 changes: 5 additions & 5 deletions playwright/cps-accessibility.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,11 @@ const components: ComponentEntry[] = [
// await page.locator('cps-select').first().click();
// }
// },
// {
// route: '/sidebar-menu',
// name: 'Sidebar menu',
// selector: 'cps-sidebar-menu'
// },
{
route: '/sidebar-menu',
name: 'Sidebar menu',
selector: 'cps-sidebar-menu'
},
// { route: '/switch', name: 'Switch', selector: 'cps-switch' },
// { route: '/tab-group', name: 'Tab group', selector: 'cps-tab-group' },
// { route: '/table', name: 'Table', selector: 'cps-table' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@
"values": [
{
"name": "CpsButtonToggleOption",
"value": "{\n \"value\": \"any\",\n \"label\": \"string\",\n \"ariaLabel\": \"string\",\n \"icon\": \"string\",\n \"disabled\": \"boolean\",\n \"tooltip\": \"string\"\n}",
"value": "{\n \"value\": \"any\",\n \"label?\": \"string\",\n \"ariaLabel?\": \"string\",\n \"icon?\": \"string\",\n \"disabled?\": \"boolean\",\n \"tooltip?\": \"string\"\n}",
"description": "CpsButtonToggleOption is used to define the options of the CpsButtonToggleComponent."
}
]
Expand Down
2 changes: 1 addition & 1 deletion projects/composition/src/app/api-data/cps-menu.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@
"values": [
{
"name": "CpsMenuItem",
"value": "{\n \"title\": \"string\",\n \"action\": \"(event?: any) => void\",\n \"icon\": \"string\",\n \"desc\": \"string\",\n \"url\": \"string\",\n \"target\": \"string\",\n \"disabled\": \"boolean\",\n \"loading\": \"boolean\"\n}",
"value": "{\n \"title?\": \"string\",\n \"ariaLabel?\": \"string\",\n \"action?\": \"(event?: any) => void\",\n \"icon?\": \"string\",\n \"desc?\": \"string\",\n \"url?\": \"string\",\n \"target?\": \"string\",\n \"disabled?\": \"boolean\",\n \"loading?\": \"boolean\"\n}",
"description": "CpsMenuItem is used to define the items of the CpsMenuComponent."
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@
"values": [
{
"name": "CpsRadioOption",
"value": "{\n \"value\": \"any\",\n \"label\": \"string\",\n \"ariaLabel\": \"string\",\n \"disabled\": \"boolean\",\n \"tooltip\": \"string\"\n}",
"value": "{\n \"value\": \"any\",\n \"label?\": \"string\",\n \"ariaLabel?\": \"string\",\n \"disabled?\": \"boolean\",\n \"tooltip?\": \"string\"\n}",
"description": "CpsRadioOption is used to define the options of the CpsRadioGroupComponent."
}
]
Expand Down
10 changes: 9 additions & 1 deletion projects/composition/src/app/api-data/cps-sidebar-menu.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@
"default": "false",
"description": "Determines whether the menu items should allow activating only exact links."
},
{
"name": "ariaLabel",
"optional": false,
"readonly": false,
"type": "string",
"default": "Main navigation",
"description": "Aria label for the sidebar, used for accessibility."
},
{
"name": "height",
"optional": false,
Expand All @@ -46,7 +54,7 @@
"values": [
{
"name": "CpsSidebarMenuItem",
"value": "{\n \"title\": \"string\",\n \"icon\": \"string\",\n \"url\": \"string\",\n \"target\": \"string\",\n \"disabled\": \"boolean\",\n \"items\": \"CpsMenuItem[]\"\n}",
"value": "{\n \"title\": \"string\",\n \"icon\": \"string\",\n \"url?\": \"string\",\n \"target?\": \"string\",\n \"disabled?\": \"boolean\",\n \"items?\": \"CpsMenuItem[]\"\n}",
"description": "CpsSidebarMenuItem is used to define the items of the CpsSidebarMenuComponent."
}
]
Expand Down
2 changes: 1 addition & 1 deletion projects/composition/src/app/api-data/cps-table.json
Original file line number Diff line number Diff line change
Expand Up @@ -744,7 +744,7 @@
"values": [
{
"name": "CpsColumnFilterCategoryOption",
"value": "{\n \"value\": \"any\",\n \"label\": \"string\",\n \"icon\": \"string\",\n \"disabled\": \"boolean\",\n \"tooltip\": \"string\"\n}",
"value": "{\n \"value\": \"any\",\n \"label?\": \"string\",\n \"icon?\": \"string\",\n \"disabled?\": \"boolean\",\n \"tooltip?\": \"string\"\n}",
"description": "CpsColumnFilterCategoryOption is used to define the options of the CpsColumnFilterCategoryComponent."
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
styleUrls: ['./menu-page.component.scss'],
host: { class: 'composition-page' }
})
export class MenuPageComponent {
items: CpsMenuItem[] = [
{
title: 'First item',
desc: 'First item description',
icon: 'remove',
action: (event: any) => {

Check warning on line 19 in projects/composition/src/app/pages/menu-page/menu-page.component.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
this.doConsoleLog(event);
}
},
Expand Down Expand Up @@ -50,6 +50,7 @@
}
},
{
ariaLabel: 'Sixth item is loading',
loading: true
},
{
Expand All @@ -59,50 +60,51 @@
}
];

Check warning on line 61 in projects/composition/src/app/pages/menu-page/menu-page.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

itemsWithoutIcons: CpsMenuItem[] = [
{
title: 'First item',
desc: 'First item description',
action: (event: any) => {
this.doConsoleLog(event);
}
},
{
title: 'Second item',
desc: 'Second item is disabled',
disabled: true,
action: (event: any) => {
this.doConsoleLog(event);
}
},
{
title: 'Third item',
action: (event: any) => {
this.doConsoleLog(event);
}
},
{
title: 'Fourth item',
desc: 'Fourth item description',
action: (event: any) => {
this.doConsoleLog(event);
}
},
{
title: 'Fifth item',
action: (event: any) => {
this.doConsoleLog(event);
}
},
{
ariaLabel: 'Sixth item is loading',
loading: true
},
{
title: 'Go google',
url: 'https://google.com',
target: '_blank'
}
];

Check warning on line 107 in projects/composition/src/app/pages/menu-page/menu-page.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

componentData = ComponetnData;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
app-component-docs-viewer {
height: 100%;
}
padding: 0 24px 0 24px;
padding: 0 1.5rem 0 1.5rem;
.desc {
margin-top: 24px;
margin-top: 1.5rem;
color: var(--cps-color-text-dark);
margin-left: 24px;
font-size: 18px;
margin-left: 1.5rem;
font-size: 1.125rem;
}
::ng-deep {
.example-content {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
role="menuitem"
[class]="itemsClasses[i]"
tabindex="-1"
[attr.aria-label]="item.ariaLabel || null"
[attr.aria-disabled]="
item.disabled || item.loading ? true : null
"
Expand All @@ -58,6 +59,7 @@
role="menuitem"
[class]="itemsClasses[i]"
tabindex="-1"
[attr.aria-label]="item.ariaLabel || null"
[attr.aria-disabled]="
item.disabled || item.loading ? true : null
"
Expand All @@ -82,6 +84,7 @@
role="menuitem"
[class]="itemsClasses[i]"
tabindex="-1"
[attr.aria-label]="item.ariaLabel || null"
[attr.aria-disabled]="
item.disabled || item.loading ? true : null
"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
SimpleChanges,
ViewChild,
ViewEncapsulation,
ViewRef
ViewRef,
inject
} from '@angular/core';
import { OverlayService, SharedModule } from 'primeng/api';
import { ConnectedOverlayScrollHandler, DomHandler } from 'primeng/dom';
Expand All @@ -35,6 +36,7 @@
import { CpsIconComponent } from '../cps-icon/cps-icon.component';
import { CpsProgressCircularComponent } from '../cps-progress-circular/cps-progress-circular.component';
import { PrimeNG } from 'primeng/config';
import { CPS_INPUT_MODALITY_SERVICE } from '../../services/cps-input-modality/cps-input-modality.service';

type Nullable<T = void> = T | null | undefined;
type VoidListener = () => void | null | undefined;
Expand All @@ -45,6 +47,7 @@
*/
export type CpsMenuItem = {
title?: string;
ariaLabel?: string;
action?: (event?: any) => void;
icon?: string;
desc?: string;
Expand Down Expand Up @@ -243,6 +246,10 @@
itemsClasses: string[] = [];

hideReason: CpsMenuHideReason | undefined;
private _openedByKeyboard = false;
private readonly _cpsInputModalityService = inject(
CPS_INPUT_MODALITY_SERVICE
);

@ViewChild('menuArrow') private _menuArrow?: ElementRef<HTMLElement>;

Expand Down Expand Up @@ -274,6 +281,18 @@
if (this.compressed) this.withIcons = this.items.some((itm) => itm.icon);
this._setItemsClasses();
}

if (changes.items) {
const hasItemsA11yViolation = this.items.some(
(item) => !item.title?.trim() && !item.ariaLabel?.trim()

Check warning on line 287 in projects/cps-ui-kit/src/lib/components/cps-menu/cps-menu.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
);

if (hasItemsA11yViolation) {
console.error(
'CpsMenuComponent: all untitled menu items must have an ariaLabel for accessibility.'
);

Check warning on line 293 in projects/cps-ui-kit/src/lib/components/cps-menu/cps-menu.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 294 in projects/cps-ui-kit/src/lib/components/cps-menu/cps-menu.component.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
}
}

ngAfterViewInit(): void {
Expand Down Expand Up @@ -321,6 +340,8 @@

this.target = target || event?.currentTarget || event?.target;
if (this.target) this.resizeObserver.observe(this.target);
this._openedByKeyboard =
this._cpsInputModalityService?.lastInput() === 'keyboard';
this.overlayVisible = true;
this.render = true;
this.position = pos || 'default';
Expand Down Expand Up @@ -390,7 +411,7 @@
this.zone.run(() => {
this.hide(CpsMenuHideReason.KEYDOWN_ESCAPE);
});
(this.target as HTMLElement)?.focus();
this._focusTarget(this._openedByKeyboard);
break;
case 'Tab':
if (this.items.length > 0) {
Expand Down Expand Up @@ -708,6 +729,22 @@
});
}

private _focusTarget(showRing: boolean): void {
const el: HTMLElement | undefined | null = this.target;
if (!el) return;
if (!showRing) {
el.classList.add('suppress-focus-visible');
el.addEventListener(
'blur',
() => el.classList.remove('suppress-focus-visible'),
{
once: true
}
);
}
el.focus();
}

private _getMenuItems(): HTMLElement[] {
if (!this.container) return [];
return Array.from(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,50 +1,78 @@
<div
class="cps-sidebar-menu"
[style.height]="height"
[ngClass]="{ 'cps-sidebar-menu-collapsed': !isExpanded }">
@for (item of items; track item) {
@if (item.url) {
<a
class="cps-sidebar-menu-item"
[routerLink]="[item.url]"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: exactRoutes }"
[ngClass]="{ disabled: item.disabled }">
<cps-icon [icon]="item.icon" size="normal"> </cps-icon>
<span
class="cps-sidebar-menu-item-label"
[@onExpand]="isExpanded ? 'expanded' : 'collapsed'">
{{ item.title }}
</span>
</a>
[style.height]="cvtHeight()"
[class.cps-sidebar-menu-collapsed]="!isExpanded">
<nav
class="cps-sidebar-menubar"
[attr.aria-label]="ariaLabel"
[attr.aria-description]="`Sidebar ${isExpanded ? 'expanded' : 'collapsed'}`">
Comment thread
fateeand marked this conversation as resolved.
@for (item of items; track item) {
@if (item.url) {
<a
class="cps-sidebar-menu-item"
tabindex="0"
[routerLink]="item.disabled ? null : [item.url]"
routerLinkActive="active"
ariaCurrentWhenActive="page"
[routerLinkActiveOptions]="{ exact: exactRoutes }"
[class.disabled]="item.disabled"
[attr.aria-disabled]="item.disabled || null"
[attr.aria-label]="item.title">
<cps-icon [icon]="item.icon" size="normal" aria-hidden="true">
</cps-icon>
<span
class="cps-sidebar-menu-item-label"
[@onExpand]="isExpanded ? 'expanded' : 'collapsed'">
{{ item.title }}
</span>
</a>
}
@if (!item.url) {
<cps-menu
#popupMenu
[items]="item.items || []"
[header]="item.title"
[focusOnShow]="false"
showTransitionOptions="0s"
hideTransitionOptions="0s"
[withArrow]="false"
(containerMouseLeave)="leaveMenu($event, popupMenu)">
</cps-menu>
<button
type="button"
(mouseenter)="showMenu($event, popupMenu)"
(mouseleave)="leaveMenu($event, popupMenu)"
(focusin)="showMenu($event, popupMenu, item)"
(focusout)="leaveMenu($event, popupMenu)"
(click)="toggleMenu($event, popupMenu, item)"
class="cps-sidebar-menu-item menu-trigger"
[class.active]="isActive(item)"
[class.menu-open]="popupMenu.isVisible()"
[class.disabled]="item.disabled"
[attr.aria-haspopup]="'menu'"
[attr.aria-expanded]="
focusedItemWithMenu === item ? popupMenu.isVisible() : null
"
[attr.aria-current]="isActive(item) ? 'page' : null"
[attr.aria-disabled]="item.disabled || null"
[attr.aria-label]="item.title">
<cps-icon [icon]="item.icon" size="normal" aria-hidden="true">
</cps-icon>
<span
class="cps-sidebar-menu-item-label"
[@onExpand]="isExpanded ? 'expanded' : 'collapsed'">
{{ item.title }}
</span>
</button>
}
}
@if (!item.url) {
<cps-menu
#popupMenu
[items]="item.items || []"
[header]="item.title"
showTransitionOptions="0s"
hideTransitionOptions="0s"
[withArrow]="false">
</cps-menu>
<div
(click)="toggleMenu($event, popupMenu)"
class="cps-sidebar-menu-item menu-trigger"
[ngClass]="{
active: isActive(item),
'menu-open': popupMenu.isVisible(),
disabled: item.disabled
}">
<cps-icon [icon]="item.icon" size="normal"> </cps-icon>
<span
class="cps-sidebar-menu-item-label"
[@onExpand]="isExpanded ? 'expanded' : 'collapsed'">
{{ item.title }}
</span>
</div>
}
}
<div class="expand-area" (click)="toggleSidebar()">
<cps-icon icon="menu-shrink" size="fill"> </cps-icon>
</div>
</nav>
<button
type="button"
class="expand-area"
(click)="toggleSidebar()"
[attr.aria-label]="isExpanded ? 'Collapse sidebar' : 'Expand sidebar'"
[attr.aria-expanded]="isExpanded">
<cps-icon icon="menu-shrink" size="normal" aria-hidden="true"> </cps-icon>
</button>
</div>
Loading
Loading