From b86e5831fa21664c89ed0ffff88dba324c0cab4c Mon Sep 17 00:00:00 2001 From: Christian Taylor Date: Tue, 13 Feb 2024 15:11:36 -0600 Subject: [PATCH] Add `autofocus` and `x-autofocus` support --- src/index.js | 23 ++++++++++++------ tests/focus.cy.js | 62 ++++++++++++++++++++++++++++++++++++++++++++--- tests/merge.cy.js | 14 ----------- 3 files changed, 74 insertions(+), 25 deletions(-) diff --git a/src/index.js b/src/index.js index a964847..103ece9 100644 --- a/src/index.js +++ b/src/index.js @@ -82,6 +82,7 @@ function Ajax(Alpine) { let config = sendConfig.get(el) || { followRedirects: globalConfig.followRedirects, history: false, + focus: true, } config = Object.assign(config, options) @@ -128,6 +129,7 @@ function parseModifiers(modifiers = []) { return { followRedirects, history, + focus: !modifiers.includes('nofocus') } } @@ -344,6 +346,7 @@ async function render(request, targets, el, config) { let wrapper = document.createRange().createContextualFragment('') 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 @@ -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) { diff --git a/tests/focus.cy.js b/tests/focus.cy.js index f669668..6c9272b 100644 --- a/tests/focus.cy.js +++ b/tests/focus.cy.js @@ -1,11 +1,11 @@ import { test, html } from './utils' -test('focus is set with [x-focus] string', - html`
`, +test('focus is maintained when merged content is morphed', + html`
`, ({ intercept, get, wait }) => { intercept('POST', '/tests', { statusCode: 200, - body: '
' + body: '
' }).as('response') get('button').focus().click() wait('@response').then(() => { @@ -13,3 +13,59 @@ test('focus is set with [x-focus] string', }) } ) + +test('focus is set with [autofocus]', + html`
Second
`, + ({ intercept, get, wait }) => { + intercept('POST', '/tests', { + statusCode: 200, + body: '
Second
' + }).as('response') + get('button').focus().click() + wait('@response').then(() => { + get('a').should('have.focus') + }) + } +) + +test('focus is ignored with the nofocus modifier', + html`
Second
`, + ({ intercept, get, wait }) => { + intercept('POST', '/tests', { + statusCode: 200, + body: '
Second
' + }).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`SecondFirst
`, + ({ intercept, get, wait }) => { + intercept('POST', '/tests', { + statusCode: 200, + body: 'Second ReplacedFirst Replaced' + }).as('response') + get('button').click() + wait('@response').then(() => { + get('#replace1').should('have.focus') + }) + } +) + +test('[x-autofocus] overrides [autofocus]', + html`
Second
`, + ({ intercept, get, wait }) => { + intercept('POST', '/tests', { + statusCode: 200, + body: '
Second
' + }).as('response') + get('button').focus().click() + wait('@response').then(() => { + get('a').should('have.focus') + }) + } +) diff --git a/tests/merge.cy.js b/tests/merge.cy.js index a282119..535a17f 100644 --- a/tests/merge.cy.js +++ b/tests/merge.cy.js @@ -75,20 +75,6 @@ test('content is merged after', } ) -test('focus is maintained when merged content is morphed', - html`
`, - ({ intercept, get, wait }) => { - intercept('POST', '/tests', { - statusCode: 200, - body: '
' - }).as('response') - get('button').focus().click() - wait('@response').then(() => { - get('button').should('have.focus') - }) - } -) - test('table elements can be merged', html`
Replace
`, ({ intercept, get, wait }) => {