slim-select 3.1.0 → 3.1.1

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.
@@ -59,32 +59,15 @@ export default defineComponent({
59
59
  config.events = {
60
60
  ...this.events,
61
61
  afterChange: (newVal: Option[]) => {
62
- const value = this.multiple ? newVal.map((option) => option.value) : newVal.length > 0 ? newVal[0].value : ''
63
-
64
- // Update v-model if value changed
65
- if (this.value !== value) {
66
- this.value = value
67
- }
68
-
69
- // Call the original afterChange callback if it exists
70
- if (ogAfterChange) {
71
- ogAfterChange(newVal)
72
- }
62
+ this.handleAfterChange(newVal, ogAfterChange)
73
63
  }
74
64
  }
75
65
 
76
66
  // Initialize SlimSelect
77
67
  this.slim = new SlimSelect(config)
78
68
 
79
- // Get SlimSelect selected values
80
- let selected = this.$props.multiple ? this.slim.getSelected() : this.slim.getSelected()[0]
81
-
82
- // Check if modelValue is the same as the value of the select
83
- if (this.value !== selected) {
84
- // If not, set the value of the select to the modelValue
85
- // Don't trigger afterChange on initial sync
86
- this.slim.setSelected(this.value, false)
87
- }
69
+ // Sync initial modelValue to SlimSelect
70
+ this.syncModelValueToSlimSelect(false)
88
71
  },
89
72
  updated() {
90
73
  // After DOM updates (like slot content changes), sync selection from modelValue
@@ -95,7 +78,8 @@ export default defineComponent({
95
78
  const needsSync = JSON.stringify(currentSelected.sort()) !== JSON.stringify(modelValues.sort())
96
79
 
97
80
  if (needsSync) {
98
- this.slim.setSelected(this.value, false)
81
+ // Use the same sync method to handle invalid values properly
82
+ this.syncModelValueToSlimSelect(false)
99
83
  }
100
84
  }
101
85
  },
@@ -108,10 +92,8 @@ export default defineComponent({
108
92
  modelValue: {
109
93
  handler: function (newVal: string | string[] | undefined) {
110
94
  if (!this.slim) return
111
-
112
- // Set the value of the select to the newVal
113
- // Don't trigger afterChange when programmatically updating from parent
114
- this.slim.setSelected(this.getCleanValue(newVal), false)
95
+ // Sync modelValue to SlimSelect (don't trigger afterChange when programmatically updating from parent)
96
+ this.syncModelValueToSlimSelect(false)
115
97
  }
116
98
  },
117
99
  data: {
@@ -138,6 +120,43 @@ export default defineComponent({
138
120
  getSlimSelect() {
139
121
  return this.slim
140
122
  },
123
+ handleAfterChange(newVal: Option[], ogAfterChange?: (newVal: Option[]) => void): void {
124
+ if (!this.slim) return
125
+
126
+ const value = this.multiple ? newVal.map((option) => option.value) : newVal.length > 0 ? newVal[0].value : ''
127
+
128
+ // Check if the current v-model value exists in options
129
+ // If it doesn't exist, don't update v-model (preserve invalid value like regular HTML select)
130
+ const currentValue = this.getCleanValue(this.$props.modelValue)
131
+ const options = this.slim.store.getDataOptions()
132
+ const currentValueExists = Array.isArray(currentValue)
133
+ ? currentValue.length > 0 && currentValue.every((val) => options.some((opt) => opt.value === val))
134
+ : currentValue !== '' && options.some((opt) => opt.value === currentValue)
135
+
136
+ // Check if the new value is valid (exists in options)
137
+ const newValueIsValid = Array.isArray(value)
138
+ ? value.length > 0 && value.every((val) => options.some((opt) => opt.value === val))
139
+ : value !== '' && options.some((opt) => opt.value === value)
140
+
141
+ // Check if value actually changed (properly compare arrays)
142
+ const valueChanged =
143
+ Array.isArray(value) && Array.isArray(this.value)
144
+ ? JSON.stringify(value.sort()) !== JSON.stringify(this.value.sort())
145
+ : this.value !== value
146
+
147
+ // Only update v-model if:
148
+ // 1. The value actually changed, AND
149
+ // 2. Either the current v-model value exists in options, OR we're setting to a valid non-empty value
150
+ // This prevents clearing invalid v-model values when we show placeholder
151
+ if (valueChanged && (currentValueExists || newValueIsValid)) {
152
+ this.value = value
153
+ }
154
+
155
+ // Call the original afterChange callback if it exists
156
+ if (ogAfterChange) {
157
+ ogAfterChange(newVal)
158
+ }
159
+ },
141
160
  getCleanValue(val: string | string[] | undefined): string | string[] {
142
161
  const multi = this.$props.multiple
143
162
 
@@ -152,6 +171,47 @@ export default defineComponent({
152
171
  }
153
172
 
154
173
  return multi ? [] : ''
174
+ },
175
+ syncModelValueToSlimSelect(runAfterChange: boolean = false): void {
176
+ if (!this.slim) return
177
+
178
+ // Only sync if modelValue is explicitly set (not undefined)
179
+ // This prevents adding placeholders when modelValue is not provided
180
+ if (this.$props.modelValue === undefined) {
181
+ return
182
+ }
183
+
184
+ const cleanValue = this.getCleanValue(this.$props.modelValue)
185
+ const data = this.slim.getData()
186
+ // Extract options from data (flatten optgroups if any)
187
+ const options = data.flatMap((item: Option | Optgroup) => ('label' in item ? item.options : [item])) as Option[]
188
+
189
+ // Check if the value exists in options
190
+ const valueExists = Array.isArray(cleanValue)
191
+ ? cleanValue.length > 0 && cleanValue.every((val) => options.some((opt) => opt.value === val))
192
+ : cleanValue !== '' && options.some((opt) => opt.value === cleanValue)
193
+
194
+ // If value doesn't exist in options and it's not empty, add a placeholder option
195
+ if (!valueExists) {
196
+ // If its not multiple, add a placeholder option
197
+ if (!Array.isArray(cleanValue)) {
198
+ // For single select, check if placeholder already exists
199
+ const hasPlaceholder = options.some((opt) => opt.placeholder)
200
+ if (!hasPlaceholder) {
201
+ // Get current data and prepend placeholder option
202
+ const currentData = this.slim.getData()
203
+ const placeholderOption: Partial<Option> = {
204
+ value: '',
205
+ text: '',
206
+ placeholder: true
207
+ }
208
+ this.slim.setData([placeholderOption].concat(currentData))
209
+ }
210
+ }
211
+ }
212
+
213
+ // Set the selected value (will select placeholder if it was just added)
214
+ this.slim.setSelected(cleanValue, runAfterChange)
155
215
  }
156
216
  }
157
217
  })
package/vitest.config.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import { defineConfig } from 'vitest/config'
2
2
  import vue from '@vitejs/plugin-vue'
3
+ import react from '@vitejs/plugin-react'
3
4
  import path from 'path'
4
5
 
5
6
  export default defineConfig({
6
- plugins: [vue()],
7
+ plugins: [vue(), react()],
7
8
  resolve: {
8
9
  alias: {
9
10
  '@': path.resolve(__dirname, 'src')
@@ -12,7 +13,7 @@ export default defineConfig({
12
13
  test: {
13
14
  globals: true,
14
15
  environment: 'jsdom',
15
- include: ['src/**/*.test.ts'],
16
+ include: ['src/**/*.test.{ts,tsx}'],
16
17
  coverage: {
17
18
  provider: 'v8',
18
19
  reporter: ['text', 'json', 'html', 'lcov'],