describe('Editor', ->
  beforeEach( ->
    @container = $('#editor-container').html('

').get(0)
    Quill.Lib.EventEmitter2.events = Quill.events
    emitter = new Quill.Lib.EventEmitter2
    @editor = new Quill.Editor(@container.firstChild, emitter, { formats: Quill.DEFAULTS.formats })
  )

  describe('_deleteAt()', ->
    tests =
      'part of line':
        initial:  ['
0123
']
        expected: ['
03
']
        index: 1, length: 2
      'part of multiple lines':
        initial:  ['
0123
', '
5678
']
        expected: ['
0178
']
        index: 2, length: 5
      'entire line keeping trailing newline':
        initial:  ['
0123
', '
5678
']
        expected: ['

', '
5678
']
        index: 0, length: 4
      'trailing newline':
        initial:  ['
0123
', '
5678
']
        expected: ['
01235678
']
        index: 4, length: 1
      'entire line and parts of neighbors':
        initial:  ['
0123
', '
5678
', '
abcd
']
        expected: ['
01cd
']
        index: 2, length: 10
      'entire doc':
        initial:  ['
0123
', '
5678
']
        expected: ['']
        index: 0, length: 10
      'entire doc except trailing newline':
        initial:  ['
0123
', '
5678
']
        expected: ['

']
        index: 0, length: 9
      'last trailing newline':
        initial:  ['
0123
']
        expected: ['
0123
']
        index: 4, length: 1

    _.each(tests, (test, name) ->
      it(name, ->
        @editor.doc.setHTML(test.initial.join(''))
        @editor._deleteAt(test.index, test.length)
        @editor.doc.optimizeLines()
        expect(@editor.root).toEqualHTML(test.expected.join(''), true)
      )
    )
  )

  describe('_formatAt()', ->
    tests =
      'part of line':
        initial:  ['
0123
']
        expected: Tandem.Delta.makeDelta({ startLength: 0, endLength: 5, ops: [
          { value: '0' }
          { value: '12', attributes: { bold: true } }
          { value: '3\n' }
        ]})
        index: 1, length: 2, name: 'bold', value: true
      'trailing newline with normal format':
        initial:  ['
0123
']
        expected: Tandem.Delta.makeDelta({ startLength: 0, endLength: 5, ops: [
          { value: '0123' }
          { value: '\n', attributes: { bold: true } }
        ]})
        index: 4, length: 1, name: 'bold', value: true
      'trailing newline with line format':
        initial:  ['
0123
']
        expected: Tandem.Delta.makeDelta({ startLength: 0, endLength: 5, ops: [
          { value: '0123' }
          { value: '\n', attributes: { align: 'right' } }
        ]})
        index: 4, length: 1, name: 'align', value: 'right'
      'part of multiple lines':
        initial:  ['
0123
', '
5678
']
        expected: Tandem.Delta.makeDelta({ startLength: 0, endLength: 10, ops: [
          { value: '01' }
          { value: '23\n', attributes: { strike: true } }
          { value: '56', attributes: { bold: true, strike: true } }
          { value: '78', attributes: { bold: true } }
          { value: '\n' }
        ]})
        index: 2, length: 5, name: 'strike', value: true
      'line contents with line level format':
        initial:  ['
0123
']
        expected: Tandem.Delta.makeDelta({ startLength: 0, endLength: 5, ops: [
          { value: '0123\n' }
        ]})
        index: 1, length: 2, name: 'align', value: 'right'

    _.each(tests, (test, name) ->
      it(name, ->
        @editor.doc.setHTML(test.initial.join(''))
        @editor._formatAt(test.index, test.length, test.name, test.value)
        @editor.doc.optimizeLines()
        expect(@editor.doc.toDelta()).toEqualDelta(test.expected)
      )
    )
  )

  describe('_insertAt()', ->
    describe('empty', ->
      tests =
        'text':
          expected: '
Test
'
          text: 'Test'
        'formatted text':
          expected: '
Test
'
          text: 'Test', formats: { bold: true, italic: true }
        'newline':
          expected: '

'
          text: '\n'
        'multiple newlines':
          expected: '

'
          text: '\n\n'
        'multiline insert':
          expected: '
A

B
'
          text: 'A\nB\n'

      _.each(tests, (test, name) ->
        it(name, ->
          @editor._insertAt(0, test.text, test.formats)
          @editor.doc.optimizeLines()
          expect(@editor.root).toEqualHTML(test.expected, true)
        )
      )

      it('formatted newline', ->
        @editor._insertAt(0, 'A\n', { bold: true })
        @editor.doc.optimizeLines()
        expect(@editor.root).toEqualHTML('
A
', true)
        expect(@editor.doc.toDelta()).toEqualDelta(Tandem.Delta.makeInsertDelta(0, 0, 'A\n', { bold: true }))
      )

      it('multiple formatted newlines', ->
        @editor._insertAt(0, 'A\nB\n', { bold: true })
        @editor.doc.optimizeLines()
        expect(@editor.root).toEqualHTML('
A

B
', true)
        expect(@editor.doc.toDelta()).toEqualDelta(Tandem.Delta.makeInsertDelta(0, 0, 'A\nB\n', { bold: true }))
      )
    )

    describe('nonempty', ->
      tests =
        'insert in middle of text':
          expected: ['
01A234567
']
          index: 2, text: 'A'
        'insert in middle of formatted text':
          expected: ['
012345A67
']
          index: 6, text: 'A'
        'insert formatted text with match':
          expected: ['
012345A67
']
          index: 6, text: 'A', formats: { bold: true, italic: true }
        'prepend newline':
          expected: ['

', '
01234567
']
          index: 0, text: '\n'
        'append newline':
          expected: ['
01234567
', '

']
          index: 8, text: '\n'
        'insert newline in middle':
          expected: ['
012345
', '
67
']
          index: 6, text: '\n'
        'insert two newlines in middle':
          expected: ['
012345
', '

', '
67
']
          index: 6, text: '\n\n'
        'insert text with newline':
          expected: ['
012345A
', '
B67
']
          index: 6, text: 'A\nB'
        'multiline insert with preceding newline':
          expected: ['
012345
', '
A
', '
B67
']
          index: 6, text: '\nA\nB'
        'multiline insert with newline after':
          expected: ['
012345A
', '
B
', '
67
']
          index: 6, text: 'A\nB\n'
        'multiline insert with newline surrounding':
          expected: ['
012345
', '
A
', '
B
', '
67
']
          index: 6, text: '\nA\nB\n'

      _.each(tests, (test, name) ->
        it(name, ->
          @editor.doc.setHTML('
01234567
')
          @editor._insertAt(test.index, test.text, test.formats)
          @editor.doc.optimizeLines()
          expect(@editor.root).toEqualHTML(test.expected.join(''), true)
        )
      )

      it('append formatted newline', ->
        @editor.doc.setHTML('
A
')
        @editor._insertAt(2, '\n', { bold: true })
        @editor.doc.optimizeLines()
        expect(@editor.root).toEqualHTML('
A

', true)
        expect(@editor.doc.toDelta()).toEqualDelta(Tandem.Delta.makeDelta({
          startLength: 0
          endLength: 3
          ops: [
            { value: 'A\n' }
            { value: '\n', attributes: { bold: true } }
          ]
        }))
      )

      it('insert after image', ->
        @editor.doc.setHTML('

')
        @editor._insertAt(1, 'A', { bold: true })
        @editor.doc.optimizeLines()
        expect(@editor.root).toEqualHTML('
A
', true)
      )
    )
  )

  describe('applyDelta()', ->
    tests =
      'insert formatted':
        initial: '
0123
'
        delta: Tandem.Delta.makeInsertDelta(5, 2, '|', { bold: true })
        expected: '
01|23
'
      'multiple formats':
        initial: '
0123
'
        delta: Tandem.Delta.makeRetainDelta(5, 1, 2, { bold: true, italic: true })
        expected: '
0123
'
      'multiple instructions':
        initial: '

0123

5678

abcd
'
        delta: Tandem.Delta.makeDelta({ startLength: 15, endLength: 17, ops: [
          { start: 0, end: 4 }
          { start: 5, end: 7 }
          { value: '|\n|' }
          { start: 7, end: 14 }
          { start: 14, end: 15, attributes: { align: 'right' } }
        ]})
        expected: '

012356|

|78

abcd
'

    _.each(tests, (test, name) ->
      it(name, ->
        @editor.doc.setHTML(test.initial)
        @editor.checkUpdate()
        @editor.applyDelta(test.delta)
        expect(@editor.root).toEqualHTML(test.expected, true)
      )
    )

    it('local change', ->
      @editor.doc.setHTML('
0123
')
      @editor.checkUpdate()
      delta = Tandem.Delta.makeInsertDelta(5, 2, '|')
      @editor.root.querySelector('span').innerHTML = '01a23'
      @editor.applyDelta(delta)
      expected = '
01|a23
'
      expect(@editor.root).toEqualHTML(expected, true)
    )
  )
)


lineOffset = 0
while line.prev?
line = line.prev
lineOffset += line.length
leaf = line.findLeaf(leafNode)
return lineOffset unless leaf?
leafOffset = 0
while leaf.prev?

keep keep keep keep replace keep keep keep

getColor = (id, lighten) ->
  alpha = if lighten then '0.4' else '1.0'
  return if id == 1 or id == 'quill-1' then "rgba(0,153,255,#{alpha})" else "rgba(255,153,51,#{alpha})"

listenEditor = (source, target) ->
  source.on(Quill.events.TEXT_CHANGE, (delta, origin) ->
    console.log source.id, 'text', delta, origin
    return if origin == 'api'
    target.updateContents(delta)
    sourceDelta = source.getContents()
    targetDelta = target.getContents()
    console.assert(sourceDelta.isEqual(targetDelta), "Editor diversion!", source, target, sourceDelta, targetDelta)
  ).on(Quill.events.SELECTION_CHANGE, (range) ->
    console.log source.id, 'selection', range
    return unless range?
    cursorManager = target.getModule('multi-cursor')
    cursorManager.moveCursor(source.id, range.end)
  )

editors = []
for num in [1, 2]
  $wrapper = $(".editor-wrapper.#{if num == 1 then 'first' else 'last'}")
  $container = $('.editor-container', $wrapper)
  editor = new Quill($container.get(0), {
    modules:
      'toolbar': { container: $('.toolbar-container', $wrapper).get(0) }
      'link-tooltip': true
      'image-tooltip': true
      'multi-cursor': true
    theme: 'snow'
  })
  authorship = editor.addModule('authorship', {
    authorId: editor.id
    color: getColor(num, true)
  })
  editors.push(editor)

listenEditor(editors[0], editors[1])
listenEditor(editors[1], editors[0])
editors[0].getModule('authorship').addAuthor(editors[1].id, getColor(editors[1].id, true))
editors[1].getModule('authorship').addAuthor(editors[0].id, getColor(editors[0].id, true))
editors[0].getModule('multi-cursor').setCursor(editors[1].id, 0, editors[1].id, getColor(editors[1].id))
editors[1].getModule('multi-cursor').setCursor(editors[0].id, 0, editors[0].id, getColor(editors[0].id))


describe('Keyboard', ->
  beforeEach( ->
    @container = $('#editor-container').get(0)
  )

  describe('toggleFormat()', ->
    beforeEach( ->
      @container.innerHTML = '

<s>01</s>2345

'
      @quill = new Quill(@container.firstChild)
      @keyboard = @quill.getModule('keyboard')
    )

    it('set if all unset', ->
      @keyboard.toggleFormat(new Quill.Lib.Range(3, 6), 'strike')
      expect(@quill.root.firstChild).toEqualHTML('<s>01</s>2<s>345</s>')
    )

    it('unset if all set', ->
      @keyboard.toggleFormat(new Quill.Lib.Range(2, 4), 'bold')
      expect(@quill.root.firstChild).toEqualHTML('<s>01</s>2345')
    )

    it('set if partially set', ->
      @keyboard.toggleFormat(new Quill.Lib.Range(3, 5), 'bold')
      expect(@quill.root.firstChild).toEqualHTML('<s>01</s>2345')
    )
  )

  describe('hotkeys', ->
    beforeEach( ->
      @container.innerHTML = '

0123

'
      @quill = new Quill(@container.firstChild)
      @keyboard = @quill.getModule('keyboard')
    )

    it('trigger', (done) ->
      hotkey = { key: 'B', metaKey: true }
      @keyboard.addHotkey(hotkey, (range) ->
        expect(range.start).toEqual(1)
        expect(range.end).toEqual(2)
        done()
      )
      @quill.setSelection(1, 2)
      Quill.DOM.triggerEvent(@quill.root, 'keydown', hotkey)
    )

    it('format', ->
      @quill.setSelection(0, 4)
      Quill.DOM.triggerEvent(@quill.root, 'keydown', Quill.Module.Keyboard.hotkeys.BOLD)
      expect(@quill.root).toEqualHTML('
0123
', true)
    )

    it('tab', ->
      @quill.setSelection(1, 3)
      Quill.DOM.triggerEvent(@quill.root, 'keydown', Quill.Module.Keyboard.hotkeys.INDENT)
      expect(@quill.root).toEqualHTML('
0\t3
', true)
    )

    it('shift + tab', ->
      @quill.setSelection(0, 2)
      Quill.DOM.triggerEvent(@quill.root, 'keydown', Quill.Module.Keyboard.hotkeys.OUTDENT)
      expect(@quill.root).toEqualHTML('
0123
', true)
    )
  )
)


keep keep keep keep add keep keep keep keep keep

_     = require('lodash')
DOM   = require('./dom')
Utils = require('./utils')

Normalizer =
  ALIASES: {
    'STRONG' : 'B'
    'EM'     : 'I'
    'DEL'    : 'S'
    'STRIKE' : 'S'
  }

  ATTRIBUTES: {
    'color': 'color'
    'face' : 'fontFamily'
    'size' : 'fontSize'
  }

  STYLES: {
    'background-color'
    'color'
    'font-family'
    'font-size'
    'text-align'
  }

  TAGS: {
    'DIV'
    'BR'
    'SPAN'
    'B'
    'I'
    'S'
    'U'
    'A'
    'IMG'
  }

  # Make sure descendant break tags are not causing multiple lines to be rendered
  handleBreaks: (lineNode) ->
    breaks = _.map(lineNode.querySelectorAll(DOM.DEFAULT_BREAK_TAG))
    _.each(breaks, (br) =>
      Utils.splitAncestors(br.nextSibling, lineNode.parentNode) if br.nextSibling?
    )
    return lineNode

  normalizeLine: (lineNode) ->
    lineNode = Normalizer.wrapInline(lineNode)
    lineNode = Normalizer.normalizeNode(lineNode)
    lineNode = Normalizer.handleBreaks(lineNode)
    Normalizer.pullBlocks(lineNode)
    Normalizer.wrapText(lineNode)
    return lineNode

  normalizeNode: (node) ->
    _.each(Normalizer.ATTRIBUTES, (style, attribute) ->
      if node.hasAttribute(attribute)
        value = node.getAttribute(attribute)
        value = Utils.convertFontSize(value) if attribute == 'size'
        node.style[style] = value
        node.removeAttribute(attribute)
    )
    Normalizer.whitelistStyles(node)
    return Normalizer.whitelistTags(node)

  # Removes unnecessary tags but does not modify line contents
  optimizeLine: (lineNode) ->
    lineNodeLength = Utils.getNodeLength(lineNode)
    nodes = DOM.getDescendants(lineNode)
    while nodes.length > 0
      node = nodes.pop()
      continue unless node?.parentNode?
      continue if DOM.EMBED_TAGS[node.tagName]?
      if node.tagName == DOM.DEFAULT_BREAK_TAG
        # Remove unneeded BRs
        DOM.removeNode(node) unless lineNodeLength == 0
      else if Utils.getNodeLength(node) == 0
        nodes.push(node.nextSibling)
        DOM.unwrap(node)
      else
        attributes = DOM.getAttributes(node)
        if node.tagName == DOM.DEFAULT_INLNE_TAG
          # Remove unneeded SPANs
          hasAttributes = _.keys(attributes).length > 0
          exposeTextNode = DOM.isTextNode(node.firstChild) and (DOM.isElement(node.previousSibling) or DOM.isElement(node.nextSibling))
          exposeToLine = node.parentNode == lineNode and DOM.isTextNode(node.firstChild)
          if !hasAttributes and !exposeTextNode and !exposeToLine
            nodes.push(node.nextSibling)
            DOM.unwrap(node)
            continue
        if node.previousSibling? and node.tagName == node.previousSibling.tagName
          # Merge similar nodes
          neighborAttributes = DOM.getAttributes(node.previousSibling)
          if _.isEqual(attributes, neighborAttributes)
            nodes.push(node.firstChild)
            DOM.moveChildren(node.previousSibling, node)
            DOM.normalize(node.previousSibling)
            DOM.removeNode(node)
    Normalizer.wrapText(lineNode)

  # Make sure descendants are all inline elements
  pullBlocks: (lineNode) ->
    curNode = lineNode.firstChild
    return unless curNode?
    if DOM.BLOCK_TAGS[curNode.tagName]?
      if curNode.nextSibling?
        Utils.splitAncestors(curNode.nextSibling, lineNode.parentNode)
      DOM.unwrap(curNode)
      Normalizer.pullBlocks(lineNode)
    curNode = curNode.nextSibling
    while curNode?
      if DOM.BLOCK_TAGS[curNode.tagName]?
        lineNode = Utils.splitAncestors(curNode, lineNode.parentNode)
        break
      curNode = curNode.nextSibling

  stripWhitespace: (html) ->
    # Remove leading and tailing whitespace
    html = html.replace(/^\s+/, '').replace(/\s+$/, '')
    # Remove whitespace between tags, requires   for legitmate spaces
    html = html.replace(/\>\s+\</g, '><')
    return html

  whitelistStyles: (node) ->
    original = DOM.getStyles(node)
    styles = _.omit(original, (value, key) ->
      return !Normalizer.STYLES[key]?
    )
    if _.keys(styles).length < _.keys(original).length
      if _.keys(styles).length > 0
        DOM.setStyles(node, styles)
      else
        node.removeAttribute('style')

  whitelistTags: (node) ->
    return unless DOM.isElement(node)
    node = DOM.switchTag(node, Normalizer.ALIASES[node.tagName]) if Normalizer.ALIASES[node.tagName]?
    if !Normalizer.TAGS[node.tagName]?
      tagName = if DOM.BLOCK_TAGS[node.tagName]? then DOM.DEFAULT_BLOCK_TAG else DOM.DEFAULT_INLNE_TAG
      node = DOM.switchTag(node, tagName)
    return node

  # Wrap inline nodes with block tags
  wrapInline: (lineNode) ->
    return lineNode if DOM.BLOCK_TAGS[lineNode.tagName]?
    blockNode = lineNode.ownerDocument.createElement(DOM.DEFAULT_BLOCK_TAG)
    lineNode.parentNode.insertBefore(blockNode, lineNode)
    while lineNode? and !DOM.BLOCK_TAGS[lineNode.tagName]?
      nextNode = lineNode.nextSibling
      blockNode.appendChild(lineNode)
      lineNode = nextNode
    return blockNode

  wrapText: (lineNode) ->
    texts = DOM.getTextNodes(lineNode)
    _.each(texts, (textNode) =>
      if textNode.previousSibling? or textNode.nextSibling? or textNode.parentNode == lineNode
        DOM.wrap(lineNode.ownerDocument.createElement(DOM.DEFAULT_INLNE_TAG), textNode)
    )

module.exports = Normalizer


- var colors = ["rgb(0, 0, 0)", "rgb(230, 0, 0)", "rgb(255, 153, 0)", "rgb(255, 255, 0)", "rgb(0, 138, 0)", "rgb(0, 102, 204)", "rgb(153, 51, 255)", "rgb(255, 255, 255)", "rgb(250, 204, 204)", "rgb(255, 235, 204)", "rgb(255, 255, 204)", "rgb(204, 232, 204)", "rgb(204, 224, 245)", "rgb(235, 214, 255)", "rgb(187, 187, 187)", "rgb(240, 102, 102)", "rgb(255, 194, 102)", "rgb(255, 255, 102)", "rgb(102, 185, 102)", "rgb(102, 163, 224)", "rgb(194, 133, 255)", "rgb(136, 136, 136)", "rgb(161, 0, 0)", "rgb(178, 107, 0)", "rgb(178, 178, 0)", "rgb(0, 97, 0)", "rgb(0, 71, 178)", "rgb(107, 36, 178)", "rgb(68, 68, 68)", "rgb(92, 0, 0)", "rgb(102, 61, 0)", "rgb(102, 102, 0)", "rgb(0, 55, 0)", "rgb(0, 41, 102)", "rgb(61, 20, 102)"]

doctype html
html
  head
    title Quill Pretty Editor Demo
    link(rel='stylesheet', type='text/css', href='http://netdna.bootstrapcdn.com/bootstrap/3.1.0/css/bootstrap.min.css')
    link(rel='stylesheet', type='text/css', href='../themes/quill.snow.css')
    link(rel='stylesheet', type='text/css', href='styles/advanced.css')
    meta(charset='utf-8')

  body
    #content-container
      each num in [1, 2]
        .editor-wrapper(class=(num == 1 ? 'first' : 'last'))
          .toolbar-container
            span.sc-format-group
              select.sc-font(title='Font')
                option(value='sans-serif', selected=true) Sans Serif
                option(value='serif') Serif
                option(value='monospace') Monospace
              select.sc-size(title='Size')
                option(value='10px') Small
                option(value='13px', selected=true) Normal
                option(value='18px') Large
                option(value='32px') Huge
            span.sc-format-group
              each c in ['sc-color', 'sc-background']
                select(class=c, title=(c=='sc-color'?'Text Color':'Background Color'))
                  each color,i in colors
                    option(value=color, selected=((c == 'sc-color' && color == 'rgb(0, 0, 0)') || (c == 'sc-background' && color == 'rgb(255, 255, 255)') ? true : false))
                if c == 'sc-color'
                  span.sc-format-separator
            span.sc-format-group
              span.sc-format-button.sc-bold(title='Bold')
              span.sc-format-separator
              span.sc-format-button.sc-italic(title='Italic')
              span.sc-format-separator
              span.sc-format-button.sc-underline(title='Italic')
            span.sc-format-group
              span.sc-format-button.sc-link(title='Link')
              span.sc-format-separator
              span.sc-format-button.sc-image(title='Image')
            span.sc-format-group
              span.sc-format-button.sc-authorship(title='Authorship')
          #editor-container.editor-container

    script(type='text/javascript', src='http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.js')
    script(type='text/javascript', src='http://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.js')
    script(type='text/javascript', src='../quill.js')
    script(type='text/javascript', src='scripts/advanced.js')


describe('Quill', ->
  beforeEach( ->
    @container = $('#editor-container').html('

0123

5678

    ').get(0)
    @quill = new Quill(@container.firstChild)
  )

  describe('constructor', ->
    it('string container', ->
      @container.innerHTML = '

'
      quill = new Quill('#target')
      expect(quill.editor.iframeContainer).toEqual(@container.firstChild)
    )

    it('invalid container', ->
      expect( =>
        @quill = new Quill('.none')
      ).toThrow(new Error('Invalid Quill container'))
    )
  )

  describe('styles', ->
    it('constructor overwrite', (done) ->
      @container.innerHTML = '
0123
'
      @quill = new Quill(@container.firstChild, {
        styles: { '.editor-container div': { 'line-height': '25px' } }
      })
      @quill.on(Quill.events.RENDER_UPDATE, (css) =>
        if css.indexOf('.editor-container div') > -1
          expect(@quill.root.firstChild.offsetHeight).toEqual(25)
          done()
      )
    )

    it('addStyles()', (done) ->
      @quill.addStyles({ '.editor-container div': { 'line-height': '25px' } })
      @quill.on(Quill.events.RENDER_UPDATE, (css) =>
        if css.indexOf('.editor-container div') > -1
          expect(@quill.root.firstChild.offsetHeight).toEqual(25)
          done()
      )
    )
  )

  describe('modules', ->
    it('addContainer()', ->
      @quill.addContainer('test-container', true)
      expect(@quill.root.parentNode.querySelector('.test-container')).toEqual(@quill.root.parentNode.firstChild)
    )

    it('addModule()', ->
      obj =
        before: ->
        after: ->
      spyOn(obj, 'before')
      spyOn(obj, 'after')
      expect(@quill.getModule('toolbar')).toBe(undefined)
      @quill.onModuleLoad('toolbar', obj.before)
      @quill.addModule('toolbar', { container: '#toolbar-container' })
      expect(@quill.getModule('toolbar')).not.toBe(null)
      expect(obj.before).toHaveBeenCalled()
      @quill.onModuleLoad('toolbar', obj.after)
      expect(obj.after).toHaveBeenCalled()
    )

    it('addModule() nonexistent', ->
      expect( =>
        @quill.addModule('nonexistent')
      ).toThrow(new Error("Cannot load nonexistent module. Are you sure you included it?"))
    )
  )

  describe('manipulation', ->
    it('deleteText()', ->
      @quill.deleteText(2, 3)
      expect(@quill.root).toEqualHTML('
013

5678
', true)
    )

    # it('formatLines()', ->
    #   @quill.formatLines(4, 2, 'align', 'right')
    #   expect(@quill.root).toEqualHTML('
    #
0123

    #
5678

    #   ', true)
    # )

    it('formatText()', ->
      @quill.formatText(2, 4, 'bold', true)
      expect(@quill.root).toEqualHTML('
0123

5678
', true)
      @quill.formatText(2, 4, 'bold', false)
      expect(@quill.root).toEqualHTML('
0123

5678
', true)
      expect(@quill.getContents(0, 4)).toEqualDelta(Tandem.Delta.makeInsertDelta(0, 0, '0123'))
    )

    # TODO add test for formatText(range, 'size', '13px')   where 13px is the default

    # it('insertEmbed()', ->
    #   @quill.insertEmbed(4, 'image', 'http://quilljs.com/images/icon.png')
    #   expect(@quill.root).toEqualHTML('
0123

5678
', true)
    # )

    it('insertText()', ->
      @quill.insertText(2, 'A')
      expect(@quill.root).toEqualHTML('
01A23

5678
', true)
    )

    it('setContents() with delta', ->
      @quill.setContents({
        startLength: 0
        endLength: 1
        ops: [{ value: 'A', attributes: { bold: true } }]
      })
      expect(@quill.root).toEqualHTML('
A
', true)
    )

    it('setContents() with ops', ->
      @quill.setContents([{ value: 'A', attributes: { bold: true } }])
      expect(@quill.root).toEqualHTML('
A
', true)
    )

    it('setHTML()', ->
      @quill.setHTML('
A
')
      expect(@quill.root).toEqualHTML('
A
', true)
    )

    it('updateContents()', ->
      @quill.updateContents(Tandem.Delta.makeInsertDelta(10, 2, 'A'))
      expect(@quill.root).toEqualHTML('
01A23

5678
', true)
    )
  )

  describe('retrievals', ->
    it('getContents() all', ->
      expect(@quill.getContents()).toEqualDelta(Tandem.Delta.makeInsertDelta(0, 0, '0123\n5678\n'))
    )

    it('getContents() partial', ->
      expect(@quill.getContents(2, 7)).toEqualDelta(Tandem.Delta.makeInsertDelta(0, 0, '23\n56'))
    )

    it('getHTML()', ->
      expect(@quill.getHTML()).toEqualHTML('
0123

5678
', true)
    )

    it('getLength()', ->
      expect(@quill.getLength()).toEqual(10)
    )

    it('getText()', ->
      expect(@quill.getText()).toEqual('0123\n5678\n')
    )
  )

  describe('selection', ->
    it('get/set range', ->
      @quill.setSelection(1, 2)
      range = @quill.getSelection()
      expect(range).not.toBe(null)
      expect(range.start).toEqual(1)
      expect(range.end).toEqual(2)
    )

    it('get/set index range', ->
      @quill.setSelection(new Quill.Lib.Range(2, 3))
      range = @quill.getSelection()
      expect(range).not.toBe(null)
      expect(range.start).toEqual(2)
      expect(range.end).toEqual(3)
    )

    it('get/set null', ->
      @quill.setSelection(1, 2)
      expect(range).not.toBe(null)
      @quill.setSelection(null)
      range = @quill.getSelection()
      expect(range).toBe(null)
    )
  )

  describe('_buildParams()', ->
    tests =
      'index range string formats'  : [1, 3, 'bold', true]
      'index range object formats'  : [1, 3, { bold: true }]
      'object range string formats' : [new Quill.Lib.Range(1, 3), 'bold', true]
      'object range object formats' : [new Quill.Lib.Range(1, 3), { bold: true }]

    _.each(tests, (args, name) ->
      it(name, ->
        [start, end, formats, source] = @quill._buildParams(args...)
        expect(start).toEqual(1)
        expect(end).toEqual(3)
        expect(formats).toEqual({ bold: true })
      )
    )

    it('source override', ->
      [start, end, formats, source] = @quill._buildParams(1, 2, {}, 'silent')
      expect(source).toEqual('silent')
    )
  )
)


DOM    = require('../dom')
Picker = require('./picker')

class ColorPicker extends Picker
  constructor: ->
    super
    DOM.addClass(@container, 'sc-color-picker')

  buildItem: (picker, option, index) ->
    item = super(picker, option, index)
    item.style.backgroundColor = option.value
    DOM.setText(item, '')
    return item

module.exports = ColorPicker


return if test.ieOmit and Quill.DOM.isIE(10)

describe('Normalizer', ->
  beforeEach( ->
    @container = $('#editor-container').html('').get(0)
  )

  describe('handleBreaks()', ->
    tests =
      'Break in middle of line':
        initial:  '
One
Two
'
        expected: '
One

Two
'
      'Break preceding line':
        initial:  '

One
'
        expected: '

One
'
      'Break after line':
        initial:  '
One

'
        expected: '
One

'

    _.each(tests, (test, name) ->
      it(name, ->
        @container.innerHTML = test.initial
        lineNode = Quill.Normalizer.handleBreaks(@container.firstChild)
        expect(@container).toEqualHTML(test.expected)
        expect(lineNode).toEqual(@container.firstChild)
      )
    )
  )

  describe('normalizeLine()', ->
    tests =
      'pull text node':
        initial:  '

AB
C

'
        expected: '
AB

C

'
      'inline with text':
        initial:  'WhatTest'
        expected: '
WhatTest
'
      'whitelist line node':
        initial:  '
Test
'
        expected: '
Test
'

    _.each(tests, (test, name) ->
      it(name, ->
        @container.innerHTML = test.initial
        lineNode = Quill.Normalizer.normalizeLine(@container.firstChild)
        expect(@container).toEqualHTML(test.expected)
        expect(lineNode).toEqual(@container.firstChild)
      )
    )
  )

  describe('normalizeNode()', ->
    it('whitelist style and tag', ->
      @container.innerHTML = 'Test'
      Quill.Normalizer.normalizeNode(@container.firstChild)
      expect(@container).toEqualHTML('Test')
    )

    it('convert size attribute', ->
      @container.innerHTML = 'Test'
      Quill.Normalizer.normalizeNode(@container.firstChild)
      expect(@container).toEqualHTML('Test')
    )
  )

  describe('optimizeLine()', ->
    tests =
      'unwrap non-format node':
        initial:  'Test'
        expected: 'Test'
      'unwrap non-format node':
        initial:  'Test'
        expected: 'Test'
      'preserve only span':
        initial:  'Test'
        expected: 'Test'
      'remove unneeded break':
        initial:  'Test
'
        expected: 'Test'
      'preserve span with attributes':
        initial:  'Test'
        expected: 'Test'
      'unwrap zero length nodes':
        initial:  ''
        expected: ''
      'unwrap eventually':
        initial:  '<s></s>'
        expected: ''
      'unwrap break on empty line':
        initial:  '
'
        expected: '
'
      'preserve break on empty line':
        initial:  '
'
        expected: '
'
      'unwrap eventually with break':
        initial:  '<s>
</s>'
        expected: '
'
      'merge similar nodes':
        initial:  'AB'
        expected: 'AB'
      'merge similar span nodes':
        initial:  'AB'
        expected: 'AB'
      'merge multiple criteria similar nodes':
        initial:  'AB'
        expected: 'AB'
      'merge similar nodes after unwrap':
        initial:  'AB'
        expected: 'AB'
      'merge recursive':
        initial:  '<s>A</s><s>B</s>'
        expected: '<s>AB</s>'
      'preserve close but not same nodes':
        initial:  'AB'
        expected: 'AB'
      'preserve similar images':
        initial:  ''
        expected: ''
      'wrap orphaned text node':
        initial:  '<s>0</s><s>1</s>'
        expected: '<s>01</s>'

    _.each(tests, (test, name) ->
      it(name, ->
        @container.innerHTML = "
#{test.initial}
"
        Quill.Normalizer.optimizeLine(@container.firstChild)
        expect(@container.firstChild).toEqualHTML(test.expected)
      )
    )
  )

  describe('pullBlocks()', ->
    tests =
      'No children':
        initial:  '

'
        expected: '

'
      'Inner block':
        initial:  '

Test

Another

'
        expected: '
Test

Another

'
      'Inner deep block':
        initial:  '

Test

'
        expected: '
Test
'
      'Inner deep recursive':
        initial:  '

Test
Test

'
        expected: '
Test

Test

'
      'Continuous inlines':
        initial:  '
A
B
Inner

'
        expected: '
A
B

Inner

'

    _.each(tests, (test, name) ->
      it(name, ->
        @container.innerHTML = test.initial
        firstChild = @container.firstChild
        Quill.Normalizer.pullBlocks(firstChild)
        expect(@container).toEqualHTML(test.expected)
        expect(firstChild).toEqual(@container.firstChild)
      )
    )
  )

  describe('stripWhitespace()', ->
    tests =
      'newlines':
        initial:
         '

            Test

'
        expected: '
Test

'
      'preceding and trailing spaces':
        initial:  '

  '
        expected: '

'
      'inner spaces':
        initial:  '

'
        expected: '

'

    _.each(tests, (test, name) ->
      it(name, ->
        strippedHTML = Quill.Normalizer.stripWhitespace(test.initial)
        expect(strippedHTML).toEqual(test.expected)
      )
    )
  )

  describe('whitelistStyles()', ->
    tests =
      'no styles':
        initial:  '

'
        expected: '

'
      'no removal':
        initial:  '

'
        expected: '

'
      'removal':
        initial:  '

'
        expected: '

'
      'complete removal':
        initial:  '

'
        expected: '

'

    _.each(tests, (test, name) ->
      it(name, ->
        @container.innerHTML = test.initial
        Quill.Normalizer.whitelistStyles(@container.firstChild)
        expect(@container).toEqualHTML(test.expected)
      )
    )
  )

  describe('whitelistTags()', ->
    tests =
      'not element':
        initial:  'Test'
        expected: 'Test'
      'no switch needed':
        initial:  'Bold'
        expected: 'Bold'
      'alias':
        initial:  'Bold'
        expected: 'Bold'
      'switch inline':
        initial:  'A'
        expected:  'A'
      'switch block':
        initial:  '
Test
'
        expected: '
Test
'

    _.each(tests, (test, name) ->
      it(name, ->
        @container.innerHTML = test.initial
        Quill.Normalizer.whitelistTags(@container.firstChild)
        expect(@container).toEqualHTML(test.expected)
      )
    )
  )

  describe('wrapInline()', ->
    tests =
      'Wrap newline':
        initial:  ['
']
        expected: ['

']
      'Wrap span':
        initial:  ['One']
        expected: ['
One
']
      'Wrap many spans':
        initial: [
          'One'
          'Two'
        ]
        expected: [
          '
OneTwo
'
        ]
      'Wrap break and span':
        initial:  ['
One']
        expected: ['

One
']

    _.each(tests, (test, name) ->
      it(name, ->
        @container.innerHTML = test.initial.join('')
        Quill.Normalizer.wrapInline(@container.firstChild)
        expect(@container).toEqualHTML(test.expected)
      )
    )
  )

  describe('wrapText()', ->
    tests =
      'inner text':
        initial:  'InnerTest'
        expected: 'InnerTest'
      'multiple inner':
        initial:  'TestTestTest'
        expected: 'TestTestTest'
      'text node':
        initial: 'Test'
        expected: 'Test'

    _.each(tests, (test, name) ->
      it(name, ->
        @container.innerHTML = test.initial
        Quill.Normalizer.wrapText(@container)
        expect(@container).toEqualHTML(test.expected)
      )
    )

    it('should wrap multiple text nodes', ->
      @container.appendChild(@container.ownerDocument.createTextNode('A'))
      @container.appendChild(@container.ownerDocument.createTextNode('B'))
      Quill.Normalizer.wrapText(@container)
      expect(@container).toEqualHTML('AB')
    )
  )
)


describe('Renderer', ->
  beforeEach( ->
    @container = $('#editor-container').html('').get(0)
  )

  it('objToCss()', ->
    css = Quill.Renderer.objToCss(
      '.editor-container a':
        'font-style': 'italic'
        'text-decoration': 'underline'
      '.editor-container b':
        'font-weight': 'bold'
    )
    expect(css).toEqual([
      '.editor-container a { font-style: italic; text-decoration: underline; }'
      '.editor-container b { font-weight: bold; }'
    ].join('\n'))
  )

  it('buildFrame()', ->
    [root, iframe] = Quill.Renderer.buildFrame(@container)
    expect(@container.querySelectorAll('iframe').length).toEqual(1)
    expect(root.ownerDocument.body.firstChild).toEqual(root)
    expect(root.innerHTML).toEqual('')
  )

  it('constructor', ->
    renderer = new Quill.Renderer(@container)
    expect(@container.querySelectorAll('iframe').length).toEqual(1)
    expect(renderer.root.id).not.toBe(null)
  )
)


describe('Selection', ->
  beforeEach( ->
    @container = $('#editor-container').get(0)
  )

  describe('helpers', ->
    beforeEach( ->
      @container.innerHTML = '

0123

<s>89</s>ab

      '
      @quill = new Quill(@container.firstChild)   # Need Quill to create iframe for focus logic
      @selection = @quill.editor.selection
    )

    tests =
      'text node':
        native: ->
          return [@quill.root.querySelector('s').firstChild, 1]
        encoded: ->
          return [@quill.root.querySelector('s').firstChild, 1]
        index: 9
      'between leaves':
        native: ->
          return [@quill.root.querySelector('s').firstChild, 2]
        encoded: ->
          return [@quill.root.querySelector('s').firstChild, 2]
        index: 10
      'break node':
        native: ->
          return [@quill.root.querySelector('br').parentNode, 0]
        encoded: ->
          return [@quill.root.querySelector('br'), 0]
        index: 5
      'before image':
        native: ->
          return [@quill.root.querySelector('img').parentNode, 0]
        encoded: ->
          return [@quill.root.querySelector('img'), 0]
        index: 6
      'after image':
        native: ->
          return [@quill.root.querySelector('img').parentNode, 1]
        encoded: ->
          return [@quill.root.querySelector('img'), 1]
        index: 7
      'end of document':
        native: ->
          return [@quill.root.querySelector('i').firstChild, 2]
        encoded: ->
          return [@quill.root.querySelector('i').firstChild, 2]
        index: 12

    describe('_decodePosition()', ->
      _.each(tests, (test, name) ->
        it(name, ->
          [node, offset] = test.encoded.call(this)
          [resultNode, resultOffset] = @selection._decodePosition(node, offset)
          [expectedNode, expectedOffset] = test.native.call(this)
          expect(resultNode).toEqual(expectedNode)
          expect(resultOffset).toEqual(expectedOffset)
        )
      )
    )

    describe('_encodePosition()', ->
      _.each(tests, (test, name) ->
        it(name, ->
          [node, offset] = test.native.call(this)
          [resultNode, resultOffset] = @selection._encodePosition(node, offset)
          [expectedNode, expectedOffset] = test.encoded.call(this)
          expect(resultNode).toEqual(expectedNode)
          expect(resultOffset).toEqual(expectedOffset)
        )
      )

      it('empty document', ->
        @container.innerHTML = '

'
        quill = new Quill(@container.firstChild)
        [resultNode, resultIndex] = quill.editor.selection._encodePosition(quill.root, 0)
        expect(resultNode).toEqual(quill.root)
        expect(resultIndex).toEqual(0)
      )
    )

    describe('_positionToIndex()', ->
      _.each(tests, (test, name) ->
        it(name, ->
          [node, offset] = test.native.call(this)
          index = @selection._positionToIndex(node, offset)
          expect(index).toEqual(test.index)
        )
      )

      it('empty document', ->
        @container.innerHTML = '

'
        quill = new Quill(@container.firstChild)
        index = quill.editor.selection._positionToIndex(quill.root, 0)
        expect(index).toEqual(0)
      )
    )

    describe('_indexToPosition()', ->
      _.each(tests, (test, name) ->
        it(name, ->
          [node, offset] = @selection._indexToPosition(test.index)
          [expectedNode, expectedOffset] = test.native.call(this)
          expect(node).toEqual(expectedNode)
          expect(offset).toEqual(expectedOffset)
        )
      )

      it('empty document', ->
        @container.innerHTML = '

'
        quill = new Quill(@container.firstChild)
        [node, offset] = quill.editor.selection._indexToPosition(0)
        expect(node).toEqual(quill.root)
        expect(offset).toEqual(0)
      )

      it('multiple consecutive images', ->
        @container.innerHTML = '

'
        quill = new Quill(@container.firstChild)
        [node, offset] = quill.editor.selection._indexToPosition(1)
        expect(node).toEqual(quill.root.firstChild)
        expect(offset).toEqual(1)
      )
    )

    describe('get/set range', ->
      _.each(tests, (test, name) ->
        it(name, ->
          @selection.setRange(new Quill.Lib.Range(test.index, test.index))
          expect(@selection.checkFocus()).toBe(true)
          range = @selection.getRange()
          expect(range).not.toEqual(null)
          expect(range.start).toEqual(test.index)
          expect(range.end).toEqual(test.index)
        )
      )

      it('entire document', ->
        @selection.setRange(new Quill.Lib.Range(0, 12))
        expect(@selection.checkFocus()).toBe(true)
        range = @selection.getRange()
        expect(range).not.toEqual(null)
        expect(range.start).toEqual(0)
        expect(range.end).toEqual(12)
      )

      it('null range', ->
        @selection.setRange(new Quill.Lib.Range(0, 0))
        expect(@selection.checkFocus()).toBe(true)
        @selection.setRange(null)
        expect(@selection.checkFocus()).toBe(false)
        range = @selection.getRange()
        expect(range).toBe(null)
      )

      it('empty document', ->
        @container.innerHTML = '

'
        quill = new Quill(@container.firstChild)
        quill.editor.selection.setRange(new Quill.Lib.Range(0, 0))
        expect(quill.editor.selection.checkFocus()).toBe(true)
        range = quill.editor.selection.getRange()
        expect(range).not.toEqual(null)
        expect(range.start).toEqual(0)
        expect(range.end).toEqual(0)
      )
    )
  )

  describe('preserve', ->
    describe('shiftAfter()', ->
      it('line optimization', ->
        @container.innerHTML = '

1234

'
        quill = new Quill(@container.firstChild)
        quill.editor.selection.setRange(new Quill.Lib.Range(0, 3))
        quill.editor._insertAt(0, Quill.DOM.EMBED_TEXT, { image: 'http://quilljs.com/images/icon.png' })
        quill.editor._formatAt(2, 4, 'bold', true)
        expect(quill.root).toEqualHTML('

1234

        ', true)
        range = quill.editor.selection.getRange()
        expect(range.start).toEqual(1)
        expect(range.end).toEqual(4)
        quill.editor.selection.shiftAfter(0, 0, _.bind(quill.editor.doc.optimizeLines, quill.editor.doc))
        range = quill.editor.selection.getRange()
        expect(range.start).toEqual(1)
        expect(range.end).toEqual(4)
      )
    )

    describe('preserve()', ->
      beforeEach( ->
        @container.innerHTML = '

'
        @quill = new Quill(@container.firstChild)
        @doc = @quill.editor.doc
        @selection = @quill.editor.selection
      )

      it('wrapInline() text', ->
        @quill.root.innerHTML = 'Test'
        textNode = @quill.root.firstChild
        @selection._setNativeRange(textNode, 0, textNode, 4)
        @selection.preserve(_.bind(@doc.rebuild, @doc))
        range = @selection.getRange()
        expect(range.start).toEqual(0)
        expect(range.end).toEqual(4)
      )

      it('wrapInline() image', ->
        @quill.root.innerHTML = ''
        @selection._setNativeRange(@quill.root, 0, @quill.root, 1)
        @selection.preserve(_.bind(@doc.rebuild, @doc))
        range = @selection.getRange()
        expect(range.start).toEqual(0)
        expect(range.end).toEqual(1)
      )

      it('handleBreaks()', ->
        @quill.root.innerHTML = '
01
34
'
        firstTextNode = @quill.root.firstChild.firstChild
        lastTextNode = @quill.root.firstChild.lastChild
        @selection._setNativeRange(firstTextNode, 1, lastTextNode, 1)
        @selection.preserve(_.bind(@doc.rebuild, @doc))
        range = @selection.getRange()
        expect(range.start).toEqual(1)
        expect(range.end).toEqual(4)
      )

      it('pullBlocks()', ->
        @quill.root.innerHTML = '

01

34

'
        firstTextNode = @quill.root.firstChild.firstChild.firstChild
        lastTextNode = @quill.root.firstChild.lastChild.firstChild
        @selection._setNativeRange(firstTextNode, 1, lastTextNode, 1)
        @selection.preserve(_.bind(@doc.rebuild, @doc))
        range = @selection.getRange()
        expect(range.start).toEqual(1)
        expect(range.end).toEqual(4)
      )

      it('wrapText()', ->
        @quill.root.innerHTML = '
0123

5678
'
        firstTextNode = @quill.root.firstChild.firstChild
        lastTextNode = @quill.root.lastChild.firstChild
        @selection._setNativeRange(firstTextNode, 2, lastTextNode, 2)
        @selection.preserve(_.bind(@doc.rebuild, @doc))
        range = @selection.getRange()
        expect(range.start).toEqual(2)
        expect(range.end).toEqual(7)
      )

      it('no range', ->
        @quill.root.innerHTML = '
Test
'
        textNode = @quill.root.querySelector('span').firstChild
        @selection._setNativeRange(null)
        @selection.preserve(_.bind(@doc.rebuild, @doc))
        range = @selection.getRange()
        expect(range).toBe(null)
      )
    )
  )
)


module.exports = (grunt) ->
  grunt.config('connect',
    server:
      options:
        keepalive: true
        port: 9000
  )


return (this.internalValue || '').toString().length

keep keep keep keep replace keep keep keep keep keep

@import '../bootstrap'
@import '../theme'

v-text-field($material)
  .v-input__slot:before
    border-color: $material.input-bottom-line

  &:not(.v-input--has-state)
    .v-input__slot:hover:before
      border-color: $material.text.primary

  &.v-input--is-disabled
    .v-input__slot:before
      border-image: repeating-linear-gradient(
        to right,
        $material.text.disabled 0px,
        $material.text.disabled 2px,
        transparent 2px,
        transparent 4px
      ) 1 repeat

  &__prefix,
  &__suffix
    color: $material.text.secondary

  &--solo
    .v-input__slot
      border-radius: 2px
      background: $material.cards

  &--solo-inverted.v-text-field--solo
    .v-input__slot
      background: rgba($material.inputs.solo-inverted)

    &.v-input--is-focused
      .v-input__slot
        background: rgba($material.inputs.solo-inverted-focused)

        .v-label,
        input
          color: $material.inputs.solo-inverted-focused-text

  &--box
    .v-input__slot
      background: $material.text-fields.box

    &:not(.v-input--is-focused)
      .v-input__slot:hover
        background: $material.text-fields.box-hover

  &--outline
    .v-input__slot
      border: 2px solid $material.text.secondary

    &:not(.v-input--is-focused):not(.v-input--has-state)
      .v-input__slot:hover
        border: 2px solid $material.text.primary

v-text-field-rtl()
  .v-label
    transform-origin: top right

  .v-counter
    margin-left: 0
    margin-right: 8px

  &--enclosed
    .v-input__append-outer
      margin-left: 0
      margin-right: 16px

    .v-input__prepend-outer
      margin-left: 16px
      margin-right: 0

  &--reverse
    input
      text-align: left

    .v-label
      transform-origin: top left

  &__prefix
    text-align: left
    padding-right: 0
    padding-left: 4px

  &__suffix
    padding-left: 0
    padding-right: 4px

  &--reverse
    .v-text-field__prefix
      text-align: right
      padding-left: 0
      padding-right: 4px

    .v-text-field__suffix
      padding-left: 0
      padding-right: 4px

theme(v-text-field, "v-text-field")
rtl(v-text-field-rtl, "v-text-field")

.v-text-field
  padding-top: $text-field-active-label-height
  margin-top: $input-top-spacing - $text-field-active-label-height

  input
    flex: 1 1 auto
    line-height: 20px
    padding: 8px 0 8px
    max-width: 100%
    min-width: 0px
    width: 100%

  .v-input__prepend-inner,
  .v-input__append-inner
    align-self: flex-start
    display: inline-flex
    margin-top: 4px
    line-height: 1
    user-select: none

  .v-input__prepend-inner
    margin-right: auto
    padding-right: 4px

  .v-input__append-inner
    margin-left: auto
    padding-left: 4px

  .v-counter
    margin-left: 8px
    white-space: nowrap

  .v-label
    max-width: 90%
    overflow: hidden
    text-overflow: ellipsis
    top: 6px // equal to the margin top of icons
    transform-origin: top left
    white-space: nowrap
    pointer-events: none

    &--active
      max-width: 100%
      transform: translateY(-18px) scale(.75)

  & > .v-input__control > .v-input__slot
    cursor: text
    transition: background $primary-transition

    &:before,
    &:after
      bottom: -1px // Needed for browser autocomplete styles
      content: ''
      left: 0
      position: absolute
      transition: $primary-transition
      width: 100%

    &:before
      border-style: solid
      border-width: thin 0 0 0

    &:after
      border-color: currentColor
      border-style: solid
      border-width: thin 0 thin 0
      transform: scaleX(0)

  &__details
    display: flex
    flex: 1 0 auto
    max-width: 100%
    overflow: hidden

  &__prefix,
  &__suffix
    align-self: center
    cursor: default

  &__prefix
    width: 16px
    text-align: right
    padding-right: 4px

  &__suffix
    padding-left: 4px
    white-space: nowrap

  &--reverse
    .v-text-field__prefix
      text-align: left
      padding-right: 0
      padding-left: 4px

    .v-text-field__suffix
      padding-left: 0
      padding-right: 4px

  &__slot
    display: flex
    flex: 1 1 auto
    position: relative

  &--box,
  &--outline
    position: relative

    .v-input__slot
      align-items: stretch

    input
      margin-top: 22px

    &.v-text-field--single-line
      input
        margin-top: 12px

    .v-label
      top: 18px

      &--active
        transform: translateY(-6px) scale(.75)

    .v-input__slot
      min-height: 56px

  &--box
    .v-input__slot
      border-top-left-radius: 4px
      border-top-right-radius: 4px

      &:before
        border-style: solid
        border-width: thin 0 thin 0

  &.v-text-field--enclosed
    margin: 0
    padding: 0

    &:not(.v-text-field--box)
      .v-progress-linear__background
        display: none

    .v-input__prepend-outer,
    .v-input__prepend-inner,
    .v-input__append-inner,
    .v-input__append-outer
      margin-top: 16px

    .v-text-field__details,
    .v-input__slot
      padding: 0 12px

    .v-text-field__details
      margin-bottom: 8px

  &.v-text-field--full-width
    &.v-input
      margin-bottom: 0
      margin-top: 0

    .v-label
      top: calc(50% - 10px)

    .v-input__control
      padding: 12px 0

    .v-input__prepend-outer,
    .v-input__append-outer
      margin-top: 4px

    .v-input__append-inner
      align-self: center
      margin-top: 0

  &--reverse
    input
      text-align: right

    .v-label
      transform-origin: top right

    .v-input__slot,
    .v-text-field__slot
      flex-direction: row-reverse

  &--solo,
  &--outline,
  &--full-width
    .v-input__slot
      &:before,
      &:after
        display: none

  &--outline
    margin-bottom: 16px
    transition: border $primary-transition

    .v-input__slot
      background: transparent !important
      border-radius: 4px

    .v-input__prepend-outer,
    .v-input__append-outer
      margin-top: 18px // 2px for border

    &.v-input--is-focused, &.v-input--has-state
      .v-input__slot
        border: 2px solid currentColor
        transition: border $primary-transition

  &.v-text-field--solo
    .v-label
      top: calc(50% - 10px)

    .v-input__control
      min-height: 48px
      padding: 0

    &:not(.v-text-field--solo-flat)
      .v-input__slot
        elevation(2)

    .v-text-field__slot
      align-items: center

    .v-input__append-inner,
    .v-input__prepend-inner
      align-self: center
      margin-top: 0

    .v-input__prepend-outer,
    .v-input__append-outer
      margin-top: 12px

  &.v-input--is-focused
    .v-input__slot:after
      transform: scaleX(1)

  &.v-input--has-state
    .v-input__slot:before
      border-color: currentColor


<template>
  <v-container grid-list-md text-xs-center>
    <v-layout row wrap>
      <v-flex xs12>
        <v-card dark color="primary">
          <v-card-text class="px-0">12</v-card-text>
        </v-card>
      </v-flex>
      <v-flex xs6 v-for="i in 2" :key="i">
        <v-card dark color="secondary">
          <v-card-text class="px-0">6</v-card-text>
        </v-card>
      </v-flex>
      <v-flex xs4 v-for="i in 3" :key="i">
        <v-card dark color="primary">
          <v-card-text class="px-0">4</v-card-text>
        </v-card>
      </v-flex>
      <v-flex xs3 v-for="i in 4" :key="i">
        <v-card dark color="secondary">
          <v-card-text class="px-0">3</v-card-text>
        </v-card>
      </v-flex>
      <v-flex xs2 v-for="i in 6" :key="i">
        <v-card dark color="primary">
          <v-card-text class="px-0">2</v-card-text>
        </v-card>
      </v-flex>
      <v-flex xs1 v-for="i in 12" :key="i">
        <v-card dark color="secondary">
          <v-card-text class="px-0">1</v-card-text>
        </v-card>
      </v-flex>
    </v-layout>
  </v-container>
</template>


import Applicationable from './Applicationable'
import Bootable from './Bootable'
import Colorable from './Colorable'
import DataIterable from './DataIterable'
import Delayable from './Delayable'
import Detachable from './Detachable'
import Filterable from './Filterable'
import Input from './Input'
import Loadable from './Loadable'
import Menuable from './Menuable'
import Picker from './Picker'
import Positionable from './Positionable'
import Rippleable from './Rippleable'
import Routable from './Routable'
import Soloable from './Soloable'
import Themeable from './Themeable'
import Transitionable from './Transitionable'
import Validatable from './Validatable'

export default {
  Applicationable,
  Bootable,
  Colorable,
  DataIterable,
  Delayable,
  Detachable,
  Filterable,
  Input,
  Loadable,
  Menuable,
  Picker,
  Positionable,
  Rippleable,
  Routable,
  Soloable,
  Themeable,
  Transitionable,
  Validatable
}


<template>
  <v-card>
    <v-toolbar
      card
      color="pink"
      dark
      dense
    >
      <v-toolbar-title class="body-2">Upcoming Changes</v-toolbar-title>
    </v-toolbar>
    <v-card-text>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
    </v-card-text>

    <v-card-actions>
      <v-spacer></v-spacer>
      <v-icon
        large
        @click="next"
      >
        mdi-chevron-right
      </v-icon>
    </v-card-actions>
  </v-card>
</template>

<script>
  export default {
    methods: {
      next () {
        alert('You clicked next!')
      }
    }
  }
</script>

<codepen-resources lang="json">
  {
    "css": ["https://cdn.materialdesignicons.com/2.5.94/css/materialdesignicons.min.css"]
  }
</codepen-resources>


const offset = (this.prefix && !this.labelValue) ? this.prefixWidth : 0

// Styles
import '../../stylus/components/_text-fields.styl'

// Extensions
import VInput from '../VInput'

// Components
import VCounter from '../VCounter'
import VLabel from '../VLabel'

// Mixins
import Maskable from '../../mixins/maskable'
import Loadable from '../../mixins/loadable'

// Directives
import Ripple from '../../directives/ripple'

// Utilities
import {
  keyCodes
} from '../../util/helpers'
import { deprecate } from '../../util/console'

const dirtyTypes = ['color', 'file', 'time', 'date', 'datetime-local', 'week', 'month']

/* @vue/component */
export default {
  name: 'v-text-field',

  directives: { Ripple },

  extends: VInput,

  mixins: [Maskable, Loadable],

  inheritAttrs: false,

  props: {
    appendOuterIcon: String,
    /** @deprecated */
    appendOuterIconCb: Function,
    autofocus: Boolean,
    box: Boolean,
    browserAutocomplete: String,
    clearable: Boolean,
    clearIcon: {
      type: String,
      default: '$vuetify.icons.clear'
    },
    clearIconCb: Function,
    color: {
      type: String,
      default: 'primary'
    },
    counter: [Boolean, Number, String],
    flat: Boolean,
    fullWidth: Boolean,
    label: String,
    outline: Boolean,
    placeholder: String,
    prefix: String,
    prependInnerIcon: String,
    /** @deprecated */
    prependInnerIconCb: Function,
    reverse: Boolean,
    singleLine: Boolean,
    solo: Boolean,
    soloInverted: Boolean,
    suffix: String,
    textarea: Boolean, // TODO: Deprecate
    type: {
      type: String,
      default: 'text'
    }
  },

  data: () => ({
    badInput: false,
    initialValue: null,
    internalChange: false,
    isClearing: false
  }),

  computed: {
    classes () {
      return {
        'v-text-field': true,
        'v-text-field--full-width': this.fullWidth,
        'v-text-field--prefix': this.prefix,
        'v-text-field--single-line': this.isSingle,
        'v-text-field--solo': this.isSolo,
        'v-text-field--solo-inverted': this.soloInverted,
        'v-text-field--solo-flat': this.flat,
        'v-text-field--box': this.box,
        'v-text-field--enclosed': this.isEnclosed,
        'v-text-field--reverse': this.reverse,
        'v-text-field--outline': this.hasOutline
      }
    },
    counterValue () {
      return (this.internalValue || '').toString().length
    },
    directivesInput () {
      return []
    },
    // TODO: Deprecate
    hasOutline () {
      return this.outline || this.textarea
    },
    internalValue: {
      get () {
        return this.lazyValue
      },
      set (val) {
        if (this.mask) {
          this.lazyValue = this.unmaskText(this.maskText(this.unmaskText(val)))
          this.setSelectionRange()
        } else {
          this.lazyValue = val
          this.$emit('input', this.lazyValue)
        }
      }
    },
    isDirty () {
      return (this.lazyValue != null &&
        this.lazyValue.toString().length > 0) ||
        this.badInput
    },
    isEnclosed () {
      return (
        this.box ||
        this.isSolo ||
        this.hasOutline ||
        this.fullWidth
      )
    },
    isLabelActive () {
      return this.isDirty || dirtyTypes.includes(this.type)
    },
    isSingle () {
      return this.isSolo || this.singleLine
    },
    isSolo () {
      return this.solo || this.soloInverted
    },
    labelPosition () {
      const offset = (this.prefix && !this.labelValue) ? 16 : 0

      return (!this.$vuetify.rtl !== !this.reverse) ? {
        left: 'auto',
        right: offset
      } : {
        left: offset,
        right: 'auto'
      }
    },
    showLabel () {
      return this.hasLabel && (!this.isSingle || (!this.isLabelActive && !this.placeholder))
    },
    labelValue () {
      return !this.isSingle &&
        Boolean(this.isFocused || this.isLabelActive || this.placeholder)
    }
  },

  watch: {
    isFocused (val) {
      // Sets validationState from validatable
      this.hasColor = val

      if (val) {
        this.initialValue = this.lazyValue
      } else if (this.initialValue !== this.lazyValue) {
        this.$emit('change', this.lazyValue)
      }
    },
    value (val) {
      if (this.mask && !this.internalChange) {
        const masked = this.maskText(this.unmaskText(val))
        this.lazyValue = this.unmaskText(masked)

        // Emit when the externally set value was modified internally
        String(val) !== this.lazyValue && this.$nextTick(() => {
          this.$refs.input.value = masked
          this.$emit('input', this.lazyValue)
        })
      } else this.lazyValue = val
    }
  },

  mounted () {
    this.autofocus && this.onFocus()
  },

  methods: {
    /** @public */
    focus () {
      this.onFocus()
    },
    /** @public */
    blur () {
      this.$refs.input ? this.$refs.input.blur() : this.onBlur()
    },
    clearableCallback () {
      this.internalValue = null
      this.$nextTick(() => this.$refs.input.focus())
    },
    genAppendSlot () {
      const slot = []

      if (this.$slots['append-outer']) {
        slot.push(this.$slots['append-outer'])
      } else if (this.appendOuterIcon) {
        slot.push(this.genIcon('appendOuter'))
      }

      return this.genSlot('append', 'outer', slot)
    },
    genPrependInnerSlot () {
      const slot = []

      if (this.$slots['prepend-inner']) {
        slot.push(this.$slots['prepend-inner'])
      } else if (this.prependInnerIcon) {
        slot.push(this.genIcon('prependInner'))
      }

      return this.genSlot('prepend', 'inner', slot)
    },
    genIconSlot () {
      const slot = []

      if (this.$slots['append']) {
        slot.push(this.$slots['append'])
      } else if (this.appendIcon) {
        slot.push(this.genIcon('append'))
      }

      return this.genSlot('append', 'inner', slot)
    },
    genInputSlot () {
      const input = VInput.methods.genInputSlot.call(this)

      const prepend = this.genPrependInnerSlot()
      prepend && input.children.unshift(prepend)

      return input
    },
    genClearIcon () {
      if (!this.clearable) return null

      const icon = !this.isDirty
        ? false
        : 'clear'

      if (this.clearIconCb) deprecate(':clear-icon-cb', '@click:clear', this)

      return this.genSlot('append', 'inner', [
        this.genIcon(
          icon,
          (!this.$listeners['click:clear'] && this.clearIconCb) || this.clearableCallback,
          false
        )
      ])
    },
    genCounter () {
      if (this.counter === false || this.counter == null) return null

      const max = this.counter === true ? this.$attrs.maxlength : this.counter

      return this.$createElement(VCounter, {
        props: {
          dark: this.dark,
          light: this.light,
          max,
          value: this.counterValue
        }
      })
    },
    genDefaultSlot () {
      return [
        this.genTextFieldSlot(),
        this.genClearIcon(),
        this.genIconSlot(),
        this.genProgress()
      ]
    },
    genLabel () {
      if (!this.showLabel) return null

      const data = {
        props: {
          absolute: true,
          color: this.validationState,
          dark: this.dark,
          disabled: this.disabled,
          focused: !this.isSingle && (this.isFocused || !!this.validationState),
          left: this.labelPosition.left,
          light: this.light,
          right: this.labelPosition.right,
          value: this.labelValue
        }
      }

      if (this.$attrs.id) data.props.for = this.$attrs.id

      return this.$createElement(VLabel, data, this.$slots.label || this.label)
    },
    genInput () {
      const listeners = Object.assign({}, this.$listeners)
      delete listeners['change'] // Change should not be bound externally

      const data = {
        style: {},
        domProps: {
          value: this.maskText(this.lazyValue)
        },
        attrs: {
          'aria-label': (!this.$attrs || !this.$attrs.id) && this.label, // Label `for` will be set if we have an id
          ...this.$attrs,
          autofocus: this.autofocus,
          disabled: this.disabled,
          readonly: this.readonly,
          type: this.type
        },
        on: Object.assign(listeners, {
          blur: this.onBlur,
          input: this.onInput,
          focus: this.onFocus,
          keydown: this.onKeyDown
        }),
        ref: 'input'
      }

      if (this.placeholder) data.attrs.placeholder = this.placeholder
      if (this.mask) data.attrs.maxlength = this.masked.length
      if (this.browserAutocomplete) data.attrs.autocomplete = this.browserAutocomplete

      return this.$createElement('input', data)
    },
    genMessages () {
      if (this.hideDetails) return null

      return this.$createElement('div', {
        staticClass: 'v-text-field__details'
      }, [
        VInput.methods.genMessages.call(this),
        this.genCounter()
      ])
    },
    genTextFieldSlot () {
      return this.$createElement('div', {
        staticClass: 'v-text-field__slot'
      }, [
        this.genLabel(),
        this.prefix ? this.genAffix('prefix') : null,
        this.genInput(),
        this.suffix ? this.genAffix('suffix') : null
      ])
    },
    genAffix (type) {
      return this.$createElement('div', {
        'class': `v-text-field__${type}`,
        ref: type
      }, this[type])
    },
    onBlur (e) {
      this.isFocused = false
      // Reset internalChange state
      // to allow external change
      // to persist
      this.internalChange = false

      this.$emit('blur', e)
    },
    onClick () {
      if (this.isFocused || this.disabled) return

      this.$refs.input.focus()
    },
    onFocus (e) {
      if (!this.$refs.input) return

      if (document.activeElement !== this.$refs.input) {
        return this.$refs.input.focus()
      }

      if (!this.isFocused) {
        this.isFocused = true
        this.$emit('focus', e)
      }
    },
    onInput (e) {
      this.internalChange = true
      this.mask && this.resetSelections(e.target)
      this.internalValue = e.target.value
      this.badInput = e.target.validity && e.target.validity.badInput
    },
    onKeyDown (e) {
      this.internalChange = true

      if (e.keyCode === keyCodes.enter) this.$emit('change', this.internalValue)

      this.$emit('keydown', e)
    },
    onMouseDown (e) {
      // Prevent input from being blurred
      if (e.target !== this.$refs.input) {
        e.preventDefault()
        e.stopPropagation()
      }

      VInput.methods.onMouseDown.call(this, e)
    },
    onMouseUp (e) {
      if (this.hasMouseDown) this.focus()

      VInput.methods.onMouseUp.call(this, e)
    }
  }
}


<template>
  <v-layout row wrap>
    <v-flex
      v-for="(bar, i) in bars"
      :key="i"
      xs12
      sm12
      md6
      class="my-3"
    >
      <v-card color="grey lighten-4" flat height="200px">
        <v-toolbar :color="bar.class" :dark="bar.dark">
          <v-toolbar-side-icon></v-toolbar-side-icon>
          <v-toolbar-title>Title</v-toolbar-title>
          <v-spacer></v-spacer>
          <v-btn icon>
            <v-icon>search</v-icon>
          </v-btn>
          <v-btn icon>
            <v-icon>favorite</v-icon>
          </v-btn>
          <v-btn icon>
            <v-icon>more_vert</v-icon>
          </v-btn>
        </v-toolbar>
      </v-card>
    </v-flex>
  </v-layout>
</template>

<script>
  export default {
    data: () => ({
      bars: [
        { 'class': '' },
        { 'class': '', dark: true },
        { 'class': 'primary', dark: true },
        { 'class': 'elevation-0' }
      ]
    })
  }
</script>


import VAutocomplete from './VAutocomplete'

export { VAutocomplete }
export default VAutocomplete


<template>
  <v-data-table
    :headers="headers"
    :items="desserts"
    class="elevation-1"
  >
    <template slot="items" slot-scope="props">
      {{ props.item.name }}
      {{ props.item.calories }}
      {{ props.item.fat }}
      {{ props.item.carbs }}
      {{ props.item.protein }}
      {{ props.item.iron }}
    </template>
    <template slot="pageText" slot-scope="props">
      Lignes {{ props.pageStart }} - {{ props.pageStop }} de {{ props.itemsLength }}
    </template>
  </v-data-table>
</template>


return this.hasLabel && (!this.isSingle || (!this.isLabelActive && !this.placeholder))

keep keep keep keep keep replace keep keep keep

return this.hasLabel && (!this.isSingle || (!this.isLabelActive && !this.placeholder && !this.prefixLabel))

@import '../bootstrap'
@import '../theme'

v-select($material)
  // Needs an explicit color to override
  // higher level color
  .v-select__selections
    color: $material.text.primary

  &.v-input--is-disabled
    .v-select__selections
      color: $material.text.disabled

  .v-chip--disabled,
  .v-select__selection--disabled
    color: $material.text.disabled

  &.v-text-field--solo-inverted.v-input--is-focused
    .v-select__selections
      color: $material.inputs.solo-inverted-focused-text

theme(v-select, 'v-select')

.v-select
  position: relative // For **attach** prop

  & > .v-input__control > .v-input__slot
    cursor: pointer

  .v-chip
    flex: 0 1 auto

  .fade-transition-leave-active
    position: absolute
    left: 0

  &.v-input--is-dirty
    ::placeholder
      color: transparent !important

  &.v-text-field--enclosed:not(.v-text-field--single-line)
    .v-select__selections
      padding-top: 24px

  &.v-text-field
    input
      flex: 1 1 // Doesn't resize on IE11 with 3rd param
      margin-top: 0
      min-width: 0
      pointer-events: none
      position: relative

  &.v-select--is-menu-active
    .v-input__icon--append .v-icon
      transform: rotate(180deg)

  &.v-select--chips
    input
      margin: 0

    .v-select__selections
      min-height: 42px

    &.v-select--chips--small
      .v-select__selections
        min-height: 32px

    &:not(.v-text-field--single-line)
      &.v-text-field--box,
      &.v-text-field--enclosed
        .v-select__selections
          min-height: 68px

        &.v-select--chips--small
          .v-select__selections
            min-height: 56px

  &.v-text-field--reverse
    .v-select__slot,
    .v-select__selections
      flex-direction: row-reverse

  &__selections
    align-items: center
    display: flex
    flex: 1 1 auto
    flex-wrap: wrap
    line-height: 18px

  &__selection
    max-width: 90%

    &--comma
      align-items: center
      display: inline-flex
      margin: 7px 4px 7px 0

  &__slot
    position: relative
    align-items: center
    display: flex
    width: 100%

  &:not(.v-text-field--single-line)
    .v-select__slot
      // If an input is a direct child
      // there is no selections div
      // and we must move input to
      // the end of the container
      > input
        align-self: flex-end


export default {
  dataIterator: {
    rowsPerPageText: 'Записей на странице:',
    rowsPerPageAll: 'Все',
    pageText: '{0}-{1} из {2}',
    noResultsText: 'Не найдено подходящих записей',
    nextPage: 'Следующая страница',
    prevPage: 'Предыдущая страница'
  },
  dataTable: {
    rowsPerPageText: 'Строк на странице:'
  },
  noDataText: 'Отсутствуют данные'
}


// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`VInput.js should apply theme to label, counter, messages and icons 1`] = `

        prepend

        foo

          bar

        append

`;

exports[`VInput.js should be in an error state 1`] = `

`;

exports[`VInput.js should be in an error state 2`] = `

`;

exports[`VInput.js should generate an icon and match snapshot 1`] = `

        list

`;

exports[`VInput.js should generate an icon and match snapshot 2`] = `

        list

`;

exports[`VInput.js should generate append and prepend slots 1`] = `

      append

`;

exports[`VInput.js should generate append and prepend slots 2`] = `

      prepend

`;

exports[`VInput.js should not generate input details 1`] = `

`;

exports[`VInput.js should render a label 1`] = `

        foo

`;

exports[`VInput.js should render a label 2`] = `

          foo

`;


Boolean(this.isFocused || this.isLabelActive || this.placeholder)

keep keep keep replace keep keep keep keep

Boolean(this.isFocused || this.isLabelActive || this.placeholder || this.prefixLabel)


<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 350 139"><defs><style>.cls-1,.cls-3{fill:#1697f6;}.cls-2{font-size:107.26px;fill:#606060;font-family:Menlo-Regular, Menlo;letter-spacing:0.02em;}.cls-3{letter-spacing:0em;}.cls-4{letter-spacing:0.04em;}</style></defs><title>feature3</title><polygon class="cls-1" points="2830.5 2212.74 -532.5 967.12 2830.5 -278.5 2830.5 2212.74"/><text class="cls-2" transform="translate(88.48 103.89) scale(0.87 1)"><<tspan class="cls-3" x="66.72" y="0">/</tspan><tspan class="cls-4" x="131.29" y="0">></tspan></text></svg>


@keyframes shake
  59%
    margin-left: 0

  60%, 80%
    margin-left: 2px

  70%, 90%
    margin-left: -2px


<template>
  <v-tabs fixed centered>
    <v-tabs-bar class="cyan" dark>
      <v-tabs-slider class="yellow"></v-tabs-slider>
      <v-tabs-item
        v-for="i in items"
        :key="i"
        :href="'#tab-' + i"
      >
        {{ i }}
      </v-tabs-item>
    </v-tabs-bar>
    <v-tabs-items>
      <v-tabs-content
        v-for="i in items"
        :key="i"
        :id="'tab-' + i"
      >
        <v-card flat>
          <v-card-text>{{ text }}</v-card-text>
        </v-card>
      </v-tabs-content>
    </v-tabs-items>
  </v-tabs>
</template>

<script>
  export default {
    data () {
      return {
        items: ['Item One', 'Item Seventeen', 'Item Five'],
        text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.'
      }
    }
  }
</script>


import ServerSideRendering from './ServerSideRendering'
import ALaCarte from './ALaCarte'

export default {
  ServerSideRendering, ALaCarte
}


<template>
  <v-data-table
      :items="items"
      class="elevation-1"
      hide-actions
      hide-headers
    >
    <template slot="items" slot-scope="props">
      {{ props.item.name }}
      {{ props.item.calories }}
      {{ props.item.fat }}
      {{ props.item.carbs }}
      {{ props.item.protein }}
      {{ props.item.sodium }}
      {{ props.item.calcium }}
      {{ props.item.iron }}
    </template>
  </v-data-table>
</template>


// Components
import VInput from '../components/VInput'

// Mixins
import Rippleable from './rippleable'
import Comparable from './comparable'

/* @vue/component */
export default {
  name: 'selectable',

  extends: VInput,

  mixins: [Rippleable, Comparable],

  model: {
    prop: 'inputValue',
    event: 'change'
  },

  props: {
    color: {
      type: String,
      default: 'accent'
    },
    id: String,
    inputValue: null,
    falseValue: null,
    trueValue: null,
    multiple: {
      type: Boolean,
      default: null
    },
    label: String
  },

  data: vm => ({
    lazyValue: vm.inputValue
  }),

  computed: {
    computedColor () {
      return this.isActive ? this.color : this.validationState
    },
    isMultiple () {
      return this.multiple === true || (this.multiple === null && Array.isArray(this.internalValue))
    },
    isActive () {
      const value = this.value
      const input = this.internalValue

      if (this.isMultiple) {
        if (!Array.isArray(input)) return false

        return input.some(item => this.valueComparator(item, value))
      }

      if (this.trueValue === undefined || this.falseValue === undefined) {
        return value
          ? this.valueComparator(value, input)
          : Boolean(input)
      }

      return this.valueComparator(input, this.trueValue)
    },
    isDirty () {
      return this.isActive
    }
  },

  watch: {
    inputValue (val) {
      this.lazyValue = val
    }
  },

  methods: {
    genLabel () {
      if (!this.hasLabel) return null

      const label = VInput.methods.genLabel.call(this)

      label.data.on = { click: this.onChange }

      return label
    },
    genInput (type, attrs) {
      return this.$createElement('input', {
        attrs: Object.assign({
          'aria-label': this.label,
          'aria-checked': this.isActive.toString(),
          disabled: this.isDisabled,
          id: this.id,
          role: type,
          type,
          value: this.inputValue
        }, attrs),
        domProps: {
          checked: this.isActive
        },
        on: {
          blur: this.onBlur,
          change: this.onChange,
          focus: this.onFocus,
          keydown: this.onKeydown
        },
        ref: 'input'
      })
    },
    onBlur () {
      this.isFocused = false
    },
    onChange () {
      if (this.isDisabled) return

      const value = this.value
      let input = this.internalValue

      if (this.isMultiple) {
        if (!Array.isArray(input)) {
          input = []
        }

        const length = input.length

        input = input.filter(item => !this.valueComparator(item, value))

        if (input.length === length) {
          input.push(value)
        }
      } else if (this.trueValue !== undefined && this.falseValue !== undefined) {
        input = this.valueComparator(input, this.trueValue) ? this.falseValue : this.trueValue
      } else if (value) {
        input = this.valueComparator(input, value) ? null : value
      } else {
        input = !input
      }

      this.validate(true, input)
      this.internalValue = input
    },
    onFocus () {
      this.isFocused = true
    },
    /** @abstract */
    onKeydown (e) {}
  }
}


@import '../bootstrap'
@import '../theme'

v-text-field($material)
  .v-input__slot:before
    border-color: $material.input-bottom-line

  &:not(.v-input--has-state)
    .v-input__slot:hover:before
      border-color: $material.text.primary

  &.v-input--is-disabled
    .v-input__slot:before
      border-image: repeating-linear-gradient(
        to right,
        $material.text.disabled 0px,
        $material.text.disabled 2px,
        transparent 2px,
        transparent 4px
      ) 1 repeat

  &__prefix,
  &__suffix
    color: $material.text.secondary

  &--solo
    .v-input__slot
      border-radius: 2px
      background: $material.cards

  &--solo-inverted.v-text-field--solo
    .v-input__slot
      background: rgba($material.inputs.solo-inverted)

    &.v-input--is-focused
      .v-input__slot
        background: rgba($material.inputs.solo-inverted-focused)

        .v-label,
        input
          color: $material.inputs.solo-inverted-focused-text

  &--box
    .v-input__slot
      background: $material.text-fields.box

    &:not(.v-input--is-focused)
      .v-input__slot:hover
        background: $material.text-fields.box-hover

  &--outline
    .v-input__slot
      border: 2px solid $material.text.secondary

    &:not(.v-input--is-focused):not(.v-input--has-state)
      .v-input__slot:hover
        border: 2px solid $material.text.primary

v-text-field-rtl()
  .v-label
    transform-origin: top right

  .v-counter
    margin-left: 0
    margin-right: 8px

  &--enclosed
    .v-input__append-outer
      margin-left: 0
      margin-right: 16px

    .v-input__prepend-outer
      margin-left: 16px
      margin-right: 0

  &--reverse
    input
      text-align: left

    .v-label
      transform-origin: top left

  &__prefix
    text-align: left
    padding-right: 0
    padding-left: 4px

  &__suffix
    padding-left: 0
    padding-right: 4px

  &--reverse
    .v-text-field__prefix
      text-align: right
      padding-left: 0
      padding-right: 4px

    .v-text-field__suffix
      padding-left: 0
      padding-right: 4px

theme(v-text-field, "v-text-field")
rtl(v-text-field-rtl, "v-text-field")

.v-text-field
  padding-top: $text-field-active-label-height
  margin-top: $input-top-spacing - $text-field-active-label-height

  input
    flex: 1 1 auto
    line-height: 20px
    padding: 8px 0 8px
    max-width: 100%
    min-width: 0px
    width: 100%

  .v-input__prepend-inner,
  .v-input__append-inner
    align-self: flex-start
    display: inline-flex
    margin-top: 4px
    line-height: 1
    user-select: none

  .v-input__prepend-inner
    margin-right: auto
    padding-right: 4px

  .v-input__append-inner
    margin-left: auto
    padding-left: 4px

  .v-counter
    margin-left: 8px
    white-space: nowrap

  .v-label
    max-width: 90%
    overflow: hidden
    text-overflow: ellipsis
    top: 6px // equal to the margin top of icons
    transform-origin: top left
    white-space: nowrap
    pointer-events: none

    &--active
      max-width: 100%
      transform: translateY(-18px) scale(.75)

  & > .v-input__control > .v-input__slot
    cursor: text
    transition: background $primary-transition

    &:before,
    &:after
      bottom: -1px // Needed for browser autocomplete styles
      content: ''
      left: 0
      position: absolute
      transition: $primary-transition
      width: 100%

    &:before
      border-style: solid
      border-width: thin 0 0 0

    &:after
      border-color: currentColor
      border-style: solid
      border-width: thin 0 thin 0
      transform: scaleX(0)

  &__details
    display: flex
    flex: 1 0 auto
    max-width: 100%
    overflow: hidden

  &__prefix,
  &__suffix
    align-self: center
    cursor: default

  &__prefix
    width: 16px
    text-align: right
    padding-right: 4px

  &__suffix
    padding-left: 4px
    white-space: nowrap

  &--reverse
    .v-text-field__prefix
      text-align: left
      padding-right: 0
      padding-left: 4px

    .v-text-field__suffix
      padding-left: 0
      padding-right: 4px

  &__slot
    display: flex
    flex: 1 1 auto
    position: relative

  &--box,
  &--outline
    position: relative

    .v-input__slot
      align-items: stretch

    input
      margin-top: 22px

    &.v-text-field--single-line
      input
        margin-top: 12px

    .v-label
      top: 18px

      &--active
        transform: translateY(-6px) scale(.75)

    .v-input__slot
      min-height: 56px

  &--box
    .v-input__slot
      border-top-left-radius: 4px
      border-top-right-radius: 4px

      &:before
        border-style: solid
        border-width: thin 0 thin 0

  &.v-text-field--enclosed
    margin: 0
    padding: 0

    &:not(.v-text-field--box)
      .v-progress-linear__background
        display: none

    .v-input__prepend-outer,
    .v-input__prepend-inner,
    .v-input__append-inner,
    .v-input__append-outer
      margin-top: 16px

    .v-text-field__details,
    .v-input__slot
      padding: 0 12px

    .v-text-field__details
      margin-bottom: 8px

  &.v-text-field--full-width
    &.v-input
      margin-bottom: 0
      margin-top: 0

    .v-label
      top: calc(50% - 10px)

    .v-input__control
      padding: 12px 0

    .v-input__prepend-outer,
    .v-input__append-outer
      margin-top: 4px

    .v-input__append-inner
      align-self: center
      margin-top: 0

  &--reverse
    input
      text-align: right

    .v-label
      transform-origin: top right

    .v-input__slot,
    .v-text-field__slot
      flex-direction: row-reverse

  &--solo,
  &--outline,
  &--full-width
    .v-input__slot
      &:before,
      &:after
        display: none

  &--outline
    margin-bottom: 16px
    transition: border $primary-transition

    .v-input__slot
      background: transparent !important
      border-radius: 4px

    .v-input__prepend-outer,
    .v-input__append-outer
      margin-top: 18px // 2px for border

    &.v-input--is-focused, &.v-input--has-state
      .v-input__slot
        border: 2px solid currentColor
        transition: border $primary-transition

  &.v-text-field--solo
    .v-label
      top: calc(50% - 10px)

    .v-input__control
      min-height: 48px
      padding: 0

    &:not(.v-text-field--solo-flat)
      .v-input__slot
        elevation(2)

    .v-text-field__slot
      align-items: center

    .v-input__append-inner,
    .v-input__prepend-inner
      align-self: center
      margin-top: 0

    .v-input__prepend-outer,
    .v-input__append-outer
      margin-top: 12px

  &.v-input--is-focused
    .v-input__slot:after
      transform: scaleX(1)

  &.v-input--has-state
    .v-input__slot:before
      border-color: currentColor


<template>
  <v-timeline>
    <v-timeline-item
      color="purple lighten-2"
      fill-dot
      right
    >
      <v-card>
        <v-card-title class="purple lighten-2">
          <v-icon
            dark
            size="42"
            class="mr-3"
          >
            mdi-magnify
          </v-icon>

Title 1

        </v-card-title>
        <v-container>
          <v-layout>
            <v-flex xs10>
              Lorem ipsum dolor sit amet, no nam oblique veritus. Commune scaevola imperdiet nec ut, sed euismod convenire principes at. Est et nobis iisque percipit.
            </v-flex>
            <v-flex xs2>
              <v-icon size="64">mdi-calendar-text</v-icon>
            </v-flex>
          </v-layout>
        </v-container>
      </v-card>
    </v-timeline-item>

    <v-timeline-item
      color="amber lighten-1"
      fill-dot
      left
      small
    >
      <v-card>
        <v-card-title class="amber lighten-1 justify-end">

Title 2

          <v-icon
            dark
            size="42"
          >mdi-home-outline</v-icon>
        </v-card-title>
        <v-container>
          <v-layout>
            <v-flex xs8>
              Lorem ipsum dolor sit amet, no nam oblique veritus. Commune scaevola imperdiet nec ut, sed euismod convenire principes at. Est et nobis iisque percipit.
            </v-flex>
            <v-flex xs4>
              Lorem ipsum dolor sit amet, no nam oblique veritus.
            </v-flex>
          </v-layout>
        </v-container>
      </v-card>
    </v-timeline-item>

    <v-timeline-item
      color="cyan lighten-1"
      fill-dot
      right
    >
      <v-card>
        <v-card-title class="cyan lighten-1">
          <v-icon
            class="mr-3"
            dark
            size="42"
          >
            mdi-email-outline
          </v-icon>

Title 3

        </v-card-title>
        <v-container>
          <v-layout>
            <v-flex
              v-for="n in 3"
              :key="n"
              xs4
            >
              Lorem ipsum dolor sit amet, no nam oblique veritus no nam oblique.
            </v-flex>
          </v-layout>
        </v-container>
      </v-card>
    </v-timeline-item>

    <v-timeline-item
      color="red lighten-1"
      fill-dot
      left
      small
    >
      <v-card>
        <v-card-title class="red lighten-1 justify-end">

Title 4

          <v-icon
            dark
            size="42"
          >
            mdi-account-multiple-outline
          </v-icon>
        </v-card-title>
        <v-container>
          <v-layout>
            <v-flex xs2>
              <v-icon size="64">mdi-server-network</v-icon>
            </v-flex>
            <v-flex xs10>
              Lorem ipsum dolor sit amet, no nam oblique veritus. Commune scaevola imperdiet nec ut, sed euismod convenire principes at. Est et nobis iisque percipit, an vim zril disputando voluptatibus.
            </v-flex>
          </v-layout>
        </v-container>
      </v-card>
    </v-timeline-item>

    <v-timeline-item
      color="green lighten-1"
      fill-dot
      right
    >
      <v-card>
        <v-card-title class="green lighten-1">
          <v-icon
            class="mr-3"
            dark
            size="42"
          >
            mdi-phone-in-talk
          </v-icon>

Title 5

        </v-card-title>
        <v-container>
          <v-layout>
            <v-flex>
              Lorem ipsum dolor sit amet, no nam oblique veritus. Commune scaevola imperdiet nec ut, sed euismod convenire principes at. Est et nobis iisque percipit, an vim zril disputando voluptatibus, vix an salutandi sententiae.
            </v-flex>
          </v-layout>
        </v-container>
      </v-card>
    </v-timeline-item>
  </v-timeline>
</template>

<codepen-resources lang="json">
  {
    "css": ["https://cdn.materialdesignicons.com/2.5.94/css/materialdesignicons.min.css"]
  }
</codepen-resources>


Условно отображает контент на смонтированном. Будет показывать только контент, если он активирован


<template>
  <v-layout row justify-center>
    <v-dialog v-model="dialog" fullscreen transition="dialog-bottom-transition" :overlay=false>
      <v-btn primary dark slot="activator">Open Dialog</v-btn>
      <v-card>
        <v-toolbar dark class="primary">
          <v-btn icon @click.native="dialog = false" dark>
            <v-icon>close</v-icon>
          </v-btn>
          <v-toolbar-title>Settings</v-toolbar-title>
          <v-spacer></v-spacer>
          <v-toolbar-items>
            <v-btn dark flat @click.native="dialog = false">Save</v-btn>
          </v-toolbar-items>
        </v-toolbar>
        <v-list three-line subheader>
          <v-subheader>User Controls</v-subheader>
          <v-list-tile avatar>
            <v-list-tile-content>
              <v-list-tile-title>Content filtering</v-list-tile-title>
              <v-list-tile-sub-title>Set the content filtering level to restrict appts that can be downloaded</v-list-tile-sub-title>
            </v-list-tile-content>
          </v-list-tile>
          <v-list-tile avatar>
            <v-list-tile-content>
              <v-list-tile-title>Password</v-list-tile-title>
              <v-list-tile-sub-title>Require password for purchase or use password to restrict purchase</v-list-tile-sub-title>
            </v-list-tile-content>
          </v-list-tile>
        </v-list>
        <v-divider></v-divider>
        <v-list three-line subheader>
          <v-subheader>General</v-subheader>
          <v-list-tile avatar>
            <v-list-tile-action>
              <v-checkbox v-model="notifications"></v-checkbox>
            </v-list-tile-action>
            <v-list-tile-content>
              <v-list-tile-title>Notifications</v-list-tile-title>
              <v-list-tile-sub-title>Notify me about updates to apps or games that I downloaded</v-list-tile-sub-title>
            </v-list-tile-content>
          </v-list-tile>
          <v-list-tile avatar>
            <v-list-tile-action>
              <v-checkbox v-model="sound"></v-checkbox>
            </v-list-tile-action>
            <v-list-tile-content>
              <v-list-tile-title>Sound</v-list-tile-title>
              <v-list-tile-sub-title>Auto-update apps at any time. Data charges may apply</v-list-tile-sub-title>
            </v-list-tile-content>
          </v-list-tile>
          <v-list-tile avatar>
            <v-list-tile-action>
              <v-checkbox v-model="widgets"></v-checkbox>
            </v-list-tile-action>
            <v-list-tile-content>
              <v-list-tile-title>Auto-add widgets</v-list-tile-title>
              <v-list-tile-sub-title>Automatically add home screen widgets</v-list-tile-sub-title>
            </v-list-tile-content>
          </v-list-tile>
        </v-list>
      </v-card>
    </v-dialog>
  </v-layout>
</template>

<script>
  export default {
    data () {
      return {
        dialog: false,
        notifications: false,
        sound: true,
        widgets: false
      }
    }
  }
</script>


&__suffix
align-self: center
cursor: default
&__prefix
width: 16px
text-align: right
padding-right: 4px
&__suffix
padding-left: 4px

keep keep keep keep keep replace keep keep keep keep keep


.DS_Store
node_modules/
dist/
/public/
npm-debug.log
.idea/
.env
.vscode
yarn-error.log
*.swp
.node-version


<template>
  <v-layout row>
    <v-flex xs12 sm6 offset-sm3>
      <v-card>
        <v-toolbar color="teal" dark>
          <v-toolbar-side-icon></v-toolbar-side-icon>
          <v-toolbar-title class="text-xs-center">New Chat</v-toolbar-title>
          <v-spacer></v-spacer>
          <v-btn icon>
            <v-icon>search</v-icon>
          </v-btn>
        </v-toolbar>
        <v-list subheader>
          <v-subheader>Recent chat</v-subheader>
            <v-list-tile avatar v-for="item in items" v-bind:key="item.title" @click="">
              <v-list-tile-avatar>
                <img v-bind:src="item.avatar"/>
              </v-list-tile-avatar>
              <v-list-tile-content>
                <v-list-tile-title v-html="item.title"></v-list-tile-title>
              </v-list-tile-content>
              <v-list-tile-action>
                <v-icon v-bind:color="item.active ? 'teal' : 'grey'">chat_bubble</v-icon>
              </v-list-tile-action>
            </v-list-tile>
        </v-list>
        <v-divider></v-divider>
        <v-list subheader>
          <v-subheader>Previous chats</v-subheader>
          <v-list-tile avatar v-for="item in items2" v-bind:key="item.title" @click="">
            <v-list-tile-avatar>
              <img v-bind:src="item.avatar"/>
            </v-list-tile-avatar>
            <v-list-tile-content>
              <v-list-tile-title v-html="item.title"></v-list-tile-title>
            </v-list-tile-content>
          </v-list-tile>
        </v-list>
      </v-card>
    </v-flex>
  </v-layout>
</template>

<script>
  export default {
    data () {
      return {
        items: [
          { active: true, title: 'Jason Oner', avatar: 'https://cdn.vuetifyjs.com/images/lists/1.jpg' },
          { active: true, title: 'Ranee Carlson', avatar: 'https://cdn.vuetifyjs.com/images/lists/2.jpg' },
          { title: 'Cindy Baker', avatar: 'https://cdn.vuetifyjs.com/images/lists/3.jpg' },
          { title: 'Ali Connors', avatar: 'https://cdn.vuetifyjs.com/images/lists/4.jpg' },
        ],
        items2: [
          { title: 'Travis Howard', avatar: 'https://cdn.vuetifyjs.com/images/lists/5.jpg' },
        ]
      }
    }
  }
</script>


부드러운 애니메이션은 UI에 대한 느낌을 좋게 만듭니다. Vue의 트랜지션 (transition; 장면전환) 시스템과 재사용가능한 펑셔널(functional) 컴포넌트를 사용하면 쉽게 어플리케이션의 모션을 조절할 수 있습니다.  대부분의 컴포넌트는 transition prop 을 이용하여 트랜지션을 조절할 수 있습니다.

v-fade-transition v-slide-x-transition v-slide-x-reverse-transition v-slide-y-transition v-slide-y-reverse-transition v-scale-transition

슬라이드 x 트랜지션은 가로축 방향으로 움직입니다.

많ㅇ느 Vuetify 컴포넌트들은 사용자가 설정할 수 있는 transition prop 을 가지고 있습니다.

페이드 트랜지션의 다른 예는 캐라솔 (Carousel) 컴포넌트에서 찾을 수 있습니다.

간단한 prop을 사용해서 트랜지션의 원점(origin)을 프로그래매틱하게 조절할 수 있습니다.

사용자 트랜지션 만들기


keep keep keep keep add keep keep keep keep


import { test } from '@/test'
import VSubheader from '@/components/VSubheader'

test('VSubheader.js', ({ mount }) => {
  it('should have custom class', () => {
    const wrapper = mount({
      render: h => h(VSubheader, { staticClass: 'foo' })
    })

    expect(wrapper.element.classList.contains('foo')).toBe(true)
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should be light', () => {
    const wrapper = mount(VSubheader, {
      propsData: { light: true }
    })

    expect(wrapper.element.classList.contains('theme--light')).toBe(true)
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should be dark', () => {
    const wrapper = mount(VSubheader, {
      propsData: { dark: true }
    })

    expect(wrapper.element.classList.contains('theme--dark')).toBe(true)
    expect(wrapper.html()).toMatchSnapshot()
  })

  it('should be inset', () => {
    const wrapper = mount(VSubheader, {
      propsData: { inset: true }
    })

    expect(wrapper.element.classList.contains('v-subheader--inset')).toBe(true)
    expect(wrapper.html()).toMatchSnapshot()
  })
})


컴포넌트 프레임웤인 Vuetify는 늘 수평적으로 확장됩니다. 프로젝트에 따라 작은 **패키지 크기**가 필요할 수도 있습니다. 아라카르트(A la crate) 시스템은 임포트(import) 할 컴포넌트들을 선택할 수 있게 하여 빌드 크기를 극적으로 _줄여줍니다_. **이미 `a-la-carte` 템플릿을 설치했다면  [다음 가이드](/guides/a-la-carte#application)로 건너 뛰셔도 됩니다.**

`vue init vuetifyjs/a-la-carte` 명령어를 사용하여 **a-la-carte** 템플릿을 cli 에서 설치하면 단일 컴포넌트를 임포트하는 예제 프로젝트를 볼 수 있습니다. 우리가 제공하는 다른 템플릿 중의 일부는 a-la-carte 컴포넌트들을 활성화하는 옵션을 제공하기도 합니다. 더 자세한 정보는 [빠른 시작 안내](/ko/getting-started/quick-start)를 참고하세요

`transform-imports` 패키지가 a-la-carte 컴포넌트에 꼭 필요한 건 아니지만 패키지들을 임포팅하는 프로세스를 간단하게 만들기 때문에 "매우" 추천합니다. 이는 컴포넌트를 임포팅할때 더 간결한 문법을 사용하게 해 줍니다.

Vuetify 를 쓰려면 `Vuetify` 와 `VApp` 컴포넌트가 둘다 필요하다는 것을 기억하세요

`Vue.use` 의 두번째 인자로 사용하는 옵션 객체는 _directives_ 와 _transitions_ 프로퍼티를 둘다 포함할 수 있습니다.

transform-imports 패키지를 사용하지 않는다면 다음처럼 각 컴포넌트를 직접 임포트해야 합니다.

아래와 같이 .vue 파일에서 컴포넌트를 임포트할 수도 있습니다. 이 경우 중요한 점은 이름을 가진 export들(named exports)을 모두 임포트하고 컴포넌트 내내에서 비구조화(destructure)해야 한다는 것입니다. 그렇지 않으면 `v-card-text` 나 `v-expansion-panel-content` 와 같은 자식 컴포넌트들을 사용할 수 없게 됩니다.

필요한 모든 스타일을 적용하려면 스타일들을 stylus 내에 임포트해야 합니다. 웹팩이 이 것을 다루기 때문에, `stylus` 와 `stylus-loader` 를 **npm** 으로 설치해야 합니다.

stylus 설정에 대한 더 자세한 설명은 테마 페이지를 참고하세요.

이제 어플리케이션의 엔트리 포인트에서 stylus를 require 해야합니다. 이 엔트리 포인트는 Vue 와  Vuetify 를 임포팅한 것과 같은 파일입니다 (보통 `main.js`나 `app.js`). 메인 `App.vue`에서 stylus 를 require 하면 업데이트마다 다시 프로세싱되기 때문에 로딩 타임이 느려질 수 있습니다.

**src** 폴더로 가서 `main.js` 를 여세요. a-la-carte 템플릿을 사용하고 계신다면 이미 일부 컴포넌트들이 로딩되도록 설정되어 있을 겁니다.

아래 코드 예제는 `<v-app>`,`<v-navigation-drawer>`, `<v-footer>`, `<v-toolbar>` 를 사용하는 어플리케이션입니다. 일단 정의된 후에는 이 마크업들을 어떤 .vue 파일에서든 사용할 수 있습니다.`

`VLayout` 이나 `VFlex` 같은 일부 컴포넌트들은 구조적인(organizational) 이유로 다른 컴포넌트들(이 경우엔 `VGrid`)에 포함되어 있습니다. 어떤 컴포넌트들을 import 해야할지 알기 위해선 다음 테이블을 참조하세요.


Input: El modelo ligado actualizado