@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.
Files changed (38) hide show
  1. package/CHANGELOG.md +186 -0
  2. package/dist/Chat/ChatComposerInput.d.ts +0 -11
  3. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  4. package/dist/Chat/ChatComposerInput.js +0 -11
  5. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  6. package/dist/Chat/useTriggerMenu.js +1 -1
  7. package/dist/Tokenizer/Tokenizer.d.ts +1 -12
  8. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  9. package/dist/Tokenizer/Tokenizer.js +1 -13
  10. package/dist/Toolbar/Toolbar.d.ts +3 -4
  11. package/dist/Toolbar/Toolbar.d.ts.map +1 -1
  12. package/dist/Toolbar/Toolbar.js +6 -6
  13. package/dist/Typeahead/BaseTypeahead.d.ts +0 -11
  14. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  15. package/dist/Typeahead/BaseTypeahead.js +1 -9
  16. package/dist/Typeahead/Typeahead.d.ts +1 -12
  17. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  18. package/dist/Typeahead/Typeahead.js +1 -13
  19. package/dist/hooks/useRenamedProp.d.ts +6 -6
  20. package/dist/hooks/useRenamedProp.js +6 -6
  21. package/package.json +4 -4
  22. package/src/Chat/ChatComposerInput.doc.mjs +3 -3
  23. package/src/Chat/ChatComposerInput.test.tsx +7 -55
  24. package/src/Chat/ChatComposerInput.tsx +0 -36
  25. package/src/Chat/useTriggerMenu.tsx +1 -4
  26. package/src/Tokenizer/Tokenizer.doc.mjs +0 -16
  27. package/src/Tokenizer/Tokenizer.test.tsx +12 -63
  28. package/src/Tokenizer/Tokenizer.tsx +1 -25
  29. package/src/Toolbar/Toolbar.doc.mjs +3 -3
  30. package/src/Toolbar/Toolbar.test.tsx +1 -1
  31. package/src/Toolbar/Toolbar.tsx +9 -10
  32. package/src/Typeahead/BaseTypeahead.doc.mjs +0 -16
  33. package/src/Typeahead/BaseTypeahead.test.tsx +1 -1
  34. package/src/Typeahead/BaseTypeahead.tsx +1 -21
  35. package/src/Typeahead/Typeahead.doc.mjs +0 -7
  36. package/src/Typeahead/Typeahead.test.tsx +24 -42
  37. package/src/Typeahead/Typeahead.tsx +1 -25
  38. 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 emptySearchText = useRenamedProp({
45
- * component: 'Tokenizer',
46
- * deprecated: 'emptySearchResultsText',
47
- * deprecatedValue: emptySearchResultsText,
48
- * replacement: 'emptySearchText',
49
- * value: emptySearchTextFromProps,
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-canary.f3a2181",
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; the older emptySearchResultsText (string) is deprecated and still works.',
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)用于查询无匹配结果时的提示;旧的 emptySearchResultsText(string)已弃用,但仍可使用。',
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, emptySearchResultsText deprecated',
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
- emptySearchResultsText: 'Nobody found',
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
- // released key keeps working through the overlap (`spec:AST-017` FR28).
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('keeps the deprecated key working, and says it is deprecated', async () => {
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('Nobody found')).toBeInTheDocument();
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('sets both'),
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). It is a released prop with a
1766
- // victim, so the replacement ships first and the old name keeps working
1767
- // through the overlap (`spec:AST-017` FR28, FR29).
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('keeps the deprecated name working, and says it is deprecated', async () => {
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('Nobody found')).toBeInTheDocument();
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('Old copy')).not.toBeInTheDocument();
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
- emptySearchResultsText,
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 block padding and minimum height, and coordinates with Button, TextInput, TabList, and Selector; children inherit this size as their default via SizeContext. Compact (sm) toolbars use a 4px block inset while inline padding remains container-owned for alignment.',
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: '工具栏尺寸。控制块轴内边距和最小高度,子组件通过 SizeContext 继承此尺寸作为默认值。sm 工具栏使用 4px 块轴内边距;行内内边距由容器控制以保持对齐。',
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 block padding + min-height and cascades to children via SizeContext. sm uses a 4px block inset; inline padding remains container-owned.',
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.',
@@ -154,7 +154,7 @@ describe('Toolbar', () => {
154
154
  });
155
155
 
156
156
  it.each([
157
- ['sm', 1],
157
+ ['sm', 2],
158
158
  ['md', 2],
159
159
  ] as const)(
160
160
  'uses the %s block inset without overriding container inline padding',
@@ -113,18 +113,18 @@ const dynamicStyles = stylex.create({
113
113
  });
114
114
 
115
115
  /**
116
- * Default block padding per toolbar size. Inline padding stays owned by the
117
- * surrounding Section/container so nested toolbars preserve the same content
118
- * line as adjacent body content.
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: 1,
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-1'],
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-1']})`,
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 block padding and child components through
168
- * SizeContext. Inline padding remains container-owned so nested toolbars share
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 — 4px block inset, fits sm buttons/inputs (28px elements)
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.',
@@ -238,7 +238,7 @@ describe('BaseTypeahead', () => {
238
238
  value={null}
239
239
  onChange={() => {}}
240
240
  debounceMs={0}
241
- emptySearchResultsText="No matching frameworks"
241
+ emptySearchText="No matching frameworks"
242
242
  />,
243
243
  );
244
244
 
@@ -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
- useRenamedProp<ReactNode>({
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',