@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
@@ -0,0 +1,165 @@
1
+ <template>
2
+ <div>
3
+ <input
4
+ ref="input"
5
+ v-bind="$attrs"
6
+ type="number"
7
+ :class="classComputed"
8
+ :value="value"
9
+ :disabled="disabled"
10
+ @blur="onBlur"
11
+ @change="onChange"
12
+ @keypress="isNumber"
13
+ />
14
+ <div v-if="!hideArrows">
15
+ <VTButton
16
+ variant="secondary"
17
+ :disabled="qty >= max"
18
+ tabindex="-1"
19
+ @click="increase"
20
+ >
21
+ <IconChevronUp text="Increase" />
22
+ </VTButton>
23
+ <VTButton
24
+ variant="secondary"
25
+ :disabled="qty <= min"
26
+ tabindex="-1"
27
+ @click="decrease"
28
+ >
29
+ <IconChevronDown text="Increase" />
30
+ </VTButton>
31
+ </div>
32
+ </div>
33
+ </template>
34
+
35
+ <script>
36
+ import { formControlMixin } from '../../../mixins/form-control';
37
+
38
+ export default {
39
+ mixins: [formControlMixin],
40
+
41
+ inheritAttrs: false,
42
+
43
+ model: {
44
+ prop: 'value',
45
+ event: 'input',
46
+ },
47
+
48
+ props: {
49
+ hideArrows: {
50
+ type: Boolean,
51
+ default: false,
52
+ },
53
+ value: {
54
+ type: [String, Number],
55
+ default: 0,
56
+ },
57
+ step: {
58
+ type: [Number],
59
+ default: 1,
60
+ },
61
+ allowDecimal: {
62
+ type: Boolean,
63
+ default: true,
64
+ },
65
+ },
66
+
67
+ data() {
68
+ return {
69
+ decimalPlaces: this.setDecimalPlaces(this.value),
70
+ // id: 'qty-' + this._uid,
71
+ };
72
+ },
73
+
74
+ computed: {
75
+ numberAllowedKeys() {
76
+ const allowed = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
77
+ if (this.allowDecimal) allowed.push('.');
78
+ return allowed;
79
+ },
80
+
81
+ qty: {
82
+ get() {
83
+ return this.value;
84
+ },
85
+ set(newVal) {
86
+ this.$emit('input', Number(newVal));
87
+ this.$emit('change');
88
+ },
89
+ },
90
+
91
+ min() {
92
+ return this.$attrs.min ? this.$attrs.min : 0;
93
+ },
94
+
95
+ max() {
96
+ return this.$attrs.max ? this.$attrs.max : Number.MAX_VALUE;
97
+ },
98
+
99
+ internalStep() {
100
+ if (this.step && this.step > 0) return this.step;
101
+ if (!this.decimalPlaces) return 1;
102
+
103
+ const decimalStep = '1'.padStart(this.decimalPlaces, '0');
104
+ return parseFloat('0.' + decimalStep);
105
+ },
106
+ },
107
+
108
+ methods: {
109
+ increase() {
110
+ this.qty = (
111
+ isNaN(parseFloat(this.qty))
112
+ ? 0
113
+ : parseFloat(this.qty) + this.internalStep
114
+ ).toFixed(this.decimalPlaces);
115
+ },
116
+
117
+ decrease() {
118
+ this.qty = (
119
+ isNaN(parseFloat(this.qty))
120
+ ? 0
121
+ : parseFloat(this.qty) - this.internalStep
122
+ ).toFixed(this.decimalPlaces);
123
+ },
124
+
125
+ /**
126
+ * Checks if key pressed is a number (0-9)
127
+ * TODO: combine validation with input price
128
+ *
129
+ * @param {object} - event
130
+ */
131
+ isNumber(ev) {
132
+ const key = ev.key;
133
+
134
+ if (!this.numberAllowedKeys.includes(key)) ev.preventDefault();
135
+ },
136
+
137
+ canUpdateQty(value) {
138
+ return value >= this.min && value <= this.max;
139
+ },
140
+
141
+ setDecimalPlaces(typedValue) {
142
+ this.decimalPlaces = typedValue.toString().includes('.')
143
+ ? typedValue.toString().split('.')[1].length
144
+ : 0;
145
+ },
146
+
147
+ onChange(event) {
148
+ // const input = event.target;
149
+ //
150
+ // if (this.canUpdateQty(input.value)) {
151
+ // this.setDecimalPlaces(input.value);
152
+ // this.qty = input.value;
153
+ // return;
154
+ // }
155
+ //
156
+ // if (input.value < this.min) input.value = this.min;
157
+ // if (input.value > this.max) input.value = this.max;
158
+ },
159
+
160
+ onBlur(event) {
161
+ this.$emit('blur', event);
162
+ },
163
+ },
164
+ };
165
+ </script>
@@ -0,0 +1,22 @@
1
+ <template>
2
+ <textarea
3
+ :class="classComputed"
4
+ :value="value"
5
+ :disabled="disabled"
6
+ v-on="listeners"
7
+ />
8
+ </template>
9
+
10
+ <script>
11
+ import { formControlMixin } from '../../../mixins/form-control';
12
+
13
+ export default {
14
+ mixins: [formControlMixin],
15
+
16
+ data() {
17
+ return {
18
+ name: 'textarea',
19
+ };
20
+ },
21
+ };
22
+ </script>
@@ -15,9 +15,7 @@ export default {
15
15
 
16
16
  provide() {
17
17
  return {
18
- api: () => {
19
- const { dark: isDark } = this;
20
-
18
+ apiListbox: () => {
21
19
  const registerTrigger = (trigger) => {
22
20
  if (!trigger) return;
23
21
  this.componentTrigger = trigger;
@@ -33,10 +31,10 @@ export default {
33
31
  this.search = search;
34
32
  };
35
33
 
36
- const registerList = (list) => {
37
- if (!list) return;
38
- this.list = list;
39
- };
34
+ // const registerList = (list) => {
35
+ // if (!list) return;
36
+ // this.list = list;
37
+ // };
40
38
 
41
39
  const registerItem = (item) => {
42
40
  if (!item) return;
@@ -55,17 +53,15 @@ export default {
55
53
 
56
54
  return {
57
55
  id: this.componentId,
58
- isDark,
59
56
  component: this.component,
60
57
  componentTrigger: this.componentTrigger,
61
58
  componentContent: this.componentContent,
62
- list: this.list,
63
59
  items: this.items,
64
60
  search: this.search,
65
61
  registerTrigger,
66
62
  registerContent,
67
63
  registerSearch,
68
- registerList,
64
+ // registerList,
69
65
  registerItem,
70
66
  unregisterItem,
71
67
  emit,
@@ -97,7 +93,6 @@ export default {
97
93
  return {
98
94
  componentId: genId(),
99
95
  search: null,
100
- list: null,
101
96
  items: [],
102
97
  };
103
98
  },
@@ -5,6 +5,7 @@
5
5
  :aria-activedescendant="activeDescedant"
6
6
  :headless="headless"
7
7
  :class="{ 'listbox-content': headless }"
8
+ :floating-ui-class="floatingUiClass"
8
9
  role="listbox"
9
10
  >
10
11
  <slot></slot>
@@ -12,54 +13,32 @@
12
13
  </template>
13
14
 
14
15
  <script>
15
- import FloatingUi from '../Utils/FloatingUi.vue';
16
+ import { floatingUiContentMixin } from '../../../mixins/floating-ui-content';
16
17
 
17
18
  export default {
18
19
  name: 'VTListboxContent',
19
20
 
20
- components: {
21
- FloatingUi,
22
- },
23
-
24
- inject: ['api'],
21
+ mixins: [floatingUiContentMixin],
25
22
 
26
- props: {
27
- headless: {
28
- type: Boolean,
29
- default: false,
30
- },
31
- bottom: {
32
- type: Boolean,
33
- default: false,
34
- },
35
- top: {
36
- type: Boolean,
37
- default: true,
38
- },
39
- },
23
+ inject: ['apiListbox'],
40
24
 
41
25
  data() {
42
26
  return {
43
27
  activeDescedant: null,
44
- visible: false,
45
28
  };
46
29
  },
47
30
 
48
31
  computed: {
49
32
  id() {
50
- return `listbox-content-${this.api().id}`;
33
+ return `listbox-content-${this.apiListbox().id}`;
51
34
  },
52
35
 
53
36
  component() {
54
- return this.api().component;
37
+ return this.apiListbox().component;
55
38
  },
56
39
 
57
40
  componentTrigger() {
58
- return this.api().componentTrigger;
59
- },
60
-
61
- search() {
62
- return this.api().search;
41
+ return this.apiListbox().componentTrigger;
63
42
  },
64
43
  },
65
44
 
@@ -68,57 +47,13 @@ export default {
68
47
  id: this.id,
69
48
  show: this.show,
70
49
  hide: this.hide,
50
+ getMousemove: this.getMousemove,
51
+ setMousemove: this.setMousemove,
52
+ unsetMousemove: this.unsetMousemove,
71
53
  setActiveDescedant: this.setActiveDescedant,
72
54
  };
73
55
 
74
- this.api().registerContent(content);
75
-
76
- // T-107 Create a directive or mixin for this
77
- document.addEventListener('click', (e) => {
78
- if (!e) {
79
- return;
80
- }
81
-
82
- e.stopPropagation();
83
-
84
- if (this.visible && !this.$el.contains(e.target)) {
85
- this.componentTrigger.onClick();
86
- }
87
- });
88
- },
89
-
90
- destroyed() {
91
- // T-162 Create a directive or mixin for this
92
- document.removeEventListener('click', this.componentTrigger.onClick);
93
- },
94
-
95
- methods: {
96
- show() {
97
- if (this.visible) return;
98
-
99
- this.visible = true;
100
-
101
- this.$nextTick(() => {
102
- this.component.setActive();
103
- if (this.search) this.search.el.focus();
104
- });
105
- },
106
-
107
- hide() {
108
- if (!this.visible) return;
109
-
110
- this.visible = false;
111
-
112
- this.$nextTick(() => {
113
- this.componentTrigger.focus();
114
- this.componentTrigger.toggleExpanded();
115
- this.component.clearActive();
116
- });
117
- },
118
-
119
- setActiveDescedant(id) {
120
- this.activeDescedant = id;
121
- },
56
+ this.apiListbox().registerContent(content);
122
57
  },
123
58
  };
124
59
  </script>
@@ -1,14 +1,7 @@
1
1
  <template>
2
2
  <li
3
3
  :id="id"
4
- :class="{
5
- ListboxItem: headless,
6
- 'relative z-10 flex items-center gap-2 px-3 py-2 no-underline': !headless,
7
- 'hover:bg-secondary-200/10': !dark && !headless,
8
- 'text-white': dark && !headless,
9
- 'pointer-events-none opacity-75': disabled && !headless,
10
- 'bg-secondary-200/10': selected && !headless,
11
- }"
4
+ :class="classComputed"
12
5
  :aria-disabled="disabled"
13
6
  :aria-selected="String(selected)"
14
7
  :tabindex="tabIndex"
@@ -24,28 +17,21 @@
24
17
  @mouseout="onMouseleave"
25
18
  @keydown.tab.prevent
26
19
  >
27
- <span class="truncate"><slot></slot></span>
20
+ <slot></slot>
28
21
  </li>
29
22
  </template>
30
23
 
31
24
  <script>
32
- import { scrollElementIntoView } from '../../utils/components';
33
- import { genId } from '../../utils/ids';
25
+ import { floatingUiItemMixin } from '../../../mixins/floating-ui-item';
34
26
 
35
27
  export default {
36
28
  name: 'VTListboxItem',
37
29
 
38
- inject: ['api'],
30
+ mixins: [floatingUiItemMixin],
31
+
32
+ inject: ['apiListbox'],
39
33
 
40
34
  props: {
41
- headless: {
42
- type: Boolean,
43
- default: false,
44
- },
45
- disabled: {
46
- type: Boolean,
47
- default: false,
48
- },
49
35
  value: {
50
36
  type: [String, Number, Object, Array],
51
37
  required: true,
@@ -54,172 +40,14 @@ export default {
54
40
 
55
41
  data() {
56
42
  return {
57
- id: `listboxitem-${genId()}`,
58
- index: null,
59
- selected: false,
60
- tabIndex: 0,
43
+ apiInjected: this.apiListbox,
44
+ componentName: 'listbox-item',
61
45
  };
62
46
  },
63
47
 
64
48
  computed: {
65
- dark() {
66
- return this.api().isDark;
67
- },
68
-
69
- items() {
70
- return this.api().items;
71
- },
72
-
73
- el() {
74
- return this.$el;
75
- },
76
-
77
- componentTrigger() {
78
- return this.api().componentTrigger;
79
- },
80
-
81
- componentContent() {
82
- return this.api().componentContent;
83
- },
84
-
85
- list() {
86
- return this.api().list;
87
- },
88
-
89
49
  search() {
90
- return this.api().search;
91
- },
92
- },
93
-
94
- watch: {
95
- selected(newValue) {
96
- if (!newValue || !this.list) return;
97
-
98
- if (this.componentContent) {
99
- this.componentContent.setActiveDescedant(this.id);
100
- }
101
-
102
- const isMousemove = this.list.getMousemove();
103
-
104
- if (!isMousemove) {
105
- scrollElementIntoView(this.el, this.list.el);
106
- }
107
- },
108
- },
109
-
110
- mounted() {
111
- const item = {
112
- id: this.id,
113
- focus: this.focus,
114
- select: this.select,
115
- unselect: this.unselect,
116
- onClick: this.onClick,
117
- };
118
-
119
- this.api().registerItem(item);
120
-
121
- this.index = this.items.length - 1;
122
- },
123
-
124
- beforeDestroy() {
125
- this.api().unregisterItem(this.id);
126
- },
127
-
128
- methods: {
129
- select() {
130
- this.selected = true;
131
- },
132
-
133
- unselect() {
134
- this.selected = false;
135
- },
136
-
137
- focus() {
138
- if (!this.el) return;
139
-
140
- this.tabIndex = -1;
141
- this.selected = true;
142
- this.el.focus();
143
- },
144
-
145
- focusFirstItem() {
146
- this.setFocusToItem(0);
147
- },
148
-
149
- focusLastItem() {
150
- this.setFocusToItem(this.items.length - 1);
151
- },
152
-
153
- /**
154
- * Focus the previous item in the menu.
155
- * If is the first item, jump to the last item.
156
- */
157
- focusPreviousItem() {
158
- const isLast = this.index === this.items.length - 1;
159
- const goToIndex = isLast ? 0 : this.index + 1;
160
-
161
- this.setFocusToItem(goToIndex);
162
- },
163
-
164
- /**
165
- * Focus the next item in the menu.
166
- * If is the last item, jump to the first item.
167
- */
168
- focusNextItem() {
169
- const isFirst = this.index === 0;
170
- const goToIndex = isFirst ? this.items.length - 1 : this.index - 1;
171
-
172
- this.setFocusToItem(goToIndex);
173
- },
174
-
175
- /**
176
- * Focus item by remove its tabindex and calling
177
- * focus to the element.
178
- *
179
- * @param {Number, String} goToIndex
180
- */
181
- setFocusToItem(goToIndex) {
182
- this.tabIndex = 0;
183
- this.selected = false;
184
-
185
- const isMousemove = this.list.getMousemove();
186
-
187
- if (isMousemove) {
188
- this.list.unsetMousemove();
189
- this.items.forEach((item) => item.unselect());
190
- }
191
-
192
- this.items[goToIndex].focus();
193
- },
194
-
195
- /**
196
- * Hides componentContent/menu and focus on componentTrigger
197
- */
198
- leaveMenu() {
199
- if (this.componentContent) this.componentContent.hide();
200
- },
201
-
202
- onKeyEsc() {
203
- this.leaveMenu();
204
- },
205
-
206
- onClick() {
207
- if (this.disabled) return;
208
-
209
- this.api().emit(this.value);
210
- this.$nextTick(() => this.leaveMenu());
211
- },
212
-
213
- onMousemove() {
214
- this.items.forEach((item) => item.unselect());
215
-
216
- this.select();
217
- this.list.setMousemove();
218
- },
219
-
220
- onMouseleave() {
221
- this.unselect();
222
- this.list.unsetMousemove();
50
+ return this.apiInjected().search;
223
51
  },
224
52
  },
225
53
  };
@@ -4,8 +4,6 @@
4
4
  :class="{
5
5
  ListboxLabel: headless,
6
6
  'mb-2 block text-xs font-normal uppercase': !headless,
7
- 'text-inherit': !dark && !headless,
8
- 'text-white': dark && !headless,
9
7
  }"
10
8
  >
11
9
  <slot></slot>
@@ -16,8 +14,6 @@
16
14
  export default {
17
15
  name: 'VTListboxLabel',
18
16
 
19
- inject: ['api'],
20
-
21
17
  props: {
22
18
  as: {
23
19
  type: String,
@@ -28,11 +24,5 @@ export default {
28
24
  default: false,
29
25
  },
30
26
  },
31
-
32
- computed: {
33
- dark() {
34
- return this.api().isDark;
35
- },
36
- },
37
27
  };
38
28
  </script>
@@ -1,22 +1,19 @@
1
1
  <template>
2
2
  <ul
3
3
  :id="id"
4
- :class="{
5
- ListboxList: headless,
6
- '-mx-3 max-h-[160px] w-auto overflow-y-auto': !headless,
7
- }"
4
+ :class="[
5
+ headless ? 'listbox-list' : 'max-h-[160px] w-auto overflow-y-auto -mx-3',
6
+ ]"
8
7
  >
9
8
  <slot></slot>
10
9
  </ul>
11
10
  </template>
12
11
 
13
12
  <script>
14
- import { genId } from "../../utils/ids";
15
-
16
13
  export default {
17
- name: "VTListboxList",
14
+ name: 'VTListboxList',
18
15
 
19
- inject: ["api"],
16
+ inject: ['apiListbox'],
20
17
 
21
18
  props: {
22
19
  headless: {
@@ -25,39 +22,33 @@ export default {
25
22
  },
26
23
  },
27
24
 
28
- data() {
29
- return {
30
- id: `listboxlist-${genId()}`,
31
- isMousemove: false,
32
- };
25
+ computed: {
26
+ id() {
27
+ return `listbox-list-${this.apiListbox().id}`;
28
+ },
33
29
  },
34
30
 
35
- mounted() {
36
- const list = {
37
- el: this.$el,
38
- getMousemove: this.getMousemove,
39
- setMousemove: this.setMousemove,
40
- unsetMousemove: this.unsetMousemove,
41
- };
31
+ // mounted() {
32
+ // const list = {
33
+ // el: this.$el,
34
+ // };
42
35
 
43
- this.api().registerList(list);
44
- },
36
+ // this.apiListbox().registerList(list);
37
+ // },
45
38
 
46
39
  methods: {
47
40
  // Mousemove instead of mouseover to support keyboard navigation.
48
41
  // The problem with mouseover is that when scrolling (scrollIntoView),
49
42
  // mouseover event gets triggered.
50
- setMousemove() {
51
- this.isMousemove = true;
52
- },
53
-
54
- unsetMousemove() {
55
- this.isMousemove = false;
56
- },
57
-
58
- getMousemove() {
59
- return this.isMousemove;
60
- },
43
+ // setMousemove() {
44
+ // this.isMousemove = true;
45
+ // },
46
+ // unsetMousemove() {
47
+ // this.isMousemove = false;
48
+ // },
49
+ // getMousemove() {
50
+ // return this.isMousemove;
51
+ // },
61
52
  },
62
53
  };
63
54
  </script>