quasar-ui-sellmate-ui-kit 3.14.2 → 3.14.4

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.
@@ -1,320 +1,320 @@
1
- <template>
2
- <q-select
3
- outlined
4
- dense
5
- options-dense
6
- :dropdown-icon="selectDownArrowIcon"
7
- v-model="model"
8
- :options="filteredOptions"
9
- emit-value
10
- map-options
11
- color="positive"
12
- class="s-select-search"
13
- :popup-content-class="
14
- [
15
- 's-select-opts',
16
- `s-select-opt-${id}`,
17
- isScrolled && 's-select-opts--scroll',
18
- popupContentClass,
19
- ].join(' ')
20
- "
21
- :option-label="optionLabel"
22
- :option-value="optionValue"
23
- :menu-offset="[0, 4]"
24
- menu-self="top left"
25
- menu-anchor="bottom start"
26
- no-error-icon
27
- hide-bottom-space
28
- ref="sSelectRef"
29
- @popup-show="handleScroll"
30
- @popup-hide="removeScroll"
31
- >
32
- <template #before-options>
33
- <div class="search-input-form-container">
34
- <input-with-search-icon
35
- v-model="search"
36
- autofocus
37
- class="select-search-input"
38
- :placeholder="searchPlaceholder"
39
- @keydown.prevent.enter="e => onSearch(e.target.value)"
40
- @keydown.prevent.arrow-down="handleKey"
41
- @keydown.prevent.arrow-up="handleKey"
42
- @on-input="handleInput"
43
- @on-delete="handleDelete"
44
- />
45
- </div>
46
- </template>
47
- <template #option="{ itemProps, opt }">
48
- <q-item v-bind="itemProps" class="custom-select-options">
49
- <q-item-section>
50
- {{ opt[optionLabel] }}
51
- </q-item-section>
52
- </q-item>
53
- </template>
54
- <template v-if="(options.length && !model) || (noSelected && !options.length)" #selected>
55
- <div v-if="noSelected && !options.length">{{ noSelected }}</div>
56
- <div v-else>
57
- {{ placeholder }}
58
- </div>
59
- </template>
60
- <template #no-option>
61
- <div class="search-input-form-container">
62
- <input-with-search-icon
63
- v-model="search"
64
- autofocus
65
- class="select-search-input"
66
- :placeholder="searchPlaceholder"
67
- @keydown.prevent.enter="e => onSearch(e.target.value)"
68
- @keydown.prevent.arrow-down="handleKey"
69
- @keydown.prevent.arrow-up="handleKey"
70
- @on-input="handleInput"
71
- @on-delete="handleDelete"
72
- />
73
- </div>
74
- <q-item class="s-select-no-option">
75
- <q-item-section class="text-grey">{{ noData }}</q-item-section>
76
- </q-item>
77
- </template>
78
- </q-select>
79
- </template>
80
-
81
- <script>
82
- import { QImg, QItem, QItemSection, QSelect, debounce, useId } from 'quasar';
83
- import { defineComponent, nextTick, ref, watch } from 'vue';
84
- import { selectDownArrowIcon } from '../assets/icons';
85
- import InputWithSearchIcon from './InputWithSearchIcon.vue';
86
-
87
- export default defineComponent({
88
- name: 'SSelectSearch',
89
- emits: ['update:modelValue', 'onSearch'],
90
- components: {
91
- QSelect,
92
- QItem,
93
- QItemSection,
94
- InputWithSearchIcon,
95
- },
96
- props: {
97
- searchPlaceholder: { type: String, default: '검색어 입력 후 Enter' },
98
- options: {
99
- type: Array,
100
- required: true,
101
- },
102
- modelValue: {
103
- type: [String, Number, null],
104
- required: true,
105
- },
106
- optionLabel: {
107
- type: String,
108
- default: 'label',
109
- },
110
- optionValue: {
111
- type: String,
112
- default: 'value',
113
- },
114
- placeholder: {
115
- type: String,
116
- default: '전체',
117
- },
118
- noData: {
119
- type: String,
120
- default: '데이터 없음',
121
- },
122
- popupContentClass: {
123
- type: String,
124
- },
125
- noSelected: {
126
- type: String,
127
- default: '선택',
128
- },
129
- },
130
- setup(props, { emit }) {
131
- const id = useId();
132
- const model = ref(props.modelValue);
133
- const filteredOptions = ref(props.options);
134
- const findLabel = () =>
135
- props.options.find(opt => opt[props.optionValue] === props.modelValue)[props.optionLabel] ||
136
- '';
137
-
138
- const search = ref('');
139
- const sSelectRef = ref(null);
140
-
141
- function handleKey(e) {
142
- if (sSelectRef.value) {
143
- sSelectRef.value.moveOptionSelection(e.key === 'ArrowDown' ? 1 : -1, true);
144
- }
145
- }
146
-
147
- function handleInput(val) {
148
- search.value = val;
149
- sSelectRef.value.setOptionIndex(-1);
150
-
151
- if (!val) {
152
- filteredOptions.value = props.options;
153
- sSelectRef.value.setOptionIndex(-1);
154
- }
155
- }
156
-
157
- function handleDelete() {
158
- sSelectRef.value.setOptionIndex(-1);
159
- search.value = '';
160
- emit('onSearch', '');
161
- }
162
-
163
- const onSearch = debounce(val => {
164
- if (!val) {
165
- filteredOptions.value = props.options;
166
- return;
167
- }
168
-
169
- // 옵션을 선택 했을 때
170
- if (sSelectRef.value.getOptionIndex() !== -1) {
171
- model.value = filteredOptions.value[sSelectRef.value.getOptionIndex()][props.optionValue];
172
- handleDelete();
173
- sSelectRef.value.setOptionIndex(-1);
174
- sSelectRef.value.hidePopup();
175
- return;
176
- }
177
-
178
- const filtered = props.options.filter(
179
- v => (v[props.optionLabel] || v).toLowerCase().indexOf(val.toLowerCase()) > -1,
180
- );
181
-
182
- if (!filtered.length) {
183
- emit('onSearch', val);
184
- sSelectRef.value.setOptionIndex(-1);
185
- return;
186
- }
187
-
188
- filteredOptions.value = filtered;
189
- }, 200);
190
-
191
- watch(
192
- () => props.modelValue,
193
- val => {
194
- model.value = val;
195
- sSelectRef.value.setOptionIndex(-1);
196
- },
197
- );
198
-
199
- watch(model, val => {
200
- emit('update:modelValue', val);
201
- });
202
-
203
- watch(
204
- () => props.options,
205
- val => {
206
- // const existingValues = new Set(filteredOptions.value.map((option) => option.value));
207
- // const nonDuplicateOptions = val.filter((option) => !existingValues.has(option.value));
208
- // filteredOptions.value.push(...nonDuplicateOptions);
209
- filteredOptions.value = val;
210
- sSelectRef.value.setOptionIndex(-1);
211
- },
212
- { deep: true },
213
- );
214
-
215
- const isScrolled = ref(false);
216
-
217
- let scrollTimeout = null;
218
- const handleScroll = () => {
219
- const selectOptsEl = document.getElementsByClassName(`s-select-opt-${id.value}`);
220
- nextTick(() => {
221
- if (selectOptsEl[0]) {
222
- selectOptsEl[0].addEventListener('scroll', () => {
223
- isScrolled.value = true;
224
-
225
- if (scrollTimeout) {
226
- clearTimeout(scrollTimeout);
227
- }
228
- scrollTimeout = setTimeout(() => {
229
- isScrolled.value = false;
230
- }, 100); // 100ms 후 스크롤 멈춤으로 간주
231
- });
232
- }
233
- });
234
- };
235
-
236
- const removeScroll = () => {
237
- const selectOptsEl = document.getElementsByClassName(`s-select-opt-${id.value}`);
238
- nextTick(() => {
239
- if (selectOptsEl[0]) {
240
- selectOptsEl[0].removeEventListener('scroll', () => {
241
- if (scrollTimeout) {
242
- clearTimeout(scrollTimeout);
243
- }
244
-
245
- isScrolled.value = false;
246
- });
247
- }
248
- });
249
- };
250
-
251
- return {
252
- model,
253
- selectDownArrowIcon,
254
- onSearch,
255
- filteredOptions,
256
- search,
257
- sSelectRef,
258
- isScrolled,
259
- id,
260
- handleKey,
261
- handleInput,
262
- handleDelete,
263
- handleScroll,
264
- removeScroll,
265
- };
266
- },
267
- });
268
- </script>
269
-
270
- <style lang="scss">
271
- @import '../css/quasar.variables.scss';
272
- @import '../css/extends.scss';
273
-
274
- .s-select-search {
275
- @extend %select;
276
-
277
- .q-field__native {
278
- display: block;
279
- min-height: 0;
280
- height: $default-height;
281
- width: 100%;
282
- padding: $select-padding !important;
283
- color: $Grey_Darken-4;
284
- white-space: nowrap;
285
- overflow: hidden;
286
- text-overflow: ellipsis;
287
-
288
- > span {
289
- max-height: $default-height;
290
- }
291
- }
292
- }
293
-
294
- .s-select-opts {
295
- @extend %select-menu-list;
296
- max-height: 252px;
297
- .search-input-form-container {
298
- position: sticky;
299
- top: 0px;
300
- padding: 4px;
301
- z-index: 1;
302
- background-color: white;
303
- }
304
- &--scroll {
305
- .search-input-form-container {
306
- box-shadow: 2px 2px 8px 2px #00000033;
307
- }
308
- }
309
- }
310
-
311
- .custom-select-options {
312
- height: $default-height;
313
- }
314
-
315
- .disabled.s-select-option {
316
- border: none;
317
- background: none !important;
318
- color: $Grey_Default !important;
319
- }
320
- </style>
1
+ <template>
2
+ <q-select
3
+ outlined
4
+ dense
5
+ options-dense
6
+ :dropdown-icon="selectDownArrowIcon"
7
+ v-model="model"
8
+ :options="filteredOptions"
9
+ emit-value
10
+ map-options
11
+ color="positive"
12
+ class="s-select-search"
13
+ :popup-content-class="
14
+ [
15
+ 's-select-opts',
16
+ `s-select-opt-${id}`,
17
+ isScrolled && 's-select-opts--scroll',
18
+ popupContentClass,
19
+ ].join(' ')
20
+ "
21
+ :option-label="optionLabel"
22
+ :option-value="optionValue"
23
+ :menu-offset="[0, 4]"
24
+ menu-self="top left"
25
+ menu-anchor="bottom start"
26
+ no-error-icon
27
+ hide-bottom-space
28
+ ref="sSelectRef"
29
+ @popup-show="handleScroll"
30
+ @popup-hide="removeScroll"
31
+ >
32
+ <template #before-options>
33
+ <div class="search-input-form-container">
34
+ <input-with-search-icon
35
+ v-model="search"
36
+ autofocus
37
+ class="select-search-input"
38
+ :placeholder="searchPlaceholder"
39
+ @keydown.prevent.enter="e => onSearch(e.target.value)"
40
+ @keydown.prevent.arrow-down="handleKey"
41
+ @keydown.prevent.arrow-up="handleKey"
42
+ @on-input="handleInput"
43
+ @on-delete="handleDelete"
44
+ />
45
+ </div>
46
+ </template>
47
+ <template #option="{ itemProps, opt }">
48
+ <q-item v-bind="itemProps" class="custom-select-options">
49
+ <q-item-section>
50
+ {{ opt[optionLabel] }}
51
+ </q-item-section>
52
+ </q-item>
53
+ </template>
54
+ <template v-if="(options.length && !model) || (noSelected && !options.length)" #selected>
55
+ <div v-if="noSelected && !options.length">{{ noSelected }}</div>
56
+ <div v-else>
57
+ {{ placeholder }}
58
+ </div>
59
+ </template>
60
+ <template #no-option>
61
+ <div class="search-input-form-container">
62
+ <input-with-search-icon
63
+ v-model="search"
64
+ autofocus
65
+ class="select-search-input"
66
+ :placeholder="searchPlaceholder"
67
+ @keydown.prevent.enter="e => onSearch(e.target.value)"
68
+ @keydown.prevent.arrow-down="handleKey"
69
+ @keydown.prevent.arrow-up="handleKey"
70
+ @on-input="handleInput"
71
+ @on-delete="handleDelete"
72
+ />
73
+ </div>
74
+ <q-item class="s-select-no-option">
75
+ <q-item-section class="text-grey">{{ noData }}</q-item-section>
76
+ </q-item>
77
+ </template>
78
+ </q-select>
79
+ </template>
80
+
81
+ <script>
82
+ import { QImg, QItem, QItemSection, QSelect, debounce, useId } from 'quasar';
83
+ import { defineComponent, nextTick, ref, watch } from 'vue';
84
+ import { selectDownArrowIcon } from '../assets/icons';
85
+ import InputWithSearchIcon from './InputWithSearchIcon.vue';
86
+
87
+ export default defineComponent({
88
+ name: 'SSelectSearch',
89
+ emits: ['update:modelValue', 'onSearch'],
90
+ components: {
91
+ QSelect,
92
+ QItem,
93
+ QItemSection,
94
+ InputWithSearchIcon,
95
+ },
96
+ props: {
97
+ searchPlaceholder: { type: String, default: '검색어 입력 후 Enter' },
98
+ options: {
99
+ type: Array,
100
+ required: true,
101
+ },
102
+ modelValue: {
103
+ type: [String, Number, null],
104
+ required: true,
105
+ },
106
+ optionLabel: {
107
+ type: String,
108
+ default: 'label',
109
+ },
110
+ optionValue: {
111
+ type: String,
112
+ default: 'value',
113
+ },
114
+ placeholder: {
115
+ type: String,
116
+ default: '전체',
117
+ },
118
+ noData: {
119
+ type: String,
120
+ default: '데이터 없음',
121
+ },
122
+ popupContentClass: {
123
+ type: String,
124
+ },
125
+ noSelected: {
126
+ type: String,
127
+ default: '선택',
128
+ },
129
+ },
130
+ setup(props, { emit }) {
131
+ const id = useId();
132
+ const model = ref(props.modelValue);
133
+ const filteredOptions = ref(props.options);
134
+ const findLabel = () =>
135
+ props.options.find(opt => opt[props.optionValue] === props.modelValue)[props.optionLabel] ||
136
+ '';
137
+
138
+ const search = ref('');
139
+ const sSelectRef = ref(null);
140
+
141
+ function handleKey(e) {
142
+ if (sSelectRef.value) {
143
+ sSelectRef.value.moveOptionSelection(e.key === 'ArrowDown' ? 1 : -1, true);
144
+ }
145
+ }
146
+
147
+ function handleInput(val) {
148
+ search.value = val;
149
+ sSelectRef.value.setOptionIndex(-1);
150
+
151
+ if (!val) {
152
+ filteredOptions.value = props.options;
153
+ sSelectRef.value.setOptionIndex(-1);
154
+ }
155
+ }
156
+
157
+ function handleDelete() {
158
+ sSelectRef.value.setOptionIndex(-1);
159
+ search.value = '';
160
+ emit('onSearch', '');
161
+ }
162
+
163
+ const onSearch = debounce(val => {
164
+ if (!val) {
165
+ filteredOptions.value = props.options;
166
+ return;
167
+ }
168
+
169
+ // 옵션을 선택 했을 때
170
+ if (sSelectRef.value.getOptionIndex() !== -1) {
171
+ model.value = filteredOptions.value[sSelectRef.value.getOptionIndex()][props.optionValue];
172
+ handleDelete();
173
+ sSelectRef.value.setOptionIndex(-1);
174
+ sSelectRef.value.hidePopup();
175
+ return;
176
+ }
177
+
178
+ const filtered = props.options.filter(
179
+ v => (v[props.optionLabel] || v).toLowerCase().indexOf(val.toLowerCase()) > -1,
180
+ );
181
+
182
+ if (!filtered.length) {
183
+ emit('onSearch', val);
184
+ sSelectRef.value.setOptionIndex(-1);
185
+ return;
186
+ }
187
+
188
+ filteredOptions.value = filtered;
189
+ }, 200);
190
+
191
+ watch(
192
+ () => props.modelValue,
193
+ val => {
194
+ model.value = val;
195
+ sSelectRef.value.setOptionIndex(-1);
196
+ },
197
+ );
198
+
199
+ watch(model, val => {
200
+ emit('update:modelValue', val);
201
+ });
202
+
203
+ watch(
204
+ () => props.options,
205
+ val => {
206
+ // const existingValues = new Set(filteredOptions.value.map((option) => option.value));
207
+ // const nonDuplicateOptions = val.filter((option) => !existingValues.has(option.value));
208
+ // filteredOptions.value.push(...nonDuplicateOptions);
209
+ filteredOptions.value = val;
210
+ sSelectRef.value.setOptionIndex(-1);
211
+ },
212
+ { deep: true },
213
+ );
214
+
215
+ const isScrolled = ref(false);
216
+
217
+ let scrollTimeout = null;
218
+ const handleScroll = () => {
219
+ const selectOptsEl = document.getElementsByClassName(`s-select-opt-${id.value}`);
220
+ nextTick(() => {
221
+ if (selectOptsEl[0]) {
222
+ selectOptsEl[0].addEventListener('scroll', () => {
223
+ isScrolled.value = true;
224
+
225
+ if (scrollTimeout) {
226
+ clearTimeout(scrollTimeout);
227
+ }
228
+ scrollTimeout = setTimeout(() => {
229
+ isScrolled.value = false;
230
+ }, 100); // 100ms 후 스크롤 멈춤으로 간주
231
+ });
232
+ }
233
+ });
234
+ };
235
+
236
+ const removeScroll = () => {
237
+ const selectOptsEl = document.getElementsByClassName(`s-select-opt-${id.value}`);
238
+ nextTick(() => {
239
+ if (selectOptsEl[0]) {
240
+ selectOptsEl[0].removeEventListener('scroll', () => {
241
+ if (scrollTimeout) {
242
+ clearTimeout(scrollTimeout);
243
+ }
244
+
245
+ isScrolled.value = false;
246
+ });
247
+ }
248
+ });
249
+ };
250
+
251
+ return {
252
+ model,
253
+ selectDownArrowIcon,
254
+ onSearch,
255
+ filteredOptions,
256
+ search,
257
+ sSelectRef,
258
+ isScrolled,
259
+ id,
260
+ handleKey,
261
+ handleInput,
262
+ handleDelete,
263
+ handleScroll,
264
+ removeScroll,
265
+ };
266
+ },
267
+ });
268
+ </script>
269
+
270
+ <style lang="scss">
271
+ @import '../css/quasar.variables.scss';
272
+ @import '../css/extends.scss';
273
+
274
+ .s-select-search {
275
+ @extend %select;
276
+
277
+ .q-field__native {
278
+ display: block;
279
+ min-height: 0;
280
+ height: $default-height;
281
+ width: 100%;
282
+ padding: $select-padding !important;
283
+ color: $Grey_Darken-4;
284
+ white-space: nowrap;
285
+ overflow: hidden;
286
+ text-overflow: ellipsis;
287
+
288
+ > span {
289
+ max-height: $default-height;
290
+ }
291
+ }
292
+ }
293
+
294
+ .s-select-opts {
295
+ @extend %select-menu-list;
296
+ max-height: 252px;
297
+ .search-input-form-container {
298
+ position: sticky;
299
+ top: 0px;
300
+ padding: 4px;
301
+ z-index: 1;
302
+ background-color: white;
303
+ }
304
+ &--scroll {
305
+ .search-input-form-container {
306
+ box-shadow: 2px 2px 8px 2px #00000033;
307
+ }
308
+ }
309
+ }
310
+
311
+ .custom-select-options {
312
+ height: $default-height;
313
+ }
314
+
315
+ .disabled.s-select-option {
316
+ border: none;
317
+ background: none !important;
318
+ color: $Grey_Default !important;
319
+ }
320
+ </style>