design-system-next 2.23.1 → 2.26.0
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.d.ts +170 -110
- package/dist/design-system-next.es.js +10268 -9387
- package/dist/design-system-next.es.js.gz +0 -0
- package/dist/design-system-next.umd.js +12 -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/package.json +3 -2
- package/src/components/date-picker/date-picker.ts +9 -12
- package/src/components/date-picker/date-picker.vue +1 -1
- package/src/components/date-picker/date-range-picker/date-range-picker.ts +9 -12
- package/src/components/date-picker/date-range-picker/use-date-range-picker.ts +64 -49
- package/src/components/date-picker/month-year-picker/month-year-picker.ts +134 -0
- package/src/components/date-picker/month-year-picker/month-year-picker.vue +233 -0
- package/src/components/date-picker/month-year-picker/use-month-year-picker.ts +603 -0
- package/src/components/date-picker/use-date-picker.ts +88 -147
- package/src/components/dropdown/dropdown.ts +6 -2
- package/src/components/dropdown/dropdown.vue +8 -1
- package/src/components/dropdown/use-dropdown.ts +13 -0
- package/src/components/list/list.ts +5 -0
- package/src/components/list/list.vue +2 -0
- package/src/components/list/use-list.ts +36 -3
- package/src/components/select/select-multiple/use-select-multiple.ts +7 -3
- package/src/components/select/use-select.ts +9 -5
- package/src/components/sidepanel/sidepanel.ts +11 -0
- package/src/components/sidepanel/sidepanel.vue +26 -4
- package/src/components/sidepanel/use-sidepanel.ts +7 -2
- package/src/components/table/table-header-dropdown/table-header-dropdown.ts +48 -0
- package/src/components/table/table-header-dropdown/table-header-dropdown.vue +88 -0
- package/src/components/table/table-pagination/table-pagination.ts +4 -0
- package/src/components/table/table-pagination/table-pagination.vue +9 -1
- package/src/components/table/table-pagination/use-table-pagination.ts +4 -3
- package/src/components/table/table.ts +9 -1
- package/src/components/table/table.vue +31 -2
- package/src/components/table/use-table.ts +6 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ref, toRefs, computed, ComputedRef, SetupContext, onMounted, watch, nextTick } from 'vue';
|
|
2
|
-
import { useVModel, onClickOutside
|
|
2
|
+
import { useVModel, onClickOutside } from '@vueuse/core';
|
|
3
3
|
|
|
4
4
|
import dayjs from 'dayjs';
|
|
5
5
|
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore';
|
|
@@ -143,8 +143,6 @@ export const useDatePicker = (props: DatePickerPropTypes, emit: SetupContext<Dat
|
|
|
143
143
|
const dateInput = ref<string>('');
|
|
144
144
|
const yearInput = ref<string>('');
|
|
145
145
|
|
|
146
|
-
const datePickerErrors = ref<{ title: string; message: string }[]>([]);
|
|
147
|
-
|
|
148
146
|
// #region - Calendar Tab
|
|
149
147
|
const calendarTabPageData = ref({
|
|
150
148
|
selectedMonth: dayjs().month(),
|
|
@@ -295,12 +293,18 @@ export const useDatePicker = (props: DatePickerPropTypes, emit: SetupContext<Dat
|
|
|
295
293
|
}
|
|
296
294
|
|
|
297
295
|
// Check if date and month are selected, but not year
|
|
298
|
-
if (dateInput.value && monthInput && !yearInput.value && !calendarTabIsDateIsDisabled(day)) {
|
|
296
|
+
if (dateInput.value && monthInput.value && !yearInput.value && !calendarTabIsDateIsDisabled(day)) {
|
|
299
297
|
return day.date.getDate() === Number(dateInput.value) && day.date.getMonth() === monthValue && !day.inactive;
|
|
300
298
|
}
|
|
301
299
|
|
|
302
300
|
// Check if date, month, and year are selected
|
|
303
|
-
if (
|
|
301
|
+
if (
|
|
302
|
+
dateInput.value &&
|
|
303
|
+
monthInput.value &&
|
|
304
|
+
yearInput.value &&
|
|
305
|
+
monthValue !== undefined &&
|
|
306
|
+
!calendarTabIsDateIsDisabled(day)
|
|
307
|
+
) {
|
|
304
308
|
return (
|
|
305
309
|
day.date.getDate() === Number(dateInput.value) &&
|
|
306
310
|
day.date.getMonth() === monthValue &&
|
|
@@ -471,10 +475,6 @@ export const useDatePicker = (props: DatePickerPropTypes, emit: SetupContext<Dat
|
|
|
471
475
|
emitDateFormats();
|
|
472
476
|
emitInputValue();
|
|
473
477
|
|
|
474
|
-
datePickerErrors.value = [];
|
|
475
|
-
|
|
476
|
-
emit('getDateErrors', datePickerErrors.value);
|
|
477
|
-
|
|
478
478
|
setTimeout(() => {
|
|
479
479
|
datePopperState.value = false;
|
|
480
480
|
}, 100);
|
|
@@ -494,11 +494,7 @@ export const useDatePicker = (props: DatePickerPropTypes, emit: SetupContext<Dat
|
|
|
494
494
|
handleConvertMonthIfValid();
|
|
495
495
|
calendarTabUpdateCalendar();
|
|
496
496
|
emitDateFormats();
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
datePickerErrors.value = [];
|
|
500
|
-
|
|
501
|
-
emit('getDateErrors', datePickerErrors.value);
|
|
497
|
+
emitInputValue();
|
|
502
498
|
};
|
|
503
499
|
// #endregion - Month Tab
|
|
504
500
|
|
|
@@ -564,11 +560,7 @@ export const useDatePicker = (props: DatePickerPropTypes, emit: SetupContext<Dat
|
|
|
564
560
|
handleConvertMonthIfValid();
|
|
565
561
|
calendarTabUpdateCalendar();
|
|
566
562
|
emitDateFormats();
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
datePickerErrors.value = [];
|
|
570
|
-
|
|
571
|
-
emit('getDateErrors', datePickerErrors.value);
|
|
563
|
+
emitInputValue();
|
|
572
564
|
};
|
|
573
565
|
// #endregion - Year Tab
|
|
574
566
|
|
|
@@ -640,8 +632,6 @@ export const useDatePicker = (props: DatePickerPropTypes, emit: SetupContext<Dat
|
|
|
640
632
|
const date = formattedDate.format('DD');
|
|
641
633
|
const year = formattedDate.format('YYYY');
|
|
642
634
|
|
|
643
|
-
handleValidateDate();
|
|
644
|
-
|
|
645
635
|
monthInput.value = month;
|
|
646
636
|
dateInput.value = date;
|
|
647
637
|
yearInput.value = year;
|
|
@@ -655,13 +645,7 @@ export const useDatePicker = (props: DatePickerPropTypes, emit: SetupContext<Dat
|
|
|
655
645
|
handleConvertMonthIfValid();
|
|
656
646
|
calendarTabUpdateCalendar();
|
|
657
647
|
emitDateFormats();
|
|
658
|
-
|
|
659
|
-
// Use the specified format for the input value
|
|
660
|
-
if (!monthInput.value && !dateInput.value && !yearInput.value) {
|
|
661
|
-
emit('getInputValue', null);
|
|
662
|
-
} else {
|
|
663
|
-
emit('getInputValue', formattedDate.format(format.value));
|
|
664
|
-
}
|
|
648
|
+
emitInputValue();
|
|
665
649
|
} else {
|
|
666
650
|
console.error(`Error: Could not parse date "${modelValue.value}" with format "${format.value}"`);
|
|
667
651
|
}
|
|
@@ -686,48 +670,71 @@ export const useDatePicker = (props: DatePickerPropTypes, emit: SetupContext<Dat
|
|
|
686
670
|
};
|
|
687
671
|
|
|
688
672
|
const handleMonthInput = () => {
|
|
689
|
-
datePopperState.value = false;
|
|
690
|
-
|
|
691
673
|
monthInput.value = monthInput.value.replace(/[^A-Za-z0-9\s]/g, '').toLocaleUpperCase();
|
|
692
674
|
|
|
693
|
-
datePickerErrors.value = [];
|
|
694
|
-
|
|
695
|
-
emit('getDateErrors', datePickerErrors.value);
|
|
696
|
-
|
|
697
675
|
handleConvertMonthIfValid();
|
|
698
676
|
|
|
699
|
-
|
|
677
|
+
// Update calendar if month, date, and year are all provided
|
|
678
|
+
if (monthInput.value && dateInput.value && yearInput.value) {
|
|
679
|
+
const monthValue = getMonthObject('text', monthInput.value)?.monthValue;
|
|
680
|
+
const yearNumber = Number(yearInput.value);
|
|
681
|
+
|
|
682
|
+
if (monthValue !== undefined && yearNumber >= minMaxYear.value.min && yearNumber <= minMaxYear.value.max) {
|
|
683
|
+
calendarTabPageData.value.selectedMonth = monthValue;
|
|
684
|
+
calendarTabPageData.value.selectedYear = yearNumber;
|
|
685
|
+
calendarTabUpdateCalendar();
|
|
686
|
+
}
|
|
687
|
+
} else if (!monthInput.value && !dateInput.value && !yearInput.value) {
|
|
688
|
+
// Clear modelValue when all inputs are empty
|
|
689
|
+
modelValue.value = '';
|
|
690
|
+
}
|
|
700
691
|
|
|
701
692
|
// Emit the partial date value as user types
|
|
702
|
-
|
|
693
|
+
emitInputValue();
|
|
703
694
|
};
|
|
704
695
|
|
|
705
696
|
const handleDateInput = () => {
|
|
706
|
-
datePopperState.value = false;
|
|
707
|
-
|
|
708
697
|
dateInput.value = dateInput.value.replace(/[^0-9]/g, '');
|
|
709
698
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
699
|
+
// Update calendar if month, date, and year are all provided
|
|
700
|
+
if (monthInput.value && dateInput.value && yearInput.value) {
|
|
701
|
+
const monthValue = getMonthObject('text', monthInput.value)?.monthValue;
|
|
702
|
+
const yearNumber = Number(yearInput.value);
|
|
713
703
|
|
|
714
|
-
|
|
704
|
+
if (monthValue !== undefined && yearNumber >= minMaxYear.value.min && yearNumber <= minMaxYear.value.max) {
|
|
705
|
+
calendarTabPageData.value.selectedMonth = monthValue;
|
|
706
|
+
calendarTabPageData.value.selectedYear = yearNumber;
|
|
707
|
+
calendarTabUpdateCalendar();
|
|
708
|
+
}
|
|
709
|
+
} else if (!monthInput.value && !dateInput.value && !yearInput.value) {
|
|
710
|
+
// Clear modelValue when all inputs are empty
|
|
711
|
+
modelValue.value = '';
|
|
712
|
+
}
|
|
715
713
|
|
|
716
714
|
// Emit the partial date value as user types
|
|
717
|
-
|
|
715
|
+
emitInputValue();
|
|
718
716
|
};
|
|
719
717
|
|
|
720
718
|
const handleYearInput = () => {
|
|
721
|
-
datePopperState.value = false;
|
|
722
|
-
|
|
723
719
|
yearInput.value = yearInput.value.replace(/[^0-9]/g, '');
|
|
724
720
|
|
|
725
|
-
|
|
721
|
+
// Update calendar if month, date, and year are all provided
|
|
722
|
+
if (monthInput.value && dateInput.value && yearInput.value) {
|
|
723
|
+
const monthValue = getMonthObject('text', monthInput.value)?.monthValue;
|
|
724
|
+
const yearNumber = Number(yearInput.value);
|
|
726
725
|
|
|
727
|
-
|
|
726
|
+
if (monthValue !== undefined && yearNumber >= minMaxYear.value.min && yearNumber <= minMaxYear.value.max) {
|
|
727
|
+
calendarTabPageData.value.selectedMonth = monthValue;
|
|
728
|
+
calendarTabPageData.value.selectedYear = yearNumber;
|
|
729
|
+
calendarTabUpdateCalendar();
|
|
730
|
+
}
|
|
731
|
+
} else if (!monthInput.value && !dateInput.value && !yearInput.value) {
|
|
732
|
+
// Clear modelValue when all inputs are empty
|
|
733
|
+
modelValue.value = '';
|
|
734
|
+
}
|
|
728
735
|
|
|
729
736
|
// Emit the partial date value as user types
|
|
730
|
-
|
|
737
|
+
emitInputValue();
|
|
731
738
|
};
|
|
732
739
|
|
|
733
740
|
const handleConvertMonthIfValid = () => {
|
|
@@ -755,51 +762,6 @@ export const useDatePicker = (props: DatePickerPropTypes, emit: SetupContext<Dat
|
|
|
755
762
|
}
|
|
756
763
|
};
|
|
757
764
|
|
|
758
|
-
const handleValidateDate = useDebounceFn(() => {
|
|
759
|
-
if (monthInput.value && dateInput.value && yearInput.value) {
|
|
760
|
-
const selectedDate = `${monthInput.value}-${dateInput.value}-${yearInput.value}`;
|
|
761
|
-
|
|
762
|
-
const isDateValid = dayjs(selectedDate, 'MM-DD-YYYY').isValid();
|
|
763
|
-
const isYearValid =
|
|
764
|
-
Number(yearInput.value) >= minMaxYear.value.min && Number(yearInput.value) <= minMaxYear.value.max;
|
|
765
|
-
|
|
766
|
-
if (isDateValid && isYearValid) {
|
|
767
|
-
datePickerErrors.value = datePickerErrors.value.filter((error) => error.title !== 'Invalid Date');
|
|
768
|
-
|
|
769
|
-
const monthValue = getMonthObject('text', monthInput.value)?.monthValue;
|
|
770
|
-
|
|
771
|
-
calendarTabPageData.value.selectedMonth = Number(monthValue);
|
|
772
|
-
calendarTabPageData.value.selectedYear = Number(yearInput.value);
|
|
773
|
-
|
|
774
|
-
calendarTabUpdateCalendar();
|
|
775
|
-
emitDateFormats();
|
|
776
|
-
} else {
|
|
777
|
-
const errorExists = datePickerErrors.value.some((error) => error.title === 'Invalid Date');
|
|
778
|
-
|
|
779
|
-
if (!errorExists) {
|
|
780
|
-
let errorMessage;
|
|
781
|
-
|
|
782
|
-
if (!isYearValid) {
|
|
783
|
-
errorMessage = `Year must be between ${minMaxYear.value.min} and ${minMaxYear.value.max}.`;
|
|
784
|
-
} else {
|
|
785
|
-
errorMessage = `Invalid Date Format. Please use ${format.value}`;
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
datePickerErrors.value.push({
|
|
789
|
-
title: 'Invalid Date',
|
|
790
|
-
message: errorMessage,
|
|
791
|
-
});
|
|
792
|
-
|
|
793
|
-
datePopperState.value = false;
|
|
794
|
-
|
|
795
|
-
emit('getDateErrors', datePickerErrors.value);
|
|
796
|
-
|
|
797
|
-
console.error(`Invalid Date: "${selectedDate}". ${errorMessage}`);
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
}, 500);
|
|
802
|
-
|
|
803
765
|
const handleTabClick = (tab: string) => {
|
|
804
766
|
if (currentTab.value === tab) {
|
|
805
767
|
currentTab.value = 'tab-calendar';
|
|
@@ -824,34 +786,28 @@ export const useDatePicker = (props: DatePickerPropTypes, emit: SetupContext<Dat
|
|
|
824
786
|
}
|
|
825
787
|
};
|
|
826
788
|
|
|
827
|
-
const
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
const isNumeric = !isNaN(Number(monthInput.value)) && !isNaN(parseFloat(monthInput.value));
|
|
833
|
-
|
|
834
|
-
if (!isNumeric) {
|
|
835
|
-
const monthIsValid = monthsList.value.find(
|
|
836
|
-
(_month: MonthsList) => _month.text.toLowerCase() === monthInput.value.toLowerCase(),
|
|
837
|
-
);
|
|
789
|
+
const emitInputValue = () => {
|
|
790
|
+
if (monthInput.value || dateInput.value || yearInput.value) {
|
|
791
|
+
emit('getInputValue', `${monthInput.value}-${dateInput.value}-${yearInput.value}`);
|
|
792
|
+
} else {
|
|
793
|
+
emit('getInputValue', null);
|
|
838
794
|
|
|
839
|
-
|
|
840
|
-
emittedMonth =
|
|
841
|
-
monthIsValid.monthValue < 10 ? `0${monthIsValid.monthValue + 1}` : `${monthIsValid.monthValue + 1}`;
|
|
842
|
-
}
|
|
843
|
-
}
|
|
795
|
+
modelValue.value = '';
|
|
844
796
|
}
|
|
845
797
|
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
798
|
+
if (monthInput.value && dateInput.value && yearInput.value) {
|
|
799
|
+
const monthIsValid = monthsList.value.find(
|
|
800
|
+
(_month: MonthsList) => _month.text.toLowerCase() === monthInput.value.toLowerCase(),
|
|
801
|
+
);
|
|
850
802
|
|
|
851
|
-
|
|
803
|
+
const yearIsValid = yearTabPageData.value.yearsArray.find((_year) => _year === Number(yearInput.value));
|
|
852
804
|
|
|
853
|
-
|
|
854
|
-
|
|
805
|
+
if (monthIsValid && yearIsValid) {
|
|
806
|
+
const _date = dayjs(`${monthInput.value}-${dateInput.value}-${yearInput.value}`, 'MM-DD-YYYY');
|
|
807
|
+
|
|
808
|
+
modelValue.value = _date.format(format.value);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
855
811
|
};
|
|
856
812
|
|
|
857
813
|
const emitDateFormats = () => {
|
|
@@ -904,28 +860,6 @@ export const useDatePicker = (props: DatePickerPropTypes, emit: SetupContext<Dat
|
|
|
904
860
|
}
|
|
905
861
|
};
|
|
906
862
|
|
|
907
|
-
const emitInputValue = () => {
|
|
908
|
-
let emittedMonth = monthInput.value;
|
|
909
|
-
|
|
910
|
-
const isNumeric = !isNaN(Number(monthInput.value)) && !isNaN(parseFloat(monthInput.value));
|
|
911
|
-
|
|
912
|
-
if (!isNumeric) {
|
|
913
|
-
const monthIsValid = monthsList.value.find(
|
|
914
|
-
(_month: MonthsList) => _month.text.toLowerCase() === monthInput.value.toLowerCase(),
|
|
915
|
-
);
|
|
916
|
-
|
|
917
|
-
if (monthIsValid) {
|
|
918
|
-
emittedMonth =
|
|
919
|
-
monthIsValid.monthValue < 10 ? `0${monthIsValid.monthValue + 1}` : `${monthIsValid.monthValue + 1}`;
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
// Format the date according to the format prop
|
|
923
|
-
const dateObj = dayjs(`${emittedMonth}-${dateInput.value}-${yearInput.value}`, 'MM-DD-YYYY');
|
|
924
|
-
|
|
925
|
-
// Use the specified format for the input value
|
|
926
|
-
emit('getInputValue', (modelValue.value = dateObj.format(format.value)));
|
|
927
|
-
};
|
|
928
|
-
|
|
929
863
|
const emitMonthList = () => {
|
|
930
864
|
emit('getMonthList', monthsList.value);
|
|
931
865
|
};
|
|
@@ -939,6 +873,9 @@ export const useDatePicker = (props: DatePickerPropTypes, emit: SetupContext<Dat
|
|
|
939
873
|
monthInput.value = '';
|
|
940
874
|
dateInput.value = '';
|
|
941
875
|
yearInput.value = '';
|
|
876
|
+
modelValue.value = '';
|
|
877
|
+
|
|
878
|
+
emitInputValue();
|
|
942
879
|
};
|
|
943
880
|
|
|
944
881
|
const handleSlotClick = () => {
|
|
@@ -972,14 +909,19 @@ export const useDatePicker = (props: DatePickerPropTypes, emit: SetupContext<Dat
|
|
|
972
909
|
}
|
|
973
910
|
});
|
|
974
911
|
|
|
975
|
-
watch(
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
912
|
+
watch(
|
|
913
|
+
minMaxYear,
|
|
914
|
+
() => {
|
|
915
|
+
yearTabPageData.value.yearsArray = Array.from(
|
|
916
|
+
{ length: minMaxYear.value.max - minMaxYear.value.min + 1 },
|
|
917
|
+
(_, index) => minMaxYear.value.min + index,
|
|
918
|
+
).filter((year) => year <= minMaxYear.value.max && year >= minMaxYear.value.min);
|
|
919
|
+
|
|
920
|
+
yearTabPageData.value.currentPage = 0;
|
|
921
|
+
emitYearList();
|
|
922
|
+
},
|
|
923
|
+
{ deep: true },
|
|
924
|
+
);
|
|
983
925
|
|
|
984
926
|
onClickOutside(datePickerRef, () => {
|
|
985
927
|
datePopperState.value = false;
|
|
@@ -1014,7 +956,6 @@ export const useDatePicker = (props: DatePickerPropTypes, emit: SetupContext<Dat
|
|
|
1014
956
|
dateInput,
|
|
1015
957
|
monthInput,
|
|
1016
958
|
yearInput,
|
|
1017
|
-
datePickerErrors,
|
|
1018
959
|
calendarTabPageData,
|
|
1019
960
|
calendarTabIsMinMonth,
|
|
1020
961
|
calendarTabIsMaxMonth,
|
|
@@ -141,13 +141,17 @@ export const dropdownPropTypes = {
|
|
|
141
141
|
type: Boolean,
|
|
142
142
|
default: false,
|
|
143
143
|
},
|
|
144
|
+
noPadding: {
|
|
145
|
+
type: Boolean,
|
|
146
|
+
default: false,
|
|
147
|
+
}
|
|
144
148
|
};
|
|
145
149
|
|
|
146
150
|
export const dropdownEmitTypes = {
|
|
147
|
-
'infinite-scroll-trigger':
|
|
151
|
+
'infinite-scroll-trigger': (value: boolean) => typeof value === 'boolean',
|
|
148
152
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
149
153
|
'update:modelValue': (_value: unknown) => true, // Accept any type of value
|
|
150
|
-
'popper-state':
|
|
154
|
+
'popper-state': (state: boolean) => typeof state === 'boolean',
|
|
151
155
|
};
|
|
152
156
|
|
|
153
157
|
export type DropdownPropTypes = ExtractPropTypes<typeof dropdownPropTypes>;
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
<template #popper>
|
|
33
33
|
<template v-if="$slots.popper">
|
|
34
34
|
<div
|
|
35
|
-
class="spr-overflow-y-auto spr-overflow-x-hidden spr-p-4"
|
|
35
|
+
:class="['spr-overflow-y-auto spr-overflow-x-hidden', !props.noPadding && 'spr-p-4']"
|
|
36
36
|
:style="{
|
|
37
37
|
width: props.popperInnerWidth,
|
|
38
38
|
}"
|
|
@@ -111,5 +111,12 @@ const {
|
|
|
111
111
|
dropdownValue,
|
|
112
112
|
removeCurrentLevelInBackLabel,
|
|
113
113
|
isLadderizedSearch,
|
|
114
|
+
showDropdown,
|
|
115
|
+
hideDropdown,
|
|
114
116
|
} = useDropdown(props, emit);
|
|
117
|
+
|
|
118
|
+
defineExpose({
|
|
119
|
+
showDropdown,
|
|
120
|
+
hideDropdown,
|
|
121
|
+
});
|
|
115
122
|
</script>
|
|
@@ -82,6 +82,17 @@ export const useDropdown = (props: DropdownPropTypes, emit: SetupContext<Dropdow
|
|
|
82
82
|
const dropdownPopperState = ref<boolean>(false);
|
|
83
83
|
const isDropdownPopperDisabled = computed(() => disabled.value);
|
|
84
84
|
|
|
85
|
+
// Exposed methods to show/hide dropdown. This is for custom trigger handling for custom dropdown.
|
|
86
|
+
// To use these methods, set :triggers="[]" on the SprDropdown component to disable default triggers. (reference: https://floating-vue.starpad.dev/api/#shown)
|
|
87
|
+
/* #region - Exposed Methods */
|
|
88
|
+
const showDropdown = () => {
|
|
89
|
+
dropdownPopperState.value = true;
|
|
90
|
+
};
|
|
91
|
+
const hideDropdown = () => {
|
|
92
|
+
dropdownPopperState.value = false;
|
|
93
|
+
};
|
|
94
|
+
/* #endregion - Exposed Methods */
|
|
95
|
+
|
|
85
96
|
const isLadderizedSearch = computed(
|
|
86
97
|
() => ladderized.value && searchString.value !== '' && normalizedValue.value.length === 0,
|
|
87
98
|
);
|
|
@@ -503,5 +514,7 @@ export const useDropdown = (props: DropdownPropTypes, emit: SetupContext<Dropdow
|
|
|
503
514
|
dropdownValue: compatPreSelectedItems, // Use compatible format for lists
|
|
504
515
|
removeCurrentLevelInBackLabel,
|
|
505
516
|
isLadderizedSearch,
|
|
517
|
+
showDropdown,
|
|
518
|
+
hideDropdown,
|
|
506
519
|
};
|
|
507
520
|
};
|
|
@@ -123,12 +123,17 @@ export const listPropTypes = {
|
|
|
123
123
|
type: Boolean,
|
|
124
124
|
default: false,
|
|
125
125
|
},
|
|
126
|
+
allowDeselect: {
|
|
127
|
+
type: Boolean,
|
|
128
|
+
default: false
|
|
129
|
+
}
|
|
126
130
|
};
|
|
127
131
|
|
|
128
132
|
export const listEmitTypes = {
|
|
129
133
|
'update:modelValue': (value: MenuListType[]) => value,
|
|
130
134
|
'update:searchValue': (value: string) => typeof value === 'string',
|
|
131
135
|
'get-single-selected-item': (item: MenuListType) => item,
|
|
136
|
+
'get-single-deselected-item': (item: MenuListType) => item,
|
|
132
137
|
};
|
|
133
138
|
|
|
134
139
|
export type ListPropTypes = ExtractPropTypes<typeof listPropTypes>;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
v-model="searchText"
|
|
9
9
|
:placeholder="props.searchableMenuPlaceholder"
|
|
10
10
|
autocomplete="off"
|
|
11
|
+
@keyup="handleSearchKeyup"
|
|
11
12
|
/>
|
|
12
13
|
<span
|
|
13
14
|
v-if="props.supportingDisplayText || props.displayListItemSelected"
|
|
@@ -121,5 +122,6 @@ const {
|
|
|
121
122
|
isItemSelected,
|
|
122
123
|
getListItemClasses,
|
|
123
124
|
handleSelectedItem,
|
|
125
|
+
handleSearchKeyup,
|
|
124
126
|
} = useList(props, emit);
|
|
125
127
|
</script>
|
|
@@ -22,6 +22,7 @@ export const useList = (props: ListPropTypes, emit: SetupContext<ListEmitTypes>[
|
|
|
22
22
|
noCheck,
|
|
23
23
|
disabledUnselectedItems,
|
|
24
24
|
stickySearchOffset,
|
|
25
|
+
allowDeselect,
|
|
25
26
|
} = toRefs(props);
|
|
26
27
|
|
|
27
28
|
const listClasses: ComputedRef<ListClasses> = computed(() => {
|
|
@@ -518,14 +519,31 @@ export const useList = (props: ListPropTypes, emit: SetupContext<ListEmitTypes>[
|
|
|
518
519
|
// Track the deselection but DON'T add items back to selectedItems when deselecting
|
|
519
520
|
trackNewlySelectedItems(item, true);
|
|
520
521
|
}
|
|
522
|
+
emit('get-single-selected-item', item);
|
|
521
523
|
} else {
|
|
522
524
|
// For single-select, simply replace the selection
|
|
523
|
-
|
|
525
|
+
if (allowDeselect.value) {
|
|
526
|
+
handleDeselect(item);
|
|
527
|
+
} else {
|
|
528
|
+
handleSingleSelect(item);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
};
|
|
524
532
|
|
|
525
|
-
|
|
533
|
+
const handleDeselect = (item: MenuListType) => {
|
|
534
|
+
if (selectedItems.value.length === 0 || !isItemSelected(item)) {
|
|
535
|
+
selectedItems.value = [item];
|
|
536
|
+
emit('get-single-selected-item', item);
|
|
537
|
+
} else {
|
|
538
|
+
selectedItems.value = [];
|
|
539
|
+
emit('get-single-deselected-item', item);
|
|
526
540
|
}
|
|
541
|
+
};
|
|
527
542
|
|
|
528
|
-
|
|
543
|
+
const handleSingleSelect = (item: MenuListType) => {
|
|
544
|
+
selectedItems.value = [item];
|
|
545
|
+
if (item.onClickFn) item.onClickFn();
|
|
546
|
+
emit('get-single-selected-item', item);
|
|
529
547
|
};
|
|
530
548
|
// #endregion - Helper Methods
|
|
531
549
|
|
|
@@ -608,6 +626,20 @@ export const useList = (props: ListPropTypes, emit: SetupContext<ListEmitTypes>[
|
|
|
608
626
|
setPreSelectedItems();
|
|
609
627
|
});
|
|
610
628
|
|
|
629
|
+
// Handle search keyup to ignore modifier-only keys
|
|
630
|
+
const handleSearchKeyup = (event: KeyboardEvent) => {
|
|
631
|
+
// Ignore pure modifier keys: Shift, Control, Alt, Meta (Command/Windows), CapsLock
|
|
632
|
+
const modifierOnlyKeys = ['Shift', 'Control', 'Alt', 'Meta', 'CapsLock'];
|
|
633
|
+
|
|
634
|
+
if (!modifierOnlyKeys.includes(event.key)) {
|
|
635
|
+
// Allow the search to proceed - v-model will handle the actual update
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// For modifier-only keys, prevent default behavior if needed
|
|
640
|
+
event.preventDefault();
|
|
641
|
+
};
|
|
642
|
+
|
|
611
643
|
return {
|
|
612
644
|
listClasses,
|
|
613
645
|
stickyOffsetStyle,
|
|
@@ -623,5 +655,6 @@ export const useList = (props: ListPropTypes, emit: SetupContext<ListEmitTypes>[
|
|
|
623
655
|
handleSearch,
|
|
624
656
|
handleSelectedItem,
|
|
625
657
|
trackNewlySelectedItems,
|
|
658
|
+
handleSearchKeyup,
|
|
626
659
|
};
|
|
627
660
|
};
|
|
@@ -376,10 +376,14 @@ export const useMultiSelect = (props: MultiSelectPropTypes, emit: SetupContext<M
|
|
|
376
376
|
{ deep: true },
|
|
377
377
|
);
|
|
378
378
|
|
|
379
|
-
watch(searchInput, () => {
|
|
380
|
-
search.value =
|
|
379
|
+
watch(searchInput, (newVal, oldVal) => {
|
|
380
|
+
search.value = newVal;
|
|
381
381
|
|
|
382
|
-
emit
|
|
382
|
+
// Only emit search-string if value actually changed (not just modifier keys)
|
|
383
|
+
// Modifier key presses alone won't change the input value
|
|
384
|
+
if (newVal !== oldVal) {
|
|
385
|
+
emit('search-string', newVal);
|
|
386
|
+
}
|
|
383
387
|
});
|
|
384
388
|
|
|
385
389
|
watch(multiSelectPopperState, (newState) => {
|
|
@@ -148,11 +148,15 @@ export const useSelect = (props: SelectPropTypes, emit: SetupContext<SelectEmitT
|
|
|
148
148
|
return selectOptions.value.filter((item) => item.text?.toString().toLowerCase().includes(query));
|
|
149
149
|
});
|
|
150
150
|
|
|
151
|
-
// Search handler:
|
|
152
|
-
const handleSearch = () => {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
151
|
+
// Search handler: only emit search-string on regular keys and ENTER, ignore modifier-only keys
|
|
152
|
+
const handleSearch = (event: KeyboardEvent) => {
|
|
153
|
+
// Ignore pure modifier keys: Shift, Control, Alt, Meta (Command/Windows), CapsLock
|
|
154
|
+
const modifierOnlyKeys = ['Shift', 'Control', 'Alt', 'Meta', 'CapsLock'];
|
|
155
|
+
|
|
156
|
+
if (!modifierOnlyKeys.includes(event.key)) {
|
|
157
|
+
isSearching.value = true;
|
|
158
|
+
debouncedEmitSearch();
|
|
159
|
+
}
|
|
156
160
|
};
|
|
157
161
|
|
|
158
162
|
const debouncedEmitSearch = useDebounceFn(() => {
|
|
@@ -20,6 +20,9 @@ export const sidepanelPropTypes = {
|
|
|
20
20
|
type: String,
|
|
21
21
|
default: 'Sidepanel Header',
|
|
22
22
|
},
|
|
23
|
+
headerSubtitle: {
|
|
24
|
+
type: String
|
|
25
|
+
},
|
|
23
26
|
/**
|
|
24
27
|
* @description Specifies the size of the side panel.
|
|
25
28
|
* Acceptable values are: `'sm'`, `'md'`, `'lg'`, `'xl'`.
|
|
@@ -104,6 +107,14 @@ export const sidepanelPropTypes = {
|
|
|
104
107
|
isActivePanel: {
|
|
105
108
|
type: Boolean,
|
|
106
109
|
default: false
|
|
110
|
+
},
|
|
111
|
+
footerNoTopBorder: {
|
|
112
|
+
type: Boolean,
|
|
113
|
+
default: false,
|
|
114
|
+
},
|
|
115
|
+
isLoading: {
|
|
116
|
+
type: Boolean,
|
|
117
|
+
default: false,
|
|
107
118
|
}
|
|
108
119
|
};
|
|
109
120
|
|
|
@@ -17,21 +17,43 @@
|
|
|
17
17
|
aria-labelledby="sidepanel-title"
|
|
18
18
|
aria-describedby="sidepanel-content"
|
|
19
19
|
:class="sidepanelClasses.sidepanelBaseClasses"
|
|
20
|
+
data-testid="sidepanel-dialog"
|
|
20
21
|
:style="{ height: typeof height === 'number' ? `${height}px` : height }"
|
|
21
22
|
>
|
|
22
23
|
<template v-if="!props.hideHeader">
|
|
23
24
|
<div v-if="!$slots.header" :class="sidepanelClasses.sidepanelHeaderClasses">
|
|
24
|
-
<div
|
|
25
|
-
|
|
25
|
+
<div v-if="!isLoading" id="headers">
|
|
26
|
+
<div id="sidepanel-title" :class="sidepanelClasses.sidepanelHeaderTitleClasses">
|
|
27
|
+
{{ headerTitle }}
|
|
28
|
+
</div>
|
|
29
|
+
<div id="sidepanel-subtitle" :class="sidepanelClasses.sidepanelHeaderSubtitleClasses">
|
|
30
|
+
<slot name="subtitle">
|
|
31
|
+
<span class="spr-text-color-base">{{ headerSubtitle }}</span>
|
|
32
|
+
</slot>
|
|
33
|
+
</div>
|
|
26
34
|
</div>
|
|
27
|
-
<div class="spr-flex spr-
|
|
35
|
+
<div v-else id="header-loaders" class="spr-w-full spr-flex spr-flex-col spr-gap-size-spacing-4xs">
|
|
36
|
+
<div class="spr-skeletal-loader spr-h-4 spr-w-[90%] spr-rounded-md"></div>
|
|
37
|
+
<div
|
|
38
|
+
v-if="headerSubtitle || $slots.subtitle"
|
|
39
|
+
class="spr-skeletal-loader spr-h-8 spr-w-[95%] spr-rounded-md"
|
|
40
|
+
></div>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div class="spr-flex spr-items-center spr-gap-size-spacing-3xs">
|
|
28
44
|
<Icon
|
|
29
45
|
v-if="props.isExpandable"
|
|
30
46
|
:class="sidepanelClasses.sidepanelHeaderIconClasses"
|
|
31
47
|
:icon="isExpanded ? 'ph:arrows-in-simple' : 'ph:arrows-out-simple'"
|
|
48
|
+
data-testid="expand-icon"
|
|
32
49
|
@click="handlePanelExpansion"
|
|
33
50
|
/>
|
|
34
|
-
<Icon
|
|
51
|
+
<Icon
|
|
52
|
+
:class="sidepanelClasses.sidepanelHeaderIconClasses"
|
|
53
|
+
icon="ph:x"
|
|
54
|
+
data-testid="x-icon"
|
|
55
|
+
@click="handleClose"
|
|
56
|
+
/>
|
|
35
57
|
</div>
|
|
36
58
|
</div>
|
|
37
59
|
<div v-else>
|