@astryxdesign/core 0.7.0-canary.f3a2181 → 0.7.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 +186 -0
- package/dist/Chat/ChatComposerInput.d.ts +0 -11
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +0 -11
- package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
- package/dist/Chat/useTriggerMenu.js +1 -1
- package/dist/Tokenizer/Tokenizer.d.ts +1 -12
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +1 -13
- package/dist/Toolbar/Toolbar.d.ts +3 -4
- package/dist/Toolbar/Toolbar.d.ts.map +1 -1
- package/dist/Toolbar/Toolbar.js +6 -6
- package/dist/Typeahead/BaseTypeahead.d.ts +0 -11
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +1 -9
- package/dist/Typeahead/Typeahead.d.ts +1 -12
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +1 -13
- package/dist/hooks/useRenamedProp.d.ts +6 -6
- package/dist/hooks/useRenamedProp.js +6 -6
- package/package.json +4 -4
- package/src/Chat/ChatComposerInput.doc.mjs +3 -3
- package/src/Chat/ChatComposerInput.test.tsx +7 -55
- package/src/Chat/ChatComposerInput.tsx +0 -36
- package/src/Chat/useTriggerMenu.tsx +1 -4
- package/src/Tokenizer/Tokenizer.doc.mjs +0 -16
- package/src/Tokenizer/Tokenizer.test.tsx +12 -63
- package/src/Tokenizer/Tokenizer.tsx +1 -25
- package/src/Toolbar/Toolbar.doc.mjs +3 -3
- package/src/Toolbar/Toolbar.test.tsx +1 -1
- package/src/Toolbar/Toolbar.tsx +9 -10
- package/src/Typeahead/BaseTypeahead.doc.mjs +0 -16
- package/src/Typeahead/BaseTypeahead.test.tsx +1 -1
- package/src/Typeahead/BaseTypeahead.tsx +1 -21
- package/src/Typeahead/Typeahead.doc.mjs +0 -7
- package/src/Typeahead/Typeahead.test.tsx +24 -42
- package/src/Typeahead/Typeahead.tsx +1 -25
- package/src/hooks/useRenamedProp.ts +6 -6
|
@@ -41,12 +41,12 @@ import { useDevWarning } from "./useDevWarning.js";
|
|
|
41
41
|
*
|
|
42
42
|
* @example
|
|
43
43
|
* ```
|
|
44
|
-
* const
|
|
45
|
-
* component: '
|
|
46
|
-
* deprecated: '
|
|
47
|
-
* deprecatedValue:
|
|
48
|
-
* replacement: '
|
|
49
|
-
* value:
|
|
44
|
+
* const label = useRenamedProp({
|
|
45
|
+
* component: 'Example',
|
|
46
|
+
* deprecated: 'oldLabel',
|
|
47
|
+
* deprecatedValue: oldLabel,
|
|
48
|
+
* replacement: 'label',
|
|
49
|
+
* value: labelFromProps,
|
|
50
50
|
* });
|
|
51
51
|
* ```
|
|
52
52
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/core",
|
|
3
|
-
"version": "0.7.0
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"displayName": "Astryx Core",
|
|
5
5
|
"description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
|
|
6
6
|
"author": "Meta Open Source",
|
|
@@ -787,8 +787,6 @@
|
|
|
787
787
|
"react-dom": ">=19.0.0"
|
|
788
788
|
},
|
|
789
789
|
"devDependencies": {
|
|
790
|
-
"@astryxdesign/a11y-spec": "0.7.0-canary.f3a2181",
|
|
791
|
-
"@astryxdesign/cli": "0.7.0-canary.f3a2181",
|
|
792
790
|
"@babel/cli": "^7.29.7",
|
|
793
791
|
"@babel/core": "^7.29.7",
|
|
794
792
|
"@babel/preset-react": "^7.29.7",
|
|
@@ -799,7 +797,9 @@
|
|
|
799
797
|
"@testing-library/jest-dom": "^6.6.0",
|
|
800
798
|
"@testing-library/react": "^16.3.2",
|
|
801
799
|
"@types/babel__core": "^7.20.5",
|
|
802
|
-
"tailwindcss": "^4.3.2"
|
|
800
|
+
"tailwindcss": "^4.3.2",
|
|
801
|
+
"@astryxdesign/a11y-spec": "0.0.0",
|
|
802
|
+
"@astryxdesign/cli": "0.7.0"
|
|
803
803
|
},
|
|
804
804
|
"dependencies": {
|
|
805
805
|
"intl-messageformat": "^11.2.9"
|
|
@@ -98,7 +98,7 @@ export const docs = {
|
|
|
98
98
|
name: 'triggers',
|
|
99
99
|
type: 'ChatComposerTrigger[]',
|
|
100
100
|
description:
|
|
101
|
-
'Trigger definitions for typeahead menus. Each trigger specifies a character (@ or /), a search source, and an onSelect handler that returns the token to insert. Per-trigger emptySearchText (ReactNode) is the message when the query matched nothing
|
|
101
|
+
'Trigger definitions for typeahead menus. Each trigger specifies a character (@ or /), a search source, and an onSelect handler that returns the token to insert. Per-trigger emptySearchText (ReactNode) is the message when the query matched nothing.',
|
|
102
102
|
},
|
|
103
103
|
{
|
|
104
104
|
name: 'debounceMs',
|
|
@@ -174,7 +174,7 @@ export const docsZh = {
|
|
|
174
174
|
placeholder: '输入为空时显示的占位文本。',
|
|
175
175
|
maxRows: '滚动前的最大可见行数。紧凑布局中使用较小值。',
|
|
176
176
|
triggers:
|
|
177
|
-
'菜单的触发定义。每个触发器指定字符(@ 或 /)、搜索源和返回要插入标记的 onSelect 处理器。每个触发器的 emptySearchText(ReactNode
|
|
177
|
+
'菜单的触发定义。每个触发器指定字符(@ 或 /)、搜索源和返回要插入标记的 onSelect 处理器。每个触发器的 emptySearchText(ReactNode)用于查询无匹配结果时的提示。',
|
|
178
178
|
debounceMs: '异步搜索源的去抖动延迟,避免过多网络请求。',
|
|
179
179
|
hasHistory: '启用 ArrowUp/Down 回溯之前提交的消息。',
|
|
180
180
|
label: '屏幕阅读器播报的无障碍标签。',
|
|
@@ -205,7 +205,7 @@ export const docsDense = {
|
|
|
205
205
|
placeholder: 'placeholder when empty',
|
|
206
206
|
maxRows: 'max visible rows before scroll; lower for compact layouts',
|
|
207
207
|
triggers:
|
|
208
|
-
'typeahead trigger defs; character(@/)+searchSource+onSelect returning token; emptySearchText (ReactNode) for no matches
|
|
208
|
+
'typeahead trigger defs; character(@/)+searchSource+onSelect returning token; emptySearchText (ReactNode) for no matches',
|
|
209
209
|
debounceMs: 'debounce for async search to avoid excess requests',
|
|
210
210
|
hasHistory: 'ArrowUp/Down to recall previous submissions',
|
|
211
211
|
label: 'a11y label for screen readers',
|
|
@@ -1136,7 +1136,7 @@ describe('ChatComposerInput', () => {
|
|
|
1136
1136
|
|
|
1137
1137
|
it('supports configurable empty/loading text', () => {
|
|
1138
1138
|
const trigger = createMentionTrigger({
|
|
1139
|
-
|
|
1139
|
+
emptySearchText: 'Nobody found',
|
|
1140
1140
|
loadingText: 'Looking up...',
|
|
1141
1141
|
menuLabel: 'People',
|
|
1142
1142
|
});
|
|
@@ -1146,7 +1146,7 @@ describe('ChatComposerInput', () => {
|
|
|
1146
1146
|
|
|
1147
1147
|
// A trigger's `emptySearchResultsText` was renamed to `emptySearchText`
|
|
1148
1148
|
// and widened from `string` to `ReactNode` (`spec:AST-056` FR1). The
|
|
1149
|
-
//
|
|
1149
|
+
// overlap ended in a minor release (`CLN-0004`, `spec:AST-017` FR31).
|
|
1150
1150
|
it('renders an element emptySearchText when the query matched nothing', async () => {
|
|
1151
1151
|
const user = userEvent.setup();
|
|
1152
1152
|
const trigger = createMentionTrigger({
|
|
@@ -1166,72 +1166,24 @@ describe('ChatComposerInput', () => {
|
|
|
1166
1166
|
});
|
|
1167
1167
|
});
|
|
1168
1168
|
|
|
1169
|
-
it('
|
|
1170
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
1171
|
-
const user = userEvent.setup();
|
|
1172
|
-
const trigger = createMentionTrigger({
|
|
1173
|
-
emptySearchResultsText: 'Nobody found',
|
|
1174
|
-
});
|
|
1175
|
-
render(<ChatComposerInput triggers={[trigger]} />);
|
|
1176
|
-
|
|
1177
|
-
await user.click(screen.getByRole('combobox'));
|
|
1178
|
-
await user.keyboard('@zzzzz');
|
|
1179
|
-
|
|
1180
|
-
await waitFor(() => {
|
|
1181
|
-
expect(screen.getByText('Nobody found')).toBeInTheDocument();
|
|
1182
|
-
});
|
|
1183
|
-
expect(warn).toHaveBeenCalledWith(
|
|
1184
|
-
expect.stringContaining(
|
|
1185
|
-
'ChatComposerInput: A trigger sets `emptySearchResultsText`, which is deprecated',
|
|
1186
|
-
),
|
|
1187
|
-
);
|
|
1188
|
-
warn.mockRestore();
|
|
1189
|
-
});
|
|
1190
|
-
|
|
1191
|
-
it('treats an explicit null key as not given, falling through to the old one', async () => {
|
|
1169
|
+
it('no longer reads the removed emptySearchResultsText key (CLN-0004)', async () => {
|
|
1192
1170
|
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
1193
1171
|
const user = userEvent.setup();
|
|
1194
1172
|
const trigger = createMentionTrigger({
|
|
1173
|
+
// @ts-expect-error -- removed in the minor release (CLN-0004)
|
|
1195
1174
|
emptySearchResultsText: 'Nobody found',
|
|
1196
|
-
emptySearchText: null,
|
|
1197
1175
|
});
|
|
1198
1176
|
render(<ChatComposerInput triggers={[trigger]} />);
|
|
1199
1177
|
|
|
1200
1178
|
await user.click(screen.getByRole('combobox'));
|
|
1201
1179
|
await user.keyboard('@zzzzz');
|
|
1202
1180
|
|
|
1203
|
-
// The same meaning the typeahead family gives `null`: not given, so
|
|
1204
|
-
// the released key still supplies the message and no warning claims a
|
|
1205
|
-
// winner the menu did not pick.
|
|
1206
1181
|
await waitFor(() => {
|
|
1207
|
-
expect(screen.getByText('
|
|
1182
|
+
expect(screen.getByText('No results')).toBeInTheDocument();
|
|
1208
1183
|
});
|
|
1184
|
+
expect(screen.queryByText('Nobody found')).not.toBeInTheDocument();
|
|
1209
1185
|
expect(warn).not.toHaveBeenCalledWith(
|
|
1210
|
-
expect.stringContaining('
|
|
1211
|
-
);
|
|
1212
|
-
warn.mockRestore();
|
|
1213
|
-
});
|
|
1214
|
-
|
|
1215
|
-
it('lets the new key win when a trigger sets both, and warns', async () => {
|
|
1216
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
1217
|
-
const user = userEvent.setup();
|
|
1218
|
-
const trigger = createMentionTrigger({
|
|
1219
|
-
emptySearchResultsText: 'Old copy',
|
|
1220
|
-
emptySearchText: 'New copy',
|
|
1221
|
-
});
|
|
1222
|
-
render(<ChatComposerInput triggers={[trigger]} />);
|
|
1223
|
-
|
|
1224
|
-
await user.click(screen.getByRole('combobox'));
|
|
1225
|
-
await user.keyboard('@zzzzz');
|
|
1226
|
-
|
|
1227
|
-
await waitFor(() => {
|
|
1228
|
-
expect(screen.getByText('New copy')).toBeInTheDocument();
|
|
1229
|
-
});
|
|
1230
|
-
expect(screen.queryByText('Old copy')).not.toBeInTheDocument();
|
|
1231
|
-
expect(warn).toHaveBeenCalledWith(
|
|
1232
|
-
expect.stringContaining(
|
|
1233
|
-
'ChatComposerInput: A trigger sets both `emptySearchResultsText` and `emptySearchText`',
|
|
1234
|
-
),
|
|
1186
|
+
expect.stringContaining('is deprecated'),
|
|
1235
1187
|
);
|
|
1236
1188
|
warn.mockRestore();
|
|
1237
1189
|
});
|
|
@@ -69,7 +69,6 @@ import {Badge, type BadgeProps} from '../Badge';
|
|
|
69
69
|
import {useChatComposerContext} from './ChatContext';
|
|
70
70
|
import {themeProps} from '../utils/themeProps';
|
|
71
71
|
import {useTranslator} from '../i18n';
|
|
72
|
-
import {useDevWarning} from '../hooks/useDevWarning';
|
|
73
72
|
|
|
74
73
|
// =============================================================================
|
|
75
74
|
// Types
|
|
@@ -167,17 +166,6 @@ export type ChatComposerTrigger = {
|
|
|
167
166
|
* @default 'No results'
|
|
168
167
|
*/
|
|
169
168
|
emptySearchText?: ReactNode;
|
|
170
|
-
/**
|
|
171
|
-
* Text shown when no results found.
|
|
172
|
-
* @default 'No results'
|
|
173
|
-
* @deprecated `DEP-0004`. Renamed to `emptySearchText`, which takes a
|
|
174
|
-
* `ReactNode` rather than a `string` — every existing value stays valid
|
|
175
|
-
* (`spec:AST-056` FR1, FR7). Still works exactly as released;
|
|
176
|
-
* `emptySearchText` wins when both are set. Removal is `CLN-0004`, in a
|
|
177
|
-
* later minor whose frozen manifest carries both ids (`spec:AST-017`
|
|
178
|
-
* FR31).
|
|
179
|
-
*/
|
|
180
|
-
emptySearchResultsText?: string;
|
|
181
169
|
/** Text shown during async search. @default 'Searching\u2026' */
|
|
182
170
|
loadingText?: string;
|
|
183
171
|
/** Accessible label for the menu. @default 'Suggestions' */
|
|
@@ -606,30 +594,6 @@ export function ChatComposerInput(props: ChatComposerInputProps) {
|
|
|
606
594
|
insertTextRef.current = insertText;
|
|
607
595
|
|
|
608
596
|
// --- Trigger menu ---
|
|
609
|
-
// Each trigger's empty-result message was renamed (`spec:AST-056` FR1); the
|
|
610
|
-
// released key keeps working through the overlap and development says which
|
|
611
|
-
// one was read (`spec:AST-017` FR28).
|
|
612
|
-
// `!= null` matches the `??` the menu resolves with, so an explicit `null`
|
|
613
|
-
// counts as "not given" on both names and the warnings cannot claim a
|
|
614
|
-
// winner the render did not pick.
|
|
615
|
-
const triggersWithDeprecatedEmptyText =
|
|
616
|
-
triggers?.filter(trigger => trigger.emptySearchResultsText != null) ?? [];
|
|
617
|
-
const triggersWithBothEmptyTexts = triggersWithDeprecatedEmptyText.filter(
|
|
618
|
-
trigger => trigger.emptySearchText != null,
|
|
619
|
-
);
|
|
620
|
-
useDevWarning(
|
|
621
|
-
'ChatComposerInput',
|
|
622
|
-
'A trigger sets `emptySearchResultsText`, which is deprecated; use ' +
|
|
623
|
-
'`emptySearchText` instead. It still works exactly as released.',
|
|
624
|
-
triggersWithDeprecatedEmptyText.length > triggersWithBothEmptyTexts.length,
|
|
625
|
-
);
|
|
626
|
-
useDevWarning(
|
|
627
|
-
'ChatComposerInput',
|
|
628
|
-
'A trigger sets both `emptySearchResultsText` and `emptySearchText`; ' +
|
|
629
|
-
'`emptySearchText` wins. `emptySearchResultsText` is deprecated — ' +
|
|
630
|
-
'drop it.',
|
|
631
|
-
triggersWithBothEmptyTexts.length > 0,
|
|
632
|
-
);
|
|
633
597
|
const triggerMenu = useTriggerMenu({
|
|
634
598
|
triggers,
|
|
635
599
|
editableRef,
|
|
@@ -640,10 +640,7 @@ export function useTriggerMenu(
|
|
|
640
640
|
|
|
641
641
|
const renderMenu = useCallback((): ReactNode => {
|
|
642
642
|
const trigger = state.activeTrigger;
|
|
643
|
-
const emptyText =
|
|
644
|
-
trigger?.emptySearchText ??
|
|
645
|
-
trigger?.emptySearchResultsText ??
|
|
646
|
-
'No results';
|
|
643
|
+
const emptyText = trigger?.emptySearchText ?? 'No results';
|
|
647
644
|
const loadingText = trigger?.loadingText ?? 'Searching\u2026';
|
|
648
645
|
|
|
649
646
|
let listContent: ReactNode;
|
|
@@ -180,13 +180,6 @@ export const docs = {
|
|
|
180
180
|
'Content shown when the query matched nothing. Takes a ReactNode, so the dead end can carry a link or a create row. Announced in a polite live region as the text it renders, read from the DOM; aria-hidden parts stay out of both, and content that renders no text announces nothing. null counts as not given, like undefined, and falls through to the default; pass an empty string to render nothing.',
|
|
181
181
|
default: "'No results found'",
|
|
182
182
|
},
|
|
183
|
-
{
|
|
184
|
-
name: 'emptySearchResultsText',
|
|
185
|
-
type: 'string',
|
|
186
|
-
description:
|
|
187
|
-
'Deprecated: renamed to emptySearchText, which takes a ReactNode rather than a string, so every existing value stays valid. Still works exactly as released; emptySearchText wins when both are set.',
|
|
188
|
-
default: "'No results found'",
|
|
189
|
-
},
|
|
190
183
|
{
|
|
191
184
|
name: 'hasAutoFocus',
|
|
192
185
|
type: 'boolean',
|
|
@@ -559,13 +552,6 @@ export const docsZh = {
|
|
|
559
552
|
'查询无匹配结果时显示的内容。接受 ReactNode,因此可在无结果处放置链接或创建入口。会从 DOM 读取其渲染出的文本在礼貌性实时区域中播报;aria-hidden 的部分两处都不包含,不渲染任何文本的内容则不会播报。null 与 undefined 同样视为未提供,将回退到默认值;若要不渲染任何内容,请传入空字符串。',
|
|
560
553
|
default: "'No results found'",
|
|
561
554
|
},
|
|
562
|
-
{
|
|
563
|
-
name: 'emptySearchResultsText',
|
|
564
|
-
type: 'string',
|
|
565
|
-
description:
|
|
566
|
-
'已弃用:改名为 emptySearchText,其类型由 string 放宽为 ReactNode,原有取值全部仍然有效。仍按已发布行为工作;两者同时设置时以 emptySearchText 为准。',
|
|
567
|
-
default: "'No results found'",
|
|
568
|
-
},
|
|
569
555
|
{
|
|
570
556
|
name: 'hasAutoFocus',
|
|
571
557
|
type: 'boolean',
|
|
@@ -831,8 +817,6 @@ export const docsDense = {
|
|
|
831
817
|
minQueryLength:
|
|
832
818
|
'Min query length before searching. Menu stays closed below it, except the hasCreate entry.',
|
|
833
819
|
emptySearchText: 'Content when the query matched nothing. ReactNode.',
|
|
834
|
-
emptySearchResultsText:
|
|
835
|
-
'deprecated, use emptySearchText (ReactNode). still works as released; emptySearchText wins if both set.',
|
|
836
820
|
hasAutoFocus: 'Auto-focus input on mount.',
|
|
837
821
|
size: 'Input+token size.',
|
|
838
822
|
debounceMs: 'Search debounce delay ms. 0 for sync sources.',
|
|
@@ -1762,9 +1762,9 @@ describe('Tokenizer end-lane reserve', () => {
|
|
|
1762
1762
|
});
|
|
1763
1763
|
|
|
1764
1764
|
// `emptySearchResultsText` was renamed to `emptySearchText` and widened from
|
|
1765
|
-
// `string` to `ReactNode` (`spec:AST-056` FR1).
|
|
1766
|
-
//
|
|
1767
|
-
//
|
|
1765
|
+
// `string` to `ReactNode` (`spec:AST-056` FR1). The overlap ended in a minor
|
|
1766
|
+
// release (`CLN-0001`–`CLN-0004`, `spec:AST-017` FR31): the old name is no
|
|
1767
|
+
// longer part of the type and is no longer read.
|
|
1768
1768
|
describe('Tokenizer emptySearchText', () => {
|
|
1769
1769
|
async function searchForNothing() {
|
|
1770
1770
|
const input = screen.getByRole('combobox');
|
|
@@ -1847,33 +1847,6 @@ describe('Tokenizer emptySearchText', () => {
|
|
|
1847
1847
|
).not.toContain('→');
|
|
1848
1848
|
});
|
|
1849
1849
|
|
|
1850
|
-
it('treats an explicit null as not given, falling through to the old name', async () => {
|
|
1851
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
1852
|
-
render(
|
|
1853
|
-
<Tokenizer
|
|
1854
|
-
label="People"
|
|
1855
|
-
searchSource={userSource}
|
|
1856
|
-
value={[]}
|
|
1857
|
-
onChange={() => {}}
|
|
1858
|
-
debounceMs={0}
|
|
1859
|
-
emptySearchResultsText="Nobody found"
|
|
1860
|
-
emptySearchText={null}
|
|
1861
|
-
/>,
|
|
1862
|
-
);
|
|
1863
|
-
await searchForNothing();
|
|
1864
|
-
|
|
1865
|
-
// `null` is what this prop's own `??` default already treats as absent,
|
|
1866
|
-
// so the released name still supplies the message and the warning says
|
|
1867
|
-
// only that it is deprecated.
|
|
1868
|
-
await waitFor(() => {
|
|
1869
|
-
expect(screen.getByText('Nobody found')).toBeInTheDocument();
|
|
1870
|
-
});
|
|
1871
|
-
expect(warn).not.toHaveBeenCalledWith(
|
|
1872
|
-
expect.stringContaining('are both set'),
|
|
1873
|
-
);
|
|
1874
|
-
warn.mockRestore();
|
|
1875
|
-
});
|
|
1876
|
-
|
|
1877
1850
|
it('falls through to the default when null is the only value given', async () => {
|
|
1878
1851
|
render(
|
|
1879
1852
|
<Tokenizer
|
|
@@ -1892,8 +1865,10 @@ describe('Tokenizer emptySearchText', () => {
|
|
|
1892
1865
|
});
|
|
1893
1866
|
});
|
|
1894
1867
|
|
|
1895
|
-
it('
|
|
1868
|
+
it('no longer reads the removed emptySearchResultsText (CLN-0001)', async () => {
|
|
1896
1869
|
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
1870
|
+
// The unknown prop reaches the root element, which React reports.
|
|
1871
|
+
const error = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
1897
1872
|
render(
|
|
1898
1873
|
<Tokenizer
|
|
1899
1874
|
label="People"
|
|
@@ -1901,47 +1876,21 @@ describe('Tokenizer emptySearchText', () => {
|
|
|
1901
1876
|
value={[]}
|
|
1902
1877
|
onChange={() => {}}
|
|
1903
1878
|
debounceMs={0}
|
|
1879
|
+
// @ts-expect-error -- removed in the minor release (CLN-0001)
|
|
1904
1880
|
emptySearchResultsText="Nobody found"
|
|
1905
1881
|
/>,
|
|
1906
1882
|
);
|
|
1907
1883
|
await searchForNothing();
|
|
1908
1884
|
|
|
1909
1885
|
await waitFor(() => {
|
|
1910
|
-
expect(screen.getByText('
|
|
1911
|
-
});
|
|
1912
|
-
expect(warn).toHaveBeenCalledWith(
|
|
1913
|
-
expect.stringContaining(
|
|
1914
|
-
'Tokenizer: `emptySearchResultsText` is deprecated; use `emptySearchText`',
|
|
1915
|
-
),
|
|
1916
|
-
);
|
|
1917
|
-
warn.mockRestore();
|
|
1918
|
-
});
|
|
1919
|
-
|
|
1920
|
-
it('lets the new name win when both are set, and warns', async () => {
|
|
1921
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
1922
|
-
render(
|
|
1923
|
-
<Tokenizer
|
|
1924
|
-
label="People"
|
|
1925
|
-
searchSource={userSource}
|
|
1926
|
-
value={[]}
|
|
1927
|
-
onChange={() => {}}
|
|
1928
|
-
debounceMs={0}
|
|
1929
|
-
emptySearchResultsText="Old copy"
|
|
1930
|
-
emptySearchText="New copy"
|
|
1931
|
-
/>,
|
|
1932
|
-
);
|
|
1933
|
-
await searchForNothing();
|
|
1934
|
-
|
|
1935
|
-
await waitFor(() => {
|
|
1936
|
-
expect(screen.getByText('New copy')).toBeInTheDocument();
|
|
1886
|
+
expect(screen.getByText('No results found')).toBeInTheDocument();
|
|
1937
1887
|
});
|
|
1938
|
-
expect(screen.queryByText('
|
|
1939
|
-
expect(warn).toHaveBeenCalledWith(
|
|
1940
|
-
expect.stringContaining(
|
|
1941
|
-
'Tokenizer: `emptySearchResultsText` and `emptySearchText` are both set',
|
|
1942
|
-
),
|
|
1888
|
+
expect(screen.queryByText('Nobody found')).not.toBeInTheDocument();
|
|
1889
|
+
expect(warn).not.toHaveBeenCalledWith(
|
|
1890
|
+
expect.stringContaining('is deprecated'),
|
|
1943
1891
|
);
|
|
1944
1892
|
warn.mockRestore();
|
|
1893
|
+
error.mockRestore();
|
|
1945
1894
|
});
|
|
1946
1895
|
});
|
|
1947
1896
|
|
|
@@ -68,7 +68,6 @@ import type {SearchableItem, SearchSource} from '../Typeahead/types';
|
|
|
68
68
|
import {mergeProps} from '../utils';
|
|
69
69
|
import {themeProps} from '../utils/themeProps';
|
|
70
70
|
import {useTranslator} from '../i18n';
|
|
71
|
-
import {useRenamedProp} from '../hooks/useRenamedProp';
|
|
72
71
|
|
|
73
72
|
// Re-export status types for convenience
|
|
74
73
|
export type {
|
|
@@ -206,18 +205,6 @@ export interface TokenizerProps<T extends SearchableItem> extends Omit<
|
|
|
206
205
|
* @default 'No results found'
|
|
207
206
|
*/
|
|
208
207
|
emptySearchText?: ReactNode;
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Text shown when no results found.
|
|
212
|
-
* @default 'No results found'
|
|
213
|
-
* @deprecated `DEP-0001`. Renamed to `emptySearchText`, which takes a
|
|
214
|
-
* `ReactNode` rather than a `string` — every existing value stays valid
|
|
215
|
-
* (`spec:AST-056` FR1, FR7). Still works exactly as released;
|
|
216
|
-
* `emptySearchText` wins when both are set. Removal is `CLN-0001`, in a
|
|
217
|
-
* later minor whose frozen manifest carries both ids (`spec:AST-017`
|
|
218
|
-
* FR31).
|
|
219
|
-
*/
|
|
220
|
-
emptySearchResultsText?: string;
|
|
221
208
|
/** Whether the input is disabled. @default false */
|
|
222
209
|
isDisabled?: boolean;
|
|
223
210
|
/**
|
|
@@ -577,8 +564,7 @@ export function Tokenizer<T extends SearchableItem>({
|
|
|
577
564
|
maxMenuItems,
|
|
578
565
|
menuWidth,
|
|
579
566
|
minQueryLength,
|
|
580
|
-
|
|
581
|
-
emptySearchText: emptySearchTextFromProps,
|
|
567
|
+
emptySearchText,
|
|
582
568
|
isDisabled = false,
|
|
583
569
|
htmlName,
|
|
584
570
|
disabledMessage,
|
|
@@ -633,16 +619,6 @@ export function Tokenizer<T extends SearchableItem>({
|
|
|
633
619
|
isEnabled: showsDisabledMessage,
|
|
634
620
|
});
|
|
635
621
|
|
|
636
|
-
// The replacement wins, the released name keeps working, and development
|
|
637
|
-
// says which one was read (`spec:AST-056` FR7, `spec:AST-017` FR28).
|
|
638
|
-
const emptySearchText = useRenamedProp<ReactNode>({
|
|
639
|
-
component: 'Tokenizer',
|
|
640
|
-
deprecated: 'emptySearchResultsText',
|
|
641
|
-
deprecatedValue: emptySearchResultsText,
|
|
642
|
-
replacement: 'emptySearchText',
|
|
643
|
-
value: emptySearchTextFromProps,
|
|
644
|
-
});
|
|
645
|
-
|
|
646
622
|
useImperativeHandle(handleRef, () => ({
|
|
647
623
|
focus() {
|
|
648
624
|
inputRef.current?.focus();
|
|
@@ -127,7 +127,7 @@ export const docs = {
|
|
|
127
127
|
name: 'size',
|
|
128
128
|
type: "'sm' | 'md' | 'lg'",
|
|
129
129
|
description:
|
|
130
|
-
'Size of the toolbar. Controls
|
|
130
|
+
'Size of the toolbar. Controls minimum height and coordinates with Button, TextInput, TabList, and Selector; children inherit this size as their default via SizeContext.',
|
|
131
131
|
default: "'md'",
|
|
132
132
|
},
|
|
133
133
|
{
|
|
@@ -190,7 +190,7 @@ export const docsZh = {
|
|
|
190
190
|
startContent: '起始内容(LTR 中靠左对齐)。',
|
|
191
191
|
centerContent: '居中内容。切换为 CSS grid(1fr auto 1fr)。',
|
|
192
192
|
endContent: '结束内容(LTR 中靠右对齐)。',
|
|
193
|
-
size: '
|
|
193
|
+
size: '工具栏尺寸。控制最小高度,子组件通过 SizeContext 继承此尺寸作为默认值。',
|
|
194
194
|
gap: '插槽内项目间距。',
|
|
195
195
|
orientation: '键盘导航方向。控制方向键方向。',
|
|
196
196
|
variant: '传递给 Section 的视觉变体。',
|
|
@@ -241,7 +241,7 @@ export const docsDense = {
|
|
|
241
241
|
startContent: 'Start-aligned content.',
|
|
242
242
|
centerContent: 'Centered content; switches to 3-col grid.',
|
|
243
243
|
endContent: 'End-aligned content.',
|
|
244
|
-
size: 'Toolbar size; controls
|
|
244
|
+
size: 'Toolbar size; controls min-height + cascades to children via SizeContext.',
|
|
245
245
|
gap: 'Gap between slot items.',
|
|
246
246
|
orientation: 'Keyboard nav direction.',
|
|
247
247
|
variant: 'Visual variant for Section.',
|
package/src/Toolbar/Toolbar.tsx
CHANGED
|
@@ -113,18 +113,18 @@ const dynamicStyles = stylex.create({
|
|
|
113
113
|
});
|
|
114
114
|
|
|
115
115
|
/**
|
|
116
|
-
* Default block padding per toolbar size. Inline padding
|
|
117
|
-
*
|
|
118
|
-
*
|
|
116
|
+
* Default block padding per toolbar size. Inline padding comes from the
|
|
117
|
+
* parent container (Card, Section, LayoutHeader) via the Section's
|
|
118
|
+
* theme default — the toolbar only controls its vertical tightness.
|
|
119
119
|
*/
|
|
120
120
|
const defaultBlockPaddingForSize: Record<ElementSize, SpacingStep> = {
|
|
121
|
-
sm:
|
|
121
|
+
sm: 2,
|
|
122
122
|
md: 2,
|
|
123
123
|
lg: 2,
|
|
124
124
|
};
|
|
125
125
|
|
|
126
126
|
const blockPaddingVarForSize: Record<ElementSize, string> = {
|
|
127
|
-
sm: spacingVars['--spacing-
|
|
127
|
+
sm: spacingVars['--spacing-2'],
|
|
128
128
|
md: spacingVars['--spacing-2'],
|
|
129
129
|
lg: spacingVars['--spacing-2'],
|
|
130
130
|
};
|
|
@@ -135,7 +135,7 @@ const blockPaddingVarForSize: Record<ElementSize, string> = {
|
|
|
135
135
|
* creating even spacing around edge-compensated items (ghost buttons, tabs).
|
|
136
136
|
*/
|
|
137
137
|
const edgeCompInsetForSize: Record<ElementSize, string> = {
|
|
138
|
-
sm: `calc(var(--container-padding-inline-start, ${spacingVars['--spacing-4']}) - ${spacingVars['--spacing-
|
|
138
|
+
sm: `calc(var(--container-padding-inline-start, ${spacingVars['--spacing-4']}) - ${spacingVars['--spacing-2']})`,
|
|
139
139
|
md: `calc(var(--container-padding-inline-start, ${spacingVars['--spacing-4']}) - ${spacingVars['--spacing-2']})`,
|
|
140
140
|
lg: `calc(var(--container-padding-inline-start, ${spacingVars['--spacing-4']}) - ${spacingVars['--spacing-2']})`,
|
|
141
141
|
};
|
|
@@ -164,11 +164,10 @@ export interface ToolbarProps extends BaseProps<HTMLDivElement> {
|
|
|
164
164
|
*/
|
|
165
165
|
label: string;
|
|
166
166
|
/**
|
|
167
|
-
* Size of the toolbar. Coordinates
|
|
168
|
-
*
|
|
169
|
-
* the surrounding content line.
|
|
167
|
+
* Size of the toolbar. Coordinates with Button, TextInput, TabList, and Selector —
|
|
168
|
+
* children inherit this size as their default via SizeContext.
|
|
170
169
|
*
|
|
171
|
-
* - `'sm'`: Compact —
|
|
170
|
+
* - `'sm'`: Compact — fits sm buttons/inputs (28px elements)
|
|
172
171
|
* - `'md'`: Standard — fits md buttons/inputs (32px elements)
|
|
173
172
|
* - `'lg'`: Spacious — fits lg buttons/inputs (36px elements)
|
|
174
173
|
* @default 'md'
|
|
@@ -165,13 +165,6 @@ export const docs = {
|
|
|
165
165
|
'Content shown when the query matched nothing. Takes a ReactNode, so the dead end can carry a link or a create row. Announced in a polite live region as the text it renders, read from the DOM; aria-hidden parts stay out of both, and content that renders no text announces nothing. null counts as not given, like undefined, and falls through to the default; pass an empty string to render nothing.',
|
|
166
166
|
default: "'No results found'",
|
|
167
167
|
},
|
|
168
|
-
{
|
|
169
|
-
name: 'emptySearchResultsText',
|
|
170
|
-
type: 'string',
|
|
171
|
-
description:
|
|
172
|
-
'Deprecated: renamed to emptySearchText, which takes a ReactNode rather than a string, so every existing value stays valid. Still works exactly as released; emptySearchText wins when both are set.',
|
|
173
|
-
default: "'No results found'",
|
|
174
|
-
},
|
|
175
168
|
{
|
|
176
169
|
name: 'isDisabled',
|
|
177
170
|
type: 'boolean',
|
|
@@ -331,13 +324,6 @@ export const docsZh = {
|
|
|
331
324
|
'查询无匹配结果时显示的内容。接受 ReactNode,因此可在无结果处放置链接或创建入口。会从 DOM 读取其渲染出的文本在礼貌性实时区域中播报;aria-hidden 的部分两处都不包含,不渲染任何文本的内容则不会播报。null 与 undefined 同样视为未提供,将回退到默认值;若要不渲染任何内容,请传入空字符串。',
|
|
332
325
|
default: "'No results found'",
|
|
333
326
|
},
|
|
334
|
-
{
|
|
335
|
-
name: 'emptySearchResultsText',
|
|
336
|
-
type: 'string',
|
|
337
|
-
description:
|
|
338
|
-
'已弃用:改名为 emptySearchText,其类型由 string 放宽为 ReactNode,原有取值全部仍然有效。仍按已发布行为工作;两者同时设置时以 emptySearchText 为准。',
|
|
339
|
-
default: "'No results found'",
|
|
340
|
-
},
|
|
341
327
|
{
|
|
342
328
|
name: 'isDisabled',
|
|
343
329
|
type: 'boolean',
|
|
@@ -479,8 +465,6 @@ export const docsDense = {
|
|
|
479
465
|
minQueryLength:
|
|
480
466
|
'Min query length before searching. Menu stays closed below it.',
|
|
481
467
|
emptySearchText: 'Content when the query matched nothing. ReactNode.',
|
|
482
|
-
emptySearchResultsText:
|
|
483
|
-
'deprecated, use emptySearchText (ReactNode). still works as released; emptySearchText wins if both set.',
|
|
484
468
|
isDisabled: 'Whether input disabled.',
|
|
485
469
|
isFocusableDisabled:
|
|
486
470
|
'Keeps disabled input focusable and blocks text entry; an already-open highlight can still be selected with Enter.',
|
|
@@ -33,7 +33,6 @@ import type {StyleXStyles} from '@stylexjs/stylex';
|
|
|
33
33
|
import {usePopover} from '../Popover/usePopover';
|
|
34
34
|
import {useAnnounce} from '../hooks/useAnnounce';
|
|
35
35
|
import {useAnnounceRenderedText} from '../hooks/useAnnounceRenderedText';
|
|
36
|
-
import {useRenamedProp} from '../hooks/useRenamedProp';
|
|
37
36
|
import {useHighlightedOptionScroll} from '../hooks/useHighlightedOptionScroll';
|
|
38
37
|
import {useIsomorphicLayoutEffect} from '../hooks/useIsomorphicLayoutEffect';
|
|
39
38
|
import {isImeKeyEvent} from '../utils/ime';
|
|
@@ -143,18 +142,6 @@ export interface BaseTypeaheadProps<T extends SearchableItem> extends Omit<
|
|
|
143
142
|
*/
|
|
144
143
|
emptySearchText?: ReactNode;
|
|
145
144
|
|
|
146
|
-
/**
|
|
147
|
-
* Text shown when no results found.
|
|
148
|
-
* @default 'No results found'
|
|
149
|
-
* @deprecated `DEP-0003`. Renamed to `emptySearchText`, which takes a
|
|
150
|
-
* `ReactNode` rather than a `string` — every existing value stays valid
|
|
151
|
-
* (`spec:AST-056` FR1, FR7). Still works exactly as released;
|
|
152
|
-
* `emptySearchText` wins when both are set. Removal is `CLN-0003`, in a
|
|
153
|
-
* later minor whose frozen manifest carries both ids (`spec:AST-017`
|
|
154
|
-
* FR31).
|
|
155
|
-
*/
|
|
156
|
-
emptySearchResultsText?: string;
|
|
157
|
-
|
|
158
145
|
/**
|
|
159
146
|
* Whether the input is disabled.
|
|
160
147
|
* @default false
|
|
@@ -465,7 +452,6 @@ export const BaseTypeahead = function BaseTypeahead<T extends SearchableItem>({
|
|
|
465
452
|
maxMenuItems = 10,
|
|
466
453
|
menuWidth,
|
|
467
454
|
minQueryLength = 1,
|
|
468
|
-
emptySearchResultsText: deprecatedEmptySearchResultsText,
|
|
469
455
|
emptySearchText: emptySearchTextFromProps,
|
|
470
456
|
isDisabled = false,
|
|
471
457
|
isFocusableDisabled = false,
|
|
@@ -500,13 +486,7 @@ export const BaseTypeahead = function BaseTypeahead<T extends SearchableItem>({
|
|
|
500
486
|
const placeholder =
|
|
501
487
|
placeholderFromProps ?? t('@astryx.typeahead.searchPlaceholder');
|
|
502
488
|
const emptySearchText =
|
|
503
|
-
|
|
504
|
-
component: 'BaseTypeahead',
|
|
505
|
-
deprecated: 'emptySearchResultsText',
|
|
506
|
-
deprecatedValue: deprecatedEmptySearchResultsText,
|
|
507
|
-
replacement: 'emptySearchText',
|
|
508
|
-
value: emptySearchTextFromProps,
|
|
509
|
-
}) ?? t('@astryx.typeahead.emptySearchResults');
|
|
489
|
+
emptySearchTextFromProps ?? t('@astryx.typeahead.emptySearchResults');
|
|
510
490
|
// The empty-state row carries the message visually, and the live region has
|
|
511
491
|
// to speak the same words. `emptySearchText` takes a ReactNode, so they are
|
|
512
492
|
// read off the rendered row after it renders rather than guessed from the
|
|
@@ -230,13 +230,6 @@ export const docs = {
|
|
|
230
230
|
'Content shown when the query matched nothing. Takes a ReactNode, so the dead end can carry a link or a create row. Announced in a polite live region as the text it renders, read from the DOM; aria-hidden parts stay out of both, and content that renders no text announces nothing. null counts as not given, like undefined, and falls through to the default; pass an empty string to render nothing.',
|
|
231
231
|
default: "'No results found'",
|
|
232
232
|
},
|
|
233
|
-
{
|
|
234
|
-
name: 'emptySearchResultsText',
|
|
235
|
-
type: 'string',
|
|
236
|
-
description:
|
|
237
|
-
'Deprecated: renamed to emptySearchText, which takes a ReactNode rather than a string, so every existing value stays valid. Still works exactly as released; emptySearchText wins when both are set.',
|
|
238
|
-
default: "'No results found'",
|
|
239
|
-
},
|
|
240
233
|
{
|
|
241
234
|
name: 'hasAutoFocus',
|
|
242
235
|
type: 'boolean',
|