Skip to content
Open
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
7 changes: 4 additions & 3 deletions src/htmx.js
Original file line number Diff line number Diff line change
Expand Up @@ -736,12 +736,13 @@ var htmx = (() => {
let trigger = hxTrigger || (elt.matches("form") ? "submit" :
elt.matches("input:not([type=button]):not([type=submit]),select,textarea") ? "change" :
"click");
this.__onTrigger(elt, trigger, initialHandler)
this.__onTrigger(elt, trigger, initialHandler, true)
}

// Wire up event listeners with full modifier support (once, prevent, stop,
// delay, throttle, changed, capture, passive, from, filter, etc.)
__onTrigger(elt, specString, handler) {
// request: true for the element's own hx-* request trigger, which cancels the native action
__onTrigger(elt, specString, handler, request) {
let specs = this.__parseTriggerSpecs(specString)
this.__htmxProp(elt).triggerSpecs.push(...specs)

Expand Down Expand Up @@ -802,7 +803,7 @@ var htmx = (() => {

// Guarded: pre-timing checks that determine if event should proceed
spec.handler = (evt) => {
if (this.__shouldCancel(evt)) evt.preventDefault();
if (request && !this.__isModifierKeyClick(evt) && this.__shouldCancel(evt)) evt.preventDefault();
if (spec.from === 'self' && evt.target !== elt) return;
if (spec.from === 'outside' && elt.contains(evt.target)) return;
if (spec.target && !evt.target?.matches?.(spec.target)) return;
Expand Down
10 changes: 10 additions & 0 deletions test/tests/attributes/hx-on.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,16 @@ describe('hx-on attribute', function() {

// --- synthetic events in simple form ---

it('hx-on:submit on a plain form does not prevent the native submit', function() {
let defaultPrevented = null;
let form = createProcessedHTML('<form action="/native" method="post" hx-on:submit="window.foo = 1"><button>go</button></form>');
form.addEventListener('submit', (evt) => defaultPrevented = evt.defaultPrevented);
form.dispatchEvent(new SubmitEvent('submit', { bubbles: true, cancelable: true }));
window.foo.should.equal(1);
defaultPrevented.should.equal(false);
delete window.foo;
});

it('hx-on:load fires immediately when element is processed', function() {
window.foo = false;
createProcessedHTML('<div hx-on:load="window.foo = true">x</div>');
Expand Down
10 changes: 7 additions & 3 deletions test/tests/end2end/cancel-behavior.js
Original file line number Diff line number Diff line change
Expand Up @@ -266,12 +266,13 @@ describe('Cancel behavior integration tests', function() {
defaultPrevented = evt.defaultPrevented;
});

const evt = new MouseEvent('click', { ctrlKey: true, bubbles: true });
const evt = new MouseEvent('click', { ctrlKey: true, bubbles: true, cancelable: true });
link.dispatchEvent(evt);
await htmx.timeout(1);

// Should not prevent default to allow browser's native behavior
defaultPrevented.should.equal(false);
fetchMock.calls.length.should.equal(0);
});

it('meta+click on link does not prevent default (allows open in new tab on Mac)', async function() {
Expand All @@ -284,12 +285,13 @@ describe('Cancel behavior integration tests', function() {
defaultPrevented = evt.defaultPrevented;
});

const evt = new MouseEvent('click', { metaKey: true, bubbles: true });
const evt = new MouseEvent('click', { metaKey: true, bubbles: true, cancelable: true });
link.dispatchEvent(evt);
await htmx.timeout(1);

// Should not prevent default to allow browser's native behavior
defaultPrevented.should.equal(false);
fetchMock.calls.length.should.equal(0);
});

it('shift+click on link does not prevent default (allows open in new window)', async function() {
Expand All @@ -302,11 +304,13 @@ describe('Cancel behavior integration tests', function() {
defaultPrevented = evt.defaultPrevented;
});

const evt = new MouseEvent('click', { shiftKey: true, bubbles: true });
const evt = new MouseEvent('click', { shiftKey: true, bubbles: true, cancelable: true });
link.dispatchEvent(evt);
await htmx.timeout(1);

// Should not prevent default to allow browser's native behavior
defaultPrevented.should.equal(false);
fetchMock.calls.length.should.equal(0);
});

});