feat: snap floating add button to edge
ci / gitleaks (push) Successful in 7s
ci / docker (push) Successful in 3m37s

This commit is contained in:
2026-09-11 17:07:27 +08:00
parent 78d178be77
commit e5cede1b98
5 changed files with 56 additions and 6 deletions
+7 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
import { archivePanelFlags, calendarModeLabel, changedHabitFields, clampFabPosition, countdownDayText, countdownKindLabel, dateKey, defaultView, formatArchivedAt, formatAuditAction, formatAuditEntity, formatHabitApiError, formatLocalShortDateTime, formatUserAgent, habitActionState, habitButtonNotice, habitButtonValue, habitWeek, invalidateHabitGridCache, isFabDrag, isHabitComplete, isHabitScheduledToday, isTaskView, nextHabitSwipeValue, nextTotalAfterLocalTaskAdd, nextTotalAfterLocalTaskRemoval, normalizeRequiredName, numericHabitInputValue, performHabitRestore, performTrashMutation, previousHabitSwipeValue, quickTaskFields, readCountdownCache, readHabitGridCache, readStoredBoolean, readStoredNavigation, shouldToggleRowSwipe, validateHabitForm, writeCountdownCache, writeHabitGridCache, writeStoredBoolean, writeStoredNavigation } from './mvp-utils'
import { archivePanelFlags, calendarModeLabel, changedHabitFields, clampFabPosition, countdownDayText, countdownKindLabel, dateKey, defaultView, formatArchivedAt, formatAuditAction, formatAuditEntity, formatHabitApiError, formatLocalShortDateTime, formatUserAgent, habitActionState, habitButtonNotice, habitButtonValue, habitWeek, invalidateHabitGridCache, isFabDrag, isHabitComplete, isHabitScheduledToday, isTaskView, nextHabitSwipeValue, nextTotalAfterLocalTaskAdd, nextTotalAfterLocalTaskRemoval, snapFabPosition, normalizeRequiredName, numericHabitInputValue, performHabitRestore, performTrashMutation, previousHabitSwipeValue, quickTaskFields, readCountdownCache, readHabitGridCache, readStoredBoolean, readStoredNavigation, shouldToggleRowSwipe, validateHabitForm, writeCountdownCache, writeHabitGridCache, writeStoredBoolean, writeStoredNavigation } from './mvp-utils'
describe('MVP view utilities', () => {
it('formats a local date as YYYY-MM-DD', () => {
@@ -181,6 +181,12 @@ describe('MVP view utilities', () => {
expect(clampFabPosition(120, 300, 390, 844)).toEqual({ x: 120, y: 300 })
})
it('snaps the add button to the nearest horizontal edge while preserving its safe vertical position', () => {
expect(snapFabPosition(40, 300, 390, 844)).toEqual({ x: 14, y: 300 })
expect(snapFabPosition(280, 300, 390, 844)).toEqual({ x: 324, y: 300 })
expect(snapFabPosition(180, 900, 390, 844)).toEqual({ x: 324, y: 718 })
})
it('validates safe habit input and emits only changed patch fields', () => {
expect(normalizeRequiredName(' 喝水 ')).toEqual({ value: '喝水', error: '' })
expect(normalizeRequiredName(' \n ')).toEqual({ value: '', error: '名称不能为空,请输入至少一个可见字符。' })
+8
View File
@@ -170,6 +170,14 @@ export function clampFabPosition(x: number, y: number, viewportWidth: number, vi
}
}
export function snapFabPosition(x: number, y: number, viewportWidth: number, viewportHeight: number, size = 52, margin = 14, bottomReserved = 74) {
const clamped = clampFabPosition(x, y, viewportWidth, viewportHeight, size, margin, bottomReserved)
const left = margin
const right = Math.max(margin, viewportWidth - size - margin)
const midpoint = viewportWidth / 2
return { x: clamped.x + size / 2 < midpoint ? left : right, y: clamped.y }
}
export function isFabDrag(deltaX: number, deltaY: number, threshold = 8) {
return Math.hypot(deltaX, deltaY) >= threshold
}