Phase 1: establish SDK component pattern with 3 exemplars
New Reka UI-style headless components following the pattern: ComponentName/context.ts — InjectionKey, provide/inject ComponentName/Root.vue — state management, slot props ComponentName/Item.vue — per-item logic, slot props ComponentName/index.ts — barrel export ScrubInput (new headless): ScrubInputRoot — scrub state, pointer capture, edit/commit logic ScrubInputField — input element for direct editing ScrubInputDisplay — read-only display with slot for value/suffix App ScrubInput.vue composes these with Tailwind styling Toolbar (new headless): ToolbarRoot — active tool, tool list, flyout state ToolbarItem — per-tool active state and select handler PageList (refactored): PageListRoot — pages, currentPageId, CRUD actions via slots Old components kept as backward-compatible aliases.
This commit is contained in:
parent
bc10c3c64e
commit
d4c102c6f4
24
packages/vue/src/PageList/PageListRoot.vue
Normal file
24
packages/vue/src/PageList/PageListRoot.vue
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useEditor } from '../context'
|
||||
|
||||
const editor = useEditor()
|
||||
|
||||
const pages = computed(() => {
|
||||
void editor.state.sceneVersion
|
||||
return editor.graph.getPages()
|
||||
})
|
||||
const currentPageId = computed(() => editor.state.currentPageId)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<slot
|
||||
:pages="pages"
|
||||
:current-page-id="currentPageId"
|
||||
:switch-page="editor.switchPage"
|
||||
:add-page="editor.addPage"
|
||||
:delete-page="editor.deletePage"
|
||||
:rename-page="editor.renamePage"
|
||||
/>
|
||||
</template>
|
||||
1
packages/vue/src/PageList/index.ts
Normal file
1
packages/vue/src/PageList/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { default as PageListRoot } from './PageListRoot.vue'
|
||||
21
packages/vue/src/ScrubInput/ScrubInputDisplay.vue
Normal file
21
packages/vue/src/ScrubInput/ScrubInputDisplay.vue
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<script setup lang="ts">
|
||||
import { useScrubInput } from './context'
|
||||
|
||||
const ctx = useScrubInput()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-if="!ctx.editing.value" v-bind="$attrs">
|
||||
<slot
|
||||
:value="ctx.displayValue.value"
|
||||
:is-mixed="ctx.isMixed.value"
|
||||
:placeholder="''"
|
||||
>
|
||||
{{ ctx.isMixed.value ? '' : ctx.displayValue.value }}
|
||||
</slot>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default { inheritAttrs: false }
|
||||
</script>
|
||||
21
packages/vue/src/ScrubInput/ScrubInputField.vue
Normal file
21
packages/vue/src/ScrubInput/ScrubInputField.vue
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<script setup lang="ts">
|
||||
import { useScrubInput } from './context'
|
||||
|
||||
const ctx = useScrubInput()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<input
|
||||
v-if="ctx.editing.value"
|
||||
:ref="(el) => { ctx.inputRef.value = el as HTMLInputElement | null }"
|
||||
type="number"
|
||||
:value="ctx.isMixed.value ? '' : ctx.displayValue.value"
|
||||
v-bind="$attrs"
|
||||
@blur="ctx.commitEdit"
|
||||
@keydown="ctx.onKeydown"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default { inheritAttrs: false }
|
||||
</script>
|
||||
126
packages/vue/src/ScrubInput/ScrubInputRoot.vue
Normal file
126
packages/vue/src/ScrubInput/ScrubInputRoot.vue
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, computed, toRef } from 'vue'
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
|
||||
import { provideScrubInput } from './context'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue: number | symbol
|
||||
min?: number
|
||||
max?: number
|
||||
step?: number
|
||||
sensitivity?: number
|
||||
placeholder?: string
|
||||
}>(), {
|
||||
min: -Infinity,
|
||||
max: Infinity,
|
||||
step: 1,
|
||||
sensitivity: 1,
|
||||
placeholder: 'Mixed'
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: number]
|
||||
commit: [value: number, previous: number]
|
||||
}>()
|
||||
|
||||
const editing = ref(false)
|
||||
const scrubbing = ref(false)
|
||||
const inputRef = ref<HTMLInputElement | null>(null)
|
||||
|
||||
const isMixed = computed(() => typeof props.modelValue === 'symbol')
|
||||
const numericValue = computed(() => isMixed.value ? 0 : (props.modelValue as number))
|
||||
const displayValue = computed(() => isMixed.value ? '' : String(Math.round(numericValue.value)))
|
||||
|
||||
let stopMove: (() => void) | undefined
|
||||
let stopUp: (() => void) | undefined
|
||||
|
||||
function startScrub(e: PointerEvent) {
|
||||
e.preventDefault()
|
||||
const startX = e.clientX
|
||||
let lastX = startX
|
||||
let accumulated = numericValue.value
|
||||
const valueBeforeScrub = numericValue.value
|
||||
let hasMoved = false
|
||||
|
||||
stopMove = useEventListener(document, 'pointermove', (ev: PointerEvent) => {
|
||||
const dx = ev.clientX - lastX
|
||||
lastX = ev.clientX
|
||||
if (!hasMoved && Math.abs(ev.clientX - startX) > 2) {
|
||||
hasMoved = true
|
||||
scrubbing.value = true
|
||||
document.body.style.cursor = 'ew-resize'
|
||||
}
|
||||
if (hasMoved) {
|
||||
accumulated += dx * props.step * props.sensitivity
|
||||
const clamped = Math.round(Math.min(props.max, Math.max(props.min, accumulated)))
|
||||
if (clamped !== props.modelValue) emit('update:modelValue', clamped)
|
||||
}
|
||||
})
|
||||
|
||||
stopUp = useEventListener(document, 'pointerup', () => {
|
||||
scrubbing.value = false
|
||||
document.body.style.cursor = ''
|
||||
stopMove?.()
|
||||
stopUp?.()
|
||||
if (hasMoved) {
|
||||
if (typeof props.modelValue === 'number' && props.modelValue !== valueBeforeScrub) {
|
||||
emit('commit', props.modelValue, valueBeforeScrub)
|
||||
}
|
||||
} else {
|
||||
startEdit()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function startEdit() {
|
||||
editing.value = true
|
||||
requestAnimationFrame(() => inputRef.value?.select())
|
||||
}
|
||||
|
||||
function commitEdit(e: Event) {
|
||||
const val = +(e.target as HTMLInputElement).value
|
||||
const previous = numericValue.value
|
||||
if (!Number.isNaN(val)) {
|
||||
const clamped = Math.min(props.max, Math.max(props.min, val))
|
||||
emit('update:modelValue', clamped)
|
||||
if (clamped !== previous) emit('commit', clamped, previous)
|
||||
}
|
||||
editing.value = false
|
||||
}
|
||||
|
||||
function onKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Enter') commitEdit(e)
|
||||
else if (e.key === 'Escape') editing.value = false
|
||||
}
|
||||
|
||||
const ctx = {
|
||||
modelValue: toRef(props, 'modelValue'),
|
||||
displayValue,
|
||||
isMixed,
|
||||
editing,
|
||||
scrubbing,
|
||||
inputRef,
|
||||
startScrub,
|
||||
startEdit,
|
||||
commitEdit,
|
||||
onKeydown
|
||||
}
|
||||
|
||||
provideScrubInput(ctx)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<slot
|
||||
:model-value="props.modelValue"
|
||||
:display-value="displayValue"
|
||||
:is-mixed="isMixed"
|
||||
:editing="editing"
|
||||
:scrubbing="scrubbing"
|
||||
:start-scrub="startScrub"
|
||||
:start-edit="startEdit"
|
||||
:commit-edit="commitEdit"
|
||||
:on-keydown="onKeydown"
|
||||
:placeholder="props.placeholder"
|
||||
/>
|
||||
</template>
|
||||
27
packages/vue/src/ScrubInput/context.ts
Normal file
27
packages/vue/src/ScrubInput/context.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { type InjectionKey, inject, provide } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
export interface ScrubInputContext {
|
||||
modelValue: Ref<number | symbol>
|
||||
displayValue: Ref<string>
|
||||
isMixed: Ref<boolean>
|
||||
editing: Ref<boolean>
|
||||
scrubbing: Ref<boolean>
|
||||
inputRef: Ref<HTMLInputElement | null>
|
||||
startScrub: (e: PointerEvent) => void
|
||||
startEdit: () => void
|
||||
commitEdit: (e: Event) => void
|
||||
onKeydown: (e: KeyboardEvent) => void
|
||||
}
|
||||
|
||||
export const SCRUB_INPUT_KEY: InjectionKey<ScrubInputContext> = Symbol('scrub-input')
|
||||
|
||||
export function provideScrubInput(ctx: ScrubInputContext) {
|
||||
provide(SCRUB_INPUT_KEY, ctx)
|
||||
}
|
||||
|
||||
export function useScrubInput(): ScrubInputContext {
|
||||
const ctx = inject(SCRUB_INPUT_KEY)
|
||||
if (!ctx) throw new Error('[open-pencil] useScrubInput() called outside <ScrubInputRoot>')
|
||||
return ctx
|
||||
}
|
||||
5
packages/vue/src/ScrubInput/index.ts
Normal file
5
packages/vue/src/ScrubInput/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export { default as ScrubInputRoot } from './ScrubInputRoot.vue'
|
||||
export { default as ScrubInputField } from './ScrubInputField.vue'
|
||||
export { default as ScrubInputDisplay } from './ScrubInputDisplay.vue'
|
||||
export { useScrubInput } from './context'
|
||||
export type { ScrubInputContext } from './context'
|
||||
23
packages/vue/src/Toolbar/ToolbarItem.vue
Normal file
23
packages/vue/src/Toolbar/ToolbarItem.vue
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
import { useToolbar } from './context'
|
||||
|
||||
import type { Tool } from '@open-pencil/core/editor'
|
||||
|
||||
const { tool } = defineProps<{
|
||||
tool: Tool
|
||||
}>()
|
||||
|
||||
const { activeTool, setTool } = useToolbar()
|
||||
|
||||
const isActive = computed(() => activeTool.value === tool)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<slot
|
||||
:active="isActive"
|
||||
:select="() => setTool(tool)"
|
||||
:tool="tool"
|
||||
/>
|
||||
</template>
|
||||
51
packages/vue/src/Toolbar/ToolbarRoot.vue
Normal file
51
packages/vue/src/Toolbar/ToolbarRoot.vue
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { EDITOR_TOOLS } from '@open-pencil/core/editor'
|
||||
|
||||
import { useEditor } from '../context'
|
||||
import { provideToolbar } from './context'
|
||||
|
||||
import type { EditorToolDef, Tool } from '@open-pencil/core/editor'
|
||||
|
||||
const { tools = EDITOR_TOOLS } = defineProps<{
|
||||
tools?: EditorToolDef[]
|
||||
}>()
|
||||
|
||||
const editor = useEditor()
|
||||
const activeTool = computed(() => editor.state.activeTool)
|
||||
const expandedFlyout = ref<Tool | null>(null)
|
||||
|
||||
function setTool(tool: Tool) {
|
||||
editor.setTool(tool)
|
||||
expandedFlyout.value = null
|
||||
}
|
||||
|
||||
function toggleFlyout(tool: Tool) {
|
||||
expandedFlyout.value = expandedFlyout.value === tool ? null : tool
|
||||
}
|
||||
|
||||
function closeFlyout() {
|
||||
expandedFlyout.value = null
|
||||
}
|
||||
|
||||
provideToolbar({
|
||||
editor,
|
||||
tools,
|
||||
activeTool,
|
||||
expandedFlyout,
|
||||
setTool,
|
||||
toggleFlyout,
|
||||
closeFlyout
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<slot
|
||||
:tools="tools"
|
||||
:active-tool="activeTool"
|
||||
:expanded-flyout="expandedFlyout"
|
||||
:set-tool="setTool"
|
||||
:toggle-flyout="toggleFlyout"
|
||||
:close-flyout="closeFlyout"
|
||||
/>
|
||||
</template>
|
||||
26
packages/vue/src/Toolbar/context.ts
Normal file
26
packages/vue/src/Toolbar/context.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { type InjectionKey, inject, provide } from 'vue'
|
||||
|
||||
import type { Editor, EditorToolDef, Tool } from '@open-pencil/core/editor'
|
||||
import type { ComputedRef, Ref } from 'vue'
|
||||
|
||||
export interface ToolbarContext {
|
||||
editor: Editor
|
||||
tools: EditorToolDef[]
|
||||
activeTool: ComputedRef<Tool>
|
||||
expandedFlyout: Ref<Tool | null>
|
||||
setTool: (tool: Tool) => void
|
||||
toggleFlyout: (tool: Tool) => void
|
||||
closeFlyout: () => void
|
||||
}
|
||||
|
||||
export const TOOLBAR_KEY: InjectionKey<ToolbarContext> = Symbol('toolbar')
|
||||
|
||||
export function provideToolbar(ctx: ToolbarContext) {
|
||||
provide(TOOLBAR_KEY, ctx)
|
||||
}
|
||||
|
||||
export function useToolbar(): ToolbarContext {
|
||||
const ctx = inject(TOOLBAR_KEY)
|
||||
if (!ctx) throw new Error('[open-pencil] useToolbar() called outside <ToolbarRoot>')
|
||||
return ctx
|
||||
}
|
||||
4
packages/vue/src/Toolbar/index.ts
Normal file
4
packages/vue/src/Toolbar/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export { default as ToolbarRoot } from './ToolbarRoot.vue'
|
||||
export { default as ToolbarItem } from './ToolbarItem.vue'
|
||||
export { useToolbar } from './context'
|
||||
export type { ToolbarContext } from './context'
|
||||
|
|
@ -4,7 +4,6 @@ import { computed } from 'vue'
|
|||
import { useEditor } from '../context'
|
||||
|
||||
const editor = useEditor()
|
||||
|
||||
const activeTool = computed(() => editor.state.activeTool)
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -18,8 +18,20 @@ export { useNodeFontStatus } from './composables/use-font-status'
|
|||
export { toast } from './toast'
|
||||
export type { Toast, ToastVariant } from './toast'
|
||||
|
||||
export {
|
||||
ScrubInputRoot,
|
||||
ScrubInputField,
|
||||
ScrubInputDisplay,
|
||||
useScrubInput
|
||||
} from './ScrubInput'
|
||||
export type { ScrubInputContext } from './ScrubInput'
|
||||
|
||||
export { ToolbarRoot, ToolbarItem, useToolbar } from './Toolbar'
|
||||
export type { ToolbarContext } from './Toolbar'
|
||||
|
||||
export { default as OpenPencilProvider } from './components/OpenPencilProvider.vue'
|
||||
export { default as OpenPencilCanvas } from './components/OpenPencilCanvas.vue'
|
||||
export { PageListRoot } from './PageList'
|
||||
export { default as PageList } from './components/PageList.vue'
|
||||
export { default as LayerTree } from './components/LayerTree.vue'
|
||||
export { default as ToolSelector } from './components/ToolSelector.vue'
|
||||
|
|
|
|||
|
|
@ -1,18 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
import { ScrubInputRoot, ScrubInputField, ScrubInputDisplay } from '@open-pencil/vue'
|
||||
|
||||
const {
|
||||
modelValue,
|
||||
min = -Infinity,
|
||||
max = Infinity,
|
||||
step = 1,
|
||||
icon,
|
||||
label,
|
||||
suffix,
|
||||
sensitivity = 1,
|
||||
placeholder = 'Mixed'
|
||||
} = defineProps<{
|
||||
const { modelValue, min, max, step, icon, label, suffix, sensitivity, placeholder } = defineProps<{
|
||||
modelValue: number | symbol
|
||||
min?: number
|
||||
max?: number
|
||||
|
|
@ -24,130 +13,56 @@ const {
|
|||
placeholder?: string
|
||||
}>()
|
||||
|
||||
const isMixed = computed(() => typeof modelValue === 'symbol')
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: number]
|
||||
commit: [value: number, previous: number]
|
||||
}>()
|
||||
|
||||
const editing = ref(false)
|
||||
const inputRef = ref<HTMLInputElement | null>(null)
|
||||
const scrubbing = ref(false)
|
||||
|
||||
let stopMove: (() => void) | undefined
|
||||
let stopUp: (() => void) | undefined
|
||||
|
||||
const numericValue = computed(() => (isMixed.value ? 0 : (modelValue as number)))
|
||||
const displayValue = computed(() => (isMixed.value ? '' : Math.round(numericValue.value)))
|
||||
|
||||
function startScrub(e: PointerEvent) {
|
||||
e.preventDefault()
|
||||
const startX = e.clientX
|
||||
let lastX = startX
|
||||
let accumulated = numericValue.value
|
||||
const valueBeforeScrub = numericValue.value
|
||||
let hasMoved = false
|
||||
|
||||
stopMove = useEventListener(document, 'pointermove', (ev: PointerEvent) => {
|
||||
const dx = ev.clientX - lastX
|
||||
lastX = ev.clientX
|
||||
if (!hasMoved && Math.abs(ev.clientX - startX) > 2) {
|
||||
hasMoved = true
|
||||
scrubbing.value = true
|
||||
document.body.style.cursor = 'ew-resize'
|
||||
}
|
||||
if (hasMoved) {
|
||||
accumulated += dx * step * sensitivity
|
||||
const clamped = Math.round(Math.min(max, Math.max(min, accumulated)))
|
||||
if (clamped !== modelValue) {
|
||||
emit('update:modelValue', clamped)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
stopUp = useEventListener(document, 'pointerup', () => {
|
||||
scrubbing.value = false
|
||||
document.body.style.cursor = ''
|
||||
stopMove?.()
|
||||
stopUp?.()
|
||||
if (hasMoved) {
|
||||
if (typeof modelValue === 'number' && modelValue !== valueBeforeScrub) {
|
||||
emit('commit', modelValue, valueBeforeScrub)
|
||||
}
|
||||
} else {
|
||||
startEdit()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function startEdit() {
|
||||
editing.value = true
|
||||
requestAnimationFrame(() => {
|
||||
inputRef.value?.select()
|
||||
})
|
||||
}
|
||||
|
||||
function commitEdit(e: Event) {
|
||||
const val = +(e.target as HTMLInputElement).value
|
||||
const previous = numericValue.value
|
||||
if (!Number.isNaN(val)) {
|
||||
const clamped = Math.min(max, Math.max(min, val))
|
||||
emit('update:modelValue', clamped)
|
||||
if (clamped !== previous) {
|
||||
emit('commit', clamped, previous)
|
||||
}
|
||||
}
|
||||
editing.value = false
|
||||
}
|
||||
|
||||
function onKeydown(e: KeyboardEvent) {
|
||||
if (e.key === 'Enter') {
|
||||
commitEdit(e)
|
||||
} else if (e.key === 'Escape') {
|
||||
editing.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
data-test-id="scrub-input"
|
||||
class="flex h-[26px] min-w-0 flex-1 items-center rounded border border-border bg-input focus-within:border-accent"
|
||||
:style="{ cursor: editing ? 'auto' : 'ew-resize' }"
|
||||
@pointerdown="!editing && startScrub($event)"
|
||||
<ScrubInputRoot
|
||||
v-slot="{ editing, startScrub, 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)"
|
||||
>
|
||||
<span
|
||||
class="flex shrink-0 items-center justify-center self-stretch px-[5px] text-muted select-none [&>*]:pointer-events-none"
|
||||
<div
|
||||
data-test-id="scrub-input"
|
||||
class="flex h-[26px] min-w-0 flex-1 items-center rounded border border-border bg-input focus-within:border-accent"
|
||||
:style="{ cursor: editing ? 'auto' : 'ew-resize' }"
|
||||
@pointerdown="!editing && startScrub($event)"
|
||||
>
|
||||
<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>
|
||||
<input
|
||||
v-if="editing"
|
||||
ref="inputRef"
|
||||
type="number"
|
||||
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"
|
||||
:value="isMixed ? '' : displayValue"
|
||||
:placeholder="placeholder"
|
||||
:min="min === -Infinity ? undefined : min"
|
||||
:max="max === Infinity ? undefined : max"
|
||||
:step="step"
|
||||
@blur="commitEdit"
|
||||
@keydown="onKeydown"
|
||||
/>
|
||||
<span
|
||||
v-else
|
||||
class="flex flex-1 items-center truncate overflow-hidden pr-1.5 text-xs select-none"
|
||||
>
|
||||
<span v-if="isMixed" class="flex-1 text-muted">{{ placeholder }}</span>
|
||||
<template v-else>
|
||||
<span class="flex-1 text-surface">{{ displayValue }}</span>
|
||||
<span v-if="suffix" class="shrink-0 text-muted">{{ suffix }}</span>
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
<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"
|
||||
/>
|
||||
<ScrubInputDisplay
|
||||
class="flex flex-1 items-center truncate overflow-hidden pr-1.5 text-xs select-none"
|
||||
>
|
||||
<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 text-muted">{{ suffix }}</span>
|
||||
</template>
|
||||
</template>
|
||||
</ScrubInputDisplay>
|
||||
</div>
|
||||
</ScrubInputRoot>
|
||||
</template>
|
||||
|
|
|
|||
Loading…
Reference in a new issue