uNmAnNeR / imaskjs

vanilla javascript input mask
https://imask.js.org
MIT License
4.96k stars 258 forks source link

Class constructor cannot be invoked without "new" #1064

Open DenisLantero opened 3 months ago

DenisLantero commented 3 months ago

I have an issue with vue-imask in Vue 3 using the setup script, I tried resetting both my npm and pnpm caches (I am using pnpm in my project), but neither seemed to fix the issue.

The problem arises as soon as I try to redefine the default blocks for the date mask, d, m and Y.

This is a simplified version of my code:

<template>
  <BsdkInputText
    v-bind="$props.inputProps"
    v-model="value"
    :mask="$props.inputProps?.mask || mask"
    @click="togglePopover"
  />
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { IMask } from 'vue-imask'
import type { FactoryArg } from 'imask'

const value = defineModel<string | null>()

const pattern = computed(() => {
  // Get the user's locale
  const userLocale = navigator.language

  // Create a date format using the user's locale
  const dateFormat = new Intl.DateTimeFormat(userLocale)

  // Get the format parts (e.g., day, month, year)
  const formatParts = dateFormat.formatToParts(new Date())

  // Create a mask pattern based on the format parts
  const maskPattern = formatParts.map((part) => {
    switch (part.type) {
      case 'day': return 'd'
      case 'month': return 'm'
      case 'year': return 'Y'
      case 'literal': return '/'
      default: return part.value
    }
  }).join('')

  return maskPattern
})

const mask = {
  mask: Date,
  lazy: false,
  pattern: pattern.value,
  blocks: {
    d: {
      mask: IMask.MaskedRange,
      from: 1,
      to: 31,
      maxLength: 2,
    },
    m: {
      mask: IMask.MaskedRange,
      from: 1,
      to: 12,
      maxLength: 2,
    },
    Y: {
      mask: IMask.MaskedRange,
      from: 1900,
      to: 9999,
    },
  },
  format: (date: Date) => {
    let day: string | number = date.getDate()
    let month: string | number = date.getMonth() + 1
    const year: string | number = date.getFullYear()

    if (day < 10) {
      day = `0${day}`
    }

    if (month < 10) {
      month = `0${month}`
    }

    return `${day}/${month}/${year}`
  },
  parse: (str: string) => {
    const [day, month, year] = str.split('/')

    return new Date(+year, +month - 1, +day)
  },

  min: props.minDate,
  max: props.maxDate,

  autofix: true,
} as FactoryArg
</script>

And this is my BsdkInputText component:

<template>
  <input
    :id
    v-imask="mask"
    :value="modelValue"
    :type
    :class="mergedInputClasses"
    :placeholder
    :required="field.required"
    :disabled="field.disabled"
    @accept="onAccept"
    @complete="onComplete"
    @input="onUpdateModelValue"
  >
</template>

<script setup lang="ts">
import { type Ref, computed, inject, ref } from 'vue'
import type { ClassEntries } from '@binarysystem/bsdk-ui-tailwind-merge'
import { useTailwindMerge } from '@binarysystem/bsdk-ui-tailwind-merge'
import { IMaskDirective as vImask } from 'vue-imask'

import type { Field } from '@binarysystem/bsdk-ui-form-field'

import type { FactoryArg } from 'imask'

export interface Props {
  id: string
  modelValue?: string | null
  type?: string
  placeholder?: string
  required?: boolean
  disabled?: boolean
  invalid?: boolean
  inputClasses?: ClassEntries
  mask?: FactoryArg
}
const props = withDefaults(defineProps<Props>(), {
  type: 'text',
  required: false,
  disabled: false,
  invalid: false,
})

const emit = defineEmits(['update:modelValue'])

const field = inject<Ref<Field>>('field', ref(props))
const { mergeClasses } = useTailwindMerge()

const mergedInputClasses = computed(() => {
// Tailwind classes logic
})

function onAccept (e: any) {
  const maskRef = e.detail

  emit('update:modelValue', maskRef.value)
}

function onComplete (e: any) {
  const maskRef = e.detail

  emit('update:modelValue', maskRef.value)
}

function onUpdateModelValue (e: any) {
  if (props.mask) {
    return
  }

  emit('update:modelValue', e.target.value)
}
</script>

First, I had to use a hack to make the v-imask directive work in Vue 3 while using the setup script, since I tried adding it to the app instance using the following, but there was a problem with types:

import type { App } from 'vue'
import '@assets/stylesheets/main.css'
import { IMaskDirective } from 'vue-imask'
import BsdkDatePicker from '@/components/BsdkDatePicker.vue'
import 'v-calendar/style.css'

export { BsdkDatePicker }

export default {
  install: (app: App) => {
    app.component('BsdkDatePicker', BsdkDatePicker)
    app.directive('imask', IMaskDirective)
  },
}

And this was the TypeScript error:

Argument of type '{ [x: string]: string | (<Opts extends FactoryArg>(el: DirectiveMaskElement<Opts>, { value: options }: { value: Opts; }) => void); name: string; }' is not assignable to parameter of type 'Directive<any, any>'.
  Type '{ [x: string]: string | (<Opts extends FactoryArg>(el: DirectiveMaskElement<Opts>, { value: options }: { value: Opts; }) => void); name: string; }' has no properties in common with type 'ObjectDirective<any, any>'.ts(2345

Anyway, the real problem is that I get the Class constructor x cannot be invoked without "new" error as soon as I try to redefine the default date blocks. Is this caused by the way I am using the vue-imask plugin, a misuse of the blocks in the mask, or anything else?

Note that if I try to remove the definition of the blocks and use the default ones, everything works correctly, but I wanted to define the blocks to have a placeholder like this DD/MM/YYYY instead of the default one __/__/____.

Please let me know, and thanks in advance.

Versions:

Originally posted by @DenisLantero in https://github.com/uNmAnNeR/imaskjs/discussions/1012#discussioncomment-10161119