@veritree/ui 0.21.1-0 → 0.21.1-10

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 (29) hide show
  1. package/mixins/floating-ui-content.js +102 -0
  2. package/mixins/floating-ui-item.js +216 -0
  3. package/mixins/floating-ui.js +14 -6
  4. package/mixins/form-control.js +72 -0
  5. package/package.json +2 -2
  6. package/src/components/Avatar/VTAvatar.vue +32 -29
  7. package/src/components/DropdownMenu/VTDropdownMenu.vue +5 -16
  8. package/src/components/DropdownMenu/VTDropdownMenuContent.vue +17 -65
  9. package/src/components/DropdownMenu/VTDropdownMenuDivider.vue +8 -5
  10. package/src/components/DropdownMenu/VTDropdownMenuItem.vue +9 -143
  11. package/src/components/DropdownMenu/VTDropdownMenuLabel.vue +3 -3
  12. package/src/components/DropdownMenu/VTDropdownMenuTrigger.vue +67 -103
  13. package/src/components/Form/VTInput.vue +28 -40
  14. package/src/components/Form/VTInputQty.vue +165 -0
  15. package/src/components/Form/VTTextarea.vue +22 -0
  16. package/src/components/Listbox/VTListbox.vue +6 -11
  17. package/src/components/Listbox/VTListboxContent.vue +11 -76
  18. package/src/components/Listbox/VTListboxItem.vue +9 -181
  19. package/src/components/Listbox/VTListboxLabel.vue +0 -10
  20. package/src/components/Listbox/VTListboxList.vue +24 -33
  21. package/src/components/Listbox/VTListboxSearch.vue +21 -18
  22. package/src/components/Listbox/VTListboxTrigger.vue +58 -97
  23. package/src/components/Popover/VTPopover.vue +1 -14
  24. package/src/components/Popover/VTPopoverContent.vue +14 -65
  25. package/src/components/Popover/VTPopoverDivider.vue +4 -11
  26. package/src/components/Popover/VTPopoverItem.vue +16 -13
  27. package/src/components/Popover/VTPopoverTrigger.vue +118 -21
  28. package/src/components/Utils/FloatingUi.vue +6 -1
  29. package/src/utils/components.js +0 -18
@@ -3,8 +3,6 @@
3
3
  :class="{
4
4
  PopoverDivider: headless,
5
5
  '-mx-3 my-2 h-[1px]': !headless,
6
- 'bg-gray-200': !dark,
7
- 'bg-fd-500': dark,
8
6
  }"
9
7
  ></div>
10
8
  </template>
@@ -13,15 +11,20 @@
13
11
  export default {
14
12
  name: 'VTDropdownMenuDivider',
15
13
 
16
- inject: ['api'],
14
+ inject: ['apiDropdownMenu'],
15
+
16
+ headless: {
17
+ type: Boolean,
18
+ default: false,
19
+ },
17
20
 
18
21
  computed: {
19
22
  dark() {
20
- return this.api().isDark;
23
+ return this.apiDropdownMenu().isDark;
21
24
  },
22
25
 
23
26
  headless() {
24
- return this.api().isHeadless;
27
+ return this.apiDropdownMenu().isHeadless;
25
28
  },
26
29
  },
27
30
  };
@@ -3,17 +3,10 @@
3
3
  :is="as"
4
4
  :id="id"
5
5
  :to="to"
6
- :class="{
7
- MenuItem: headless,
8
- '-mx-3 flex min-w-max items-center gap-2 px-3 py-2 text-inherit no-underline':
9
- !headless,
10
- 'hover:bg-secondary-200/10': !dark && !headless,
11
- 'text-white': dark && !headless,
12
- 'pointer-events-none opacity-75': disabled && !headless,
13
- 'bg-secondary-200/10': selected && !headless,
14
- }"
15
- :tabindex="tabIndex"
6
+ :class="classComputed"
16
7
  :aria-disabled="disabled"
8
+ :tabindex="tabIndex"
9
+ class="-mx-3"
17
10
  role="menuitem"
18
11
  @click.stop.prevent="onClick"
19
12
  @keydown.down.prevent="focusPreviousItem"
@@ -29,12 +22,14 @@
29
22
  </template>
30
23
 
31
24
  <script>
32
- import { genId } from '../../utils/ids';
25
+ import { floatingUiItemMixin } from '../../../mixins/floating-ui-item';
33
26
 
34
27
  export default {
35
28
  name: 'VTDropdownMenuItem',
36
29
 
37
- inject: ['api'],
30
+ mixins: [floatingUiItemMixin],
31
+
32
+ inject: ['apiDropdownMenu'],
38
33
 
39
34
  props: {
40
35
  to: {
@@ -45,148 +40,19 @@ export default {
45
40
  type: String,
46
41
  default: null,
47
42
  },
48
- disabled: {
49
- type: Boolean,
50
- default: false,
51
- },
52
43
  },
53
44
 
54
45
  data() {
55
46
  return {
56
- index: null,
57
- selected: false,
58
- tabIndex: 0,
47
+ apiInjected: this.apiDropdownMenu,
48
+ componentName: 'dropdown-menu-item',
59
49
  };
60
50
  },
61
51
 
62
52
  computed: {
63
- id() {
64
- return `dropdown-menu-item-${this.api().id}-${genId()}`;
65
- },
66
-
67
- dark() {
68
- return this.api().isDark;
69
- },
70
-
71
- headless() {
72
- return this.api().isHeadless;
73
- },
74
-
75
53
  as() {
76
54
  return this.href ? 'a' : this.to ? 'NuxtLink' : 'button';
77
55
  },
78
-
79
- items() {
80
- return this.api().items;
81
- },
82
-
83
- el() {
84
- return this.$el;
85
- },
86
-
87
- componentTrigger() {
88
- return this.api().componentTrigger;
89
- },
90
-
91
- componentContent() {
92
- return this.api().componentContent;
93
- },
94
- },
95
-
96
- mounted() {
97
- const item = {
98
- select: this.select,
99
- unselect: this.unselect,
100
- focus: this.focus,
101
- };
102
-
103
- this.api().registerItem(item);
104
-
105
- this.index = this.items.length - 1;
106
- },
107
-
108
- methods: {
109
- select() {
110
- this.selected = true;
111
- },
112
-
113
- unselect() {
114
- this.selected = false;
115
- },
116
-
117
- focus() {
118
- if (!this.el) return;
119
-
120
- this.tabIndex = -1;
121
- this.selected = true;
122
- this.el.focus();
123
- },
124
-
125
- focusFirstItem() {
126
- this.setFocusToItem(0);
127
- },
128
-
129
- focusLastItem() {
130
- this.setFocusToItem(this.items.length - 1);
131
- },
132
-
133
- /**
134
- * Focus the previous item in the menu.
135
- * If is the first item, jump to the last item.
136
- */
137
- focusPreviousItem() {
138
- const isLast = this.index === this.items.length - 1;
139
- const goToIndex = isLast ? 0 : this.index + 1;
140
-
141
- this.setFocusToItem(goToIndex);
142
- },
143
-
144
- /**
145
- * Focus the next item in the menu.
146
- * If is the last item, jump to the first item.
147
- */
148
- focusNextItem() {
149
- const isFirst = this.index === 0;
150
- const goToIndex = isFirst ? this.items.length - 1 : this.index - 1;
151
-
152
- this.setFocusToItem(goToIndex);
153
- },
154
-
155
- /**
156
- * Focus item by remove its tabindex and calling
157
- * focus to the element.
158
- *
159
- * @param {Number, String} goToIndex
160
- */
161
- setFocusToItem(goToIndex) {
162
- this.tabIndex = 0;
163
- this.selected = false;
164
-
165
- // set all selected to false
166
- this.items.forEach((item) => item.unselect());
167
-
168
- // focus item
169
- this.items[goToIndex].focus();
170
- },
171
-
172
- /**
173
- * Hides content/menu and focus on trigger
174
- */
175
- leaveMenu() {
176
- if (this.componentContent) this.componentContent.hide();
177
- if (this.componentTrigger) this.componentTrigger.focus();
178
- },
179
-
180
- onKeyEsc() {
181
- this.leaveMenu();
182
- },
183
-
184
- onClick() {
185
- if (this.disabled) return;
186
-
187
- this.$emit('click');
188
- this.$nextTick(() => this.leaveMenu());
189
- },
190
56
  },
191
57
  };
192
58
  </script>
@@ -15,15 +15,15 @@
15
15
  export default {
16
16
  name: 'VTDropdownMenuLabel',
17
17
 
18
- inject: ['api'],
18
+ inject: ['apiDropdownMenu'],
19
19
 
20
20
  computed: {
21
21
  dark() {
22
- return this.api().isDark;
22
+ return this.apiDropdownMenu().isDark;
23
23
  },
24
24
 
25
25
  headless() {
26
- return this.api().isHeadless;
26
+ return this.apiDropdownMenu().isHeadless;
27
27
  },
28
28
  },
29
29
  };
@@ -4,9 +4,9 @@
4
4
  :aria-haspopup="hasPopup"
5
5
  :aria-expanded="expanded"
6
6
  :aria-controls="controls"
7
- @keydown.down.prevent="onKeyArrowDown"
8
- @keydown.up.prevent="onKeyArrowUp"
9
- @keydown.esc.stop="onKeyesc"
7
+ @keydown.down.prevent="onKeyDownOrUp"
8
+ @keydown.up.prevent="onKeyDownOrUp"
9
+ @keydown.esc.stop="onKeyEsc"
10
10
  >
11
11
  <slot></slot>
12
12
  </div>
@@ -16,7 +16,7 @@
16
16
  export default {
17
17
  name: 'VTDropdownMenuTrigger',
18
18
 
19
- inject: ['api'],
19
+ inject: ['apiDropdownMenu'],
20
20
 
21
21
  data() {
22
22
  return {
@@ -29,15 +29,15 @@ export default {
29
29
 
30
30
  computed: {
31
31
  id() {
32
- return `dropdown-menu-trigger-${this.api().id}`;
32
+ return `dropdown-menu-trigger-${this.apiDropdownMenu().id}`;
33
33
  },
34
34
 
35
35
  componentContent() {
36
- return this.api().componentContent;
36
+ return this.apiDropdownMenu().componentContent;
37
37
  },
38
38
 
39
39
  items() {
40
- return this.api().items;
40
+ return this.apiDropdownMenu().items;
41
41
  },
42
42
 
43
43
  firstMenuItem() {
@@ -49,17 +49,21 @@ export default {
49
49
  },
50
50
  },
51
51
 
52
+ watch: {
53
+ expanded() {
54
+ this.toggleAriaHasPopup();
55
+ },
56
+ },
57
+
52
58
  mounted() {
53
59
  const trigger = {
54
- toggleExpanded: this.toggleExpanded,
55
- hideExpanded: this.hideExpanded,
60
+ id: this.id,
56
61
  el: this.$el,
62
+ cancel: this.cancel,
57
63
  focus: this.focus,
58
- id: this.id,
59
- onClick: this.onClick,
60
64
  };
61
65
 
62
- this.api().registerTrigger(trigger);
66
+ this.apiDropdownMenu().registerTrigger(trigger);
63
67
 
64
68
  this.setTrigger();
65
69
  this.addTriggerEvents();
@@ -90,128 +94,88 @@ export default {
90
94
  });
91
95
  },
92
96
 
93
- /**
94
- * Shows content/menu if not already visible
95
- */
96
- showContent() {
97
- this.expanded = true;
98
- this.componentContent.show();
99
- },
97
+ init(e) {
98
+ if (!this.componentContent) {
99
+ return;
100
+ }
100
101
 
101
- /**
102
- * Focus slot element if it exists and toggle expanded
103
- */
104
- focus() {
105
- if (this.trigger) this.trigger.focus();
106
- },
102
+ if (this.expanded) {
103
+ this.cancel();
104
+ return;
105
+ }
107
106
 
108
- //
109
- toggleExpanded() {
110
- if (!this.expanded) return;
111
- this.expanded = false;
112
- },
107
+ this.expanded = true;
113
108
 
114
- /**
115
- * Sets aria expanded attribute/state to false
116
- */
117
- hideExpanded() {
118
- this.expanded = false;
109
+ // delay stop propagation to close other visible
110
+ // dropdowns and delay click event to control
111
+ // this dropdown visibility
112
+ setTimeout(() => e.stopImmediatePropagation(), 50);
113
+ setTimeout(() => this.showComponentContent(), 100);
119
114
  },
120
115
 
121
- /**
122
- * Toggles aria popup/controls attribute/state
123
- */
124
- toggleHasPopup() {
125
- if (!this.componentContent) return;
126
-
127
- this.hasPopup = !this.hasPopup;
128
-
129
- if (!this.hasPopup) {
130
- this.controls = null;
116
+ cancel() {
117
+ if (!this.componentContent) {
131
118
  return;
132
119
  }
133
120
 
134
- this.controls = this.componentContent.id;
121
+ this.expanded = false;
122
+
123
+ this.hideComponentContent();
135
124
  },
136
125
 
137
- /**
138
- * 1. Set aria expanded attribute/state to false
139
- * 2. Close the menu
140
- */
141
- hide() {
142
- this.hideExpanded();
126
+ focus() {
127
+ if (this.trigger) this.trigger.focus();
128
+ },
143
129
 
144
- this.$nextTick(() => {
145
- this.componentContent.hide();
146
- });
130
+ showComponentContent() {
131
+ this.componentContent.show();
147
132
  },
148
133
 
149
- /**
150
- * On click, do the following:
151
- *
152
- * 1. Toggle aria expanded attribute/state
153
- * 2. Open the menu if it's closed
154
- * 3. Close the menu if it's open
155
- */
156
- onClick(e) {
157
- if (!this.componentContent) return;
134
+ hideComponentContent() {
135
+ this.componentContent.hide();
136
+ },
158
137
 
138
+ toggleAriaHasPopup() {
159
139
  if (this.expanded) {
160
- this.componentContent.hide();
140
+ this.hasPopup = this.componentContent !== null;
141
+ this.controls = this.hasPopup ? this.componentContent.id : null;
142
+
161
143
  return;
162
144
  }
163
145
 
164
- // delay stop propagation to close other visible
165
- // dropdowns and delay click event to control
166
- // this dropdown visibility
167
- setTimeout(() => e.stopImmediatePropagation(), 50);
168
- setTimeout(() => this.showContent(), 100);
146
+ this.hasPopup = null;
147
+ this.controls = null;
169
148
  },
170
149
 
171
- /**
172
- * On key arrow down, do the following:
173
- *
174
- * 1. if the menu is not expanded, expand it and focus the first menu item
175
- * 2. if the menu is expanded, focus the first menu item
176
- */
177
- onKeyArrowDown() {
178
- if (!this.componentContent) return;
179
-
180
- this.showContent();
181
-
182
- // settimeout here is delaying the focusing the element
183
- // since it is not rendered yet. All items will only
184
- // be available when the content is fully visible.
185
- this.$nextTick(() => {
186
- setTimeout(() => this.firstMenuItem.focus(), 150);
187
- });
150
+ onClick(e) {
151
+ this.init(e);
188
152
  },
189
153
 
190
- /**
191
- * On key arrow up, do the following:
192
- *
193
- * 1. if the menu is not expanded, expand it and focus the last menu item
194
- * 2. if the menu is expanded, focus the last menu item
195
- */
196
- onKeyArrowUp() {
197
- if (!this.componentContent) return;
154
+ onKeyDownOrUp(e) {
155
+ if (!this.expanded) {
156
+ this.$el.click(e);
157
+ }
198
158
 
199
- this.showContent();
159
+ const keyCode = e.code;
160
+ const listItemPosition =
161
+ keyCode === 'ArrowDown'
162
+ ? 'firstMenuItem'
163
+ : keyCode === 'ArrowUp'
164
+ ? 'lastMenuItem'
165
+ : null;
200
166
 
201
167
  // settimeout here is delaying the focusing the element
202
168
  // since it is not rendered yet. All items will only
203
169
  // be available when the content is fully visible.
204
170
  this.$nextTick(() => {
205
- setTimeout(() => this.lastMenuItem.focus(), 150);
171
+ setTimeout(() => this[listItemPosition].focus(), 100);
206
172
  });
207
173
  },
208
174
 
209
- onKeyesc() {
210
- if (!this.componentContent) return;
211
-
212
- if (this.expanded) {
213
- this.componentContent.hide();
214
- }
175
+ // change it to a better name or move the methods inside to another function
176
+ onKeyEsc() {
177
+ this.cancel();
178
+ this.focus();
215
179
  },
216
180
  },
217
181
  };
@@ -1,52 +1,40 @@
1
1
  <template>
2
2
  <input
3
- v-bind="$attrs"
4
- :class="[
5
- headless
6
- ? 'form-control'
7
- : 'border border-solid py-2 px-3 rounded text-inherit',
8
- headless
9
- ? `form-control--${variant}`
10
- : isError
11
- ? 'border-error-300'
12
- : 'border-gray-300',
13
- ]"
3
+ :class="classComputed"
14
4
  :value="value"
15
- @input="$emit('input', $event.target.value)"
16
- @blur="$emit('blur')"
5
+ :disabled="disabled"
6
+ v-on="listeners"
17
7
  />
18
8
  </template>
19
9
 
20
10
  <script>
21
- export default {
22
- model: {
23
- prop: 'value',
24
- event: 'input',
25
- },
11
+ import { formControlMixin } from '../../../mixins/form-control';
26
12
 
27
- props: {
28
- disabled: {
29
- type: Boolean,
30
- default: false,
31
- },
32
- value: {
33
- type: [String, Number, Object, Array],
34
- default: null,
35
- },
36
- variant: {
37
- type: [String, Object, Function],
38
- default: '',
39
- },
40
- headless: {
41
- type: Boolean,
42
- default: false,
43
- },
44
- },
13
+ export default {
14
+ mixins: [formControlMixin],
45
15
 
46
- computed: {
47
- isError() {
48
- return this.variant === 'error';
49
- },
16
+ data() {
17
+ return {
18
+ name: 'input',
19
+ };
50
20
  },
51
21
  };
52
22
  </script>
23
+
24
+ <style scoped>
25
+ input[type='date']::-webkit-inner-spin-button,
26
+ input[type='date']::-webkit-calendar-picker-indicator {
27
+ position: absolute;
28
+ opacity: 0;
29
+ }
30
+
31
+ input[type='number'] {
32
+ appearance: textfield;
33
+ }
34
+
35
+ input[type='number']::-webkit-inner-spin-button,
36
+ input[type='number']::-webkit-outer-spin-button {
37
+ appearance: none;
38
+ -webkit-appearance: none;
39
+ }
40
+ </style>