diff --git a/docs/.vitepress/plugins/ComponentPreview.ts b/docs/.vitepress/plugins/ComponentPreview.ts index ed6972cae..c62bfebdb 100644 --- a/docs/.vitepress/plugins/ComponentPreview.ts +++ b/docs/.vitepress/plugins/ComponentPreview.ts @@ -64,6 +64,7 @@ export default function (md: MarkdownRenderer) { const { realPath, path: _path } = state.env as MarkdownEnv const childFiles = readdirSync(resolve(dirname(realPath ?? _path), pathName), { withFileTypes: false, recursive: true }) + .map(file => typeof file === 'string' ? file.split(/[/\\]/).join('/') : file) const groupedFiles = props.type === 'example' ? { tailwind: childFiles } diff --git a/docs/content/docs/guides/dates.md b/docs/content/docs/guides/dates.md index c7c292a8b..a29a07cf6 100644 --- a/docs/content/docs/guides/dates.md +++ b/docs/content/docs/guides/dates.md @@ -77,7 +77,7 @@ const date = new CalendarDate(1995, 8, 18) const minDate = new CalendarDate(1995, 8, 1) const maxDate = new CalendarDate(1995, 8, 31) -parseStringToDateValue('1995-08-18') // returns a DateValue object +parseStringToDateValue('1995-08-18', date) // returns a DateValue object toDate(date) // returns a Date object isCalendarDateTime(date) // returns false isZonedDateTime(date) // returns false diff --git a/packages/core/src/Listbox/ListboxFilter.vue b/packages/core/src/Listbox/ListboxFilter.vue index b14829d5d..e750e1cbb 100644 --- a/packages/core/src/Listbox/ListboxFilter.vue +++ b/packages/core/src/Listbox/ListboxFilter.vue @@ -62,7 +62,7 @@ onMounted(() => { :data-disabled="disabled ? '' : undefined" :aria-disabled="disabled ?? undefined" type="text" - @keydown.down.up.left.right.home.end.prevent="rootContext.onKeydownNavigation" + @keydown.down.up.home.end.prevent="rootContext.onKeydownNavigation" @keydown.enter="rootContext.onKeydownEnter" @input="(event: InputEvent) => { modelValue = (event.target as HTMLInputElement).value diff --git a/packages/core/src/Listbox/ListboxRoot.vue b/packages/core/src/Listbox/ListboxRoot.vue index a92244771..57b5746f1 100644 --- a/packages/core/src/Listbox/ListboxRoot.vue +++ b/packages/core/src/Listbox/ListboxRoot.vue @@ -374,7 +374,7 @@ provideListboxRootContext({ @focusout="async (event: FocusEvent) => { const target = (event.relatedTarget || event.target) as HTMLElement | null await nextTick() - if (highlightedElement && !currentElement.contains(target)) { + if (highlightedElement && currentElement && !currentElement.contains(target)) { onLeave(event) } }" diff --git a/packages/core/src/Pagination/Pagination.test.ts b/packages/core/src/Pagination/Pagination.test.ts index d2506f0d9..8d140af7d 100644 --- a/packages/core/src/Pagination/Pagination.test.ts +++ b/packages/core/src/Pagination/Pagination.test.ts @@ -56,6 +56,74 @@ describe('given default Pagination', () => { }) }) +const ALL_PAGINATION_BUTTONS_AS_A_PROPS = { + first: { as: 'a' }, + prev: { as: 'a' }, + listItem: { as: 'a' }, + next: { as: 'a' }, + last: { as: 'a' }, +} + +describe('given Pagination with as buttons', () => { + let wrapper: VueWrapper> + + beforeEach(() => { + document.body.innerHTML = '' + wrapper = mount(Pagination, { attachTo: document.body, props: { ...ALL_PAGINATION_BUTTONS_AS_A_PROPS } }) + }) + + it('should pass axe accessibility tests', async () => { + expect(await axe(wrapper.element)).toHaveNoViolations() + }) + + it('should not unselect page 1 after clicking on Prev Page trigger', async () => { + await wrapper.find('[aria-label="Previous Page"]').trigger('click') + expect(wrapper.find('[aria-label="Page 1"]').attributes('data-selected')).toBe('true') + }) + + it('should not unselect last page after clicking on Next Page trigger', async () => { + await wrapper.find('[aria-label="Last Page"]').trigger('click') + const lastPageLabel = wrapper.find('[data-selected="true"]').attributes('aria-label') + await wrapper.find('[aria-label="Next Page"]').trigger('click') + expect(wrapper.find('[data-selected="true"]').attributes('aria-label')).toBe(lastPageLabel) + }) +}) + +describe('given Pagination with as buttons and disabled', () => { + let wrapper: VueWrapper> + + const INITIAL_PAGE = 2 // Do not set to first or last page + + beforeEach(async () => { + document.body.innerHTML = '' + wrapper = mount(Pagination, { attachTo: document.body, props: { ...ALL_PAGINATION_BUTTONS_AS_A_PROPS } }) + await wrapper.find(`[aria-label="Page ${INITIAL_PAGE}"]`).trigger('click') + wrapper.setProps({ root: { disabled: true } }) + }) + + it('should pass axe accessibility tests', async () => { + expect(await axe(wrapper.element)).toHaveNoViolations() + }) + + it('should ignore clicking on First Page trigger', async () => { + await wrapper.find('[aria-label="First Page"]').trigger('click') + + expect(wrapper.find('[data-selected="true"]').attributes('aria-label')).toBe(`Page ${INITIAL_PAGE}`) + }) + + it('should ignore clicking on Last Page trigger', async () => { + await wrapper.find('[aria-label="Last Page"]').trigger('click') + + expect(wrapper.find('[data-selected="true"]').attributes('aria-label')).toBe(`Page ${INITIAL_PAGE}`) + }) + + it('should ignore clicking on any non-selected page', async () => { + await wrapper.find('[aria-label="Page 1"]').trigger('click') + + expect(wrapper.find('[data-selected="true"]').attributes('aria-label')).toBe(`Page ${INITIAL_PAGE}`) + }) +}) + describe('given show-edges Pagination', () => { let wrapper: VueWrapper> diff --git a/packages/core/src/Pagination/PaginationFirst.vue b/packages/core/src/Pagination/PaginationFirst.vue index 453ff6504..83ba07038 100644 --- a/packages/core/src/Pagination/PaginationFirst.vue +++ b/packages/core/src/Pagination/PaginationFirst.vue @@ -6,6 +6,7 @@ export interface PaginationFirstProps extends PrimitiveProps {}