@loop8/vue-select 0.1.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/README.md +383 -0
- package/dist/components/Loop8Select.vue.d.ts +110 -0
- package/dist/index.d.ts +8 -0
- package/dist/loop8-select.es.js +600 -0
- package/dist/loop8-select.umd.js +1 -0
- package/dist/style.css +1 -0
- package/dist/types.d.ts +42 -0
- package/package.json +49 -0
- package/src/styles/loop-select.css +231 -0
- package/src/styles/spinner.css +26 -0
package/README.md
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
# Vue Loop8 Select
|
|
2
|
+
|
|
3
|
+
A Vue 3 select component inspired by Select2, built using the Composition API.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Single and multiple select support
|
|
8
|
+
- Search/filtering capabilities
|
|
9
|
+
- Remote data loading via AJAX
|
|
10
|
+
- Infinite scrolling pagination for large datasets
|
|
11
|
+
- Custom templates with slots for rich display options
|
|
12
|
+
- Keyboard navigation
|
|
13
|
+
- Customizable via props
|
|
14
|
+
- No jQuery dependency
|
|
15
|
+
- Form integration with standard HTML form elements
|
|
16
|
+
- Fully compatible with Vue 3 and the Composition API
|
|
17
|
+
- Works with v-model for two-way binding
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install @loop8/vue-select
|
|
23
|
+
# or
|
|
24
|
+
yarn add @loop8/vue-select
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Importing Styles
|
|
28
|
+
|
|
29
|
+
After installing the package, you need to import the required CSS files in your main entry file (usually `main.js` or `main.ts`):
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
// Import the component styles
|
|
33
|
+
import '@loop8/vue-select/src/styles/loop-select.css';
|
|
34
|
+
import '@loop8/vue-select/src/styles/spinner.css';
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The styles are modular and can be customized by overriding the CSS classes. For complete styling documentation and available CSS classes, see the [Usage Guide](USAGE.md#customizing-appearance).
|
|
38
|
+
|
|
39
|
+
## Styling and Customization
|
|
40
|
+
|
|
41
|
+
Loop8Select is designed to be easily customized:
|
|
42
|
+
|
|
43
|
+
- Uses global, non-scoped CSS classes that are simple to override
|
|
44
|
+
- Provides slots for custom option and selection templates
|
|
45
|
+
- Supports custom loading indicators
|
|
46
|
+
- Can be themed for light/dark modes or to match your application design by overriding CSS classes
|
|
47
|
+
|
|
48
|
+
```vue
|
|
49
|
+
<!-- Basic usage -->
|
|
50
|
+
<Loop8Select
|
|
51
|
+
v-model="selected"
|
|
52
|
+
:options="options"
|
|
53
|
+
/>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
For complete styling documentation and available CSS classes, see the [Usage Guide](USAGE.md#customizing-appearance).
|
|
57
|
+
|
|
58
|
+
## Usage
|
|
59
|
+
|
|
60
|
+
### Global Registration
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
import { createApp } from 'vue';
|
|
64
|
+
import Loop8Select from '@loop8/vue-select';
|
|
65
|
+
import App from './App.vue';
|
|
66
|
+
|
|
67
|
+
const app = createApp(App);
|
|
68
|
+
app.use(Loop8Select);
|
|
69
|
+
app.mount('#app');
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Local Registration
|
|
73
|
+
|
|
74
|
+
```vue
|
|
75
|
+
<template>
|
|
76
|
+
<div>
|
|
77
|
+
<Loop8Select
|
|
78
|
+
v-model="selected"
|
|
79
|
+
:options="options"
|
|
80
|
+
:multiple="false"
|
|
81
|
+
name="my-select"
|
|
82
|
+
placeholder="Select an option"
|
|
83
|
+
/>
|
|
84
|
+
</div>
|
|
85
|
+
</template>
|
|
86
|
+
|
|
87
|
+
<script>
|
|
88
|
+
import { ref } from 'vue';
|
|
89
|
+
import { Loop8Select } from '@loop8/vue-select';
|
|
90
|
+
|
|
91
|
+
export default {
|
|
92
|
+
components: {
|
|
93
|
+
Loop8Select
|
|
94
|
+
},
|
|
95
|
+
setup() {
|
|
96
|
+
const selected = ref(null);
|
|
97
|
+
const options = ref([
|
|
98
|
+
{ value: 1, text: 'Option 1' },
|
|
99
|
+
{ value: 2, text: 'Option 2' },
|
|
100
|
+
{ value: 3, text: 'Option 3' }
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
selected,
|
|
105
|
+
options
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
</script>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### With AJAX Data Loading
|
|
113
|
+
|
|
114
|
+
```vue
|
|
115
|
+
<template>
|
|
116
|
+
<Loop8Select
|
|
117
|
+
v-model="selected"
|
|
118
|
+
:options="[]"
|
|
119
|
+
:ajax="ajaxConfig"
|
|
120
|
+
valueKey="id"
|
|
121
|
+
labelKey="name"
|
|
122
|
+
name="ajax-select"
|
|
123
|
+
placeholder="Search..."
|
|
124
|
+
/>
|
|
125
|
+
</template>
|
|
126
|
+
|
|
127
|
+
<script>
|
|
128
|
+
import { ref } from 'vue';
|
|
129
|
+
import { Loop8Select, Loop8SelectAjaxConfig } from '@loop8/vue-select';
|
|
130
|
+
|
|
131
|
+
export default {
|
|
132
|
+
components: { Loop8Select },
|
|
133
|
+
setup() {
|
|
134
|
+
const selected = ref(null);
|
|
135
|
+
|
|
136
|
+
const ajaxConfig = {
|
|
137
|
+
url: 'https://api.example.com/search',
|
|
138
|
+
method: 'GET',
|
|
139
|
+
minimumInputLength: 2,
|
|
140
|
+
processResults: (data) => {
|
|
141
|
+
return {
|
|
142
|
+
results: data.items,
|
|
143
|
+
more: false
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
selected,
|
|
150
|
+
ajaxConfig
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
</script>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### With Infinite Scrolling Pagination
|
|
158
|
+
|
|
159
|
+
```vue
|
|
160
|
+
<template>
|
|
161
|
+
<Loop8Select
|
|
162
|
+
v-model="selected"
|
|
163
|
+
:options="[]"
|
|
164
|
+
:ajax="paginationConfig"
|
|
165
|
+
valueKey="id"
|
|
166
|
+
labelKey="name"
|
|
167
|
+
name="paginated-select"
|
|
168
|
+
placeholder="Search with infinite scrolling..."
|
|
169
|
+
/>
|
|
170
|
+
</template>
|
|
171
|
+
|
|
172
|
+
<script>
|
|
173
|
+
import { ref } from 'vue';
|
|
174
|
+
import { Loop8Select } from '@loop8/vue-select';
|
|
175
|
+
|
|
176
|
+
export default {
|
|
177
|
+
components: { Loop8Select },
|
|
178
|
+
setup() {
|
|
179
|
+
const selected = ref(null);
|
|
180
|
+
|
|
181
|
+
const paginationConfig = {
|
|
182
|
+
url: 'https://api.example.com/search',
|
|
183
|
+
pagination: true,
|
|
184
|
+
limit: 20,
|
|
185
|
+
processResults: (data) => {
|
|
186
|
+
return {
|
|
187
|
+
results: data.items,
|
|
188
|
+
more: data.has_more // true if there are more results
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
return { selected, paginationConfig };
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
</script>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### With Custom Templates
|
|
200
|
+
|
|
201
|
+
```vue
|
|
202
|
+
<template>
|
|
203
|
+
<Loop8Select v-model="selectedCountry" :options="countries" valueKey="code" labelKey="name">
|
|
204
|
+
<!-- Custom option template -->
|
|
205
|
+
<template #option="{ option, selected, highlighted }">
|
|
206
|
+
<div class="country-option" :class="{ selected, highlighted }">
|
|
207
|
+
<span class="flag">{{ option.flag }}</span>
|
|
208
|
+
<span class="name">{{ option.name }}</span>
|
|
209
|
+
<span class="region">{{ option.region }}</span>
|
|
210
|
+
</div>
|
|
211
|
+
</template>
|
|
212
|
+
|
|
213
|
+
<!-- Custom selected option template -->
|
|
214
|
+
<template #selected-option="{ option }">
|
|
215
|
+
<div class="selected-country">
|
|
216
|
+
<span class="flag">{{ option.flag }}</span>
|
|
217
|
+
<span class="name">{{ option.name }}</span>
|
|
218
|
+
</div>
|
|
219
|
+
</template>
|
|
220
|
+
</Loop8Select>
|
|
221
|
+
</template>
|
|
222
|
+
|
|
223
|
+
<script>
|
|
224
|
+
import { ref } from 'vue';
|
|
225
|
+
import { Loop8Select } from '@loop8/vue-select';
|
|
226
|
+
|
|
227
|
+
export default {
|
|
228
|
+
components: { Loop8Select },
|
|
229
|
+
setup() {
|
|
230
|
+
const selectedCountry = ref(null);
|
|
231
|
+
const countries = ref([
|
|
232
|
+
{ code: 'FI', name: 'Finland', flag: '🇫🇮', region: 'Europe' },
|
|
233
|
+
{ code: 'US', name: 'United States', flag: '🇺🇸', region: 'North America' },
|
|
234
|
+
{ code: 'DE', name: 'Germany', flag: '🇩🇪', region: 'Europe' },
|
|
235
|
+
{ code: 'JP', name: 'Japan', flag: '🇯🇵', region: 'Asia' }
|
|
236
|
+
]);
|
|
237
|
+
|
|
238
|
+
return { selectedCountry, countries };
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
</script>
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### With Array Values
|
|
245
|
+
|
|
246
|
+
```vue
|
|
247
|
+
<template>
|
|
248
|
+
<Loop8Select
|
|
249
|
+
v-model="selected"
|
|
250
|
+
:options="['Option 1', 'Option 2', 'Option 3']"
|
|
251
|
+
name="simple-select"
|
|
252
|
+
/>
|
|
253
|
+
</template>
|
|
254
|
+
|
|
255
|
+
<script>
|
|
256
|
+
import { ref } from 'vue';
|
|
257
|
+
import { Loop8Select } from '@loop8/vue-select';
|
|
258
|
+
|
|
259
|
+
export default {
|
|
260
|
+
components: { Loop8Select },
|
|
261
|
+
setup() {
|
|
262
|
+
const selected = ref(null);
|
|
263
|
+
return { selected };
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
</script>
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Multiple Select
|
|
270
|
+
|
|
271
|
+
```vue
|
|
272
|
+
<template>
|
|
273
|
+
<Loop8Select
|
|
274
|
+
v-model="selectedItems"
|
|
275
|
+
:options="options"
|
|
276
|
+
:multiple="true"
|
|
277
|
+
name="multi-select"
|
|
278
|
+
/>
|
|
279
|
+
</template>
|
|
280
|
+
|
|
281
|
+
<script>
|
|
282
|
+
import { ref } from 'vue';
|
|
283
|
+
import { Loop8Select } from '@loop8/vue-select';
|
|
284
|
+
|
|
285
|
+
export default {
|
|
286
|
+
components: { Loop8Select },
|
|
287
|
+
setup() {
|
|
288
|
+
const selectedItems = ref([]);
|
|
289
|
+
const options = ref([
|
|
290
|
+
{ value: 1, text: 'Option 1' },
|
|
291
|
+
{ value: 2, text: 'Option 2' },
|
|
292
|
+
{ value: 3, text: 'Option 3' }
|
|
293
|
+
]);
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
selectedItems,
|
|
297
|
+
options
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
</script>
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## Props
|
|
305
|
+
|
|
306
|
+
| Prop | Type | Default | Description |
|
|
307
|
+
|------|------|---------|-------------|
|
|
308
|
+
| modelValue | Any | null | The selected value(s), used with v-model |
|
|
309
|
+
| options | Array | [] | Array of options to select from (objects or primitive values) |
|
|
310
|
+
| placeholder | String | 'Select an option' | Placeholder text to display when no option is selected |
|
|
311
|
+
| multiple | Boolean | false | Allow multiple selections |
|
|
312
|
+
| disabled | Boolean | false | Disable the select component |
|
|
313
|
+
| required | Boolean | false | Mark the select as required for form validation |
|
|
314
|
+
| name | String | '' | Name attribute for the hidden select element (for form submission) |
|
|
315
|
+
| loading | Boolean | false | Show loading state |
|
|
316
|
+
| valueKey | String | 'value' | Key to use for option values when using object options |
|
|
317
|
+
| labelKey | String | 'text' | Key to use for option labels when using object options |
|
|
318
|
+
| ajax | Object | null | Configuration for AJAX data loading and initial value resolution |
|
|
319
|
+
|
|
320
|
+
## Slots
|
|
321
|
+
|
|
322
|
+
| Slot | Scope | Description |
|
|
323
|
+
|------|-------|-------------|
|
|
324
|
+
| option | { option, selected, highlighted, index } | Template for rendering dropdown options |
|
|
325
|
+
| selected-option | { option, remove } | Template for rendering selected items |
|
|
326
|
+
| loading-more | none | Template for the "loading more" indicator |
|
|
327
|
+
|
|
328
|
+
## Events
|
|
329
|
+
|
|
330
|
+
| Event | Description |
|
|
331
|
+
|-------|-------------|
|
|
332
|
+
| update:modelValue | Emitted when the selection changes, provides the selected value(s) |
|
|
333
|
+
| update:selectedOptions | Emitted when the selection changes, provides the full selected option object(s) including labels and other data |
|
|
334
|
+
| invalid-option | Emitted when an invalid option is detected. For single select, provides the invalid value. For multiple select, provides an array of invalid values. The component automatically removes invalid values from the selection. |
|
|
335
|
+
|
|
336
|
+
### Example: Using selectedOptions
|
|
337
|
+
|
|
338
|
+
```vue
|
|
339
|
+
<template>
|
|
340
|
+
<Loop8Select
|
|
341
|
+
v-model="selectedValue"
|
|
342
|
+
@update:selectedOptions="handleSelectedOptions"
|
|
343
|
+
:options="options"
|
|
344
|
+
/>
|
|
345
|
+
<div v-if="selectedOption">
|
|
346
|
+
Selected: {{ selectedOption.text }}
|
|
347
|
+
</div>
|
|
348
|
+
</template>
|
|
349
|
+
|
|
350
|
+
<script>
|
|
351
|
+
import { ref } from 'vue';
|
|
352
|
+
import { Loop8Select } from '@loop8/vue-select';
|
|
353
|
+
|
|
354
|
+
export default {
|
|
355
|
+
components: { Loop8Select },
|
|
356
|
+
setup() {
|
|
357
|
+
const selectedValue = ref(null);
|
|
358
|
+
const selectedOption = ref(null);
|
|
359
|
+
|
|
360
|
+
const options = ref([
|
|
361
|
+
{ value: 1, text: 'Option 1' },
|
|
362
|
+
{ value: 2, text: 'Option 2' },
|
|
363
|
+
{ value: 3, text: 'Option 3' }
|
|
364
|
+
]);
|
|
365
|
+
|
|
366
|
+
const handleSelectedOptions = (option) => {
|
|
367
|
+
selectedOption.value = option;
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
return {
|
|
371
|
+
selectedValue,
|
|
372
|
+
selectedOption,
|
|
373
|
+
options,
|
|
374
|
+
handleSelectedOptions
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
</script>
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
## License
|
|
382
|
+
|
|
383
|
+
MIT
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { PropType } from 'vue';
|
|
2
|
+
import { Loop8SelectOption, Loop8SelectAjaxConfig } from '../types';
|
|
3
|
+
declare const _sfc_main: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
|
|
4
|
+
modelValue: {
|
|
5
|
+
type: PropType<any>;
|
|
6
|
+
default: null;
|
|
7
|
+
};
|
|
8
|
+
options: {
|
|
9
|
+
type: PropType<Loop8SelectOption[]>;
|
|
10
|
+
default: () => never[];
|
|
11
|
+
};
|
|
12
|
+
placeholder: {
|
|
13
|
+
type: StringConstructor;
|
|
14
|
+
default: string;
|
|
15
|
+
};
|
|
16
|
+
multiple: {
|
|
17
|
+
type: BooleanConstructor;
|
|
18
|
+
default: boolean;
|
|
19
|
+
};
|
|
20
|
+
disabled: {
|
|
21
|
+
type: BooleanConstructor;
|
|
22
|
+
default: boolean;
|
|
23
|
+
};
|
|
24
|
+
required: {
|
|
25
|
+
type: BooleanConstructor;
|
|
26
|
+
default: boolean;
|
|
27
|
+
};
|
|
28
|
+
name: {
|
|
29
|
+
type: StringConstructor;
|
|
30
|
+
default: string;
|
|
31
|
+
};
|
|
32
|
+
loading: {
|
|
33
|
+
type: BooleanConstructor;
|
|
34
|
+
default: boolean;
|
|
35
|
+
};
|
|
36
|
+
valueKey: {
|
|
37
|
+
type: StringConstructor;
|
|
38
|
+
default: string;
|
|
39
|
+
};
|
|
40
|
+
labelKey: {
|
|
41
|
+
type: StringConstructor;
|
|
42
|
+
default: string;
|
|
43
|
+
};
|
|
44
|
+
ajax: {
|
|
45
|
+
type: PropType<Loop8SelectAjaxConfig>;
|
|
46
|
+
default: null;
|
|
47
|
+
};
|
|
48
|
+
}>, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("update:modelValue" | "invalid-option" | "update:selectedOptions")[], "update:modelValue" | "invalid-option" | "update:selectedOptions", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
49
|
+
modelValue: {
|
|
50
|
+
type: PropType<any>;
|
|
51
|
+
default: null;
|
|
52
|
+
};
|
|
53
|
+
options: {
|
|
54
|
+
type: PropType<Loop8SelectOption[]>;
|
|
55
|
+
default: () => never[];
|
|
56
|
+
};
|
|
57
|
+
placeholder: {
|
|
58
|
+
type: StringConstructor;
|
|
59
|
+
default: string;
|
|
60
|
+
};
|
|
61
|
+
multiple: {
|
|
62
|
+
type: BooleanConstructor;
|
|
63
|
+
default: boolean;
|
|
64
|
+
};
|
|
65
|
+
disabled: {
|
|
66
|
+
type: BooleanConstructor;
|
|
67
|
+
default: boolean;
|
|
68
|
+
};
|
|
69
|
+
required: {
|
|
70
|
+
type: BooleanConstructor;
|
|
71
|
+
default: boolean;
|
|
72
|
+
};
|
|
73
|
+
name: {
|
|
74
|
+
type: StringConstructor;
|
|
75
|
+
default: string;
|
|
76
|
+
};
|
|
77
|
+
loading: {
|
|
78
|
+
type: BooleanConstructor;
|
|
79
|
+
default: boolean;
|
|
80
|
+
};
|
|
81
|
+
valueKey: {
|
|
82
|
+
type: StringConstructor;
|
|
83
|
+
default: string;
|
|
84
|
+
};
|
|
85
|
+
labelKey: {
|
|
86
|
+
type: StringConstructor;
|
|
87
|
+
default: string;
|
|
88
|
+
};
|
|
89
|
+
ajax: {
|
|
90
|
+
type: PropType<Loop8SelectAjaxConfig>;
|
|
91
|
+
default: null;
|
|
92
|
+
};
|
|
93
|
+
}>> & Readonly<{
|
|
94
|
+
"onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
|
|
95
|
+
"onInvalid-option"?: ((...args: any[]) => any) | undefined;
|
|
96
|
+
"onUpdate:selectedOptions"?: ((...args: any[]) => any) | undefined;
|
|
97
|
+
}>, {
|
|
98
|
+
modelValue: any;
|
|
99
|
+
options: Loop8SelectOption[];
|
|
100
|
+
placeholder: string;
|
|
101
|
+
multiple: boolean;
|
|
102
|
+
disabled: boolean;
|
|
103
|
+
required: boolean;
|
|
104
|
+
name: string;
|
|
105
|
+
loading: boolean;
|
|
106
|
+
valueKey: string;
|
|
107
|
+
labelKey: string;
|
|
108
|
+
ajax: Loop8SelectAjaxConfig;
|
|
109
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
110
|
+
export default _sfc_main;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { App } from 'vue';
|
|
2
|
+
import Loop8Select from './components/Loop8Select.vue';
|
|
3
|
+
import type { Loop8SelectOption, Loop8SelectProps, Loop8SelectAjaxConfig, PaginationInfo } from './types';
|
|
4
|
+
export { Loop8Select, Loop8SelectOption, Loop8SelectProps, Loop8SelectAjaxConfig, PaginationInfo };
|
|
5
|
+
declare const _default: {
|
|
6
|
+
install: (app: App) => void;
|
|
7
|
+
};
|
|
8
|
+
export default _default;
|