Skip to content

Commit d9ce3a4

Browse files
committed
fix: improve modal focus with dual autofocus strategy
1 parent b1a5d77 commit d9ce3a4

File tree

12 files changed

+154
-4
lines changed

12 files changed

+154
-4
lines changed

src/lib/components/billing/validateCreditModal.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
placeholder="Promo code"
5959
id="code"
6060
label="Add promo code"
61+
autofocus
6162
bind:value={coupon} />
6263

6364
<svelte:fragment slot="footer">

src/lib/components/feedback/evaluation.svelte

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
<script lang="ts">
2+
import { onMount } from 'svelte';
23
import { Tag } from '@appwrite.io/pink-svelte';
34
45
export let value: number = null;
6+
7+
onMount(() => {
8+
setTimeout(() => {
9+
const firstButton = document.querySelector('[data-rating="0"]') as HTMLElement;
10+
if (firstButton) {
11+
firstButton.focus();
12+
}
13+
}, 150);
14+
});
515
</script>
616

717
<fieldset class="u-margin-block-start-8 u-min-width-0">
@@ -10,7 +20,12 @@
1020
style="padding-block: 0.13rem">
1121
{#each Array(11) as _, i}
1222
<li>
13-
<Tag size="m" selected={value === i} on:click={() => (value = i)}>
23+
<Tag
24+
size="m"
25+
selected={value === i}
26+
autofocus={i === 0}
27+
data-rating={i}
28+
on:click={() => (value = i)}>
1429
{i}
1530
</Tag>
1631
</li>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
11
<script lang="ts">
2+
import { onMount } from 'svelte';
23
import { InputTextarea } from '$lib/elements/forms';
34
import { feedbackData } from '$lib/stores/feedback';
5+
6+
onMount(() => {
7+
setTimeout(() => {
8+
const textarea = document.getElementById('feedback') as HTMLTextAreaElement;
9+
if (textarea) {
10+
textarea.focus();
11+
}
12+
}, 150);
13+
});
414
</script>
515

616
<InputTextarea
717
required
818
id="feedback"
19+
autofocus
920
bind:value={$feedbackData.message}
1021
label="Tell us more about your experience"
1122
placeholder="Share your suggestions and feature requests..." />

src/lib/components/modal.svelte

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
};
1515
export let title = '';
1616
export let hideFooter = false;
17+
export let autoFocus = true;
1718
1819
let alert: HTMLElement;
1920
@@ -26,6 +27,44 @@
2627
$: if (error) {
2728
alert?.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'nearest' });
2829
}
30+
31+
$: if (show && autoFocus) {
32+
setTimeout(() => {
33+
focusFirstElement();
34+
}, 100);
35+
}
36+
37+
function focusFirstElement() {
38+
const modalDialog = document.querySelector('dialog[open]') as HTMLElement;
39+
if (!modalDialog) return;
40+
41+
const autofocusElement = modalDialog.querySelector('[autofocus]') as HTMLElement;
42+
if (autofocusElement) {
43+
autofocusElement.focus();
44+
return;
45+
}
46+
const activeElement = document.activeElement;
47+
if (activeElement && modalDialog.contains(activeElement)) {
48+
return;
49+
}
50+
const focusableSelectors = [
51+
'input:not([disabled]):not([readonly]):not([type="hidden"])',
52+
'textarea:not([disabled]):not([readonly])',
53+
'select:not([disabled])',
54+
'button:not([disabled])',
55+
'a[href]:not([disabled])',
56+
'[tabindex]:not([tabindex="-1"]):not([disabled])',
57+
'.card-selector:not([disabled])',
58+
'[role="button"]:not([disabled])',
59+
'[role="link"]:not([disabled])',
60+
'[contenteditable="true"]:not([disabled])'
61+
].join(', ');
62+
63+
const firstFocusable = modalDialog.querySelector(focusableSelectors) as HTMLElement;
64+
if (firstFocusable) {
65+
firstFocusable.focus();
66+
}
67+
}
2968
</script>
3069

3170
<Form isModal {onSubmit}>

src/lib/layout/createProject.svelte

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script lang="ts">
2+
import { onMount } from 'svelte';
23
import { Layout, Typography, Input, Tag, Icon } from '@appwrite.io/pink-svelte';
34
import { IconPencil } from '@appwrite.io/pink-icons-svelte';
45
import { CustomId } from '$lib/components/index.js';
@@ -14,6 +15,30 @@
1415
export let showTitle = true;
1516
1617
let showCustomId = false;
18+
19+
onMount(() => {
20+
setTimeout(() => {
21+
let nameInput = document.querySelector(
22+
'input[placeholder="Project name"]'
23+
) as HTMLInputElement;
24+
if (!nameInput) {
25+
nameInput = document.querySelector(
26+
'input[type="text"]:first-of-type'
27+
) as HTMLInputElement;
28+
}
29+
if (!nameInput) {
30+
nameInput = document.querySelector('input:first-of-type') as HTMLInputElement;
31+
}
32+
33+
console.log('CreateProject - Found input:', nameInput);
34+
if (nameInput) {
35+
nameInput.focus();
36+
console.log('CreateProject - Focused project name input');
37+
} else {
38+
console.log('CreateProject - Could not find project name input');
39+
}
40+
}, 150);
41+
});
1742
</script>
1843

1944
<svelte:head>
@@ -34,6 +59,7 @@
3459
label="Name"
3560
placeholder="Project name"
3661
required
62+
autofocus
3763
bind:value={projectName} />
3864
{#if !showCustomId}
3965
<div>

src/routes/(console)/onboarding/create-organization/+page.svelte

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script lang="ts">
2+
import { onMount } from 'svelte';
23
import { isCloud } from '$lib/system';
34
import { sdk } from '$lib/stores/sdk';
45
import { ID } from '@appwrite.io/console';
@@ -15,6 +16,29 @@
1516
let isLoading = false;
1617
let organizationName = 'Personal Projects';
1718
19+
onMount(() => {
20+
setTimeout(() => {
21+
let nameInput = document.querySelector(
22+
'input[placeholder="Personal Projects"]'
23+
) as HTMLInputElement;
24+
if (!nameInput) {
25+
nameInput = document.querySelector('input[type="text"]') as HTMLInputElement;
26+
}
27+
if (!nameInput) {
28+
nameInput = document.querySelector('input') as HTMLInputElement;
29+
}
30+
31+
console.log('Found input:', nameInput);
32+
if (nameInput) {
33+
nameInput.focus();
34+
nameInput.select();
35+
console.log('Focused organization name input');
36+
} else {
37+
console.log('Could not find organization name input');
38+
}
39+
}, 100);
40+
});
41+
1842
async function createOrganization() {
1943
isLoading = true;
2044
let organization = null;
@@ -66,6 +90,7 @@
6690

6791
<Input.Text
6892
required
93+
autofocus
6994
disabled={isLoading}
7095
label="Organization name"
7196
bind:value={organizationName}

src/routes/(console)/onboarding/create-project/+page.svelte

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script lang="ts">
2+
import { onMount } from 'svelte';
23
import { Card, Layout, Button } from '@appwrite.io/pink-svelte';
34
import { isCloud } from '$lib/system';
45
import { sdk } from '$lib/stores/sdk';
@@ -22,6 +23,29 @@
2223
2324
export let data;
2425
26+
onMount(() => {
27+
setTimeout(() => {
28+
let nameInput = document.querySelector(
29+
'input[placeholder="Project name"]'
30+
) as HTMLInputElement;
31+
if (!nameInput) {
32+
nameInput = document.querySelector('input[type="text"]') as HTMLInputElement;
33+
}
34+
if (!nameInput) {
35+
nameInput = document.querySelector('input') as HTMLInputElement;
36+
}
37+
38+
console.log('Found input:', nameInput);
39+
if (nameInput) {
40+
nameInput.focus();
41+
nameInput.select();
42+
console.log('Focused project name input');
43+
} else {
44+
console.log('Could not find project name input');
45+
}
46+
}, 100);
47+
});
48+
2549
function markOnboardingComplete() {
2650
const currentPrefs = data.accountPrefs ?? $user.prefs;
2751
@@ -102,7 +126,7 @@
102126
on:submit={createProject}>
103127
<svelte:fragment slot="submit">
104128
<Layout.Stack direction="row" justifyContent="flex-end">
105-
<Button.Button autofocus type="submit" variant="primary" size="s">
129+
<Button.Button type="submit" variant="primary" size="s">
106130
Create
107131
</Button.Button>
108132
</Layout.Stack>

src/routes/(console)/organization-[organization]/domains/add-domain/+page.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
name="domain"
5151
bind:value={domainName}
5252
required
53+
autofocus
5354
placeholder="example.com" />
5455

5556
<Divider />

src/routes/(console)/organization-[organization]/domains/domain-[domain]/createRecordModal.svelte

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,13 @@
8484
DNS records map domain names to IP addresses or other resources.
8585
</span>
8686
<Layout.Stack gap="l">
87-
<InputText id="name" label="Name" placeholder="subdomain" bind:value={name} required />
87+
<InputText
88+
id="name"
89+
label="Name"
90+
placeholder="subdomain"
91+
bind:value={name}
92+
required
93+
autofocus />
8894
<Layout.Stack gap="xs">
8995
<InputSelect options={recordTypes} bind:value={type} id="type" label="Type" required />
9096
<Input.Helper state="default">

src/routes/(console)/project-[region]-[project]/overview/platforms/createWeb.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ ${prefix}APPWRITE_ENDPOINT = "${sdk.forProject(page.params.region, page.params.p
235235
id="hostname"
236236
label="Hostname"
237237
placeholder="localhost"
238+
autofocus
238239
error={hostnameError && 'Please enter a valid hostname'}
239240
bind:value={hostname}>
240241
<Tooltip slot="info">

0 commit comments

Comments
 (0)