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
4 changes: 2 additions & 2 deletions .github/workflows/angular.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9
version: 10
run_install: false
- name: Get pnpm store directory
shell: bash
Expand Down Expand Up @@ -61,7 +61,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9
version: 10
run_install: false
- name: Get pnpm store directory
shell: bash
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build-and-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9
version: 10
run_install: false

- name: Get pnpm store directory
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9
version: 10
run_install: false
- name: Get pnpm store directory
- name: Get pnpm store directory
shell: bash
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- name: Setup pnpm cache
Expand Down Expand Up @@ -56,7 +56,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9
version: 10
run_install: false
- name: Get pnpm store directory
shell: bash
Expand Down Expand Up @@ -88,7 +88,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9
version: 10
run_install: false
- name: Get pnpm store directory
shell: bash
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9
version: 10
- name: Setup Node
uses: actions/setup-node@v4
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/react.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9
version: 10
run_install: false
- name: Get pnpm store directory
shell: bash
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tokens.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9
version: 10
run_install: false
- name: Get pnpm store directory
shell: bash
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.idea
node_modules
.DS_Store
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Agent Instructions

## Git Rules
- Never commit `.DS_Store` files
- Never commit OS-specific files (Thumbs.db, etc.)
3 changes: 3 additions & 0 deletions angular/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -175,5 +175,8 @@
"@schematics/angular:resolver": {
"typeSeparator": "."
}
},
"cli": {
"analytics": false
}
}
22 changes: 20 additions & 2 deletions core/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const globals = require('globals');
const tsParser = require('@typescript-eslint/parser');
const typescriptEslint = require('@typescript-eslint/eslint-plugin');
const js = require('@eslint/js');
const jsxA11y = require('eslint-plugin-jsx-a11y');

const { FlatCompat } = require('@eslint/eslintrc');

Expand Down Expand Up @@ -33,10 +34,16 @@ module.exports = defineConfig([
}
},

extends: compat.extends('eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'),
extends: compat.extends(
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:jsx-a11y/recommended',
'prettier'
),

plugins: {
'@typescript-eslint': typescriptEslint
'@typescript-eslint': typescriptEslint,
'jsx-a11y': jsxA11y
},

rules: {
Expand All @@ -52,6 +59,17 @@ module.exports = defineConfig([
allowTernary: true,
allowShortCircuit: true
}
],
// A11y rules - adjusted for Stencil web components
'jsx-a11y/click-events-have-key-events': 'warn',
'jsx-a11y/no-static-element-interactions': 'warn',
'jsx-a11y/no-noninteractive-tabindex': 'warn',
// Allow Stencil component patterns
'jsx-a11y/anchor-is-valid': [
'error',
{
aspects: ['invalidHref']
}
]
}
},
Expand Down
3 changes: 3 additions & 0 deletions core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,11 @@
"@types/toastify-js": "^1.12.3",
"@typescript-eslint/eslint-plugin": "8.52.0",
"@typescript-eslint/parser": "8.52.0",
"axe-core": "4.11.1",
"babel-loader": "9.2.1",
"eslint": "9.39.2",
"eslint-config-prettier": "9.1.2",
"eslint-plugin-jsx-a11y": "6.10.0",
"jest": "29.7.0",
"jest-cli": "29.7.0",
"prettier": "3.7.4",
Expand Down
43 changes: 37 additions & 6 deletions core/src/components/cat-button/cat-button.e2e.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,41 @@
import { expect } from '@playwright/test';
import { test } from '@stencil/playwright';
import { newE2EPage } from '@stencil/core/testing';
import { checkA11y, formatViolations } from '../../utils/a11y-test';

test.describe('cat-button', () => {
test('renders', async ({ page }) => {
describe('cat-button', () => {
it('renders', async () => {
const page = await newE2EPage();
await page.setContent('<cat-button></cat-button>');
const element = await page.locator('cat-button');
await expect(element).toHaveClass('hydrated');

const element = await page.find('cat-button');
expect(element).toHaveClass('hydrated');
});

describe('accessibility', () => {
it('should have no a11y violations with text content', async () => {
const page = await newE2EPage();
await page.setContent('<cat-button>Click me</cat-button>');

const violations = await checkA11y(page);
expect(formatViolations(violations)).toBe('No violations');
expect(violations).toEqual([]);
});

it('should have no a11y violations with icon and a11y-label', async () => {
const page = await newE2EPage();
await page.setContent('<cat-button icon="check" a11y-label="Confirm action"></cat-button>');

const violations = await checkA11y(page);
expect(formatViolations(violations)).toBe('No violations');
expect(violations).toEqual([]);
});

it('should have no a11y violations when disabled', async () => {
const page = await newE2EPage();
await page.setContent('<cat-button disabled>Disabled button</cat-button>');

const violations = await checkA11y(page);
expect(formatViolations(violations)).toBe('No violations');
expect(violations).toEqual([]);
});
});
});
1 change: 1 addition & 0 deletions core/src/components/cat-date-inline/cat-date-inline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ export class CatDateInline {
<Host aria-label={this.label || undefined}>
<div class={{ 'label-container': true, 'label-hidden': this.labelHidden }}>
{(this.hasSlottedLabel || this.label) && (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions -- onClick provides focus to datepicker, which is intentional for search-ui
<label id={`${this.id}-label`} htmlFor={this.id} part="label" onClick={() => this.doFocus()}>
<span class="label-wrapper">
{(this.hasSlottedLabel && <slot name="label"></slot>) || this.label}
Expand Down
2 changes: 1 addition & 1 deletion core/src/components/cat-i18n/cat-i18n-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class CatI18nRegistry {
this._locale = Intl.getCanonicalLocales(locale)[0];
log.info(`[CatI18nRegistry::${this.id}] Set locale: ${this._locale}`);
!silent && window.dispatchEvent(this.buildEvent('cat-i18n-setLocale', { locale, id: this.id }));
} catch (err) {
} catch {
log.error(`[CatI18nRegistry::${this.id}] Invalid locale: ${locale}`);
}
}
Expand Down
1 change: 1 addition & 0 deletions core/src/components/cat-input/cat-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ export class CatInput {
</div>
<div class={{ 'input-color': this.type === 'color', 'input-container': true }}>
<div class="input-outer-wrapper">
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions -- clicking wrapper focuses the native input; no keyboard handler needed as input itself handles keyboard */}
<div
class={{
'input-wrapper': true,
Expand Down
12 changes: 6 additions & 6 deletions core/src/components/cat-menu/cat-menu.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -340,24 +340,24 @@ describe('cat-menu', () => {
});

describe('menu content', () => {
it('should render nav with role="menu" and default vertical orientation', async () => {
it('should render div with role="menu" and default vertical orientation', async () => {
const page = await newSpecPage({
components: [CatMenu, CatDropdown, CatButton],
html: `<cat-menu></cat-menu>`
});

const nav = page.root?.shadowRoot?.querySelector('nav[role="menu"]');
expect(nav?.getAttribute('aria-orientation')).toBe('vertical');
const menu = page.root?.shadowRoot?.querySelector('div[role="menu"]');
expect(menu?.getAttribute('aria-orientation')).toBe('vertical');
});

it('should render nav with horizontal orientation when arrowNavigation is horizontal', async () => {
it('should render div with horizontal orientation when arrowNavigation is horizontal', async () => {
const page = await newSpecPage({
components: [CatMenu, CatDropdown, CatButton],
html: `<cat-menu arrow-navigation="horizontal"></cat-menu>`
});

const nav = page.root?.shadowRoot?.querySelector('nav[role="menu"]');
expect(nav?.getAttribute('aria-orientation')).toBe('horizontal');
const menu = page.root?.shadowRoot?.querySelector('div[role="menu"]');
expect(menu?.getAttribute('aria-orientation')).toBe('horizontal');
});
});

Expand Down
4 changes: 2 additions & 2 deletions core/src/components/cat-menu/cat-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -284,11 +284,11 @@ export class CatMenu {
>
{!this.triggerIconOnly && <slot name="trigger-label">{this.triggerLabel}</slot>}
</cat-button>
<nav role="menu" slot="content" class="cat-menu-list" aria-orientation={this.arrowNavigation}>
<div role="menu" slot="content" class="cat-menu-list" aria-orientation={this.arrowNavigation}>
<ul>
<slot></slot>
</ul>
</nav>
</div>
</cat-dropdown>
</Host>
);
Expand Down
2 changes: 0 additions & 2 deletions core/src/components/cat-radio/cat-radio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,6 @@ export class CatRadio {
'align-center': this.alignment === 'center',
'align-end': this.alignment === 'bottom'
}}
role="radio"
aria-checked={this.checked ? 'true' : 'false'}
>
<span class="radio">
<input
Expand Down
6 changes: 5 additions & 1 deletion core/src/components/cat-select/cat-select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -634,10 +634,12 @@ export class CatSelect {
</div>

<div class="select-container">
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events -- keyboard handling is managed by the component's @Listen('keydown') decorator */}
<div
class={{ 'select-wrapper': true, 'select-disabled': this.disabled, 'select-invalid': this.invalid }}
ref={el => (this.trigger = el)}
id={this.id}
tabIndex={this.disabled ? -1 : 0}
role="combobox"
aria-expanded={this.state.isOpen || this.isPillboxActive()}
aria-controls={this.isPillboxActive() ? `select-pillbox-${this.id}` : `select-listbox-${this.id}`}
Expand Down Expand Up @@ -706,6 +708,7 @@ export class CatSelect {
class="select-input"
role="combobox"
ref={el => (this.input = el)}
aria-expanded={this.state.isOpen}
aria-controls={this.isPillboxActive() ? `select-pillbox-${this.id}` : `select-listbox-${this.id}`}
aria-activedescendant={this.activeDescendant}
aria-invalid={this.invalid ? 'true' : undefined}
Expand Down Expand Up @@ -781,7 +784,7 @@ export class CatSelect {
class="select-options"
role="listbox"
aria-multiselectable={this.multiple}
aria-setsize={this.state.totalElements}
aria-label={this.label || i18n.t('select.options')}
id={`select-listbox-${this.id}`}
>
{this.optionsList}
Expand Down Expand Up @@ -861,6 +864,7 @@ export class CatSelect {
</span>
</cat-checkbox>
) : (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions -- keyboard navigation handled by parent combobox via @Listen('keydown')
<div
class={{
'select-option-inner': true,
Expand Down
2 changes: 1 addition & 1 deletion core/src/components/cat-tag/cat-tag.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe('cat-tag', () => {
<div class="label-container"></div>
<div class="input-wrapper">
<div class="input-inner-wrapper">
<input class="tags-input" id="tags-cat-input-0-input" part="input" role="combobox">
<input class="tags-input" id="tags-cat-input-0-input" part="input">
</div>
</div>
`);
Expand Down
1 change: 0 additions & 1 deletion core/src/components/cat-tag/cat-tag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,6 @@ export class CatTag {
part="input"
id={`tags-${this.id}-input`}
class="tags-input"
role="combobox"
ref={el => (this.input = el as HTMLInputElement)}
aria-invalid={this.invalid ? 'true' : undefined}
aria-describedby={this.hasHint ? this.id + '-hint' : undefined}
Expand Down
Loading
Loading