@veritree/ui 0.19.2-2 → 0.19.2-21

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 (62) hide show
  1. package/mixins/floating-ui-content.js +81 -0
  2. package/mixins/floating-ui-item.js +266 -0
  3. package/mixins/floating-ui.js +67 -0
  4. package/mixins/form-control-icon.js +53 -0
  5. package/mixins/form-control.js +73 -0
  6. package/package.json +7 -3
  7. package/src/components/Avatar/VTAvatar.vue +32 -29
  8. package/src/components/Button/VTButton.vue +9 -5
  9. package/src/components/Dialog/VTDialog.vue +6 -11
  10. package/src/components/Dialog/VTDialogClose.vue +9 -9
  11. package/src/components/Dialog/VTDialogContent.vue +9 -9
  12. package/src/components/Dialog/VTDialogFooter.vue +5 -5
  13. package/src/components/Dialog/VTDialogHeader.vue +8 -8
  14. package/src/components/Dialog/VTDialogMain.vue +8 -8
  15. package/src/components/Dialog/VTDialogOverlay.vue +8 -8
  16. package/src/components/Dialog/VTDialogTitle.vue +4 -4
  17. package/src/components/Disclosure/VTDisclosureContent.vue +1 -1
  18. package/src/components/Disclosure/VTDisclosureDetails.vue +2 -2
  19. package/src/components/Disclosure/VTDisclosureHeader.vue +1 -1
  20. package/src/components/Disclosure/VTDisclosureIcon.vue +1 -1
  21. package/src/components/Drawer/VTDrawer.vue +14 -16
  22. package/src/components/Drawer/VTDrawerClose.vue +9 -9
  23. package/src/components/Drawer/VTDrawerContent.vue +8 -8
  24. package/src/components/Drawer/VTDrawerFooter.vue +3 -3
  25. package/src/components/Drawer/VTDrawerHeader.vue +4 -4
  26. package/src/components/Drawer/VTDrawerMain.vue +5 -5
  27. package/src/components/Drawer/VTDrawerOverlay.vue +6 -6
  28. package/src/components/Drawer/VTDrawerTitle.vue +5 -5
  29. package/src/components/DropdownMenu/VTDropdownMenu.vue +27 -29
  30. package/src/components/DropdownMenu/VTDropdownMenuContent.vue +27 -70
  31. package/src/components/DropdownMenu/VTDropdownMenuDivider.vue +8 -5
  32. package/src/components/DropdownMenu/VTDropdownMenuItem.vue +14 -123
  33. package/src/components/DropdownMenu/VTDropdownMenuLabel.vue +3 -3
  34. package/src/components/DropdownMenu/VTDropdownMenuTrigger.vue +96 -121
  35. package/src/components/Form/VTFormFeedback.vue +33 -22
  36. package/src/components/Form/VTFormGroup.vue +5 -7
  37. package/src/components/Form/VTFormLabel.vue +22 -0
  38. package/src/components/Form/VTFormRow.vue +5 -0
  39. package/src/components/Form/VTInput.vue +40 -0
  40. package/src/components/Form/VTInputIcon.vue +35 -0
  41. package/src/components/Form/VTInputPassword.vue +55 -0
  42. package/src/components/Form/VTTextarea.vue +22 -0
  43. package/src/components/Listbox/VTListbox.vue +122 -50
  44. package/src/components/Listbox/VTListboxContent.vue +20 -116
  45. package/src/components/Listbox/VTListboxItem.vue +116 -166
  46. package/src/components/Listbox/VTListboxLabel.vue +3 -14
  47. package/src/components/Listbox/VTListboxList.vue +10 -40
  48. package/src/components/Listbox/VTListboxSearch.vue +76 -68
  49. package/src/components/Listbox/VTListboxTrigger.vue +75 -86
  50. package/src/components/Popover/VTPopover.vue +24 -30
  51. package/src/components/Popover/VTPopoverContent.vue +24 -59
  52. package/src/components/Popover/VTPopoverDivider.vue +4 -11
  53. package/src/components/Popover/VTPopoverItem.vue +21 -14
  54. package/src/components/Popover/VTPopoverTrigger.vue +126 -21
  55. package/src/components/Tabs/VTTab.vue +10 -11
  56. package/src/components/Tabs/VTTabGroup.vue +9 -7
  57. package/src/components/Tabs/VTTabPanel.vue +4 -5
  58. package/src/components/Transitions/FadeInOut.vue +2 -2
  59. package/src/components/Utils/FloatingUi.vue +87 -0
  60. package/package-lock.json +0 -13
  61. package/src/components/Modal/VTModal.vue +0 -69
  62. package/src/utils/genId.js +0 -13
@@ -1,34 +1,20 @@
1
1
  <template>
2
2
  <button
3
+ :id="id"
4
+ :class="classComputed"
5
+ :disabled="disabled"
3
6
  :aria-expanded="expanded"
4
7
  :aria-haspopup="hasPopup"
5
- :class="{
6
- 'Listbox-button': headless,
7
- 'flex w-full justify-between rounded-md border border-solid py-2 px-3':
8
- !headless,
9
- 'border-gray-300 text-gray-500': !dark && !headless,
10
- 'border-white/70 text-white focus-visible:ring-2 focus-visible:ring-white':
11
- dark && !headless,
12
- }"
13
8
  type="button"
14
- @click.stop.prevent="onClick"
15
- @keydown.down.prevent="onKeyArrowDown"
16
- @keydown.up.prevent="onKeyArrowUp"
9
+ @click.prevent="onClick"
10
+ @keydown.down.prevent="onKeyDownOrUp"
11
+ @keydown.up.prevent="onKeyDownOrUp"
17
12
  @keydown.esc.stop="onKeyEsc"
18
13
  >
19
- <span
20
- :class="{
21
- 'Listbox-button__text': headless,
22
- 'text-left': !headless,
23
- }"
24
- >
14
+ <span :class="[headless ? 'listbox-button__text' : 'truncate text-left']">
25
15
  <slot></slot>
26
16
  </span>
27
- <span
28
- :class="{
29
- 'Listbox-button__icon': headless,
30
- }"
31
- >
17
+ <span :class="[headless ? 'listbox-button__icon' : 'shrink-0']">
32
18
  <IconChevronDown
33
19
  class="transition-transform"
34
20
  :class="{ 'rotate-180': expanded }"
@@ -38,6 +24,10 @@
38
24
  </template>
39
25
 
40
26
  <script>
27
+ import {
28
+ formControlMixin,
29
+ formControlStyleMixin,
30
+ } from '../../../mixins/form-control';
41
31
  import { IconChevronDown } from '@veritree/icons';
42
32
 
43
33
  export default {
@@ -45,33 +35,29 @@ export default {
45
35
 
46
36
  components: { IconChevronDown },
47
37
 
48
- inject: ['api'],
38
+ mixins: [formControlMixin, formControlStyleMixin],
49
39
 
50
- props: {
51
- headless: {
52
- type: Boolean,
53
- default: false,
54
- },
55
- },
40
+ inject: ['apiListbox'],
56
41
 
57
42
  data() {
58
43
  return {
44
+ name: 'listbox-trigger',
59
45
  expanded: false,
60
46
  hasPopup: false,
61
47
  };
62
48
  },
63
49
 
64
50
  computed: {
65
- dark() {
66
- return this.api().isDark;
51
+ id() {
52
+ return `listbox-trigger-${this.apiListbox().id}`;
67
53
  },
68
54
 
69
- content() {
70
- return this.api().content;
55
+ componentContent() {
56
+ return this.apiListbox().componentContent;
71
57
  },
72
58
 
73
59
  items() {
74
- return this.api().items;
60
+ return this.apiListbox().items;
75
61
  },
76
62
 
77
63
  firstMenuItem() {
@@ -86,83 +72,86 @@ export default {
86
72
  mounted() {
87
73
  const trigger = {
88
74
  el: this.$el,
75
+ cancel: this.cancel,
89
76
  focus: this.focus,
90
- onClick: this.onClick,
91
- contract: this.contract,
77
+ id: this.id,
92
78
  };
93
79
 
94
- this.api().registerTrigger(trigger);
80
+ this.apiListbox().registerTrigger(trigger);
95
81
  },
96
82
 
97
83
  methods: {
98
- /**
99
- * Shows content/menu if not already visible
100
- */
101
- showContent() {
84
+ init(e) {
85
+ if (!this.componentContent) {
86
+ return;
87
+ }
88
+
89
+ if (this.expanded) {
90
+ this.cancel();
91
+ return;
92
+ }
93
+
102
94
  this.expanded = true;
103
- this.content.show();
104
- },
105
95
 
106
- focus() {
107
- this.$el.focus();
96
+ // delay stop propagation to close other visible
97
+ // dropdowns and delay click event to control
98
+ // this dropdown visibility
99
+ setTimeout(() => e.stopImmediatePropagation(), 50);
100
+ setTimeout(() => this.showComponentContent(), 100);
108
101
  },
109
102
 
110
- contract() {
111
- if (!this.expanded) return;
103
+ cancel() {
104
+ if (!this.componentContent) {
105
+ return;
106
+ }
107
+
112
108
  this.expanded = false;
113
- },
114
109
 
115
- /**
116
- * On click, do the following:
117
- *
118
- * 1. Toggle aria expanded attribute/state
119
- * 2. Open the menu if it's closed
120
- * 3. Close the menu if it's open
121
- */
122
- onClick() {
123
- if (!this.content) return;
124
- this.expanded ? this.content.hide() : this.showContent();
110
+ this.hideComponentContent();
125
111
  },
126
112
 
127
- /**
128
- * On key arrow down, do the following:
129
- *
130
- * 1. if the menu is not expanded, expand it and focus the first menu item
131
- * 2. if the menu is expanded, focus the first menu item
132
- */
133
- onKeyArrowDown() {
134
- if (!this.content) return;
113
+ focus() {
114
+ this.$el.focus();
115
+ },
135
116
 
136
- this.showContent();
117
+ showComponentContent() {
118
+ this.componentContent.show();
119
+ },
137
120
 
138
- this.$nextTick(() => {
139
- this.firstMenuItem.focus();
140
- });
121
+ hideComponentContent() {
122
+ this.componentContent.hide();
141
123
  },
142
124
 
143
- /**
144
- * On key arrow up, do the following:
145
- *
146
- * 1. if the menu is not expanded, expand it and focus the last menu item
147
- * 2. if the menu is expanded, focus the last menu item
148
- */
149
- onKeyArrowUp() {
150
- if (!this.content) return;
125
+ onClick(e) {
126
+ this.init(e);
127
+ this.$emit('click');
128
+ },
151
129
 
152
- this.showContent();
130
+ onKeyDownOrUp(e) {
131
+ if (!this.expanded) {
132
+ this.$el.click(e);
133
+ }
153
134
 
135
+ const keyCode = e.code;
136
+ const listItemPosition =
137
+ keyCode === 'ArrowDown'
138
+ ? 'firstMenuItem'
139
+ : keyCode === 'ArrowUp'
140
+ ? 'lastMenuItem'
141
+ : null;
142
+
143
+ // settimeout here is delaying the focusing the element
144
+ // since it is not rendered yet. All items will only
145
+ // be available when the content is fully visible.
154
146
  this.$nextTick(() => {
155
- this.lastMenuItem.focus();
147
+ setTimeout(() => this[listItemPosition].focus(), 100);
156
148
  });
157
149
  },
158
150
 
151
+ // change it to a better name or move the methods inside to another function
159
152
  onKeyEsc() {
160
- if (!this.content) return;
161
-
162
- if (this.expanded) {
163
- this.toggleExpanded();
164
- this.content.hide();
165
- }
153
+ this.cancel();
154
+ this.focus();
166
155
  },
167
156
  },
168
157
  };
@@ -2,43 +2,41 @@
2
2
  <div
3
3
  :id="id"
4
4
  class="relative"
5
- :aria-haspopup="content ? 'true' : null"
6
- :aria-controls="content ? content.id : null"
5
+ :aria-haspopup="componentContent ? 'true' : null"
6
+ :aria-controls="componentContent ? componentContent.id : null"
7
7
  >
8
8
  <slot></slot>
9
9
  </div>
10
10
  </template>
11
11
 
12
12
  <script>
13
- import { genId } from "../../utils/ids";
13
+ import { floatingUiMixin } from '../../../mixins/floating-ui';
14
+ import { genId } from '../../utils/ids';
14
15
 
15
16
  export default {
16
- name: "VTPopover",
17
+ name: 'VTPopover',
18
+
19
+ mixins: [floatingUiMixin],
17
20
 
18
21
  provide() {
19
22
  return {
20
- api: () => {
21
- const { dark: isDark, headless: isHeadless, right: isRight } = this;
22
- const { id, button, content } = this;
23
-
24
- const registerButton = (button) => {
25
- if (!button) return;
26
- this.button = button;
23
+ apiPopover: () => {
24
+ const registerTrigger = (trigger) => {
25
+ if (!trigger) return;
26
+ this.componentTrigger = trigger;
27
27
  };
28
28
 
29
29
  const registerContent = (content) => {
30
30
  if (!content) return;
31
- this.content = content;
31
+ this.componentContent = content;
32
32
  };
33
33
 
34
34
  return {
35
- id,
36
- isDark,
37
- isHeadless,
38
- isRight,
39
- button,
40
- content,
41
- registerButton,
35
+ id: this.componentId,
36
+ component: this.component,
37
+ componentTrigger: this.componentTrigger,
38
+ componentContent: this.componentContent,
39
+ registerTrigger,
42
40
  registerContent,
43
41
  };
44
42
  },
@@ -50,22 +48,18 @@ export default {
50
48
  type: Boolean,
51
49
  default: false,
52
50
  },
53
- dark: {
54
- type: Boolean,
55
- default: false,
56
- },
57
- right: {
58
- type: Boolean,
59
- default: false,
60
- },
61
51
  },
62
52
 
63
53
  data() {
64
54
  return {
65
- id: `popover-${genId()}`,
66
- button: null,
67
- content: null,
55
+ componentId: genId(),
68
56
  };
69
57
  },
58
+
59
+ computed: {
60
+ id() {
61
+ return `popover-${this.componentId}`;
62
+ },
63
+ },
70
64
  };
71
65
  </script>
@@ -1,82 +1,47 @@
1
1
  <template>
2
- <transition
3
- enter-active-class="duration-200 ease-out"
4
- enter-class="translate-y-[15px] opacity-0"
5
- enter-to-class="translate-y-0 opacity-100"
6
- leave-active-class="duration-200 ease-in"
7
- leave-class="translate-y-0 opacity-100"
8
- leave-to-class="translate-y-[15px] opacity-0"
9
- @after-leave="hide"
2
+ <FloatingUi
3
+ :visible="visible"
4
+ :id="id"
5
+ :headless="headless"
6
+ :class="{ 'popover-content': headless }"
7
+ :floating-ui-class="floatingUiClass"
10
8
  >
11
- <div
12
- v-show="visible"
13
- :id="id"
14
- :class="{
15
- PopoverPanel: headless,
16
- 'absolute top-full mt-3 rounded-md shadow-300 ': !headless,
17
- 'bg-white': !dark,
18
- 'border-gray-700 bg-forest-default shadow-gray-700': dark,
19
- 'left-0': !right,
20
- 'right-0': right,
21
- }"
22
- >
23
- <slot></slot>
24
- </div>
25
- </transition>
9
+ <slot></slot>
10
+ </FloatingUi>
26
11
  </template>
27
12
 
28
13
  <script>
29
- import { genId } from "../../utils/ids";
14
+ import { floatingUiContentMixin } from '../../../mixins/floating-ui-content';
30
15
 
31
16
  export default {
32
- name: "VTPopoverContent",
17
+ name: 'VTPopoverContent',
33
18
 
34
- inject: ["api"],
19
+ mixins: [floatingUiContentMixin],
35
20
 
36
- data() {
37
- return {
38
- id: `popover-panel-${genId()}`,
39
- visible: false,
40
- };
41
- },
21
+ inject: ['apiPopover'],
42
22
 
43
23
  computed: {
44
- dark() {
45
- return this.api().isDark;
24
+ id() {
25
+ return `popover-content-${this.apiPopover().id}`;
46
26
  },
47
27
 
48
- headless() {
49
- return this.api().isHeadless;
28
+ component() {
29
+ return this.apiPopover().component;
50
30
  },
51
31
 
52
- right() {
53
- return this.api().isRight;
32
+ componentTrigger() {
33
+ return this.apiPopover().componentTrigger;
54
34
  },
55
35
  },
56
36
 
57
37
  mounted() {
58
- this.api().registerContent(this);
59
-
60
- // TODO: Create a directive or mixin for this
61
- document.addEventListener("click", (e) => {
62
- e.stopPropagation();
63
- if (this.visible && !this.$el.contains(e.target)) this.hide();
64
- });
65
- },
66
-
67
- destroyed() {
68
- // TODO: Create a directive or mixin for this
69
- document.removeEventListener("click", this.hide());
70
- },
71
-
72
- methods: {
73
- show() {
74
- this.visible = true;
75
- },
38
+ const content = {
39
+ id: this.id,
40
+ show: this.show,
41
+ hide: this.hide,
42
+ };
76
43
 
77
- hide() {
78
- this.visible = false;
79
- },
44
+ this.apiPopover().registerContent(content);
80
45
  },
81
46
  };
82
47
  </script>
@@ -3,8 +3,6 @@
3
3
  :class="{
4
4
  PopoverDivider: headless,
5
5
  'h-[1px]': !headless,
6
- 'bg-white': !dark,
7
- 'bg-fd-500': dark,
8
6
  }"
9
7
  ></div>
10
8
  </template>
@@ -13,15 +11,10 @@
13
11
  export default {
14
12
  name: 'VTPopoverDivider',
15
13
 
16
- inject: ['api'],
17
-
18
- computed: {
19
- dark() {
20
- return this.api().isDark;
21
- },
22
-
23
- headless() {
24
- return this.api().isHeadless;
14
+ props: {
15
+ headless: {
16
+ type: Boolean,
17
+ default: false,
25
18
  },
26
19
  },
27
20
  };
@@ -1,13 +1,12 @@
1
1
  <template>
2
2
  <component
3
3
  :is="as"
4
- :class="{
5
- PopoverItem: headless,
6
- '-mx-3 flex min-w-max items-center gap-2 px-3 py-2 no-underline':
7
- !headless,
8
- 'text-fd-500': !dark,
9
- 'text-white hover:bg-fd-450': dark,
10
- }"
4
+ :class="[
5
+ // default styles
6
+ headless
7
+ ? 'popover-item'
8
+ : 'hover:bg-secondary-200/10 relative z-10 -mx-3 flex items-center gap-2 px-3 py-2 text-inherit no-underline',
9
+ ]"
11
10
  @click="onClick"
12
11
  >
13
12
  <slot></slot>
@@ -18,30 +17,38 @@
18
17
  export default {
19
18
  name: 'VTPopoverItem',
20
19
 
21
- inject: ['api'],
20
+ inject: ['apiPopover'],
22
21
 
23
22
  props: {
24
- to: {
25
- type: [String, Object],
26
- default: null,
23
+ headless: {
24
+ type: Boolean,
25
+ default: false,
27
26
  },
28
27
  href: {
29
28
  type: String,
30
29
  default: null,
31
30
  },
31
+ to: {
32
+ type: [String, Object],
33
+ default: null,
34
+ },
32
35
  },
33
36
 
34
37
  computed: {
35
38
  dark() {
36
- return this.api().isDark;
39
+ return this.apiPopover().isDark;
37
40
  },
38
41
 
39
42
  headless() {
40
- return this.api().isHeadless;
43
+ return this.apiPopover().isHeadless;
41
44
  },
42
45
 
43
46
  as() {
44
- return this.href ? 'a' : this.to ? 'NuxtLink' : 'button';
47
+ return this.href
48
+ ? 'a'
49
+ : this.to
50
+ ? resolveComponent('NuxtLink')
51
+ : 'button';
45
52
  },
46
53
  },
47
54
 
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <div @keydown.esc.prevent="onEsc">
2
+ <div :id="id" @keydown.esc.prevent="onKeyEsc">
3
3
  <slot></slot>
4
4
  </div>
5
5
  </template>
@@ -8,43 +8,148 @@
8
8
  export default {
9
9
  name: 'VTPopoverTrigger',
10
10
 
11
- inject: ['api'],
11
+ inject: ['apiPopover'],
12
+
13
+ data() {
14
+ return {
15
+ expanded: false,
16
+ hasPopup: false,
17
+ controls: null,
18
+ trigger: null,
19
+ };
20
+ },
12
21
 
13
22
  computed: {
14
- content() {
15
- return this.api().content;
23
+ id() {
24
+ return `popover-trigger-${this.apiPopover().id}`;
25
+ },
26
+
27
+ componentContent() {
28
+ return this.apiPopover().componentContent;
16
29
  },
17
30
  },
18
31
 
19
32
  mounted() {
20
- this.api().registerButton(this);
21
- this.handleSlotTrigger();
33
+ const trigger = {
34
+ id: this.id,
35
+ el: this.$el,
36
+ cancel: this.cancel,
37
+ focus: this.focus,
38
+ };
39
+
40
+ this.apiPopover().registerTrigger(trigger);
41
+
42
+ this.setTrigger();
43
+ this.addTriggerEvents();
44
+ },
45
+
46
+ destroyed() {
47
+ this.trigger.removeEventListener('click', this.onClick);
22
48
  },
23
49
 
24
50
  methods: {
25
- handleSlotTrigger() {
26
- const slot = this.$slots.default;
27
- const trigger = slot[0].elm;
51
+ setTrigger() {
52
+ this.trigger = this.$el.querySelector(':first-child');
53
+ },
28
54
 
29
- if (slot.length > 1) {
30
- console.error('VTPopoverButton only accepts one item in its slot');
55
+ /**
56
+ * Add event listener to slot element
57
+ *
58
+ * The click event has to be added to the slot child element
59
+ * since we are not setting the onclick on the component
60
+ * itself.
61
+ *
62
+ * Slot must have only one child element. It avoids
63
+ * errors related to adding the event listener.
64
+ */
65
+ addTriggerEvents() {
66
+ this.trigger.addEventListener('click', (e) => {
67
+ this.onClick(e);
68
+ });
69
+ },
70
+
71
+ init(e) {
72
+ if (!this.componentContent) {
31
73
  return;
32
74
  }
33
75
 
34
- trigger.addEventListener('click', (e) => {
35
- this.onClick();
36
- e.stopPropagation();
37
- });
76
+ if (this.expanded) {
77
+ this.cancel();
78
+ return;
79
+ }
80
+
81
+ this.expanded = true;
82
+
83
+ // delay stop propagation to close other visible
84
+ // dropdowns and delay click event to control
85
+ // this dropdown visibility
86
+ setTimeout(() => e.stopImmediatePropagation(), 50);
87
+ setTimeout(() => this.showComponentContent(), 100);
38
88
  },
39
89
 
40
- onClick() {
41
- if (this.content.visible) this.content.hide();
42
- else this.content.show();
90
+ cancel() {
91
+ if (!this.componentContent) {
92
+ return;
93
+ }
94
+
95
+ this.expanded = false;
96
+
97
+ this.hideComponentContent();
98
+ },
99
+
100
+ focus() {
101
+ if (this.trigger) this.trigger.focus();
102
+ },
103
+
104
+ showComponentContent() {
105
+ this.componentContent.show();
106
+ },
107
+
108
+ hideComponentContent() {
109
+ this.componentContent.hide();
110
+ },
111
+
112
+ toggleAriaHasPopup() {
113
+ if (this.expanded) {
114
+ this.hasPopup = this.componentContent !== null;
115
+ this.controls = this.hasPopup ? this.componentContent.id : null;
116
+
117
+ return;
118
+ }
119
+
120
+ this.hasPopup = null;
121
+ this.controls = null;
122
+ },
123
+
124
+ onClick(e) {
125
+ this.init(e);
126
+ },
127
+
128
+ onKeyDownOrUp(e) {
129
+ if (!this.expanded) {
130
+ this.$el.click(e);
131
+ }
132
+
133
+ const keyCode = e.code;
134
+ const listItemPosition =
135
+ keyCode === 'ArrowDown'
136
+ ? 'firstMenuItem'
137
+ : keyCode === 'ArrowUp'
138
+ ? 'lastMenuItem'
139
+ : null;
140
+
141
+ // settimeout here is delaying the focusing the element
142
+ // since it is not rendered yet. All items will only
143
+ // be available when the content is fully visible.
144
+ this.$nextTick(() => {
145
+ setTimeout(() => this[listItemPosition].focus(), 100);
146
+ });
43
147
  },
44
148
 
45
- onEsc() {
46
- if (!this.content) return;
47
- this.content.hide();
149
+ // change it to a better name or move the methods inside to another function
150
+ onKeyEsc() {
151
+ this.cancel();
152
+ this.focus();
48
153
  },
49
154
  },
50
155
  };