Skip to content

Commit

Permalink
feat(combobox) add closeOnSelect and overall combobox improvements (#833
Browse files Browse the repository at this point in the history
)

Co-authored-by: Segun Adebayo <[email protected]>
  • Loading branch information
srflp and segunadebayo authored Sep 2, 2023
1 parent 97840d7 commit 579d4a0
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 31 deletions.
8 changes: 8 additions & 0 deletions .changeset/large-books-own.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@zag-js/combobox": patch
"@zag-js/dom-event": patch
"@zag-js/shared": patch
"website": patch
---

Add support for `closeOnSelect`
41 changes: 32 additions & 9 deletions .xstate/combobox.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,20 @@ const fetchMachine = createMachine({
"openOnClick": false,
"autoComplete": false,
"autoComplete": false,
"hasFocusedOption && autoComplete && closeOnSelect": false,
"hasFocusedOption && autoComplete": false,
"hasFocusedOption && closeOnSelect": false,
"hasFocusedOption": false,
"autoHighlight": false,
"autoComplete": false,
"closeOnSelect": false,
"autoComplete && isLastOptionFocused": false,
"autoComplete && isFirstOptionFocused": false,
"selectOnTab": false,
"closeOnSelect": false,
"autoComplete": false,
"autoComplete": false
"autoComplete": false,
"closeOnSelect": false
},
entry: ["setupLiveRegion"],
exit: ["removeLiveRegion"],
Expand Down Expand Up @@ -146,12 +151,18 @@ const fetchMachine = createMachine({
actions: ["focusLastOption", "preventDefault"]
},
ENTER: [{
cond: "hasFocusedOption && autoComplete",
cond: "hasFocusedOption && autoComplete && closeOnSelect",
target: "focused",
actions: ["selectActiveOption", "invokeOnClose"]
}, {
cond: "hasFocusedOption && autoComplete",
actions: "selectActiveOption"
}, {
cond: "hasFocusedOption",
cond: "hasFocusedOption && closeOnSelect",
target: "focused",
actions: ["selectOption", "invokeOnClose"]
}, {
cond: "hasFocusedOption",
actions: "selectOption"
}],
CHANGE: [{
Expand Down Expand Up @@ -180,10 +191,13 @@ const fetchMachine = createMachine({
target: "focused",
actions: "invokeOnClose"
},
CLICK_OPTION: {
CLICK_OPTION: [{
cond: "closeOnSelect",
target: "focused",
actions: ["selectOption", "invokeOnClose"]
}
}, {
actions: ["selectOption"]
}]
}
},
interacting: {
Expand Down Expand Up @@ -221,10 +235,13 @@ const fetchMachine = createMachine({
target: "idle",
actions: ["selectOption", "invokeOnClose"]
},
ENTER: {
ENTER: [{
cond: "closeOnSelect",
target: "focused",
actions: ["selectOption", "invokeOnClose"]
},
}, {
actions: ["selectOption"]
}],
CHANGE: [{
cond: "autoComplete",
target: "suggesting",
Expand All @@ -239,10 +256,13 @@ const fetchMachine = createMachine({
}, {
actions: ["setActiveOption", "setNavigationData"]
}],
CLICK_OPTION: {
CLICK_OPTION: [{
cond: "closeOnSelect",
target: "focused",
actions: ["selectOption", "invokeOnClose"]
},
}, {
actions: ["selectOption"]
}],
ESCAPE: {
target: "focused",
actions: "invokeOnClose"
Expand Down Expand Up @@ -271,9 +291,12 @@ const fetchMachine = createMachine({
"openOnClick": ctx => ctx["openOnClick"],
"isCustomValue && !allowCustomValue": ctx => ctx["isCustomValue && !allowCustomValue"],
"autoComplete": ctx => ctx["autoComplete"],
"hasFocusedOption && autoComplete && closeOnSelect": ctx => ctx["hasFocusedOption && autoComplete && closeOnSelect"],
"hasFocusedOption && autoComplete": ctx => ctx["hasFocusedOption && autoComplete"],
"hasFocusedOption && closeOnSelect": ctx => ctx["hasFocusedOption && closeOnSelect"],
"hasFocusedOption": ctx => ctx["hasFocusedOption"],
"autoHighlight": ctx => ctx["autoHighlight"],
"closeOnSelect": ctx => ctx["closeOnSelect"],
"autoComplete && isLastOptionFocused": ctx => ctx["autoComplete && isLastOptionFocused"],
"autoComplete && isFirstOptionFocused": ctx => ctx["autoComplete && isFirstOptionFocused"],
"selectOnTab": ctx => ctx["selectOnTab"]
Expand Down
8 changes: 4 additions & 4 deletions packages/machines/combobox/src/combobox.connect.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getEventKey, getNativeEvent, isLeftClick, type EventKeyMap } from "@zag-js/dom-event"
import { getEventKey, getNativeEvent, isLeftClick, type EventKeyMap, isContextMenuEvent } from "@zag-js/dom-event"
import { ariaAttr, dataAttr } from "@zag-js/dom-query"
import { getPlacementStyles } from "@zag-js/popper"
import type { NormalizeProps, PropTypes } from "@zag-js/types"
Expand Down Expand Up @@ -278,12 +278,12 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
if (optionState.isDisabled) return
send({ type: "POINTEROVER_OPTION", id, value, label })
},
onPointerUp() {
if (optionState.isDisabled) return
onPointerUp(event) {
if (optionState.isDisabled || isContextMenuEvent(event)) return
send({ type: "CLICK_OPTION", src: "pointerup", id, value, label })
},
onAuxClick(event) {
if (optionState.isDisabled) return
if (optionState.isDisabled || isContextMenuEvent(event)) return
event.preventDefault()
send({ type: "CLICK_OPTION", src: "auxclick", id, value, label })
},
Expand Down
56 changes: 42 additions & 14 deletions packages/machines/combobox/src/combobox.machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export function machine(userContext: UserDefinedContext) {
isCustomValue: (data) => data.inputValue !== data.previousValue,
inputBehavior: "none",
selectionBehavior: "set",
closeOnSelect: true,
...ctx,
positioning: {
placement: "bottom",
Expand Down Expand Up @@ -201,13 +202,21 @@ export function machine(userContext: UserDefinedContext) {
},
ENTER: [
{
guard: and("hasFocusedOption", "autoComplete"),
guard: and("hasFocusedOption", "autoComplete", "closeOnSelect"),
target: "focused",
actions: ["selectActiveOption", "invokeOnClose"],
},
{
guard: and("hasFocusedOption", "autoComplete"),
actions: "selectActiveOption",
},
{
guard: "hasFocusedOption",
guard: and("hasFocusedOption", "closeOnSelect"),
target: "focused",
actions: ["selectOption", "invokeOnClose"],
},
{
guard: "hasFocusedOption",
actions: "selectOption",
},
],
Expand Down Expand Up @@ -243,10 +252,16 @@ export function machine(userContext: UserDefinedContext) {
target: "focused",
actions: "invokeOnClose",
},
CLICK_OPTION: {
target: "focused",
actions: ["selectOption", "invokeOnClose"],
},
CLICK_OPTION: [
{
guard: "closeOnSelect",
target: "focused",
actions: ["selectOption", "invokeOnClose"],
},
{
actions: ["selectOption"],
},
],
},
},

Expand Down Expand Up @@ -289,10 +304,16 @@ export function machine(userContext: UserDefinedContext) {
target: "idle",
actions: ["selectOption", "invokeOnClose"],
},
ENTER: {
target: "focused",
actions: ["selectOption", "invokeOnClose"],
},
ENTER: [
{
guard: "closeOnSelect",
target: "focused",
actions: ["selectOption", "invokeOnClose"],
},
{
actions: ["selectOption"],
},
],
CHANGE: [
{
guard: "autoComplete",
Expand All @@ -313,10 +334,16 @@ export function machine(userContext: UserDefinedContext) {
actions: ["setActiveOption", "setNavigationData"],
},
],
CLICK_OPTION: {
target: "focused",
actions: ["selectOption", "invokeOnClose"],
},
CLICK_OPTION: [
{
guard: "closeOnSelect",
target: "focused",
actions: ["selectOption", "invokeOnClose"],
},
{
actions: ["selectOption"],
},
],
ESCAPE: {
target: "focused",
actions: "invokeOnClose",
Expand Down Expand Up @@ -349,6 +376,7 @@ export function machine(userContext: UserDefinedContext) {
allowCustomValue: (ctx) => !!ctx.allowCustomValue,
hasFocusedOption: (ctx) => !!ctx.focusedId,
selectOnTab: (ctx) => !!ctx.selectOnTab,
closeOnSelect: (ctx) => !!ctx.closeOnSelect,
},

activities: {
Expand Down
4 changes: 4 additions & 0 deletions packages/machines/combobox/src/combobox.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ type PublicContext = DirectionProperty &
* The behavior of the combobox when an option is selected
*/
selectionBehavior?: "clear" | "set"
/**
* Whether the select should close after an option is selected
*/
closeOnSelect?: boolean
/**
* Whether to select the focused option when the `Tab` key is pressed
*/
Expand Down
5 changes: 1 addition & 4 deletions packages/utilities/dom-event/src/assertion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,10 @@ export function isVirtualClick(e: MouseEvent | PointerEvent): boolean {
export const isLeftClick = (e: Pick<MouseEvent, "button">) => e.button === 0

export const isContextMenuEvent = (e: Pick<MouseEvent, "button" | "ctrlKey" | "metaKey">) => {
return e.button === 2 || (isCtrlKey(e) && e.button === 0)
return e.button === 2 || (isMac() && e.ctrlKey && e.button === 0)
}

export const isModifiedEvent = (e: Pick<KeyboardEvent, "ctrlKey" | "metaKey" | "altKey">) =>
e.ctrlKey || e.altKey || e.metaKey

const isMac = () => /Mac|iPod|iPhone|iPad/.test(window.navigator.platform)

export const isCtrlKey = (e: Pick<KeyboardEvent, "ctrlKey" | "metaKey">) =>
isMac() ? e.metaKey && !e.ctrlKey : e.ctrlKey && !e.metaKey
1 change: 1 addition & 0 deletions shared/src/controls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export const comboboxControls = defineControls({
loop: { type: "boolean", defaultValue: true },
openOnClick: { type: "boolean", defaultValue: false },
blurOnSelect: { type: "boolean", defaultValue: false },
closeOnSelect: { type: "boolean", defaultValue: true },
selectOnTab: { type: "boolean", defaultValue: true },
})

Expand Down
15 changes: 15 additions & 0 deletions website/data/components/combobox.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,21 @@ return {
}
```

## Close on select

This behaviour ensures that the menu is closed when an option is selected and is
`true` by default. It's only concerned with when an option is selected with
pointer or enter key. To disable the behaviour, set the `closeOnSelect` property
in the machine's context to `false`.

```jsx {3}
const [state, send] = useMachine(
combobox.machine({
closeOnSelect: false,
}),
)
```

## Making the combobox readonly

To make a combobox readonly, set the context's `readOnly` property to `true`
Expand Down

4 comments on commit 579d4a0

@vercel
Copy link

@vercel vercel bot commented on 579d4a0 Sep 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vercel
Copy link

@vercel vercel bot commented on 579d4a0 Sep 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

zag-nextjs – ./examples/next-ts

zag-nextjs-git-main-chakra-ui.vercel.app
zag-nextjs-chakra-ui.vercel.app
zag-two.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 579d4a0 Sep 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

zag-vue – ./examples/vue-ts

zag-vue-chakra-ui.vercel.app
zag-vue.vercel.app
zag-vue-git-main-chakra-ui.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 579d4a0 Sep 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

zag-solid – ./examples/solid-ts

zag-solid-git-main-chakra-ui.vercel.app
zag-solid-chakra-ui.vercel.app
zag-solid.vercel.app

Please sign in to comment.