Skip to content

Commit e3806f8

Browse files
committed
Fix note lookup layout on iPhone
1 parent 016158d commit e3806f8

8 files changed

Lines changed: 129 additions & 7 deletions

File tree

NoteLookupModal.svelte

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,13 @@
443443
border-radius: var(--input-radius);
444444
box-shadow: var(--input-shadow);
445445
}
446+
.tag-input-shell {
447+
width: 100%;
448+
max-width: 100%;
449+
min-width: 0;
450+
overflow: hidden;
451+
border-radius: var(--radius-s);
452+
}
446453
.tag-input-shell:focus-within,
447454
.note-input:focus {
448455
border-color: var(--interactive-accent);
@@ -460,6 +467,9 @@
460467
align-items: center;
461468
flex-wrap: wrap;
462469
gap: 0.3rem;
470+
width: 100%;
471+
max-width: 100%;
472+
min-width: 0;
463473
min-height: 2.25rem;
464474
padding: 0.3rem 0.45rem;
465475
}
@@ -493,9 +503,17 @@
493503
--pill-corner-shape: var(--tag-corner-shape);
494504
--pill-weight: var(--tag-weight);
495505
font-size: var(--tag-size);
506+
min-width: 0;
507+
max-width: 100%;
496508
height: auto;
497509
box-shadow: none;
498510
}
511+
.tag-chip .multi-select-pill-content {
512+
min-width: 0;
513+
overflow: hidden;
514+
text-overflow: ellipsis;
515+
white-space: nowrap;
516+
}
499517
.tag-chip.excluded {
500518
--pill-color: var(--text-error);
501519
--pill-color-hover: var(--text-error);
@@ -657,12 +675,23 @@
657675
height: calc(100dvh - 0.75rem);
658676
max-height: calc(100dvh - 0.75rem);
659677
}
678+
:global(body.is-phone .tagfolder-note-lookup-modal) {
679+
width: var(--dialog-width);
680+
max-width: var(--dialog-max-width);
681+
height: 100%;
682+
max-height: var(--dialog-max-height);
683+
margin-top: auto;
684+
}
660685
.note-lookup,
661686
.tag-field {
662687
gap: 0.3rem;
663688
}
664689
.tag-chips {
665690
padding: 0.2rem 0.3rem;
691+
max-height: min(calc(var(--input-height) * 1.5), 15dvh);
692+
overflow-x: hidden;
693+
overflow-y: auto;
694+
align-content: flex-start;
666695
}
667696
.note-input {
668697
padding: 0.4rem 0.55rem;

docs/note-lookup-design.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ Note
3737

3838
Selected tags are displayed as removable chips. Positive tags use the normal tag treatment; excluded tags use a visually distinct treatment and retain the leading minus sign. Tag completions appear as a popover over the content only while the tag input is focused and contains text, so they do not reduce the result list's height.
3939

40+
On phone-sized viewports, the selected-tag field is limited to approximately two compact chip rows or 15% of the available viewport height, whichever is smaller. This gives the note results priority. Additional chips can still scroll vertically inside the field for removal, rather than pushing the note field and results off screen. Long tag labels are truncated with an ellipsis, and the field does not scroll horizontally. The field uses Obsidian's compact radius while individual tags retain the standard pill shape.
41+
4042
The implementation uses Obsidian's `multi-select-pill`, `suggestion-container`, `suggestion-item`, and `is-selected` structures. Theme-owned variables define pill colours, borders, radii, suggestion selection, and mobile spacing. Plug-in CSS is limited to the two-field layout, compact result details, and the dashed excluded-tag state.
4143

4244
Each compact note row places the file name and parent path on its first line, followed by the positive-tag match count. An alternative title and the matching tags use a smaller second line when present. On narrow mobile viewports, matching tags are omitted from the row so that the file name, path, and more results remain visible. The result list takes all remaining dialogue height and scrolls independently.
@@ -45,6 +47,8 @@ The active note uses Obsidian's standard suggestion selection background. File d
4547

4648
The result list is not a third focus target. It has an active row, shown by the `` marker above, while DOM focus remains in one of the two inputs.
4749

50+
On iPhone, the dialogue uses Obsidian's phone dialogue width, maximum height, and safe-area variables. This keeps the title and standard Close control below the status area while preserving Obsidian's normal mobile touch target.
51+
4852
## Focus model
4953

5054
The dialogue opens with DOM focus in the tag input for both commands. The similar-note command preselects its highest-ranked result, so `Enter` can open that result immediately even though the preloaded tags remain editable.

manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"id": "obsidian-tagfolder",
33
"name": "TagFolder",
4-
"version": "0.18.19-beta.1",
4+
"version": "0.18.19-beta.2",
55
"minAppVersion": "1.7.2",
66
"description": "Show tags as folder.",
77
"author": "vorotamoroz",

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "obsidian-tagfolder",
3-
"version": "0.18.19-beta.1",
3+
"version": "0.18.19-beta.2",
44
"description": "Show tags as folder.",
55
"main": "main.js",
66
"type": "module",

test/e2e-obsidian/note-lookup.mts

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ async function seedLookupNotes(testSession: TagFolderTestSession): Promise<void>
2828
await vault.create("Beta.md", "#foo");
2929
await vault.create("Archived.md", "#bar #archive");
3030
await vault.create("This is an extremely long note filename for a narrow mobile screen.md", "#foo #bar");
31+
await vault.create(
32+
"Mobile tags.md",
33+
"#TypeScript #Obsidian #P2P #WebRTC #CRDT #Research #Project #Mobile #Testing #Keyboard #Responsive #Accessibility",
34+
);
3135
});
3236

3337
await page.waitForFunction(() => {
@@ -39,12 +43,82 @@ async function seedLookupNotes(testSession: TagFolderTestSession): Promise<void>
3943
};
4044
}
4145
).app;
42-
const file = obsidianApp?.vault?.getAbstractFileByPath("Alpha.md");
43-
return file != null && (obsidianApp?.metadataCache?.getFileCache(file)?.tags?.length ?? 0) >= 2;
46+
const alpha = obsidianApp?.vault?.getAbstractFileByPath("Alpha.md");
47+
const mobile = obsidianApp?.vault?.getAbstractFileByPath("Mobile tags.md");
48+
return alpha != null
49+
&& mobile != null
50+
&& (obsidianApp?.metadataCache?.getFileCache(alpha)?.tags?.length ?? 0) >= 2
51+
&& (obsidianApp?.metadataCache?.getFileCache(mobile)?.tags?.length ?? 0) >= 12;
4452
}, undefined, { timeout: 10_000 });
4553
});
4654
}
4755

56+
async function verifyPhoneLayout(testSession: TagFolderTestSession): Promise<void> {
57+
await withObsidianPage(testSession.session.remoteDebuggingPort, async (page) => {
58+
await page.setViewportSize({ width: 390, height: 430 });
59+
await page.evaluate(() => {
60+
document.body.classList.add("is-mobile", "is-phone");
61+
document.body.style.setProperty("--safe-area-inset-top", "47px");
62+
document.body.style.setProperty("--safe-area-inset-right", "0px");
63+
document.body.style.setProperty("--safe-area-inset-bottom", "34px");
64+
document.body.style.setProperty("--safe-area-inset-left", "0px");
65+
});
66+
67+
try {
68+
await executeCommand(page, OPEN_BY_TAGS_COMMAND);
69+
const modal = page.locator(".tagfolder-note-lookup-modal");
70+
await modal.waitFor({ state: "visible", timeout: 10_000 });
71+
const tagInput = modal.locator("#tagfolder-note-lookup-tag-input");
72+
for (const tag of [
73+
"TypeScript", "Obsidian", "P2P", "WebRTC", "CRDT", "Research",
74+
"Project", "Mobile", "Testing", "Keyboard", "Responsive", "Accessibility",
75+
]) {
76+
await tagInput.fill(tag);
77+
await tagInput.press("Enter");
78+
}
79+
80+
const layout = await modal.evaluate((element) => {
81+
const close = element.querySelector<HTMLElement>(".modal-close-button");
82+
const tagShell = element.querySelector<HTMLElement>(".tag-input-shell");
83+
const tagChips = element.querySelector<HTMLElement>(".tag-chips");
84+
const noteInput = element.querySelector<HTMLElement>("#tagfolder-note-lookup-note-input");
85+
if (!close || !tagShell || !tagChips || !noteInput) throw new Error("Phone layout elements were unavailable");
86+
const modalRect = element.getBoundingClientRect();
87+
const closeRect = close.getBoundingClientRect();
88+
const shellRect = tagShell.getBoundingClientRect();
89+
const noteRect = noteInput.getBoundingClientRect();
90+
const shellStyle = getComputedStyle(tagShell);
91+
const inputHeight = Number.parseFloat(shellStyle.getPropertyValue("--input-height"));
92+
const shellRadius = Number.parseFloat(shellStyle.borderTopLeftRadius);
93+
const safeAreaTop = 47;
94+
return {
95+
modalClearsSafeArea: modalRect.top >= safeAreaTop - 1,
96+
closeClearsSafeArea: closeRect.top >= safeAreaTop,
97+
closeHasTouchTarget: closeRect.width >= 44 && closeRect.height >= 44,
98+
tagsStayInsideModal: shellRect.left >= modalRect.left && shellRect.right <= modalRect.right,
99+
tagsDoNotScrollHorizontally: tagShell.scrollWidth <= tagShell.clientWidth,
100+
tagAreaPrioritisesNotes: tagChips.clientHeight <= Math.min(inputHeight * 1.5, innerHeight * 0.15) + 1,
101+
tagFieldUsesCompactRadius: shellRadius <= 12,
102+
noteInputRemainsVisible: noteRect.bottom <= innerHeight,
103+
};
104+
});
105+
if (!Object.values(layout).every(Boolean)) {
106+
throw new Error(`Phone layout did not respect safe areas and tag bounds: ${JSON.stringify(layout)}`);
107+
}
108+
await tagInput.press("Escape");
109+
} finally {
110+
await page.evaluate(() => {
111+
document.body.classList.remove("is-mobile", "is-phone");
112+
for (const property of [
113+
"--safe-area-inset-top", "--safe-area-inset-right",
114+
"--safe-area-inset-bottom", "--safe-area-inset-left",
115+
]) document.body.style.removeProperty(property);
116+
});
117+
await page.setViewportSize({ width: 1280, height: 960 });
118+
}
119+
});
120+
}
121+
48122
async function executeCommand(page: Page, commandId: string) {
49123
const executed = await page.evaluate((id) => {
50124
const obsidianApp = (
@@ -173,6 +247,7 @@ async function main(): Promise<void> {
173247
testSession = await startTagFolderTestSession();
174248
await seedLookupNotes(testSession);
175249
await verifyLookupWorkflow(testSession);
250+
await verifyPhoneLayout(testSession);
176251
console.log("TagFolder note lookup passed in real Obsidian");
177252
} finally {
178253
if (testSession) await stopTagFolderTestSession(testSession);

updates.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
## 0.18.19-beta.2
2+
3+
14th July, 2026
4+
5+
I have finally implemented a feature I have wanted for a very long time. I hope you will all enjoy it too.
6+
7+
### Fixes
8+
9+
- Fixed the note lookup dialogue on iPhone so that its title and Close control remain below the status area.
10+
- Limited the selected-tag field on phones to two rows, with additional tags scrolling inside the field, so that the note filter and results receive more space.
11+
- Reduced the selected-tag field's corner radius on phones while retaining Obsidian's standard pill treatment for individual tags.
12+
- Truncated long tag labels and prevented the selected-tag field from scrolling horizontally on narrow screens.
13+
114
## 0.18.19-beta.1
215

316
14th July, 2026

versions.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"0.18.14": "1.7.2",
33
"0.18.18": "1.7.2",
4-
"0.18.19-beta.1": "1.7.2"
4+
"0.18.19-beta.1": "1.7.2",
5+
"0.18.19-beta.2": "1.7.2"
56
}

0 commit comments

Comments
 (0)