|
|
@ -20,18 +20,32 @@ enum TransitionDirection { |
|
|
|
Right = 'right', |
|
|
|
Right = 'right', |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
enum AnimationTarget { |
|
|
|
|
|
|
|
ArrowLeft = 'arrow-left', |
|
|
|
|
|
|
|
ArrowRight = 'arrow-right', |
|
|
|
|
|
|
|
OkButton = 'ok-button', |
|
|
|
|
|
|
|
SubmitButton = 'submit-button', |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const { md } = useBreakpoints(breakpointsTailwind) |
|
|
|
const { md } = useBreakpoints(breakpointsTailwind) |
|
|
|
|
|
|
|
|
|
|
|
const { v$, formState, formColumns, submitForm, submitted, secondsRemain, sharedFormView, onReset } = useSharedFormStoreOrThrow() |
|
|
|
const { v$, formState, formColumns, submitForm, submitted, secondsRemain, sharedFormView, sharedViewMeta, onReset } = |
|
|
|
|
|
|
|
useSharedFormStoreOrThrow() |
|
|
|
|
|
|
|
|
|
|
|
const isTransitioning = ref(false) |
|
|
|
const isTransitioning = ref(false) |
|
|
|
|
|
|
|
|
|
|
|
const transitionName = ref<TransitionDirection>(TransitionDirection.Left) |
|
|
|
const transitionName = ref<TransitionDirection>(TransitionDirection.Left) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const animationTarget = ref<AnimationTarget>(AnimationTarget.ArrowRight) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const isAnimating = ref(false) |
|
|
|
|
|
|
|
|
|
|
|
const el = ref<HTMLDivElement>() |
|
|
|
const el = ref<HTMLDivElement>() |
|
|
|
|
|
|
|
|
|
|
|
provide(DropZoneRef, el) |
|
|
|
provide(DropZoneRef, el) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const transitionDuration = computed(() => sharedViewMeta.value.transitionDuration || 250) |
|
|
|
|
|
|
|
|
|
|
|
const steps = computed(() => { |
|
|
|
const steps = computed(() => { |
|
|
|
if (!formColumns.value) return [] |
|
|
|
if (!formColumns.value) return [] |
|
|
|
|
|
|
|
|
|
|
@ -72,17 +86,27 @@ function transition(direction: TransitionDirection) { |
|
|
|
setTimeout(() => { |
|
|
|
setTimeout(() => { |
|
|
|
transitionName.value = |
|
|
|
transitionName.value = |
|
|
|
transitionName.value === TransitionDirection.Left ? TransitionDirection.Right : TransitionDirection.Left |
|
|
|
transitionName.value === TransitionDirection.Left ? TransitionDirection.Right : TransitionDirection.Left |
|
|
|
}, 500) |
|
|
|
}, transitionDuration.value / 2) |
|
|
|
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
|
setTimeout(() => { |
|
|
|
isTransitioning.value = false |
|
|
|
isTransitioning.value = false |
|
|
|
|
|
|
|
|
|
|
|
setTimeout(focusInput, 100) |
|
|
|
setTimeout(focusInput, 100) |
|
|
|
}, 1000) |
|
|
|
}, transitionDuration.value) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async function goNext() { |
|
|
|
function animate(target: AnimationTarget) { |
|
|
|
if (isLast.value) return |
|
|
|
animationTarget.value = target |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
isAnimating.value = true |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
|
|
|
isAnimating.value = false |
|
|
|
|
|
|
|
}, transitionDuration.value / 2) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function goNext(animationTarget?: AnimationTarget) { |
|
|
|
|
|
|
|
if (isLast.value || submitted.value) return |
|
|
|
|
|
|
|
|
|
|
|
if (!field.value || !field.value.title) return |
|
|
|
if (!field.value || !field.value.title) return |
|
|
|
|
|
|
|
|
|
|
@ -93,13 +117,22 @@ async function goNext() { |
|
|
|
if (!isValid) return |
|
|
|
if (!isValid) return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
transition(TransitionDirection.Left) |
|
|
|
animate(animationTarget || AnimationTarget.ArrowRight) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout( |
|
|
|
|
|
|
|
() => { |
|
|
|
|
|
|
|
transition(TransitionDirection.Left) |
|
|
|
|
|
|
|
|
|
|
|
goToNext() |
|
|
|
goToNext() |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
animationTarget === AnimationTarget.OkButton ? 300 : 0, |
|
|
|
|
|
|
|
) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async function goPrevious() { |
|
|
|
async function goPrevious(animationTarget?: AnimationTarget) { |
|
|
|
if (isFirst.value) return |
|
|
|
if (isFirst.value || submitted.value) return |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
animate(animationTarget || AnimationTarget.ArrowLeft) |
|
|
|
|
|
|
|
|
|
|
|
transition(TransitionDirection.Right) |
|
|
|
transition(TransitionDirection.Right) |
|
|
|
|
|
|
|
|
|
|
@ -128,8 +161,19 @@ function resetForm() { |
|
|
|
|
|
|
|
|
|
|
|
onReset(resetForm) |
|
|
|
onReset(resetForm) |
|
|
|
|
|
|
|
|
|
|
|
onKeyStroke(['ArrowLeft', 'ArrowDown'], goPrevious) |
|
|
|
onKeyStroke(['ArrowLeft', 'ArrowDown'], () => { |
|
|
|
onKeyStroke(['ArrowRight', 'ArrowUp', 'Enter', 'Space'], goNext) |
|
|
|
goPrevious(AnimationTarget.ArrowLeft) |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
onKeyStroke(['ArrowRight', 'ArrowUp'], () => { |
|
|
|
|
|
|
|
goNext(AnimationTarget.ArrowRight) |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
onKeyStroke(['Enter', 'Space'], () => { |
|
|
|
|
|
|
|
if (isLast.value) { |
|
|
|
|
|
|
|
submitForm() |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
goNext(AnimationTarget.OkButton) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => { |
|
|
|
onMounted(() => { |
|
|
|
focusInput() |
|
|
|
focusInput() |
|
|
@ -154,7 +198,7 @@ onMounted(() => { |
|
|
|
<div ref="el" class="pt-8 md:p-0 w-full h-full flex flex-col"> |
|
|
|
<div ref="el" class="pt-8 md:p-0 w-full h-full flex flex-col"> |
|
|
|
<div |
|
|
|
<div |
|
|
|
v-if="sharedFormView" |
|
|
|
v-if="sharedFormView" |
|
|
|
style="height: max(40vh, 250px); min-height: 250px" |
|
|
|
style="height: max(40vh, 225px); min-height: 225px" |
|
|
|
class="max-w-[max(33%,600px)] mx-auto flex flex-col justify-end" |
|
|
|
class="max-w-[max(33%,600px)] mx-auto flex flex-col justify-end" |
|
|
|
> |
|
|
|
> |
|
|
|
<div class="px-4 md:px-0 flex flex-col justify-end"> |
|
|
|
<div class="px-4 md:px-0 flex flex-col justify-end"> |
|
|
@ -171,7 +215,7 @@ onMounted(() => { |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<div class="h-full w-full flex items-center px-4 md:px-0"> |
|
|
|
<div class="h-full w-full flex items-center px-4 md:px-0"> |
|
|
|
<Transition :name="`slide-${transitionName}`" :duration="1000" mode="out-in"> |
|
|
|
<Transition :name="`slide-${transitionName}`" :duration="transitionDuration" mode="out-in"> |
|
|
|
<div |
|
|
|
<div |
|
|
|
ref="el" |
|
|
|
ref="el" |
|
|
|
:key="field.title" |
|
|
|
:key="field.title" |
|
|
@ -219,6 +263,11 @@ onMounted(() => { |
|
|
|
<div class="block text-[14px]" data-cy="nc-survey-form__field-description"> |
|
|
|
<div class="block text-[14px]" data-cy="nc-survey-form__field-description"> |
|
|
|
{{ field.description }} |
|
|
|
{{ field.description }} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<div v-if="field.uidt === UITypes.LongText" class="text-sm text-gray-500 flex flex-wrap items-center"> |
|
|
|
|
|
|
|
Shift <MdiAppleKeyboardShift class="mx-1 text-primary" /> + Enter |
|
|
|
|
|
|
|
<MaterialSymbolsKeyboardReturn class="mx-1 text-primary" /> to make a line break |
|
|
|
|
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
@ -227,10 +276,14 @@ onMounted(() => { |
|
|
|
<div class="flex-1 flex justify-center"> |
|
|
|
<div class="flex-1 flex justify-center"> |
|
|
|
<div v-if="isLast && !submitted && !v$.$invalid" class="text-center my-4"> |
|
|
|
<div v-if="isLast && !submitted && !v$.$invalid" class="text-center my-4"> |
|
|
|
<button |
|
|
|
<button |
|
|
|
|
|
|
|
:class=" |
|
|
|
|
|
|
|
animationTarget === AnimationTarget.SubmitButton && isAnimating |
|
|
|
|
|
|
|
? 'transform translate-y-[1px] translate-x-[1px] ring ring-accent ring-opacity-100' |
|
|
|
|
|
|
|
: '' |
|
|
|
|
|
|
|
" |
|
|
|
type="submit" |
|
|
|
type="submit" |
|
|
|
class="uppercase scaling-btn prose-sm" |
|
|
|
class="uppercase scaling-btn prose-sm" |
|
|
|
data-cy="nc-survey-form__btn-submit" |
|
|
|
data-cy="nc-survey-form__btn-submit" @click="submitForm" |
|
|
|
@click="submitForm" |
|
|
|
|
|
|
|
> |
|
|
|
> |
|
|
|
{{ $t('general.submit') }} |
|
|
|
{{ $t('general.submit') }} |
|
|
|
</button> |
|
|
|
</button> |
|
|
@ -245,7 +298,12 @@ onMounted(() => { |
|
|
|
<button |
|
|
|
<button |
|
|
|
class="bg-opacity-100 scaling-btn flex items-center gap-1" |
|
|
|
class="bg-opacity-100 scaling-btn flex items-center gap-1" |
|
|
|
data-cy="nc-survey-form__btn-next" |
|
|
|
data-cy="nc-survey-form__btn-next" |
|
|
|
:class="v$.localState[field.title]?.$error ? 'after:!bg-gray-100 after:!ring-red-500' : ''" |
|
|
|
:class="[ |
|
|
|
|
|
|
|
v$.localState[field.title]?.$error ? 'after:!bg-gray-100 after:!ring-red-500' : '', |
|
|
|
|
|
|
|
animationTarget === AnimationTarget.OkButton && isAnimating |
|
|
|
|
|
|
|
? 'transform translate-y-[2px] translate-x-[2px] after:(!ring !ring-accent !ring-opacity-100)' |
|
|
|
|
|
|
|
: '', |
|
|
|
|
|
|
|
]" |
|
|
|
@click="goNext" |
|
|
|
@click="goNext" |
|
|
|
> |
|
|
|
> |
|
|
|
<Transition name="fade"> |
|
|
|
<Transition name="fade"> |
|
|
@ -261,7 +319,7 @@ onMounted(() => { |
|
|
|
|
|
|
|
|
|
|
|
<!-- todo: i18n --> |
|
|
|
<!-- todo: i18n --> |
|
|
|
<div class="hidden md:flex text-sm text-gray-500 items-center gap-1"> |
|
|
|
<div class="hidden md:flex text-sm text-gray-500 items-center gap-1"> |
|
|
|
Press Enter <MaterialSymbolsKeyboardReturn class="mt-1" /> |
|
|
|
Press Enter <MaterialSymbolsKeyboardReturn class="text-primary" /> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
@ -319,9 +377,13 @@ onMounted(() => { |
|
|
|
> |
|
|
|
> |
|
|
|
<a-tooltip :title="isFirst ? '' : 'Go to previous'" :mouse-enter-delay="0.25" :mouse-leave-delay="0"> |
|
|
|
<a-tooltip :title="isFirst ? '' : 'Go to previous'" :mouse-enter-delay="0.25" :mouse-leave-delay="0"> |
|
|
|
<button |
|
|
|
<button |
|
|
|
|
|
|
|
:class=" |
|
|
|
|
|
|
|
animationTarget === AnimationTarget.ArrowLeft && isAnimating |
|
|
|
|
|
|
|
? 'transform translate-y-[1px] translate-x-[1px] text-primary' |
|
|
|
|
|
|
|
: '' |
|
|
|
|
|
|
|
" |
|
|
|
class="p-0.5 flex items-center group color-transition" |
|
|
|
class="p-0.5 flex items-center group color-transition" |
|
|
|
data-cy="nc-survey-form__icon-prev" |
|
|
|
data-cy="nc-survey-form__icon-prev" @click="goPrevious" |
|
|
|
@click="goPrevious" |
|
|
|
|
|
|
|
> |
|
|
|
> |
|
|
|
<MdiChevronLeft :class="isFirst ? 'text-gray-300' : 'group-hover:text-accent'" class="text-2xl md:text-md" /> |
|
|
|
<MdiChevronLeft :class="isFirst ? 'text-gray-300' : 'group-hover:text-accent'" class="text-2xl md:text-md" /> |
|
|
|
</button> |
|
|
|
</button> |
|
|
@ -332,9 +394,17 @@ onMounted(() => { |
|
|
|
:mouse-enter-delay="0.25" |
|
|
|
:mouse-enter-delay="0.25" |
|
|
|
:mouse-leave-delay="0" |
|
|
|
:mouse-leave-delay="0" |
|
|
|
> |
|
|
|
> |
|
|
|
<button class="p-0.5 flex items-center group color-transition" data-cy="nc-survey-form__icon-next" @click="goNext"> |
|
|
|
<button |
|
|
|
|
|
|
|
:class=" |
|
|
|
|
|
|
|
animationTarget === AnimationTarget.ArrowRight && isAnimating |
|
|
|
|
|
|
|
? 'transform translate-y-[1px] translate-x-[-1px] text-primary' |
|
|
|
|
|
|
|
: '' |
|
|
|
|
|
|
|
" |
|
|
|
|
|
|
|
class="p-0.5 flex items-center group color-transition" |
|
|
|
|
|
|
|
data-cy="nc-survey-form__icon-next" @click="goNext" |
|
|
|
|
|
|
|
> |
|
|
|
<MdiChevronRight |
|
|
|
<MdiChevronRight |
|
|
|
:class="isLast || v$.localState[field.title]?.$error ? 'text-gray-300' : 'group-hover:text-accent'" |
|
|
|
:class="[isLast || v$.localState[field.title]?.$error ? 'text-gray-300' : 'group-hover:text-accent']" |
|
|
|
class="text-2xl md:text-md" |
|
|
|
class="text-2xl md:text-md" |
|
|
|
/> |
|
|
|
/> |
|
|
|
</button> |
|
|
|
</button> |
|
|
|