From 13cd5d5141022a7212987bd7ccfd9d0999cb905f Mon Sep 17 00:00:00 2001 From: Michael Beckemeyer Date: Tue, 19 May 2026 12:35:09 +0200 Subject: [PATCH 1/6] fix: trigger elements in shadow root --- .changeset/clear-queens-lose.md | 5 +++++ .changeset/flat-taxes-clap.md | 5 +++++ .changeset/pretty-candies-hunt.md | 10 ++++++++++ packages/core/src/types.ts | 2 +- packages/machines/dialog/src/dialog.dom.ts | 2 +- packages/machines/drawer/src/drawer.dom.ts | 2 +- packages/machines/hover-card/src/hover-card.dom.ts | 5 ++++- packages/machines/menu/src/menu.dom.ts | 4 ++-- packages/machines/popover/src/popover.dom.ts | 2 +- packages/machines/tooltip/src/tooltip.dom.ts | 2 +- packages/utilities/dom-query/src/query.ts | 2 +- 11 files changed, 32 insertions(+), 9 deletions(-) create mode 100644 .changeset/clear-queens-lose.md create mode 100644 .changeset/flat-taxes-clap.md create mode 100644 .changeset/pretty-candies-hunt.md diff --git a/.changeset/clear-queens-lose.md b/.changeset/clear-queens-lose.md new file mode 100644 index 0000000000..91b90bd189 --- /dev/null +++ b/.changeset/clear-queens-lose.md @@ -0,0 +1,5 @@ +--- +"@zag-js/core": patch +--- + +Change return type of Scope.getRootNode() from Node to Element to support querySelector / querySelectorAll. diff --git a/.changeset/flat-taxes-clap.md b/.changeset/flat-taxes-clap.md new file mode 100644 index 0000000000..e9fb18fb00 --- /dev/null +++ b/.changeset/flat-taxes-clap.md @@ -0,0 +1,5 @@ +--- +"@zag-js/dom-query": patch +--- + +Accept ShadowRoot as argument for query and queryAll. diff --git a/.changeset/pretty-candies-hunt.md b/.changeset/pretty-candies-hunt.md new file mode 100644 index 0000000000..f731c1017e --- /dev/null +++ b/.changeset/pretty-candies-hunt.md @@ -0,0 +1,10 @@ +--- +"@zag-js/hover-card": patch +"@zag-js/popover": patch +"@zag-js/tooltip": patch +"@zag-js/dialog": patch +"@zag-js/drawer": patch +"@zag-js/menu": patch +--- + +Fix trigger element lookups in shadow root. diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index eaca5aac58..cbf686d6ef 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -78,7 +78,7 @@ export interface BindableFn { export interface Scope { id?: string | undefined ids?: Record | undefined - getRootNode: () => ShadowRoot | Document | Node + getRootNode: () => ShadowRoot | Document | Element getById: (id: string) => T | null getActiveElement: () => HTMLElement | null isActiveElement: (elem: HTMLElement | null) => boolean diff --git a/packages/machines/dialog/src/dialog.dom.ts b/packages/machines/dialog/src/dialog.dom.ts index b24ee2cc0c..95fd958769 100644 --- a/packages/machines/dialog/src/dialog.dom.ts +++ b/packages/machines/dialog/src/dialog.dom.ts @@ -25,7 +25,7 @@ export const getDescriptionEl = (ctx: Scope) => ctx.getById(getDescriptionId(ctx export const getCloseTriggerEl = (ctx: Scope) => ctx.getById(getCloseTriggerId(ctx)) export const getTriggerEls = (ctx: Scope) => - queryAll(ctx.getDoc(), `[data-scope="dialog"][data-part="trigger"][data-ownedby="${ctx.id}"]`) + queryAll(ctx.getRootNode(), `[data-scope="dialog"][data-part="trigger"][data-ownedby="${ctx.id}"]`) export const getActiveTriggerEl = (ctx: Scope, value: string | null): HTMLElement | null => { return value == null ? getTriggerEls(ctx)[0] : ctx.getById(getTriggerId(ctx, value)) diff --git a/packages/machines/drawer/src/drawer.dom.ts b/packages/machines/drawer/src/drawer.dom.ts index 67c2983422..71c9f72b23 100644 --- a/packages/machines/drawer/src/drawer.dom.ts +++ b/packages/machines/drawer/src/drawer.dom.ts @@ -13,7 +13,7 @@ export const getTriggerId = (ctx: Scope, value?: string) => { } export const getTriggerEls = (ctx: Scope): HTMLElement[] => - queryAll(ctx.getDoc(), `[data-scope="drawer"][data-part="trigger"][data-ownedby="${ctx.id}"]`) + queryAll(ctx.getRootNode(), `[data-scope="drawer"][data-part="trigger"][data-ownedby="${ctx.id}"]`) export const getActiveTriggerEl = (ctx: Scope, value: string | null): HTMLElement | null => { if (value == null) return getTriggerEl(ctx) ?? getTriggerEls(ctx)[0] diff --git a/packages/machines/hover-card/src/hover-card.dom.ts b/packages/machines/hover-card/src/hover-card.dom.ts index 90f77e6020..520b5f5519 100644 --- a/packages/machines/hover-card/src/hover-card.dom.ts +++ b/packages/machines/hover-card/src/hover-card.dom.ts @@ -16,7 +16,10 @@ export const getContentEl = (scope: Scope) => scope.getById(getContentId(scope)) export const getPositionerEl = (scope: Scope) => scope.getById(getPositionerId(scope)) export const getTriggerEls = (scope: Scope): HTMLElement[] => - queryAll(scope.getDoc(), `[data-scope="hover-card"][data-part="trigger"][data-ownedby="${scope.id}"]`) + queryAll( + scope.getRootNode(), + `[data-scope="hover-card"][data-part="trigger"][data-ownedby="${scope.id}"]`, + ) export const getActiveTriggerEl = (scope: Scope, value: string | null): HTMLElement | null => { return value == null ? getTriggerEls(scope)[0] : scope.getById(getTriggerId(scope, value)) diff --git a/packages/machines/menu/src/menu.dom.ts b/packages/machines/menu/src/menu.dom.ts index 6964a2a1b1..c8bff3e713 100644 --- a/packages/machines/menu/src/menu.dom.ts +++ b/packages/machines/menu/src/menu.dom.ts @@ -33,10 +33,10 @@ export const getArrowEl = (ctx: Scope) => ctx.getById(getArrowId(ctx)) export const getContextTriggerEl = (ctx: Scope) => ctx.getById(getContextTriggerId(ctx)) export const getTriggerEls = (ctx: Scope): HTMLElement[] => - queryAll(ctx.getDoc(), `[data-scope="menu"][data-part="trigger"][data-ownedby="${ctx.id}"]`) + queryAll(ctx.getRootNode(), `[data-scope="menu"][data-part="trigger"][data-ownedby="${ctx.id}"]`) export const getContextTriggerEls = (ctx: Scope): HTMLElement[] => - queryAll(ctx.getDoc(), `[data-scope="menu"][data-part="context-trigger"][data-ownedby="${ctx.id}"]`) + queryAll(ctx.getRootNode(), `[data-scope="menu"][data-part="context-trigger"][data-ownedby="${ctx.id}"]`) export const getActiveTriggerEl = (ctx: Scope, value: string | null): HTMLElement | null => { // When value is null, use ID-based lookup (works for submenus with trigger-item) diff --git a/packages/machines/popover/src/popover.dom.ts b/packages/machines/popover/src/popover.dom.ts index 41a99bcebd..39b78b8218 100644 --- a/packages/machines/popover/src/popover.dom.ts +++ b/packages/machines/popover/src/popover.dom.ts @@ -19,7 +19,7 @@ export const getCloseTriggerId = (scope: Scope) => scope.ids?.closeTrigger ?? `p export const getAnchorEl = (scope: Scope) => scope.getById(getAnchorId(scope)) export const getTriggerEls = (scope: Scope): HTMLElement[] => - queryAll(scope.getDoc(), `[data-scope="popover"][data-part="trigger"][data-ownedby="${scope.id}"]`) + queryAll(scope.getRootNode(), `[data-scope="popover"][data-part="trigger"][data-ownedby="${scope.id}"]`) export const getActiveTriggerEl = (scope: Scope, value: string | null): HTMLElement | null => { return value == null ? getTriggerEls(scope)[0] : scope.getById(getTriggerId(scope, value)) diff --git a/packages/machines/tooltip/src/tooltip.dom.ts b/packages/machines/tooltip/src/tooltip.dom.ts index 7067f0b353..d6ad7e3df9 100644 --- a/packages/machines/tooltip/src/tooltip.dom.ts +++ b/packages/machines/tooltip/src/tooltip.dom.ts @@ -23,7 +23,7 @@ export const getPositionerEl = (scope: Scope) => scope.getById(getPositionerId(s export const getArrowEl = (scope: Scope) => scope.getById(getArrowId(scope)) export const getTriggerEls = (scope: Scope): HTMLElement[] => - queryAll(scope.getDoc(), `[data-scope="tooltip"][data-part="trigger"][data-ownedby="${scope.id}"]`) + queryAll(scope.getRootNode(), `[data-scope="tooltip"][data-part="trigger"][data-ownedby="${scope.id}"]`) export const getActiveTriggerEl = (scope: Scope, value: string | null): HTMLElement | null => { return value == null ? getTriggerEls(scope)[0] : scope.getById(getTriggerId(scope, value)) diff --git a/packages/utilities/dom-query/src/query.ts b/packages/utilities/dom-query/src/query.ts index 2815b4c0e5..e38aa38798 100644 --- a/packages/utilities/dom-query/src/query.ts +++ b/packages/utilities/dom-query/src/query.ts @@ -1,4 +1,4 @@ -type Root = Document | Element | null | undefined +type Root = Document | ShadowRoot | Element | null | undefined export function queryAll(root: Root, selector: string) { return Array.from(root?.querySelectorAll(selector) ?? []) From a10c6487b01aaac4d3dae25e4b69799fae118e78 Mon Sep 17 00:00:00 2001 From: Segun Adebayo Date: Thu, 21 May 2026 11:44:10 +0100 Subject: [PATCH 2/6] docs: update --- website/demos/image-cropper.tsx | 3 +-- website/public/static/image-cropper-demo.jpg | Bin 0 -> 18211 bytes 2 files changed, 1 insertion(+), 2 deletions(-) create mode 100644 website/public/static/image-cropper-demo.jpg diff --git a/website/demos/image-cropper.tsx b/website/demos/image-cropper.tsx index ef27585662..2ff5bf6988 100644 --- a/website/demos/image-cropper.tsx +++ b/website/demos/image-cropper.tsx @@ -43,9 +43,8 @@ export function ImageCropper(props: ImageCropperProps) {
Dog to be croppedkOB2~m`Ui17plJNY1= zg!8EKD9F+NT?1$Ws3|F_C@H9^sHkXYsA=he4D@t#^z1CRnSh)e++3U-U@(t>gb)v( z7(W;+tN;;{l#-Q|6=E2U0(+suiIFAyjllTkv=uhG~Cf`9lTAmfaRrJ-fL!^X~WPf$o0 zA|fj%ub>E3($vz{f$8e$n_D0(t*mWqU0mJVJv@VN1(!_;Nx;WmQviOKV$uN9WuAfx)5SkVLRMNJIa1GG=lLUMWfzbu%ib zz}tM%G1NefjFQGa8h#o07mzdR11+n7?5BHQ{|D{=Ap3s@7W@B;?0G8<>m_S%=1MhDVshNhGO=2pt@r?&Ja z11;i_I8$Ed^h%OM;T5|BP4MR8V8f^%xC;6HeZ#I_G@_#w8^ zK4wrn$N%841bkeUsD zw&3rGThSgj6Jh&ZN&jrY6E9n9G%{{^@mXsACz87K(>VIiBca#k8Ah^a{i832A>~R1 zb4JPyO}tWQr?eVoqjUY8JuS+IUPyG!7Tgiuaid=@k}_hY3u3o@+BXV!m1EbmLer+E zoA2M@)vYY81+teWCugbs6GM#km$DZ*g7fzor};? zY7C^WfnOkNo((mUw5E`s$Ut2&o_~^)7M4a+*oN7fLKZY{{hLtxcM=)?ovDB7liwpn zV@1OShlhkzOr^o@DbDq8q(7MpN~>1Yq`zfh*DWbVrr17%6gw9RYXF3Y6wT6*DgOG% ze_LIw>B(1EjSU&MBn6wH(`)S;=NvB6`(Pa__&;I4PUWJq*I3J>!K+YZEG!*TB45lUm2-q>>~q-W8bQiu8-l29GlP|Z@yKNf$;R>= zLWWs}_RD~eIzODsv4v)pEN3{uGNLTj9v{Rch&2*Krg-i#I_d4B6En3RnC{;L9wl<5 zKOs0%2o{u=h-w4h5PI$%WV2iHl8ea?LA$`c+J$M}9v-AZ6RNx-38uRn!Fgm;Cx!5SLCmm(v zDFUPJ*QrvCcTN5ZE{f#&ing2|{z7s@#eHxLQ!%R|_@){SB^|~>YJKm%dV_d{cl!(Q zY~+zp6+v`8D`Ur>@{RAh?0s;__|6Z4?&((fm+7Qxb)Z%DwVoa# z-i_lEZeowa0ugVyysNXHE>F!!M`HK=`6MTMQ-TiBMr>0mIDuJ`Ov4R*j?#TAA>f$^ z&-|BANv>AUP=N$8*%*u4SGC91tP9dKW5|4!O(NO?-{AXz+BY!`9_gL-=hwiTydk|*VB>ud07AvxO+GeDv?wWDn zX0zPGTPxOsPlsvaqLqvAL5rK>HvauQp7Vuil1AYAC#qMbz4v zo70eW8V#!^`!5FDcf0R~T0+y*ulMt@7<PXn#u0(kWaa}4j>`mpoaig}a>{+I zonXvj46EsU?si!kzSy}^_H_B&a0-H(B@OnOk6v)~Y9OFJtvI`YC5Pp?!cM5D{n%?Z zJYTecWo1S)BFnkl9Ib05v|-F(8fQ$IduH2ycb}~Fi9tALw|Z#GPRXhLDK6$7mAi>c zVHOeoDSO^c?=-CiuK+4pNQliSt31k6t6In6DFvw_KW+9zlg(pqHHN5dH`w zEADSa9B5Z1+7+3{vyu)W$moCEd$7c^WYz}lXE<2xhGIr{O_BfIsQSMT_<R@`p>yCUDRu#L~O&y*EQks77K*79#9QE_!!Y#-g1v@7c38SOjQ^7xPe$6}}Q*(Ow1>FU*^Q`p(U0J+N0H+;3}EH3g=#$jB2o7Lv2@6{Q97e+ktyXgdNp8I)6NQA$L~%RdMs{*%uu57K z6>uWE@JNbshb5Hf(zdO#!7mE`>d^ARu!qkVRU^O34Yr__g4F5Q;x5`T*_f{>&93m3 z|5&K++1v{aVH=TKqiAojACoXCmb0nTx**|bn++p-zhnC_*mjo?iu(4NR1KZWtJXW@ z4SI2Dt>eT}P_Xp`H0k{g^0tvNJvNW+V`0{gXk^K~DC4q04tk}92J7961I@QkRR#mT zj^vXI347RFr`(S3_HS*Wd+s6M1>fFh2%Fu%v}$YYqBMc_^9PHVnDp0~&VEk*sN*}{ zfuho{Td1$Ly$QNw79Aye@Aypt#xjKO->n@)iv+Uh?9Pd3K)S+l00@IJTN!XTF_l*l z_{{-{3(}<~-cv*)*~&@H^$6~{;pI{^<(c}(gc!;bGsDtoc0B`QIxIk)sH&}a@1dVC z9qmhyU|20wt zT_qDC@Kv>}JtxoK1Y0L*xjY4*Zx_VAV38TYJA3`%YM+k|S#?u?l^3ztFwd{-02z1P zsyy*J)ReVbx;vs0TG5IxPieV=M%^`Rn{%B`&;PB%5FAjYL<*ytt>jy6!A(XhpcHFK z#Butp^4x{UA%$w^%+HBuHDrS!QX@V=_B!?9p-v&uQq)Xe85x4V+hWv)f{N6Xm)N(x zJ0)E0s{v&tzz3G&gvg^OdcnU5<@d(ZZ4?6-1J6y-2frRx{LXQ?t8j*6<2e9*P;t9U zRk)S5o%qdzOX9UyXU?QY1oqQkz=sgQ4(W49PZte>K%I*LVokKR;z@OX}jEaJKkI zr!oI6`u0q5hy_J!8^2a-P93?I7Py)CSzF7$c9WXR2_)m?zjvw`Ey|>v&cbwHy1srR z#bO5tmeYqzAvuBcn5P#Hc0OVYHg(V;jb1NC8w=It8HbH;BdyN*s6W@#kayE$U-04s#(Fvoz zm&H)FhCO=5eOU8{H}*)&H2v3BA@19s;280~oSED*cgvq6?5nEBEngf;yqbOC3JS*1 z+VGTB?-YkSAo+WX`V8OQMkT47B~Fel6epM7reeK+@#yD#*sCL0-xNwvolx z<-4=2JTrWTt`;zKpbI)CH_mtug^u`Oc1{EijVsb-d+`LpA(aAev=wXR7<41xotDhn zx)fS$v|)ab#BdNRl(3&Zdm99xEH^hLGqxJ9IwAyCoUQP&x#zFX$+wzOreFkPUTJ<50Y?39_u6=p)lV@Od@2Yy}}4iQlWB zcz4c&-RSJJ@!ftRgfxxvzVn1t@gwl@Be}j5{b9+$n1Ur0q5OxUZRfSV-j*ls;W8No zi$Xb}OcAGeKZHvm@{-d(z8s6poXW^Yel`xP*J$<@J)}2{`+e003_C1MpC-r#${T} zJLQGS#qT;p|4a--4nLk&H~g*g8U6w=r8sT7ciwtilw7FbtF4s;cy}wxn|W)zE*bf z7%yUUyD7$qGs3-KpD_2y<}4fR8ey5=JbvVJ7^3b=r&~tZ@ezoG@aLHVEPH4_<*QjfN9^@ZBY3MQI$BNy`0$R9~K-EaRT3|YXy?WJI*4Xf$}M4 z+5`BtQj=Y?Or=E$UnTOx1p@AdOjvm?F} z&xm{ho{g7f85{VjZ--8#4cJcxbfxOPfy9JY0-a;516Ae*VoV#M<=`2L*7S%utQo?B zgW6|C+AV6@yri!R`DOs!)Iyy(IRMWQBX9;9x~gzIRok@~4g64G^s7pO+dGGSIKsX1 zM4CIus+;Q?n2g9r6p2;Sy(Z4A`TxE*FPs#3W~*IjyPorTgw?9zK$*-J9nN@o>}CnH|XJ z`h^X7iA(Zx8UI?aV5F~rK02F|hFt1;{x_jO{=7HvW-iOW<8&MyV!CmR#q8p#I_q^BW={QDM$n!+8Lsw!tmCC=g zQ!lX%uZ4Bl1@i)u8*>X>THVJ_>S}PlqQk1ld#LYnXUu*U6ssm0Omb6RZzBhUrF8nG zq5S1upWb}v9ukm(0yq|uu-_;cVg!|+Jk+0M37AjP`8@pLI-&9X&{(L|jb3^o)y|`+ ztQ@=^y=tI6ale3D_(Mk8fBVUFcJo=VyOid?w@{*Ci|F}^9&!GjLPTC^HhZ#20gtNc zQ4GpN)!WXn!BaMz@#E$wiMvXGswkg&W8x09v~0g6O+~A2){ZwNoCvjc!KS7mJBM7a zp>xfw=B7+N<_dhJqH%hnG+oK)m?@gd>5Zx>6f%*yvr-|@thgjiSW`oWbAH$lvP_g3 zp-hfp4M6GMg@E4`O&?QF3Nf~H_AxA?7Mw$Xyx;~~=zXzWGvgWTUqGi}> z6a6ByFrm3pFef1+)*sF;`%B^zqPvtp+5Uyo3%6JX1x@RB_2B%i=rP^;G=AqZ@D?!w zxn(QPg%fhIdh@1gA#!~1x6Q={TPX{&|08-XZJO!Q;?$E}k*!v>#@|(AK!3?y@g-2! zWwuU_j(5M9uSLphkWa!(%Q(<{Bn}?_Xh@lMfK>&Jf+bWU!U*yEnc4!nvd4A_sr&KZ z%4xoOJWd#=&BCZmZ(%B$J+@^T-)#*y(eHmFe^9wGeO8vb;4_UKjHV3Z%AQ(Z#ppW4 zEiV;&HB>{c>WnXVUTn8`XXN+Wk50|HgbOi`h9?cA{6yTeb!I+z37pVj&P#g`Vk-0N zh=x1!k;NXKSi&ITeP}LbE~q#qB5r5kUHg1mYqc)cXs39D>TB3j*zDLsApb ziw7`#F=JQYtB9*8uE*;lTTklc<_h+V1Jf4Pkye_HV`WUDy5ny8O)-)Y_8K z4?2>ZKY9ushr^c-7Lci5ix`_2sC^wEA!mU*@?BBttawscn0jHO_i=~=#Hz47amuVg=MM<$>QKC&Na8i?bP3AS~4`n z%$ByNi=;(Zq@t+I?St<vt_acZqD9>x%llpm^0-dGj4RA|K}XMG zmefSkew=};t6r~~0VDQsR9a;BR*{%TtpT|s3lPBH*`B zzt78c)vKdqrCG7vqUZq9Tc2RXl#GHzVkxP47E=Udi#a+~yCX>e?&XTF=kvDaL)8YaRg`4*j6cB!Rt^&vu^{{Pv>A){G9v=UqsO)-y@C1tL;w zQhuv95ta_IduJb{!xdGQ44w=i59Fpcs#*x_-$(Qja&GXMQ5#xl?@s5pO2Y>p&?88u z&g6|*Jm-;oIREFtuk#2EOQla{FdaQGpHxR7m7GKPyP2TE0$TdCK#Ymp?ewNd?#0S3 zbntCUO|`qdHiXR=*t5jF++Sc?ct8@W*wN z&!QYH)%d`L;;~_?nmCWZ)7^OfL*vDDzemYRzHc=K0&aq`y^dxYAFb(#DU11F*;Fe9r@9g-Q4yS`Wd4ZDvLD^a*=YgTk(1El*+cnM zoVFm6JyRw+z6fL13L2_I{+Ol+D-r~2J=3z;^{pbQIbCjoOg8I8AdaL)<3VPAfQ-$~ z-&y&}ZkE7>WR~%0=jxW9u!+UeX=0|4p~@Cl_8bR9+~h%6-q>erwEMb3XmygL3PLA^ zYNr^_@`7ryw1sbhD7gTBES8+nart)P#t&q^FPG^e zYQY{6=DqkS8=}!?*G)qm1IZej{b{QA-@W07S-U{Nrd{g`<4vy!?C_BLWOU`I*$^yS zDyF?Ya1{e{{$nda7hyhkH%C$t9As%m9}q2RtOh-pty+iZQCeglW9R5n$6v*O@bceXS|!9G}(LO_}(ds|$^gEs63*19fXy9tn=a98MktqGLd zc{x|ig#6oBmBKv%U3-#(?7+|JRSgb%9eAYALsB4(tSWU`F^8nmF3U2}%Wf1#hIi{fE>K#`eoLyu1k}p$vl$zu zNHwbF{AR!=B(`9=$`!Bo!@M<=m(Lyrt@Rok&dRME!6NxzcQjyVx`$jA>qjJ(buNjz zO*O^Ue_G!JejkVQ)(gIR|L(v~ux6)BB59$D6{W|62veXGU98iJ2}4nMeH7fcSS_rG zp%&UK;=9$!326rDCCr(GXN{y?L6F7zN=Gm{#;e1Sh6;l!alXd%0Ge93L6C&^XN@nS zTv^k+h^o-_I57sR9LMT{Ig`e$EHh|2-&Zp|QEBH=fwz>?wqZBsf=#AY^51HQH)tL7 z){)87mm-^|2Yc+*rq)S!0;{u)5!EuO0^dZ2`&H=8SDlPH)iAzQXBw3i-@I(|huekT zvKU>OAzl)Rz~30Ano;Kj^A>a?$3cuKu4_scjL7cP-M-OzELVe@g?i2%WGC+Gk?hnO zzsP-AYXttFAF#uKuHy~~`SGm`do-Lac~O2o^Paw6xG7>jWBvRn&?art#%i&MUgF|} zin4R#UYA0GPzI_ZpUe5})8z^+Q~yz)+lYf$i&XPa<7@lTfs#XO*&i~qB_PF*?%r#5 zpx4S5gvHt(Oq*>}t^dQ|GX{r*!^VU$B0N8Ke!P`Ra86`Hb2kl&YimOqYovRfe2FD5 zzEHLZcpHb{ApE*O53p+kd(8@n@!8|y^6r9yQ@PZ86YNr%_G``Mq8fVJiIR$a zszb0n5Y$1~{N>Kf%N>V8YtW!o+@ zD7O+R<3F}=aOUQ(+~I2FV};Y*66w^*s|Agz!enLfb+_qZja7x7ic}!5A2FmlTCLKI zaw<)5J({D7+@7`OzkFKNYMmuU+}TL2Vi}F(#hWzs-f6G&2_T@+Kfm(`IZdbc%g(aQ zT6|lefvQvRO%30RC8vB54D(f0)18dVxCXBBR)KEEEf`12w-Gb5(WfWds)P>yTBG!H z7x8lkK~v+9f`!Bo{ReQ3u>!J{Y9X^E+s7M?@b^*AGYA6bawe}2)O7a3O zRf`32Po|dS7poo5uAPf`dw5pxFhFb`%<3I6Q>h`?oVXDm?9Ui?t&LgBH-CBTY^a9Sy+narY0U{CBEP*us z3-IcG&1Elgn;bhGwyZxokTGNz%LmfqqyYWlBqQ@cT;5T*`F28m07@3{#jst;b#W29 zvc5#1s{@@~#rY(^gHmZaHaMIsA>&`Q%uWmflxMznC&kQxIMn%yH{#5Pc*iuCfE(ve zCSD0O@3rRLCZSg&A?m&^v|v{OBWyOcp5;+ik8`P6;U71@+ZCyj+S9cvBHl~d#k%#8 zP|5cB^`>cyRcO#(*JZb+=?O))$VsxI`IFwDl+4U0yRwSMScy4&yS`-gJv5ZM!SKLC z(~%H2$0m%rTqx079(q&erw#(oww_jY6=z|R=Q#XP)eV*XVkj>FT=vr-db#`-F&& zn1W*6@3)sgUQ(Y^ghW!?b_JhDc9qf|i7U2Ss_NEE_)4({N--3Dx~gSvl5DIe@s5!F zedZWywik(6AEoGW?@Z1a^JyTPUtE|klAw~C-V~LOaIb6Tly$XlYQCiuvFL*a{pXTt zD+3eB9Q{>Qrt$dLX7F<)D7L{r=XD@LWMG}+X?hb=E_Z*WiF~iR%3{*3U0pW;Dz<4u z)4TiYOwcMR{h+i374g?Y3e1AG9xHcUUfE8%i>;^{*?0NBj*Hb{u#6_1zy?~X2OiUf z=SNfhu-MdlC~q8O>7qWz>1A55)2f+)1nM^xs+IR;chRrZ((vC6Kebt+zqQK+(+>;< zro~ciJ-1zG$^xkEF3Gt_Js~e_ZAA*w^p5?QUCC25a(B;+{;4fuSH_&#wZ0IxtGaYu zoeovpqGc_0SzMu+`Y@zfp#~Q}f^xk%){x39_&WP0l<}v_NA!ZuXJU^r%cruOErqY~ z{EA1aOdVAU5|+nQ+5GIB6>kLEyK`jv$WI^a;9(F!9haG{SM@hU4;PJJ^dIWfx4wfY z7RFYeHc^i%KCJkaSTz=4sWZA-LtWnCxLe0~5=C8J%8<{xM8dD_5IEa*a>kjX!m%%& zw5+N&Keiz>T~m9faFmm<#VCyL0aR&g;tVbBX1xlmpVL2{vQ{(^ELj zR;+I`=|+I4PH9SKTSjaJyeL$wvij1^Qrb+( zer*5s8!9{F13cJ?PS1BfO*C&eo1Xy#B}5hHzupXKAm{)GIqnU_jaAh1Z_@t-1V`^$ zCciFRoEhu)!c<1)V2|XZS)kWdD1*zKDUx!n37)~cwUiW#H1WpP^n>yst<~h2!-4+b zGE~>2ThHJ|wrGDP@yU)MEBPv!_KfAZ4I3UB_T6jTT1ex9f}VC;t9Hk3k`-VFD}vST zQCBg8?{Jmh$z0H?OMjj2u{#2uCjzVEdfw=*c7lpH2a58N)|no(M$qJ0`1{FZp4us| zW3#f^KA(-b#_A@9Q<4Qp!B<5wG%4AK1r{tHpZx~u^3d;9>xj-vrq&@wkF4H_ini1c zvz1aW^;Y+8Q_Q!!nYt8iw>h+8f|K9 z7;5e)PDe~;hC&4n>KGi-IR^`k%sU+KKo4dtrr}#|Q%AV1%X2e|X`U;BFAFTom(GaYDWo@KmisVx?&?4mwjM`ypL8$C5@-zhA|J>G02zf4PL$mrJP zGOaHBINn&FD|OH4(K{0n&H3LZe9c0cBHBob`J`rz{^!{`-lcQnZTqTH_K$EcLhF)^ z->_{JfD!tF3JTrZ??uRN5MA!>{E^QdRV#_z*F-mAwn)-MInfF{8G39#%Sygi5sPhY-LV6*?xUG78_oB*p)X1rb=|NzLpdEubNel#E0w=cQW75xPlH9x0!t()$_$n zP4$kLQDCF8?vVZ0)xyTxOBrxkC;5UPb5wFpuP@bV%ZaOqZq4%D6h!W0pO>@gY43&x zxQwH`9moMLwLz>Ha0wL|c*)Fp(>2oWOD)fN35%A}TlObk)S%|WqA5L=DoZKuIXZKZcw@uhYB>A}w=f;~#psEoT zAJ&JYq}Qr}g7XF0j4{pqnL|))*cj5321-Q*1qGy(uKugVnBQuR6#Q2*&BJ&{ z719iDT$^UE4vS%tck0RYz|y=(bKz7%ODrDXs$eh*JbHXKoEK()c~yN&X+lv<4vuBZb~@VLQsq_p*|)@RMJ zA&~buAE8E_WaMd4Rkg%9o{LW*#z7gs5bop0QP=^6sXL-AKRiGT+c1BqrDki=>do<= zz(-OG7t4t#v!|D#J~fSc{MD4sdb@=Ki*2>%^tXn)G*7R&-tSsQ661d_ld*9po*U4= zYSqefcaYRktp4U@V2*8*hDUfhgkZPT`A#jy=mF=2QdWqam!4C%B z*hj?=SmLbIYrrMy`AGhb_zO^4X>u`!7@;jNwlkcDre5*4BU>Ar2e~1%_ z|9acPu&Sh&Tw&OT3D$sdrz~E)U+_q>>dybZQ``$JGzbuH&=|4h)8|nKp7bQj5M#5pHmz4b|!C51}9W?5G(yG=5bI`U$w!K!Doi(}^ly(cm<_tB; zd&RgEq-FapOZz%Asl19PZ()geBgPmKH-l|slN9Q#Vs4Ns=8h;liFm?EQm50ZRMtpY zsyVDXO-61BOabtuFx!QK>xl(BwNAzXlMLP_jc?Q&CV|)0U+jJRs0tS0cH=8Ifzc*H zV8&KwvFKmFXP6GqE4ZNYkk#PXX=>l`KwH|MYk(B1d!_5n@WSrJUkc>ZPN%PhEUI<$9zowEf5{)A-1OI24zmQ5~@*wxUMD$XzcB7vA3%`zmRM z!MR54mz37I@Sh0QX@oznwdgVf<0P0=)4cXd{a$eWxmG1t0cPIVq3QOC% zVM+1H^iL;Ei^dxS5tpO^)^6%oT&>w&_6y+LC@-_v*qJBByr8VZQiBHhdFcjE;Cd6j_gesQOO-npqdro+t)7%z{+6O9!jz z(fqM zX%wH{MJWaU1stP&nkrHtWCTT32#b9-mj~SvCsAA2A`3tb9e{ieKQ~^IcxLZ{sGL3(#n@g z4$(nAD-QW;JNdX+b#>e{rc=2bYwf0+<94rP5G#meiLWUZMuHZo4XDt2x#n+lfo#E9 z9YPIT=BK4?^el#~)6YKY$D8p#ubh9gLbR_`^kK^GAdeDRC|lR|aZIE#RwMU_n?ZMm z`}Dh8PkruOwRi^77oXWv<82(hDt~i*yW+5K&U@61@7=Y;Ee|q*0*_yDmb^_5BF0dC zZH*Jme;WVg)T&{An@+u-8f2`V?KMD!JXIqL0*z{Y(!z}Jm43RmHZB>oFfMA5NRrvg zjo#T&8-W}1{W0*tukA8_=~Ek-Hyx@b`Wc$CX>`SC3*h1HW9&tVfR^cMIByn zNv~8IKbVlNTwUG)$3FjUL7K!g-d(aar!s`%OVcL5He3D+_)+aXrK{cZGODDceYvaM zh1=mTfN({4w9!3A9|+bd0|ziD(Fu#6eiIviEF^YwlXQgqYCGI?<1=n?xr9chMR=m6 zdambp?K(H_h;(cnd_1&za>{oB%{~VW2+3ZUSO9)@QL`fh^mPNml`scv8hj=e9hdbeT zq>J@#h&}W1XW0AyEH@XWX8I)ymZWXL?Jw`Ux8{`{j0L7MY3RuO77;fem@{}PFqb|7 zJ~8fFUQvv?)Z zFzD(`TD<-&n^|{3|8rv`bCF?ZybgF+jjCk3d#HAG0i*LN&u(qO`vdmGh|tH6NjnH1 zL6EbE=Dlr2Yu=7#a6mk|Qr z6i1t~tl4G!hHXC=$+c6la`DdKpuc7E)kEOxHEuVzvqiY-U*(KQ1c2r!j4;Tx?&GKq zUYO&c5p-i?8XX&*jv&yE{@lyD&7T?nt)gef*vQjH#xK>t!+-N%Es%I#_DlO$v(05v z*~||lDQp`uN5j%3NADR&0$>lbg*r>I?rBYK)?)n~yYe$p+Ri2BqW+42(~a{#pby?M zn&P!Bfw#`=CU@otH$OP`>!wHZgE|XXo^yHaWy7=EajV73d>54O<9t` z(4}ZSc}N9__Sd93yCV6uP>&pr`g4WJ7)JT4p-hIq=rA?ic*@m3kGf{TJQm%U!`D@7 z{w767TK7Nkv}lqU+U#F*GI|{@A?oW;UP4T2L9N0dlFl z8{B*N!_4NVMT^3Zn#~IQ#X#5Yo{7Fz5^m3gQ%A&lCWoD!s@85eXGxE1sSV4cx?OS)&i4BchQu(wW{C_iEm|$J;@J3V6@YO_-;7 zq~G596z2g3$;s6;X6)q-kYH%`33m*+kw06BN;$J5c%5v$kvs!UUXMJ9_aTGef-OXm z3Y9i(YFc-2^~LG{N-N_Qh7lK9vtr3p#}>m^nDxobz$K>^+HaNT_T8V%OQd1c;o836 z!rhR$P+MM)JeiH{9+Sg1VRLwWFG0O`r=nJ3anPvW>(p91=0=Jvg)C$970(!d^m_0Q zY$1&d_jGF69J>0TAuscf%>nOAGt)iM{JDXCZ`3;d%>JsY4i%?Pkm&@yK~V0J6WbAF z)}McpTN39lu`MT_?FcPz)nN_rx4&Tp;Eje8|gD5S5 zT#QzcjN_B4Kep@k{B7i(p@4`?Jr2Nfz_Lozwyx}dSyMIu_F)CC@)xV?e#D>cY)gDJ z6D5x8B%hV%zb&zJq=nZB_;5Y*ar`moqO%(7<4>Nz*;8fU>4=6nIX5-l%9EqF&lc$N$Ujx-!TOrZgn<2BraOIUkVc<)0ebEOGPtpov;Jg1ot|@+MxLWVE&^~M)Z3mV9At~v2SLS0R^moK6_EiLDMgjwGxlQDIgHD`WpOi4 zYO`Lv*8Ny^eTqi5BJSx^+83n#8LopThjdE8ZPzqX7v6UN#DoTn>&#N_J^W8m3M-`3w_h_2ewUouEM~_?!mTbiERnfCZM;!+JuK@l90r~?>&pLJRCQulw@Bkc~ zo=s`IQ-zK>7i?xNvS+FG6(m!bh>k)|&R2J8v~6S&eX3W;KXpZOy3$Wnbt)9xE}p$i z;|z+U3^zZ|yp!9uJ9n!VHc>lD>_}HYG4Ix^8>VR)8Fu`{2J6i{&S1A_7nj|g-LYFe zW2aLEXv^N##4+j9Nh9rjs*jx3!Xzti<+)6rz;aDx+T65#GQ=FQHDcvjH1oXFO~V;0 zlk~1?imaWkbx?F82HMo4G(tBy8BP6gItq?6x^$2n?|clNz*F1Hw~a!Q#EzM$QNHY< zdUNFK7_K{tGEixaCzFf`hQxhB+n*Th6-3?wrcvYk;5QhjB~{@O^)Apg*<1u zq~fG)O0%OE%C&t${pFyS{HH}=vN9xR|F_do}G{KrD++hTRxB#@OP=kC&&_gikr3d1CyOgzW z939;Ur9KxU2O|KC(lxYz0;<@@-r9OpD&P#@;fWmfrmXfeO{a0@?pWD}Vsk}6?%4ph zL5eE5BiNs5F6N9bk+XLP^r$B+(r4496i_#@FD`&*oN-T9X##FMuLg=LVmHvYZhX9f zfPHG?5#){r>L{*RtxsPMCZLiM+=ZCqtx&s(6H|7_Eze3QsHde-V>NlUojnWZDLBJ?SK12s^TK&M2asQ8JUU zGsaaxRt0*IO0OqOU^gI9MK!qk4~8((?_hm!eQHaWXPh1U40oc6UPFviMTo8wIBcFi zY9qW`d6~MSj0z~Go|gvq2<0KmqKvO39`#yD3(9<=V<#lzwG>e$VJ7q@Qqn3$#d10^ z9Mk2FIL^mr=Zub&QCLbhJ1Ep@OG4B(srEMVToxUO#a1_0HgSu0Qyaew{l0>VDj}q~ zMOv(%zV{-^7_#3s4944443LJ!qnp?#WO{l3TJez>Yi8z0=@J z@|9pe8Yrq!QdeX-IJ+Q*<`*mY_D5|hU zXevyQTQsef1oZT%R3kQHQHSZzN+_t7lR6;?%8lC9GI-^YWq!aM4mhT5K6H~YJ%2hV zu841AmRnq`;z>yiK2my*T4p@5ah!qaMHIS<+FXWt9yMvCY#_!)Ij1Wl#)l<%;1kx0 zDPL0DQiFbCJO$S(HuI8m%``?ySu^Z&MHHJ7NFHe1DoKzXxFCNzU$VEKk&?aJ9Ab(p zB_^)U-(wk4rrD&IN)P(nFKV(bV%x;I_Y_fHe4vkA!C0rL#J5(&hRZJ?`c-=sf(Tb| z4=yvmIiiZ%4f{S*kJ!P~a%i+CSI7pVxVKA~ijV_#BZ?@j*ShYDuTD0#j5fD@#BkU+ P$TU$!dg*9*FLVFd2&@)M literal 0 HcmV?d00001 From 352f21e170334a3fb50c2d9252ed45d1540ddd71 Mon Sep 17 00:00:00 2001 From: Segun Adebayo Date: Thu, 21 May 2026 12:15:27 +0100 Subject: [PATCH 3/6] fix(dismissable): guard null document.body in pointer-blocking cleanup Closes #3133 --- .../dismissable-pointer-event-null-body.md | 5 +++++ .../dismissable/src/pointer-event-outside.ts | 16 ++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 .changeset/dismissable-pointer-event-null-body.md diff --git a/.changeset/dismissable-pointer-event-null-body.md b/.changeset/dismissable-pointer-event-null-body.md new file mode 100644 index 0000000000..3777bfe50a --- /dev/null +++ b/.changeset/dismissable-pointer-event-null-body.md @@ -0,0 +1,5 @@ +--- +"@zag-js/dismissable": patch +--- + +Fix crash (`Cannot read properties of null (reading 'style')`) when a pointer-blocking dialog or popover closes during SPA route teardown. diff --git a/packages/utilities/dismissable/src/pointer-event-outside.ts b/packages/utilities/dismissable/src/pointer-event-outside.ts index 161965d184..ea4d14bff6 100644 --- a/packages/utilities/dismissable/src/pointer-event-outside.ts +++ b/packages/utilities/dismissable/src/pointer-event-outside.ts @@ -19,10 +19,12 @@ export function disablePointerEventsOutside(node: HTMLElement, persistentElement const cleanups: VoidFunction[] = [] if (layerStack.hasPointerBlockingLayer() && !doc.body.hasAttribute("data-inert")) { - originalBodyPointerEvents = document.body.style.pointerEvents + originalBodyPointerEvents = doc.body.style.pointerEvents queueMicrotask(() => { - doc.body.style.pointerEvents = "none" - doc.body.setAttribute("data-inert", "") + const body = doc.body + if (!body) return + body.style.pointerEvents = "none" + body.setAttribute("data-inert", "") }) } @@ -41,9 +43,11 @@ export function disablePointerEventsOutside(node: HTMLElement, persistentElement return () => { if (layerStack.hasPointerBlockingLayer()) return queueMicrotask(() => { - doc.body.style.pointerEvents = originalBodyPointerEvents - doc.body.removeAttribute("data-inert") - if (doc.body.style.length === 0) doc.body.removeAttribute("style") + const body = doc.body + if (!body) return + body.style.pointerEvents = originalBodyPointerEvents + body.removeAttribute("data-inert") + if (body.style.length === 0) body.removeAttribute("style") }) cleanups.forEach((fn) => fn()) } From c3b1fa5c23fd6f70841abc964418024da894b8ab Mon Sep 17 00:00:00 2001 From: Segun Adebayo Date: Thu, 21 May 2026 12:23:45 +0100 Subject: [PATCH 4/6] docs(image-cropper): move demo image to public root --- website/demos/image-cropper.tsx | 2 +- website/public/{static => }/image-cropper-demo.jpg | Bin 2 files changed, 1 insertion(+), 1 deletion(-) rename website/public/{static => }/image-cropper-demo.jpg (100%) diff --git a/website/demos/image-cropper.tsx b/website/demos/image-cropper.tsx index 2ff5bf6988..5b1a10f31e 100644 --- a/website/demos/image-cropper.tsx +++ b/website/demos/image-cropper.tsx @@ -43,7 +43,7 @@ export function ImageCropper(props: ImageCropperProps) {
Dog to be cropped Date: Thu, 21 May 2026 13:53:26 +0200 Subject: [PATCH 5/6] fix: handle custom trigger elements correctly (#3129) --- packages/machines/dialog/src/dialog.dom.ts | 5 ++++- packages/machines/dialog/src/dialog.machine.ts | 2 +- packages/machines/drawer/src/drawer.machine.ts | 2 +- packages/machines/hover-card/src/hover-card.dom.ts | 5 ++++- packages/machines/hover-card/src/hover-card.machine.ts | 2 +- packages/machines/popover/src/popover.dom.ts | 7 ++++++- packages/machines/popover/src/popover.machine.ts | 2 +- packages/machines/tooltip/src/tooltip.dom.ts | 5 ++++- 8 files changed, 22 insertions(+), 8 deletions(-) diff --git a/packages/machines/dialog/src/dialog.dom.ts b/packages/machines/dialog/src/dialog.dom.ts index 95fd958769..8eb83328aa 100644 --- a/packages/machines/dialog/src/dialog.dom.ts +++ b/packages/machines/dialog/src/dialog.dom.ts @@ -28,5 +28,8 @@ export const getTriggerEls = (ctx: Scope) => queryAll(ctx.getRootNode(), `[data-scope="dialog"][data-part="trigger"][data-ownedby="${ctx.id}"]`) export const getActiveTriggerEl = (ctx: Scope, value: string | null): HTMLElement | null => { - return value == null ? getTriggerEls(ctx)[0] : ctx.getById(getTriggerId(ctx, value)) + if (value == null) { + return getTriggerEl(ctx) ?? getTriggerEls(ctx)[0] + } + return ctx.getById(getTriggerId(ctx, value)) } diff --git a/packages/machines/dialog/src/dialog.machine.ts b/packages/machines/dialog/src/dialog.machine.ts index c82bc3a7d6..79a5c7adec 100644 --- a/packages/machines/dialog/src/dialog.machine.ts +++ b/packages/machines/dialog/src/dialog.machine.ts @@ -133,7 +133,7 @@ export const machine = createMachine({ defer: true, pointerBlocking: prop("modal"), layerStyleTargets: [() => dom.getBackdropEl(scope), () => dom.getPositionerEl(scope)], - exclude: dom.getTriggerEls(scope), + exclude: [dom.getTriggerEl(scope), ...dom.getTriggerEls(scope)].filter(Boolean) as HTMLElement[], onInteractOutside(event) { prop("onInteractOutside")?.(event) if (!prop("closeOnInteractOutside")) { diff --git a/packages/machines/drawer/src/drawer.machine.ts b/packages/machines/drawer/src/drawer.machine.ts index 364cd07a07..88beaf4d97 100644 --- a/packages/machines/drawer/src/drawer.machine.ts +++ b/packages/machines/drawer/src/drawer.machine.ts @@ -689,7 +689,7 @@ export const machine = createMachine({ defer: true, pointerBlocking: prop("modal"), layerStyleTargets: [() => dom.getBackdropEl(scope), () => dom.getPositionerEl(scope)], - exclude: [dom.getTriggerEl(scope)], + exclude: [dom.getTriggerEl(scope), ...dom.getTriggerEls(scope)].filter(Boolean) as HTMLElement[], onInteractOutside(event) { prop("onInteractOutside")?.(event) if (!prop("closeOnInteractOutside")) { diff --git a/packages/machines/hover-card/src/hover-card.dom.ts b/packages/machines/hover-card/src/hover-card.dom.ts index 520b5f5519..b0e6d325c5 100644 --- a/packages/machines/hover-card/src/hover-card.dom.ts +++ b/packages/machines/hover-card/src/hover-card.dom.ts @@ -22,5 +22,8 @@ export const getTriggerEls = (scope: Scope): HTMLElement[] => ) export const getActiveTriggerEl = (scope: Scope, value: string | null): HTMLElement | null => { - return value == null ? getTriggerEls(scope)[0] : scope.getById(getTriggerId(scope, value)) + if (value == null) { + return getTriggerEl(scope) ?? getTriggerEls(scope)[0] + } + return scope.getById(getTriggerId(scope, value)) } diff --git a/packages/machines/hover-card/src/hover-card.machine.ts b/packages/machines/hover-card/src/hover-card.machine.ts index 24be139b39..61f310709d 100644 --- a/packages/machines/hover-card/src/hover-card.machine.ts +++ b/packages/machines/hover-card/src/hover-card.machine.ts @@ -271,7 +271,7 @@ export const machine = createMachine({ return trackDismissableElement(getContentEl, { type: "popover", defer: true, - exclude: dom.getTriggerEls(scope), + exclude: [dom.getTriggerEl(scope), ...dom.getTriggerEls(scope)].filter(Boolean) as HTMLElement[], onDismiss() { send({ type: "CLOSE", src: "interact-outside" }) }, diff --git a/packages/machines/popover/src/popover.dom.ts b/packages/machines/popover/src/popover.dom.ts index 39b78b8218..411ef450f1 100644 --- a/packages/machines/popover/src/popover.dom.ts +++ b/packages/machines/popover/src/popover.dom.ts @@ -18,11 +18,16 @@ export const getCloseTriggerId = (scope: Scope) => scope.ids?.closeTrigger ?? `p export const getAnchorEl = (scope: Scope) => scope.getById(getAnchorId(scope)) +export const getTriggerEl = (scope: Scope) => scope.getById(getTriggerId(scope)) + export const getTriggerEls = (scope: Scope): HTMLElement[] => queryAll(scope.getRootNode(), `[data-scope="popover"][data-part="trigger"][data-ownedby="${scope.id}"]`) export const getActiveTriggerEl = (scope: Scope, value: string | null): HTMLElement | null => { - return value == null ? getTriggerEls(scope)[0] : scope.getById(getTriggerId(scope, value)) + if (value == null) { + return getTriggerEl(scope) ?? getTriggerEls(scope)[0] + } + return scope.getById(getTriggerId(scope, value)) } export const getContentEl = (scope: Scope) => scope.getById(getContentId(scope)) export const getPositionerEl = (scope: Scope) => scope.getById(getPositionerId(scope)) diff --git a/packages/machines/popover/src/popover.machine.ts b/packages/machines/popover/src/popover.machine.ts index dbe5913b60..0b91fa4b80 100644 --- a/packages/machines/popover/src/popover.machine.ts +++ b/packages/machines/popover/src/popover.machine.ts @@ -169,7 +169,7 @@ export const machine = createMachine({ return trackDismissableElement(getContentEl, { type: "popover", pointerBlocking: prop("modal"), - exclude: dom.getTriggerEls(scope), + exclude: [dom.getTriggerEl(scope), ...dom.getTriggerEls(scope)].filter(Boolean) as HTMLElement[], defer: true, onEscapeKeyDown(event) { prop("onEscapeKeyDown")?.(event) diff --git a/packages/machines/tooltip/src/tooltip.dom.ts b/packages/machines/tooltip/src/tooltip.dom.ts index d6ad7e3df9..d1c19e03d1 100644 --- a/packages/machines/tooltip/src/tooltip.dom.ts +++ b/packages/machines/tooltip/src/tooltip.dom.ts @@ -26,5 +26,8 @@ export const getTriggerEls = (scope: Scope): HTMLElement[] => queryAll(scope.getRootNode(), `[data-scope="tooltip"][data-part="trigger"][data-ownedby="${scope.id}"]`) export const getActiveTriggerEl = (scope: Scope, value: string | null): HTMLElement | null => { - return value == null ? getTriggerEls(scope)[0] : scope.getById(getTriggerId(scope, value)) + if (value == null) { + return getTriggerEl(scope) ?? getTriggerEls(scope)[0] + } + return scope.getById(getTriggerId(scope, value)) } From de7524cfc27c2c183345ee019c547eefda2771f1 Mon Sep 17 00:00:00 2001 From: Segun Adebayo Date: Thu, 21 May 2026 12:56:54 +0100 Subject: [PATCH 6/6] chore: add changeset for custom trigger id fix (#3129) --- .changeset/custom-trigger-id-handling.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .changeset/custom-trigger-id-handling.md diff --git a/.changeset/custom-trigger-id-handling.md b/.changeset/custom-trigger-id-handling.md new file mode 100644 index 0000000000..61da93f4fa --- /dev/null +++ b/.changeset/custom-trigger-id-handling.md @@ -0,0 +1,9 @@ +--- +"@zag-js/popover": patch +"@zag-js/dialog": patch +"@zag-js/drawer": patch +"@zag-js/hover-card": patch +"@zag-js/tooltip": patch +--- + +Fix custom trigger elements (via `ids.trigger`) being ignored when shared across components (e.g. wrapping a `Popover.Trigger` in a `Tooltip` with the same id), causing broken positioning and a close-then-reopen cycle on trigger clicks.