Mobile input tweaks (#9686)

- Buttons are marked as `touch-manipulation` so double-tapping on them
doesn't initiate a zoom.
- Move scrubable inputs to usePointerSwipe
- Strangely, swipe direction was inverted on mobile. This solves the
issue and simplifies code
  - Moves event handlers into the scrubbable input component
- Make the slightly bigger buttons only apply when on mobile.
- Updates the workflows dropdown to have a check by the activeWorkflow
and truncate workflow names
- Displays dropzones (for the image preview) on mobile, but disables the
prompt to drag and drop an image if none is selected.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9686-Mobile-input-tweaks-31f6d73d3650811d9025d0cd1ac58534)
by [Unito](https://www.unito.io)
This commit is contained in:
AustinMroz
2026-03-09 23:08:42 -07:00
committed by GitHub
parent fbcd36d355
commit 1d7a5b9e0b
8 changed files with 60 additions and 83 deletions

View File

@@ -120,7 +120,7 @@ function getDropIndicator(node: LGraphNode) {
return {
iconClass: 'icon-[lucide--image]',
imageUrl: buildImageUrl(),
label: t('linearMode.dragAndDropImage'),
label: props.mobile ? undefined : t('linearMode.dragAndDropImage'),
onClick: () => node.widgets?.[1]?.callback?.(undefined)
}
}
@@ -206,14 +206,14 @@ defineExpose({ runButtonClick })
<DropZone
:on-drag-over="nodeData.onDragOver"
:on-drag-drop="nodeData.onDragDrop"
:drop-indicator="mobile ? undefined : nodeData.dropIndicator"
:drop-indicator="nodeData.dropIndicator"
class="text-muted-foreground"
>
<NodeWidgets
:node-data
:class="
cn(
'gap-y-3 rounded-lg py-3 *:has-[textarea]:h-50 **:[.col-span-2]:grid-cols-1 **:[.h-7]:h-10',
'gap-y-3 rounded-lg py-3 *:has-[textarea]:h-50 **:[.col-span-2]:grid-cols-1 not-md:**:[.h-7]:h-10',
nodeData.hasErrors &&
'ring-2 ring-node-stroke-error ring-inset'
)
@@ -325,4 +325,9 @@ defineExpose({ runButtonClick })
</section>
</div>
</div>
<div
v-else-if="mobile"
class="flex size-full items-center bg-base-background p-4 text-center"
v-text="t('linearMode.mobileNoWorkflow')"
/>
</template>

View File

@@ -8,7 +8,6 @@ import DropdownMenu from '@/components/common/DropdownMenu.vue'
import AssetsSidebarTab from '@/components/sidebar/tabs/AssetsSidebarTab.vue'
import CurrentUserButton from '@/components/topbar/CurrentUserButton.vue'
import Button from '@/components/ui/button/Button.vue'
import Popover from '@/components/ui/Popover.vue'
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
import { useWorkflowService } from '@/platform/workflow/core/services/workflowService'
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
@@ -76,13 +75,16 @@ function onClick(index: number) {
}
const workflowsEntries = computed(() => {
return workflowStore.openWorkflows.map((w) => ({
label: w.filename,
icon: w.activeState?.extra?.linearMode
? 'icon-[lucide--panels-top-left] bg-primary-background'
: undefined,
command: () => workflowService.openWorkflow(w)
}))
return [
...workflowStore.openWorkflows.map((w) => ({
label: w.filename,
icon: w.activeState?.extra?.linearMode
? 'icon-[lucide--panels-top-left] bg-primary-background'
: undefined,
command: () => workflowService.openWorkflow(w),
checked: workflowStore.activeWorkflow === w
}))
]
})
const menuEntries = computed<MenuItem[]>(() => [
@@ -157,9 +159,9 @@ const menuEntries = computed<MenuItem[]>(() => [
class="flex h-16 w-full items-center gap-3 border-b border-border-subtle bg-base-background px-4 py-3"
>
<DropdownMenu :entries="menuEntries" />
<Popover
<DropdownMenu
:entries="workflowsEntries"
class="w-(--reka-popover-content-available-width)"
class="max-h-[40vh] w-(--reka-dropdown-menu-content-available-width)"
:collision-padding="20"
>
<template #button>
@@ -179,7 +181,7 @@ const menuEntries = computed<MenuItem[]>(() => [
/>
</div>
</template>
</Popover>
</DropdownMenu>
<CurrentUserButton v-if="isLoggedIn" :show-arrow="false" />
</header>
<div class="size-full rounded-b-4xl contain-content">

View File

@@ -121,12 +121,6 @@ const buttonsDisabled = computed(() => {
)
})
function updateValueBy(delta: number) {
const max = filteredProps.value.max ?? Number.MAX_VALUE
const min = filteredProps.value.min ?? -Number.MAX_VALUE
modelValue.value = Math.min(max, Math.max(min, modelValue.value + delta))
}
const buttonTooltip = computed(() => {
if (buttonsDisabled.value) {
return 'Increment/decrement disabled: value exceeds JavaScript precision limit (±2^53)'
@@ -171,10 +165,6 @@ const inputAriaAttrs = computed(() => ({
:parse-value="parseWidgetValue"
:input-attrs="inputAriaAttrs"
:class="cn(WidgetInputBaseClass, 'relative flex h-7 grow text-xs')"
@keydown.up.prevent="updateValueBy(stepValue)"
@keydown.down.prevent="updateValueBy(-stepValue)"
@keydown.page-up.prevent="updateValueBy(10 * stepValue)"
@keydown.page-down.prevent="updateValueBy(-10 * stepValue)"
>
<template #background>
<div