Skip to content

Commit

Permalink
Add autofocus and x-autofocus support (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
imacrayon committed Mar 1, 2024
1 parent bb11bb5 commit b893380
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 25 deletions.
23 changes: 15 additions & 8 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ function Ajax(Alpine) {
let config = sendConfig.get(el) || {
followRedirects: globalConfig.followRedirects,
history: false,
focus: true,
}
config = Object.assign(config, options)

Expand Down Expand Up @@ -128,6 +129,7 @@ function parseModifiers(modifiers = []) {
return {
followRedirects,
history,
focus: !modifiers.includes('nofocus')
}
}

Expand Down Expand Up @@ -344,6 +346,7 @@ async function render(request, targets, el, config) {

let wrapper = document.createRange().createContextualFragment('<template>' + response.html + '</template>')
let fragment = wrapper.firstElementChild.content
let focused = !config.focus
let renders = targets.map(async target => {
let template = fragment.getElementById(target.id)
let strategy = mergeConfig.get(target)?.strategy || globalConfig.mergeStrategy
Expand All @@ -366,20 +369,24 @@ async function render(request, targets, el, config) {
let freshEl = await merge(strategy, target, template)

if (freshEl) {
freshEl.removeAttribute('aria-busy')
freshEl.dataset.source = response.url
freshEl.removeAttribute('aria-busy')
if (!focused) {
['[x-autofocus]', '[autofocus]'].forEach(selector => {
if (focused) return
let autofocus = freshEl.matches(selector) ? freshEl : freshEl.querySelector(selector)
if (autofocus) {
focusOn(autofocus)
focused = true
}
})
}
}

return freshEl
})

targets = await Promise.all(renders)
let focus = el.getAttribute('x-focus')
if (focus) {
focusOn(document.getElementById(focus))
}

return targets
return await Promise.all(renders)
}

async function merge(strategy, from, to) {
Expand Down
62 changes: 59 additions & 3 deletions tests/focus.cy.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,71 @@
import { test, html } from './utils'

test('focus is set with [x-focus] string',
html`<form x-init x-target id="replace" method="post" x-focus="toggle_button"><button aria-pressed="false">Like</button></form>`,
test('focus is maintained when merged content is morphed',
html`<form x-init x-target id="replace" method="post" x-merge="morph"><button aria-pressed="false">Like</button></form>`,
({ intercept, get, wait }) => {
intercept('POST', '/tests', {
statusCode: 200,
body: '<form x-target id="replace" method="post"><button id="toggle_button" aria-pressed="true">Unlike</button></form>'
body: '<form x-target id="replace" method="post"><button aria-pressed="true">Unlike</button></form>'
}).as('response')
get('button').focus().click()
wait('@response').then(() => {
get('button').should('have.focus')
})
}
)

test('focus is set with [autofocus]',
html`<form x-init x-target id="replace" method="post"><button>First</button><a href="#">Second</a></form>`,
({ intercept, get, wait }) => {
intercept('POST', '/tests', {
statusCode: 200,
body: '<form x-init x-target id="replace" method="post"><button>First</button><a href="#" autofocus>Second</a></form>'
}).as('response')
get('button').focus().click()
wait('@response').then(() => {
get('a').should('have.focus')
})
}
)

test('focus is ignored with the nofocus modifier',
html`<form x-init x-target.nofocus id="replace" method="post"><button>First</button><a href="#">Second</a></form>`,
({ intercept, get, wait }) => {
intercept('POST', '/tests', {
statusCode: 200,
body: '<form x-init x-target id="replace" method="post"><button>First</button><a href="#" autofocus>Second</a></form>'
}).as('response')
get('button').focus().click()
wait('@response').then(() => {
get('a').should('not.have.focus')
})
}
)

test('first listed target is focused when multiple [autofocus] are merged',
html`<a href="#" autofocus id="replace2">Second</a><a href="#" autofocus id="replace1">First</a><form x-init x-target="replace1 replace2" method="post"><button></button></form>`,
({ intercept, get, wait }) => {
intercept('POST', '/tests', {
statusCode: 200,
body: '<a href="#" autofocus id="replace2">Second Replaced</a><a href="#" autofocus id="replace1">First Replaced</a>'
}).as('response')
get('button').click()
wait('@response').then(() => {
get('#replace1').should('have.focus')
})
}
)

test('[x-autofocus] overrides [autofocus]',
html`<form x-init x-target id="replace" method="post"><button>First</button><a href="#">Second</a></form>`,
({ intercept, get, wait }) => {
intercept('POST', '/tests', {
statusCode: 200,
body: '<form x-init x-target id="replace" method="post"><button autofocus>First</button><a href="#" x-autofocus>Second</a></form>'
}).as('response')
get('button').focus().click()
wait('@response').then(() => {
get('a').should('have.focus')
})
}
)
14 changes: 0 additions & 14 deletions tests/merge.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,20 +75,6 @@ test('content is merged after',
}
)

test('focus is maintained when merged content is morphed',
html`<form x-init x-target id="replace" method="post" x-merge="morph"><button aria-pressed="false">Like</button></form>`,
({ intercept, get, wait }) => {
intercept('POST', '/tests', {
statusCode: 200,
body: '<form x-target id="replace" method="post"><button aria-pressed="true">Unlike</button></form>'
}).as('response')
get('button').focus().click()
wait('@response').then(() => {
get('button').should('have.focus')
})
}
)

test('table elements can be merged',
html`<table><tr id="row"><td>Replace</td></tr></table><form x-init x-target="row" method="post"><button></button></form>`,
({ intercept, get, wait }) => {
Expand Down

0 comments on commit b893380

Please sign in to comment.