openpencil/src/components/ScrubInput.vue
2026-05-15 12:28:50 +03:00

99 lines
3.3 KiB
Vue

<script setup lang="ts">
import { computed, useAttrs } from 'vue'
import { ScrubInputRoot, ScrubInputField, ScrubInputDisplay, testId } from '@open-pencil/vue'
import { useEditorStore } from '@/app/editor/active-store'
const attrs = useAttrs()
const store = useEditorStore()
const rootTestId = computed(() => (attrs['data-test-id'] as string | undefined) ?? 'scrub-input')
const { modelValue, min, max, step, icon, label, suffix, sensitivity, placeholder } = defineProps<{
modelValue: number | symbol
min?: number
max?: number
step?: number
icon?: string
label?: string
suffix?: string
sensitivity?: number
placeholder?: string
}>()
const emit = defineEmits<{
'update:modelValue': [value: number]
'editing-change': [editing: boolean]
commit: [value: number, previous: number]
}>()
defineOptions({ inheritAttrs: false })
</script>
<template>
<ScrubInputRoot
v-slot="{ editing, actions, placeholder: ph }"
:model-value="modelValue"
:min="min"
:max="max"
:step="step"
:sensitivity="sensitivity"
:placeholder="placeholder"
@update:model-value="emit('update:modelValue', $event)"
@commit="(val: number, prev: number) => emit('commit', val, prev)"
@editing-change="
(editing: boolean) => {
store.state.scrubInputFocused = editing
emit('editing-change', editing)
}
"
>
<div
v-bind="{ ...attrs, ...testId(rootTestId) }"
:tabindex="editing ? undefined : 0"
:class="[
attrs.class,
'group flex h-[26px] min-w-0 flex-1 items-center rounded border border-border bg-input focus-within:border-accent focus:border-accent'
]"
:style="{ cursor: editing ? 'auto' : 'ew-resize' }"
@pointerdown="
!editing &&
!($event.target as HTMLElement)?.closest?.('button') &&
actions.startScrub($event)
"
@focus="!editing && actions.startEdit()"
>
<span v-if="attrs['data-test-id']" data-test-id="scrub-input" class="hidden" />
<span
class="flex shrink-0 items-center justify-center self-stretch px-[5px] text-muted select-none [&>*]:pointer-events-none"
>
<slot name="icon">
<span v-if="icon" class="text-[11px] leading-none">{{ icon }}</span>
</slot>
<span v-if="label" class="text-[11px] leading-none">{{ label }}</span>
</span>
<ScrubInputField
data-test-id="scrub-input-field"
class="min-w-0 flex-1 cursor-text border-none bg-transparent pr-1.5 font-[inherit] text-xs text-surface outline-none"
:min="min === -Infinity ? undefined : min"
:max="max === Infinity ? undefined : max"
:step="step"
/>
<slot v-if="editing" name="suffix" />
<ScrubInputDisplay
class="flex flex-1 items-center truncate overflow-hidden text-xs select-none"
:class="$slots.suffix ? 'pr-0' : 'pr-1.5'"
>
<template #default="{ value, isMixed: mixed }">
<span v-if="mixed" class="flex-1 text-muted">{{ ph }}</span>
<template v-else>
<span class="flex-1 text-surface">{{ value }}</span>
<span v-if="suffix" class="shrink-0 pr-1.5 text-muted">{{ suffix }}</span>
</template>
<slot name="suffix" />
</template>
</ScrubInputDisplay>
</div>
</ScrubInputRoot>
</template>