@veritree/ui 0.51.0 → 0.52.0

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.
@@ -32,7 +32,7 @@ export const floatingUiItemMixin = {
32
32
  // default styles
33
33
  this.headless
34
34
  ? `${this.componentName}`
35
- : 'relative z-10 flex items-center gap-2 px-3 py-2 text-inherit no-underline',
35
+ : 'relative z-10 flex items-center gap-2 px-3 py-2 text-inherit no-underline cursor-pointer',
36
36
  // disabled state styles
37
37
  this.headless
38
38
  ? this.disabled
@@ -44,7 +44,9 @@ export const floatingUiItemMixin = {
44
44
  // selected state styles
45
45
  this.headless
46
46
  ? this.selected
47
- ? `${this.componentName}--selected`
47
+ ? this.multiple
48
+ ? null
49
+ : `${this.componentName}--selected`
48
50
  : null
49
51
  : this.selected
50
52
  ? 'bg-gray-200'
@@ -217,6 +219,20 @@ export const floatingUiItemMixin = {
217
219
  return this.items.findIndex((item) => item.isSelected());
218
220
  },
219
221
 
222
+ handleToogle() {
223
+ const checkbox = this.$refs[`${this.id}-checkbox`];
224
+
225
+ if (checkbox) {
226
+ checkbox.click();
227
+ }
228
+ },
229
+
230
+ handleClick() {
231
+ this.value !== undefined
232
+ ? this.apiInjected().emit(this.value)
233
+ : this.$emit('click');
234
+ },
235
+
220
236
  leaveMenu() {
221
237
  if (this.componentTrigger) {
222
238
  this.componentTrigger.cancel();
@@ -225,15 +241,12 @@ export const floatingUiItemMixin = {
225
241
  },
226
242
 
227
243
  onClick() {
228
- if (this.disabled) {
244
+ if (this?.disabled) {
229
245
  return;
230
246
  }
231
247
 
232
- this.value !== undefined
233
- ? this.apiInjected().emit(this.value)
234
- : this.$emit('click');
235
-
236
- this.$nextTick(() => this.leaveMenu());
248
+ this?.multiple ? this.handleToogle() : this.handleClick();
249
+ this?.multiple ? null : this.$nextTick(() => this.leaveMenu());
237
250
  },
238
251
 
239
252
  onKeyEsc() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veritree/ui",
3
- "version": "0.51.0",
3
+ "version": "0.52.0",
4
4
  "description": "veritree ui library",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -18,7 +18,7 @@
18
18
  "dependencies": {
19
19
  "@floating-ui/dom": "^1.2.0",
20
20
  "@linusborg/vue-simple-portal": "^0.1.5",
21
- "@veritree/icons": "^0.43.0"
21
+ "@veritree/icons": "^0.49.0"
22
22
  },
23
23
  "devDependencies": {
24
24
  "np": "^8.0.4",
@@ -16,17 +16,6 @@ export default {
16
16
  provide() {
17
17
  return {
18
18
  apiListbox: () => {
19
- const $mutable = {};
20
-
21
- // Used to get and update the value computed
22
- // that will then emit the value up to the
23
- // parent component
24
- Object.defineProperty($mutable, 'valueComputed', {
25
- enumerable: true,
26
- get: () => this.valueComputed,
27
- set: (value) => (this.valueComputed = value),
28
- });
29
-
30
19
  /**
31
20
  * This function registers a trigger by setting its value to the componentTrigger property of the current object.
32
21
  * @param {VueComponent} trigger - The trigger to be registered.
@@ -78,6 +67,27 @@ export default {
78
67
  this.valueComputed = value;
79
68
  };
80
69
 
70
+ /**
71
+ * pushValueToValueComputed handles pushing new selected values
72
+ * to an array that will replace the valueComputed. It is
73
+ * only used when the prop multiple is set to true.
74
+ */
75
+ const pushValueToValueComputed = (value) => {
76
+ const newArr = Array.isArray(value) ? value : [value];
77
+ this.valueComputed = [...this.valueComputed, ...newArr];
78
+ };
79
+
80
+ /**
81
+ * removeValueFromValueComputed handles removing aselected value
82
+ * from the valueComputed array by just filtering it out. It
83
+ * is also only used when the prop multiple is true.
84
+ */
85
+ const removeValueFromValueComputed = (value) => {
86
+ this.valueComputed = this.valueComputed.filter(
87
+ (valueComputed) => valueComputed !== value
88
+ );
89
+ };
90
+
81
91
  return {
82
92
  id: this.componentId,
83
93
  component: this.component,
@@ -87,7 +97,7 @@ export default {
87
97
  items: this.items,
88
98
  multiple: this.multiple,
89
99
  search: this.search,
90
- $mutable,
100
+ valueComputed: this.valueComputed,
91
101
  registerTrigger,
92
102
  registerContent,
93
103
  registerSearch,
@@ -95,6 +105,8 @@ export default {
95
105
  unregisterItem,
96
106
  pushSearch,
97
107
  clearSearch,
108
+ pushValueToValueComputed,
109
+ removeValueFromValueComputed,
98
110
  emit,
99
111
  };
100
112
  },
@@ -105,11 +117,11 @@ export default {
105
117
  /**
106
118
  * The value of the component. Can be a string, number, object, or array.
107
119
  * @type {string|number|object|array}
108
- * @default null
120
+ * @default []
109
121
  */
110
122
  value: {
111
123
  type: [String, Number, Object, Array],
112
- default: null,
124
+ default: () => [],
113
125
  },
114
126
  /**
115
127
  * Determines whether the button will use its default atomic style (tailwind) or its default class
@@ -16,20 +16,25 @@
16
16
  @keydown.enter.prevent="onClick"
17
17
  @keydown.tab.prevent
18
18
  >
19
- <span v-if="apiListbox().multiple">
19
+ <template v-if="this.multiple">
20
20
  <input
21
- v-model="apiListbox().$mutable.valueComputed"
22
21
  :id="`${id}-checkbox`"
22
+ :ref="`${id}-checkbox`"
23
23
  :value="value"
24
+ :checked="checked"
25
+ class="form-control-check"
24
26
  type="checkbox"
25
27
  @click.stop
26
- @change="onChange"
28
+ @change="onChange(value, $event)"
27
29
  />
28
- </span>
29
- <slot></slot>
30
- <span v-if="wasSelected" class="ml-auto">
31
- <IconCheckOutline class="text-secondary-300 h-5 w-5" />
32
- </span>
30
+ <slot></slot>
31
+ </template>
32
+ <template v-else>
33
+ <slot></slot>
34
+ <span v-if="checked" class="ml-auto">
35
+ <IconCheckOutline class="text-secondary-300 h-5 w-5" />
36
+ </span>
37
+ </template>
33
38
  </li>
34
39
  </template>
35
40
 
@@ -76,22 +81,30 @@ export default {
76
81
  return this.apiListbox().search;
77
82
  },
78
83
 
79
- /**
80
- * The difference between selected (in the mixin) and
81
- * wasSelected is that selected is more related to
82
- * the aria-selected while wasSelected is the
83
- * real selected option for the listbox
84
- */
85
- wasSelected() {
84
+ multiple() {
85
+ return this.apiListbox().multiple;
86
+ },
87
+
88
+ checked() {
89
+ if (this.multiple) {
90
+ return this.apiListbox().valueComputed.some(
91
+ (value) => JSON.stringify(this.value) === JSON.stringify(value)
92
+ );
93
+ }
94
+
86
95
  return (
87
96
  JSON.stringify(this.value) ===
88
- JSON.stringify(this.apiListbox().$mutable.valueComputed)
97
+ JSON.stringify(this.apiListbox().valueComputed)
89
98
  );
90
99
  },
91
100
  },
92
101
 
93
102
  mounted() {
94
- if (this.wasSelected) {
103
+ if (this.multiple) {
104
+ return;
105
+ }
106
+
107
+ if (this.checked) {
95
108
  /**
96
109
  * There are some conflicts between the portal and the interaction
97
110
  * with the element. It's unclear why it happens but if no delay,
@@ -170,6 +183,12 @@ export default {
170
183
  this.apiListbox().clearSearch();
171
184
  }, 1000);
172
185
  },
186
+
187
+ onChange(value, event) {
188
+ event.target.checked
189
+ ? this.apiListbox().pushValueToValueComputed(value)
190
+ : this.apiListbox().removeValueFromValueComputed(value);
191
+ },
173
192
  },
174
193
  };
175
194
  </script>
@@ -15,7 +15,9 @@
15
15
  <slot></slot>
16
16
  </span>
17
17
  <span
18
- :class="[headless ? 'listbox-button__icon' : 'shrink-0 text-gray-500']"
18
+ :class="[
19
+ headless ? 'listbox-button__icon' : 'ml-2 shrink-0 text-gray-500',
20
+ ]"
19
21
  >
20
22
  <IconChevronDown
21
23
  class="transition-transform"