Set min/max year based on platform limitations 1901...2100.
This commit is contained in:
parent
9a4d1c7196
commit
cb7a111020
@ -31,6 +31,8 @@ import {
|
||||
toLocalString,
|
||||
mondayIndex,
|
||||
DEFAULT_TZ,
|
||||
MIN_YEAR,
|
||||
MAX_YEAR,
|
||||
} from '@/utils/date'
|
||||
import { addDays } from 'date-fns'
|
||||
import WeekRow from './WeekRow.vue'
|
||||
@ -43,8 +45,6 @@ const minVirtualWeek = ref(0)
|
||||
const visibleWeeks = ref([])
|
||||
|
||||
const config = {
|
||||
min_year: 1900,
|
||||
max_year: 2100,
|
||||
weekend: getLocaleWeekendDays(),
|
||||
}
|
||||
|
||||
@ -116,7 +116,7 @@ const handleWheel = (e) => {
|
||||
const currentYear = calendarStore.viewYear
|
||||
const delta = Math.round(e.deltaY * (1 / 3))
|
||||
if (!delta) return
|
||||
const newYear = Math.max(config.min_year, Math.min(config.max_year, currentYear + delta))
|
||||
const newYear = Math.max(MIN_YEAR, Math.min(MAX_YEAR, currentYear + delta))
|
||||
if (newYear === currentYear) return
|
||||
|
||||
const topDisplayIndex = Math.floor(viewportEl.value.scrollTop / rowHeight.value)
|
||||
@ -156,8 +156,8 @@ const goToTodayHandler = () => {
|
||||
onMounted(() => {
|
||||
rowHeight.value = computeRowHeight()
|
||||
|
||||
const minYearDate = new Date(config.min_year, 0, 1)
|
||||
const maxYearLastDay = new Date(config.max_year, 11, 31)
|
||||
const minYearDate = new Date(MIN_YEAR, 0, 1)
|
||||
const maxYearLastDay = new Date(MAX_YEAR, 11, 31)
|
||||
const lastWeekMonday = addDays(maxYearLastDay, -mondayIndex(maxYearLastDay))
|
||||
|
||||
minVirtualWeek.value = getWeekIndex(minYearDate)
|
||||
|
@ -6,6 +6,8 @@ import {
|
||||
reorderByFirstDay,
|
||||
getISOWeek,
|
||||
getISOWeekYear,
|
||||
MIN_YEAR,
|
||||
MAX_YEAR,
|
||||
} from '@/utils/date'
|
||||
import Numeric from '@/components/Numeric.vue'
|
||||
import { addDays } from 'date-fns'
|
||||
@ -49,7 +51,7 @@ function isoWeekMonday(isoYear, isoWeek) {
|
||||
|
||||
function changeYear(y) {
|
||||
if (y == null) return
|
||||
y = Math.round(Math.max(calendarStore.minYear, Math.min(calendarStore.maxYear, y)))
|
||||
y = Math.round(Math.max(MIN_YEAR, Math.min(MAX_YEAR, y)))
|
||||
if (y === currentYear.value) return
|
||||
const vw = topVirtualWeek.value
|
||||
// Fraction within current row
|
||||
@ -94,8 +96,8 @@ const weekdayNames = computed(() => {
|
||||
<Numeric
|
||||
:model-value="currentYear"
|
||||
@update:modelValue="changeYear"
|
||||
:min="calendarStore.minYear"
|
||||
:max="calendarStore.maxYear"
|
||||
:min="MIN_YEAR"
|
||||
:max="MAX_YEAR"
|
||||
:step="1"
|
||||
aria-label="Year"
|
||||
number-prefix=""
|
||||
|
@ -17,6 +17,8 @@ import {
|
||||
getOccurrenceIndex,
|
||||
getVirtualOccurrenceEndDate,
|
||||
getISOWeek,
|
||||
MIN_YEAR,
|
||||
MAX_YEAR,
|
||||
} from '@/utils/date'
|
||||
import { toLocalString, fromLocalString, DEFAULT_TZ } from '@/utils/date'
|
||||
import { addDays, differenceInCalendarDays, differenceInWeeks } from 'date-fns'
|
||||
@ -76,14 +78,14 @@ function registerTap(rawDate, type) {
|
||||
}
|
||||
|
||||
const minVirtualWeek = computed(() => {
|
||||
const date = new Date(calendarStore.minYear, 0, 1)
|
||||
const date = new Date(MIN_YEAR, 0, 1)
|
||||
const dayOffset = (date.getDay() - calendarStore.config.first_day + 7) % 7
|
||||
const firstDayOfWeek = addDays(date, -dayOffset)
|
||||
return differenceInWeeks(firstDayOfWeek, baseDate.value)
|
||||
})
|
||||
|
||||
const maxVirtualWeek = computed(() => {
|
||||
const date = new Date(calendarStore.maxYear, 11, 31)
|
||||
const date = new Date(MAX_YEAR, 11, 31)
|
||||
const dayOffset = (date.getDay() - calendarStore.config.first_day + 7) % 7
|
||||
const firstDayOfWeek = addDays(date, -dayOffset)
|
||||
return differenceInWeeks(firstDayOfWeek, baseDate.value)
|
||||
@ -276,7 +278,7 @@ function createWeek(virtualWeek) {
|
||||
|
||||
let monthLabel = null
|
||||
if (hasFirst && monthToLabel !== null) {
|
||||
if (labelYear && labelYear <= calendarStore.config.max_year) {
|
||||
if (labelYear && labelYear <= MAX_YEAR) {
|
||||
let weeksSpan = 0
|
||||
const d = addDays(cur, -1)
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div
|
||||
ref="rootEl"
|
||||
class="mini-stepper drag-mode"
|
||||
:class="[extraClass, { dragging }]"
|
||||
:class="[extraClass, { dragging, 'pointer-locked': pointerLocked }]"
|
||||
:aria-label="ariaLabel"
|
||||
role="spinbutton"
|
||||
:aria-valuemin="minValue"
|
||||
@ -19,7 +19,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, ref, onBeforeUnmount } from 'vue'
|
||||
|
||||
const model = defineModel({ type: Number, default: 0 })
|
||||
|
||||
@ -102,46 +102,91 @@ const dragging = ref(false)
|
||||
const rootEl = ref(null)
|
||||
let startX = 0
|
||||
let startY = 0
|
||||
let startVal = 0
|
||||
let accumX = 0 // accumulated horizontal movement since last applied step (works for both locked & unlocked)
|
||||
let lastClientX = 0 // previous clientX when not pointer locked
|
||||
const pointerLocked = ref(false)
|
||||
|
||||
function updatePointerLocked() {
|
||||
pointerLocked.value =
|
||||
typeof document !== 'undefined' && document.pointerLockElement === rootEl.value
|
||||
// Reset baseline if lock just engaged
|
||||
if (pointerLocked.value) {
|
||||
accumX = 0
|
||||
startX = 0 // not used while locked
|
||||
}
|
||||
}
|
||||
|
||||
function addPointerLockListeners() {
|
||||
if (typeof document === 'undefined') return
|
||||
document.addEventListener('pointerlockchange', updatePointerLocked)
|
||||
document.addEventListener('pointerlockerror', updatePointerLocked)
|
||||
}
|
||||
function removePointerLockListeners() {
|
||||
if (typeof document === 'undefined') return
|
||||
document.removeEventListener('pointerlockchange', updatePointerLocked)
|
||||
document.removeEventListener('pointerlockerror', updatePointerLocked)
|
||||
}
|
||||
|
||||
function onPointerDown(e) {
|
||||
e.preventDefault()
|
||||
startX = e.clientX
|
||||
startY = e.clientY
|
||||
startVal = current.value
|
||||
lastClientX = e.clientX
|
||||
accumX = 0
|
||||
dragging.value = true
|
||||
try {
|
||||
e.currentTarget.setPointerCapture(e.pointerId)
|
||||
e.currentTarget.setPointerCapture?.(e.pointerId)
|
||||
} catch {}
|
||||
rootEl.value?.addEventListener('pointermove', onPointerMove)
|
||||
rootEl.value?.addEventListener('pointerup', onPointerUp, { once: true })
|
||||
rootEl.value?.addEventListener('pointercancel', onPointerCancel, { once: true })
|
||||
if (e.pointerType === 'mouse' && rootEl.value?.requestPointerLock) {
|
||||
addPointerLockListeners()
|
||||
try {
|
||||
rootEl.value.requestPointerLock()
|
||||
} catch {}
|
||||
}
|
||||
document.addEventListener('pointermove', onPointerMove)
|
||||
document.addEventListener('pointerup', onPointerUp, { once: true })
|
||||
document.addEventListener('pointercancel', onPointerCancel, { once: true })
|
||||
}
|
||||
function onPointerMove(e) {
|
||||
if (!dragging.value) return
|
||||
// Prevent page scroll on touch while dragging
|
||||
if (e.pointerType === 'touch') e.preventDefault()
|
||||
const primary = e.clientX - startX // horizontal only
|
||||
const steps = Math.trunc(primary / props.pixelsPerStep)
|
||||
|
||||
// Find current value index in all valid values
|
||||
const currentIndex = allValidValues.value.indexOf(startVal)
|
||||
if (currentIndex === -1) return // shouldn't happen
|
||||
|
||||
const newIndex = currentIndex + steps
|
||||
if (props.clamp) {
|
||||
const clampedIndex = Math.max(0, Math.min(newIndex, allValidValues.value.length - 1))
|
||||
const next = allValidValues.value[clampedIndex]
|
||||
if (next !== current.value) current.value = next
|
||||
let dx
|
||||
if (pointerLocked.value) {
|
||||
dx = e.movementX || 0
|
||||
} else {
|
||||
if (newIndex >= 0 && newIndex < allValidValues.value.length) {
|
||||
const next = allValidValues.value[newIndex]
|
||||
dx = e.clientX - lastClientX
|
||||
lastClientX = e.clientX
|
||||
}
|
||||
if (!dx) return
|
||||
accumX += dx
|
||||
const stepSize = props.pixelsPerStep || 1
|
||||
let steps = Math.trunc(accumX / stepSize)
|
||||
if (steps === 0) return
|
||||
// Apply steps relative to current value index each time (dynamic baseline) and keep leftover pixels
|
||||
const applySteps = (count) => {
|
||||
const currentIndex = allValidValues.value.indexOf(current.value)
|
||||
if (currentIndex === -1) return
|
||||
let targetIndex = currentIndex + count
|
||||
if (props.clamp) {
|
||||
targetIndex = Math.max(0, Math.min(targetIndex, allValidValues.value.length - 1))
|
||||
}
|
||||
if (targetIndex >= 0 && targetIndex < allValidValues.value.length) {
|
||||
const next = allValidValues.value[targetIndex]
|
||||
if (next !== current.value) current.value = next
|
||||
}
|
||||
}
|
||||
applySteps(steps)
|
||||
// Remove consumed distance (works even if clamped; leftover ensures responsiveness keeps consistent feel)
|
||||
accumX -= steps * stepSize
|
||||
}
|
||||
function endDragListeners() {
|
||||
rootEl.value?.removeEventListener('pointermove', onPointerMove)
|
||||
document.removeEventListener('pointermove', onPointerMove)
|
||||
if (pointerLocked.value && document.exitPointerLock) {
|
||||
try {
|
||||
document.exitPointerLock()
|
||||
} catch {}
|
||||
}
|
||||
removePointerLockListeners()
|
||||
}
|
||||
function onPointerUp() {
|
||||
dragging.value = false
|
||||
@ -267,4 +312,7 @@ function onWheel(e) {
|
||||
.mini-stepper.drag-mode.dragging {
|
||||
cursor: grabbing;
|
||||
}
|
||||
.mini-stepper.drag-mode.pointer-locked.dragging {
|
||||
cursor: none; /* hide cursor for infinite drag */
|
||||
}
|
||||
</style>
|
||||
|
@ -10,9 +10,6 @@ import {
|
||||
import { differenceInCalendarDays, addDays } from 'date-fns'
|
||||
import { initializeHolidays, getAvailableCountries, getAvailableStates } from '@/utils/holidays'
|
||||
|
||||
const MIN_YEAR = 1900
|
||||
const MAX_YEAR = 2100
|
||||
|
||||
export const useCalendarStore = defineStore('calendar', {
|
||||
state: () => ({
|
||||
today: toLocalString(new Date(), DEFAULT_TZ),
|
||||
@ -23,8 +20,6 @@ export const useCalendarStore = defineStore('calendar', {
|
||||
_holidaysInitialized: false,
|
||||
config: {
|
||||
select_days: 14,
|
||||
min_year: MIN_YEAR,
|
||||
max_year: MAX_YEAR,
|
||||
first_day: 1,
|
||||
holidays: {
|
||||
enabled: true,
|
||||
@ -34,12 +29,6 @@ export const useCalendarStore = defineStore('calendar', {
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
getters: {
|
||||
minYear: () => MIN_YEAR,
|
||||
maxYear: () => MAX_YEAR,
|
||||
},
|
||||
|
||||
actions: {
|
||||
_resolveCountry(code) {
|
||||
if (!code || code !== 'auto') return code
|
||||
|
@ -23,6 +23,9 @@ const monthAbbr = [
|
||||
'nov',
|
||||
'dec',
|
||||
]
|
||||
// Calendar year bounds (used instead of config.min_year / config.max_year)
|
||||
const MIN_YEAR = 1901
|
||||
const MAX_YEAR = 2100
|
||||
|
||||
// Core helpers ------------------------------------------------------------
|
||||
/**
|
||||
@ -320,6 +323,8 @@ function formatTodayString(date) {
|
||||
export {
|
||||
// constants
|
||||
monthAbbr,
|
||||
MIN_YEAR,
|
||||
MAX_YEAR,
|
||||
DEFAULT_TZ,
|
||||
// core tz helpers
|
||||
makeTZDate,
|
||||
|
Loading…
x
Reference in New Issue
Block a user