@veritree/ui 0.21.1-7 → 0.21.1-9

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.
@@ -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>
@@ -31,10 +31,10 @@ export default {
31
31
  this.search = search;
32
32
  };
33
33
 
34
- const registerList = (list) => {
35
- if (!list) return;
36
- this.list = list;
37
- };
34
+ // const registerList = (list) => {
35
+ // if (!list) return;
36
+ // this.list = list;
37
+ // };
38
38
 
39
39
  const registerItem = (item) => {
40
40
  if (!item) return;
@@ -56,13 +56,12 @@ export default {
56
56
  component: this.component,
57
57
  componentTrigger: this.componentTrigger,
58
58
  componentContent: this.componentContent,
59
- list: this.list,
60
59
  items: this.items,
61
60
  search: this.search,
62
61
  registerTrigger,
63
62
  registerContent,
64
63
  registerSearch,
65
- registerList,
64
+ // registerList,
66
65
  registerItem,
67
66
  unregisterItem,
68
67
  emit,
@@ -94,7 +93,6 @@ export default {
94
93
  return {
95
94
  componentId: genId(),
96
95
  search: null,
97
- list: null,
98
96
  items: [],
99
97
  };
100
98
  },
@@ -47,16 +47,13 @@ export default {
47
47
  id: this.id,
48
48
  show: this.show,
49
49
  hide: this.hide,
50
+ getMousemove: this.getMousemove,
51
+ setMousemove: this.setMousemove,
52
+ unsetMousemove: this.unsetMousemove,
50
53
  setActiveDescedant: this.setActiveDescedant,
51
54
  };
52
55
 
53
56
  this.apiListbox().registerContent(content);
54
57
  },
55
-
56
- methods: {
57
- setActiveDescedant(id) {
58
- this.activeDescedant = id;
59
- },
60
- },
61
58
  };
62
59
  </script>
@@ -1,28 +1,7 @@
1
1
  <template>
2
2
  <li
3
3
  :id="id"
4
- :class="[
5
- // default styles
6
- headless
7
- ? 'listbox-item'
8
- : 'relative z-10 -mx-3 flex items-center gap-2 px-3 py-2 text-inherit no-underline hover:bg-secondary-200/10',
9
- // disabled state styles
10
- headless
11
- ? disabled
12
- ? 'listbox-item--disabled'
13
- : null
14
- : disabled
15
- ? 'pointer-events-none opacity-75'
16
- : null,
17
- // selected state styles
18
- headless
19
- ? selected
20
- ? 'lisbox-item--selected'
21
- : null
22
- : selected
23
- ? 'bg-secondary-200/10'
24
- : null,
25
- ]"
4
+ :class="classComputed"
26
5
  :aria-disabled="disabled"
27
6
  :aria-selected="String(selected)"
28
7
  :tabindex="tabIndex"
@@ -38,28 +17,21 @@
38
17
  @mouseout="onMouseleave"
39
18
  @keydown.tab.prevent
40
19
  >
41
- <span class="truncate"><slot></slot></span>
20
+ <slot></slot>
42
21
  </li>
43
22
  </template>
44
23
 
45
24
  <script>
46
- import { scrollElementIntoView } from '../../utils/components';
47
- import { genId } from '../../utils/ids';
25
+ import { floatingUiItemMixin } from '../../../mixins/floating-ui-item';
48
26
 
49
27
  export default {
50
28
  name: 'VTListboxItem',
51
29
 
30
+ mixins: [floatingUiItemMixin],
31
+
52
32
  inject: ['apiListbox'],
53
33
 
54
34
  props: {
55
- headless: {
56
- type: Boolean,
57
- default: false,
58
- },
59
- disabled: {
60
- type: Boolean,
61
- default: false,
62
- },
63
35
  value: {
64
36
  type: [String, Number, Object, Array],
65
37
  required: true,
@@ -68,171 +40,14 @@ export default {
68
40
 
69
41
  data() {
70
42
  return {
71
- id: `listboxitem-${genId()}`,
72
- index: null,
73
- selected: false,
74
- tabIndex: 0,
43
+ apiInjected: this.apiListbox,
44
+ componentName: 'listbox-item',
75
45
  };
76
46
  },
77
47
 
78
48
  computed: {
79
- items() {
80
- return this.apiListbox().items;
81
- },
82
-
83
- el() {
84
- return this.$el;
85
- },
86
-
87
- componentTrigger() {
88
- return this.apiListbox().componentTrigger;
89
- },
90
-
91
- componentContent() {
92
- return this.apiListbox().componentContent;
93
- },
94
-
95
- list() {
96
- return this.apiListbox().list;
97
- },
98
-
99
49
  search() {
100
- return this.apiListbox().search;
101
- },
102
- },
103
-
104
- watch: {
105
- selected(newValue) {
106
- if (!newValue || !this.list) return;
107
-
108
- if (this.componentContent) {
109
- this.componentContent.setActiveDescedant(this.id);
110
- }
111
-
112
- const isMousemove = this.list.getMousemove();
113
-
114
- if (!isMousemove) {
115
- scrollElementIntoView(this.el, this.list.el);
116
- }
117
- },
118
- },
119
-
120
- mounted() {
121
- const item = {
122
- id: this.id,
123
- focus: this.focus,
124
- select: this.select,
125
- unselect: this.unselect,
126
- onClick: this.onClick,
127
- };
128
-
129
- this.apiListbox().registerItem(item);
130
-
131
- this.index = this.items.length - 1;
132
- },
133
-
134
- beforeDestroy() {
135
- this.apiListbox().unregisterItem(this.id);
136
- },
137
-
138
- methods: {
139
- select() {
140
- this.selected = true;
141
- },
142
-
143
- unselect() {
144
- this.selected = false;
145
- },
146
-
147
- focus() {
148
- if (!this.el) return;
149
-
150
- this.tabIndex = -1;
151
- this.selected = true;
152
- this.el.focus();
153
- },
154
-
155
- focusFirstItem() {
156
- this.setFocusToItem(0);
157
- },
158
-
159
- focusLastItem() {
160
- this.setFocusToItem(this.items.length - 1);
161
- },
162
-
163
- /**
164
- * Focus the previous item in the menu.
165
- * If is the first item, jump to the last item.
166
- */
167
- focusPreviousItem() {
168
- const isLast = this.index === this.items.length - 1;
169
- const goToIndex = isLast ? 0 : this.index + 1;
170
-
171
- this.setFocusToItem(goToIndex);
172
- },
173
-
174
- /**
175
- * Focus the next item in the menu.
176
- * If is the last item, jump to the first item.
177
- */
178
- focusNextItem() {
179
- const isFirst = this.index === 0;
180
- const goToIndex = isFirst ? this.items.length - 1 : this.index - 1;
181
-
182
- this.setFocusToItem(goToIndex);
183
- },
184
-
185
- /**
186
- * Focus item by remove its tabindex and calling
187
- * focus to the element.
188
- *
189
- * @param {Number, String} goToIndex
190
- */
191
- setFocusToItem(goToIndex) {
192
- this.tabIndex = 0;
193
- this.selected = false;
194
-
195
- const isMousemove = this.list.getMousemove();
196
-
197
- if (isMousemove) {
198
- this.list.unsetMousemove();
199
- this.items.forEach((item) => item.unselect());
200
- }
201
-
202
- this.items[goToIndex].focus();
203
- },
204
-
205
- /**
206
- * Hides componentContent/menu and focus on componentTrigger
207
- */
208
- leaveMenu() {
209
- if (this.componentTrigger) {
210
- this.componentTrigger.cancel();
211
- this.componentTrigger.focus();
212
- }
213
- },
214
-
215
- onKeyEsc() {
216
- this.leaveMenu();
217
- },
218
-
219
- onClick() {
220
- if (this.disabled) return;
221
-
222
- this.apiListbox().emit(this.value);
223
- this.$nextTick(() => this.leaveMenu());
224
- },
225
-
226
- onMousemove() {
227
- this.items.forEach((item) => item.unselect());
228
-
229
- this.select();
230
- this.list.setMousemove();
231
- },
232
-
233
- onMouseleave() {
234
- this.unselect();
235
- this.list.unsetMousemove();
50
+ return this.apiInjected().search;
236
51
  },
237
52
  },
238
53
  };
@@ -1,5 +1,10 @@
1
1
  <template>
2
- <ul :id="id" :class="[headless ? 'listbox-list' : 'max-h-[160px] w-auto']">
2
+ <ul
3
+ :id="id"
4
+ :class="[
5
+ headless ? 'listbox-list' : 'max-h-[160px] w-auto overflow-y-auto -mx-3',
6
+ ]"
7
+ >
3
8
  <slot></slot>
4
9
  </ul>
5
10
  </template>
@@ -17,44 +22,33 @@ export default {
17
22
  },
18
23
  },
19
24
 
20
- data() {
21
- return {
22
- isMousemove: false,
23
- };
24
- },
25
-
26
25
  computed: {
27
26
  id() {
28
27
  return `listbox-list-${this.apiListbox().id}`;
29
28
  },
30
29
  },
31
30
 
32
- mounted() {
33
- const list = {
34
- el: this.$el,
35
- getMousemove: this.getMousemove,
36
- setMousemove: this.setMousemove,
37
- unsetMousemove: this.unsetMousemove,
38
- };
31
+ // mounted() {
32
+ // const list = {
33
+ // el: this.$el,
34
+ // };
39
35
 
40
- this.apiListbox().registerList(list);
41
- },
36
+ // this.apiListbox().registerList(list);
37
+ // },
42
38
 
43
39
  methods: {
44
40
  // Mousemove instead of mouseover to support keyboard navigation.
45
41
  // The problem with mouseover is that when scrolling (scrollIntoView),
46
42
  // mouseover event gets triggered.
47
- setMousemove() {
48
- this.isMousemove = true;
49
- },
50
-
51
- unsetMousemove() {
52
- this.isMousemove = false;
53
- },
54
-
55
- getMousemove() {
56
- return this.isMousemove;
57
- },
43
+ // setMousemove() {
44
+ // this.isMousemove = true;
45
+ // },
46
+ // unsetMousemove() {
47
+ // this.isMousemove = false;
48
+ // },
49
+ // getMousemove() {
50
+ // return this.isMousemove;
51
+ // },
58
52
  },
59
53
  };
60
54
  </script>
@@ -1,8 +1,8 @@
1
1
  <template>
2
2
  <input
3
3
  v-model="search"
4
- :class="{ 'listbox-search': headless, 'form-control mb-1': !headless }"
5
4
  type="text"
5
+ :class="classComputed"
6
6
  @input="onChange"
7
7
  @click.stop
8
8
  @keydown.down.prevent="focusNextItem"
@@ -16,22 +16,20 @@
16
16
  </template>
17
17
 
18
18
  <script>
19
+ import { formControlMixin } from '../../../mixins/form-control';
20
+
19
21
  export default {
20
22
  name: 'VTListboxSearch',
21
23
 
22
- inject: ['apiListbox'],
24
+ mixins: [formControlMixin],
23
25
 
24
- props: {
25
- headless: {
26
- type: Boolean,
27
- default: false,
28
- },
29
- },
26
+ inject: ['apiListbox'],
30
27
 
31
28
  data() {
32
29
  return {
33
- search: '',
30
+ name: 'listbox-search',
34
31
  index: -1,
32
+ search: '',
35
33
  };
36
34
  },
37
35
 
@@ -40,8 +38,8 @@ export default {
40
38
  return this.apiListbox().componentTrigger;
41
39
  },
42
40
 
43
- list() {
44
- return this.apiListbox().list;
41
+ componentContent() {
42
+ return this.apiListbox().componentContent;
45
43
  },
46
44
 
47
45
  items() {
@@ -107,10 +105,10 @@ export default {
107
105
  },
108
106
 
109
107
  unselectItem() {
110
- const isMousemove = this.list.getMousemove();
108
+ const isMousemove = this.componentContent.getMousemove();
111
109
 
112
110
  if (isMousemove) {
113
- this.list.unsetMousemove();
111
+ this.componentContent.unsetMousemove();
114
112
  this.items.forEach((item) => item.unselect());
115
113
  }
116
114
 
@@ -1,37 +1,19 @@
1
1
  <template>
2
2
  <button
3
3
  :id="id"
4
+ :class="classComputed"
4
5
  :aria-expanded="expanded"
5
6
  :aria-haspopup="hasPopup"
6
- :class="[
7
- headless
8
- ? 'listbox-button'
9
- : 'flex w-full justify-between border border-solid py-2 px-3 gap-3 rounded text-inherit max-w-full',
10
- headless
11
- ? `listbox-button--${variant}`
12
- : isError
13
- ? 'border-error-300'
14
- : 'border-gray-300',
15
- ]"
16
7
  type="button"
17
8
  @click.prevent="onClick"
18
9
  @keydown.down.prevent="onKeyDownOrUp"
19
10
  @keydown.up.prevent="onKeyDownOrUp"
20
11
  @keydown.esc.stop="onKeyEsc"
21
12
  >
22
- <span
23
- :class="{
24
- 'listbox-button__text': headless,
25
- 'text-left': !headless,
26
- }"
27
- >
13
+ <span :class="[headless ? 'listbox-button__text' : 'text-left truncate']">
28
14
  <slot></slot>
29
15
  </span>
30
- <span
31
- :class="{
32
- 'listbox-button__icon': headless,
33
- }"
34
- >
16
+ <span :class="[headless ? 'listbox-button__icon' : 'shrink-0']">
35
17
  <IconChevronDown
36
18
  class="transition-transform"
37
19
  :class="{ 'rotate-180': expanded }"
@@ -41,6 +23,7 @@
41
23
  </template>
42
24
 
43
25
  <script>
26
+ import { formControlMixin } from '../../../mixins/form-control';
44
27
  import { IconChevronDown } from '@veritree/icons';
45
28
 
46
29
  export default {
@@ -48,25 +31,13 @@ export default {
48
31
 
49
32
  components: { IconChevronDown },
50
33
 
51
- inject: ['apiListbox'],
34
+ mixins: [formControlMixin],
52
35
 
53
- props: {
54
- disabled: {
55
- type: Boolean,
56
- default: false,
57
- },
58
- headless: {
59
- type: Boolean,
60
- default: false,
61
- },
62
- variant: {
63
- type: [String, Object, Function],
64
- default: '',
65
- },
66
- },
36
+ inject: ['apiListbox'],
67
37
 
68
38
  data() {
69
39
  return {
40
+ name: 'listbox-button',
70
41
  expanded: false,
71
42
  hasPopup: false,
72
43
  };
@@ -77,10 +48,6 @@ export default {
77
48
  return `listbox-trigger-${this.apiListbox().id}`;
78
49
  },
79
50
 
80
- isError() {
81
- return this.variant === 'error';
82
- },
83
-
84
51
  componentContent() {
85
52
  return this.apiListbox().componentContent;
86
53
  },
@@ -15,7 +15,7 @@
15
15
  :class="[
16
16
  headless
17
17
  ? null
18
- : 'absolute z-50 grid min-w-min overflow-hidden rounded-md py-2 px-3 border-gray-100 bg-white shadow-300',
18
+ : 'absolute z-50 grid overflow-x-hidden rounded-md py-2 px-3 border-gray-100 bg-white shadow-300',
19
19
  floatingUiClass ? floatingUiClass : null,
20
20
  ]"
21
21
  >