diff --git a/packages/machines/accordion/src/accordion.connect.ts b/packages/machines/accordion/src/accordion.connect.ts index 090daf25e7..cbf8d48a00 100644 --- a/packages/machines/accordion/src/accordion.connect.ts +++ b/packages/machines/accordion/src/accordion.connect.ts @@ -35,7 +35,7 @@ export function connect(state: State, send const { isOpen, isFocused } = api.getItemState(props) return normalize.element({ "data-part": "item", - id: dom.getGroupId(state.context, props.value), + id: dom.getItemId(state.context, props.value), "data-expanded": dataAttr(isOpen), "data-focus": dataAttr(isFocused), }) diff --git a/packages/machines/accordion/src/accordion.dom.ts b/packages/machines/accordion/src/accordion.dom.ts index e55e1fa07f..af94a31729 100644 --- a/packages/machines/accordion/src/accordion.dom.ts +++ b/packages/machines/accordion/src/accordion.dom.ts @@ -5,10 +5,10 @@ import type { MachineContext as Ctx } from "./accordion.types" export const dom = { getDoc: (ctx: Ctx) => ctx.doc ?? document, - getRootId: (ctx: Ctx) => `accordion-${ctx.uid}`, - getGroupId: (ctx: Ctx, id: string) => `accordion-${ctx.uid}-item-${id}`, - getContentId: (ctx: Ctx, id: string) => `accordion-${ctx.uid}-content-${id}`, - getTriggerId: (ctx: Ctx, id: string) => `accordion-${ctx.uid}-trigger-${id}`, + getRootId: (ctx: Ctx) => ctx.ids?.root ?? `accordion-${ctx.uid}`, + getItemId: (ctx: Ctx, value: string) => ctx.ids?.item?.(value) ?? `accordion-${ctx.uid}-item-${value}`, + getContentId: (ctx: Ctx, value: string) => ctx.ids?.content?.(value) ?? `accordion-${ctx.uid}-content-${value}`, + getTriggerId: (ctx: Ctx, value: string) => ctx.ids?.trigger?.(value) ?? `accordion-${ctx.uid}-trigger-${value}`, getRootEl: (ctx: Ctx) => dom.getDoc(ctx).getElementById(dom.getRootId(ctx)), getTriggers: (ctx: Ctx) => { diff --git a/packages/machines/accordion/src/accordion.types.ts b/packages/machines/accordion/src/accordion.types.ts index c7be6f67e1..2c2f715174 100644 --- a/packages/machines/accordion/src/accordion.types.ts +++ b/packages/machines/accordion/src/accordion.types.ts @@ -1,7 +1,18 @@ import type { StateMachine as S } from "@zag-js/core" import type { Context } from "@zag-js/types" +type IdMap = Partial<{ + root: string + item(value: string): string + content(value: string): string + trigger(value: string): string +}> + export type MachineContext = Context<{ + /** + * The ids of the elements in the accordion. Useful for composition. + */ + ids?: IdMap /** * Whether multple accordion items can be open at the same time. * @default false diff --git a/packages/machines/combobox/src/combobox.dom.ts b/packages/machines/combobox/src/combobox.dom.ts index b072d4dfef..4460ea0e90 100644 --- a/packages/machines/combobox/src/combobox.dom.ts +++ b/packages/machines/combobox/src/combobox.dom.ts @@ -6,16 +6,16 @@ import type { MachineContext as Ctx } from "./combobox.types" export const dom = { getDoc: (ctx: Ctx) => ctx.doc ?? document, - getRootId: (ctx: Ctx) => `combobox-${ctx.uid}-root`, - getLabelId: (ctx: Ctx) => `combobox-${ctx.uid}-label`, - getControlId: (ctx: Ctx) => `combobox-${ctx.uid}`, - getInputId: (ctx: Ctx) => `combobox-${ctx.uid}-input`, - getListboxId: (ctx: Ctx) => `combobox-${ctx.uid}-listbox`, - getPositionerId: (ctx: Ctx) => `combobox-${ctx.uid}-popover`, - getToggleBtnId: (ctx: Ctx) => `combobox-${ctx.uid}-toggle-btn`, - getClearBtnId: (ctx: Ctx) => `combobox-${ctx.uid}-clear-btn`, - getOptionId: (ctx: Ctx, id: number | string, index?: number) => - [`combobox-${ctx.uid}-option-${id}`, index].filter((v) => v != null).join("-"), + getRootId: (ctx: Ctx) => ctx.ids?.root ?? `combobox-${ctx.uid}-root`, + getLabelId: (ctx: Ctx) => ctx.ids?.label ?? `combobox-${ctx.uid}-label`, + getControlId: (ctx: Ctx) => ctx.ids?.control ?? `combobox-${ctx.uid}`, + getInputId: (ctx: Ctx) => ctx.ids?.input ?? `combobox-${ctx.uid}-input`, + getListboxId: (ctx: Ctx) => ctx.ids?.listbox ?? `combobox-${ctx.uid}-listbox`, + getPositionerId: (ctx: Ctx) => `combobox-${ctx.uid}-popper`, + getToggleBtnId: (ctx: Ctx) => ctx.ids?.toggleBtn ?? `combobox-${ctx.uid}-toggle-btn`, + getClearBtnId: (ctx: Ctx) => ctx.ids?.clearBtn ?? `combobox-${ctx.uid}-clear-btn`, + getOptionId: (ctx: Ctx, id: string, index?: number) => + ctx.ids?.option?.(id, index) ?? [`combobox-${ctx.uid}-option-${id}`, index].filter((v) => v != null).join("-"), getActiveOptionEl: (ctx: Ctx) => (ctx.activeId ? dom.getDoc(ctx).getElementById(ctx.activeId) : null), getListboxEl: (ctx: Ctx) => dom.getDoc(ctx).getElementById(dom.getListboxId(ctx)), diff --git a/packages/machines/combobox/src/combobox.types.ts b/packages/machines/combobox/src/combobox.types.ts index f86ca7b10f..1355925ae2 100644 --- a/packages/machines/combobox/src/combobox.types.ts +++ b/packages/machines/combobox/src/combobox.types.ts @@ -15,7 +15,22 @@ type IntlMessages = { navigationHint?: string } +type IdMap = Partial<{ + root: string + label: string + control: string + input: string + listbox: string + toggleBtn: string + clearBtn: string + option(id: string, index?: number): string +}> + export type MachineContext = Context<{ + /** + * The ids of the elements in the combobox. Useful for composition. + */ + ids?: IdMap /** * The current value of the combobox's input */ diff --git a/packages/machines/dialog/src/dialog.dom.ts b/packages/machines/dialog/src/dialog.dom.ts index 5bd55d6ad7..26c67793ec 100644 --- a/packages/machines/dialog/src/dialog.dom.ts +++ b/packages/machines/dialog/src/dialog.dom.ts @@ -3,13 +3,14 @@ import { MachineContext as Ctx } from "./dialog.types" export const dom = { getDoc: (ctx: Ctx) => ctx.doc ?? document, getWin: (ctx: Ctx) => dom.getDoc(ctx).defaultView ?? window, - getUnderlayId: (ctx: Ctx) => `dialog-underlay-${ctx.uid}`, - getBackdropId: (ctx: Ctx) => `dialog-backdrop-${ctx.uid}`, - getContentId: (ctx: Ctx) => `dialog-content-${ctx.uid}`, - getTriggerId: (ctx: Ctx) => `dialog-trigger-${ctx.uid}`, - getTitleId: (ctx: Ctx) => `dialog-title-${ctx.uid}`, - getDescriptionId: (ctx: Ctx) => `dialog-desc-${ctx.uid}`, - getCloseButtonId: (ctx: Ctx) => `dialog-close-btn-${ctx.uid}`, + + getUnderlayId: (ctx: Ctx) => ctx.ids?.underlay ?? `dialog-underlay-${ctx.uid}`, + getBackdropId: (ctx: Ctx) => ctx.ids?.backdrop ?? `dialog-backdrop-${ctx.uid}`, + getContentId: (ctx: Ctx) => ctx.ids?.content ?? `dialog-content-${ctx.uid}`, + getTriggerId: (ctx: Ctx) => ctx.ids?.trigger ?? `dialog-trigger-${ctx.uid}`, + getTitleId: (ctx: Ctx) => ctx.ids?.title ?? `dialog-title-${ctx.uid}`, + getDescriptionId: (ctx: Ctx) => ctx.ids?.description ?? `dialog-desc-${ctx.uid}`, + getCloseButtonId: (ctx: Ctx) => ctx.ids?.closeBtn ?? `dialog-close-btn-${ctx.uid}`, getContentEl: (ctx: Ctx) => dom.getDoc(ctx).getElementById(dom.getContentId(ctx)) as HTMLElement, getUnderlayEl: (ctx: Ctx) => dom.getDoc(ctx).getElementById(dom.getUnderlayId(ctx)) as HTMLElement, diff --git a/packages/machines/dialog/src/dialog.types.ts b/packages/machines/dialog/src/dialog.types.ts index 0d77d0f347..92bd685670 100644 --- a/packages/machines/dialog/src/dialog.types.ts +++ b/packages/machines/dialog/src/dialog.types.ts @@ -1,6 +1,20 @@ import type { Context, MaybeElement } from "@zag-js/types" +type IdMap = Partial<{ + trigger: string + underlay: string + backdrop: string + content: string + closeBtn: string + title: string + description: string +}> + export type MachineContext = Context<{ + /** + * The ids of the elements in the dialog. Useful for composition. + */ + ids?: IdMap /** * @internal Whether the dialog title is rendered */ diff --git a/packages/machines/editable/src/editable.dom.ts b/packages/machines/editable/src/editable.dom.ts index cab4edc8e7..7f0a0cf2f1 100644 --- a/packages/machines/editable/src/editable.dom.ts +++ b/packages/machines/editable/src/editable.dom.ts @@ -6,15 +6,15 @@ type HTMLInputEl = HTMLInputElement | null export const dom = { getDoc: (ctx: Ctx) => ctx.doc ?? document, - getRootId: (ctx: Ctx) => `editable-${ctx.uid}`, - getAreaId: (ctx: Ctx) => `editable-${ctx.uid}-area`, - getLabelId: (ctx: Ctx) => `editable-${ctx.uid}-label`, - getPreviewId: (ctx: Ctx) => `editable-${ctx.uid}-preview`, - getInputId: (ctx: Ctx) => `editable-${ctx.uid}-input`, - getControlGroupId: (ctx: Ctx) => `editable-${ctx.uid}-controls`, - getSubmitBtnId: (ctx: Ctx) => `editable-${ctx.uid}-submit-btn`, - getCancelBtnId: (ctx: Ctx) => `editable-${ctx.uid}-cancel-btn`, - getEditBtnId: (ctx: Ctx) => `editable-${ctx.uid}-edit-btn`, + getRootId: (ctx: Ctx) => ctx.ids?.root ?? `editable-${ctx.uid}`, + getAreaId: (ctx: Ctx) => ctx.ids?.area ?? `editable-${ctx.uid}-area`, + getLabelId: (ctx: Ctx) => ctx.ids?.label ?? `editable-${ctx.uid}-label`, + getPreviewId: (ctx: Ctx) => ctx.ids?.preview ?? `editable-${ctx.uid}-preview`, + getInputId: (ctx: Ctx) => ctx.ids?.input ?? `editable-${ctx.uid}-input`, + getControlGroupId: (ctx: Ctx) => ctx.ids?.controlGroup ?? `editable-${ctx.uid}-controls`, + getSubmitBtnId: (ctx: Ctx) => ctx.ids?.submitBtn ?? `editable-${ctx.uid}-submit-btn`, + getCancelBtnId: (ctx: Ctx) => ctx.ids?.cancelBtn ?? `editable-${ctx.uid}-cancel-btn`, + getEditBtnId: (ctx: Ctx) => ctx.ids?.editBtn ?? `editable-${ctx.uid}-edit-btn`, getInputEl: (ctx: Ctx) => dom.getDoc(ctx).getElementById(dom.getInputId(ctx)) as HTMLInputEl, getPreviewEl: (ctx: Ctx) => dom.getDoc(ctx).getElementById(dom.getPreviewId(ctx)) as HTMLInputEl, diff --git a/packages/machines/editable/src/editable.types.ts b/packages/machines/editable/src/editable.types.ts index 04a823a1ad..e2b7667597 100644 --- a/packages/machines/editable/src/editable.types.ts +++ b/packages/machines/editable/src/editable.types.ts @@ -12,7 +12,23 @@ type IntlMessages = { input: string } +type IdMap = Partial<{ + root: string + area: string + label: string + preview: string + input: string + controlGroup: string + submitBtn: string + cancelBtn: string + editBtn: string +}> + export type MachineContext = Context<{ + /** + * The ids of the elements in the editable. Useful for composition. + */ + ids?: IdMap /** * Whether the input's value is invalid. */ diff --git a/packages/machines/menu/src/menu.dom.ts b/packages/machines/menu/src/menu.dom.ts index 9689c23836..17830f79d6 100644 --- a/packages/machines/menu/src/menu.dom.ts +++ b/packages/machines/menu/src/menu.dom.ts @@ -7,13 +7,13 @@ type HTMLEl = HTMLElement | null export const dom = { getDoc: (ctx: Ctx) => ctx.doc ?? document, - getTriggerId: (ctx: Ctx) => `menu-${ctx.uid}-trigger`, - getContextTriggerId: (ctx: Ctx) => `menu-${ctx.uid}-context-trigger`, - getContentId: (ctx: Ctx) => `menu-${ctx.uid}-menulist`, - getArrowId: (ctx: Ctx) => `popover-${ctx.uid}--arrow`, - getPositionerId: (ctx: Ctx) => `tooltip-${ctx.uid}--popper`, - getGroupId: (ctx: Ctx, id: string) => `menu-${ctx.uid}-group-${id}`, - getLabelId: (ctx: Ctx, id: string) => `menu-${ctx.uid}-label-${id}`, + getTriggerId: (ctx: Ctx) => ctx.ids?.trigger ?? `menu-${ctx.uid}-trigger`, + getContextTriggerId: (ctx: Ctx) => ctx.ids?.contextTrigger ?? `menu-${ctx.uid}-context-trigger`, + getContentId: (ctx: Ctx) => ctx.ids?.content ?? `menu-${ctx.uid}-menulist`, + getArrowId: (ctx: Ctx) => `menu-${ctx.uid}--arrow`, + getPositionerId: (ctx: Ctx) => `menu-${ctx.uid}--popper`, + getGroupId: (ctx: Ctx, id: string) => ctx.ids?.group?.(id) ?? `menu-${ctx.uid}-group-${id}`, + getLabelId: (ctx: Ctx, id: string) => ctx.ids?.label?.(id) ?? `menu-${ctx.uid}-label-${id}`, getContentEl: (ctx: Ctx) => dom.getDoc(ctx).getElementById(dom.getContentId(ctx)) as HTMLEl, getPositionerEl: (ctx: Ctx) => dom.getDoc(ctx).getElementById(dom.getPositionerId(ctx)), diff --git a/packages/machines/menu/src/menu.types.ts b/packages/machines/menu/src/menu.types.ts index f3e50986e6..db2ffdb242 100644 --- a/packages/machines/menu/src/menu.types.ts +++ b/packages/machines/menu/src/menu.types.ts @@ -10,7 +10,19 @@ export type MachineState = { tags: "visible" } +type IdMap = Partial<{ + trigger: string + contextTrigger: string + content: string + label(id: string): string + group(id: string): string +}> + export type MachineContext = Context<{ + /** + * The ids of the elements in the menu. Useful for composition. + */ + ids?: IdMap /** * The values of radios and checkboxes in the menu. */ diff --git a/packages/machines/number-input/src/number-input.dom.ts b/packages/machines/number-input/src/number-input.dom.ts index 35fee4107a..3c9de170bb 100644 --- a/packages/machines/number-input/src/number-input.dom.ts +++ b/packages/machines/number-input/src/number-input.dom.ts @@ -9,13 +9,13 @@ export const dom = { getDoc: (ctx: Ctx) => ctx.doc ?? document, getWin: (ctx: Ctx) => dom.getDoc(ctx).defaultView ?? window, - getInputId: (ctx: Ctx) => `number-input-${ctx.uid}-input`, - getIncButtonId: (ctx: Ctx) => `number-input-${ctx.uid}-inc-btn`, - getDecButtonId: (ctx: Ctx) => `number-input-${ctx.uid}-dec-btn`, - getScrubberId: (ctx: Ctx) => `number-input-${ctx.uid}-scrubber`, + getInputId: (ctx: Ctx) => ctx.ids?.input ?? `number-input-${ctx.uid}-input`, + getIncButtonId: (ctx: Ctx) => ctx.ids?.incBtn ?? `number-input-${ctx.uid}-inc-btn`, + getDecButtonId: (ctx: Ctx) => ctx.ids?.decBtn ?? `number-input-${ctx.uid}-dec-btn`, + getScrubberId: (ctx: Ctx) => ctx.ids?.scrubber ?? `number-input-${ctx.uid}-scrubber`, getCursorId: (ctx: Ctx) => `number-input-${ctx.uid}-cursor`, - getLabelId: (ctx: Ctx) => `number-input-${ctx.uid}-label`, - getRootId: (ctx: Ctx) => `number-input-${ctx.uid}-root`, + getLabelId: (ctx: Ctx) => ctx.ids?.label ?? `number-input-${ctx.uid}-label`, + getRootId: (ctx: Ctx) => ctx.ids?.root ?? `number-input-${ctx.uid}-root`, getInputEl: (ctx: Ctx) => dom.getDoc(ctx).getElementById(dom.getInputId(ctx)) as InputEl, getIncButtonEl: (ctx: Ctx) => dom.getDoc(ctx).getElementById(dom.getIncButtonId(ctx)) as ButtonEl, diff --git a/packages/machines/number-input/src/number-input.types.ts b/packages/machines/number-input/src/number-input.types.ts index 2e37e8302f..5e3c87c51f 100644 --- a/packages/machines/number-input/src/number-input.types.ts +++ b/packages/machines/number-input/src/number-input.types.ts @@ -6,6 +6,15 @@ type ValidityState = "rangeUnderflow" | "rangeOverflow" type InputSelection = Record<"start" | "end", number | null> +type IdMap = Partial<{ + root: string + label: string + input: string + incBtn: string + decBtn: string + scrubber: string +}> + type IntlMessages = { /** * Function that returns the human-readable value. @@ -23,6 +32,10 @@ type IntlMessages = { } export type MachineContext = Context<{ + /** + * The ids of the elements in the number input. Useful for composition. + */ + ids?: IdMap /** * The name attribute of the number input. Useful for form submission. */ diff --git a/packages/machines/pin-input/src/pin-input.connect.ts b/packages/machines/pin-input/src/pin-input.connect.ts index a9d98105bf..cbf54a17c8 100644 --- a/packages/machines/pin-input/src/pin-input.connect.ts +++ b/packages/machines/pin-input/src/pin-input.connect.ts @@ -53,7 +53,7 @@ export function connect( disabled: state.context.disabled, "data-disabled": dataAttr(state.context.disabled), "data-complete": dataAttr(isValueComplete), - id: dom.getInputId(state.context, index), + id: dom.getInputId(state.context, index.toString()), "data-ownedby": dom.getRootId(state.context), "aria-label": messages.inputLabel(index, state.context.valueLength), inputMode: state.context.otp || state.context.type === "numeric" ? "numeric" : "text", diff --git a/packages/machines/pin-input/src/pin-input.dom.ts b/packages/machines/pin-input/src/pin-input.dom.ts index c17653ea5a..bcb4b826a2 100644 --- a/packages/machines/pin-input/src/pin-input.dom.ts +++ b/packages/machines/pin-input/src/pin-input.dom.ts @@ -4,8 +4,8 @@ import { MachineContext as Ctx } from "./pin-input.types" export const dom = { getDoc: (ctx: Ctx) => ctx.doc ?? document, - getRootId: (ctx: Ctx) => `pin-input-${ctx.uid}`, - getInputId: (ctx: Ctx, id: string | number) => `pin-input-${ctx.uid}-${id}`, + getRootId: (ctx: Ctx) => ctx.ids?.root ?? `pin-input-${ctx.uid}`, + getInputId: (ctx: Ctx, id: string) => ctx.ids?.input?.(id) ?? `pin-input-${ctx.uid}-${id}`, getRootEl: (ctx: Ctx) => dom.getDoc(ctx).getElementById(dom.getRootId(ctx)), getElements: (ctx: Ctx) => { diff --git a/packages/machines/pin-input/src/pin-input.types.ts b/packages/machines/pin-input/src/pin-input.types.ts index 652d29c66f..6f0f487eda 100644 --- a/packages/machines/pin-input/src/pin-input.types.ts +++ b/packages/machines/pin-input/src/pin-input.types.ts @@ -4,7 +4,16 @@ type IntlMessages = { inputLabel: (index: number, length: number) => string } +type IdMap = Partial<{ + root: string + input(id: string): string +}> + export type MachineContext = Context<{ + /** + * The ids of the elements in the pin input. Useful for composition. + */ + ids?: IdMap /** * Whether the inputs are disabled */ diff --git a/packages/machines/popover/src/popover.dom.ts b/packages/machines/popover/src/popover.dom.ts index 1c2358332c..f2f9c5eefa 100644 --- a/packages/machines/popover/src/popover.dom.ts +++ b/packages/machines/popover/src/popover.dom.ts @@ -6,14 +6,14 @@ export const dom = { getDoc: (ctx: Ctx) => ctx.doc ?? document, getActiveEl: (ctx: Ctx) => dom.getDoc(ctx).activeElement, - getAnchorId: (ctx: Ctx) => `popover-${ctx.uid}-anchor`, - getTriggerId: (ctx: Ctx) => `popover-${ctx.uid}-trigger`, - getContentId: (ctx: Ctx) => `popover-${ctx.uid}-content`, + getAnchorId: (ctx: Ctx) => ctx.ids?.anchor ?? `popover-${ctx.uid}-anchor`, + getTriggerId: (ctx: Ctx) => ctx.ids?.trigger ?? `popover-${ctx.uid}-trigger`, + getContentId: (ctx: Ctx) => ctx.ids?.content ?? `popover-${ctx.uid}-content`, getPositionerId: (ctx: Ctx) => `popover-${ctx.uid}-popper`, - getTitleId: (ctx: Ctx) => `popover-${ctx.uid}-title`, - getDescriptionId: (ctx: Ctx) => `popover-${ctx.uid}-desc`, - getCloseButtonId: (ctx: Ctx) => `popover-${ctx.uid}-close-button`, getArrowId: (ctx: Ctx) => `popover-${ctx.uid}-arrow`, + getTitleId: (ctx: Ctx) => ctx.ids?.title ?? `popover-${ctx.uid}-title`, + getDescriptionId: (ctx: Ctx) => ctx.ids?.description ?? `popover-${ctx.uid}-desc`, + getCloseButtonId: (ctx: Ctx) => ctx.ids?.closeBtn ?? `popover-${ctx.uid}-close-button`, getAnchorEl: (ctx: Ctx) => dom.getDoc(ctx).getElementById(dom.getAnchorId(ctx)), getTriggerEl: (ctx: Ctx) => dom.getDoc(ctx).getElementById(dom.getTriggerId(ctx)), diff --git a/packages/machines/popover/src/popover.types.ts b/packages/machines/popover/src/popover.types.ts index f7bbc3027f..635f84ded6 100644 --- a/packages/machines/popover/src/popover.types.ts +++ b/packages/machines/popover/src/popover.types.ts @@ -1,7 +1,20 @@ import type { PositioningOptions, Placement } from "@zag-js/popper" import type { Context, MaybeElement } from "@zag-js/types" +type IdMap = Partial<{ + anchor: string + trigger: string + content: string + title: string + description: string + closeBtn: string +}> + export type MachineContext = Context<{ + /** + * The ids of the elements in the popover. Useful for composition. + */ + ids?: IdMap /** * @internal Whether the dialog title is rendered */ @@ -10,6 +23,12 @@ export type MachineContext = Context<{ * @internal Whether the dialog description is rendered */ isDescriptionRendered: boolean + /** + * + * @internal Whether the reference element is rendered to be used as the + * positioning reference + */ + isAnchorRendered: boolean /** * Whether the popover should be modal. When set to `true`: * - interaction with outside elements will be disabled @@ -53,12 +72,6 @@ export type MachineContext = Context<{ * Function invoked when the popover is closed. */ onClose?: () => void - /** - * - * @internal Whether the reference element is rendered to be used as the - * positioning reference - */ - isAnchorRendered: boolean /** * The user provided options used to position the popover content */ diff --git a/packages/machines/range-slider/src/range-slider.dom.ts b/packages/machines/range-slider/src/range-slider.dom.ts index bfd6d3c4fe..33b78def8f 100644 --- a/packages/machines/range-slider/src/range-slider.dom.ts +++ b/packages/machines/range-slider/src/range-slider.dom.ts @@ -57,14 +57,14 @@ export function getRangeStyle(ctx: Ctx): Style { export const dom = { getDoc: (ctx: Ctx) => ctx.doc ?? document, - getRootId: (ctx: Ctx) => `slider-${ctx.uid}`, - getThumbId: (ctx: Ctx, index: number) => `slider-thumb-${ctx.uid}-${index}`, + getRootId: (ctx: Ctx) => ctx.ids?.root ?? `slider-${ctx.uid}`, + getThumbId: (ctx: Ctx, index: number) => ctx.ids?.thumb?.(index) ?? `slider-thumb-${ctx.uid}-${index}`, getInputId: (ctx: Ctx, index: number) => `slider-input-${ctx.uid}-${index}`, - getControlId: (ctx: Ctx) => `slider-${ctx.uid}-root`, - getTrackId: (ctx: Ctx) => `slider-${ctx.uid}-track`, - getRangeId: (ctx: Ctx) => `slider-${ctx.uid}-range`, - getLabelId: (ctx: Ctx) => `slider-${ctx.uid}-label`, - getOutputId: (ctx: Ctx) => `slider-${ctx.uid}-output`, + getControlId: (ctx: Ctx) => ctx.ids?.control ?? `slider-${ctx.uid}-root`, + getTrackId: (ctx: Ctx) => ctx.ids?.track ?? `slider-${ctx.uid}-track`, + getRangeId: (ctx: Ctx) => ctx.ids?.range ?? `slider-${ctx.uid}-range`, + getLabelId: (ctx: Ctx) => ctx.ids?.label ?? `slider-${ctx.uid}-label`, + getOutputId: (ctx: Ctx) => ctx.ids?.output ?? `slider-${ctx.uid}-output`, getMarkerId: (ctx: Ctx, value: number) => `slider-marker-${ctx.uid}-${value}`, getThumbEl: (ctx: Ctx, index: number) => dom.getDoc(ctx).getElementById(dom.getThumbId(ctx, index)), diff --git a/packages/machines/range-slider/src/range-slider.types.ts b/packages/machines/range-slider/src/range-slider.types.ts index 3c82927304..202d6ba1cb 100644 --- a/packages/machines/range-slider/src/range-slider.types.ts +++ b/packages/machines/range-slider/src/range-slider.types.ts @@ -1,7 +1,21 @@ import type { StateMachine as S } from "@zag-js/core" import { Context } from "@zag-js/types" +type IdMap = Partial<{ + root: string + thumb(index: number): string + control: string + track: string + range: string + label: string + output: string +}> + export type MachineContext = Context<{ + /** + * The ids of the elements in the range slider. Useful for composition. + */ + ids: IdMap /** * The aria-label of each slider thumb. Useful for providing an accessible name to the slider */ diff --git a/packages/machines/rating/src/rating.connect.ts b/packages/machines/rating/src/rating.connect.ts index 762772b988..6cfb3aa729 100644 --- a/packages/machines/rating/src/rating.connect.ts +++ b/packages/machines/rating/src/rating.connect.ts @@ -83,7 +83,7 @@ export function connect( return normalize.element({ "data-part": "item", - id: dom.getItemId(state.context, index), + id: dom.getItemId(state.context, index.toString()), role: "radio", tabIndex: isDisabled ? undefined : isChecked ? 0 : -1, "aria-roledescription": "rating", diff --git a/packages/machines/rating/src/rating.dom.ts b/packages/machines/rating/src/rating.dom.ts index 7ec78880e2..a73fa41d3b 100644 --- a/packages/machines/rating/src/rating.dom.ts +++ b/packages/machines/rating/src/rating.dom.ts @@ -4,11 +4,11 @@ import { MachineContext as Ctx } from "./rating.types" export const dom = { getDoc: (ctx: Ctx) => ctx.doc ?? document, - getRootId: (ctx: Ctx) => `rating-${ctx.uid}`, - getLabelId: (ctx: Ctx) => `rating-${ctx.uid}-label`, - getInputId: (ctx: Ctx) => `rating-${ctx.uid}-input`, - getItemGroupId: (ctx: Ctx) => `rating-${ctx.uid}-item-group`, - getItemId: (ctx: Ctx, id: string | number) => `rating-${ctx.uid}-star-${id}`, + getRootId: (ctx: Ctx) => ctx.ids?.root ?? `rating-${ctx.uid}`, + getLabelId: (ctx: Ctx) => ctx.ids?.label ?? `rating-${ctx.uid}-label`, + getInputId: (ctx: Ctx) => ctx.ids?.input ?? `rating-${ctx.uid}-input`, + getItemGroupId: (ctx: Ctx) => ctx.ids?.itemGroup ?? `rating-${ctx.uid}-item-group`, + getItemId: (ctx: Ctx, id: string) => ctx.ids?.item?.(id) ?? `rating-${ctx.uid}-star-${id}`, getItemGroupEl: (ctx: Ctx) => dom.getDoc(ctx).getElementById(dom.getItemGroupId(ctx)), getRadioEl: (ctx: Ctx) => diff --git a/packages/machines/rating/src/rating.types.ts b/packages/machines/rating/src/rating.types.ts index 4b05d94c09..3b9563034c 100644 --- a/packages/machines/rating/src/rating.types.ts +++ b/packages/machines/rating/src/rating.types.ts @@ -7,7 +7,19 @@ type IntlMessages = { ratingValueText(index: number): string } +type IdMap = Partial<{ + root: string + label: string + input: string + itemGroup: string + item(id: string): string +}> + export type MachineContext = Context<{ + /** + * The ids of the elements in the rating. Useful for composition. + */ + ids?: IdMap /** * Specifies the localized strings that identifies the accessibility elements and their states */ diff --git a/packages/machines/slider/src/slider.dom.ts b/packages/machines/slider/src/slider.dom.ts index e421803f0f..99e618478c 100644 --- a/packages/machines/slider/src/slider.dom.ts +++ b/packages/machines/slider/src/slider.dom.ts @@ -93,14 +93,14 @@ function getControlStyle(ctx: Pick): Style { export const dom = { getDoc: (ctx: Ctx) => ctx.doc ?? document, - getRootId: (ctx: Ctx) => `slider-${ctx.uid}`, - getThumbId: (ctx: Ctx) => `slider-${ctx.uid}-thumb`, - getControlId: (ctx: Ctx) => `slider-${ctx.uid}-control`, + getRootId: (ctx: Ctx) => ctx.ids?.root ?? `slider-${ctx.uid}`, + getThumbId: (ctx: Ctx) => ctx.ids?.thumb ?? `slider-${ctx.uid}-thumb`, + getControlId: (ctx: Ctx) => ctx.ids?.control ?? `slider-${ctx.uid}-control`, getInputId: (ctx: Ctx) => `slider-${ctx.uid}-input`, - getOutputId: (ctx: Ctx) => `slider-${ctx.uid}-output`, - getTrackId: (ctx: Ctx) => `slider--${ctx.uid}track`, - getRangeId: (ctx: Ctx) => `slider-${ctx.uid}-range`, - getLabelId: (ctx: Ctx) => `slider-${ctx.uid}-label`, + getOutputId: (ctx: Ctx) => ctx.ids?.output ?? `slider-${ctx.uid}-output`, + getTrackId: (ctx: Ctx) => ctx.ids?.track ?? `slider--${ctx.uid}track`, + getRangeId: (ctx: Ctx) => ctx.ids?.track ?? `slider-${ctx.uid}-range`, + getLabelId: (ctx: Ctx) => ctx.ids?.label ?? `slider-${ctx.uid}-label`, getMarkerId: (ctx: Ctx, value: number) => `slider-${ctx.uid}-marker-${value}`, getThumbEl: (ctx: Ctx) => dom.getDoc(ctx).getElementById(dom.getThumbId(ctx)), diff --git a/packages/machines/slider/src/slider.types.ts b/packages/machines/slider/src/slider.types.ts index 6513460334..3e6950ca00 100644 --- a/packages/machines/slider/src/slider.types.ts +++ b/packages/machines/slider/src/slider.types.ts @@ -1,6 +1,20 @@ import { Context } from "@zag-js/types" +type IdMap = Partial<{ + root: string + thumb: string + control: string + track: string + range: string + label: string + output: string +}> + export type MachineContext = Context<{ + /** + * The ids of the elements in the slider. Useful for composition. + */ + ids: IdMap /** * The value of the slider */ diff --git a/packages/machines/splitter/src/splitter.connect.ts b/packages/machines/splitter/src/splitter.connect.ts index 20bcdc1b21..5aac7fb81f 100644 --- a/packages/machines/splitter/src/splitter.connect.ts +++ b/packages/machines/splitter/src/splitter.connect.ts @@ -92,7 +92,7 @@ export function connect( labelProps: normalize.element({ "data-part": "label", - id: dom.getSplitterLabelId(state.context), + id: dom.getLabelId(state.context), }), splitterProps: normalize.element({ @@ -104,7 +104,7 @@ export function connect( "aria-valuemin": min, "aria-valuemax": max, "aria-orientation": state.context.orientation, - "aria-labelledby": dom.getSplitterLabelId(state.context), + "aria-labelledby": dom.getLabelId(state.context), "aria-controls": dom.getPrimaryPaneId(state.context), "data-orientation": state.context.orientation, "data-focus": dataAttr(isFocused), diff --git a/packages/machines/splitter/src/splitter.dom.ts b/packages/machines/splitter/src/splitter.dom.ts index c4a8d323cf..2cae2320ea 100644 --- a/packages/machines/splitter/src/splitter.dom.ts +++ b/packages/machines/splitter/src/splitter.dom.ts @@ -3,12 +3,12 @@ import { MachineContext as Ctx } from "./splitter.types" export const dom = { getDoc: (ctx: Ctx) => ctx.doc ?? document, - getRootId: (ctx: Ctx) => `${ctx.uid}-root`, - getSplitterId: (ctx: Ctx) => `${ctx.uid}-splitter`, - getToggleButtonId: (ctx: Ctx) => `${ctx.uid}-toggle-button`, - getSplitterLabelId: (ctx: Ctx) => `${ctx.uid}-splitter-label`, - getPrimaryPaneId: (ctx: Ctx) => `${ctx.uid}-primary-pane`, - getSecondaryPaneId: (ctx: Ctx) => `${ctx.uid}-secondary-pane`, + getRootId: (ctx: Ctx) => ctx.ids?.root ?? `${ctx.uid}-root`, + getSplitterId: (ctx: Ctx) => ctx.ids?.splitter ?? `${ctx.uid}-splitter`, + getToggleButtonId: (ctx: Ctx) => ctx.ids?.toggleBtn ?? `${ctx.uid}-toggle-button`, + getLabelId: (ctx: Ctx) => ctx.ids?.label ?? `${ctx.uid}-splitter-label`, + getPrimaryPaneId: (ctx: Ctx) => ctx.ids?.primaryPane ?? `${ctx.uid}-primary-pane`, + getSecondaryPaneId: (ctx: Ctx) => ctx.ids?.secondaryPane ?? `${ctx.uid}-secondary-pane`, getSplitterEl: (ctx: Ctx) => dom.getDoc(ctx).getElementById(dom.getSplitterId(ctx)), getPrimaryPaneEl: (ctx: Ctx) => dom.getDoc(ctx).getElementById(dom.getPrimaryPaneId(ctx)), diff --git a/packages/machines/splitter/src/splitter.types.ts b/packages/machines/splitter/src/splitter.types.ts index 089e5ce201..d5ff796e0e 100644 --- a/packages/machines/splitter/src/splitter.types.ts +++ b/packages/machines/splitter/src/splitter.types.ts @@ -1,6 +1,19 @@ import { Context } from "@zag-js/types" +type IdMap = Partial<{ + root: string + splitter: string + label: string + toggleBtn: string + primaryPane: string + secondaryPane: string +}> + export type MachineContext = Context<{ + /** + * The ids of the elements in the splitter. Useful for composition. + */ + ids?: IdMap /** * Whether to allow the separator to be dragged. */ diff --git a/packages/machines/tabs/src/tabs.dom.ts b/packages/machines/tabs/src/tabs.dom.ts index 021c4df6af..5a67d0d726 100644 --- a/packages/machines/tabs/src/tabs.dom.ts +++ b/packages/machines/tabs/src/tabs.dom.ts @@ -5,11 +5,11 @@ import { MachineContext as Ctx } from "./tabs.types" export const dom = { getDoc: (ctx: Ctx) => ctx.doc ?? document, - getRootId: (ctx: Ctx) => `tabs-${ctx.uid}`, - getTriggerGroupId: (ctx: Ctx) => `tabs-${ctx.uid}-trigger-group`, - getContentId: (ctx: Ctx, id: string) => `tabs-${ctx.uid}-content-${id}`, - getContentGroupId: (ctx: Ctx) => `tabs-${ctx.uid}-content-group`, - getTriggerId: (ctx: Ctx, id: string) => `tabs-${ctx.uid}-trigger-${id}`, + getRootId: (ctx: Ctx) => ctx.ids?.root ?? `tabs-${ctx.uid}`, + getTriggerGroupId: (ctx: Ctx) => ctx.ids?.triggerGroup ?? `tabs-${ctx.uid}-trigger-group`, + getContentId: (ctx: Ctx, id: string) => ctx.ids?.content ?? `tabs-${ctx.uid}-content-${id}`, + getContentGroupId: (ctx: Ctx) => ctx.ids?.contentGroup ?? `tabs-${ctx.uid}-content-group`, + getTriggerId: (ctx: Ctx, id: string) => ctx.ids?.trigger ?? `tabs-${ctx.uid}-trigger-${id}`, getIndicatorId: (ctx: Ctx) => `tabs-${ctx.uid}-indicator`, getTriggerGroupEl: (ctx: Ctx) => dom.getDoc(ctx).getElementById(dom.getTriggerGroupId(ctx)), diff --git a/packages/machines/tabs/src/tabs.types.ts b/packages/machines/tabs/src/tabs.types.ts index bd8673378a..21015b4b3f 100644 --- a/packages/machines/tabs/src/tabs.types.ts +++ b/packages/machines/tabs/src/tabs.types.ts @@ -5,7 +5,19 @@ type IntlMessages = { deleteLabel?(value: string): string } +type IdMap = Partial<{ + root: string + trigger: string + triggerGroup: string + contentGroup: string + content: string +}> + export type MachineContext = Context<{ + /** + * The ids of the elements in the tabs. Useful for composition. + */ + ids?: IdMap /** * Specifies the localized strings that identifies the accessibility elements and their states */ diff --git a/packages/machines/tags-input/src/tags-input.dom.ts b/packages/machines/tags-input/src/tags-input.dom.ts index 3709726f04..0673b1a1e5 100644 --- a/packages/machines/tags-input/src/tags-input.dom.ts +++ b/packages/machines/tags-input/src/tags-input.dom.ts @@ -4,23 +4,26 @@ import { MachineContext as Ctx, TagProps } from "./tags-input.types" export const dom = { getDoc: (ctx: Ctx) => ctx.doc ?? document, - getRootId: (ctx: Ctx) => `tags-input-${ctx.uid}-root`, - getInputId: (ctx: Ctx) => `tags-input-${ctx.uid}-input`, - getEditInputId: (ctx: Ctx) => `${ctx.editedId}-input`, - getClearButtonId: (ctx: Ctx) => `tags-input-${ctx.uid}-clear-btn`, + getRootId: (ctx: Ctx) => ctx.ids?.root ?? `tags-input-${ctx.uid}-root`, + getInputId: (ctx: Ctx) => ctx.ids?.input ?? `tags-input-${ctx.uid}-input`, + getClearButtonId: (ctx: Ctx) => ctx.ids?.clearBtn ?? `tags-input-${ctx.uid}-clear-btn`, getHiddenInputId: (ctx: Ctx) => `tags-input-${ctx.uid}-hidden-input`, - getLabelId: (ctx: Ctx) => `tags-input-${ctx.uid}-label`, - getControlId: (ctx: Ctx) => `tags-input-${ctx.uid}-control`, - getTagId: (ctx: Ctx, opt: TagProps) => `tags-input-${ctx.uid}-tag-${opt.value}-${opt.index}`, - getTagDeleteBtnId: (ctx: Ctx, opt: TagProps) => `${dom.getTagId(ctx, opt)}-delete-btn`, - getTagInputId: (ctx: Ctx, opt: TagProps) => `${dom.getTagId(ctx, opt)}-input`, + getLabelId: (ctx: Ctx) => ctx.ids?.label ?? `tags-input-${ctx.uid}-label`, + getControlId: (ctx: Ctx) => ctx.ids?.control ?? `tags-input-${ctx.uid}-control`, + getTagId: (ctx: Ctx, opt: TagProps) => ctx.ids?.tag?.(opt) ?? `tags-input-${ctx.uid}-tag-${opt.value}-${opt.index}`, + getTagDeleteBtnId: (ctx: Ctx, opt: TagProps) => + ctx.ids?.tagDeleteBtn?.(opt) ?? `${dom.getTagId(ctx, opt)}-delete-btn`, + getTagInputId: (ctx: Ctx, opt: TagProps) => ctx.ids?.tagInput?.(opt) ?? `${dom.getTagId(ctx, opt)}-input`, getTagInputEl: (ctx: Ctx, opt: TagProps) => dom.getDoc(ctx)?.getElementById(dom.getTagInputId(ctx, opt)) as HTMLInputElement | null, getRootEl: (ctx: Ctx) => dom.getDoc(ctx).getElementById(dom.getRootId(ctx)), getInputEl: (ctx: Ctx) => dom.getDoc(ctx).getElementById(dom.getInputId(ctx)) as HTMLInputElement | null, getHiddenInputEl: (ctx: Ctx) => dom.getDoc(ctx).getElementById(dom.getHiddenInputId(ctx)) as HTMLInputElement | null, + + getEditInputId: (ctx: Ctx) => `${ctx.editedId}-input`, getEditInputEl: (ctx: Ctx) => dom.getDoc(ctx).getElementById(dom.getEditInputId(ctx)) as HTMLInputElement | null, + getElements: (ctx: Ctx) => queryAll(dom.getRootEl(ctx), `[data-part=tag]:not([data-disabled])`), getIndexOfId: (ctx: Ctx, id: string) => indexOfId(dom.getElements(ctx), id), diff --git a/packages/machines/tags-input/src/tags-input.types.ts b/packages/machines/tags-input/src/tags-input.types.ts index 310ba3a1b8..2a73161282 100644 --- a/packages/machines/tags-input/src/tags-input.types.ts +++ b/packages/machines/tags-input/src/tags-input.types.ts @@ -25,7 +25,22 @@ type Log = | { type: "paste"; values: string[] } | { type: "set"; values: string[] } +type IdMap = Partial<{ + root: string + input: string + clearBtn: string + label: string + control: string + tag(opts: TagProps): string + tagDeleteBtn(opts: TagProps): string + tagInput(opts: TagProps): string +}> + export type MachineContext = Context<{ + /** + * The ids of the elements in the tags input. Useful for composition. + */ + ids?: IdMap /** * The output log for the screen reader to speak */ diff --git a/packages/machines/toggle/src/toggle.dom.ts b/packages/machines/toggle/src/toggle.dom.ts index b239ec4aaf..99d2580431 100644 --- a/packages/machines/toggle/src/toggle.dom.ts +++ b/packages/machines/toggle/src/toggle.dom.ts @@ -2,7 +2,7 @@ import type { MachineContext as Ctx } from "./toggle.types" export const dom = { getDoc: (ctx: Ctx) => ctx.doc ?? document, - getRootId: (ctx: Ctx) => `toggle-${ctx.uid}`, - getButtonId: (ctx: Ctx) => `toggle-${ctx.uid}--button`, + getRootId: (ctx: Ctx) => ctx.ids?.root ?? `toggle-${ctx.uid}`, + getButtonId: (ctx: Ctx) => ctx.ids?.button ?? `toggle-${ctx.uid}--button`, getButtonEl: (ctx: Ctx) => dom.getDoc(ctx).getElementById(dom.getButtonId(ctx)), } diff --git a/packages/machines/toggle/src/toggle.types.ts b/packages/machines/toggle/src/toggle.types.ts index ca11d2a713..ca11ce8d4b 100644 --- a/packages/machines/toggle/src/toggle.types.ts +++ b/packages/machines/toggle/src/toggle.types.ts @@ -1,7 +1,16 @@ import type { StateMachine as S } from "@zag-js/core" import { Context } from "@zag-js/types" +type IdMap = Partial<{ + root: string + button: string +}> + export type MachineContext = Context<{ + /** + * The ids of the elements in the toggle. Useful for composition. + */ + ids?: IdMap /** * Specifies the localized strings that identifies the accessibility elements and their states */ diff --git a/packages/machines/tooltip/src/tooltip.dom.ts b/packages/machines/tooltip/src/tooltip.dom.ts index b4aaad73be..60a22d8290 100644 --- a/packages/machines/tooltip/src/tooltip.dom.ts +++ b/packages/machines/tooltip/src/tooltip.dom.ts @@ -5,8 +5,8 @@ export const dom = { getDoc: (ctx: Ctx) => ctx.doc || document, getWin: (ctx: Ctx) => ctx.doc?.defaultView || window, - getTriggerId: (ctx: Ctx) => `tooltip-${ctx.id}--trigger`, - getContentId: (ctx: Ctx) => `tooltip-${ctx.id}--content`, + getTriggerId: (ctx: Ctx) => ctx.ids?.trigger ?? `tooltip-${ctx.id}--trigger`, + getContentId: (ctx: Ctx) => ctx.ids?.content ?? `tooltip-${ctx.id}--content`, getArrowId: (ctx: Ctx) => `tooltip-${ctx.id}--arrow`, getPositionerId: (ctx: Ctx) => `tooltip-${ctx.id}--popper`, portalId: "tooltip-portal", diff --git a/packages/machines/tooltip/src/tooltip.types.ts b/packages/machines/tooltip/src/tooltip.types.ts index 737c7d03ee..c275ea0a13 100644 --- a/packages/machines/tooltip/src/tooltip.types.ts +++ b/packages/machines/tooltip/src/tooltip.types.ts @@ -1,6 +1,15 @@ import { Placement, PositioningOptions } from "@zag-js/popper" +type IdMap = Partial<{ + trigger: string + content: string +}> + export type MachineContext = { + /** + * The ids of the elements in the tooltip. Useful for composition. + */ + ids?: IdMap /** * @internal The owner document of the tooltip. */