<commit_before>// leave NAMED_MASKS at top of file (code referenced from docs)
const NAMED_MASKS = {
  date: '####/##/##',
  datetime: '####/##/## ##:##',
  time: '##:##',
  fulltime: '##:##:##',
  phone: '(###) ### - ####',
  card: '#### #### #### ####'
}

const TOKENS = {
  '#': { pattern: '[\\d]', negate: '[^\\d]' },

  S: { pattern: '[a-zA-Z]', negate: '[^a-zA-Z]' },
  N: { pattern: '[0-9a-zA-Z]', negate: '[^0-9a-zA-Z]' },

  A: { pattern: '[a-zA-Z]', negate: '[^a-zA-Z]', transform: v => v.toLocaleUpperCase() },
  a: { pattern: '[a-zA-Z]', negate: '[^a-zA-Z]', transform: v => v.toLocaleLowerCase() },

  X: { pattern: '[0-9a-zA-Z]', negate: '[^0-9a-zA-Z]', transform: v => v.toLocaleUpperCase() },
  x: { pattern: '[0-9a-zA-Z]', negate: '[^0-9a-zA-Z]', transform: v => v.toLocaleLowerCase() }
}

const KEYS = Object.keys(TOKENS)
KEYS.forEach(key => {
  TOKENS[key].regex = new RegExp(TOKENS[key].pattern)
})

const
  tokenRegexMask = new RegExp('\\\\([^.*+?^${}()|([\\]])|([.*+?^${}()|[\\]])|([' + KEYS.join('') + '])|(.)', 'g'),
  escRegex = /[.*+?^${}()|[\]\\]/g

const MARKER = String.fromCharCode(1)

export default {
  props: {
    mask: String,
    reverseFillMask: Boolean,
    fillMask: [Boolean, String],
    unmaskedValue: Boolean
  },

  watch: {
    type () {
      this.__updateMaskInternals()
    },

    mask (v) {
      if (v !== void 0) {
        this.__updateMaskValue(this.innerValue, true)
      }
      else {
        const val = this.__unmask(this.innerValue)
        this.__updateMaskInternals()
        this.value !== val && this.$emit('input', val)
      }
    },

    fillMask () {
      this.hasMask === true && this.__updateMaskValue(this.innerValue, true)
    },

    reverseFillMask () {
      this.hasMask === true && this.__updateMaskValue(this.innerValue, true)
    },

    unmaskedValue () {
      this.hasMask === true && this.__updateMaskValue(this.innerValue)
    }
  },

  methods: {
    __getInitialMaskedValue () {
      this.__updateMaskInternals()

      if (this.hasMask === true) {
        const masked = this.__mask(this.__unmask(this.value))

        return this.fillMask !== false
          ? this.__fillWithMask(masked)
          : masked
      }

      return this.value
    },

    __getPaddedMaskMarked (size) {
      if (size < this.maskMarked.length) {
        return this.maskMarked.slice(-size)
      }

      let
        maskMarked = this.maskMarked,
        padPos = maskMarked.indexOf(MARKER),
        pad = ''

      if (padPos > -1) {
        for (let i = size - maskMarked.length; i > 0; i--) {
          pad += MARKER
        }

        maskMarked = maskMarked.slice(0, padPos) + pad + maskMarked.slice(padPos)
      }

      return maskMarked
    },

    __updateMaskInternals () {
      this.hasMask = this.mask !== void 0 &&
        this.mask.length > 0 &&
        ['text', 'search', 'url', 'tel', 'password'].includes(this.type)

      if (this.hasMask === false) {
        this.computedUnmask = void 0
        this.maskMarked = ''
        this.maskReplaced = ''
        return
      }

      const
        computedMask = NAMED_MASKS[this.mask] === void 0
          ? this.mask
          : NAMED_MASKS[this.mask],
        fillChar = typeof this.fillMask === 'string' && this.fillMask.length > 0
          ? this.fillMask.slice(0, 1)
          : '_',
        fillCharEscaped = fillChar.replace(escRegex, '\\$&'),
        unmask = [],
        extract = [],
        mask = []

      let
        firstMatch = this.reverseFillMask === true,
        unmaskChar = '',
        negateChar = ''

      computedMask.replace(tokenRegexMask, (_, char1, esc, token, char2) => {
        if (token !== void 0) {
          const c = TOKENS[token]
          mask.push(c)
          negateChar = c.negate
          if (firstMatch === true) {
            extract.push('(?:' + negateChar + '+?)?(' + c.pattern + '+)?(?:' + negateChar + '+?)?(' + c.pattern + '+)?')
            firstMatch = false
          }
          extract.push('(?:' + negateChar + '+?)?(' + c.pattern + ')?')
        }
        else if (esc !== void 0) {
          unmaskChar = '\\' + esc
          mask.push(esc)
          unmask.push('([^' + unmaskChar + ']+)?' + unmaskChar + '?')
        }
        else {
          const c = char1 !== void 0 ? char1 : char2
          unmaskChar = c.replace(escRegex, '\\\\$&')
          mask.push(c)
          unmask.push('([^' + unmaskChar + ']+)?' + unmaskChar + '?')
        }
      })

      const
        unmaskMatcher = new RegExp(
          '^' +
          unmask.join('') +
          '(' + (unmaskChar === '' ? '.' : '[^' + unmaskChar + ']') + '+)?' +
          '$'
        ),
        extractMatcher = new RegExp(
          '^' +
          (this.reverseFillMask === true ? fillCharEscaped + '*' : '') +
          extract.join('') +
          '(' + (negateChar === '' ? '.' : negateChar) + '+)?' +
          (this.reverseFillMask === true ? '' : fillCharEscaped + '*') +
          '$'
        )

      this.computedMask = mask
      this.computedUnmask = val => {
        const unmaskMatch = unmaskMatcher.exec(val)
        if (unmaskMatch !== null) {
          val = unmaskMatch.slice(1).join('')
        }

        const extractMatch = extractMatcher.exec(val)
        if (extractMatch !== null) {
          return extractMatch.slice(1).join('')
        }

        return val
      }
      this.maskMarked = mask.map(v => typeof v === 'string' ? v : MARKER).join('')
      this.maskReplaced = this.maskMarked.split(MARKER).join(fillChar)
    },

    __updateMaskValue (rawVal, updateMaskInternals) {
      const
        inp = this.$refs.input,
        oldCursor = this.reverseFillMask === true
          ? inp.value.length - inp.selectionEnd
          : inp.selectionEnd,
        unmasked = this.__unmask(rawVal)

      // Update here so unmask uses the original fillChar
      updateMaskInternals === true && this.__updateMaskInternals()

      const
        masked = this.fillMask !== false
          ? this.__fillWithMask(this.__mask(unmasked))
          : this.__mask(unmasked),
        changed = this.innerValue !== masked

      // We want to avoid "flickering" so we set value immediately
      inp.value !== masked && (inp.value = masked)
<commit_msg>fix(QInput): Fix freeze when using mask<commit_after>// leave NAMED_MASKS at top of file (code referenced from docs)
const NAMED_MASKS = {
  date: '####/##/##',
  datetime: '####/##/## ##:##',
  time: '##:##',
  fulltime: '##:##:##',
  phone: '(###) ### - ####',
  card: '#### #### #### ####'
}

const TOKENS = {
  '#': { pattern: '[\\d]', negate: '[^\\d]' },

  S: { pattern: '[a-zA-Z]', negate: '[^a-zA-Z]' },
  N: { pattern: '[0-9a-zA-Z]', negate: '[^0-9a-zA-Z]' },

  A: { pattern: '[a-zA-Z]', negate: '[^a-zA-Z]', transform: v => v.toLocaleUpperCase() },
  a: { pattern: '[a-zA-Z]', negate: '[^a-zA-Z]', transform: v => v.toLocaleLowerCase() },

  X: { pattern: '[0-9a-zA-Z]', negate: '[^0-9a-zA-Z]', transform: v => v.toLocaleUpperCase() },
  x: { pattern: '[0-9a-zA-Z]', negate: '[^0-9a-zA-Z]', transform: v => v.toLocaleLowerCase() }
}

const KEYS = Object.keys(TOKENS)
KEYS.forEach(key => {
  TOKENS[key].regex = new RegExp(TOKENS[key].pattern)
})

const
  tokenRegexMask = new RegExp('\\\\([^.*+?^${}()|([\\]])|([.*+?^${}()|[\\]])|([' + KEYS.join('') + '])|(.)', 'g'),
  escRegex = /[.*+?^${}()|[\]\\]/g

const MARKER = String.fromCharCode(1)

export default {
  props: {
    mask: String,
    reverseFillMask: Boolean,
    fillMask: [Boolean, String],
    unmaskedValue: Boolean
  },

  watch: {
    type () {
      this.__updateMaskInternals()
    },

    mask (v) {
      if (v !== void 0) {
        this.__updateMaskValue(this.innerValue, true)
      }
      else {
        const val = this.__unmask(this.innerValue)
        this.__updateMaskInternals()
        this.value !== val && this.$emit('input', val)
      }
    },

    fillMask () {
      this.hasMask === true && this.__updateMaskValue(this.innerValue, true)
    },

    reverseFillMask () {
      this.hasMask === true && this.__updateMaskValue(this.innerValue, true)
    },

    unmaskedValue () {
      this.hasMask === true && this.__updateMaskValue(this.innerValue)
    }
  },

  methods: {
    __getInitialMaskedValue () {
      this.__updateMaskInternals()

      if (this.hasMask === true) {
        const masked = this.__mask(this.__unmask(this.value))

        return this.fillMask !== false
          ? this.__fillWithMask(masked)
          : masked
      }

      return this.value
    },

    __getPaddedMaskMarked (size) {
      if (size < this.maskMarked.length) {
        return this.maskMarked.slice(-size)
      }

      let
        maskMarked = this.maskMarked,
        padPos = maskMarked.indexOf(MARKER),
        pad = ''

      if (padPos > -1) {
        for (let i = size - maskMarked.length; i > 0; i--) {
          pad += MARKER
        }

        maskMarked = maskMarked.slice(0, padPos) + pad + maskMarked.slice(padPos)
      }

      return maskMarked
    },

    __updateMaskInternals () {
      this.hasMask = this.mask !== void 0 &&
        this.mask.length > 0 &&
        ['text', 'search', 'url', 'tel', 'password'].includes(this.type)

      if (this.hasMask === false) {
        this.computedUnmask = void 0
        this.maskMarked = ''
        this.maskReplaced = ''
        return
      }

      const
        computedMask = NAMED_MASKS[this.mask] === void 0
          ? this.mask
          : NAMED_MASKS[this.mask],
        fillChar = typeof this.fillMask === 'string' && this.fillMask.length > 0
          ? this.fillMask.slice(0, 1)
          : '_',
        fillCharEscaped = fillChar.replace(escRegex, '\\$&'),
        unmask = [],
        extract = [],
        mask = []

      let
        firstMatch = this.reverseFillMask === true,
        unmaskChar = '',
        negateChar = ''

      computedMask.replace(tokenRegexMask, (_, char1, esc, token, char2) => {
        if (token !== void 0) {
          const c = TOKENS[token]
          mask.push(c)
          negateChar = c.negate
          if (firstMatch === true) {
            extract.push('(?:' + negateChar + '+?)?(' + c.pattern + '+)?(?:' + negateChar + '+?)?(' + c.pattern + '+)?')
            firstMatch = false
          }
          extract.push('(?:' + negateChar + '+?)?(' + c.pattern + ')?')
        }
        else if (esc !== void 0) {
          unmaskChar = '\\' + (esc === '\\' ? '' : esc)
          mask.push(esc)
          unmask.push('([^' + unmaskChar + ']+)?' + unmaskChar + '?')
        }
        else {
          const c = char1 !== void 0 ? char1 : char2
          unmaskChar = c === '\\' ? '\\\\\\\\' : c.replace(escRegex, '\\\\$&')
          mask.push(c)
          unmask.push('([^' + unmaskChar + ']+)?' + unmaskChar + '?')
        }
      })

      const
        unmaskMatcher = new RegExp(
          '^' +
          unmask.join('') +
          '(' + (unmaskChar === '' ? '.' : '[^' + unmaskChar + ']') + '+)?' +
          '$'
        ),
        extractLast = extract.length - 1,
        extractMatcher = extract.map((re, index) => {
          if (index === 0 && this.reverseFillMask === true) {
            return new RegExp('^' + fillCharEscaped + '*' + re)
          }
          else if (index === extractLast) {
            return new RegExp(
              '^' + re +
              '(' + (negateChar === '' ? '.' : negateChar) + '+)?' +
              (this.reverseFillMask === true ? '$' : fillCharEscaped + '*')
            )
          }

          return new RegExp('^' + re)
        })

      this.computedMask = mask
      this.computedUnmask = val => {
        const unmaskMatch = unmaskMatcher.exec(val)
        if (unmaskMatch !== null) {
          val = unmaskMatch.slice(1).join('')
        }

        const
          extractMatch = [],
          extractMatcherLength = extractMatcher.length

        for (let i = 0, str = val; i < extractMatcherLength; i++) {
          const m = extractMatcher[i].exec(str)

          if (m === null) {
            break
          }

          str = str.slice(m.shift().length)
          extractMatch.push(...m)
        }
        if (extractMatch.length > 0) {
          return extractMatch.join('')
        }

        return val
      }
      this.maskMarked = mask.map(v => typeof v === 'string' ? v : MARKER).join('')
      this.maskReplaced = this.maskMarked.split(MARKER).join(fillChar)
    },

    __updateMaskValue (rawVal, updateMaskInternals) {
      const
        inp = this.$refs.input,
        oldCursor = this.reverseFillMask === true
          ? inp.value.length - inp.selectionEnd
          : inp.selectionEnd,
        unmasked = this.__unmask(rawVal)

      // Update here so unmask uses the original fillChar
      updateMaskInternals === true && this.__updateMaskInternals()

      const
        masked = this.fillMask !== false
          ? this.__fillWithMask(this.__mask(unmasked))
          : this.__mask(unmasked),
        changed = this.innerValue !== masked

      // We want to avoid "flickering" so we set value immediately
      inp.value !== masked && (inp.value = masked)

{'fraction_non_alphanumeric': 0.18874273478127868, 'fraction_numerical': 0.005429795044356072, 'mean_word_length': 2.0020661157024793, 'pattern_counts': {'":': 0, '<': 6, '<?xml version=': 0, '>': 29, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3180689', 'n_tokens_mistral': 4503, 'n_tokens_neox': 4354, 'n_words': 804}