fix(ui): 日期占位改用不透明遮罩盖住原生文字,不再依赖伪元素着色
把 ::-webkit-datetime-edit 系列伪元素设为透明的做法在部分环境下不生效, 空值日期框会同时显示自绘的「选择日期」与原生「yyyy/mm/日」,文字重叠。 改为用一层不透明背景直接盖住原生提示:靠绘制顺序生效,不依赖伪元素支持 与 CSS 选择器匹配;显示时机由 Vue 状态(空值 && 未聚焦)控制。同时删掉 style.css 里已失效的 .date-field--blank 规则。
This commit is contained in:
@@ -59,25 +59,51 @@ describe('DatePicker', () => {
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* 占位层是「盖住」而不是「隐藏」原生文字:靠绘制顺序生效,不依赖
|
||||
* ::-webkit-datetime-edit 之类的伪元素着色。因此这里重点断言
|
||||
* 显示时机与不透明背景 —— 少任何一项都会重新出现文字重叠。
|
||||
*/
|
||||
describe('DatePicker 空值占位', () => {
|
||||
it('空值时显示自定义占位文案', () => {
|
||||
expect(mountPicker(null).text()).toContain('选择日期')
|
||||
const placeholderOf = (wrapper: ReturnType<typeof mountPicker>) =>
|
||||
wrapper.find('[data-role="date-placeholder"]')
|
||||
|
||||
it('空值且未聚焦时显示自绘占位文案', () => {
|
||||
expect(placeholderOf(mountPicker(null)).text()).toBe('选择日期')
|
||||
})
|
||||
|
||||
it('有值时不再显示占位文案', () => {
|
||||
expect(mountPicker('2026-10-20').text()).not.toContain('选择日期')
|
||||
it('有值时不再显示占位', () => {
|
||||
expect(placeholderOf(mountPicker('2026-10-20')).exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('空值时给输入框加上遮罩类(遮住 yyyy/mm/日)', () => {
|
||||
expect(mountPicker(null).find('input').classes()).toContain('date-field--blank')
|
||||
it('聚焦后占位让位,原生分段可见', async () => {
|
||||
const wrapper = mountPicker(null)
|
||||
await wrapper.find('input').trigger('focus')
|
||||
expect(placeholderOf(wrapper).exists()).toBe(false)
|
||||
})
|
||||
|
||||
it('有值时不加遮罩类', () => {
|
||||
expect(mountPicker('2026-10-20').find('input').classes()).not.toContain('date-field--blank')
|
||||
it('失焦后占位恢复', async () => {
|
||||
const wrapper = mountPicker(null)
|
||||
await wrapper.find('input').trigger('focus')
|
||||
await wrapper.find('input').trigger('blur')
|
||||
expect(placeholderOf(wrapper).exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('占位层不拦截鼠标事件(点击仍能唤起原生选择器)', () => {
|
||||
const placeholder = mountPicker(null).find('span')
|
||||
expect(placeholder.classes()).toContain('pointer-events-none')
|
||||
expect(placeholderOf(mountPicker(null)).classes()).toContain('pointer-events-none')
|
||||
})
|
||||
|
||||
it('占位层带不透明背景,且与输入框配色一致', () => {
|
||||
const classes = placeholderOf(mountPicker(null)).classes()
|
||||
expect(classes).toContain('bg-white')
|
||||
expect(classes).toContain('dark:bg-slate-800')
|
||||
expect(mountPicker(null).find('input').classes()).toContain('dark:bg-slate-800')
|
||||
})
|
||||
|
||||
it('占位层只覆盖左侧,给日历图标留出位置', () => {
|
||||
const classes = placeholderOf(mountPicker(null)).classes()
|
||||
expect(classes).toContain('right-9')
|
||||
expect(classes).toContain('inset-y-px')
|
||||
expect(classes).toContain('left-px')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -7,24 +7,35 @@
|
||||
* - 自带可访问性、键盘操作与本地化日历,无需自己实现
|
||||
* - 深色模式下日历弹层跟随 html 的 color-scheme
|
||||
*
|
||||
* 空值时刻意遮住原生占位文字:Chrome 会按页面 lang 拼出「yyyy/mm/日」
|
||||
* 这种半英半中的提示,既别扭又容易被误读成已有内容。改为显示自己的
|
||||
* 「选择日期」,聚焦时再让位给原生分段输入(否则编辑时看不见数字)。
|
||||
* 空值时的占位处理:
|
||||
* 浏览器(Edge 尤甚)会给空日期框显示「yyyy / mm / 日」这种半英半中的
|
||||
* 格式提示,既与界面语言不统一,又容易被误读成已有内容。
|
||||
*
|
||||
* 曾经用 CSS 把 `::-webkit-datetime-edit` 等伪元素设成透明,但在部分环境下
|
||||
* 不生效(实测在用户机器上原生文字照样显示)。现在改为**用一层不透明背景
|
||||
* 直接盖住**:
|
||||
* - 不依赖伪元素支持,也不依赖 CSS 选择器匹配,纯靠绘制顺序(后者在上)
|
||||
* - 显示时机由 Vue 状态决定(空值 && 未聚焦),确定可控
|
||||
* - 聚焦时让位,原生分段正常显示,不影响编辑
|
||||
*
|
||||
* 代价:占位层的背景色必须与输入框完全一致,否则会看出色块。
|
||||
*/
|
||||
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const props = defineProps<{ modelValue: string | null; label?: string }>()
|
||||
|
||||
const emit = defineEmits<{ 'update:modelValue': [value: string | null] }>()
|
||||
|
||||
const focused = ref(false)
|
||||
|
||||
/** 空值且未聚焦时才盖住原生提示 */
|
||||
const showPlaceholder = computed(() => props.modelValue === null && !focused.value)
|
||||
|
||||
function onChange(event: Event): void {
|
||||
const value = (event.target as HTMLInputElement).value
|
||||
emit('update:modelValue', value === '' ? null : value)
|
||||
}
|
||||
|
||||
/** 空值且未聚焦时才遮住原生占位文字 */
|
||||
function hideNativePlaceholder(): boolean {
|
||||
return props.modelValue === null
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -32,16 +43,22 @@ function hideNativePlaceholder(): boolean {
|
||||
<div class="relative min-w-0 flex-1">
|
||||
<input
|
||||
type="date"
|
||||
class="peer w-full rounded-md border border-slate-300 px-2.5 py-1.5 text-sm text-slate-700 outline-none focus:border-sky-400 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-200 dark:focus:border-sky-600"
|
||||
:class="{ 'date-field--blank': hideNativePlaceholder() }"
|
||||
class="w-full rounded-md border border-slate-300 bg-white px-2.5 py-1.5 text-sm text-slate-700 outline-none focus:border-sky-400 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-200 dark:focus:border-sky-600"
|
||||
:value="modelValue ?? ''"
|
||||
:aria-label="label ?? '截止日期'"
|
||||
@focus="focused = true"
|
||||
@blur="focused = false"
|
||||
@change="onChange"
|
||||
/>
|
||||
|
||||
<!--
|
||||
背景色必须与输入框一致(浅色 bg-white / 深色 bg-slate-800),否则盖不干净。
|
||||
左侧内缩 1px 让边框可见;右侧留出日历图标的位置,避免把它也盖住。
|
||||
-->
|
||||
<span
|
||||
v-if="hideNativePlaceholder()"
|
||||
class="pointer-events-none absolute inset-y-0 left-2.5 flex items-center text-sm text-slate-400 peer-focus:hidden dark:text-slate-500"
|
||||
v-if="showPlaceholder"
|
||||
data-role="date-placeholder"
|
||||
class="pointer-events-none absolute inset-y-px left-px right-9 flex items-center rounded-l-md bg-white pl-2 text-sm text-slate-400 dark:bg-slate-800 dark:text-slate-500"
|
||||
>
|
||||
选择日期
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user