@astryxdesign/core 0.4.2 → 0.4.3-canary.07f9998
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 +45 -0
- package/README.md +79 -45
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +3 -19
- package/dist/Banner/Banner.d.ts +9 -2
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +84 -33
- package/dist/Banner/index.d.ts +14 -12
- package/dist/Banner/index.d.ts.map +1 -1
- package/dist/Banner/index.js +10 -8
- package/dist/BottomSheet/BottomSheet.d.ts +66 -0
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -0
- package/dist/BottomSheet/BottomSheet.js +381 -0
- package/dist/BottomSheet/BottomSheetPanel.d.ts +57 -0
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -0
- package/dist/BottomSheet/BottomSheetPanel.js +395 -0
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts +61 -0
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -0
- package/dist/BottomSheet/BottomSheetSwitcher.js +410 -0
- package/dist/BottomSheet/BottomSheetSwitcherContext.d.ts +21 -0
- package/dist/BottomSheet/BottomSheetSwitcherContext.d.ts.map +1 -0
- package/dist/BottomSheet/BottomSheetSwitcherContext.js +13 -0
- package/dist/BottomSheet/index.d.ts +11 -0
- package/dist/BottomSheet/index.d.ts.map +1 -0
- package/dist/BottomSheet/index.js +12 -0
- package/dist/BottomSheet/snapOffsets.d.ts +72 -0
- package/dist/BottomSheet/snapOffsets.d.ts.map +1 -0
- package/dist/BottomSheet/snapOffsets.js +130 -0
- package/dist/BottomSheet/useMobileKeyboard.d.ts +25 -0
- package/dist/BottomSheet/useMobileKeyboard.d.ts.map +1 -0
- package/dist/BottomSheet/useMobileKeyboard.js +384 -0
- package/dist/BottomSheet/useSheetGestures.d.ts +170 -0
- package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -0
- package/dist/BottomSheet/useSheetGestures.js +881 -0
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +3 -4
- package/dist/Chat/ChatTokenizedText.js +1 -1
- package/dist/ComplexSelector/ComplexSelector.d.ts +38 -4
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +98 -31
- package/dist/ComplexSelector/index.d.ts +2 -2
- package/dist/ComplexSelector/index.d.ts.map +1 -1
- package/dist/ComplexSelector/index.js +1 -1
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +4 -2
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +9 -1
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +31 -1
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +2 -1
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +55 -12
- package/dist/MobileNav/MobileNav.d.ts +12 -0
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +102 -30
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +10 -1
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +9 -1
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +4 -1
- package/dist/PowerSearch/PowerSearchEditPopover.js +1 -1
- package/dist/PowerSearch/formatFilterValue.d.ts.map +1 -1
- package/dist/PowerSearch/formatFilterValue.js +2 -4
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +10 -1
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +5 -1
- package/dist/Table/columnUtils.d.ts.map +1 -1
- package/dist/Table/columnUtils.js +4 -1
- package/dist/TextArea/TextArea.d.ts +6 -3
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +17 -6
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +8 -1
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +4 -1
- package/dist/TreeList/TreeList.js +2 -1
- package/dist/Typeahead/BaseTypeahead.js +1 -1
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +10 -1
- package/dist/astryx.css +25 -2
- package/dist/hooks/index.d.ts +8 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +8 -1
- package/dist/hooks/useFocusTrap.d.ts +0 -11
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +1 -11
- package/dist/hooks/useInputStatusIcon.d.ts.map +1 -1
- package/dist/hooks/useInputStatusIcon.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/theme/defineTheme.d.ts +30 -7
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/defineTheme.js +36 -47
- package/dist/theme/expandColorScale.d.ts +17 -2
- package/dist/theme/expandColorScale.d.ts.map +1 -1
- package/dist/theme/expandColorScale.js +63 -33
- package/dist/theme/mergeComponents.d.ts +20 -0
- package/dist/theme/mergeComponents.d.ts.map +1 -0
- package/dist/theme/mergeComponents.js +56 -0
- package/dist/theme/onMediaTokens.d.ts +6 -1
- package/dist/theme/onMediaTokens.d.ts.map +1 -1
- package/dist/theme/onMediaTokens.js +11 -3
- package/dist/utils/characters.d.ts +27 -0
- package/dist/utils/characters.d.ts.map +1 -0
- package/dist/utils/characters.js +83 -0
- package/dist/utils/ime.d.ts +5 -0
- package/dist/utils/ime.d.ts.map +1 -0
- package/dist/utils/ime.js +63 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +3 -1
- package/package.json +9 -8
- package/src/Avatar/Avatar.test.tsx +6 -1
- package/src/Avatar/Avatar.tsx +3 -21
- package/src/Banner/Banner.doc.mjs +12 -10
- package/src/Banner/Banner.test.tsx +108 -0
- package/src/Banner/Banner.tsx +124 -38
- package/src/Banner/index.ts +15 -13
- package/src/BottomSheet/BottomSheet.doc.mjs +266 -0
- package/src/BottomSheet/BottomSheet.test.tsx +1865 -0
- package/src/BottomSheet/BottomSheet.tsx +539 -0
- package/src/BottomSheet/BottomSheetPanel.test.tsx +240 -0
- package/src/BottomSheet/BottomSheetPanel.tsx +542 -0
- package/src/BottomSheet/BottomSheetSwitcher.doc.mjs +219 -0
- package/src/BottomSheet/BottomSheetSwitcher.test.tsx +850 -0
- package/src/BottomSheet/BottomSheetSwitcher.tsx +628 -0
- package/src/BottomSheet/BottomSheetSwitcherContext.ts +51 -0
- package/src/BottomSheet/index.ts +15 -0
- package/src/BottomSheet/snapOffsets.test.ts +187 -0
- package/src/BottomSheet/snapOffsets.ts +159 -0
- package/src/BottomSheet/useMobileKeyboard.ts +566 -0
- package/src/BottomSheet/useSheetGestures.test.ts +833 -0
- package/src/BottomSheet/useSheetGestures.ts +1333 -0
- package/src/Chat/ChatComposerInput.tsx +3 -4
- package/src/Chat/ChatTokenizedText.tsx +1 -1
- package/src/ComplexSelector/ComplexSelector.doc.mjs +55 -6
- package/src/ComplexSelector/ComplexSelector.test.tsx +197 -6
- package/src/ComplexSelector/ComplexSelector.tsx +156 -28
- package/src/ComplexSelector/index.ts +3 -1
- package/src/ContextMenu/ContextMenu.tsx +4 -2
- package/src/DateInput/DateInput.test.tsx +21 -0
- package/src/DateInput/DateInput.tsx +9 -1
- package/src/DateTimeInput/DateTimeInput.doc.mjs +4 -0
- package/src/DateTimeInput/DateTimeInput.test.tsx +137 -0
- package/src/DateTimeInput/DateTimeInput.tsx +39 -2
- package/src/Dialog/Dialog.tsx +2 -1
- package/src/Markdown/parser.test.ts +53 -0
- package/src/Markdown/parser.ts +53 -12
- package/src/MobileNav/MobileNav.tsx +128 -37
- package/src/MobileNav/MobileNavCloseEdgeCases.test.tsx +426 -0
- package/src/MobileNav/MobileNavCloseTiming.test.ts +58 -0
- package/src/MobileNav/MobileNavCloseVisibility.test.tsx +172 -0
- package/src/MultiSelector/MultiSelector.test.tsx +31 -0
- package/src/MultiSelector/MultiSelector.tsx +10 -1
- package/src/NumberInput/NumberInput.test.tsx +18 -0
- package/src/NumberInput/NumberInput.tsx +9 -1
- package/src/PowerSearch/PowerSearch.test.tsx +48 -3
- package/src/PowerSearch/PowerSearch.tsx +4 -1
- package/src/PowerSearch/PowerSearchEditPopover.tsx +1 -1
- package/src/PowerSearch/formatFilterValue.test.ts +22 -0
- package/src/PowerSearch/formatFilterValue.ts +2 -4
- package/src/Selector/Selector.test.tsx +33 -0
- package/src/Selector/Selector.tsx +10 -1
- package/src/Switch/Switch.test.tsx +16 -0
- package/src/Switch/Switch.tsx +7 -0
- package/src/Table/Table.test.tsx +6 -0
- package/src/Table/columnUtils.ts +3 -1
- package/src/TextArea/TextArea.doc.mjs +1 -1
- package/src/TextArea/TextArea.test.tsx +72 -0
- package/src/TextArea/TextArea.tsx +26 -8
- package/src/TimeInput/TimeInput.test.tsx +23 -0
- package/src/TimeInput/TimeInput.tsx +8 -0
- package/src/Tooltip/useTooltip.tsx +4 -1
- package/src/TreeList/TreeList.doc.mjs +2 -2
- package/src/TreeList/TreeList.tsx +1 -1
- package/src/Typeahead/BaseTypeahead.tsx +1 -1
- package/src/Typeahead/Typeahead.test.tsx +35 -0
- package/src/Typeahead/Typeahead.tsx +10 -1
- package/src/__tests__/stubMatchMedia.test.ts +79 -0
- package/src/__tests__/stubMatchMedia.ts +95 -0
- package/src/hooks/index.ts +9 -5
- package/src/hooks/useFocusTrap.ts +1 -14
- package/src/hooks/useInputStatusIcon.test.tsx +19 -0
- package/src/hooks/useInputStatusIcon.tsx +7 -0
- package/src/index.ts +1 -0
- package/src/theme/defineTheme.test.ts +127 -0
- package/src/theme/defineTheme.ts +70 -55
- package/src/theme/expandColorScale.test.ts +138 -0
- package/src/theme/expandColorScale.ts +89 -44
- package/src/theme/extensibleAxes.test.ts +365 -0
- package/src/theme/mergeComponents.ts +59 -0
- package/src/theme/onMediaTokens.ts +9 -2
- package/src/utils/characters.test.ts +141 -0
- package/src/utils/characters.ts +88 -0
- package/src/utils/ime.test.ts +32 -0
- package/src/utils/ime.ts +69 -0
- package/src/utils/index.ts +4 -0
- package/dist/astryx.umd.js +0 -147
- package/dist/astryx.umd.js.map +0 -7
|
@@ -43,7 +43,7 @@ import {
|
|
|
43
43
|
typeScaleVars,
|
|
44
44
|
typographyVars,
|
|
45
45
|
} from '../theme/tokens.stylex';
|
|
46
|
-
import {mergeProps} from '../utils';
|
|
46
|
+
import {mergeProps, isImeKeyEvent} from '../utils';
|
|
47
47
|
import {useTriggerMenu} from './useTriggerMenu';
|
|
48
48
|
import {useChatComposerTokens, isCustomToken} from './useChatComposerTokens';
|
|
49
49
|
import {ensureCaretInside, insertTextAtCursor} from './chatComposerSelection';
|
|
@@ -559,9 +559,8 @@ export function ChatComposerInput(props: ChatComposerInputProps) {
|
|
|
559
559
|
|
|
560
560
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
561
561
|
// Never submit mid-composition — an IME uses Enter to commit a
|
|
562
|
-
// candidate
|
|
563
|
-
|
|
564
|
-
if (e.nativeEvent.isComposing || e.nativeEvent.keyCode === 229) {
|
|
562
|
+
// candidate. See utils/ime.ts for the full rationale.
|
|
563
|
+
if (isImeKeyEvent(e.nativeEvent)) {
|
|
565
564
|
return;
|
|
566
565
|
}
|
|
567
566
|
|
|
@@ -74,7 +74,7 @@ function isCustomToken(
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
function escapeRegExp(str: string): string {
|
|
77
|
-
return str.replace(/[.*+?^${}()|[
|
|
77
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
// =============================================================================
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @file ComplexSelector.doc.mjs
|
|
5
|
+
* @input ComplexSelector public API and composition contract
|
|
6
|
+
* @output Exports full and dense component documentation
|
|
7
|
+
* @position Core documentation consumed by CLI and Storybook autodocs
|
|
8
|
+
*
|
|
9
|
+
* SYNC: When modified, update ComplexSelector.tsx, tests, and stories.
|
|
10
|
+
*/
|
|
11
|
+
|
|
3
12
|
/** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */
|
|
4
13
|
|
|
5
14
|
export const docs = {
|
|
@@ -19,7 +28,10 @@ export const docs = {
|
|
|
19
28
|
],
|
|
20
29
|
theming: {
|
|
21
30
|
targets: [
|
|
22
|
-
{
|
|
31
|
+
{
|
|
32
|
+
className: 'astryx-complex-selector',
|
|
33
|
+
visualProps: ['variant', 'size', 'status'],
|
|
34
|
+
},
|
|
23
35
|
{
|
|
24
36
|
className: 'astryx-complex-selector-indicator-icon',
|
|
25
37
|
states: ['state'],
|
|
@@ -32,7 +44,7 @@ export const docs = {
|
|
|
32
44
|
name: 'ComplexSelector',
|
|
33
45
|
displayName: 'Complex Selector',
|
|
34
46
|
description:
|
|
35
|
-
'
|
|
47
|
+
'An input or toolbar trigger and dialog-popover shell for custom selector content.',
|
|
36
48
|
props: [
|
|
37
49
|
{
|
|
38
50
|
name: 'label',
|
|
@@ -93,9 +105,21 @@ export const docs = {
|
|
|
93
105
|
{
|
|
94
106
|
name: 'size',
|
|
95
107
|
type: "'sm' | 'md' | 'lg'",
|
|
96
|
-
description: '
|
|
108
|
+
description: 'Exact trigger height: sm 28px, md 32px, or lg 36px.',
|
|
97
109
|
default: "'md'",
|
|
98
110
|
},
|
|
111
|
+
{
|
|
112
|
+
name: 'variant',
|
|
113
|
+
type: "'input' | 'ghost'",
|
|
114
|
+
description:
|
|
115
|
+
'Visual trigger style. Input is the bordered form treatment; ghost matches toolbar buttons.',
|
|
116
|
+
default: "'input'",
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: 'startIcon',
|
|
120
|
+
type: 'ReactNode | IconType',
|
|
121
|
+
description: 'Icon displayed at the start of the trigger.',
|
|
122
|
+
},
|
|
99
123
|
{
|
|
100
124
|
name: 'width',
|
|
101
125
|
type: 'SizeValue',
|
|
@@ -107,6 +131,18 @@ export const docs = {
|
|
|
107
131
|
description: 'Popup placement.',
|
|
108
132
|
default: "'below'",
|
|
109
133
|
},
|
|
134
|
+
{
|
|
135
|
+
name: 'alignment',
|
|
136
|
+
type: "'start' | 'center' | 'end'",
|
|
137
|
+
description: 'Popup alignment along the placement axis.',
|
|
138
|
+
default: "'start'",
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: 'handleRef',
|
|
142
|
+
type: 'React.Ref<ComplexSelectorHandle>',
|
|
143
|
+
description:
|
|
144
|
+
'Imperative handle for programmatic control. Exposes open(), close(), toggle(), and isOpen().',
|
|
145
|
+
},
|
|
110
146
|
{
|
|
111
147
|
name: 'contentXstyle',
|
|
112
148
|
type: 'StyleXStyles',
|
|
@@ -119,6 +155,16 @@ export const docs = {
|
|
|
119
155
|
description:
|
|
120
156
|
'Use ComplexSelector when a selection needs richer custom content than a Selector option row. It is intentionally one component: ComplexSelector owns the field, trigger, popover, focus restore, and changeAction flow, while the content render prop owns the selector-specific accessible structure.',
|
|
121
157
|
bestPractices: [
|
|
158
|
+
{
|
|
159
|
+
guidance: true,
|
|
160
|
+
description:
|
|
161
|
+
'Use variant="ghost" with a startIcon when the selector is triggered from a toolbar. Use alignment="end" when a wide surface should align its end edge to the trigger.',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
guidance: true,
|
|
165
|
+
description:
|
|
166
|
+
'For staged editors, keep draft state in the composed content and call the provided onChange helper only from Apply. Cancel or dismiss without committing.',
|
|
167
|
+
},
|
|
122
168
|
{
|
|
123
169
|
guidance: true,
|
|
124
170
|
description:
|
|
@@ -164,7 +210,7 @@ export const docsDense = {
|
|
|
164
210
|
group: 'Selector',
|
|
165
211
|
category: 'Data Input',
|
|
166
212
|
description:
|
|
167
|
-
'
|
|
213
|
+
'Input/ghost trigger + dialog-popover shell for rich custom selectors. Content gets value/onChange/close/state; content owns semantics. Use focus hooks and evaluate custom content against WCAG 2.2.',
|
|
168
214
|
usage: {
|
|
169
215
|
description:
|
|
170
216
|
'Use when a selection needs richer custom content than a Selector row. One component: it owns field, trigger, popover, focus restore, and changeAction; the render prop owns the selector-specific accessible structure.',
|
|
@@ -211,10 +257,13 @@ export const docsDense = {
|
|
|
211
257
|
value: 'Controlled value.',
|
|
212
258
|
onChange: 'Commit value.',
|
|
213
259
|
changeAction: 'Async action after onChange; drives optimistic value/busy.',
|
|
214
|
-
children:
|
|
215
|
-
'Render custom dialog content from (value,onChange,close,state).',
|
|
260
|
+
children: 'Render custom dialog content from (value,onChange,close,state).',
|
|
216
261
|
triggerLabel: 'Closed trigger label/content.',
|
|
262
|
+
variant: 'input for forms; ghost for toolbar triggers.',
|
|
263
|
+
startIcon: 'Leading trigger icon.',
|
|
217
264
|
placement: 'Popup placement.',
|
|
265
|
+
alignment: 'Popup alignment.',
|
|
266
|
+
handleRef: 'Imperative open/close/toggle handle.',
|
|
218
267
|
accessibility:
|
|
219
268
|
'Custom content must provide its own accessible structure. Use focus hooks and evaluate against WCAG 2.2.',
|
|
220
269
|
},
|
|
@@ -2,17 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @file ComplexSelector.test.tsx
|
|
5
|
-
* @input Uses vitest, Testing Library, user-event
|
|
6
|
-
* @output Unit tests for
|
|
7
|
-
* @position Tests; validates
|
|
5
|
+
* @input Uses vitest, Testing Library, user-event, and ComplexSelector
|
|
6
|
+
* @output Unit tests for selection, trigger variants, positioning, and the imperative handle
|
|
7
|
+
* @position Tests; validates the ComplexSelector public interaction contract
|
|
8
8
|
*
|
|
9
9
|
* SYNC: When ComplexSelector.tsx API changes, update these tests.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import
|
|
13
|
-
import {
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import {beforeEach, describe, expect, it, vi} from 'vitest';
|
|
14
|
+
import {act, fireEvent, render, screen, waitFor} from '@testing-library/react';
|
|
14
15
|
import userEvent from '@testing-library/user-event';
|
|
15
|
-
import {ComplexSelector} from './ComplexSelector';
|
|
16
|
+
import {ComplexSelector, type ComplexSelectorHandle} from './ComplexSelector';
|
|
17
|
+
|
|
18
|
+
const originalMatches = HTMLElement.prototype.matches;
|
|
19
|
+
|
|
20
|
+
// Mock the Popover API, which jsdom does not implement.
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
HTMLElement.prototype.showPopover = vi.fn(function (this: HTMLElement) {
|
|
23
|
+
this.setAttribute('popover-open', '');
|
|
24
|
+
const event = new Event('toggle');
|
|
25
|
+
Object.defineProperty(event, 'newState', {value: 'open'});
|
|
26
|
+
this.dispatchEvent(event);
|
|
27
|
+
});
|
|
28
|
+
HTMLElement.prototype.hidePopover = vi.fn(function (this: HTMLElement) {
|
|
29
|
+
this.removeAttribute('popover-open');
|
|
30
|
+
const event = new Event('toggle');
|
|
31
|
+
Object.defineProperty(event, 'newState', {value: 'closed'});
|
|
32
|
+
this.dispatchEvent(event);
|
|
33
|
+
});
|
|
34
|
+
Object.defineProperty(HTMLElement.prototype, 'matches', {
|
|
35
|
+
configurable: true,
|
|
36
|
+
value: function (this: HTMLElement, selector: string): boolean {
|
|
37
|
+
if (selector === ':popover-open') {
|
|
38
|
+
return this.hasAttribute('popover-open');
|
|
39
|
+
}
|
|
40
|
+
return originalMatches.call(this, selector);
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
});
|
|
16
44
|
|
|
17
45
|
type FruitValue = {
|
|
18
46
|
fruit: 'Apple' | 'Banana';
|
|
@@ -83,6 +111,35 @@ function FruitComplexSelector({
|
|
|
83
111
|
}
|
|
84
112
|
|
|
85
113
|
describe('ComplexSelector', () => {
|
|
114
|
+
it('defaults to md and reflects explicit trigger sizes', () => {
|
|
115
|
+
const {container, rerender} = render(
|
|
116
|
+
<ComplexSelector label="Fruit blend" value="Apple">
|
|
117
|
+
{() => <div>Options</div>}
|
|
118
|
+
</ComplexSelector>,
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const getSelector = () =>
|
|
122
|
+
container.querySelector('.astryx-complex-selector');
|
|
123
|
+
|
|
124
|
+
expect(getSelector()).toHaveAttribute('data-size', 'md');
|
|
125
|
+
|
|
126
|
+
rerender(
|
|
127
|
+
<ComplexSelector label="Fruit blend" value="Apple" size="sm">
|
|
128
|
+
{() => <div>Options</div>}
|
|
129
|
+
</ComplexSelector>,
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
expect(getSelector()).toHaveAttribute('data-size', 'sm');
|
|
133
|
+
|
|
134
|
+
rerender(
|
|
135
|
+
<ComplexSelector label="Fruit blend" value="Apple" size="lg">
|
|
136
|
+
{() => <div>Options</div>}
|
|
137
|
+
</ComplexSelector>,
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
expect(getSelector()).toHaveAttribute('data-size', 'lg');
|
|
141
|
+
});
|
|
142
|
+
|
|
86
143
|
it('renders custom content with value and commits through onChange', async () => {
|
|
87
144
|
const user = userEvent.setup();
|
|
88
145
|
const onChange = vi.fn();
|
|
@@ -174,6 +231,140 @@ describe('ComplexSelector', () => {
|
|
|
174
231
|
await user.click(screen.getByRole('button', {name: 'Done', ...h}));
|
|
175
232
|
expect(trigger).toHaveAttribute('aria-expanded', 'false');
|
|
176
233
|
});
|
|
234
|
+
|
|
235
|
+
it('renders a ghost toolbar trigger with a start icon', () => {
|
|
236
|
+
const {container} = render(
|
|
237
|
+
<ComplexSelector
|
|
238
|
+
label="View options"
|
|
239
|
+
value={['name']}
|
|
240
|
+
variant="ghost"
|
|
241
|
+
startIcon="viewColumns"
|
|
242
|
+
status={{type: 'warning', message: 'Unsaved changes'}}
|
|
243
|
+
data-testid="view-options">
|
|
244
|
+
{() => <div>Columns</div>}
|
|
245
|
+
</ComplexSelector>,
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
expect(container.querySelector('.astryx-complex-selector')).toHaveAttribute(
|
|
249
|
+
'data-variant',
|
|
250
|
+
'ghost',
|
|
251
|
+
);
|
|
252
|
+
expect(container.querySelector('.astryx-field-status')).toHaveAttribute(
|
|
253
|
+
'data-variant',
|
|
254
|
+
'detached',
|
|
255
|
+
);
|
|
256
|
+
expect(
|
|
257
|
+
screen.getByTestId('view-options').querySelectorAll('svg'),
|
|
258
|
+
).toHaveLength(2);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('supports end-aligned popup positioning', () => {
|
|
262
|
+
render(
|
|
263
|
+
<ComplexSelector label="View options" value={[]} alignment="end">
|
|
264
|
+
{() => <div>Columns</div>}
|
|
265
|
+
</ComplexSelector>,
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
const popover = screen
|
|
269
|
+
.getByRole('dialog', {hidden: true})
|
|
270
|
+
.closest('[popover]');
|
|
271
|
+
expect(popover?.getAttribute('style')).toContain(
|
|
272
|
+
'position-area: self-block-end span-self-inline-start',
|
|
273
|
+
);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('exposes imperative open, close, toggle, and isOpen via handleRef', async () => {
|
|
277
|
+
const handleRef = React.createRef<ComplexSelectorHandle>();
|
|
278
|
+
render(
|
|
279
|
+
<ComplexSelector label="View options" value={[]} handleRef={handleRef}>
|
|
280
|
+
{() => <button type="button">Apply</button>}
|
|
281
|
+
</ComplexSelector>,
|
|
282
|
+
);
|
|
283
|
+
const trigger = screen.getByRole('button', {name: 'View options'});
|
|
284
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'false');
|
|
285
|
+
expect(handleRef.current?.isOpen()).toBe(false);
|
|
286
|
+
|
|
287
|
+
act(() => {
|
|
288
|
+
handleRef.current?.open();
|
|
289
|
+
});
|
|
290
|
+
await waitFor(() => {
|
|
291
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'true');
|
|
292
|
+
});
|
|
293
|
+
expect(handleRef.current?.isOpen()).toBe(true);
|
|
294
|
+
|
|
295
|
+
act(() => {
|
|
296
|
+
handleRef.current?.close();
|
|
297
|
+
});
|
|
298
|
+
await waitFor(() => {
|
|
299
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'false');
|
|
300
|
+
});
|
|
301
|
+
expect(handleRef.current?.isOpen()).toBe(false);
|
|
302
|
+
|
|
303
|
+
act(() => {
|
|
304
|
+
handleRef.current?.toggle();
|
|
305
|
+
});
|
|
306
|
+
await waitFor(() => {
|
|
307
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'true');
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
act(() => {
|
|
311
|
+
handleRef.current?.toggle();
|
|
312
|
+
});
|
|
313
|
+
await waitFor(() => {
|
|
314
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'false');
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('does not open via the imperative handle when disabled', async () => {
|
|
319
|
+
const handleRef = React.createRef<ComplexSelectorHandle>();
|
|
320
|
+
render(
|
|
321
|
+
<ComplexSelector
|
|
322
|
+
label="View options"
|
|
323
|
+
value={[]}
|
|
324
|
+
isDisabled
|
|
325
|
+
handleRef={handleRef}>
|
|
326
|
+
{() => <button type="button">Apply</button>}
|
|
327
|
+
</ComplexSelector>,
|
|
328
|
+
);
|
|
329
|
+
const trigger = screen.getByRole('button', {name: 'View options'});
|
|
330
|
+
|
|
331
|
+
act(() => {
|
|
332
|
+
handleRef.current?.open();
|
|
333
|
+
});
|
|
334
|
+
act(() => {
|
|
335
|
+
handleRef.current?.toggle();
|
|
336
|
+
});
|
|
337
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'false');
|
|
338
|
+
expect(handleRef.current?.isOpen()).toBe(false);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('does not reopen from the trigger click that follows light dismiss', async () => {
|
|
342
|
+
const user = userEvent.setup();
|
|
343
|
+
render(
|
|
344
|
+
<ComplexSelector label="View options" value={[]}>
|
|
345
|
+
{() => <button type="button">Apply</button>}
|
|
346
|
+
</ComplexSelector>,
|
|
347
|
+
);
|
|
348
|
+
const trigger = screen.getByRole('button', {name: 'View options'});
|
|
349
|
+
|
|
350
|
+
await user.click(trigger);
|
|
351
|
+
const popover = screen
|
|
352
|
+
.getByRole('dialog', {hidden: true})
|
|
353
|
+
.closest('[popover]');
|
|
354
|
+
expect(popover).not.toBeNull();
|
|
355
|
+
|
|
356
|
+
const closeEvent = new Event('toggle');
|
|
357
|
+
Object.defineProperty(closeEvent, 'newState', {value: 'closed'});
|
|
358
|
+
fireEvent(popover as HTMLElement, closeEvent);
|
|
359
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'false');
|
|
360
|
+
|
|
361
|
+
const showCallCount = vi.mocked(HTMLElement.prototype.showPopover).mock
|
|
362
|
+
.calls.length;
|
|
363
|
+
fireEvent.click(trigger);
|
|
364
|
+
expect(HTMLElement.prototype.showPopover).toHaveBeenCalledTimes(
|
|
365
|
+
showCallCount,
|
|
366
|
+
);
|
|
367
|
+
});
|
|
177
368
|
});
|
|
178
369
|
|
|
179
370
|
describe('ComplexSelector popup theme target', () => {
|