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
5 changes: 5 additions & 0 deletions .changeset/fix-combobox-items-on-value-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@zag-js/combobox": patch
---

Fix `onValueChange` returning empty `items` array when using controlled value
15 changes: 15 additions & 0 deletions e2e/combobox.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,21 @@ test.describe("combobox", () => {
await I.seeInputHasValue("")
})

test("[callback] onValueChange should include selected item in items", async () => {
await I.clickTrigger()
await I.clickItem("Zambia")
await I.seeOnValueChangeItems("Zambia")
})

test("[callback] onValueChange items should be empty after clearing", async () => {
await I.clickTrigger()
await I.clickItem("Zambia")
await I.seeOnValueChangeItems("Zambia")
await I.clickTrigger()
await I.clickClearTrigger()
await I.seeOnValueChangeItemsIsEmpty()
})

test("[no value] enter behavior for custom values", async () => {
await I.controls.select("inputBehavior", "none")
await I.type("foo")
Expand Down
12 changes: 12 additions & 0 deletions e2e/models/combobox.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,16 @@ export class ComboboxModel extends Model {
dontSeeValueText = async () => {
await expect(this.valueText).toHaveText("")
}

get onValueChangeItems() {
return this.page.locator("[data-testid=on-value-change-items]")
}

seeOnValueChangeItems = async (text: string) => {
await expect(this.onValueChangeItems).toContainText(text)
}

seeOnValueChangeItemsIsEmpty = async () => {
await expect(this.onValueChangeItems).toHaveText("")
}
}
5 changes: 5 additions & 0 deletions examples/next-ts/pages/combobox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default function Page() {
const controls = useControls(comboboxControls)

const [options, setOptions] = useState(comboboxData)
const [selectedItems, setSelectedItems] = useState<Item[]>([])

const collection = combobox.collection({
items: options,
Expand All @@ -34,6 +35,9 @@ export default function Page() {
const filtered = matchSorter(comboboxData, inputValue, { keys: ["label"] })
setOptions(filtered.length > 0 ? filtered : comboboxData)
},
onValueChange({ items }) {
setSelectedItems(items as Item[])
},
...controls.context,
})

Expand All @@ -47,6 +51,7 @@ export default function Page() {
<button data-testid="clear-value-button" onClick={() => api.clearValue()}>
Clear Value
</button>
<pre data-testid="on-value-change-items">{selectedItems.map((item) => item.label).join(", ")}</pre>
<br />
<div {...api.getRootProps()}>
<label {...api.getLabelProps()}>Select country</label>
Expand Down
13 changes: 6 additions & 7 deletions packages/machines/combobox/src/combobox.machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,14 @@ export const machine = createMachine({
const prevSelectedItems = context.get("selectedItems")
const collection = prop("collection")

// When controlled, use prop value for selectedItems so they stay in sync when controller ignores selection
const effectiveValue = prop("value") || value
const findItems = (vals: string[]) =>
vals.map((v) => prevSelectedItems.find((item) => collection.getItemValue(item) === v) || collection.find(v))

const nextItems = effectiveValue.map((v) => {
const item = prevSelectedItems.find((item) => collection.getItemValue(item) === v)
return item || collection.find(v)
})
const nextItems = findItems(value)

context.set("selectedItems", nextItems)
// When controlled, use prop value for selectedItems so they stay in sync when controller ignores selection
const effectiveValue = prop("value") || value
context.set("selectedItems", findItems(effectiveValue))

prop("onValueChange")?.({ value, items: nextItems })
},
Expand Down
1 change: 1 addition & 0 deletions website/components/code-area.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export function CodeArea(props: CodeAreaProps) {
margin: "0",
padding: "40px 24px !important",
height: "full",
borderWidth: "0",
},
}}
>
Expand Down
2 changes: 2 additions & 0 deletions website/components/multi-framework.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,11 @@ export function MultiframeworkTabs() {
css={{
"& #playground": {
marginY: "0",
height: "full",
},
"& [data-part=root]": {
transform: "scale(1.5) translateY(40px)",
marginTop: "-120px",
},
}}
>
Expand Down
3 changes: 2 additions & 1 deletion website/data/snippets/react/number-input/usage.mdx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
```jsx
import * as numberInput from "@zag-js/number-input"
import { useMachine, normalizeProps } from "@zag-js/react"
import { useId } from "react"

export function NumberInput() {
const service = useMachine(numberInput.machine, { id: "1" })
const service = useMachine(numberInput.machine, { id: useId() })

const api = numberInput.connect(service, normalizeProps)

Expand Down
4 changes: 2 additions & 2 deletions website/data/snippets/vue/number-input/usage.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
<script setup>
import * as numberInput from "@zag-js/number-input"
import { normalizeProps, useMachine } from "@zag-js/vue"
import { computed } from "vue"
import { computed, useId } from "vue"
const service = useMachine(numberInput.machine, { id: "1" })
const service = useMachine(numberInput.machine, { id: useId() })
const api = computed(() => numberInput.connect(service, normalizeProps))
</script>

Expand Down
2 changes: 1 addition & 1 deletion website/panda.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ export default defineConfig({
block: {
value: {
_light: "{colors.gray.50}",
_dark: "{colors.gray.800/20}",
_dark: "{colors.gray.800}",
},
},
inline: {
Expand Down
Loading