vueless 1.3.7-beta.2 → 1.3.7-beta.3
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.
- package/components.d.ts +1 -0
- package/components.ts +1 -0
- package/constants.d.ts +2 -0
- package/constants.js +2 -0
- package/package.json +2 -2
- package/ui.container-col/UCol.vue +0 -1
- package/ui.container-collapsible/UCollapsible.vue +190 -0
- package/ui.container-collapsible/config.ts +45 -0
- package/ui.container-collapsible/constants.ts +1 -0
- package/ui.container-collapsible/storybook/docs.mdx +17 -0
- package/ui.container-collapsible/storybook/stories.ts +261 -0
- package/ui.container-collapsible/tests/UCollapsible.test.ts +571 -0
- package/ui.container-collapsible/types.ts +57 -0
- package/ui.dropdown/UDropdown.vue +324 -0
- package/ui.dropdown/config.ts +27 -0
- package/ui.dropdown/constants.ts +1 -0
- package/ui.dropdown/storybook/docs.mdx +17 -0
- package/ui.dropdown/storybook/stories.ts +286 -0
- package/ui.dropdown/tests/UDropdown.test.ts +631 -0
- package/ui.dropdown/types.ts +127 -0
- package/ui.dropdown-badge/UDropdownBadge.vue +119 -227
- package/ui.dropdown-badge/config.ts +18 -15
- package/ui.dropdown-badge/tests/UDropdownBadge.test.ts +201 -67
- package/ui.dropdown-button/UDropdownButton.vue +121 -226
- package/ui.dropdown-button/config.ts +32 -28
- package/ui.dropdown-button/tests/UDropdownButton.test.ts +189 -73
- package/ui.dropdown-link/UDropdownLink.vue +123 -233
- package/ui.dropdown-link/config.ts +15 -18
- package/ui.dropdown-link/tests/UDropdownLink.test.ts +190 -71
- package/ui.form-listbox/UListbox.vue +2 -3
- package/ui.form-listbox/config.ts +2 -2
- package/ui.form-select/config.ts +1 -1
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
import { isEqual } from "lodash-es";
|
|
2
|
+
import { computed, useTemplateRef } from "vue";
|
|
4
3
|
|
|
5
4
|
import { useUI } from "../composables/useUI";
|
|
6
5
|
import { getDefaults } from "../utils/ui";
|
|
7
6
|
|
|
8
7
|
import UIcon from "../ui.image-icon/UIcon.vue";
|
|
9
8
|
import ULink from "../ui.button-link/ULink.vue";
|
|
10
|
-
import
|
|
11
|
-
|
|
12
|
-
import vClickOutside from "../v.click-outside/vClickOutside";
|
|
9
|
+
import UDropdown from "../ui.dropdown/UDropdown.vue";
|
|
13
10
|
|
|
14
11
|
import { COMPONENT_NAME } from "./constants";
|
|
15
12
|
import defaultConfig from "./config";
|
|
16
13
|
|
|
17
14
|
import type { Props, Config } from "./types";
|
|
18
|
-
import type { Option, SelectedValue } from "../ui.form-listbox/types";
|
|
19
15
|
|
|
20
16
|
defineOptions({ inheritAttrs: false });
|
|
21
17
|
|
|
@@ -63,66 +59,9 @@ const emit = defineEmits([
|
|
|
63
59
|
"update:search",
|
|
64
60
|
]);
|
|
65
61
|
|
|
66
|
-
type
|
|
67
|
-
|
|
68
|
-
const isShownOptions = ref(false);
|
|
69
|
-
const isClickingOption = ref(false);
|
|
70
|
-
const listboxRef = useTemplateRef<ULisboxRef>("dropdown-list");
|
|
71
|
-
const wrapperRef = useTemplateRef<HTMLDivElement>("wrapper");
|
|
72
|
-
|
|
73
|
-
const elementId = props.id || useId();
|
|
74
|
-
|
|
75
|
-
const dropdownValue = computed({
|
|
76
|
-
get: () => {
|
|
77
|
-
if (props.multiple && !Array.isArray(props.modelValue)) {
|
|
78
|
-
return props.modelValue ? [props.modelValue] : [];
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return props.modelValue;
|
|
82
|
-
},
|
|
83
|
-
set: (value) => emit("update:modelValue", value),
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
const dropdownSearch = computed({
|
|
87
|
-
get: () => props.search ?? "",
|
|
88
|
-
set: (value: string) => emit("update:search", value),
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
const selectedOptions = computed(() => {
|
|
92
|
-
if (props.multiple) {
|
|
93
|
-
return props.options.filter((option) => {
|
|
94
|
-
return (
|
|
95
|
-
option[props.valueKey] &&
|
|
96
|
-
(dropdownValue.value as SelectedValue[]).find((selected) =>
|
|
97
|
-
isEqual(selected, option[props.valueKey]),
|
|
98
|
-
)
|
|
99
|
-
);
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return [
|
|
104
|
-
props.options.find(
|
|
105
|
-
(option) => option[props.valueKey] && isEqual(option[props.valueKey], dropdownValue.value),
|
|
106
|
-
),
|
|
107
|
-
].filter((option) => !!option);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
const linkLabel = computed(() => {
|
|
111
|
-
if (!props.labelDisplayCount || !selectedOptions.value.length) {
|
|
112
|
-
return props.label;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const selectedLabels = selectedOptions.value
|
|
116
|
-
.slice(0, props.labelDisplayCount)
|
|
117
|
-
.map((option) => option[props.labelKey]);
|
|
118
|
-
const restLabelCount = selectedOptions.value.length - props.labelDisplayCount;
|
|
62
|
+
type UDropdownRef = InstanceType<typeof UDropdown>;
|
|
119
63
|
|
|
120
|
-
|
|
121
|
-
selectedLabels.push(`+${restLabelCount}`);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return selectedLabels.join(", ");
|
|
125
|
-
});
|
|
64
|
+
const dropdownRef = useTemplateRef<UDropdownRef>("dropdown");
|
|
126
65
|
|
|
127
66
|
const toggleIconName = computed(() => {
|
|
128
67
|
if (typeof props.toggleIcon === "string") {
|
|
@@ -132,197 +71,148 @@ const toggleIconName = computed(() => {
|
|
|
132
71
|
return props.toggleIcon ? config.value.defaults.toggleIcon : "";
|
|
133
72
|
});
|
|
134
73
|
|
|
135
|
-
function
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (Array.isArray(value)) {
|
|
139
|
-
return value.map((item) => item[labelKey]).join(", ");
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return "";
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function onSearchChange(query: string) {
|
|
146
|
-
emit("searchChange", query);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function onClickLink() {
|
|
150
|
-
if (props.disabled) return;
|
|
151
|
-
|
|
152
|
-
isShownOptions.value = !isShownOptions.value;
|
|
153
|
-
|
|
154
|
-
if (isShownOptions.value) {
|
|
155
|
-
nextTick(() => listboxRef.value?.wrapperRef?.focus());
|
|
156
|
-
|
|
157
|
-
emit("open");
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function hideOptions() {
|
|
162
|
-
isShownOptions.value = false;
|
|
163
|
-
dropdownSearch.value = "";
|
|
164
|
-
|
|
165
|
-
emit("close");
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function onClickOption(option: Option) {
|
|
169
|
-
isClickingOption.value = true;
|
|
170
|
-
|
|
171
|
-
emit("clickOption", option);
|
|
172
|
-
|
|
173
|
-
if (!props.multiple && props.closeOnSelect) hideOptions();
|
|
174
|
-
|
|
175
|
-
nextTick(() => {
|
|
176
|
-
setTimeout(() => {
|
|
177
|
-
isClickingOption.value = false;
|
|
178
|
-
}, 10);
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function handleClickOutside() {
|
|
183
|
-
if (isClickingOption.value) return;
|
|
184
|
-
|
|
185
|
-
hideOptions();
|
|
74
|
+
function hide() {
|
|
75
|
+
dropdownRef.value?.hide();
|
|
186
76
|
}
|
|
187
77
|
|
|
188
78
|
defineExpose({
|
|
189
79
|
/**
|
|
190
|
-
* A reference to the
|
|
80
|
+
* A reference to the dropdown wrapper element for direct DOM manipulation.
|
|
191
81
|
* @property {HTMLDivElement}
|
|
192
82
|
*/
|
|
193
|
-
wrapperRef,
|
|
83
|
+
wrapperRef: computed(() => dropdownRef.value?.wrapperRef),
|
|
194
84
|
|
|
195
85
|
/**
|
|
196
|
-
* Hides the dropdown
|
|
86
|
+
* Hides the dropdown.
|
|
197
87
|
* @property {function}
|
|
198
88
|
*/
|
|
199
|
-
|
|
89
|
+
hide,
|
|
200
90
|
});
|
|
201
91
|
|
|
202
|
-
|
|
203
|
-
* Get element / nested component attributes for each config token ✨
|
|
92
|
+
/*
|
|
93
|
+
* Vueless: Get element / nested component attributes for each config token ✨
|
|
204
94
|
* Applies: `class`, `config`, redefined default `props` and dev `vl-...` attributes.
|
|
205
95
|
*/
|
|
206
96
|
const mutatedProps = computed(() => ({
|
|
207
97
|
/* component state, not a props */
|
|
208
|
-
opened:
|
|
98
|
+
opened: dropdownRef.value?.isOpened ?? false,
|
|
209
99
|
}));
|
|
210
100
|
|
|
211
|
-
const { config, getDataTest,
|
|
212
|
-
|
|
101
|
+
const { config, getDataTest, dropdownLinkAttrs, toggleLinkAttrs, toggleIconAttrs } = useUI<Config>(
|
|
102
|
+
defaultConfig,
|
|
103
|
+
mutatedProps,
|
|
104
|
+
"toggleLink",
|
|
105
|
+
);
|
|
213
106
|
</script>
|
|
214
107
|
|
|
215
108
|
<template>
|
|
216
|
-
<
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
:
|
|
222
|
-
|
|
223
|
-
|
|
109
|
+
<UDropdown
|
|
110
|
+
:id="id"
|
|
111
|
+
ref="dropdown"
|
|
112
|
+
:model-value="modelValue"
|
|
113
|
+
:label="label"
|
|
114
|
+
:label-display-count="labelDisplayCount"
|
|
115
|
+
:search="search"
|
|
116
|
+
:y-position="yPosition"
|
|
117
|
+
:x-position="xPosition"
|
|
118
|
+
:disabled="disabled"
|
|
119
|
+
:options="options"
|
|
120
|
+
:options-limit="optionsLimit"
|
|
121
|
+
:visible-options="visibleOptions"
|
|
122
|
+
:label-key="labelKey"
|
|
123
|
+
:value-key="valueKey"
|
|
124
|
+
:group-label-key="groupLabelKey"
|
|
125
|
+
:group-value-key="groupValueKey"
|
|
126
|
+
:searchable="searchable"
|
|
127
|
+
:multiple="multiple"
|
|
128
|
+
:color="color"
|
|
129
|
+
:size="size"
|
|
130
|
+
:close-on-select="closeOnSelect"
|
|
131
|
+
v-bind="dropdownLinkAttrs"
|
|
132
|
+
:data-test="dataTest"
|
|
133
|
+
@click-option="(option) => emit('clickOption', option)"
|
|
134
|
+
@update:model-value="(value) => emit('update:modelValue', value)"
|
|
135
|
+
@update:search="(value) => emit('update:search', value)"
|
|
136
|
+
@search-change="(query) => emit('searchChange', query)"
|
|
137
|
+
@open="emit('open')"
|
|
138
|
+
@close="emit('close')"
|
|
224
139
|
>
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
:label="linkLabel"
|
|
236
|
-
:color="color"
|
|
237
|
-
:dashed="dashed"
|
|
238
|
-
:disabled="disabled"
|
|
239
|
-
:underlined="underlined"
|
|
240
|
-
:title="getFullOptionLabels(selectedOptions)"
|
|
241
|
-
v-bind="dropdownLinkAttrs"
|
|
242
|
-
:data-test="getDataTest()"
|
|
243
|
-
@click="onClickLink"
|
|
244
|
-
>
|
|
245
|
-
<template #default>
|
|
246
|
-
<!--
|
|
247
|
-
@slot Use it to add something instead of the default label.
|
|
248
|
-
@binding {string} label
|
|
249
|
-
@binding {boolean} opened
|
|
250
|
-
-->
|
|
251
|
-
<slot :label="linkLabel" :opened="isShownOptions" />
|
|
252
|
-
</template>
|
|
253
|
-
</ULink>
|
|
254
|
-
|
|
255
|
-
<!--
|
|
256
|
-
@slot Use it to add something instead of the toggle icon.
|
|
257
|
-
@binding {boolean} opened
|
|
258
|
-
@binding {function} toggle
|
|
259
|
-
-->
|
|
260
|
-
<slot name="toggle" :opened="isShownOptions" :toggle="onClickLink">
|
|
261
|
-
<UIcon
|
|
262
|
-
v-if="toggleIconName"
|
|
263
|
-
interactive
|
|
140
|
+
<template #default="{ opened, displayLabel, fullLabel }">
|
|
141
|
+
<!--
|
|
142
|
+
@slot Use it to add something before the label.
|
|
143
|
+
@binding {boolean} opened
|
|
144
|
+
-->
|
|
145
|
+
<slot name="left" :opened="opened" />
|
|
146
|
+
|
|
147
|
+
<ULink
|
|
148
|
+
:size="size"
|
|
149
|
+
:label="displayLabel"
|
|
264
150
|
:color="color"
|
|
151
|
+
:dashed="dashed"
|
|
265
152
|
:disabled="disabled"
|
|
266
|
-
:
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
v-model:search="dropdownSearch"
|
|
278
|
-
:searchable="searchable"
|
|
279
|
-
:multiple="multiple"
|
|
280
|
-
:size="size"
|
|
281
|
-
:color="color"
|
|
282
|
-
:options="options"
|
|
283
|
-
:options-limit="optionsLimit"
|
|
284
|
-
:visible-options="visibleOptions"
|
|
285
|
-
:label-key="labelKey"
|
|
286
|
-
:value-key="valueKey"
|
|
287
|
-
:group-label-key="groupLabelKey"
|
|
288
|
-
:group-value-key="groupValueKey"
|
|
289
|
-
v-bind="listboxAttrs"
|
|
290
|
-
:data-test="getDataTest('list')"
|
|
291
|
-
@click-option="onClickOption"
|
|
292
|
-
@search-change="onSearchChange"
|
|
293
|
-
@update:search="(value) => emit('update:search', value)"
|
|
294
|
-
>
|
|
295
|
-
<template #before-option="{ option, index }">
|
|
296
|
-
<!--
|
|
297
|
-
@slot Use it to add something before option.
|
|
298
|
-
@binding {object} option
|
|
299
|
-
@binding {number} index
|
|
300
|
-
-->
|
|
301
|
-
<slot name="before-option" :option="option" :index="index" />
|
|
302
|
-
</template>
|
|
303
|
-
|
|
304
|
-
<template #option="{ option, index }">
|
|
305
|
-
<!--
|
|
306
|
-
@slot Use it to customize the option.
|
|
307
|
-
@binding {object} option
|
|
308
|
-
@binding {number} index
|
|
153
|
+
:underlined="underlined"
|
|
154
|
+
:title="fullLabel"
|
|
155
|
+
v-bind="toggleLinkAttrs"
|
|
156
|
+
tabindex="-1"
|
|
157
|
+
:data-test="getDataTest()"
|
|
158
|
+
>
|
|
159
|
+
<template #default>
|
|
160
|
+
<!--
|
|
161
|
+
@slot Use it to add something instead of the default label.
|
|
162
|
+
@binding {string} label
|
|
163
|
+
@binding {boolean} opened
|
|
309
164
|
-->
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
<
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
165
|
+
<slot :label="displayLabel" :opened="opened" />
|
|
166
|
+
</template>
|
|
167
|
+
</ULink>
|
|
168
|
+
|
|
169
|
+
<!--
|
|
170
|
+
@slot Use it to add something instead of the toggle icon.
|
|
171
|
+
@binding {boolean} opened
|
|
172
|
+
-->
|
|
173
|
+
<slot name="toggle" :opened="opened">
|
|
174
|
+
<UIcon
|
|
175
|
+
v-if="toggleIconName"
|
|
176
|
+
interactive
|
|
177
|
+
:color="color"
|
|
178
|
+
:disabled="disabled"
|
|
179
|
+
:name="toggleIconName"
|
|
180
|
+
v-bind="toggleIconAttrs"
|
|
181
|
+
:data-test="getDataTest('toggle')"
|
|
182
|
+
/>
|
|
183
|
+
</slot>
|
|
184
|
+
</template>
|
|
185
|
+
|
|
186
|
+
<template #before-option="{ option, index }">
|
|
187
|
+
<!--
|
|
188
|
+
@slot Use it to add something before option.
|
|
189
|
+
@binding {object} option
|
|
190
|
+
@binding {number} index
|
|
191
|
+
-->
|
|
192
|
+
<slot name="before-option" :option="option" :index="index" />
|
|
193
|
+
</template>
|
|
194
|
+
|
|
195
|
+
<template #option="{ option, index }">
|
|
196
|
+
<!--
|
|
197
|
+
@slot Use it to customize the option.
|
|
198
|
+
@binding {object} option
|
|
199
|
+
@binding {number} index
|
|
200
|
+
-->
|
|
201
|
+
<slot name="option" :option="option" :index="index" />
|
|
202
|
+
</template>
|
|
203
|
+
|
|
204
|
+
<template #after-option="{ option, index }">
|
|
205
|
+
<!--
|
|
206
|
+
@slot Use it to add something after option.
|
|
207
|
+
@binding {object} option
|
|
208
|
+
@binding {number} index
|
|
209
|
+
-->
|
|
210
|
+
<slot name="after-option" :option="option" :index="index" />
|
|
211
|
+
</template>
|
|
212
|
+
|
|
213
|
+
<template #empty>
|
|
214
|
+
<!-- @slot Use it to add something instead of empty state. -->
|
|
215
|
+
<slot name="empty" />
|
|
216
|
+
</template>
|
|
217
|
+
</UDropdown>
|
|
328
218
|
</template>
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
export default /*tw*/ {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
dropdownLink: {
|
|
3
|
+
base: `
|
|
4
|
+
{UDropdown}
|
|
5
|
+
inline-flex gap-0.5 items-center justify-between rounded-small h-max
|
|
6
|
+
focus-visible:outline-offset-4
|
|
7
|
+
`,
|
|
8
|
+
defaults: {
|
|
9
|
+
size: {
|
|
10
|
+
sm: "sm",
|
|
11
|
+
md: "md",
|
|
12
|
+
lg: "lg",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
toggleLink: "{ULink} focus-visible:outline-hidden",
|
|
7
17
|
toggleIcon: {
|
|
8
18
|
base: "{UIcon} block transition duration-300",
|
|
9
19
|
defaults: {
|
|
@@ -15,19 +25,6 @@ export default /*tw*/ {
|
|
|
15
25
|
},
|
|
16
26
|
compoundVariants: [{ opened: true, class: "rotate-180" }],
|
|
17
27
|
},
|
|
18
|
-
listbox: {
|
|
19
|
-
base: "{UListbox} w-fit",
|
|
20
|
-
variants: {
|
|
21
|
-
yPosition: {
|
|
22
|
-
top: "bottom-full mb-2",
|
|
23
|
-
bottom: "top-full mt-2",
|
|
24
|
-
},
|
|
25
|
-
xPosition: {
|
|
26
|
-
left: "left-0",
|
|
27
|
-
right: "right-0",
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
28
|
defaults: {
|
|
32
29
|
color: "primary",
|
|
33
30
|
size: "md",
|