fix(template): Use `<div>` instead of `<main>` to support Vue3 apps mounting

Vue3 does not replace the element while mounting but only renders within
(replace inner HTML).
So it would result in two stacked `<main>` elements which is invalid and
an accessibility issue.
Instead we just use a `<div>` element for mounting.

For Vue2 apps this does not change anything as the whole element will be
replaced with a new `<main>` element.
For vanilla apps this will slightly decrease the accessibility as the
main landmark is now missing, but this is not a hard accessibility issue
as it would be for Vue3 apps having two main elements.

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
pull/50127/head
Ferdinand Thiessen 2025-01-10 18:17:22 +07:00
parent dffab2221d
commit 182acb1e29
No known key found for this signature in database
GPG Key ID: 45FAE7268762B400
4 changed files with 9 additions and 8 deletions

@ -47,12 +47,12 @@ p($theme->getTitle());
</div> </div>
</header> </header>
<?php endif; ?> <?php endif; ?>
<main> <div>
<h1 class="hidden-visually"> <h1 class="hidden-visually">
<?php p($theme->getName()); ?> <?php p($theme->getName()); ?>
</h1> </h1>
<?php print_unescaped($_['content']); ?> <?php print_unescaped($_['content']); ?>
</main> </div>
</div> </div>
</div> </div>
<?php <?php

@ -80,7 +80,7 @@ p($theme->getTitle());
</div> </div>
</header> </header>
<main id="content" class="app-<?php p($_['appid']) ?>"> <div id="content" class="app-<?php p($_['appid']) ?>">
<h1 class="hidden-visually"> <h1 class="hidden-visually">
<?php <?php
if (isset($template) && $template->getHeaderTitle() !== '') { if (isset($template) && $template->getHeaderTitle() !== '') {
@ -90,7 +90,8 @@ p($theme->getTitle());
} ?> } ?>
</h1> </h1>
<?php print_unescaped($_['content']); ?> <?php print_unescaped($_['content']); ?>
</main> </div>
<?php if (isset($template) && $template->getFooterVisible() && ($theme->getLongFooter() !== '' || $_['showSimpleSignUpLink'])) { ?> <?php if (isset($template) && $template->getFooterVisible() && ($theme->getLongFooter() !== '' || $_['showSimpleSignUpLink'])) { ?>
<footer> <footer>
<p><?php print_unescaped($theme->getLongFooter()); ?></p> <p><?php print_unescaped($theme->getLongFooter()); ?></p>

@ -81,7 +81,7 @@ p($theme->getTitle());
</div> </div>
</header> </header>
<main id="content" class="app-<?php p($_['appid']) ?>"> <div id="content" class="app-<?php p($_['appid']) ?>">
<h1 class="hidden-visually" id="page-heading-level-1"> <h1 class="hidden-visually" id="page-heading-level-1">
<?php p((!empty($_['application']) && !empty($_['pageTitle']) && $_['application'] != $_['pageTitle']) <?php p((!empty($_['application']) && !empty($_['pageTitle']) && $_['application'] != $_['pageTitle'])
? $_['application'] . ': ' . $_['pageTitle'] ? $_['application'] . ': ' . $_['pageTitle']
@ -89,7 +89,7 @@ p($theme->getTitle());
); ?> ); ?>
</h1> </h1>
<?php print_unescaped($_['content']); ?> <?php print_unescaped($_['content']); ?>
</main> </div>
<div id="profiler-toolbar"></div> <div id="profiler-toolbar"></div>
</body> </body>
</html> </html>

@ -122,7 +122,7 @@ describe('Accessibility of Nextcloud theming colors', () => {
// Unset background image and thus use background-color for testing blur background (images do not work with axe-core) // Unset background image and thus use background-color for testing blur background (images do not work with axe-core)
doc.body.style.backgroundImage = 'unset' doc.body.style.backgroundImage = 'unset'
const root = doc.querySelector('main') const root = doc.querySelector('#content')
if (root === null) { if (root === null) {
throw new Error('No test root found') throw new Error('No test root found')
} }
@ -137,7 +137,7 @@ describe('Accessibility of Nextcloud theming colors', () => {
it(`color contrast of ${foreground} on ${background}`, () => { it(`color contrast of ${foreground} on ${background}`, () => {
cy.document().then(doc => { cy.document().then(doc => {
const element = createTestCase(foreground, background) const element = createTestCase(foreground, background)
const root = doc.querySelector('main') const root = doc.querySelector('#content')
// eslint-disable-next-line no-unused-expressions // eslint-disable-next-line no-unused-expressions
expect(root).not.to.be.undefined expect(root).not.to.be.undefined
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion