Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/tokenizer-menu.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@astryxdesign/core': patch
---

[feat] Tokenizer forwards BaseTypeahead's `menu` and `onOpenChange` in its inline presentation, and its handle gains `open()` and `close()`. A token whose own button calls `open()` with a dialog-role menu opens the dialog, and Escape returns focus to that token: focus that a press puts on a token's control, and the menu's focus return, stay there instead of moving to the input. The sheet presentation keeps its own sheet.
Comment thread
astracat-bot[bot] marked this conversation as resolved.
Resolved

@cixzhang
28 changes: 16 additions & 12 deletions docs/specs/components/Tokenizer/Tokenizer.spec.md

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions packages/core/api-report.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 34 additions & 6 deletions packages/core/src/Tokenizer/Tokenizer.doc.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,19 @@ export const docs = {
name: 'handleRef',
type: 'React.Ref<TokenizerHandle>',
description:
'Imperative handle exposing focusInput(), focusFirstToken(), focusLastToken(), clearInput(), and selectAll().',
'Imperative handle exposing focus(), blur(), open() and close(). open() and close() drive the inline menu; the sheet presentation ignores them.',
},
{
name: 'menu',
type: 'BaseTypeaheadMenu',
description:
"What the menu shows. role 'listbox' (default) is the result list; its optional render({listbox, query, isBusy, isEmptySearch, close}) places information such as a header or a hint around it, and anything a person acts on stays a result so the arrow keys reach it. role 'dialog' shows render({query, close}) in a dialog named by label that takes focus; Escape, an outside press or close() returns focus to where it was when the menu opened. Astryx owns the layer either way. Switching from listbox to dialog keeps the menu open: call handleRef.open() from onChange. The sheet presentation keeps its own sheet and ignores it.",
},
{
name: 'onOpenChange',
type: '(isOpen: boolean) => void',
description:
'Called when the inline menu opens or closes. It reports changes only; drive the menu with handleRef. Not called by the sheet presentation.',
},
{
name: 'width',
Expand Down Expand Up @@ -296,7 +308,7 @@ export const docs = {
name: 'presentation',
type: "'popover' | 'bottom-sheet' | 'adaptive'",
description:
'How the field presents its choices. adaptive (the default) uses the bottom sheet on compact touch screens and the popover otherwise, resolved the same way Selector resolves it; the server render and the hydrating render are the popover. popover is the inline combobox with its anchored suggestion menu on every device. bottom-sheet replaces it on every device with a field showing the selected tokens, whose trigger opens one tall modal sheet: a search row on top, full-width checkbox rows that keep their place while the sheet is open, a Create row with hasCreate, and a footer pinned in every state with a confirmed Clear all and Save; the sheet edits a draft, so onChange fires once, when Save commits it, and a swipe, scrim or Escape close of a changed draft asks before discarding. In the sheet presentation the field box holds the trigger, the tokens and endContent, and keeps the box, theming target and focus ring of the inline field; the trigger is the control: ref, id, aria-* and data-* reach it, and ref receives the trigger button there, so type it Ref<HTMLInputElement | HTMLButtonElement>, which the component accepts (TokenizerProps keeps its released input ref type). endContent sits at the inline end of the field and renderToken renders its tokens, as in the inline field; their own controls act without opening the sheet. renderItem, hasClear (the sheet has its own Clear all), hasEntriesOnFocus, menuWidth, tokenOverflowBehavior (the field wraps its tokens) and hasAutoFocus have no surface in the sheet, and the field warns once in development when they are passed. Pass presentation="popover" to keep the inline field and those props on every device.',
'How the field presents its choices. adaptive (the default) uses the bottom sheet on compact touch screens and the popover otherwise, resolved the same way Selector resolves it; the server render and the hydrating render are the popover. popover is the inline combobox with its anchored suggestion menu on every device. bottom-sheet replaces it on every device with a field showing the selected tokens, whose trigger opens one tall modal sheet: a search row on top, full-width checkbox rows that keep their place while the sheet is open, a Create row with hasCreate, and a footer pinned in every state with a confirmed Clear all and Save; the sheet edits a draft, so onChange fires once, when Save commits it, and a swipe, scrim or Escape close of a changed draft asks before discarding. In the sheet presentation the field box holds the trigger, the tokens and endContent, and keeps the box, theming target and focus ring of the inline field; the trigger is the control: ref, id, aria-* and data-* reach it, and ref receives the trigger button there, so type it Ref<HTMLInputElement | HTMLButtonElement>, which the component accepts (TokenizerProps keeps its released input ref type). endContent sits at the inline end of the field and renderToken renders its tokens, as in the inline field; their own controls act without opening the sheet. renderItem, hasClear (the sheet has its own Clear all), hasEntriesOnFocus, menuWidth, menu, onOpenChange, tokenOverflowBehavior (the field wraps its tokens) and hasAutoFocus have no surface in the sheet, and the field warns once in development when they are passed. Pass presentation="popover" to keep the inline field and those props on every device.',
default: "'adaptive'",
},
],
Expand Down Expand Up @@ -604,7 +616,20 @@ export const docsZh = {
{
name: 'handleRef',
type: 'React.Ref<TokenizerHandle>',
description: '用于 focus() 和 blur() 控制的命令式句柄。',
description:
'命令式句柄,提供 focus()、blur()、open() 和 close()。open() 和 close() 控制内联菜单;底部面板呈现会忽略它们。',
},
{
name: 'menu',
type: 'BaseTypeaheadMenu',
description:
"菜单显示的内容。role 为 'listbox'(默认)时显示结果列表;可选的 render({listbox, query, isBusy, isEmptySearch, close}) 可在列表周围放置标题或提示等信息,用户可操作的内容仍需作为结果放在列表中,以便方向键可达。role 为 'dialog' 时在以 label 命名的对话框中显示 render({query, close}),并将焦点移入;按 Escape、点击外部或调用 close() 后焦点回到菜单打开时的位置。两种情况下图层都由 Astryx 管理。从 listbox 切换到 dialog 时菜单保持打开:在 onChange 中调用 handleRef.open()。底部面板呈现保留自己的面板并忽略此属性。",
},
{
name: 'onOpenChange',
type: '(isOpen: boolean) => void',
description:
'内联菜单打开或关闭时调用。它只报告变化;请用 handleRef 控制菜单。底部面板呈现不会调用它。',
},
{
name: 'containerXstyle',
Expand Down Expand Up @@ -642,7 +667,7 @@ export const docsZh = {
name: 'presentation',
type: "'popover' | 'bottom-sheet' | 'adaptive'",
description:
'字段如何呈现选项。adaptive(默认)在紧凑触摸屏上使用底部面板,其他情况使用 popover,解析方式与 Selector 相同;服务端渲染和水合期间的渲染均为 popover。popover 在所有设备上为内联组合框及其锚定建议菜单。bottom-sheet 在所有设备上改为一个显示已选标记的字段,点按其中的触发按钮后打开一个高的模态底部面板——顶部为搜索行,下方为整行复选框行(面板打开期间位置保持不变),hasCreate 时有「创建」行,底部固定显示需确认的「全部清除」和「保存」;面板编辑的是草稿,onChange 仅在「保存」提交时触发一次,草稿有改动时滑动、点击遮罩或按 Escape 关闭会先询问是否放弃。在底部面板呈现中,字段框包含触发按钮、标记和 endContent,并保留内联字段的外框、主题目标和焦点环;触发按钮即为控件:ref、id、aria-* 和 data-* 都作用于它,ref 在该呈现中收到的是触发按钮,因此请将其类型声明为组件所接受的 Ref<HTMLInputElement | HTMLButtonElement>(TokenizerProps 保留已发布的输入框 ref 类型)。endContent 位于字段的行内末端,renderToken 渲染字段中的标记,与内联字段相同;其中的控件可直接操作,不会打开底部面板。renderItem、hasClear(底部面板自带“全部清除”)、hasEntriesOnFocus、menuWidth、tokenOverflowBehavior(字段会换行显示标记)和 hasAutoFocus 在底部面板中没有作用对象,传入时开发模式下会警告一次。传入 presentation="popover" 可在所有设备上保留内联字段和这些属性。',
'字段如何呈现选项。adaptive(默认)在紧凑触摸屏上使用底部面板,其他情况使用 popover,解析方式与 Selector 相同;服务端渲染和水合期间的渲染均为 popover。popover 在所有设备上为内联组合框及其锚定建议菜单。bottom-sheet 在所有设备上改为一个显示已选标记的字段,点按其中的触发按钮后打开一个高的模态底部面板——顶部为搜索行,下方为整行复选框行(面板打开期间位置保持不变),hasCreate 时有「创建」行,底部固定显示需确认的「全部清除」和「保存」;面板编辑的是草稿,onChange 仅在「保存」提交时触发一次,草稿有改动时滑动、点击遮罩或按 Escape 关闭会先询问是否放弃。在底部面板呈现中,字段框包含触发按钮、标记和 endContent,并保留内联字段的外框、主题目标和焦点环;触发按钮即为控件:ref、id、aria-* 和 data-* 都作用于它,ref 在该呈现中收到的是触发按钮,因此请将其类型声明为组件所接受的 Ref<HTMLInputElement | HTMLButtonElement>(TokenizerProps 保留已发布的输入框 ref 类型)。endContent 位于字段的行内末端,renderToken 渲染字段中的标记,与内联字段相同;其中的控件可直接操作,不会打开底部面板。renderItem、hasClear(底部面板自带“全部清除”)、hasEntriesOnFocus、menuWidth、menu、onOpenChange、tokenOverflowBehavior(字段会换行显示标记)和 hasAutoFocus 在底部面板中没有作用对象,传入时开发模式下会警告一次。传入 presentation="popover" 可在所有设备上保留内联字段和这些属性。',
default: "'adaptive'",
},
],
Expand Down Expand Up @@ -843,8 +868,11 @@ export const docsDense = {
startIcon:
'Icon at input start, before tokens. Icon name, SVG component, or ReactNode.',
endContent: 'Content at input row end. For buttons, counts, controls.',
handleRef: 'Imperative handle for focus() and blur() control.',
handleRef:
'Handle: focus(), blur(), open(), close() (open/close inline only).',
menu: "Menu content: {role:'listbox', render?({listbox,query,isBusy,isEmptySearch,close})} (info around the list; actions stay results) | {role:'dialog', label, render({query,close})} (takes focus; close returns focus to opener, else input). Astryx owns the layer. open() from onChange keeps it open across a role switch. Sheet ignores it (dev warning).",
onOpenChange: 'Inline menu open/close report. Not called by the sheet.',
presentation:
"'adaptive' (default: sheet on compact touch, else popover; SSR/hydration render popover) | 'popover' (inline combobox, every device) | 'bottom-sheet' (field box with trigger + one searchable modal sheet of checkbox rows editing a draft that Save commits; onChange fires once, on Save). Sheet: field box keeps the inline box, theming target and focus ring; ref reaches the trigger button; endContent (field end) and renderToken (field tokens) work as inline; renderItem/hasClear/hasEntriesOnFocus/menuWidth/tokenOverflowBehavior/hasAutoFocus inert (dev warning). presentation=\"popover\" keeps them on every device.",
"'adaptive' (default: sheet on compact touch, else popover; SSR/hydration render popover) | 'popover' (inline combobox, every device) | 'bottom-sheet' (field box with trigger + one searchable modal sheet of checkbox rows editing a draft that Save commits; onChange fires once, on Save). Sheet: field box keeps the inline box, theming target and focus ring; ref reaches the trigger button; endContent (field end) and renderToken (field tokens) work as inline; renderItem/hasClear/hasEntriesOnFocus/menuWidth/menu/onOpenChange/tokenOverflowBehavior/hasAutoFocus inert (dev warning). presentation=\"popover\" keeps them on every device.",
},
};
Loading
Loading