design-system-next 2.13.6 → 2.14.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/dist/design-system-next.es.js +11820 -10296
- package/dist/design-system-next.es.js.gz +0 -0
- package/dist/design-system-next.umd.js +17 -12
- package/dist/design-system-next.umd.js.gz +0 -0
- package/dist/main.css +1 -1
- package/dist/main.css.gz +0 -0
- package/dist/package.json.d.ts +3 -1
- package/package.json +3 -1
- package/src/App.vue +1 -83
- package/src/assets/styles/tailwind.css +13 -11
- package/src/components/date-picker/date-picker.ts +0 -1
- package/src/components/date-picker/date-picker.vue +2 -2
- package/src/components/date-picker/date-range-picker/date-range-picker.vue +59 -36
- package/src/components/dropdown/dropdown.ts +3 -2
- package/src/components/dropdown/dropdown.vue +2 -2
- package/src/components/dropdown/use-dropdown.ts +89 -70
- package/src/components/input/input.ts +0 -1
- package/src/components/input/input.vue +1 -1
- package/src/components/list/ladderized-list/ladderized-list-back.vue +2 -1
- package/src/components/list/ladderized-list/ladderized-list.vue +3 -18
- package/src/components/list/ladderized-list/use-ladderized-list.ts +33 -21
- package/src/components/list/list.ts +4 -1
- package/src/components/list/list.vue +155 -136
- package/src/components/select/select-ladderized/select-ladderized.ts +19 -1
- package/src/components/select/select-ladderized/select-ladderized.vue +42 -41
- package/src/components/select/select-ladderized/use-select-ladderized.ts +20 -33
- package/src/components/select/select-multiple/select-multiple.ts +19 -1
- package/src/components/select/select-multiple/select-multiple.vue +18 -15
- package/src/components/select/select-multiple/use-select-multiple.ts +5 -15
- package/src/components/select/select.ts +19 -1
- package/src/components/select/select.vue +50 -53
- package/src/components/select/use-select.ts +4 -13
- package/src/components/sidenav/use-sidenav.ts +10 -3
- package/src/components/table/table.ts +33 -3
- package/src/components/table/table.vue +46 -13
- package/src/components/table/use-draggable-table-rows.ts +57 -0
- package/src/components/table/use-table.ts +143 -7
- package/src/components/textarea/textarea.vue +7 -1
|
@@ -1,169 +1,188 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="spr-font-main">
|
|
3
|
-
<div
|
|
3
|
+
<div
|
|
4
|
+
v-if="props.searchableMenu"
|
|
5
|
+
:class="[
|
|
6
|
+
'spr-sticky spr-z-20',
|
|
7
|
+
'spr-grid spr-gap-3 spr-bg-white-50 spr-p-size-spacing-3xs',
|
|
8
|
+
'spr-border-color-weak spr-border spr-border-x-0 spr-border-b spr-border-t-0 spr-border-solid',
|
|
9
|
+
]"
|
|
10
|
+
:style="{
|
|
11
|
+
top:
|
|
12
|
+
typeof props.stickySearchOffset === 'number'
|
|
13
|
+
? props.stickySearchOffset + 'px'
|
|
14
|
+
: String(props.stickySearchOffset),
|
|
15
|
+
}"
|
|
16
|
+
>
|
|
4
17
|
<spr-input-search v-model="searchText" :placeholder="props.searchableMenuPlaceholder" autocomplete="off" />
|
|
5
|
-
|
|
6
|
-
<div v-if="isParentMenu" class="spr-background-color-surface spr-h-[1px]"></div>
|
|
7
18
|
</div>
|
|
8
19
|
|
|
9
|
-
<
|
|
10
|
-
<template v-if="
|
|
11
|
-
<
|
|
12
|
-
<div
|
|
13
|
-
<div
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
</template>
|
|
38
|
-
<template v-else>
|
|
39
|
-
<div :class="[item.textColor, 'spr-flex spr-flex-row spr-items-center spr-gap-size-spacing-3xs']">
|
|
40
|
-
<span v-if="item.icon" :class="[item.iconColor, 'spr-mt-[2px]']">
|
|
41
|
-
<icon :icon="item.icon" width="20px" height="20px" />
|
|
42
|
-
</span>
|
|
43
|
-
<div class="spr-flex spr-flex-auto spr-flex-col spr-justify-start">
|
|
44
|
-
<span class="spr-text-left spr-text-xs">
|
|
45
|
-
{{ item.text }}
|
|
46
|
-
</span>
|
|
47
|
-
<span v-if="item.subtext" class="spr-body-xs-regular spr-text-color-base spr-text-left">
|
|
48
|
-
{{ item.subtext }}
|
|
49
|
-
</span>
|
|
50
|
-
</div>
|
|
51
|
-
</div>
|
|
52
|
-
<template v-if="!props.multiSelect && !props.dropdown">
|
|
53
|
-
<Icon
|
|
54
|
-
v-if="isItemSelected(item) && !props.noCheck"
|
|
55
|
-
class="spr-text-color-brand-base spr-w-[1.39em]"
|
|
56
|
-
icon="ph:check"
|
|
20
|
+
<div class="spr-p-size-spacing-3xs">
|
|
21
|
+
<template v-if="props.groupItemsBy">
|
|
22
|
+
<template v-if="groupedMenuList && groupedMenuList.length > 0">
|
|
23
|
+
<div class="spr-grid spr-gap-2">
|
|
24
|
+
<div v-for="(list, listIndex) in groupedMenuList" :key="listIndex" class="spr-grid spr-gap-0.5">
|
|
25
|
+
<div
|
|
26
|
+
v-if="list.groupLabel !== 'no-group'"
|
|
27
|
+
class="spr-label-xs-medium spr-text-color-base spr-px-size-spacing-4xs spr-py-size-spacing-3xs spr-uppercase"
|
|
28
|
+
>
|
|
29
|
+
<span>
|
|
30
|
+
{{ list.groupLabel }}
|
|
31
|
+
</span>
|
|
32
|
+
</div>
|
|
33
|
+
<div
|
|
34
|
+
v-for="(item, itemIndex) in list.items"
|
|
35
|
+
:key="itemIndex"
|
|
36
|
+
:class="getListItemClasses(item)"
|
|
37
|
+
@click="handleSelectedItem(item)"
|
|
38
|
+
>
|
|
39
|
+
<spr-checkbox v-if="props.multiSelect" :checked="isItemSelected(item)" />
|
|
40
|
+
<template v-if="props.lozenge">
|
|
41
|
+
<spr-lozenge
|
|
42
|
+
:label="item.text || (item.lozengeProps?.label as string)"
|
|
43
|
+
:tone="item.lozengeProps?.tone as string & (typeof LOZENGE_TONE)[number]"
|
|
44
|
+
:fill="item.lozengeProps?.fill as boolean"
|
|
45
|
+
:url="item.lozengeProps?.url as string"
|
|
46
|
+
:icon="item.icon || (item.lozengeProps?.icon as string)"
|
|
47
|
+
:postfix-icon="item.lozengeProps?.postfixIcon as string"
|
|
57
48
|
/>
|
|
58
49
|
</template>
|
|
59
|
-
<template v-
|
|
60
|
-
<
|
|
61
|
-
v-if="item.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
50
|
+
<template v-else>
|
|
51
|
+
<div :class="[item.textColor, 'spr-flex spr-flex-row spr-items-center spr-gap-size-spacing-3xs']">
|
|
52
|
+
<span v-if="item.icon" :class="[item.iconColor, 'spr-mt-[2px]']">
|
|
53
|
+
<icon :icon="item.icon" width="20px" height="20px" />
|
|
54
|
+
</span>
|
|
55
|
+
<div class="spr-flex spr-flex-auto spr-flex-col spr-justify-start">
|
|
56
|
+
<span class="spr-text-left spr-text-xs">
|
|
57
|
+
{{ item.text }}
|
|
58
|
+
</span>
|
|
59
|
+
<span v-if="item.subtext" class="spr-body-xs-regular spr-text-color-base spr-text-left">
|
|
60
|
+
{{ item.subtext }}
|
|
61
|
+
</span>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
<template v-if="props.ladderized">
|
|
65
|
+
<template v-if="item.sublevel && item.sublevel?.length > 0">
|
|
66
|
+
<Icon class="spr-text-color-weak spr-size-4" icon="ph:caret-right" />
|
|
67
|
+
</template>
|
|
68
|
+
<template v-else>
|
|
69
|
+
<Icon
|
|
70
|
+
v-if="isItemSelected(item) && !props.noCheck"
|
|
71
|
+
class="spr-text-color-brand-base spr-w-[1.39em]"
|
|
72
|
+
icon="ph:check"
|
|
73
|
+
/>
|
|
74
|
+
</template>
|
|
75
|
+
</template>
|
|
76
|
+
<template v-else>
|
|
77
|
+
<Icon
|
|
78
|
+
v-if="isItemSelected(item) && !props.noCheck && !props.multiSelect"
|
|
79
|
+
class="spr-text-color-brand-base spr-w-[1.39em]"
|
|
80
|
+
icon="ph:check"
|
|
81
|
+
/>
|
|
82
|
+
</template>
|
|
65
83
|
</template>
|
|
66
|
-
</
|
|
84
|
+
</div>
|
|
67
85
|
</div>
|
|
68
86
|
</div>
|
|
69
|
-
</
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
</
|
|
87
|
+
</template>
|
|
88
|
+
<template v-else>
|
|
89
|
+
<div v-if="props.loading" class="spr-skeletal-loader spr-h-8 spr-w-full spr-rounded-md" />
|
|
90
|
+
<div v-else class="spr-flex spr-items-center spr-justify-center spr-p-2 spr-text-center">
|
|
91
|
+
<span class="spr-body-sm-regular spr-m-0">No results found</span>
|
|
92
|
+
</div>
|
|
93
|
+
</template>
|
|
76
94
|
</template>
|
|
77
|
-
</template>
|
|
78
95
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
:class="[
|
|
105
|
-
'spr-flex spr-flex-auto spr-flex-col spr-justify-start',
|
|
106
|
-
{ 'spr-text-color-disabled': item.disabled },
|
|
107
|
-
]"
|
|
108
|
-
>
|
|
109
|
-
<span class="spr-text-left spr-text-xs">{{ item.text }}</span>
|
|
110
|
-
<span
|
|
111
|
-
v-if="item.subtext"
|
|
96
|
+
<template v-else>
|
|
97
|
+
<template v-if="localizedMenuList && localizedMenuList.length > 0">
|
|
98
|
+
<div
|
|
99
|
+
v-for="(item, index) in localizedMenuList"
|
|
100
|
+
:key="index"
|
|
101
|
+
:class="getListItemClasses(item)"
|
|
102
|
+
@click="handleSelectedItem(item)"
|
|
103
|
+
>
|
|
104
|
+
<spr-checkbox v-if="props.multiSelect" :disabled="item.disabled" :checked="isItemSelected(item)" />
|
|
105
|
+
<template v-if="props.lozenge">
|
|
106
|
+
<spr-lozenge
|
|
107
|
+
:label="item.text || (item.lozengeProps?.label as string)"
|
|
108
|
+
:tone="item.lozengeProps?.tone as string & (typeof LOZENGE_TONE)[number]"
|
|
109
|
+
:fill="item.lozengeProps?.fill as boolean"
|
|
110
|
+
:url="item.lozengeProps?.url as string"
|
|
111
|
+
:icon="item.lozengeProps?.icon as string"
|
|
112
|
+
:postfix-icon="item.lozengeProps?.postfixIcon as string"
|
|
113
|
+
/>
|
|
114
|
+
</template>
|
|
115
|
+
<template v-else>
|
|
116
|
+
<div :class="[item.textColor, 'spr-flex spr-flex-row spr-items-center spr-gap-size-spacing-3xs']">
|
|
117
|
+
<span v-if="item.icon" :class="[item.iconColor, 'spr-mt-[2px]']"
|
|
118
|
+
><icon :icon="item.icon" width="20px" height="20px"
|
|
119
|
+
/></span>
|
|
120
|
+
<div
|
|
112
121
|
:class="[
|
|
113
|
-
'spr-
|
|
122
|
+
'spr-flex spr-flex-auto spr-flex-col spr-justify-start',
|
|
114
123
|
{ 'spr-text-color-disabled': item.disabled },
|
|
115
124
|
]"
|
|
116
125
|
>
|
|
117
|
-
{{ item.
|
|
118
|
-
|
|
126
|
+
<span class="spr-text-left spr-text-xs">{{ item.text }}</span>
|
|
127
|
+
<span
|
|
128
|
+
v-if="item.subtext"
|
|
129
|
+
:class="[
|
|
130
|
+
'spr-body-xs-regular spr-text-color-base spr-text-left',
|
|
131
|
+
{ 'spr-text-color-disabled': item.disabled },
|
|
132
|
+
]"
|
|
133
|
+
>
|
|
134
|
+
{{ item.subtext }}
|
|
135
|
+
</span>
|
|
136
|
+
</div>
|
|
119
137
|
</div>
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
138
|
+
<template v-if="props.ladderized">
|
|
139
|
+
<template v-if="item.sublevel && item.sublevel?.length > 0">
|
|
140
|
+
<Icon class="spr-text-color-weak spr-size-4" icon="ph:caret-right" />
|
|
141
|
+
</template>
|
|
142
|
+
<template v-else>
|
|
143
|
+
<Icon
|
|
144
|
+
v-if="isItemSelected(item) && !props.noCheck"
|
|
145
|
+
class="spr-text-color-brand-base spr-w-[1.39em]"
|
|
146
|
+
icon="ph:check"
|
|
147
|
+
/>
|
|
148
|
+
</template>
|
|
149
|
+
</template>
|
|
150
|
+
<template v-else>
|
|
151
|
+
<Icon
|
|
152
|
+
v-if="isItemSelected(item) && !props.noCheck && !props.multiSelect"
|
|
153
|
+
class="spr-text-color-brand-base spr-w-[1.39em]"
|
|
154
|
+
icon="ph:check"
|
|
155
|
+
/>
|
|
156
|
+
</template>
|
|
134
157
|
</template>
|
|
135
|
-
</
|
|
136
|
-
</
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
</
|
|
158
|
+
</div>
|
|
159
|
+
</template>
|
|
160
|
+
<template v-else>
|
|
161
|
+
<div v-if="props.loading" class="spr-skeletal-loader spr-h-8 spr-w-full spr-rounded-md" />
|
|
162
|
+
<div v-else class="spr-flex spr-items-center spr-justify-center spr-p-2 spr-text-center">
|
|
163
|
+
<span class="spr-body-sm-regular spr-m-0">No results found</span>
|
|
164
|
+
</div>
|
|
165
|
+
</template>
|
|
143
166
|
</template>
|
|
144
|
-
</
|
|
167
|
+
</div>
|
|
145
168
|
</div>
|
|
146
169
|
</template>
|
|
147
170
|
|
|
148
171
|
<script lang="ts" setup>
|
|
149
172
|
import { Icon } from '@iconify/vue';
|
|
150
|
-
|
|
151
|
-
import { useList } from './use-list';
|
|
173
|
+
|
|
152
174
|
import SprCheckbox from '@/components/checkbox/checkbox.vue';
|
|
153
175
|
import SprInputSearch from '@/components/input/input-search/input-search.vue';
|
|
154
176
|
import SprLozenge from '@/components/lozenge/lozenge.vue';
|
|
177
|
+
|
|
155
178
|
import { LOZENGE_TONE } from '@/components/lozenge/lozenge';
|
|
156
179
|
|
|
180
|
+
import { listPropTypes, listEmitTypes } from './list';
|
|
181
|
+
import { useList } from './use-list';
|
|
182
|
+
|
|
157
183
|
const props = defineProps(listPropTypes);
|
|
158
184
|
const emit = defineEmits(listEmitTypes);
|
|
159
185
|
|
|
160
|
-
const {
|
|
161
|
-
|
|
162
|
-
localizedMenuList,
|
|
163
|
-
groupedMenuList,
|
|
164
|
-
isParentMenu,
|
|
165
|
-
isItemSelected,
|
|
166
|
-
getListItemClasses,
|
|
167
|
-
handleSelectedItem,
|
|
168
|
-
} = useList(props, emit);
|
|
186
|
+
const { searchText, localizedMenuList, groupedMenuList, isItemSelected, getListItemClasses, handleSelectedItem } =
|
|
187
|
+
useList(props, emit);
|
|
169
188
|
</script>
|
|
@@ -20,12 +20,12 @@ const PLACEMENTS_TYPES = [
|
|
|
20
20
|
] as const;
|
|
21
21
|
|
|
22
22
|
const POPPER_STRATEGY_TYPES = ['fixed', 'absolute'] as const;
|
|
23
|
+
const TRIGGER_EVENTS = ['click', 'hover', 'focus', 'touch'] as const;
|
|
23
24
|
|
|
24
25
|
export const selectLadderizedPropTypes = {
|
|
25
26
|
id: {
|
|
26
27
|
type: String,
|
|
27
28
|
required: true,
|
|
28
|
-
default: 'select-ladderized',
|
|
29
29
|
},
|
|
30
30
|
modelValue: {
|
|
31
31
|
type: Array as PropType<string[]>,
|
|
@@ -97,6 +97,24 @@ export const selectLadderizedPropTypes = {
|
|
|
97
97
|
validator: (value: (typeof PLACEMENTS_TYPES)[number]) => PLACEMENTS_TYPES.includes(value),
|
|
98
98
|
default: 'bottom',
|
|
99
99
|
},
|
|
100
|
+
triggers: {
|
|
101
|
+
type: Array as PropType<(typeof TRIGGER_EVENTS)[number][]>,
|
|
102
|
+
validator: (value: (typeof TRIGGER_EVENTS)[number][]) => {
|
|
103
|
+
return value.every((val) => TRIGGER_EVENTS.includes(val));
|
|
104
|
+
},
|
|
105
|
+
default: () => [],
|
|
106
|
+
},
|
|
107
|
+
popperTriggers: {
|
|
108
|
+
type: Array as PropType<(typeof TRIGGER_EVENTS)[number][]>,
|
|
109
|
+
validator: (value: (typeof TRIGGER_EVENTS)[number][]) => {
|
|
110
|
+
return value.every((val) => TRIGGER_EVENTS.includes(val));
|
|
111
|
+
},
|
|
112
|
+
default: () => [],
|
|
113
|
+
},
|
|
114
|
+
autoHide: {
|
|
115
|
+
type: Boolean,
|
|
116
|
+
default: true,
|
|
117
|
+
},
|
|
100
118
|
wrapperPosition: {
|
|
101
119
|
type: String,
|
|
102
120
|
default: 'relative',
|
|
@@ -8,15 +8,15 @@
|
|
|
8
8
|
</label>
|
|
9
9
|
|
|
10
10
|
<Menu
|
|
11
|
-
:shown="ladderizedSelectPopperState"
|
|
11
|
+
v-model:shown="ladderizedSelectPopperState"
|
|
12
12
|
aria-id="ladderized-select-wrapper"
|
|
13
13
|
distance="4"
|
|
14
14
|
:placement="props.placement"
|
|
15
|
-
:triggers="
|
|
16
|
-
:popper-
|
|
17
|
-
:auto-hide="
|
|
15
|
+
:triggers="props.triggers"
|
|
16
|
+
:popper-triggers="props.popperTriggers"
|
|
17
|
+
:auto-hide="props.autoHide"
|
|
18
18
|
:disabled="isLadderizedSelectPopperDisabled"
|
|
19
|
-
:container="
|
|
19
|
+
:container="`#${props.id}`"
|
|
20
20
|
:strategy="
|
|
21
21
|
props.popperStrategy === 'fixed' || props.popperStrategy === 'absolute' ? props.popperStrategy : 'absolute'
|
|
22
22
|
"
|
|
@@ -26,44 +26,46 @@
|
|
|
26
26
|
width: props.width,
|
|
27
27
|
}"
|
|
28
28
|
>
|
|
29
|
-
<div
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
29
|
+
<div ref="ladderizedSelectState">
|
|
30
|
+
<div @click="ladderizedSelectPopperState = !ladderizedSelectPopperState">
|
|
31
|
+
<spr-input
|
|
32
|
+
:id="`input-${props.id}`"
|
|
33
|
+
v-model="inputText"
|
|
34
|
+
class="spr-cursor-pointer"
|
|
35
|
+
:placeholder="props.placeholder"
|
|
36
|
+
autocomplete="off"
|
|
37
|
+
:helper-text="props.helperText"
|
|
38
|
+
:helper-icon="props.helperIcon"
|
|
39
|
+
:display-helper="props.displayHelper"
|
|
40
|
+
readonly
|
|
41
|
+
:disabled="props.disabled"
|
|
42
|
+
:error="props.error"
|
|
43
|
+
>
|
|
44
|
+
<template #icon>
|
|
45
|
+
<div class="spr-flex spr-items-center spr-gap-1">
|
|
46
|
+
<Icon
|
|
47
|
+
v-if="props.clearable && inputText"
|
|
48
|
+
class="spr-cursor-pointer"
|
|
49
|
+
icon="ph:x"
|
|
50
|
+
@click.stop="handleClear"
|
|
51
|
+
/>
|
|
52
|
+
<Icon icon="ph:caret-down" />
|
|
53
|
+
</div>
|
|
54
|
+
</template>
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
<template #helperMessage>
|
|
57
|
+
<slot name="helperMessage" />
|
|
58
|
+
</template>
|
|
59
|
+
</spr-input>
|
|
60
|
+
</div>
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
<!-- This div used to poppulate popper menu -->
|
|
63
|
+
<div :id="props.id" :style="{ width: props.popperWidth }"></div>
|
|
64
|
+
</div>
|
|
63
65
|
|
|
64
66
|
<template #popper>
|
|
65
67
|
<div
|
|
66
|
-
ref="
|
|
68
|
+
ref="ladderizedSelectPopperRef"
|
|
67
69
|
class="spr-grid spr-max-h-[300px] spr-gap-0.5 spr-overflow-y-auto spr-overflow-x-hidden"
|
|
68
70
|
>
|
|
69
71
|
<template v-if="ladderizedSelectOptions.length > 0">
|
|
@@ -106,15 +108,14 @@ const emit = defineEmits(selectLadderizedEmitTypes);
|
|
|
106
108
|
|
|
107
109
|
const {
|
|
108
110
|
ladderizedClasses,
|
|
111
|
+
ladderizedSelectState,
|
|
109
112
|
ladderizedSelectPopperState,
|
|
110
|
-
|
|
113
|
+
ladderizedSelectPopperRef,
|
|
111
114
|
ladderizedSelectOptions,
|
|
112
115
|
isLadderizedSelectPopperDisabled,
|
|
113
116
|
ladderizedSelectModel,
|
|
114
117
|
inputText,
|
|
115
118
|
handleSelectedLadderizedItem,
|
|
116
|
-
handleSearch,
|
|
117
119
|
handleClear,
|
|
118
|
-
handleOptionsToggle,
|
|
119
120
|
} = useSelectLadderized(props, emit as SelectLadderizedEmitFn);
|
|
120
121
|
</script>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ref, toRefs, computed, watch } from 'vue';
|
|
2
|
-
import { useVModel,
|
|
2
|
+
import { useVModel, onClickOutside } from '@vueuse/core';
|
|
3
3
|
|
|
4
4
|
import type { SelectLadderizedPropTypes } from './select-ladderized';
|
|
5
5
|
|
|
@@ -17,9 +17,11 @@ export const useSelectLadderized = (
|
|
|
17
17
|
supportingLabelClasses: 'spr-body-sm-regular spr-text-color-supporting',
|
|
18
18
|
}));
|
|
19
19
|
|
|
20
|
+
const ladderizedSelectState = ref<HTMLDivElement | null>(null);
|
|
21
|
+
|
|
20
22
|
// Popper Variables
|
|
21
|
-
const ladderizedSelectPopperState = ref(false);
|
|
22
|
-
const
|
|
23
|
+
const ladderizedSelectPopperState = ref<boolean>(false);
|
|
24
|
+
const ladderizedSelectPopperRef = ref<HTMLElement | null>(null);
|
|
23
25
|
const isLadderizedSelectPopperDisabled = computed(() => disabled.value);
|
|
24
26
|
|
|
25
27
|
// Ladderized Select Model
|
|
@@ -28,8 +30,7 @@ export const useSelectLadderized = (
|
|
|
28
30
|
|
|
29
31
|
// Input Variables
|
|
30
32
|
const inputText = ref<string>('');
|
|
31
|
-
const
|
|
32
|
-
const wasCleared = ref(false);
|
|
33
|
+
const wasCleared = ref<boolean>(false);
|
|
33
34
|
|
|
34
35
|
const isLeafNode = (item: MenuListType): boolean => {
|
|
35
36
|
return !item.sublevel || item.sublevel.length === 0;
|
|
@@ -102,8 +103,6 @@ export const useSelectLadderized = (
|
|
|
102
103
|
|
|
103
104
|
if (leafItem && isLeafNode(leafItem)) {
|
|
104
105
|
ladderizedSelectPopperState.value = false;
|
|
105
|
-
|
|
106
|
-
emit('popper-state', false);
|
|
107
106
|
}
|
|
108
107
|
|
|
109
108
|
return;
|
|
@@ -141,24 +140,12 @@ export const useSelectLadderized = (
|
|
|
141
140
|
|
|
142
141
|
if (isLeafNode(itemToCheck)) {
|
|
143
142
|
ladderizedSelectPopperState.value = false;
|
|
144
|
-
|
|
145
|
-
emit('popper-state', false);
|
|
146
143
|
}
|
|
147
144
|
} else if (selectedItems.length === 0 && !wasCleared.value) {
|
|
148
145
|
inputText.value = '';
|
|
149
146
|
}
|
|
150
147
|
};
|
|
151
148
|
|
|
152
|
-
const handleSearch = () => {
|
|
153
|
-
isSearching.value = true;
|
|
154
|
-
|
|
155
|
-
debouncedEmitSearch();
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
const debouncedEmitSearch = useDebounceFn(() => {
|
|
159
|
-
// Optionally emit search event here if needed
|
|
160
|
-
}, 300);
|
|
161
|
-
|
|
162
149
|
const handleClear = () => {
|
|
163
150
|
wasCleared.value = true;
|
|
164
151
|
|
|
@@ -167,14 +154,6 @@ export const useSelectLadderized = (
|
|
|
167
154
|
emit('update:modelValue', []);
|
|
168
155
|
};
|
|
169
156
|
|
|
170
|
-
const handleOptionsToggle = () => {
|
|
171
|
-
ladderizedSelectPopperState.value = true;
|
|
172
|
-
|
|
173
|
-
isSearching.value = false;
|
|
174
|
-
|
|
175
|
-
emit('popper-state', true);
|
|
176
|
-
};
|
|
177
|
-
|
|
178
157
|
// Watch for changes in modelValue to update inputText
|
|
179
158
|
watch(
|
|
180
159
|
() => ladderizedSelectModel.value,
|
|
@@ -209,23 +188,31 @@ export const useSelectLadderized = (
|
|
|
209
188
|
{ immediate: true },
|
|
210
189
|
);
|
|
211
190
|
|
|
212
|
-
|
|
213
|
-
|
|
191
|
+
watch(ladderizedSelectPopperState, (newState) => {
|
|
192
|
+
emit('popper-state', newState);
|
|
193
|
+
});
|
|
214
194
|
|
|
215
|
-
|
|
195
|
+
// Close only when clicking completely outside both the popper and the trigger wrapper.
|
|
196
|
+
onClickOutside(ladderizedSelectPopperRef, (event) => {
|
|
197
|
+
const triggerWrapper = ladderizedSelectState.value;
|
|
198
|
+
if (triggerWrapper && triggerWrapper.contains(event.target as Node)) {
|
|
199
|
+
return; // ignore clicks on trigger content
|
|
200
|
+
}
|
|
201
|
+
if (ladderizedSelectPopperState.value) {
|
|
202
|
+
ladderizedSelectPopperState.value = false;
|
|
203
|
+
}
|
|
216
204
|
});
|
|
217
205
|
|
|
218
206
|
return {
|
|
219
207
|
ladderizedClasses,
|
|
208
|
+
ladderizedSelectState,
|
|
220
209
|
ladderizedSelectPopperState,
|
|
221
|
-
|
|
210
|
+
ladderizedSelectPopperRef,
|
|
222
211
|
ladderizedSelectOptions,
|
|
223
212
|
isLadderizedSelectPopperDisabled,
|
|
224
213
|
ladderizedSelectModel,
|
|
225
214
|
inputText,
|
|
226
215
|
handleSelectedLadderizedItem,
|
|
227
|
-
handleSearch,
|
|
228
216
|
handleClear,
|
|
229
|
-
handleOptionsToggle,
|
|
230
217
|
};
|
|
231
218
|
};
|
|
@@ -24,12 +24,12 @@ const PLACEMENTS_TYPES = [
|
|
|
24
24
|
] as const;
|
|
25
25
|
|
|
26
26
|
const POPPER_STRATEGY_TYPES = ['fixed', 'absolute'] as const;
|
|
27
|
+
const TRIGGER_EVENTS = ['click', 'hover', 'focus', 'touch'] as const;
|
|
27
28
|
|
|
28
29
|
export const multiSelectPropTypes = {
|
|
29
30
|
id: {
|
|
30
31
|
type: String,
|
|
31
32
|
required: true,
|
|
32
|
-
default: 'multi-select',
|
|
33
33
|
},
|
|
34
34
|
modelValue: {
|
|
35
35
|
type: Array as PropType<(string | number | Record<string, unknown>)[]>,
|
|
@@ -76,6 +76,24 @@ export const multiSelectPropTypes = {
|
|
|
76
76
|
validator: (value: (typeof PLACEMENTS_TYPES)[number]) => PLACEMENTS_TYPES.includes(value),
|
|
77
77
|
default: 'bottom',
|
|
78
78
|
},
|
|
79
|
+
triggers: {
|
|
80
|
+
type: Array as PropType<(typeof TRIGGER_EVENTS)[number][]>,
|
|
81
|
+
validator: (value: (typeof TRIGGER_EVENTS)[number][]) => {
|
|
82
|
+
return value.every((val) => TRIGGER_EVENTS.includes(val));
|
|
83
|
+
},
|
|
84
|
+
default: () => [],
|
|
85
|
+
},
|
|
86
|
+
popperTriggers: {
|
|
87
|
+
type: Array as PropType<(typeof TRIGGER_EVENTS)[number][]>,
|
|
88
|
+
validator: (value: (typeof TRIGGER_EVENTS)[number][]) => {
|
|
89
|
+
return value.every((val) => TRIGGER_EVENTS.includes(val));
|
|
90
|
+
},
|
|
91
|
+
default: () => [],
|
|
92
|
+
},
|
|
93
|
+
autoHide: {
|
|
94
|
+
type: Boolean,
|
|
95
|
+
default: false,
|
|
96
|
+
},
|
|
79
97
|
popperStrategy: {
|
|
80
98
|
type: String,
|
|
81
99
|
validator: (value: 'fixed' | 'absolute') => POPPER_STRATEGY_TYPES.includes(value),
|