slim-select 3.1.2 → 3.1.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.
- package/dist/react/index.js +3 -5
- package/dist/react/index.umd.js +1 -1
- package/dist/slimselect.cjs.js +1 -1
- package/dist/slimselect.cjs.js.map +1 -1
- package/dist/slimselect.es.js +3 -5
- package/dist/slimselect.es.js.map +1 -1
- package/dist/slimselect.iife.js +1 -1
- package/dist/slimselect.iife.js.map +1 -1
- package/dist/slimselect.js +1 -1
- package/dist/slimselect.js.map +1 -1
- package/dist/vue/index.js +27 -29
- package/dist/vue/index.umd.js +1 -1
- package/package.json +1 -1
- package/src/slim-select/index.test.ts +167 -0
- package/src/slim-select/index.ts +3 -2
- package/src/slim-select/render.ts +11 -0
- package/src/slim-select/select.ts +12 -7
|
@@ -840,6 +840,173 @@ describe('SlimSelect Module', () => {
|
|
|
840
840
|
label.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
841
841
|
}).not.toThrow()
|
|
842
842
|
})
|
|
843
|
+
|
|
844
|
+
test('clicking main div closes SlimSelect when wrapped in label', async () => {
|
|
845
|
+
document.body.innerHTML = `
|
|
846
|
+
<label>
|
|
847
|
+
Select a Country
|
|
848
|
+
<select id="wrapped-label-close-test">
|
|
849
|
+
<option value="usa">United States</option>
|
|
850
|
+
<option value="uk">United Kingdom</option>
|
|
851
|
+
</select>
|
|
852
|
+
</label>
|
|
853
|
+
`
|
|
854
|
+
|
|
855
|
+
const slim = new SlimSelect({
|
|
856
|
+
select: '#wrapped-label-close-test',
|
|
857
|
+
settings: {
|
|
858
|
+
allowDeselect: true
|
|
859
|
+
}
|
|
860
|
+
})
|
|
861
|
+
|
|
862
|
+
// Initially closed
|
|
863
|
+
expect(slim.settings.isOpen).toBe(false)
|
|
864
|
+
|
|
865
|
+
// Click the label to open
|
|
866
|
+
const label = document.querySelector('label') as HTMLLabelElement
|
|
867
|
+
label.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
|
|
868
|
+
await new Promise((r) => setTimeout(r, 300)) // after animation
|
|
869
|
+
expect(slim.settings.isOpen).toBe(true)
|
|
870
|
+
|
|
871
|
+
// Now click the main div to close
|
|
872
|
+
const mainDiv = document.querySelector(`.ss-main[data-id="${slim.settings.id}"]`) as HTMLElement
|
|
873
|
+
expect(mainDiv).toBeTruthy()
|
|
874
|
+
|
|
875
|
+
mainDiv.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
|
|
876
|
+
await new Promise((r) => setTimeout(r, 300)) // after animation
|
|
877
|
+
|
|
878
|
+
// SlimSelect should be closed
|
|
879
|
+
expect(slim.settings.isOpen).toBe(false)
|
|
880
|
+
|
|
881
|
+
slim.destroy()
|
|
882
|
+
})
|
|
883
|
+
|
|
884
|
+
test('deselect button works when wrapped in label', async () => {
|
|
885
|
+
document.body.innerHTML = `
|
|
886
|
+
<label>
|
|
887
|
+
Select a Country
|
|
888
|
+
<select id="wrapped-label-deselect-test">
|
|
889
|
+
<option value="usa">United States</option>
|
|
890
|
+
<option value="uk">United Kingdom</option>
|
|
891
|
+
</select>
|
|
892
|
+
</label>
|
|
893
|
+
`
|
|
894
|
+
|
|
895
|
+
const slim = new SlimSelect({
|
|
896
|
+
select: '#wrapped-label-deselect-test',
|
|
897
|
+
settings: {
|
|
898
|
+
allowDeselect: true
|
|
899
|
+
}
|
|
900
|
+
})
|
|
901
|
+
|
|
902
|
+
// Select an option
|
|
903
|
+
slim.setSelected('usa')
|
|
904
|
+
await new Promise((r) => setTimeout(r, 10))
|
|
905
|
+
expect(slim.getSelected()).toEqual(['usa'])
|
|
906
|
+
|
|
907
|
+
// Click the deselect button
|
|
908
|
+
const deselectButton = document.querySelector(`[data-id="${slim.settings.id}"] .ss-deselect`) as HTMLElement
|
|
909
|
+
expect(deselectButton).toBeTruthy()
|
|
910
|
+
|
|
911
|
+
deselectButton.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
|
|
912
|
+
await new Promise((r) => setTimeout(r, 10))
|
|
913
|
+
|
|
914
|
+
// Should be deselected (returns array with empty string for single select)
|
|
915
|
+
expect(slim.getSelected()).toEqual([''])
|
|
916
|
+
|
|
917
|
+
slim.destroy()
|
|
918
|
+
})
|
|
919
|
+
|
|
920
|
+
test('clicking main div toggles when wrapped in label', async () => {
|
|
921
|
+
document.body.innerHTML = `
|
|
922
|
+
<label>
|
|
923
|
+
Select a Country
|
|
924
|
+
<select id="wrapped-label-toggle-test">
|
|
925
|
+
<option value="usa">United States</option>
|
|
926
|
+
<option value="uk">United Kingdom</option>
|
|
927
|
+
</select>
|
|
928
|
+
</label>
|
|
929
|
+
`
|
|
930
|
+
|
|
931
|
+
const slim = new SlimSelect({
|
|
932
|
+
select: '#wrapped-label-toggle-test'
|
|
933
|
+
})
|
|
934
|
+
|
|
935
|
+
// Initially closed
|
|
936
|
+
expect(slim.settings.isOpen).toBe(false)
|
|
937
|
+
|
|
938
|
+
// Click main div to open
|
|
939
|
+
const mainDiv = document.querySelector(`.ss-main[data-id="${slim.settings.id}"]`) as HTMLElement
|
|
940
|
+
mainDiv.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
|
|
941
|
+
await new Promise((r) => setTimeout(r, 10))
|
|
942
|
+
expect(slim.settings.isOpen).toBe(true)
|
|
943
|
+
|
|
944
|
+
// Click main div again to close
|
|
945
|
+
mainDiv.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
|
|
946
|
+
await new Promise((r) => setTimeout(r, 10))
|
|
947
|
+
expect(slim.settings.isOpen).toBe(false)
|
|
948
|
+
|
|
949
|
+
// Click main div again to open
|
|
950
|
+
mainDiv.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
|
|
951
|
+
await new Promise((r) => setTimeout(r, 10))
|
|
952
|
+
expect(slim.settings.isOpen).toBe(true)
|
|
953
|
+
|
|
954
|
+
slim.destroy()
|
|
955
|
+
})
|
|
956
|
+
|
|
957
|
+
test('clicking label on second select closes first select when both have labels', async () => {
|
|
958
|
+
document.body.innerHTML = `
|
|
959
|
+
<label>
|
|
960
|
+
First Select
|
|
961
|
+
<select id="first-select">
|
|
962
|
+
<option value="1">Option 1</option>
|
|
963
|
+
<option value="2">Option 2</option>
|
|
964
|
+
</select>
|
|
965
|
+
</label>
|
|
966
|
+
<label>
|
|
967
|
+
Second Select
|
|
968
|
+
<select id="second-select">
|
|
969
|
+
<option value="a">Option A</option>
|
|
970
|
+
<option value="b">Option B</option>
|
|
971
|
+
</select>
|
|
972
|
+
</label>
|
|
973
|
+
`
|
|
974
|
+
|
|
975
|
+
const slim1 = new SlimSelect({
|
|
976
|
+
select: '#first-select'
|
|
977
|
+
})
|
|
978
|
+
|
|
979
|
+
const slim2 = new SlimSelect({
|
|
980
|
+
select: '#second-select'
|
|
981
|
+
})
|
|
982
|
+
|
|
983
|
+
// Both should be closed initially
|
|
984
|
+
expect(slim1.settings.isOpen).toBe(false)
|
|
985
|
+
expect(slim2.settings.isOpen).toBe(false)
|
|
986
|
+
|
|
987
|
+
// Click first label to open first select
|
|
988
|
+
const label1 = document.querySelector('label:first-of-type') as HTMLLabelElement
|
|
989
|
+
label1.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
|
|
990
|
+
await new Promise((r) => setTimeout(r, 300))
|
|
991
|
+
expect(slim1.settings.isOpen).toBe(true)
|
|
992
|
+
expect(slim2.settings.isOpen).toBe(false)
|
|
993
|
+
|
|
994
|
+
// Click second label - should close first and open second
|
|
995
|
+
const label2 = document.querySelector('label:last-of-type') as HTMLLabelElement
|
|
996
|
+
label2.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
|
|
997
|
+
await new Promise((r) => setTimeout(r, 300))
|
|
998
|
+
expect(slim1.settings.isOpen).toBe(false)
|
|
999
|
+
expect(slim2.settings.isOpen).toBe(true)
|
|
1000
|
+
|
|
1001
|
+
// Click first label again - should close second and open first
|
|
1002
|
+
label1.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
|
|
1003
|
+
await new Promise((r) => setTimeout(r, 300))
|
|
1004
|
+
expect(slim1.settings.isOpen).toBe(true)
|
|
1005
|
+
expect(slim2.settings.isOpen).toBe(false)
|
|
1006
|
+
|
|
1007
|
+
slim1.destroy()
|
|
1008
|
+
slim2.destroy()
|
|
1009
|
+
})
|
|
843
1010
|
})
|
|
844
1011
|
|
|
845
1012
|
describe('option changes race condition scenarios', () => {
|
package/src/slim-select/index.ts
CHANGED
|
@@ -151,10 +151,11 @@ export default class SlimSelect {
|
|
|
151
151
|
// and onOptionsChange wouldn't be called directly
|
|
152
152
|
this.setData(data || [])
|
|
153
153
|
}
|
|
154
|
-
// Set up label click handler to
|
|
154
|
+
// Set up label click handler to toggle SlimSelect
|
|
155
|
+
// This allows clicking the label to open/close, matching the main div behavior
|
|
155
156
|
this.select.onLabelClick = () => {
|
|
156
157
|
if (!this.settings.disabled) {
|
|
157
|
-
this.open()
|
|
158
|
+
this.settings.isOpen ? this.close() : this.open()
|
|
158
159
|
}
|
|
159
160
|
}
|
|
160
161
|
|
|
@@ -75,6 +75,17 @@ export default class Render {
|
|
|
75
75
|
this.updateClassStyles()
|
|
76
76
|
this.updateAriaAttributes()
|
|
77
77
|
|
|
78
|
+
// Position content off-screen initially to prevent scrollbars
|
|
79
|
+
// This must be after updateClassStyles() since it removes all style attributes
|
|
80
|
+
// The main element isn't in the DOM yet, so getBoundingClientRect() would return zeros
|
|
81
|
+
// Once opened, moveContent() will position it correctly
|
|
82
|
+
if (this.settings.contentPosition !== 'relative') {
|
|
83
|
+
this.content.main.style.top = '-9999px'
|
|
84
|
+
this.content.main.style.left = '-9999px'
|
|
85
|
+
this.content.main.style.margin = '0'
|
|
86
|
+
this.content.main.style.width = 'auto'
|
|
87
|
+
}
|
|
88
|
+
|
|
78
89
|
// Add content to the content location settings
|
|
79
90
|
if (this.settings.contentLocation) {
|
|
80
91
|
this.settings.contentLocation.appendChild(this.content.main)
|
|
@@ -556,17 +556,22 @@ export default class Select {
|
|
|
556
556
|
|
|
557
557
|
// Add click handler to label
|
|
558
558
|
const labelClickHandler = (e: MouseEvent) => {
|
|
559
|
+
const target = e.target as HTMLElement
|
|
560
|
+
|
|
561
|
+
// Only continue if the target is the label
|
|
562
|
+
if (target !== label) {
|
|
563
|
+
return
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// At this point, we know the click is directly on the label element
|
|
559
567
|
// Prevent default label behavior (focusing the select)
|
|
568
|
+
// Don't stop propagation to allow other handlers (like closing other SlimSelect instances)
|
|
560
569
|
e.preventDefault()
|
|
561
|
-
e.stopPropagation()
|
|
570
|
+
// e.stopPropagation() // dont stop propagation
|
|
562
571
|
|
|
563
|
-
// Trigger the callback to
|
|
572
|
+
// Trigger the callback to toggle SlimSelect
|
|
564
573
|
if (this.onLabelClick) {
|
|
565
|
-
|
|
566
|
-
// are prevented by our prevent handlers
|
|
567
|
-
setTimeout(() => {
|
|
568
|
-
this.onLabelClick!()
|
|
569
|
-
}, 0)
|
|
574
|
+
this.onLabelClick!()
|
|
570
575
|
}
|
|
571
576
|
}
|
|
572
577
|
|