diff --git a/src/components/DatePicker.spec.ts b/src/components/DatePicker.spec.ts index e438acd..f5667cb 100644 --- a/src/components/DatePicker.spec.ts +++ b/src/components/DatePicker.spec.ts @@ -59,25 +59,51 @@ describe('DatePicker', () => { }) }) +/** + * 占位层是「盖住」而不是「隐藏」原生文字:靠绘制顺序生效,不依赖 + * ::-webkit-datetime-edit 之类的伪元素着色。因此这里重点断言 + * 显示时机与不透明背景 —— 少任何一项都会重新出现文字重叠。 + */ describe('DatePicker 空值占位', () => { - it('空值时显示自定义占位文案', () => { - expect(mountPicker(null).text()).toContain('选择日期') + const placeholderOf = (wrapper: ReturnType) => + 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') }) }) diff --git a/src/components/DatePicker.vue b/src/components/DatePicker.vue index 09cce2d..82e277c 100644 --- a/src/components/DatePicker.vue +++ b/src/components/DatePicker.vue @@ -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 -}