|
|
@@ -16,7 +16,7 @@
|
|
|
:key="idx"
|
|
|
class="grid-item"
|
|
|
:class="{ long: g.isLongGrid }"
|
|
|
- :style="g.showText ? { height: '40px' } : gridItemStyle"
|
|
|
+ :style="{ width: props.gutter + 'px', height: g.showText ? '40px' : '24px' }"
|
|
|
>
|
|
|
<text v-if="g.showText" class="grid-num">{{ g.displayNum }}</text>
|
|
|
</view>
|
|
|
@@ -80,36 +80,69 @@ buildGrid()
|
|
|
// offsetScroll: amount to offset due to extra grids on left
|
|
|
const offsetScroll = computed(() => extraGridCount * props.gutter)
|
|
|
|
|
|
+// window width and half (indicator is centered horizontally)
|
|
|
+const windowWidth = uni?.getSystemInfoSync?.().windowWidth || 375
|
|
|
+const halfWindow = windowWidth / 2
|
|
|
+
|
|
|
+// centered positions for extremes
|
|
|
+const minCenterLeft = computed(() => offsetScroll.value - halfWindow + props.gutter / 2)
|
|
|
+const maxCenterLeft = computed(() => totalUnits.value * props.gutter + offsetScroll.value - halfWindow + props.gutter / 2)
|
|
|
+
|
|
|
// initial position
|
|
|
onMounted(() => {
|
|
|
// center on initial value
|
|
|
const initIndex = Math.round((props.initialValue - props.min) / props.step)
|
|
|
- scrollLeft.value = initIndex * props.gutter + offsetScroll.value - (uni?.getSystemInfoSync?.().windowWidth || 375) / 2 + props.gutter / 2
|
|
|
+ // use computed windowWidth to center the initial index under the indicator
|
|
|
+ scrollLeft.value = initIndex * props.gutter + offsetScroll.value - halfWindow + props.gutter / 2
|
|
|
})
|
|
|
|
|
|
function onScroll(e: any) {
|
|
|
const left = e?.detail?.scrollLeft ?? e?.detail?.scrollLeft ?? 0
|
|
|
actualScrollLeft.value = left
|
|
|
- let value = Math.round((left - offsetScroll.value) / props.gutter) * props.step + props.min
|
|
|
+ // compute index under the centered indicator (float)
|
|
|
+ const centerPos = left + halfWindow - offsetScroll.value - props.gutter / 2
|
|
|
+ const idxFloat = centerPos / props.gutter
|
|
|
+ // calculate actual scrollable max
|
|
|
+ const maxScroll = Math.max(0, totalWidth.value - windowWidth)
|
|
|
+ // if center would go left of min, clamp scrollLeft to minCenterLeft and emit min
|
|
|
+ if (idxFloat <= 0) {
|
|
|
+ const clamped = Math.min(Math.max(minCenterLeft.value, 0), maxScroll)
|
|
|
+ scrollLeft.value = clamped
|
|
|
+ actualScrollLeft.value = clamped
|
|
|
+ emit('update:value', props.min)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // if center would go right of max, clamp to maxCenterLeft and emit max
|
|
|
+ if (idxFloat >= totalUnits.value) {
|
|
|
+ const clamped = Math.min(Math.max(maxCenterLeft.value, 0), maxScroll)
|
|
|
+ scrollLeft.value = clamped
|
|
|
+ actualScrollLeft.value = clamped
|
|
|
+ emit('update:value', props.max)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const idx = Math.round(idxFloat)
|
|
|
+ let value = idx * props.step + props.min
|
|
|
if (value < props.min) value = props.min
|
|
|
if (value > props.max) value = props.max
|
|
|
emit('update:value', value)
|
|
|
}
|
|
|
|
|
|
function adjustScrollPosition() {
|
|
|
+ // snap so the nearest tick is centered under the indicator
|
|
|
const left = actualScrollLeft.value
|
|
|
- const minLeft = offsetScroll.value
|
|
|
- const maxLeft = (totalUnits.value) * props.gutter + offsetScroll.value
|
|
|
- if (left < minLeft) {
|
|
|
- scrollLeft.value = minLeft
|
|
|
- } else if (left > maxLeft) {
|
|
|
- scrollLeft.value = maxLeft
|
|
|
- } else {
|
|
|
- // snap to nearest gutter
|
|
|
- const dry = left - offsetScroll.value
|
|
|
- const rem = Math.round(dry / props.gutter) * props.gutter
|
|
|
- scrollLeft.value = rem + offsetScroll.value
|
|
|
- }
|
|
|
+ // compute index under center
|
|
|
+ const idx = Math.round((left + halfWindow - offsetScroll.value - props.gutter / 2) / props.gutter)
|
|
|
+ // compute target scrollLeft that centers this index
|
|
|
+ const targetLeft = idx * props.gutter + offsetScroll.value - halfWindow + props.gutter / 2
|
|
|
+ // compute centered positions for min and max so extremes map to centered
|
|
|
+ const minCenterLeft = 0 + offsetScroll.value - halfWindow + props.gutter / 2
|
|
|
+ const maxCenterLeft = totalUnits.value * props.gutter + offsetScroll.value - halfWindow + props.gutter / 2
|
|
|
+ // actual scrollable bounds
|
|
|
+ const maxScroll = Math.max(0, totalWidth.value - windowWidth)
|
|
|
+ // clamp target to the centered min/max first, then to actual scroll range
|
|
|
+ const bounded = Math.min(Math.max(targetLeft, minCenterLeft), maxCenterLeft)
|
|
|
+ const clamped = Math.min(Math.max(bounded, 0), maxScroll)
|
|
|
+ scrollLeft.value = clamped
|
|
|
}
|
|
|
|
|
|
function onTouchEnd() {
|
|
|
@@ -117,7 +150,8 @@ function onTouchEnd() {
|
|
|
adjustScrollPosition()
|
|
|
// compute value and emit change
|
|
|
const left = scrollLeft.value
|
|
|
- let value = Math.round((left - offsetScroll.value) / props.gutter) * props.step + props.min
|
|
|
+ const idx = Math.round((left + halfWindow - offsetScroll.value - props.gutter / 2) / props.gutter)
|
|
|
+ let value = idx * props.step + props.min
|
|
|
if (value < props.min) value = props.min
|
|
|
if (value > props.max) value = props.max
|
|
|
emit('change', value)
|