diff --git a/.changeset/shaggy-facts-serve.md b/.changeset/shaggy-facts-serve.md new file mode 100644 index 0000000000..9cbcacc3dd --- /dev/null +++ b/.changeset/shaggy-facts-serve.md @@ -0,0 +1,5 @@ +--- +"@zag-js/menu": patch +--- + +Delay prop normalization in menu getTriggerItemProps until after prop merging diff --git a/packages/machines/menu/src/menu.connect.ts b/packages/machines/menu/src/menu.connect.ts index 806a9d1193..07eec52841 100644 --- a/packages/machines/menu/src/menu.connect.ts +++ b/packages/machines/menu/src/menu.connect.ts @@ -1,5 +1,6 @@ import type { Service } from "@zag-js/core" import { mergeProps } from "@zag-js/core" +import { createNormalizer } from "@zag-js/types" import { ariaAttr, dataAttr, @@ -22,6 +23,9 @@ import { parts } from "./menu.anatomy" import * as dom from "./menu.dom" import type { ItemProps, ItemState, MenuApi, MenuSchema, OptionItemProps, OptionItemState } from "./menu.types" +// We need to avoid mixing framework-agnostic logic with framework-specific prop handling +const identityProps = createNormalizer((v) => v) + export function connect(service: Service, normalize: NormalizeProps): MenuApi { const { context, send, state, computed, prop, scope } = service @@ -61,11 +65,12 @@ export function connect(service: Service, norma } } - function getItemProps(props: ItemProps) { + function getItemProps(props: ItemProps, normalized = true) { const { closeOnSelect, valueText, value } = props const itemState = getItemState(props) const id = dom.getItemId(scope, value) - return normalize.element({ + + const itemProps = identityProps.element({ ...parts.item.attrs, id, role: "menuitem", @@ -110,6 +115,8 @@ export function connect(service: Service, norma send({ type: "ITEM_CLICK", target, id, closeOnSelect }) }, }) + + return normalized ? normalize.element(itemProps) : itemProps } return { @@ -177,12 +184,13 @@ export function connect(service: Service, norma }, getTriggerItemProps(childApi) { - const triggerProps = childApi.getTriggerProps() - return mergeProps(getItemProps({ value: triggerProps.id }), triggerProps) as T["element"] + const triggerProps = childApi.getTriggerProps(false) + const itemProps = getItemProps({ value: triggerProps.id }, false) + return normalize.element(mergeProps(itemProps, triggerProps)) }, - getTriggerProps() { - return normalize.button({ + getTriggerProps(normalized = true) { + const triggerProps = identityProps.button({ ...(isSubmenu ? parts.triggerItem.attrs : parts.trigger.attrs), "data-placement": context.get("currentPlacement"), type: "button", @@ -256,6 +264,8 @@ export function connect(service: Service, norma } }, }) + + return normalized ? normalize.button(triggerProps) : triggerProps }, getIndicatorProps() { diff --git a/packages/machines/menu/src/menu.types.ts b/packages/machines/menu/src/menu.types.ts index 2116069db0..e4915777c4 100644 --- a/packages/machines/menu/src/menu.types.ts +++ b/packages/machines/menu/src/menu.types.ts @@ -176,8 +176,8 @@ export type ParentMenuService = Pick Record - getTriggerProps: () => Record + getItemProps: (props: ItemProps, normalized?: boolean) => Record + getTriggerProps: (normalized?: boolean) => Record } export interface ItemProps { @@ -317,14 +317,14 @@ export interface MenuApi { getContextTriggerProps: () => T["element"] getTriggerItemProps: (childApi: A) => T["element"] - getTriggerProps: () => T["button"] + getTriggerProps: (normalized?: boolean) => T["button"] getIndicatorProps: () => T["element"] getPositionerProps: () => T["element"] getArrowProps: () => T["element"] getArrowTipProps: () => T["element"] getContentProps: () => T["element"] getSeparatorProps: () => T["element"] - getItemProps: (options: ItemProps) => T["element"] + getItemProps: (options: ItemProps, normalized?: boolean) => T["element"] getOptionItemProps: (option: OptionItemProps) => T["element"] getItemIndicatorProps: (option: ItemBaseProps) => T["element"] getItemTextProps: (option: ItemBaseProps) => T["element"] diff --git a/packages/machines/tour/src/tour.connect.ts b/packages/machines/tour/src/tour.connect.ts index c1aef3e732..6086ec2199 100644 --- a/packages/machines/tour/src/tour.connect.ts +++ b/packages/machines/tour/src/tour.connect.ts @@ -71,6 +71,7 @@ export function connect(service: TourService, normalize: No send({ type: "STEPS.SET", value: next, src: "removeStep" }) }, updateStep(id, stepOverrides) { + // Should mergeProps here merge the effect functions? It does not, it takes the last it finds. const next = steps.map((step) => (step.id === id ? mergeProps(step, stepOverrides) : step)) send({ type: "STEPS.SET", value: next, src: "updateStep" }) },