@wordpress/ui 0.11.0 → 0.12.1-next.v.202604201441.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +4 -4
- package/build/alert-dialog/popup.cjs +4 -4
- package/build/alert-dialog/popup.cjs.map +2 -2
- package/build/collapsible-card/header.cjs +10 -0
- package/build/collapsible-card/header.cjs.map +3 -3
- package/build/dialog/context.cjs +21 -9
- package/build/dialog/context.cjs.map +2 -2
- package/build/dialog/footer.cjs +4 -4
- package/build/dialog/footer.cjs.map +2 -2
- package/build/dialog/header.cjs +4 -4
- package/build/dialog/header.cjs.map +2 -2
- package/build/dialog/popup.cjs +4 -4
- package/build/dialog/popup.cjs.map +2 -2
- package/build/dialog/title.cjs +9 -6
- package/build/dialog/title.cjs.map +2 -2
- package/build/form/primitives/select/item.cjs +3 -3
- package/build/form/primitives/select/item.cjs.map +2 -2
- package/build/form/primitives/select/popup.cjs +3 -3
- package/build/form/primitives/select/popup.cjs.map +2 -2
- package/build/link/link.cjs +8 -18
- package/build/link/link.cjs.map +2 -2
- package/build/link/types.cjs.map +1 -1
- package/build/notice/action-button.cjs +3 -3
- package/build/notice/action-button.cjs.map +2 -2
- package/build/notice/action-link.cjs +8 -7
- package/build/notice/action-link.cjs.map +2 -2
- package/build/notice/actions.cjs +3 -3
- package/build/notice/actions.cjs.map +2 -2
- package/build/notice/close-icon.cjs +3 -3
- package/build/notice/close-icon.cjs.map +2 -2
- package/build/notice/description.cjs +3 -3
- package/build/notice/description.cjs.map +2 -2
- package/build/notice/root.cjs +3 -3
- package/build/notice/root.cjs.map +2 -2
- package/build/notice/title.cjs +3 -3
- package/build/notice/title.cjs.map +2 -2
- package/build/popover/arrow.cjs +4 -4
- package/build/popover/arrow.cjs.map +2 -2
- package/build/popover/context.cjs +21 -9
- package/build/popover/context.cjs.map +2 -2
- package/build/popover/description.cjs +4 -4
- package/build/popover/description.cjs.map +2 -2
- package/build/popover/popup.cjs +8 -5
- package/build/popover/popup.cjs.map +2 -2
- package/build/popover/title.cjs +5 -2
- package/build/popover/title.cjs.map +2 -2
- package/build/tabs/context.cjs +9 -22
- package/build/tabs/context.cjs.map +2 -2
- package/build/tabs/list.cjs +4 -4
- package/build/tabs/list.cjs.map +2 -2
- package/build/tabs/panel.cjs +19 -6
- package/build/tabs/panel.cjs.map +3 -3
- package/build/tabs/tab.cjs +4 -4
- package/build/tabs/tab.cjs.map +2 -2
- package/build/tooltip/popup.cjs +4 -4
- package/build/tooltip/popup.cjs.map +2 -2
- package/build/utils/use-schedule-validation.cjs +59 -0
- package/build/utils/use-schedule-validation.cjs.map +7 -0
- package/build-module/alert-dialog/popup.mjs +4 -4
- package/build-module/alert-dialog/popup.mjs.map +2 -2
- package/build-module/collapsible-card/header.mjs +10 -0
- package/build-module/collapsible-card/header.mjs.map +3 -3
- package/build-module/dialog/context.mjs +21 -9
- package/build-module/dialog/context.mjs.map +2 -2
- package/build-module/dialog/footer.mjs +4 -4
- package/build-module/dialog/footer.mjs.map +2 -2
- package/build-module/dialog/header.mjs +4 -4
- package/build-module/dialog/header.mjs.map +2 -2
- package/build-module/dialog/popup.mjs +4 -4
- package/build-module/dialog/popup.mjs.map +2 -2
- package/build-module/dialog/title.mjs +10 -7
- package/build-module/dialog/title.mjs.map +2 -2
- package/build-module/form/primitives/select/item.mjs +3 -3
- package/build-module/form/primitives/select/item.mjs.map +2 -2
- package/build-module/form/primitives/select/popup.mjs +3 -3
- package/build-module/form/primitives/select/popup.mjs.map +2 -2
- package/build-module/link/link.mjs +8 -18
- package/build-module/link/link.mjs.map +2 -2
- package/build-module/notice/action-button.mjs +3 -3
- package/build-module/notice/action-button.mjs.map +2 -2
- package/build-module/notice/action-link.mjs +8 -7
- package/build-module/notice/action-link.mjs.map +2 -2
- package/build-module/notice/actions.mjs +3 -3
- package/build-module/notice/actions.mjs.map +2 -2
- package/build-module/notice/close-icon.mjs +3 -3
- package/build-module/notice/close-icon.mjs.map +2 -2
- package/build-module/notice/description.mjs +3 -3
- package/build-module/notice/description.mjs.map +2 -2
- package/build-module/notice/root.mjs +3 -3
- package/build-module/notice/root.mjs.map +2 -2
- package/build-module/notice/title.mjs +3 -3
- package/build-module/notice/title.mjs.map +2 -2
- package/build-module/popover/arrow.mjs +4 -4
- package/build-module/popover/arrow.mjs.map +2 -2
- package/build-module/popover/context.mjs +21 -9
- package/build-module/popover/context.mjs.map +2 -2
- package/build-module/popover/description.mjs +4 -4
- package/build-module/popover/description.mjs.map +2 -2
- package/build-module/popover/popup.mjs +8 -5
- package/build-module/popover/popup.mjs.map +2 -2
- package/build-module/popover/title.mjs +6 -3
- package/build-module/popover/title.mjs.map +2 -2
- package/build-module/tabs/context.mjs +11 -24
- package/build-module/tabs/context.mjs.map +2 -2
- package/build-module/tabs/list.mjs +4 -4
- package/build-module/tabs/list.mjs.map +2 -2
- package/build-module/tabs/panel.mjs +19 -6
- package/build-module/tabs/panel.mjs.map +3 -3
- package/build-module/tabs/tab.mjs +4 -4
- package/build-module/tabs/tab.mjs.map +2 -2
- package/build-module/tooltip/popup.mjs +4 -4
- package/build-module/tooltip/popup.mjs.map +2 -2
- package/build-module/utils/use-schedule-validation.mjs +34 -0
- package/build-module/utils/use-schedule-validation.mjs.map +7 -0
- package/build-types/alert-dialog/stories/index.story.d.ts +1 -1
- package/build-types/alert-dialog/stories/index.story.d.ts.map +1 -1
- package/build-types/badge/stories/index.story.d.ts.map +1 -1
- package/build-types/collapsible-card/header.d.ts.map +1 -1
- package/build-types/dialog/context.d.ts +1 -1
- package/build-types/dialog/context.d.ts.map +1 -1
- package/build-types/dialog/title.d.ts.map +1 -1
- package/build-types/empty-state/stories/index.story.d.ts +1 -1
- package/build-types/empty-state/stories/index.story.d.ts.map +1 -1
- package/build-types/form/input-control/stories/index.story.d.ts +1 -1
- package/build-types/form/input-control/stories/index.story.d.ts.map +1 -1
- package/build-types/form/primitives/field/stories/index.story.d.ts +1 -1
- package/build-types/form/primitives/field/stories/index.story.d.ts.map +1 -1
- package/build-types/form/primitives/fieldset/stories/index.story.d.ts +1 -1
- package/build-types/form/primitives/fieldset/stories/index.story.d.ts.map +1 -1
- package/build-types/form/primitives/input/stories/index.story.d.ts +1 -1
- package/build-types/form/primitives/input/stories/index.story.d.ts.map +1 -1
- package/build-types/form/primitives/input-layout/stories/index.story.d.ts +1 -1
- package/build-types/form/primitives/input-layout/stories/index.story.d.ts.map +1 -1
- package/build-types/form/primitives/select/stories/index.story.d.ts +1 -1
- package/build-types/form/primitives/select/stories/index.story.d.ts.map +1 -1
- package/build-types/link/link.d.ts.map +1 -1
- package/build-types/link/types.d.ts +1 -2
- package/build-types/link/types.d.ts.map +1 -1
- package/build-types/notice/action-link.d.ts.map +1 -1
- package/build-types/popover/context.d.ts +1 -1
- package/build-types/popover/context.d.ts.map +1 -1
- package/build-types/popover/popup.d.ts.map +1 -1
- package/build-types/popover/stories/index.story.d.ts +1 -1
- package/build-types/popover/stories/index.story.d.ts.map +1 -1
- package/build-types/popover/title.d.ts.map +1 -1
- package/build-types/stack/stories/index.story.d.ts.map +1 -1
- package/build-types/tabs/context.d.ts.map +1 -1
- package/build-types/tabs/panel.d.ts.map +1 -1
- package/build-types/tabs/stories/index.story.d.ts +1 -1
- package/build-types/tabs/stories/index.story.d.ts.map +1 -1
- package/build-types/text/stories/index.story.d.ts.map +1 -1
- package/build-types/tooltip/stories/index.story.d.ts +1 -1
- package/build-types/tooltip/stories/index.story.d.ts.map +1 -1
- package/build-types/tooltip/stories/usage-guidelines.story.d.ts.map +1 -1
- package/build-types/utils/use-schedule-validation.d.ts +13 -0
- package/build-types/utils/use-schedule-validation.d.ts.map +1 -0
- package/package.json +11 -11
- package/src/alert-dialog/stories/index.story.tsx +2 -2
- package/src/badge/stories/choosing-intent.story.tsx +1 -1
- package/src/badge/stories/index.story.tsx +1 -0
- package/src/collapsible-card/header.tsx +2 -0
- package/src/dialog/context.tsx +28 -15
- package/src/dialog/style.module.css +12 -0
- package/src/dialog/test/index.test.tsx +222 -142
- package/src/dialog/title.tsx +6 -4
- package/src/empty-state/stories/index.story.tsx +2 -1
- package/src/form/input-control/stories/index.story.tsx +4 -1
- package/src/form/primitives/field/stories/index.story.tsx +1 -1
- package/src/form/primitives/fieldset/stories/index.story.tsx +1 -1
- package/src/form/primitives/input/stories/index.story.tsx +2 -1
- package/src/form/primitives/input-layout/stories/index.story.tsx +2 -1
- package/src/form/primitives/select/stories/index.story.tsx +1 -1
- package/src/link/link.tsx +12 -26
- package/src/link/style.module.css +4 -16
- package/src/link/test/index.test.tsx +31 -27
- package/src/link/types.ts +1 -2
- package/src/notice/action-link.tsx +7 -4
- package/src/notice/style.module.css +5 -5
- package/src/popover/context.tsx +28 -12
- package/src/popover/popup.tsx +4 -1
- package/src/popover/stories/index.story.tsx +2 -1
- package/src/popover/style.module.css +23 -1
- package/src/popover/test/index.test.tsx +146 -70
- package/src/popover/title.tsx +6 -3
- package/src/stack/stories/index.story.tsx +1 -0
- package/src/tabs/context.tsx +14 -34
- package/src/tabs/panel.tsx +7 -2
- package/src/tabs/stories/index.story.tsx +2 -1
- package/src/tabs/style.module.css +0 -17
- package/src/tabs/test/index.test.tsx +7 -3
- package/src/text/stories/index.story.tsx +1 -0
- package/src/tooltip/stories/index.story.tsx +2 -1
- package/src/tooltip/stories/usage-guidelines.story.tsx +5 -1
- package/src/tooltip/style.module.css +12 -0
- package/src/utils/css/item-popup.module.css +12 -0
- package/src/utils/use-schedule-validation.ts +45 -0
- package/build/types/css-modules.d.cjs +0 -2
- package/build/types/css-modules.d.cjs.map +0 -7
- package/build/types/react.d.cjs +0 -5
- package/build/types/react.d.cjs.map +0 -7
- package/build-module/types/css-modules.d.mjs +0 -1
- package/build-module/types/css-modules.d.mjs.map +0 -7
- package/build-module/types/react.d.mjs +0 -3
- package/build-module/types/react.d.mjs.map +0 -7
- package/src/types/css-modules.d.ts +0 -4
- package/src/types/react.d.ts +0 -7
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import { render, screen, waitFor } from '@testing-library/react';
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
|
-
import {
|
|
3
|
+
import { createRef, useState } from '@wordpress/element';
|
|
4
4
|
import * as Popover from '../index';
|
|
5
5
|
|
|
6
|
+
function collectUncaughtErrors() {
|
|
7
|
+
const errors: Error[] = [];
|
|
8
|
+
const handler = ( event: ErrorEvent ) => {
|
|
9
|
+
event.preventDefault();
|
|
10
|
+
errors.push( event.error );
|
|
11
|
+
};
|
|
12
|
+
window.addEventListener( 'error', handler );
|
|
13
|
+
return {
|
|
14
|
+
errors,
|
|
15
|
+
cleanup: () => window.removeEventListener( 'error', handler ),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
6
19
|
describe( 'Popover', () => {
|
|
7
20
|
describe( 'forwards ref', () => {
|
|
8
21
|
it( 'should forward ref on Trigger', () => {
|
|
@@ -613,84 +626,87 @@ describe( 'Popover', () => {
|
|
|
613
626
|
} );
|
|
614
627
|
|
|
615
628
|
describe( 'title validation', () => {
|
|
629
|
+
// Suppress console.error from React act() warnings and jsdom
|
|
630
|
+
// unhandled-error logging. Validation errors are caught via
|
|
631
|
+
// collectUncaughtErrors (window 'error' event) instead.
|
|
632
|
+
let originalConsoleError: typeof console.error;
|
|
633
|
+
|
|
634
|
+
beforeEach( () => {
|
|
635
|
+
// eslint-disable-next-line no-console
|
|
636
|
+
originalConsoleError = console.error;
|
|
637
|
+
// eslint-disable-next-line no-console
|
|
638
|
+
console.error = jest.fn();
|
|
639
|
+
} );
|
|
640
|
+
|
|
641
|
+
afterEach( () => {
|
|
642
|
+
// eslint-disable-next-line no-console
|
|
643
|
+
console.error = originalConsoleError;
|
|
644
|
+
} );
|
|
645
|
+
|
|
616
646
|
it( 'should throw when Popover.Title is missing', async () => {
|
|
617
647
|
const user = userEvent.setup();
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
// Suppress console.error from React error boundary
|
|
621
|
-
const spy = jest
|
|
622
|
-
.spyOn( console, 'error' )
|
|
623
|
-
.mockImplementation( () => {} );
|
|
648
|
+
const { errors, cleanup } = collectUncaughtErrors();
|
|
624
649
|
|
|
625
650
|
render(
|
|
626
|
-
<
|
|
627
|
-
<Popover.
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
</Popover.Root>
|
|
631
|
-
</ErrorBoundary>
|
|
651
|
+
<Popover.Root>
|
|
652
|
+
<Popover.Trigger>Open</Popover.Trigger>
|
|
653
|
+
<Popover.Popup>No title here</Popover.Popup>
|
|
654
|
+
</Popover.Root>
|
|
632
655
|
);
|
|
633
656
|
|
|
634
657
|
await user.click( screen.getByRole( 'button', { name: 'Open' } ) );
|
|
635
658
|
|
|
636
659
|
await waitFor( () => {
|
|
637
|
-
expect(
|
|
638
|
-
expect.objectContaining( {
|
|
639
|
-
message: expect.stringContaining(
|
|
640
|
-
'Missing <Popover.Title>'
|
|
641
|
-
),
|
|
642
|
-
} )
|
|
643
|
-
);
|
|
660
|
+
expect( errors.length ).toBeGreaterThan( 0 );
|
|
644
661
|
} );
|
|
645
662
|
|
|
646
|
-
|
|
663
|
+
expect( errors[ 0 ].message ).toBe(
|
|
664
|
+
'Popover: Missing <Popover.Title>. ' +
|
|
665
|
+
'For accessibility, every popover requires a title. ' +
|
|
666
|
+
'If needed, the title can be visually hidden but must not be omitted.'
|
|
667
|
+
);
|
|
668
|
+
|
|
669
|
+
cleanup();
|
|
647
670
|
} );
|
|
648
671
|
|
|
649
672
|
it( 'should throw when Popover.Title is empty', async () => {
|
|
650
673
|
const user = userEvent.setup();
|
|
651
|
-
const
|
|
652
|
-
|
|
653
|
-
const spy = jest
|
|
654
|
-
.spyOn( console, 'error' )
|
|
655
|
-
.mockImplementation( () => {} );
|
|
674
|
+
const { errors, cleanup } = collectUncaughtErrors();
|
|
656
675
|
|
|
657
676
|
render(
|
|
658
|
-
<
|
|
659
|
-
<Popover.
|
|
660
|
-
|
|
661
|
-
<Popover.
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
</Popover.Root>
|
|
665
|
-
</ErrorBoundary>
|
|
677
|
+
<Popover.Root>
|
|
678
|
+
<Popover.Trigger>Open</Popover.Trigger>
|
|
679
|
+
<Popover.Popup>
|
|
680
|
+
<Popover.Title />
|
|
681
|
+
</Popover.Popup>
|
|
682
|
+
</Popover.Root>
|
|
666
683
|
);
|
|
667
684
|
|
|
668
685
|
await user.click( screen.getByRole( 'button', { name: 'Open' } ) );
|
|
669
686
|
|
|
670
687
|
await waitFor( () => {
|
|
671
|
-
expect(
|
|
672
|
-
expect.objectContaining( {
|
|
673
|
-
message: expect.stringContaining( 'cannot be empty' ),
|
|
674
|
-
} )
|
|
675
|
-
);
|
|
688
|
+
expect( errors.length ).toBeGreaterThan( 0 );
|
|
676
689
|
} );
|
|
677
690
|
|
|
678
|
-
|
|
691
|
+
expect( errors[ 0 ].message ).toBe(
|
|
692
|
+
'Popover: <Popover.Title> cannot be empty. ' +
|
|
693
|
+
'Provide meaningful text content for the popover title.'
|
|
694
|
+
);
|
|
695
|
+
|
|
696
|
+
cleanup();
|
|
679
697
|
} );
|
|
680
698
|
|
|
681
699
|
it( 'should not throw when Popover.Title is present', async () => {
|
|
682
700
|
const user = userEvent.setup();
|
|
683
|
-
const
|
|
701
|
+
const { errors, cleanup } = collectUncaughtErrors();
|
|
684
702
|
|
|
685
703
|
render(
|
|
686
|
-
<
|
|
687
|
-
<Popover.
|
|
688
|
-
|
|
689
|
-
<Popover.
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
</Popover.Root>
|
|
693
|
-
</ErrorBoundary>
|
|
704
|
+
<Popover.Root>
|
|
705
|
+
<Popover.Trigger>Open</Popover.Trigger>
|
|
706
|
+
<Popover.Popup>
|
|
707
|
+
<Popover.Title>Valid Title</Popover.Title>
|
|
708
|
+
</Popover.Popup>
|
|
709
|
+
</Popover.Root>
|
|
694
710
|
);
|
|
695
711
|
|
|
696
712
|
await user.click( screen.getByRole( 'button', { name: 'Open' } ) );
|
|
@@ -699,29 +715,89 @@ describe( 'Popover', () => {
|
|
|
699
715
|
expect( screen.getByText( 'Valid Title' ) ).toBeVisible();
|
|
700
716
|
} );
|
|
701
717
|
|
|
702
|
-
|
|
718
|
+
await new Promise( ( resolve ) => setTimeout( resolve, 50 ) );
|
|
719
|
+
expect( errors ).toHaveLength( 0 );
|
|
720
|
+
|
|
721
|
+
cleanup();
|
|
703
722
|
} );
|
|
704
|
-
} );
|
|
705
|
-
} );
|
|
706
723
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
{ hasError: boolean }
|
|
710
|
-
> {
|
|
711
|
-
state = { hasError: false };
|
|
724
|
+
it( 'should throw when title is removed after mount', async () => {
|
|
725
|
+
const { errors, cleanup } = collectUncaughtErrors();
|
|
712
726
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
727
|
+
const ui = ( showTitle: boolean ) => (
|
|
728
|
+
<Popover.Root defaultOpen>
|
|
729
|
+
<Popover.Trigger>Open</Popover.Trigger>
|
|
730
|
+
<Popover.Popup>
|
|
731
|
+
{ showTitle && <Popover.Title>My Title</Popover.Title> }
|
|
732
|
+
<p>Content</p>
|
|
733
|
+
</Popover.Popup>
|
|
734
|
+
</Popover.Root>
|
|
735
|
+
);
|
|
716
736
|
|
|
717
|
-
|
|
718
|
-
this.props.onError( error );
|
|
719
|
-
}
|
|
737
|
+
const { rerender } = render( ui( true ) );
|
|
720
738
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
739
|
+
// Wait for the popover to render.
|
|
740
|
+
await waitFor( () => {
|
|
741
|
+
expect( screen.getByText( 'Content' ) ).toBeInTheDocument();
|
|
742
|
+
} );
|
|
743
|
+
|
|
744
|
+
// Let initial validation settle — no errors expected.
|
|
745
|
+
await new Promise( ( resolve ) => setTimeout( resolve, 50 ) );
|
|
746
|
+
expect( errors ).toHaveLength( 0 );
|
|
747
|
+
|
|
748
|
+
// Remove the title via rerender.
|
|
749
|
+
rerender( ui( false ) );
|
|
750
|
+
|
|
751
|
+
await waitFor( () => {
|
|
752
|
+
expect( errors.length ).toBeGreaterThan( 0 );
|
|
753
|
+
} );
|
|
754
|
+
|
|
755
|
+
expect( errors[ 0 ].message ).toBe(
|
|
756
|
+
'Popover: Missing <Popover.Title>. ' +
|
|
757
|
+
'For accessibility, every popover requires a title. ' +
|
|
758
|
+
'If needed, the title can be visually hidden but must not be omitted.'
|
|
759
|
+
);
|
|
760
|
+
|
|
761
|
+
cleanup();
|
|
762
|
+
} );
|
|
763
|
+
|
|
764
|
+
it( 'should recover when title is added back', async () => {
|
|
765
|
+
const { errors, cleanup } = collectUncaughtErrors();
|
|
766
|
+
|
|
767
|
+
const ui = ( showTitle: boolean ) => (
|
|
768
|
+
<Popover.Root defaultOpen>
|
|
769
|
+
<Popover.Trigger>Open</Popover.Trigger>
|
|
770
|
+
<Popover.Popup>
|
|
771
|
+
{ showTitle && <Popover.Title>My Title</Popover.Title> }
|
|
772
|
+
<p>Content</p>
|
|
773
|
+
</Popover.Popup>
|
|
774
|
+
</Popover.Root>
|
|
775
|
+
);
|
|
776
|
+
|
|
777
|
+
const { rerender } = render( ui( false ) );
|
|
778
|
+
|
|
779
|
+
// Wait for the popover to render.
|
|
780
|
+
await waitFor( () => {
|
|
781
|
+
expect( screen.getByText( 'Content' ) ).toBeInTheDocument();
|
|
782
|
+
} );
|
|
783
|
+
|
|
784
|
+
// Initially no title — should error.
|
|
785
|
+
await waitFor( () => {
|
|
786
|
+
expect( errors.length ).toBeGreaterThan( 0 );
|
|
787
|
+
} );
|
|
788
|
+
|
|
789
|
+
const errorCountAfterInitial = errors.length;
|
|
790
|
+
|
|
791
|
+
// Add the title back via rerender.
|
|
792
|
+
rerender( ui( true ) );
|
|
793
|
+
|
|
794
|
+
// Wait for deferred validation to settle.
|
|
795
|
+
await new Promise( ( resolve ) => setTimeout( resolve, 50 ) );
|
|
796
|
+
|
|
797
|
+
// No new errors should have been thrown.
|
|
798
|
+
expect( errors ).toHaveLength( errorCountAfterInitial );
|
|
799
|
+
|
|
800
|
+
cleanup();
|
|
801
|
+
} );
|
|
802
|
+
} );
|
|
803
|
+
} );
|
package/src/popover/title.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Popover as _Popover } from '@base-ui/react/popover';
|
|
2
2
|
import { useMergeRefs } from '@wordpress/compose';
|
|
3
|
-
import { forwardRef,
|
|
3
|
+
import { forwardRef, useEffect, useRef } from '@wordpress/element';
|
|
4
4
|
import { Text } from '../text';
|
|
5
5
|
import { usePopoverValidationContext } from './context';
|
|
6
6
|
import type { TitleProps } from './types';
|
|
@@ -27,8 +27,11 @@ const Title = forwardRef< HTMLHeadingElement, TitleProps >(
|
|
|
27
27
|
const internalRef = useRef< HTMLHeadingElement >( null );
|
|
28
28
|
const mergedRef = useMergeRefs( [ internalRef, forwardedRef ] );
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
useEffect( () => {
|
|
31
|
+
if ( validationContext ) {
|
|
32
|
+
return validationContext.registerTitle( internalRef.current );
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
32
35
|
}, [ validationContext ] );
|
|
33
36
|
|
|
34
37
|
return (
|
package/src/tabs/context.tsx
CHANGED
|
@@ -2,10 +2,11 @@ import {
|
|
|
2
2
|
createContext,
|
|
3
3
|
useContext,
|
|
4
4
|
useCallback,
|
|
5
|
+
useEffect,
|
|
5
6
|
useMemo,
|
|
6
7
|
useRef,
|
|
7
|
-
useEffect,
|
|
8
8
|
} from '@wordpress/element';
|
|
9
|
+
import { useScheduleValidation } from '../utils/use-schedule-validation';
|
|
9
10
|
|
|
10
11
|
type TabsValidationContextType = {
|
|
11
12
|
registerTab: () => () => void;
|
|
@@ -77,33 +78,20 @@ function TabsValidationProviderDev( {
|
|
|
77
78
|
} ) {
|
|
78
79
|
const tabCountRef = useRef( 0 );
|
|
79
80
|
const panelCountRef = useRef( 0 );
|
|
80
|
-
const validationScheduledRef = useRef< ReturnType<
|
|
81
|
-
typeof setTimeout
|
|
82
|
-
> | null >( null );
|
|
83
81
|
|
|
84
|
-
const scheduleValidation =
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
const scheduleValidation = useScheduleValidation( () => {
|
|
83
|
+
const tabCount = tabCountRef.current;
|
|
84
|
+
const panelCount = panelCountRef.current;
|
|
85
|
+
|
|
86
|
+
if ( tabCount !== panelCount ) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
`Tabs: Tab/Panel count mismatch (${ tabCount } Tabs, ${ panelCount } Panels). ` +
|
|
89
|
+
`Each Tab must be associated with exactly one Panel. ` +
|
|
90
|
+
`Mismatched or missing associations can break screen reader navigation ` +
|
|
91
|
+
`and violate WAI-ARIA Tabs pattern requirements.`
|
|
92
|
+
);
|
|
87
93
|
}
|
|
88
|
-
|
|
89
|
-
// Schedule validation for the next tick to allow all
|
|
90
|
-
// registrations/unregistrations to complete.
|
|
91
|
-
validationScheduledRef.current = setTimeout( () => {
|
|
92
|
-
const tabCount = tabCountRef.current;
|
|
93
|
-
const panelCount = panelCountRef.current;
|
|
94
|
-
|
|
95
|
-
if ( tabCount !== panelCount ) {
|
|
96
|
-
throw new Error(
|
|
97
|
-
`Tabs: Tab/Panel count mismatch (${ tabCount } Tabs, ${ panelCount } Panels). ` +
|
|
98
|
-
`Each Tab must be associated with exactly one Panel. ` +
|
|
99
|
-
`Mismatched or missing associations can break screen reader navigation ` +
|
|
100
|
-
`and violate WAI-ARIA Tabs pattern requirements.`
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
validationScheduledRef.current = null;
|
|
105
|
-
}, 0 );
|
|
106
|
-
}, [] );
|
|
94
|
+
} );
|
|
107
95
|
|
|
108
96
|
const registerTab = useCallback( () => {
|
|
109
97
|
tabCountRef.current += 1;
|
|
@@ -125,14 +113,6 @@ function TabsValidationProviderDev( {
|
|
|
125
113
|
};
|
|
126
114
|
}, [ scheduleValidation ] );
|
|
127
115
|
|
|
128
|
-
useEffect( () => {
|
|
129
|
-
return () => {
|
|
130
|
-
if ( validationScheduledRef.current ) {
|
|
131
|
-
clearTimeout( validationScheduledRef.current );
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
}, [] );
|
|
135
|
-
|
|
136
116
|
const contextValue = useMemo(
|
|
137
117
|
() => ( {
|
|
138
118
|
registerTab,
|
package/src/tabs/panel.tsx
CHANGED
|
@@ -2,7 +2,8 @@ import { forwardRef } from '@wordpress/element';
|
|
|
2
2
|
import clsx from 'clsx';
|
|
3
3
|
import { Tabs as _Tabs } from '@base-ui/react/tabs';
|
|
4
4
|
import { useRegisterPanel } from './context';
|
|
5
|
-
import
|
|
5
|
+
import defenseStyles from '../utils/css/global-css-defense.module.css';
|
|
6
|
+
import focusStyles from '../utils/css/focus.module.css';
|
|
6
7
|
import type { TabPanelProps } from './types';
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -18,7 +19,11 @@ export const Panel = forwardRef< HTMLDivElement, TabPanelProps >(
|
|
|
18
19
|
return (
|
|
19
20
|
<_Tabs.Panel
|
|
20
21
|
ref={ forwardedRef }
|
|
21
|
-
className={ clsx(
|
|
22
|
+
className={ clsx(
|
|
23
|
+
defenseStyles.div,
|
|
24
|
+
focusStyles[ 'outset-ring--focus-visible' ],
|
|
25
|
+
className
|
|
26
|
+
) }
|
|
22
27
|
{ ...otherProps }
|
|
23
28
|
/>
|
|
24
29
|
);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
2
|
import { useState, cloneElement } from '@wordpress/element';
|
|
3
3
|
import { link, more, wordpress } from '@wordpress/icons';
|
|
4
|
-
import
|
|
4
|
+
import * as Tabs from '../';
|
|
5
|
+
import * as Tooltip from '../../tooltip';
|
|
5
6
|
|
|
6
7
|
const meta: Meta< typeof Tabs.Root > = {
|
|
7
8
|
title: 'Design System/Components/Tabs',
|
|
@@ -249,21 +249,4 @@
|
|
|
249
249
|
rotate: 180deg;
|
|
250
250
|
}
|
|
251
251
|
}
|
|
252
|
-
|
|
253
|
-
.tabpanel {
|
|
254
|
-
&:focus {
|
|
255
|
-
box-shadow: none;
|
|
256
|
-
outline: none;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
&:focus-visible {
|
|
260
|
-
box-shadow:
|
|
261
|
-
0 0 0 var(--wpds-border-width-focus)
|
|
262
|
-
var(--wpds-color-stroke-focus-brand);
|
|
263
|
-
|
|
264
|
-
/* Windows high contrast mode. */
|
|
265
|
-
outline: 2px solid transparent;
|
|
266
|
-
outline-offset: 0;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
252
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* eslint-disable jest/no-conditional-expect */
|
|
2
|
-
import { render, screen, waitFor } from '@testing-library/react';
|
|
2
|
+
import { act, render, screen, waitFor } from '@testing-library/react';
|
|
3
3
|
import userEvent from '@testing-library/user-event';
|
|
4
4
|
import { DirectionProvider } from '@base-ui/react/direction-provider';
|
|
5
5
|
import { useEffect, useState, createRef } from '@wordpress/element';
|
|
@@ -2344,7 +2344,9 @@ describe( 'Tabs', () => {
|
|
|
2344
2344
|
await waitForComponentToBeInitializedWithSelectedTab( 'One' );
|
|
2345
2345
|
|
|
2346
2346
|
// Wait a bit to ensure validation has run
|
|
2347
|
-
await
|
|
2347
|
+
await act(
|
|
2348
|
+
() => new Promise( ( resolve ) => setTimeout( resolve, 50 ) )
|
|
2349
|
+
);
|
|
2348
2350
|
|
|
2349
2351
|
expect( errors ).toHaveLength( 0 );
|
|
2350
2352
|
|
|
@@ -2391,7 +2393,9 @@ describe( 'Tabs', () => {
|
|
|
2391
2393
|
await waitForComponentToBeInitializedWithSelectedTab( 'One' );
|
|
2392
2394
|
|
|
2393
2395
|
// Wait for validation
|
|
2394
|
-
await
|
|
2396
|
+
await act(
|
|
2397
|
+
() => new Promise( ( resolve ) => setTimeout( resolve, 50 ) )
|
|
2398
|
+
);
|
|
2395
2399
|
|
|
2396
2400
|
// No errors since counts match
|
|
2397
2401
|
expect( errors ).toHaveLength( 0 );
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
2
|
import { formatBold, formatItalic } from '@wordpress/icons';
|
|
3
|
-
import { Icon
|
|
3
|
+
import { Icon } from '../../icon';
|
|
4
|
+
import * as Tooltip from '../';
|
|
4
5
|
|
|
5
6
|
const meta: Meta< typeof Tooltip.Root > = {
|
|
6
7
|
title: 'Design System/Components/Tooltip',
|
|
@@ -5,7 +5,11 @@ import {
|
|
|
5
5
|
formatUnderline,
|
|
6
6
|
info,
|
|
7
7
|
} from '@wordpress/icons';
|
|
8
|
-
import
|
|
8
|
+
import * as Tooltip from '../';
|
|
9
|
+
import { Icon } from '../../icon';
|
|
10
|
+
import { IconButton } from '../../icon-button';
|
|
11
|
+
import * as Popover from '../../popover';
|
|
12
|
+
import { VisuallyHidden } from '../../visually-hidden';
|
|
9
13
|
|
|
10
14
|
const meta: Meta = {
|
|
11
15
|
title: 'Design System/Components/Tooltip/Usage Guidelines',
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
@layer wp-ui-utilities, wp-ui-components, wp-ui-compositions, wp-ui-overrides;
|
|
2
2
|
|
|
3
|
+
/*
|
|
4
|
+
* Temporary workaround for a Base UI tabbability regression with
|
|
5
|
+
* checkVisibility() and display: contents.
|
|
6
|
+
* See: https://github.com/mui/base-ui/issues/4622
|
|
7
|
+
*
|
|
8
|
+
* This must stay outside the CSS layers to override ThemeProvider's
|
|
9
|
+
* unlayered display: contents.
|
|
10
|
+
*/
|
|
11
|
+
[data-wpds-theme-provider-id]:has(> .popup) {
|
|
12
|
+
display: block;
|
|
13
|
+
}
|
|
14
|
+
|
|
3
15
|
@layer wp-ui-components {
|
|
4
16
|
.positioner {
|
|
5
17
|
z-index: var(--wp-ui-tooltip-z-index, initial);
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
@layer wp-ui-utilities, wp-ui-components, wp-ui-compositions, wp-ui-overrides;
|
|
2
2
|
|
|
3
|
+
/*
|
|
4
|
+
* Temporary workaround for a Base UI tabbability regression with
|
|
5
|
+
* checkVisibility() and display: contents.
|
|
6
|
+
* See: https://github.com/mui/base-ui/issues/4622
|
|
7
|
+
*
|
|
8
|
+
* This must stay outside the CSS layers to override ThemeProvider's
|
|
9
|
+
* unlayered display: contents.
|
|
10
|
+
*/
|
|
11
|
+
[data-wpds-theme-provider-id]:has(> .popup) {
|
|
12
|
+
display: block;
|
|
13
|
+
}
|
|
14
|
+
|
|
3
15
|
@layer wp-ui-utilities {
|
|
4
16
|
.popup {
|
|
5
17
|
composes: dropdown-motion from "./dropdown-motion.module.css";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef } from '@wordpress/element';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Dev-only hook that returns a stable `scheduleValidation` function.
|
|
5
|
+
*
|
|
6
|
+
* Each call debounces to `setTimeout(…, 0)` so that rapid
|
|
7
|
+
* register / unregister cycles (e.g. React strict-mode double-mount)
|
|
8
|
+
* settle before the check runs. The timer is cleaned up on unmount,
|
|
9
|
+
* and calls after unmount are silently ignored.
|
|
10
|
+
*
|
|
11
|
+
* @param validate Callback that performs the actual validation.
|
|
12
|
+
* Stored in a ref — safe to pass an unstable closure.
|
|
13
|
+
*/
|
|
14
|
+
export function useScheduleValidation( validate: () => void ) {
|
|
15
|
+
const validateRef = useRef( validate );
|
|
16
|
+
validateRef.current = validate;
|
|
17
|
+
|
|
18
|
+
const timerRef = useRef< ReturnType< typeof setTimeout > | null >( null );
|
|
19
|
+
const unmountedRef = useRef( false );
|
|
20
|
+
|
|
21
|
+
const scheduleValidation = useCallback( () => {
|
|
22
|
+
if ( unmountedRef.current ) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if ( timerRef.current ) {
|
|
26
|
+
clearTimeout( timerRef.current );
|
|
27
|
+
}
|
|
28
|
+
timerRef.current = setTimeout( () => {
|
|
29
|
+
validateRef.current();
|
|
30
|
+
timerRef.current = null;
|
|
31
|
+
}, 0 );
|
|
32
|
+
}, [] );
|
|
33
|
+
|
|
34
|
+
useEffect( () => {
|
|
35
|
+
unmountedRef.current = false;
|
|
36
|
+
return () => {
|
|
37
|
+
unmountedRef.current = true;
|
|
38
|
+
if ( timerRef.current ) {
|
|
39
|
+
clearTimeout( timerRef.current );
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}, [] );
|
|
43
|
+
|
|
44
|
+
return scheduleValidation;
|
|
45
|
+
}
|
package/build/types/react.d.cjs
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/types/react.d.ts"],
|
|
4
|
-
"sourcesContent": ["import 'react';\n\ndeclare module 'react' {\n\tinterface CSSProperties {\n\t\t[ key: `--${ string }` ]: string | number | undefined;\n\t}\n}\n"],
|
|
5
|
-
"mappings": ";;;AAAA,mBAAO;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
//# sourceMappingURL=css-modules.d.mjs.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../src/types/react.d.ts"],
|
|
4
|
-
"sourcesContent": ["import 'react';\n\ndeclare module 'react' {\n\tinterface CSSProperties {\n\t\t[ key: `--${ string }` ]: string | number | undefined;\n\t}\n}\n"],
|
|
5
|
-
"mappings": ";AAAA,OAAO;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|