Skip to content

Commit bb1c5c3

Browse files
calebporzioclaude
andcommitted
fix(morph): close dialogs properly when removing open attribute
Morphing a `<dialog>` opened via `showModal()` would call `removeAttribute('open')` directly, leaving the dialog in the browser's top layer and making the page permanently inert. Now calls `dialog.close()` instead, which properly exits the top layer. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4674e39 commit bb1c5c3

File tree

2 files changed

+43
-2
lines changed

2 files changed

+43
-2
lines changed

packages/morph/src/morph.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,11 @@ function createMorphContext(options = {}) {
174174
let name = domAttributes[i].name;
175175

176176
if (! to.hasAttribute(name)) {
177-
// Remove attribute...
178-
from.removeAttribute(name)
177+
if (name === 'open' && from.nodeName === 'DIALOG' && from.open) {
178+
from.close()
179+
} else {
180+
from.removeAttribute(name)
181+
}
179182
}
180183
}
181184

tests/cypress/integration/plugins/morph.spec.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,3 +1061,41 @@ test('can ignore region between comment markers using skipUntil',
10611061
get('li:nth-of-type(3)').should(haveText('baz'))
10621062
},
10631063
)
1064+
1065+
test('morph properly closes dialog opened with showModal()',
1066+
[html`
1067+
<div x-data>
1068+
<dialog>
1069+
<p>Dialog content</p>
1070+
</dialog>
1071+
<button id="outside">Outside</button>
1072+
</div>
1073+
`],
1074+
({ get }, reload, window, document) => {
1075+
// Open the dialog with showModal() so it enters the top layer
1076+
get('dialog').then(([dialog]) => {
1077+
dialog.showModal()
1078+
expect(dialog.open).to.be.true
1079+
})
1080+
1081+
// Morph to a version without 'open' attribute (same as original)
1082+
get('div').then(([el]) => {
1083+
window.Alpine.morph(el, `
1084+
<div x-data>
1085+
<dialog>
1086+
<p>Dialog content</p>
1087+
</dialog>
1088+
<button id="outside">Outside</button>
1089+
</div>
1090+
`)
1091+
})
1092+
1093+
// Dialog should be closed
1094+
get('dialog').then(([dialog]) => {
1095+
expect(dialog.open).to.be.false
1096+
})
1097+
1098+
// Page should not be inert — outside button should be clickable
1099+
get('#outside').click()
1100+
},
1101+
)

0 commit comments

Comments
 (0)