From e99cb40458b8c97ae79b1ba79750b4f23a0cd10b Mon Sep 17 00:00:00 2001 From: Kjetil Date: Sat, 28 Mar 2026 02:13:10 +0100 Subject: [PATCH] fix(resizer): widen divider hit area on touch devices Increase the effective drag region for touch-capable devices so the mail/message divider is easier to grab on iOS while preserving the existing desktop grab width. #1452 Closes #1452 --- src/app/changelog/changes.ts | 559 ++++++++++++++++++- src/app/directives/resizer.directive.spec.ts | 64 +++ src/app/directives/resizer.directive.ts | 19 +- 3 files changed, 636 insertions(+), 6 deletions(-) create mode 100644 src/app/directives/resizer.directive.spec.ts diff --git a/src/app/changelog/changes.ts b/src/app/changelog/changes.ts index 125deeb28..fca4d82da 100644 --- a/src/app/changelog/changes.ts +++ b/src/app/changelog/changes.ts @@ -71,11 +71,564 @@ export class ChangelogEntry { // BEGIN:AUTOGENERATED const changes = [ [ - "2b05c475", - "1747382829", + "e42d70ad", + "1772635017", + "fix", + "tests", + "Format date according to local TZ in tests" + ], + [ + "3e26a8f9", + "1772634692", + "test", + "draftdesk", + "check for double line break in draftdesk unit tests" + ], + [ + "13471db2", + "1772634692", + "fix", + "compose", + "add double line break at top of replies" + ], + [ + "ae85bb70", + "1771944794", + "refactor", + "cleanup", + "remove extra newline" + ], + [ + "7a82a35a", + "1770942956", + "fix", + "stability", + "revert removal of fonts" + ], + [ + "16279fab", + "1770875924", + "fix", + "stability", + "address castaway review comments for PR #1765" + ], + [ + "5efd36f2", + "1770215427", + "refactor", + "rxjs", + "remove unused take import in test" + ], + [ + "08d1476b", + "1770214776", + "refactor", + "cleanup", + "remove unused imports" + ], + [ + "aeed9e93", + "1770214767", + "refactor", + "rxjs", + "remove redundant pipe take 1s on firstValueFrom" + ], + [ + "995f055a", + "1770214759", + "refactor", + "cleanup", + "remove code that turned out to be unnecessary" + ], + [ + "1b5feaae", + "1770214753", + "fix", + "build", + "gen-env" + ], + [ + "a9730ff0", + "1770214747", + "build", + "deps", + "update package lock json" + ], + [ + "47e1c3bd", + "1770214747", + "style", + "cleanup", + "remove obsolete CSS styles" + ], + [ + "796fecff", + "1770214746", + "refactor", + "cleanup", + "remove unused code and constants" + ], + [ + "3ea607c5", + "1770214746", + "fix", + "stability", + "gate service worker registration" + ], + [ + "6718dd69", + "1770214714", + "ci", + "tooling", + "update ESLint configuration" + ], + [ + "a38da338", + "1770214713", + "feat", + "migration", + "migrate to RxJS 7" + ], + [ + "99b6b322", + "1770214713", + "refactor", + "2fa", + "Change 2FA unlock code description" + ], + [ + "15d5b4f1", + "1769272608", + "style", + "account-upgrades", + "amend text regarding sub-accounts" + ], + [ + "9f4c2fb3", + "1769009210", + "refactor", + "2fa", + "Change 2FA unlock code description" + ], + [ + "f2d51985", + "1768489312", + "fix", + "cart", + "Fix add/remove cart item" + ], + [ + "3bc39da0", + "1768480595", + "feat", + "upgrades", + "Show recommended plans with addons if needed to downgrade" + ], + [ + "eaf16ee2", + "1768388642", + "refactor", + "upgrades", + "Ensure currency calculations are correct" + ], + [ + "1b76a7d4", + "1767483281", + "fix", + "mailviewer", + "intercept `mailto:` links and open a compose window" + ], + [ + "b52c415a", + "1765455095", + "fix", + "notifications", + "Check Notifcation API exists when showing button" + ], + [ + "ad665a71", + "1764772951", + "fix", + "plans", + "Enable downgrade to Micro including Email Hosting" + ], + [ + "840522fb", + "1764245620", + "style", + "payment", + "Change \"pending\" to \"incomplete\"." + ], + [ + "9ada126e", + "1764175688", + "fix", + "stripe", + "Fail early if stripe submission errors" + ], + [ + "757de0ed", + "1762962071", + "fix", + "stripe", + "Improve on-page status updates for Stripe payments" + ], + [ + "35cb13aa", + "1761061904", + "fix", + "notification", + "notification permission on click" + ], + [ + "524e9134", + "1760020202", + "style", + "payment", + "Correct image source URLs." + ], + [ + "42c6b9d2", + "1759989960", + "style", + "payment", + "Move shopping cart style back to main stylesheet for it to work." + ], + [ + "2154f5a8", + "1759989960", + "style", + "payment", + "Include missing SCSS file." + ], + [ + "07f415a6", + "1759989960", + "style", + "payment", + "Add Apple Pay logo and imrpove formatting." + ], + [ + "09968dc8", + "1759916636", + "feat", + "dkim", + "Display all users domains" + ], + [ + "a6316817", + "1759692383", + "style", + "payment", + "Minor textual correction." + ], + [ + "89bf44f6", + "1759692157", + "style", + "payment", + "Add cryptocurrency logos." + ], + [ + "2cc33c58", + "1759689457", + "style", + "payment", + "Bundle Stripe and PayPal, and improve layout." + ], + [ + "89de7403", + "1759202407", + "style", + "payment", + "Add note about number of email aliases on sub-accounts." + ], + [ + "a04b4700", + "1759144876", + "fix", + "mailviewer", + "Show images with ngsw bypass" + ], + [ + "0eb7556f", + "1757937300", + "fix", + "2fa", + "Show QR codes for 2FA generation" + ], + [ + "440fbdfc", + "1756825896", + "fix", + "sentry", + "Downgrade client to make it work with sentry server" + ], + [ + "10e135be", + "1756794090", + "style", + "payment", + "Fix typo." + ], + [ + "7ebeca44", + "1756335277", + "style", + "settings", + "Moved and updated link to documentation." + ], + [ + "a1faab26", + "1756305250", + "fix", + "paymentcards", + "Make add card dialog fit on screen" + ], + [ + "50b8e5db", + "1755783796", "feat", "worker", - "Add sentry to xapian worker scope" + "Improve Sentry integration" + ], + [ + "db8ccdef", + "1754909871", + "fix", + "stripe", + "Allow dialog to scroll (make Submit reachable)" + ], + [ + "4bdcff06", + "1751829714", + "style", + "dkim", + "Add note about format." + ], + [ + "7d6ddd10", + "1751477343", + "fix", + "cart", + "Renewing a subscription now sets a quantity of 1" + ], + [ + "1eebaa8a", + "1751367740", + "fix", + "commit-lint", + "allow hyphen in commit subject" + ], + [ + "67c0a118", + "1751367409", + "fix", + "login", + "make login inputs required" + ], + [ + "29eb8869", + "1751367360", + "fix", + "side-menu", + "make buttons keyboard navigable" + ], + [ + "8d22da9f", + "1751363648", + "fix", + "upgrades", + "Highlight correct \"current sub\" in plans table" + ], + [ + "d40892f9", + "1751296588", + "build", + "lint", + "Remove typos from commit-lint tags" + ], + [ + "e7be3886", + "1751296588", + "fix", + "upgrades", + "Indicate which plans would put the user over quota" + ], + [ + "ea501342", + "1751291013", + "fix", + "subscriptions", + "Only warn about close-quota for Disk/File quotas" + ], + [ + "1a198352", + "1751277492", + "fix", + "upgrades", + "Prevent two subscriptions being bought at once" + ], + [ + "9e4d014c", + "1750951971", + "fix", + "mailviewer", + "Text/HTML views blank when attachment has odd types" + ], + [ + "290ebe7e", + "1750694678", + "fix", + "upgrades", + "Remove ability to buy plans when over usage" + ], + [ + "8b4ad27a", + "1750691421", + "fix", + "upgrades", + "Checks quota usage for plan upgrades table" + ], + [ + "77657d08", + "1750691117", + "build", + "lint", + "Fix allowed commit-message tags" + ], + [ + "19ee3d5f", + "1750410342", + "style", + "upgrades", + "Improve formatting of recommended plans and other fixes." + ], + [ + "e53115a7", + "1750354119", + "fix", + "upgrades", + "Compare user's quotas when displaying plans" + ], + [ + "6d14aa2b", + "1750088797", + "fix", + "renewals", + "Removes confusing Yes/No for renewal checkboxes" + ], + [ + "b2d769b7", + "1750084083", + "fix", + "mailviewer", + "Fixes attachment display with HTML (again)" + ], + [ + "4a4e08e2", + "1749740369", + "fix", + "mailviewer", + "Only show attachments that are not inline in HTML" + ], + [ + "1beabc35", + "1749571399", + "fix", + "folderlist", + "place dir expander button above the anchor overlay" + ], + [ + "1e5cee80", + "1749544633", + "fix", + "github", + "make checkout of fork work in commit lint workflow" + ], + [ + "8d0d3cde", + "1749227244", + "style", + "mail", + "Make Allow and Block Sender descriptions consistent." + ], + [ + "652e3748", + "1749031846", + "build", + "ci", + "Remove redundant cache action" + ], + [ + "9a0189bb", + "1749031810", + "build", + "ci", + "prevent improper commit formatting from being merged into master" + ], + [ + "6cfc7dd4", + "1748872985", + "fix", + "details", + "show alternative email resend on email change" + ], + [ + "0036ce78", + "1748517484", + "fix", + "payments", + "Handle post stripe 3d secure correctly" + ], + [ + "ea2c77ca", + "1747908332", + "fix", + "build", + "Remove unused dependencies (revert)" + ], + [ + "2ef0626f", + "1747734901", + "build", + "deps", + "Remove unused dependencies" + ], + [ + "183d42ad", + "1747729699", + "build", + "deps", + "Remove unused dependencies" + ], + [ + "90d46835", + "1747328794", + "fix", + "payments", + "Renew or create products after Stripe payment" + ], + [ + "9b2580c3", + "1747302470", + "fix", + "folderlist", + "Make folder buttons keyboard navigable" + ], + [ + "51cb8dfe", + "1747297115", + "feat", + "login", + "Add autcomplete attrs in accordance with WCAG guidelines" + ], + [ + "403f7367", + "1747296965", + "fix", + "login", + "Fix user not sent with login req even when field is filled" ], [ "111120bd", diff --git a/src/app/directives/resizer.directive.spec.ts b/src/app/directives/resizer.directive.spec.ts new file mode 100644 index 000000000..4ad5b6d19 --- /dev/null +++ b/src/app/directives/resizer.directive.spec.ts @@ -0,0 +1,64 @@ +// --------- BEGIN RUNBOX LICENSE --------- +// Copyright (C) 2016-2026 Runbox Solutions AS (runbox.com). +// +// This file is part of Runbox 7. +// +// Runbox 7 is free software: You can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the +// Free Software Foundation, either version 3 of the License, or (at your +// option) any later version. +// +// Runbox 7 is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Runbox 7. If not, see . +// ---------- END RUNBOX LICENSE ---------- + +import { Component } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ResizerDirective } from './resizer.directive'; + +@Component({ + template: '
' +}) +class HostComponent {} + +describe('ResizerDirective', () => { + let fixture: ComponentFixture; + let directive: ResizerDirective; + let hostElement: HTMLElement; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [HostComponent, ResizerDirective], + }).compileComponents(); + + fixture = TestBed.createComponent(HostComponent); + fixture.detectChanges(); + + hostElement = fixture.nativeElement.querySelector('div'); + directive = fixture.debugElement.children[0].injector.get(ResizerDirective); + spyOn(hostElement, 'getBoundingClientRect').and.returnValue({ + left: 0, + width: 100, + } as DOMRect); + }); + + it('keeps the desktop drag region at the configured grab width', () => { + spyOn(window, 'matchMedia').and.returnValue({ matches: false } as MediaQueryList); + spyOnProperty(window.navigator, 'maxTouchPoints', 'get').and.returnValue(0); + + expect(directive.inDragRegion({ clientX: 80 })).toBeFalse(); + expect(directive.inDragRegion({ clientX: 95 })).toBeTrue(); + }); + + it('widens the drag region on touch devices', () => { + spyOn(window, 'matchMedia').and.returnValue({ matches: true } as MediaQueryList); + + expect(directive.inDragRegion({ clientX: 80 })).toBeTrue(); + }); +}); diff --git a/src/app/directives/resizer.directive.ts b/src/app/directives/resizer.directive.ts index 01886d35c..69815d2f1 100644 --- a/src/app/directives/resizer.directive.ts +++ b/src/app/directives/resizer.directive.ts @@ -19,6 +19,8 @@ import { Directive, ElementRef, OnInit, Input, Renderer2 } from '@angular/core'; +const TOUCH_GRAB_WIDTH = 24; + @Directive({ selector: '[appResizable]' // Attribute selector }) @@ -95,7 +97,18 @@ export class ResizerDirective implements OnInit { ); } - ngOnInit(): void { +private isTouchDragSurface(): boolean { + return (window.matchMedia && window.matchMedia('(pointer: coarse)').matches) + || navigator.maxTouchPoints > 0; +} + +private get effectiveGrabWidth(): number { + return this.isTouchDragSurface() + ? Math.max(this.resizableGrabWidth, TOUCH_GRAB_WIDTH) + : this.resizableGrabWidth; +} + +ngOnInit(): void { if (this.position !== 'end') { this.el.nativeElement.style['border-right'] = this.resizableGrabWidth + 'px solid darkgrey'; } else { @@ -109,12 +122,12 @@ export class ResizerDirective implements OnInit { const rect = this.el.nativeElement.getBoundingClientRect(); const dragX1 = (rect.left ); - return evt.clientX < dragX1 + this.resizableGrabWidth; + return evt.clientX < dragX1 + this.effectiveGrabWidth; } else { const rect = this.el.nativeElement.getBoundingClientRect(); const dragX1 = (rect.left + rect.width); - return evt.clientX > dragX1 - this.resizableGrabWidth; + return evt.clientX > dragX1 - this.effectiveGrabWidth; } }