vuetify 2.4.4 → 2.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/json/attributes.json +7 -7
  2. package/dist/json/web-types.json +12 -12
  3. package/dist/vuetify.css +3 -0
  4. package/dist/vuetify.css.map +1 -1
  5. package/dist/vuetify.js +88 -34
  6. package/dist/vuetify.js.map +1 -1
  7. package/dist/vuetify.min.css +2 -2
  8. package/dist/vuetify.min.js +2 -2
  9. package/es5/components/VCalendar/VCalendarCategory.js +8 -6
  10. package/es5/components/VCalendar/VCalendarCategory.js.map +1 -1
  11. package/es5/components/VCombobox/VCombobox.js +7 -1
  12. package/es5/components/VCombobox/VCombobox.js.map +1 -1
  13. package/es5/components/VData/VData.js +1 -1
  14. package/es5/components/VData/VData.js.map +1 -1
  15. package/es5/components/VFileInput/VFileInput.js +1 -1
  16. package/es5/components/VFileInput/VFileInput.js.map +1 -1
  17. package/es5/components/VLazy/VLazy.js +3 -8
  18. package/es5/components/VLazy/VLazy.js.map +1 -1
  19. package/es5/components/VList/VListItem.js +1 -1
  20. package/es5/components/VList/VListItem.js.map +1 -1
  21. package/es5/components/VRating/VRating.js +0 -1
  22. package/es5/components/VRating/VRating.js.map +1 -1
  23. package/es5/components/VSelect/VSelect.js +19 -10
  24. package/es5/components/VSelect/VSelect.js.map +1 -1
  25. package/es5/components/VTextField/VTextField.js +8 -2
  26. package/es5/components/VTextField/VTextField.js.map +1 -1
  27. package/es5/framework.js +1 -1
  28. package/es5/locale/sk.js +1 -1
  29. package/es5/locale/sk.js.map +1 -1
  30. package/es5/services/theme/utils.js +10 -10
  31. package/es5/services/theme/utils.js.map +1 -1
  32. package/es5/util/dom.js +33 -0
  33. package/es5/util/dom.js.map +1 -0
  34. package/lib/components/VCalendar/VCalendarCategory.js +7 -5
  35. package/lib/components/VCalendar/VCalendarCategory.js.map +1 -1
  36. package/lib/components/VCombobox/VCombobox.js +6 -1
  37. package/lib/components/VCombobox/VCombobox.js.map +1 -1
  38. package/lib/components/VData/VData.js +1 -1
  39. package/lib/components/VData/VData.js.map +1 -1
  40. package/lib/components/VFileInput/VFileInput.js +1 -1
  41. package/lib/components/VFileInput/VFileInput.js.map +1 -1
  42. package/lib/components/VLazy/VLazy.js +3 -8
  43. package/lib/components/VLazy/VLazy.js.map +1 -1
  44. package/lib/components/VList/VListItem.js +1 -1
  45. package/lib/components/VList/VListItem.js.map +1 -1
  46. package/lib/components/VRating/VRating.js +0 -1
  47. package/lib/components/VRating/VRating.js.map +1 -1
  48. package/lib/components/VSelect/VSelect.js +9 -2
  49. package/lib/components/VSelect/VSelect.js.map +1 -1
  50. package/lib/components/VTextField/VTextField.js +7 -2
  51. package/lib/components/VTextField/VTextField.js.map +1 -1
  52. package/lib/framework.js +1 -1
  53. package/lib/locale/sk.js +1 -1
  54. package/lib/locale/sk.js.map +1 -1
  55. package/lib/util/dom.js +24 -0
  56. package/lib/util/dom.js.map +1 -0
  57. package/package.json +2 -2
  58. package/src/components/VAutocomplete/__tests__/VAutocomplete.spec.ts +3 -1
  59. package/src/components/VAutocomplete/__tests__/VAutocomplete2.spec.ts +3 -0
  60. package/src/components/VAutocomplete/__tests__/VAutocomplete3.spec.ts +1 -0
  61. package/src/components/VCalendar/VCalendarCategory.ts +7 -5
  62. package/src/components/VCombobox/VCombobox.ts +10 -0
  63. package/src/components/VCombobox/__tests__/VCombobox-multiple.spec.ts +1 -1
  64. package/src/components/VCombobox/__tests__/VCombobox.spec.ts +1 -0
  65. package/src/components/VData/VData.ts +1 -1
  66. package/src/components/VDataTable/VDataTableHeader.sass +2 -0
  67. package/src/components/VFileInput/VFileInput.ts +1 -1
  68. package/src/components/VLazy/VLazy.ts +6 -11
  69. package/src/components/VList/VListItem.ts +1 -1
  70. package/src/components/VList/__tests__/VListItem.spec.ts +1 -1
  71. package/src/components/VRating/VRating.ts +0 -1
  72. package/src/components/VSelect/VSelect.ts +13 -4
  73. package/src/components/VSelect/__tests__/VSelect.spec.ts +67 -0
  74. package/src/components/VSelect/__tests__/VSelect2.spec.ts +26 -0
  75. package/src/components/VSelect/__tests__/__snapshots__/VSelect2.spec.ts.snap +4 -4
  76. package/src/components/VTextField/VTextField.ts +9 -4
  77. package/src/components/VTextField/__tests__/VTextField.spec.ts +15 -4
  78. package/src/locale/sk.ts +1 -1
  79. package/src/util/__tests__/dom.spec.ts +42 -0
  80. package/src/util/dom.ts +24 -0
  81. package/types/services/icons.d.ts +2 -2
@@ -156,6 +156,7 @@ describe('VAutocomplete.ts', () => {
156
156
 
157
157
  it('should not hide menu when no data but has no-data slot', async () => {
158
158
  const wrapper = mountFunction({
159
+ attachToDocument: true,
159
160
  propsData: {
160
161
  combobox: true,
161
162
  },
@@ -278,6 +279,7 @@ describe('VAutocomplete.ts', () => {
278
279
 
279
280
  it('should not show menu when items are updated and hide-no-data is enabled', async () => {
280
281
  const wrapper = mountFunction({
282
+ attachToDocument: true,
281
283
  propsData: {
282
284
  hideNoData: true,
283
285
  items: ['Something first'],
@@ -373,6 +375,7 @@ describe('VAutocomplete.ts', () => {
373
375
  // https://github.com/vuetifyjs/vuetify/issues/4580
374
376
  it('should display menu when hide-no-date and hide-selected are enabled and selected item does not match search', async () => {
375
377
  const wrapper = mountFunction({
378
+ attachToDocument: true,
376
379
  propsData: {
377
380
  items: [1, 2],
378
381
  value: 1,
@@ -43,6 +43,7 @@ describe('VAutocomplete.ts', () => {
43
43
  // https://github.com/vuetifyjs/vuetify/issues/7259
44
44
  it('should update search when same item is selected', async () => {
45
45
  const wrapper = mountFunction({
46
+ attachToDocument: true,
46
47
  propsData: {
47
48
  items: ['foo'],
48
49
  value: 'foo',
@@ -71,11 +71,13 @@ export default VCalendarDaily.extend({
71
71
  }, categoryName === null ? this.categoryForInvalid : categoryName)
72
72
  },
73
73
  genDays (): VNode[] {
74
- const d = this.days[0]
75
- let days = this.days.slice()
76
- days = new Array(this.parsedCategories.length)
77
- days.fill(d)
78
- return days.map((v, i) => this.genDay(v, 0, i))
74
+ const days: VNode[] = []
75
+ this.days.forEach(d => {
76
+ const day = new Array(this.parsedCategories.length)
77
+ day.fill(d)
78
+ days.push(...day.map((v, i) => this.genDay(v, 0, i)))
79
+ })
80
+ return days
79
81
  },
80
82
  genDay (day: CalendarTimestamp, index: number, categoryIndex: number): VNode {
81
83
  const category = this.parsedCategories[categoryIndex]
@@ -154,6 +154,16 @@ export default VAutocomplete.extend({
154
154
  this.updateEditing()
155
155
  } else {
156
156
  VAutocomplete.options.methods.selectItem.call(this, item)
157
+
158
+ // if selected item contains search value,
159
+ // remove the search string
160
+ if (
161
+ this.internalSearch &&
162
+ this.multiple &&
163
+ this.getText(item).toLocaleLowerCase().includes(this.internalSearch.toLocaleLowerCase())
164
+ ) {
165
+ this.internalSearch = null
166
+ }
157
167
  }
158
168
  },
159
169
  setSelectedItems () {
@@ -403,7 +403,7 @@ describe('VCombobox.ts', () => {
403
403
 
404
404
  await wrapper.vm.$nextTick()
405
405
 
406
- expect(wrapper.vm.internalSearch).toBe('a')
406
+ expect(wrapper.vm.internalSearch).toBeNull()
407
407
  expect(change).toHaveBeenCalledWith(['aaa'])
408
408
  })
409
409
 
@@ -234,6 +234,7 @@ describe('VCombobox.ts', () => {
234
234
  { text: 'Vuetify', value: 3 },
235
235
  ]
236
236
  const wrapper = mountFunction({
237
+ attachToDocument: true,
237
238
  propsData: {
238
239
  items,
239
240
  },
@@ -365,7 +365,7 @@ export default Vue.extend({
365
365
  // Make sure we don't try to display non-existant page if items suddenly change
366
366
  // TODO: Could possibly move this to pageStart/pageStop?
367
367
  if (this.serverItemsLength === -1 && items.length <= this.pageStart) {
368
- this.internalOptions.page = Math.max(1, this.internalOptions.page - 1)
368
+ this.internalOptions.page = Math.max(1, Math.ceil(items.length / this.internalOptions.itemsPerPage))
369
369
  }
370
370
 
371
371
  return items.slice(this.pageStart, this.pageStop)
@@ -27,6 +27,8 @@
27
27
  pointer-events: auto
28
28
  cursor: pointer
29
29
  outline: 0
30
+ .v-data-table-header__icon
31
+ line-height: .9
30
32
 
31
33
  &.active, &:hover
32
34
  .v-data-table-header__icon
@@ -120,7 +120,7 @@ export default VTextField.extend({
120
120
  return this.$attrs.hasOwnProperty('multiple')
121
121
  },
122
122
  text (): string[] {
123
- if (!this.isDirty) return [this.placeholder]
123
+ if (!this.isDirty && (this.isFocused || !this.hasLabel)) return [this.placeholder]
124
124
 
125
125
  return this.internalArrayValue.map((file: File) => {
126
126
  const {
@@ -52,18 +52,13 @@ export default mixins(
52
52
 
53
53
  methods: {
54
54
  genContent () {
55
- const slot = getSlot(this)
55
+ const children = this.isActive && getSlot(this)
56
56
 
57
- /* istanbul ignore if */
58
- if (!this.transition) return slot
59
-
60
- const children = []
61
-
62
- if (this.isActive) children.push(slot)
63
-
64
- return this.$createElement('transition', {
65
- props: { name: this.transition },
66
- }, children)
57
+ return this.transition
58
+ ? this.$createElement('transition', {
59
+ props: { name: this.transition },
60
+ }, children)
61
+ : children
67
62
  },
68
63
  onObserve (
69
64
  entries: IntersectionObserverEntry[],
@@ -139,7 +139,7 @@ export default baseMixins.extend<options>().extend({
139
139
  } else if (this.isInNav) {
140
140
  // do nothing, role is inherit
141
141
  } else if (this.isInGroup) {
142
- attrs.role = 'listitem'
142
+ attrs.role = 'option'
143
143
  attrs['aria-selected'] = String(this.isActive)
144
144
  } else if (this.isInMenu) {
145
145
  attrs.role = this.isClickable ? 'menuitem' : undefined
@@ -202,7 +202,7 @@ describe('VListItem.ts', () => {
202
202
  const wrapper3 = mountFunction({
203
203
  provide: { isInGroup: true },
204
204
  })
205
- expect(wrapper3.element.getAttribute('role')).toBe('listitem')
205
+ expect(wrapper3.element.getAttribute('role')).toBe('option')
206
206
 
207
207
  // In menu
208
208
  const wrapper4 = mountFunction({
@@ -224,7 +224,6 @@ export default mixins(
224
224
 
225
225
  return this.$createElement(VIcon, this.setTextColor(this.getColor(props), {
226
226
  attrs: {
227
- tabindex: -1,
228
227
  'aria-label': this.$vuetify.lang.t(this.iconLabel, i + 1, Number(this.length)),
229
228
  },
230
229
  directives: this.directives,
@@ -155,9 +155,15 @@ export default baseMixins.extend<options>().extend({
155
155
  return `list-${this._uid}`
156
156
  },
157
157
  computedCounterValue (): number {
158
- return this.multiple
159
- ? this.selectedItems.length
160
- : (this.getText(this.selectedItems[0]) || '').toString().length
158
+ const value = this.multiple
159
+ ? this.selectedItems
160
+ : (this.getText(this.selectedItems[0]) || '').toString()
161
+
162
+ if (typeof this.counterValue === 'function') {
163
+ return this.counterValue(value)
164
+ }
165
+
166
+ return value.length
161
167
  },
162
168
  directives (): VNodeDirective[] | undefined {
163
169
  return this.isFocused ? [{
@@ -790,6 +796,9 @@ export default baseMixins.extend<options>().extend({
790
796
 
791
797
  window.requestAnimationFrame(() => {
792
798
  menu.getTiles()
799
+
800
+ if (!menu.hasClickableTiles) return this.activateMenu()
801
+
793
802
  switch (keyCode) {
794
803
  case keyCodes.up:
795
804
  menu.prevTile()
@@ -804,7 +813,7 @@ export default baseMixins.extend<options>().extend({
804
813
  menu.lastTile()
805
814
  break
806
815
  }
807
- menu.activeTile && menu.activeTile.click()
816
+ this.selectItem(this.allItems[this.getMenuIndex()])
808
817
  })
809
818
  },
810
819
  selectItem (item: object) {
@@ -3,6 +3,7 @@ import Vue from 'vue'
3
3
 
4
4
  // Components
5
5
  import VSelect from '../VSelect'
6
+ import VDialog from '../../VDialog/VDialog'
6
7
  import {
7
8
  VListItem,
8
9
  VListItemTitle,
@@ -14,6 +15,8 @@ import {
14
15
  mount,
15
16
  Wrapper,
16
17
  } from '@vue/test-utils'
18
+ import { keyCodes } from '../../../util/helpers'
19
+ import { waitAnimationFrame } from '../../../../test'
17
20
 
18
21
  // eslint-disable-next-line max-statements
19
22
  describe('VSelect.ts', () => {
@@ -456,4 +459,68 @@ describe('VSelect.ts', () => {
456
459
 
457
460
  expect(wrapper.vm.$attrs.autocomplete).toBe('on')
458
461
  })
462
+
463
+ // Based on issue: https://github.com/vuetifyjs/vuetify/issues/12769
464
+ it('should cycle through selected items as per kep up & down without closing hosting menu dialog', async () => {
465
+ const items = ['Foo', 'Bar', 'Fizz', 'Buzz']
466
+
467
+ const dialogClickOutside = jest.fn()
468
+
469
+ const dialogWrapper = mount(VDialog, {
470
+ slots: {
471
+ default: {
472
+ render: h => h(VSelect, {
473
+ props: {
474
+ items,
475
+ },
476
+ }),
477
+ },
478
+ },
479
+ propsData: {
480
+ value: false,
481
+ fullscreen: true,
482
+ },
483
+ mocks: {
484
+ $vuetify: {
485
+ lang: {
486
+ t: (val: string) => val,
487
+ },
488
+ theme: {
489
+ dark: false,
490
+ },
491
+ breakpoint: {},
492
+ },
493
+ },
494
+ }) as Wrapper<InstanceType<typeof VDialog>>
495
+
496
+ dialogWrapper.vm.$on('click:outside', dialogClickOutside)
497
+
498
+ // Open dialog
499
+ dialogWrapper.setProps({ value: true })
500
+ await dialogWrapper.vm.$nextTick()
501
+
502
+ // Confirm Dialog is open
503
+ expect(dialogWrapper.vm.isActive).toBe(true)
504
+
505
+ const selectWrapper = dialogWrapper.find({ name: 'v-select' }) as Wrapper<Instance>
506
+
507
+ // Press key down twice to move selected item from null to Bar
508
+ const keyDownEvent = new KeyboardEvent('keydown', { keyCode: keyCodes.down })
509
+ selectWrapper.vm.onKeyDown(keyDownEvent)
510
+ await waitAnimationFrame()
511
+ selectWrapper.vm.onKeyDown(keyDownEvent)
512
+ await waitAnimationFrame()
513
+ expect(selectWrapper.vm.internalValue).toBe('Bar')
514
+
515
+ // Press key up once to move selected item from Bar to Foo
516
+ const keyUpEvent = new KeyboardEvent('keyup', { keyCode: keyCodes.up })
517
+ selectWrapper.vm.onKeyDown(keyUpEvent)
518
+ await waitAnimationFrame()
519
+ expect(selectWrapper.vm.internalValue).toBe('Foo')
520
+
521
+ // Confirm dialog click outside event has not been called
522
+ expect(dialogClickOutside).toHaveBeenCalledTimes(0)
523
+ // Confirm dialog is still open
524
+ expect(dialogWrapper.vm.isActive).toBe(true)
525
+ })
459
526
  })
@@ -162,6 +162,32 @@ describe('VSelect.ts', () => {
162
162
  expect(wrapper.vm.computedCounterValue).toBe(2)
163
163
  })
164
164
 
165
+ it('should return the correct counter value', async () => {
166
+ const wrapper = mountFunction({
167
+ propsData: {
168
+ items: ['foo', 'bar'],
169
+ value: 'foo',
170
+ },
171
+ })
172
+
173
+ expect(wrapper.vm.computedCounterValue).toBe(3)
174
+
175
+ wrapper.setProps({
176
+ multiple: true,
177
+ value: ['foo'],
178
+ })
179
+
180
+ expect(wrapper.vm.computedCounterValue).toBe(1)
181
+
182
+ wrapper.setProps({
183
+ counterValue: (value?: string): number => 2,
184
+ multiple: false,
185
+ value: undefined,
186
+ })
187
+
188
+ expect(wrapper.vm.computedCounterValue).toBe(2)
189
+ })
190
+
165
191
  it('should emit a single change event', async () => {
166
192
  const wrapper = mountFunction({
167
193
  attachToDocument: true,
@@ -6,7 +6,7 @@ exports[`VSelect.ts should be clearable with prop, dirty and multi select 1`] =
6
6
  <div role="button"
7
7
  aria-haspopup="listbox"
8
8
  aria-expanded="false"
9
- aria-owns="list-100"
9
+ aria-owns="list-106"
10
10
  class="v-input__slot"
11
11
  >
12
12
  <div class="v-select__slot">
@@ -14,7 +14,7 @@ exports[`VSelect.ts should be clearable with prop, dirty and multi select 1`] =
14
14
  <div class="v-select__selection v-select__selection--comma">
15
15
  1
16
16
  </div>
17
- <input id="input-100"
17
+ <input id="input-106"
18
18
  readonly="readonly"
19
19
  type="text"
20
20
  aria-readonly="false"
@@ -66,7 +66,7 @@ exports[`VSelect.ts should be clearable with prop, dirty and single select 1`] =
66
66
  <div role="button"
67
67
  aria-haspopup="listbox"
68
68
  aria-expanded="false"
69
- aria-owns="list-93"
69
+ aria-owns="list-99"
70
70
  class="v-input__slot"
71
71
  >
72
72
  <div class="v-select__slot">
@@ -74,7 +74,7 @@ exports[`VSelect.ts should be clearable with prop, dirty and single select 1`] =
74
74
  <div class="v-select__selection v-select__selection--comma">
75
75
  1
76
76
  </div>
77
- <input id="input-93"
77
+ <input id="input-99"
78
78
  readonly="readonly"
79
79
  type="text"
80
80
  aria-readonly="false"
@@ -18,6 +18,7 @@ import resize from '../../directives/resize'
18
18
  import ripple from '../../directives/ripple'
19
19
 
20
20
  // Utilities
21
+ import { attachedRoot } from '../../util/dom'
21
22
  import { convertToUnit, keyCodes } from '../../util/helpers'
22
23
  import { breaking, consoleWarn } from '../../util/console'
23
24
 
@@ -446,7 +447,10 @@ export default baseMixins.extend<options>().extend({
446
447
  onFocus (e?: Event) {
447
448
  if (!this.$refs.input) return
448
449
 
449
- if (document.activeElement !== this.$refs.input) {
450
+ const root = attachedRoot(this.$el)
451
+ if (!root) return
452
+
453
+ if (root.activeElement !== this.$refs.input) {
450
454
  return this.$refs.input.focus()
451
455
  }
452
456
 
@@ -500,9 +504,10 @@ export default baseMixins.extend<options>().extend({
500
504
  if (
501
505
  !this.autofocus ||
502
506
  typeof document === 'undefined' ||
503
- !this.$refs.input ||
504
- document.activeElement === this.$refs.input
505
- ) return false
507
+ !this.$refs.input) return false
508
+
509
+ const root = attachedRoot(this.$el)
510
+ if (!root || root.activeElement === this.$refs.input) return false
506
511
 
507
512
  this.$refs.input.focus()
508
513
 
@@ -96,7 +96,9 @@ describe('VTextField.ts', () => { // eslint-disable-line max-statements
96
96
  })
97
97
 
98
98
  it('should start validating on input', async () => {
99
- const wrapper = mountFunction()
99
+ const wrapper = mountFunction({
100
+ attachToDocument: true,
101
+ })
100
102
 
101
103
  expect(wrapper.vm.shouldValidate).toEqual(false)
102
104
  wrapper.setProps({ value: 'asd' })
@@ -218,6 +220,7 @@ describe('VTextField.ts', () => { // eslint-disable-line max-statements
218
220
  it('should start validating on blur', async () => {
219
221
  const rule = jest.fn().mockReturnValue(true)
220
222
  const wrapper = mountFunction({
223
+ attachToDocument: true,
221
224
  propsData: {
222
225
  rules: [rule],
223
226
  validateOnBlur: true,
@@ -291,7 +294,9 @@ describe('VTextField.ts', () => { // eslint-disable-line max-statements
291
294
  })
292
295
  },
293
296
  }
294
- const wrapper = mount(component)
297
+ const wrapper = mount(component, {
298
+ attachToDocument: true,
299
+ })
295
300
 
296
301
  const input = wrapper.findAll('input').at(0)
297
302
 
@@ -640,7 +645,9 @@ describe('VTextField.ts', () => { // eslint-disable-line max-statements
640
645
  })
641
646
 
642
647
  it('should have focus and blur methods', async () => {
643
- const wrapper = mountFunction()
648
+ const wrapper = mountFunction({
649
+ attachToDocument: true,
650
+ })
644
651
  const onBlur = jest.spyOn(wrapper.vm.$refs.input, 'blur')
645
652
  const onFocus = jest.spyOn(wrapper.vm.$refs.input, 'focus')
646
653
 
@@ -777,7 +784,9 @@ describe('VTextField.ts', () => { // eslint-disable-line max-statements
777
784
  })
778
785
  },
779
786
  }
780
- const wrapper = mount(component)
787
+ const wrapper = mount(component, {
788
+ attachToDocument: true,
789
+ })
781
790
 
782
791
  const inputElement = wrapper.findAll('input').at(0)
783
792
  const clearIcon = wrapper.find('.v-input__icon--clear .v-icon')
@@ -812,6 +821,7 @@ describe('VTextField.ts', () => { // eslint-disable-line max-statements
812
821
 
813
822
  it('should autofocus text-field when intersected', async () => {
814
823
  const wrapper = mountFunction({
824
+ attachToDocument: true,
815
825
  propsData: { autofocus: true },
816
826
  })
817
827
  const input = wrapper.find('input')
@@ -844,6 +854,7 @@ describe('VTextField.ts', () => { // eslint-disable-line max-statements
844
854
 
845
855
  it('should use the correct icon color when using the solo inverted prop', () => {
846
856
  const wrapper = mountFunction({
857
+ attachToDocument: true,
847
858
  propsData: { soloInverted: true },
848
859
  mocks: {
849
860
  $vuetify: {
package/src/locale/sk.ts CHANGED
@@ -63,7 +63,7 @@ export default {
63
63
  },
64
64
  rating: {
65
65
  ariaLabel: {
66
- icon: 'Rating {0} of {1}',
66
+ icon: 'Hodnotenie {0} z {1}',
67
67
  },
68
68
  },
69
69
  }
@@ -0,0 +1,42 @@
1
+ import Vue from 'vue'
2
+ import {
3
+ mount,
4
+ } from '@vue/test-utils'
5
+ import {
6
+ attachedRoot,
7
+ } from '../dom'
8
+
9
+ const FooComponent = Vue.extend({
10
+ render (h) {
11
+ return h('div', ['foo'])
12
+ },
13
+ })
14
+
15
+ describe('dom', () => {
16
+ it('should properly detect an element\'s root', () => {
17
+ const shadowHost = document.createElement('div')
18
+ expect(attachedRoot(shadowHost)).toBeNull()
19
+
20
+ const shadowRoot = shadowHost.attachShadow({ mode: 'closed' })
21
+ expect(attachedRoot(shadowRoot)).toBeNull()
22
+
23
+ document.body.appendChild(shadowHost)
24
+ expect(attachedRoot(shadowHost)).toBe(document)
25
+
26
+ expect(attachedRoot(shadowRoot)).toBe(shadowRoot)
27
+
28
+ const insideDiv = document.createElement('div')
29
+ expect(attachedRoot(insideDiv)).toBeNull()
30
+
31
+ shadowRoot.appendChild(insideDiv)
32
+ expect(attachedRoot(insideDiv)).toBe(shadowRoot)
33
+ })
34
+
35
+ it('should detect the root of mounted components', () => {
36
+ const attachedWrapper = mount(FooComponent, { attachToDocument: true })
37
+ expect(attachedRoot(attachedWrapper.element)).toBe(document)
38
+
39
+ const detachedWrapper = mount(FooComponent, { attachToDocument: false })
40
+ expect(attachedRoot(detachedWrapper.element)).toBeNull()
41
+ })
42
+ })
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Returns:
3
+ * - 'null' if the node is not attached to the DOM
4
+ * - the root node (HTMLDocument | ShadowRoot) otherwise
5
+ */
6
+ export function attachedRoot (node: Node): null | HTMLDocument | ShadowRoot {
7
+ /* istanbul ignore next */
8
+ if (typeof node.getRootNode !== 'function') {
9
+ // Shadow DOM not supported (IE11), lets find the root of this node
10
+ while (node.parentNode) node = node.parentNode
11
+
12
+ // The root parent is the document if the node is attached to the DOM
13
+ if (node !== document) return null
14
+
15
+ return document
16
+ }
17
+
18
+ const root = node.getRootNode()
19
+
20
+ // The composed root node is the document if the node is attached to the DOM
21
+ if (root !== document && root.getRootNode({ composed: true }) !== document) return null
22
+
23
+ return root as HTMLDocument | ShadowRoot
24
+ }
@@ -2,7 +2,6 @@
2
2
  import { Component } from 'vue'
3
3
 
4
4
  export interface Icons extends IconsOptions {
5
- component?: Component | string
6
5
  iconfont: Iconfont
7
6
  values: VuetifyIcons
8
7
  }
@@ -10,13 +9,14 @@ export interface Icons extends IconsOptions {
10
9
  export type Iconfont = 'mdi' | 'mdiSvg' | 'md' | 'fa' | 'faSvg' | 'fa4'
11
10
 
12
11
  export interface IconsOptions {
12
+ component?: Component | string
13
13
  /**
14
14
  * Select a base icon font to use. Note that none of these are included, you must install them yourself
15
15
  *
16
16
  * md: <a href="https://material.io/icons">material.io</a> (default)
17
17
  * mdi: <a href="https://materialdesignicons.com">MDI</a>
18
18
  * fa: <a href="https://fontawesome.com/get-started/web-fonts-with-css">FontAwesome 5</a>
19
- * fa4: <a href="">FontAwesome 4</a> TODO: link
19
+ * fa4: <a href="https://fontawesome.com/v4.7.0/">FontAwesome 4</a>
20
20
  * faSvg: <a href="https://fontawesome.com/how-to-use/on-the-web/using-with/vuejs">FontAwesome SVG</a>
21
21
  */
22
22
  iconfont?: Iconfont