softui-css 1.11.0 → 1.12.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/README.md +1 -1
- package/dist/softui.css +200 -1
- package/dist/softui.js +670 -595
- package/dist/softui.min.css +1 -1
- package/dist/softui.min.js +1 -1
- package/package.json +4 -1
package/dist/softui.js
CHANGED
|
@@ -300,6 +300,9 @@ const SoftUI = (() => {
|
|
|
300
300
|
// Selectable pricing
|
|
301
301
|
initSelectablePricing();
|
|
302
302
|
|
|
303
|
+
// Drawers
|
|
304
|
+
initDrawers();
|
|
305
|
+
|
|
303
306
|
// Data Tables
|
|
304
307
|
initDataTables();
|
|
305
308
|
|
|
@@ -527,16 +530,16 @@ const SoftUI = (() => {
|
|
|
527
530
|
});
|
|
528
531
|
|
|
529
532
|
document.addEventListener('click', function(e) {
|
|
530
|
-
|
|
533
|
+
const trigger = e.target.closest('.sui-collapsible-trigger');
|
|
531
534
|
if (!trigger) return;
|
|
532
535
|
|
|
533
|
-
|
|
536
|
+
const collapsible = trigger.closest('.sui-collapsible');
|
|
534
537
|
if (!collapsible) return;
|
|
535
538
|
|
|
536
|
-
|
|
539
|
+
const content = collapsible.querySelector('.sui-collapsible-content');
|
|
537
540
|
if (!content) return;
|
|
538
541
|
|
|
539
|
-
|
|
542
|
+
const isOpen = collapsible.classList.contains('open');
|
|
540
543
|
|
|
541
544
|
if (isOpen) {
|
|
542
545
|
collapsible.classList.remove('open');
|
|
@@ -714,7 +717,7 @@ const SoftUI = (() => {
|
|
|
714
717
|
// Context Menu
|
|
715
718
|
// =========================================
|
|
716
719
|
function initContextMenu() {
|
|
717
|
-
|
|
720
|
+
let openMenu = null;
|
|
718
721
|
|
|
719
722
|
function closeAll() {
|
|
720
723
|
if (openMenu) {
|
|
@@ -731,9 +734,9 @@ const SoftUI = (() => {
|
|
|
731
734
|
menu.style.top = '0px';
|
|
732
735
|
menu.classList.add('open');
|
|
733
736
|
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
+
const rect = menu.getBoundingClientRect();
|
|
738
|
+
const vw = window.innerWidth;
|
|
739
|
+
const vh = window.innerHeight;
|
|
737
740
|
|
|
738
741
|
if (x + rect.width > vw) x = vw - rect.width - 4;
|
|
739
742
|
if (y + rect.height > vh) y = vh - rect.height - 4;
|
|
@@ -746,21 +749,21 @@ const SoftUI = (() => {
|
|
|
746
749
|
|
|
747
750
|
// Right-click triggers
|
|
748
751
|
document.addEventListener('contextmenu', function(e) {
|
|
749
|
-
|
|
752
|
+
const trigger = e.target.closest('[data-sui-context]');
|
|
750
753
|
if (!trigger) return;
|
|
751
754
|
|
|
752
755
|
e.preventDefault();
|
|
753
756
|
closeAll();
|
|
754
757
|
|
|
755
|
-
|
|
756
|
-
|
|
758
|
+
const menuId = trigger.getAttribute('data-sui-context');
|
|
759
|
+
const menu = document.getElementById(menuId);
|
|
757
760
|
if (!menu) return;
|
|
758
761
|
|
|
759
762
|
positionMenu(menu, e.clientX, e.clientY);
|
|
760
763
|
openMenu = menu;
|
|
761
764
|
|
|
762
765
|
// Focus first item for keyboard nav
|
|
763
|
-
|
|
766
|
+
const firstItem = menu.querySelector('.sui-context-item:not(.disabled), .sui-context-sub-trigger');
|
|
764
767
|
if (firstItem) firstItem.focus();
|
|
765
768
|
});
|
|
766
769
|
|
|
@@ -773,15 +776,15 @@ const SoftUI = (() => {
|
|
|
773
776
|
|
|
774
777
|
// Click on item closes (unless checkbox/radio)
|
|
775
778
|
document.addEventListener('click', function(e) {
|
|
776
|
-
|
|
779
|
+
const item = e.target.closest('.sui-context-item');
|
|
777
780
|
if (!item || !openMenu) return;
|
|
778
781
|
if (!item.closest('.sui-context-menu')) return;
|
|
779
782
|
|
|
780
783
|
// Checkbox toggle
|
|
781
784
|
if (item.hasAttribute('data-sui-context-check')) {
|
|
782
|
-
|
|
785
|
+
const check = item.querySelector('.sui-context-check');
|
|
783
786
|
if (check) {
|
|
784
|
-
|
|
787
|
+
const isChecked = check.textContent.trim() !== '';
|
|
785
788
|
check.textContent = isChecked ? '' : '\u2713';
|
|
786
789
|
}
|
|
787
790
|
return; // Don't close on checkbox click
|
|
@@ -789,11 +792,11 @@ const SoftUI = (() => {
|
|
|
789
792
|
|
|
790
793
|
// Radio toggle
|
|
791
794
|
if (item.hasAttribute('data-sui-context-radio')) {
|
|
792
|
-
|
|
795
|
+
const group = item.getAttribute('data-sui-context-radio');
|
|
793
796
|
openMenu.querySelectorAll('[data-sui-context-radio="' + group + '"] .sui-context-check').forEach(function(c) {
|
|
794
797
|
c.textContent = '';
|
|
795
798
|
});
|
|
796
|
-
|
|
799
|
+
const radio = item.querySelector('.sui-context-check');
|
|
797
800
|
if (radio) radio.textContent = '\u2022';
|
|
798
801
|
return; // Don't close on radio click
|
|
799
802
|
}
|
|
@@ -806,13 +809,13 @@ const SoftUI = (() => {
|
|
|
806
809
|
|
|
807
810
|
// Submenu hover
|
|
808
811
|
document.addEventListener('mouseenter', function(e) {
|
|
809
|
-
|
|
812
|
+
const subTrigger = e.target.closest && e.target.closest('.sui-context-sub-trigger');
|
|
810
813
|
if (!subTrigger) return;
|
|
811
|
-
|
|
814
|
+
const sub = subTrigger.closest('.sui-context-sub');
|
|
812
815
|
if (!sub) return;
|
|
813
816
|
|
|
814
817
|
// Close sibling subs
|
|
815
|
-
|
|
818
|
+
const parent = sub.parentElement;
|
|
816
819
|
if (parent) {
|
|
817
820
|
parent.querySelectorAll(':scope > .sui-context-sub.open').forEach(function(s) {
|
|
818
821
|
if (s !== sub) s.classList.remove('open');
|
|
@@ -822,7 +825,7 @@ const SoftUI = (() => {
|
|
|
822
825
|
}, true);
|
|
823
826
|
|
|
824
827
|
document.addEventListener('mouseleave', function(e) {
|
|
825
|
-
|
|
828
|
+
const sub = e.target.closest && e.target.closest('.sui-context-sub');
|
|
826
829
|
if (!sub) return;
|
|
827
830
|
// Only close if not moving into the sub-content
|
|
828
831
|
setTimeout(function() {
|
|
@@ -836,7 +839,7 @@ const SoftUI = (() => {
|
|
|
836
839
|
document.addEventListener('keydown', function(e) {
|
|
837
840
|
if (e.key === 'Escape' && openMenu) {
|
|
838
841
|
// If a sub is open, close it first
|
|
839
|
-
|
|
842
|
+
const openSub = openMenu.querySelector('.sui-context-sub.open');
|
|
840
843
|
if (openSub) {
|
|
841
844
|
openSub.classList.remove('open');
|
|
842
845
|
openSub.querySelector('.sui-context-sub-trigger').focus();
|
|
@@ -850,11 +853,11 @@ const SoftUI = (() => {
|
|
|
850
853
|
// Arrow key navigation
|
|
851
854
|
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
852
855
|
e.preventDefault();
|
|
853
|
-
|
|
854
|
-
|
|
856
|
+
const activeContainer = openMenu.querySelector('.sui-context-sub.open > .sui-context-sub-content') || openMenu;
|
|
857
|
+
const items = Array.from(activeContainer.querySelectorAll(':scope > .sui-context-item:not(.disabled), :scope > .sui-context-sub > .sui-context-sub-trigger'));
|
|
855
858
|
if (items.length === 0) return;
|
|
856
859
|
|
|
857
|
-
|
|
860
|
+
let current = items.indexOf(document.activeElement);
|
|
858
861
|
if (e.key === 'ArrowDown') {
|
|
859
862
|
current = current < items.length - 1 ? current + 1 : 0;
|
|
860
863
|
} else {
|
|
@@ -865,12 +868,12 @@ const SoftUI = (() => {
|
|
|
865
868
|
|
|
866
869
|
// ArrowRight opens submenu
|
|
867
870
|
if (e.key === 'ArrowRight') {
|
|
868
|
-
|
|
871
|
+
const focused = document.activeElement;
|
|
869
872
|
if (focused && focused.classList.contains('sui-context-sub-trigger')) {
|
|
870
|
-
|
|
873
|
+
const sub = focused.closest('.sui-context-sub');
|
|
871
874
|
if (sub) {
|
|
872
875
|
sub.classList.add('open');
|
|
873
|
-
|
|
876
|
+
const first = sub.querySelector('.sui-context-sub-content .sui-context-item:not(.disabled), .sui-context-sub-content .sui-context-sub-trigger');
|
|
874
877
|
if (first) first.focus();
|
|
875
878
|
}
|
|
876
879
|
}
|
|
@@ -878,7 +881,7 @@ const SoftUI = (() => {
|
|
|
878
881
|
|
|
879
882
|
// ArrowLeft closes submenu
|
|
880
883
|
if (e.key === 'ArrowLeft') {
|
|
881
|
-
|
|
884
|
+
const openSub = document.activeElement && document.activeElement.closest('.sui-context-sub.open');
|
|
882
885
|
if (openSub && openSub.closest('.sui-context-menu') === openMenu) {
|
|
883
886
|
openSub.classList.remove('open');
|
|
884
887
|
openSub.querySelector('.sui-context-sub-trigger').focus();
|
|
@@ -887,7 +890,7 @@ const SoftUI = (() => {
|
|
|
887
890
|
|
|
888
891
|
// Enter activates
|
|
889
892
|
if (e.key === 'Enter') {
|
|
890
|
-
|
|
893
|
+
const focused = document.activeElement;
|
|
891
894
|
if (focused && (focused.classList.contains('sui-context-item') || focused.classList.contains('sui-context-sub-trigger'))) {
|
|
892
895
|
focused.click();
|
|
893
896
|
}
|
|
@@ -904,15 +907,15 @@ const SoftUI = (() => {
|
|
|
904
907
|
// =========================================
|
|
905
908
|
function initCommand() {
|
|
906
909
|
document.querySelectorAll('.sui-command[data-sui-command]').forEach(function(cmd) {
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
+
const input = cmd.querySelector('.sui-command-input');
|
|
911
|
+
const list = cmd.querySelector('.sui-command-list');
|
|
912
|
+
const empty = cmd.querySelector('.sui-command-empty');
|
|
910
913
|
if (!input || !list) return;
|
|
911
914
|
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
915
|
+
const items = list.querySelectorAll('.sui-command-item');
|
|
916
|
+
const groups = list.querySelectorAll('.sui-command-group');
|
|
917
|
+
const separators = list.querySelectorAll('.sui-command-separator');
|
|
918
|
+
let focusedIndex = -1;
|
|
916
919
|
|
|
917
920
|
function getVisibleItems() {
|
|
918
921
|
return Array.from(list.querySelectorAll('.sui-command-item:not([hidden])'));
|
|
@@ -927,29 +930,29 @@ const SoftUI = (() => {
|
|
|
927
930
|
}
|
|
928
931
|
|
|
929
932
|
function filter() {
|
|
930
|
-
|
|
931
|
-
|
|
933
|
+
const query = input.value.toLowerCase().trim();
|
|
934
|
+
let anyVisible = false;
|
|
932
935
|
|
|
933
936
|
items.forEach(function(item) {
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
+
const text = item.textContent.toLowerCase();
|
|
938
|
+
const keywords = (item.getAttribute('data-keywords') || '').toLowerCase();
|
|
939
|
+
const match = !query || text.indexOf(query) !== -1 || keywords.indexOf(query) !== -1;
|
|
937
940
|
item.hidden = !match;
|
|
938
941
|
if (match) anyVisible = true;
|
|
939
942
|
});
|
|
940
943
|
|
|
941
944
|
// Hide groups with no visible items
|
|
942
945
|
groups.forEach(function(group) {
|
|
943
|
-
|
|
946
|
+
const hasVisible = group.querySelector('.sui-command-item:not([hidden])');
|
|
944
947
|
group.hidden = !hasVisible;
|
|
945
948
|
});
|
|
946
949
|
|
|
947
950
|
// Hide separators between hidden groups
|
|
948
951
|
separators.forEach(function(sep) {
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
952
|
+
const next = sep.nextElementSibling;
|
|
953
|
+
const prev = sep.previousElementSibling;
|
|
954
|
+
const nextHidden = next && next.hidden;
|
|
955
|
+
const prevHidden = prev && prev.hidden;
|
|
953
956
|
sep.hidden = nextHidden || prevHidden;
|
|
954
957
|
});
|
|
955
958
|
|
|
@@ -965,7 +968,7 @@ const SoftUI = (() => {
|
|
|
965
968
|
|
|
966
969
|
// Keyboard nav
|
|
967
970
|
cmd.addEventListener('keydown', function(e) {
|
|
968
|
-
|
|
971
|
+
const visibleItems = getVisibleItems();
|
|
969
972
|
|
|
970
973
|
if (e.key === 'ArrowDown') {
|
|
971
974
|
e.preventDefault();
|
|
@@ -990,14 +993,14 @@ const SoftUI = (() => {
|
|
|
990
993
|
// Mouse hover updates focus
|
|
991
994
|
items.forEach(function(item) {
|
|
992
995
|
item.addEventListener('mouseenter', function() {
|
|
993
|
-
|
|
996
|
+
const visibleItems = getVisibleItems();
|
|
994
997
|
focusedIndex = visibleItems.indexOf(item);
|
|
995
998
|
updateFocus(visibleItems);
|
|
996
999
|
});
|
|
997
1000
|
});
|
|
998
1001
|
|
|
999
1002
|
// Initial focus on first item
|
|
1000
|
-
|
|
1003
|
+
const initial = getVisibleItems();
|
|
1001
1004
|
if (initial.length > 0) {
|
|
1002
1005
|
focusedIndex = 0;
|
|
1003
1006
|
updateFocus(initial);
|
|
@@ -1006,8 +1009,8 @@ const SoftUI = (() => {
|
|
|
1006
1009
|
|
|
1007
1010
|
// Dialog mode — Cmd+K / Ctrl+K
|
|
1008
1011
|
document.querySelectorAll('.sui-command-dialog').forEach(function(dialog) {
|
|
1009
|
-
|
|
1010
|
-
|
|
1012
|
+
const cmd = dialog.querySelector('.sui-command');
|
|
1013
|
+
const input = cmd ? cmd.querySelector('.sui-command-input') : null;
|
|
1011
1014
|
|
|
1012
1015
|
function openDialog() {
|
|
1013
1016
|
dialog.classList.add('open');
|
|
@@ -1025,7 +1028,7 @@ const SoftUI = (() => {
|
|
|
1025
1028
|
}
|
|
1026
1029
|
|
|
1027
1030
|
// Cmd+K / Ctrl+K to open
|
|
1028
|
-
|
|
1031
|
+
const shortcut = dialog.dataset.suiCommandKey || 'k';
|
|
1029
1032
|
document.addEventListener('keydown', function(e) {
|
|
1030
1033
|
if ((e.metaKey || e.ctrlKey) && e.key === shortcut) {
|
|
1031
1034
|
e.preventDefault();
|
|
@@ -1065,9 +1068,9 @@ const SoftUI = (() => {
|
|
|
1065
1068
|
// Calendar
|
|
1066
1069
|
// =========================================
|
|
1067
1070
|
function initCalendar() {
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
+
const MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'];
|
|
1072
|
+
const MONTHS_SHORT = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
|
|
1073
|
+
const DAYS = ['Su','Mo','Tu','We','Th','Fr','Sa'];
|
|
1071
1074
|
|
|
1072
1075
|
function daysInMonth(year, month) {
|
|
1073
1076
|
return new Date(year, month + 1, 0).getDate();
|
|
@@ -1079,23 +1082,23 @@ const SoftUI = (() => {
|
|
|
1079
1082
|
|
|
1080
1083
|
function between(d, start, end) {
|
|
1081
1084
|
if (!start || !end) return false;
|
|
1082
|
-
|
|
1085
|
+
const t = d.getTime(), s = Math.min(start.getTime(), end.getTime()), e = Math.max(start.getTime(), end.getTime());
|
|
1083
1086
|
return t > s && t < e;
|
|
1084
1087
|
}
|
|
1085
1088
|
|
|
1086
1089
|
function parseDate(str) {
|
|
1087
1090
|
if (!str) return null;
|
|
1088
1091
|
if (str === 'today') return new Date(new Date().setHours(0,0,0,0));
|
|
1089
|
-
|
|
1092
|
+
const parts = str.split('-');
|
|
1090
1093
|
if (parts.length === 3) return new Date(+parts[0], +parts[1] - 1, +parts[2]);
|
|
1091
1094
|
return null;
|
|
1092
1095
|
}
|
|
1093
1096
|
|
|
1094
1097
|
function formatDate(d, includeTime, hour, minute, period) {
|
|
1095
|
-
|
|
1098
|
+
let str = MONTHS[d.getMonth()].substring(0, 3) + ' ' + d.getDate() + ', ' + d.getFullYear();
|
|
1096
1099
|
if (includeTime) {
|
|
1097
|
-
|
|
1098
|
-
|
|
1100
|
+
const hh = (hour !== undefined && hour !== null) ? hour : 12;
|
|
1101
|
+
const mm = (minute !== undefined && minute !== null) ? minute : 0;
|
|
1099
1102
|
str += ' ' + pad(hh) + ':' + pad(mm);
|
|
1100
1103
|
if (period) str += ' ' + period;
|
|
1101
1104
|
}
|
|
@@ -1103,23 +1106,23 @@ const SoftUI = (() => {
|
|
|
1103
1106
|
}
|
|
1104
1107
|
|
|
1105
1108
|
document.querySelectorAll('.sui-calendar[data-sui-calendar]').forEach(function(cal) {
|
|
1106
|
-
|
|
1107
|
-
|
|
1109
|
+
const mode = cal.dataset.suiCalendar || 'single';
|
|
1110
|
+
const today = new Date();
|
|
1108
1111
|
today.setHours(0,0,0,0);
|
|
1109
1112
|
|
|
1110
|
-
|
|
1111
|
-
|
|
1113
|
+
const minDate = parseDate(cal.dataset.suiMin);
|
|
1114
|
+
const maxDate = parseDate(cal.dataset.suiMax);
|
|
1112
1115
|
|
|
1113
|
-
|
|
1116
|
+
const disabledDays = [];
|
|
1114
1117
|
if (cal.dataset.suiDisabled) {
|
|
1115
1118
|
cal.dataset.suiDisabled.split(',').forEach(function(s) {
|
|
1116
|
-
|
|
1119
|
+
const d = parseDate(s.trim());
|
|
1117
1120
|
if (d) disabledDays.push(d);
|
|
1118
1121
|
});
|
|
1119
1122
|
}
|
|
1120
1123
|
|
|
1121
1124
|
function isDisabled(d) {
|
|
1122
|
-
for (
|
|
1125
|
+
for (let i = 0; i < disabledDays.length; i++) {
|
|
1123
1126
|
if (sameDay(d, disabledDays[i])) return true;
|
|
1124
1127
|
}
|
|
1125
1128
|
if (minDate && d < minDate) return true;
|
|
@@ -1127,18 +1130,18 @@ const SoftUI = (() => {
|
|
|
1127
1130
|
return false;
|
|
1128
1131
|
}
|
|
1129
1132
|
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1133
|
+
let selected = null;
|
|
1134
|
+
let rangeStart = null;
|
|
1135
|
+
let rangeEnd = null;
|
|
1136
|
+
let defaultPlaceholder = '';
|
|
1137
|
+
let viewMode = 'days'; // 'days', 'months', 'years'
|
|
1138
|
+
let yearPageStart = 0;
|
|
1136
1139
|
|
|
1137
1140
|
// Time picker
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1141
|
+
const hasTime = cal.hasAttribute('data-sui-calendar-time');
|
|
1142
|
+
const is24h = cal.getAttribute('data-sui-calendar-time') === '24h';
|
|
1143
|
+
let timeHour = is24h ? 0 : 12, timeMinute = 0, timePeriod = 'AM';
|
|
1144
|
+
let timeRow = null, hourInput = null, minuteInput = null, periodBtn = null;
|
|
1142
1145
|
|
|
1143
1146
|
if (hasTime) {
|
|
1144
1147
|
timeRow = cal.querySelector('.sui-calendar-time');
|
|
@@ -1146,7 +1149,7 @@ const SoftUI = (() => {
|
|
|
1146
1149
|
timeRow = document.createElement('div');
|
|
1147
1150
|
timeRow.className = 'sui-calendar-time';
|
|
1148
1151
|
|
|
1149
|
-
|
|
1152
|
+
const label = document.createElement('span');
|
|
1150
1153
|
label.className = 'sui-calendar-time-label';
|
|
1151
1154
|
label.textContent = 'Time';
|
|
1152
1155
|
timeRow.appendChild(label);
|
|
@@ -1159,7 +1162,7 @@ const SoftUI = (() => {
|
|
|
1159
1162
|
hourInput.setAttribute('aria-label', 'Hour');
|
|
1160
1163
|
timeRow.appendChild(hourInput);
|
|
1161
1164
|
|
|
1162
|
-
|
|
1165
|
+
const sep = document.createElement('span');
|
|
1163
1166
|
sep.className = 'sui-calendar-time-sep';
|
|
1164
1167
|
sep.textContent = ':';
|
|
1165
1168
|
timeRow.appendChild(sep);
|
|
@@ -1181,7 +1184,7 @@ const SoftUI = (() => {
|
|
|
1181
1184
|
}
|
|
1182
1185
|
|
|
1183
1186
|
// Insert before clear button or append
|
|
1184
|
-
|
|
1187
|
+
const clearBtn = cal.querySelector('[data-sui-calendar-clear]');
|
|
1185
1188
|
if (clearBtn) {
|
|
1186
1189
|
cal.insertBefore(timeRow, clearBtn);
|
|
1187
1190
|
} else {
|
|
@@ -1193,11 +1196,11 @@ const SoftUI = (() => {
|
|
|
1193
1196
|
periodBtn = timeRow.querySelector('.sui-calendar-time-period');
|
|
1194
1197
|
}
|
|
1195
1198
|
|
|
1196
|
-
|
|
1197
|
-
|
|
1199
|
+
const hourMax = is24h ? 23 : 12;
|
|
1200
|
+
const hourMin = is24h ? 0 : 1;
|
|
1198
1201
|
|
|
1199
1202
|
function parseHour(v) {
|
|
1200
|
-
|
|
1203
|
+
let n = parseInt(v, 10);
|
|
1201
1204
|
if (isNaN(n) || n < 0) n = 0;
|
|
1202
1205
|
if (n > 23) n = 23;
|
|
1203
1206
|
if (is24h) return { hour: n };
|
|
@@ -1207,10 +1210,10 @@ const SoftUI = (() => {
|
|
|
1207
1210
|
if (n === 12) return { hour: 12, period: 'PM' };
|
|
1208
1211
|
return { hour: n - 12, period: 'PM' };
|
|
1209
1212
|
}
|
|
1210
|
-
function clampMinute(v) {
|
|
1213
|
+
function clampMinute(v) { const n = parseInt(v, 10); if (isNaN(n) || n < 0) return 0; if (n > 59) return 59; return n; }
|
|
1211
1214
|
|
|
1212
1215
|
hourInput.addEventListener('blur', function() {
|
|
1213
|
-
|
|
1216
|
+
const result = parseHour(this.value);
|
|
1214
1217
|
timeHour = result.hour;
|
|
1215
1218
|
if (!is24h && result.period) {
|
|
1216
1219
|
timePeriod = result.period;
|
|
@@ -1253,31 +1256,31 @@ const SoftUI = (() => {
|
|
|
1253
1256
|
}
|
|
1254
1257
|
}
|
|
1255
1258
|
|
|
1256
|
-
|
|
1257
|
-
|
|
1259
|
+
let monthContainers = cal.querySelectorAll('.sui-calendar-month');
|
|
1260
|
+
const isMultiMonth = monthContainers.length > 0;
|
|
1258
1261
|
if (!isMultiMonth) monthContainers = [cal];
|
|
1259
1262
|
|
|
1260
|
-
|
|
1263
|
+
const viewOffsets = [];
|
|
1261
1264
|
monthContainers.forEach(function(mc, i) { viewOffsets.push(i); });
|
|
1262
1265
|
|
|
1263
|
-
|
|
1264
|
-
|
|
1266
|
+
let viewYear = today.getFullYear();
|
|
1267
|
+
let viewMonth = today.getMonth();
|
|
1265
1268
|
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
+
const prevBtn = cal.querySelector('[data-sui-calendar-prev]');
|
|
1270
|
+
const nextBtn = cal.querySelector('[data-sui-calendar-next]');
|
|
1271
|
+
const titleEl = cal.querySelector('.sui-calendar-header .sui-calendar-title');
|
|
1269
1272
|
|
|
1270
1273
|
function renderDays() {
|
|
1271
1274
|
viewMode = 'days';
|
|
1272
1275
|
if (timeRow) timeRow.style.display = '';
|
|
1273
1276
|
monthContainers.forEach(function(mc, idx) {
|
|
1274
|
-
|
|
1275
|
-
|
|
1277
|
+
let m = viewMonth + viewOffsets[idx];
|
|
1278
|
+
let y = viewYear;
|
|
1276
1279
|
while (m > 11) { m -= 12; y++; }
|
|
1277
1280
|
while (m < 0) { m += 12; y--; }
|
|
1278
1281
|
|
|
1279
1282
|
// Title
|
|
1280
|
-
|
|
1283
|
+
const t = mc.querySelector('.sui-calendar-title');
|
|
1281
1284
|
if (t) {
|
|
1282
1285
|
if (idx === 0 && !isMultiMonth && t === titleEl) {
|
|
1283
1286
|
t.textContent = MONTHS[m] + ' ' + y;
|
|
@@ -1287,24 +1290,24 @@ const SoftUI = (() => {
|
|
|
1287
1290
|
}
|
|
1288
1291
|
}
|
|
1289
1292
|
|
|
1290
|
-
|
|
1293
|
+
const grid = mc.querySelector('.sui-calendar-grid');
|
|
1291
1294
|
if (!grid) return;
|
|
1292
1295
|
grid.innerHTML = '';
|
|
1293
1296
|
grid.style.gridTemplateColumns = 'repeat(7, 1fr)';
|
|
1294
1297
|
|
|
1295
1298
|
DAYS.forEach(function(d) {
|
|
1296
|
-
|
|
1299
|
+
const lbl = document.createElement('div');
|
|
1297
1300
|
lbl.className = 'sui-calendar-day-label';
|
|
1298
1301
|
lbl.textContent = d;
|
|
1299
1302
|
grid.appendChild(lbl);
|
|
1300
1303
|
});
|
|
1301
1304
|
|
|
1302
|
-
|
|
1303
|
-
|
|
1305
|
+
const firstDay = new Date(y, m, 1).getDay();
|
|
1306
|
+
const total = daysInMonth(y, m);
|
|
1304
1307
|
|
|
1305
|
-
|
|
1306
|
-
for (
|
|
1307
|
-
|
|
1308
|
+
const prevTotal = daysInMonth(y, m - 1);
|
|
1309
|
+
for (let p = firstDay - 1; p >= 0; p--) {
|
|
1310
|
+
const btn = document.createElement('button');
|
|
1308
1311
|
btn.className = 'sui-calendar-day outside';
|
|
1309
1312
|
btn.textContent = prevTotal - p;
|
|
1310
1313
|
btn.type = 'button';
|
|
@@ -1312,9 +1315,9 @@ const SoftUI = (() => {
|
|
|
1312
1315
|
grid.appendChild(btn);
|
|
1313
1316
|
}
|
|
1314
1317
|
|
|
1315
|
-
for (
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
+
for (let d = 1; d <= total; d++) {
|
|
1319
|
+
const date = new Date(y, m, d);
|
|
1320
|
+
const btn = document.createElement('button');
|
|
1318
1321
|
btn.className = 'sui-calendar-day';
|
|
1319
1322
|
btn.textContent = d;
|
|
1320
1323
|
btn.type = 'button';
|
|
@@ -1335,7 +1338,7 @@ const SoftUI = (() => {
|
|
|
1335
1338
|
e.stopPropagation();
|
|
1336
1339
|
if (mode === 'single') {
|
|
1337
1340
|
selected = dt;
|
|
1338
|
-
|
|
1341
|
+
const detail = { date: dt };
|
|
1339
1342
|
if (hasTime) { detail.hour = timeHour; detail.minute = timeMinute; detail.period = timePeriod; }
|
|
1340
1343
|
cal.dispatchEvent(new CustomEvent('sui-date-select', { detail: detail }));
|
|
1341
1344
|
} else if (mode === 'range') {
|
|
@@ -1355,10 +1358,10 @@ const SoftUI = (() => {
|
|
|
1355
1358
|
grid.appendChild(btn);
|
|
1356
1359
|
}
|
|
1357
1360
|
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
for (
|
|
1361
|
-
|
|
1361
|
+
const totalCells = firstDay + total;
|
|
1362
|
+
const remaining = totalCells % 7 === 0 ? 0 : 7 - (totalCells % 7);
|
|
1363
|
+
for (let n = 1; n <= remaining; n++) {
|
|
1364
|
+
const btn = document.createElement('button');
|
|
1362
1365
|
btn.className = 'sui-calendar-day outside';
|
|
1363
1366
|
btn.textContent = n;
|
|
1364
1367
|
btn.type = 'button';
|
|
@@ -1379,13 +1382,13 @@ const SoftUI = (() => {
|
|
|
1379
1382
|
}
|
|
1380
1383
|
|
|
1381
1384
|
// Only render in first (or only) grid
|
|
1382
|
-
|
|
1385
|
+
const grid = monthContainers[0].querySelector('.sui-calendar-grid');
|
|
1383
1386
|
if (!grid) return;
|
|
1384
1387
|
grid.innerHTML = '';
|
|
1385
1388
|
grid.style.gridTemplateColumns = 'repeat(4, 1fr)';
|
|
1386
1389
|
|
|
1387
|
-
for (
|
|
1388
|
-
|
|
1390
|
+
for (let m = 0; m < 12; m++) {
|
|
1391
|
+
const btn = document.createElement('button');
|
|
1389
1392
|
btn.className = 'sui-calendar-day';
|
|
1390
1393
|
btn.textContent = MONTHS_SHORT[m];
|
|
1391
1394
|
btn.type = 'button';
|
|
@@ -1408,20 +1411,20 @@ const SoftUI = (() => {
|
|
|
1408
1411
|
function renderYears() {
|
|
1409
1412
|
viewMode = 'years';
|
|
1410
1413
|
if (timeRow) timeRow.style.display = 'none';
|
|
1411
|
-
|
|
1412
|
-
|
|
1414
|
+
const start = yearPageStart;
|
|
1415
|
+
const end = start + 11;
|
|
1413
1416
|
if (titleEl) {
|
|
1414
1417
|
titleEl.textContent = start + ' – ' + end;
|
|
1415
1418
|
titleEl.style.cursor = 'default';
|
|
1416
1419
|
}
|
|
1417
1420
|
|
|
1418
|
-
|
|
1421
|
+
const grid = monthContainers[0].querySelector('.sui-calendar-grid');
|
|
1419
1422
|
if (!grid) return;
|
|
1420
1423
|
grid.innerHTML = '';
|
|
1421
1424
|
grid.style.gridTemplateColumns = 'repeat(4, 1fr)';
|
|
1422
1425
|
|
|
1423
|
-
for (
|
|
1424
|
-
|
|
1426
|
+
for (let yr = start; yr <= end; yr++) {
|
|
1427
|
+
const btn = document.createElement('button');
|
|
1425
1428
|
btn.className = 'sui-calendar-day';
|
|
1426
1429
|
btn.textContent = yr;
|
|
1427
1430
|
btn.type = 'button';
|
|
@@ -1442,9 +1445,9 @@ const SoftUI = (() => {
|
|
|
1442
1445
|
}
|
|
1443
1446
|
|
|
1444
1447
|
function updateClear() {
|
|
1445
|
-
|
|
1448
|
+
const clearBtn = cal.querySelector('[data-sui-calendar-clear]');
|
|
1446
1449
|
if (clearBtn) {
|
|
1447
|
-
|
|
1450
|
+
const hasSelection = mode === 'single' ? !!selected : !!(rangeStart || rangeEnd);
|
|
1448
1451
|
clearBtn.style.display = hasSelection ? '' : 'none';
|
|
1449
1452
|
}
|
|
1450
1453
|
}
|
|
@@ -1515,11 +1518,11 @@ const SoftUI = (() => {
|
|
|
1515
1518
|
renderDays();
|
|
1516
1519
|
|
|
1517
1520
|
// Date Picker integration
|
|
1518
|
-
|
|
1521
|
+
const picker = cal.closest('.sui-datepicker');
|
|
1519
1522
|
if (picker) {
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
+
const trigger = picker.querySelector('.sui-datepicker-trigger');
|
|
1524
|
+
const popover = picker.querySelector('.sui-datepicker-popover');
|
|
1525
|
+
const placeholderEl = trigger ? trigger.querySelector('.sui-datepicker-placeholder') : null;
|
|
1523
1526
|
if (placeholderEl) defaultPlaceholder = placeholderEl.textContent;
|
|
1524
1527
|
|
|
1525
1528
|
if (trigger && popover) {
|
|
@@ -1537,13 +1540,13 @@ const SoftUI = (() => {
|
|
|
1537
1540
|
|
|
1538
1541
|
cal.addEventListener('sui-date-select', function(e) {
|
|
1539
1542
|
if (!trigger) return;
|
|
1540
|
-
|
|
1543
|
+
const span = trigger.querySelector('.sui-datepicker-value') || trigger.querySelector('.sui-datepicker-placeholder');
|
|
1541
1544
|
if (mode === 'single' && e.detail.date) {
|
|
1542
|
-
|
|
1545
|
+
const text = formatDate(e.detail.date, hasTime, e.detail.hour, e.detail.minute, e.detail.is24h ? null : e.detail.period);
|
|
1543
1546
|
if (span) { span.textContent = text; span.className = 'sui-datepicker-value'; }
|
|
1544
1547
|
if (!hasTime && popover) popover.classList.remove('open');
|
|
1545
1548
|
} else if (mode === 'range' && e.detail.start && e.detail.end) {
|
|
1546
|
-
|
|
1549
|
+
const text = formatDate(e.detail.start) + ' – ' + formatDate(e.detail.end);
|
|
1547
1550
|
if (span) { span.textContent = text; span.className = 'sui-datepicker-value'; }
|
|
1548
1551
|
if (popover) popover.classList.remove('open');
|
|
1549
1552
|
}
|
|
@@ -1551,7 +1554,7 @@ const SoftUI = (() => {
|
|
|
1551
1554
|
});
|
|
1552
1555
|
|
|
1553
1556
|
cal.addEventListener('sui-date-clear', function() {
|
|
1554
|
-
|
|
1557
|
+
const span = trigger.querySelector('.sui-datepicker-value') || trigger.querySelector('.sui-datepicker-placeholder');
|
|
1555
1558
|
if (span) { span.textContent = defaultPlaceholder; span.className = 'sui-datepicker-placeholder'; }
|
|
1556
1559
|
updateClear();
|
|
1557
1560
|
});
|
|
@@ -1564,18 +1567,18 @@ const SoftUI = (() => {
|
|
|
1564
1567
|
// =========================================
|
|
1565
1568
|
function initTimePicker() {
|
|
1566
1569
|
document.querySelectorAll('.sui-timepicker[data-sui-timepicker]').forEach(function(tp) {
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1570
|
+
const is24h = tp.getAttribute('data-sui-timepicker') === '24h';
|
|
1571
|
+
const hourMax = is24h ? 23 : 12;
|
|
1572
|
+
const hourMin = is24h ? 0 : 1;
|
|
1573
|
+
let tHour = is24h ? 0 : 12, tMinute = 0, tPeriod = 'AM';
|
|
1571
1574
|
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
+
let hInput = tp.querySelectorAll('.sui-calendar-time-input')[0];
|
|
1576
|
+
let mInput = tp.querySelectorAll('.sui-calendar-time-input')[1];
|
|
1577
|
+
let pBtn = tp.querySelector('.sui-calendar-time-period');
|
|
1575
1578
|
|
|
1576
1579
|
if (!hInput || !mInput) {
|
|
1577
1580
|
// Auto-build the UI
|
|
1578
|
-
|
|
1581
|
+
const label = document.createElement('span');
|
|
1579
1582
|
label.className = 'sui-calendar-time-label';
|
|
1580
1583
|
label.textContent = 'Time';
|
|
1581
1584
|
tp.appendChild(label);
|
|
@@ -1588,7 +1591,7 @@ const SoftUI = (() => {
|
|
|
1588
1591
|
hInput.setAttribute('aria-label', 'Hour');
|
|
1589
1592
|
tp.appendChild(hInput);
|
|
1590
1593
|
|
|
1591
|
-
|
|
1594
|
+
const sep = document.createElement('span');
|
|
1592
1595
|
sep.className = 'sui-calendar-time-sep';
|
|
1593
1596
|
sep.textContent = ':';
|
|
1594
1597
|
tp.appendChild(sep);
|
|
@@ -1611,7 +1614,7 @@ const SoftUI = (() => {
|
|
|
1611
1614
|
}
|
|
1612
1615
|
|
|
1613
1616
|
function parseH(v) {
|
|
1614
|
-
|
|
1617
|
+
let n = parseInt(v, 10);
|
|
1615
1618
|
if (isNaN(n) || n < 0) n = 0;
|
|
1616
1619
|
if (n > 23) n = 23;
|
|
1617
1620
|
if (is24h) return { hour: n };
|
|
@@ -1620,14 +1623,14 @@ const SoftUI = (() => {
|
|
|
1620
1623
|
if (n === 12) return { hour: 12, period: 'PM' };
|
|
1621
1624
|
return { hour: n - 12, period: 'PM' };
|
|
1622
1625
|
}
|
|
1623
|
-
function clampM(v) {
|
|
1626
|
+
function clampM(v) { const n = parseInt(v, 10); if (isNaN(n) || n < 0) return 0; if (n > 59) return 59; return n; }
|
|
1624
1627
|
|
|
1625
1628
|
function fireChange() {
|
|
1626
1629
|
tp.dispatchEvent(new CustomEvent('sui-time-change', { detail: { hour: tHour, minute: tMinute, period: is24h ? null : tPeriod, is24h: is24h } }));
|
|
1627
1630
|
}
|
|
1628
1631
|
|
|
1629
1632
|
hInput.addEventListener('blur', function() {
|
|
1630
|
-
|
|
1633
|
+
const result = parseH(this.value);
|
|
1631
1634
|
tHour = result.hour;
|
|
1632
1635
|
if (!is24h && result.period) { tPeriod = result.period; if (pBtn) pBtn.textContent = tPeriod; }
|
|
1633
1636
|
this.value = pad(tHour);
|
|
@@ -1661,7 +1664,7 @@ const SoftUI = (() => {
|
|
|
1661
1664
|
// Menubar
|
|
1662
1665
|
// =========================================
|
|
1663
1666
|
function initMenubar() {
|
|
1664
|
-
|
|
1667
|
+
let menubarOpen = false;
|
|
1665
1668
|
|
|
1666
1669
|
function closeAllMenus(bar) {
|
|
1667
1670
|
bar.querySelectorAll('.sui-menubar-menu.open').forEach(function(m) {
|
|
@@ -1685,9 +1688,9 @@ const SoftUI = (() => {
|
|
|
1685
1688
|
}
|
|
1686
1689
|
|
|
1687
1690
|
document.addEventListener('click', function(e) {
|
|
1688
|
-
|
|
1691
|
+
const trigger = e.target.closest('.sui-menubar-trigger');
|
|
1689
1692
|
if (trigger) {
|
|
1690
|
-
|
|
1693
|
+
const menu = trigger.closest('.sui-menubar-menu');
|
|
1691
1694
|
if (menu.classList.contains('open')) {
|
|
1692
1695
|
closeAllMenus(menu.closest('.sui-menubar'));
|
|
1693
1696
|
} else {
|
|
@@ -1698,10 +1701,10 @@ const SoftUI = (() => {
|
|
|
1698
1701
|
}
|
|
1699
1702
|
|
|
1700
1703
|
// Submenu triggers
|
|
1701
|
-
|
|
1704
|
+
const subTrigger = e.target.closest('.sui-menubar-sub-trigger');
|
|
1702
1705
|
if (subTrigger) {
|
|
1703
|
-
|
|
1704
|
-
|
|
1706
|
+
const sub = subTrigger.closest('.sui-menubar-sub');
|
|
1707
|
+
const isOpen = sub.classList.contains('open');
|
|
1705
1708
|
// Close sibling subs
|
|
1706
1709
|
sub.parentElement.querySelectorAll('.sui-menubar-sub.open').forEach(function(s) {
|
|
1707
1710
|
s.classList.remove('open');
|
|
@@ -1712,9 +1715,9 @@ const SoftUI = (() => {
|
|
|
1712
1715
|
}
|
|
1713
1716
|
|
|
1714
1717
|
// Clicking a menubar item closes the menu
|
|
1715
|
-
|
|
1718
|
+
const item = e.target.closest('.sui-menubar-item');
|
|
1716
1719
|
if (item && item.closest('.sui-menubar')) {
|
|
1717
|
-
|
|
1720
|
+
const bar = item.closest('.sui-menubar');
|
|
1718
1721
|
closeAllMenus(bar);
|
|
1719
1722
|
return;
|
|
1720
1723
|
}
|
|
@@ -1728,9 +1731,9 @@ const SoftUI = (() => {
|
|
|
1728
1731
|
// Hover to switch menus when one is already open
|
|
1729
1732
|
document.addEventListener('mouseenter', function(e) {
|
|
1730
1733
|
if (!menubarOpen) return;
|
|
1731
|
-
|
|
1734
|
+
const trigger = e.target.closest ? e.target.closest('.sui-menubar-trigger') : null;
|
|
1732
1735
|
if (!trigger) return;
|
|
1733
|
-
|
|
1736
|
+
const menu = trigger.closest('.sui-menubar-menu');
|
|
1734
1737
|
if (menu && !menu.classList.contains('open')) {
|
|
1735
1738
|
openMenu(menu);
|
|
1736
1739
|
}
|
|
@@ -1764,16 +1767,16 @@ const SoftUI = (() => {
|
|
|
1764
1767
|
// =========================================
|
|
1765
1768
|
function initCombobox() {
|
|
1766
1769
|
document.querySelectorAll('.sui-combobox').forEach(function(combo) {
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1770
|
+
const trigger = combo.querySelector('.sui-combobox-trigger');
|
|
1771
|
+
const content = combo.querySelector('.sui-combobox-content');
|
|
1772
|
+
const input = combo.querySelector('.sui-combobox-input');
|
|
1773
|
+
const items = combo.querySelectorAll('.sui-combobox-item');
|
|
1774
|
+
const valueEl = combo.querySelector('.sui-combobox-value');
|
|
1775
|
+
const chipsEl = combo.querySelector('.sui-combobox-chips');
|
|
1776
|
+
const emptyEl = combo.querySelector('.sui-combobox-empty');
|
|
1777
|
+
const clearBtn = combo.querySelector('.sui-combobox-clear');
|
|
1778
|
+
const isMultiple = combo.classList.contains('sui-combobox-multiple');
|
|
1779
|
+
let placeholder = valueEl ? valueEl.textContent : '';
|
|
1777
1780
|
if (chipsEl) placeholder = chipsEl.textContent.trim();
|
|
1778
1781
|
|
|
1779
1782
|
if (!trigger || !content) return;
|
|
@@ -1797,18 +1800,18 @@ const SoftUI = (() => {
|
|
|
1797
1800
|
}
|
|
1798
1801
|
|
|
1799
1802
|
function filterItems(query) {
|
|
1800
|
-
|
|
1801
|
-
|
|
1803
|
+
const q = query.toLowerCase();
|
|
1804
|
+
let visibleCount = 0;
|
|
1802
1805
|
items.forEach(function(item) {
|
|
1803
|
-
|
|
1804
|
-
|
|
1806
|
+
const text = item.textContent.toLowerCase();
|
|
1807
|
+
const match = !q || text.indexOf(q) !== -1;
|
|
1805
1808
|
item.style.display = match ? '' : 'none';
|
|
1806
1809
|
if (match) visibleCount++;
|
|
1807
1810
|
});
|
|
1808
1811
|
// Show/hide groups based on visible children
|
|
1809
1812
|
combo.querySelectorAll('.sui-combobox-label').forEach(function(label) {
|
|
1810
|
-
|
|
1811
|
-
|
|
1813
|
+
let next = label.nextElementSibling;
|
|
1814
|
+
let hasVisible = false;
|
|
1812
1815
|
while (next && !next.classList.contains('sui-combobox-label') && !next.classList.contains('sui-combobox-separator')) {
|
|
1813
1816
|
if (next.classList.contains('sui-combobox-item') && next.style.display !== 'none') hasVisible = true;
|
|
1814
1817
|
next = next.nextElementSibling;
|
|
@@ -1825,7 +1828,7 @@ const SoftUI = (() => {
|
|
|
1825
1828
|
|
|
1826
1829
|
function updateClear() {
|
|
1827
1830
|
if (!clearBtn) return;
|
|
1828
|
-
|
|
1831
|
+
const hasSelection = combo.querySelectorAll('.sui-combobox-item.selected').length > 0;
|
|
1829
1832
|
clearBtn.classList.toggle('visible', hasSelection);
|
|
1830
1833
|
}
|
|
1831
1834
|
|
|
@@ -1842,9 +1845,9 @@ const SoftUI = (() => {
|
|
|
1842
1845
|
function updateChips() {
|
|
1843
1846
|
if (!chipsEl) return;
|
|
1844
1847
|
chipsEl.innerHTML = '';
|
|
1845
|
-
|
|
1848
|
+
const selectedItems = combo.querySelectorAll('.sui-combobox-item.selected');
|
|
1846
1849
|
if (selectedItems.length === 0) {
|
|
1847
|
-
|
|
1850
|
+
const ph = document.createElement('span');
|
|
1848
1851
|
ph.className = 'placeholder';
|
|
1849
1852
|
ph.textContent = placeholder;
|
|
1850
1853
|
chipsEl.appendChild(ph);
|
|
@@ -1852,10 +1855,10 @@ const SoftUI = (() => {
|
|
|
1852
1855
|
return;
|
|
1853
1856
|
}
|
|
1854
1857
|
selectedItems.forEach(function(item) {
|
|
1855
|
-
|
|
1858
|
+
const chip = document.createElement('span');
|
|
1856
1859
|
chip.className = 'sui-combobox-chip';
|
|
1857
1860
|
chip.textContent = item.getAttribute('data-value') || item.textContent.trim();
|
|
1858
|
-
|
|
1861
|
+
const remove = document.createElement('span');
|
|
1859
1862
|
remove.className = 'sui-combobox-chip-remove';
|
|
1860
1863
|
remove.innerHTML = '✕';
|
|
1861
1864
|
remove.addEventListener('click', function(e) {
|
|
@@ -1935,19 +1938,19 @@ const SoftUI = (() => {
|
|
|
1935
1938
|
// =========================================
|
|
1936
1939
|
function initResizable() {
|
|
1937
1940
|
document.querySelectorAll('.sui-resizable').forEach(function(container) {
|
|
1938
|
-
|
|
1939
|
-
|
|
1941
|
+
const isVertical = container.classList.contains('sui-resizable-vertical');
|
|
1942
|
+
const handles = container.querySelectorAll(':scope > .sui-resizable-handle');
|
|
1940
1943
|
|
|
1941
1944
|
// Initialize panels with flex-grow from data-size or equal split
|
|
1942
|
-
|
|
1945
|
+
const panels = Array.from(container.querySelectorAll(':scope > .sui-resizable-panel'));
|
|
1943
1946
|
panels.forEach(function(p) {
|
|
1944
|
-
|
|
1947
|
+
const size = parseFloat(p.getAttribute('data-size')) || (100 / panels.length);
|
|
1945
1948
|
p.style.flexGrow = size;
|
|
1946
1949
|
});
|
|
1947
1950
|
|
|
1948
1951
|
handles.forEach(function(handle) {
|
|
1949
|
-
|
|
1950
|
-
|
|
1952
|
+
const prevPanel = handle.previousElementSibling;
|
|
1953
|
+
const nextPanel = handle.nextElementSibling;
|
|
1951
1954
|
if (!prevPanel || !nextPanel) return;
|
|
1952
1955
|
|
|
1953
1956
|
handle.setAttribute('tabindex', '0');
|
|
@@ -1963,13 +1966,13 @@ const SoftUI = (() => {
|
|
|
1963
1966
|
}
|
|
1964
1967
|
|
|
1965
1968
|
function resize(delta) {
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1969
|
+
const prevG = getGrow(prevPanel);
|
|
1970
|
+
const nextG = getGrow(nextPanel);
|
|
1971
|
+
const total = prevG + nextG;
|
|
1972
|
+
const prevMin = getMin(prevPanel);
|
|
1973
|
+
const nextMin = getMin(nextPanel);
|
|
1974
|
+
const newPrev = Math.max(prevMin, Math.min(total - nextMin, prevG + delta));
|
|
1975
|
+
const newNext = total - newPrev;
|
|
1973
1976
|
prevPanel.style.flexGrow = newPrev;
|
|
1974
1977
|
nextPanel.style.flexGrow = newNext;
|
|
1975
1978
|
}
|
|
@@ -1979,27 +1982,27 @@ const SoftUI = (() => {
|
|
|
1979
1982
|
handle.focus();
|
|
1980
1983
|
handle.classList.add('dragging');
|
|
1981
1984
|
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
+
const prevG = getGrow(prevPanel);
|
|
1986
|
+
const nextG = getGrow(nextPanel);
|
|
1987
|
+
const totalG = prevG + nextG;
|
|
1985
1988
|
|
|
1986
1989
|
// Measure actual pixel sizes of the two panels
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1990
|
+
const prevPx = isVertical ? prevPanel.offsetHeight : prevPanel.offsetWidth;
|
|
1991
|
+
const nextPx = isVertical ? nextPanel.offsetHeight : nextPanel.offsetWidth;
|
|
1992
|
+
const pairPx = prevPx + nextPx;
|
|
1993
|
+
const startPos = isVertical ? e.clientY : e.clientX;
|
|
1991
1994
|
|
|
1992
|
-
|
|
1993
|
-
|
|
1995
|
+
const prevMin = getMin(prevPanel);
|
|
1996
|
+
const nextMin = getMin(nextPanel);
|
|
1994
1997
|
|
|
1995
1998
|
function onPointerMove(ev) {
|
|
1996
|
-
|
|
1997
|
-
|
|
1999
|
+
const pos = isVertical ? ev.clientY : ev.clientX;
|
|
2000
|
+
let delta = pos - startPos;
|
|
1998
2001
|
// Clamp delta so panels stay within 0..pairPx range
|
|
1999
2002
|
delta = Math.max(-prevPx, Math.min(nextPx, delta));
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
+
const ratio = pairPx > 0 ? delta / pairPx : 0;
|
|
2004
|
+
const newPrev = Math.max(prevMin, Math.min(totalG - nextMin, prevG + ratio * totalG));
|
|
2005
|
+
const newNext = totalG - newPrev;
|
|
2003
2006
|
prevPanel.style.flexGrow = newPrev;
|
|
2004
2007
|
nextPanel.style.flexGrow = newNext;
|
|
2005
2008
|
}
|
|
@@ -2018,9 +2021,9 @@ const SoftUI = (() => {
|
|
|
2018
2021
|
|
|
2019
2022
|
// Keyboard: Arrow keys resize, Home/End for extremes
|
|
2020
2023
|
handle.addEventListener('keydown', function(e) {
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
+
const step = e.shiftKey ? 10 : 2;
|
|
2025
|
+
const growKey = isVertical ? 'ArrowDown' : 'ArrowRight';
|
|
2026
|
+
const shrinkKey = isVertical ? 'ArrowUp' : 'ArrowLeft';
|
|
2024
2027
|
|
|
2025
2028
|
if (e.key === growKey) {
|
|
2026
2029
|
e.preventDefault();
|
|
@@ -2144,13 +2147,13 @@ const SoftUI = (() => {
|
|
|
2144
2147
|
document.querySelectorAll('.sui-otp[data-sui-otp]').forEach(function(otp) {
|
|
2145
2148
|
if (otp.dataset.suiOtpDisabled !== undefined) return;
|
|
2146
2149
|
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2150
|
+
const slots = otp.querySelectorAll('.sui-otp-slot');
|
|
2151
|
+
const len = slots.length;
|
|
2152
|
+
const pattern = otp.dataset.suiOtpPattern || 'digits'; // "digits" or "alphanumeric"
|
|
2153
|
+
const regex = pattern === 'alphanumeric' ? /^[a-zA-Z0-9]$/ : /^[0-9]$/;
|
|
2151
2154
|
|
|
2152
2155
|
// Create hidden input
|
|
2153
|
-
|
|
2156
|
+
const input = document.createElement('input');
|
|
2154
2157
|
input.className = 'sui-otp-input';
|
|
2155
2158
|
input.setAttribute('inputmode', pattern === 'alphanumeric' ? 'text' : 'numeric');
|
|
2156
2159
|
input.setAttribute('autocomplete', 'one-time-code');
|
|
@@ -2159,14 +2162,14 @@ const SoftUI = (() => {
|
|
|
2159
2162
|
otp.appendChild(input);
|
|
2160
2163
|
|
|
2161
2164
|
function updateSlots() {
|
|
2162
|
-
|
|
2165
|
+
const val = input.value;
|
|
2163
2166
|
slots.forEach(function(slot, i) {
|
|
2164
2167
|
slot.textContent = val[i] || '';
|
|
2165
2168
|
slot.classList.toggle('sui-otp-filled', !!val[i]);
|
|
2166
2169
|
slot.classList.remove('sui-otp-active');
|
|
2167
2170
|
});
|
|
2168
2171
|
// Show cursor on current slot
|
|
2169
|
-
|
|
2172
|
+
const pos = Math.min(val.length, len - 1);
|
|
2170
2173
|
if (document.activeElement === input && val.length < len) {
|
|
2171
2174
|
slots[pos].classList.add('sui-otp-active');
|
|
2172
2175
|
} else if (document.activeElement === input && val.length === len) {
|
|
@@ -2176,8 +2179,8 @@ const SoftUI = (() => {
|
|
|
2176
2179
|
|
|
2177
2180
|
input.addEventListener('input', function() {
|
|
2178
2181
|
// Filter to allowed characters
|
|
2179
|
-
|
|
2180
|
-
for (
|
|
2182
|
+
let filtered = '';
|
|
2183
|
+
for (let i = 0; i < input.value.length && filtered.length < len; i++) {
|
|
2181
2184
|
if (regex.test(input.value[i])) {
|
|
2182
2185
|
filtered += pattern === 'alphanumeric' ? input.value[i].toUpperCase() : input.value[i];
|
|
2183
2186
|
}
|
|
@@ -2207,7 +2210,7 @@ const SoftUI = (() => {
|
|
|
2207
2210
|
e.stopPropagation();
|
|
2208
2211
|
input.focus();
|
|
2209
2212
|
// Set cursor position
|
|
2210
|
-
|
|
2213
|
+
const pos = Math.min(i, input.value.length);
|
|
2211
2214
|
input.setSelectionRange(pos, pos);
|
|
2212
2215
|
updateSlots();
|
|
2213
2216
|
});
|
|
@@ -2222,13 +2225,13 @@ const SoftUI = (() => {
|
|
|
2222
2225
|
// =========================================
|
|
2223
2226
|
function initToggleGroups() {
|
|
2224
2227
|
document.querySelectorAll('.sui-toggle-group[data-sui-toggle]').forEach(function(group) {
|
|
2225
|
-
|
|
2226
|
-
|
|
2228
|
+
const mode = group.dataset.suiToggle; // "single" or "multi"
|
|
2229
|
+
const items = group.querySelectorAll('.sui-toggle-group-item:not([disabled])');
|
|
2227
2230
|
|
|
2228
2231
|
items.forEach(function(item) {
|
|
2229
2232
|
item.addEventListener('click', function() {
|
|
2230
2233
|
if (mode === 'single') {
|
|
2231
|
-
|
|
2234
|
+
const wasActive = item.classList.contains('active');
|
|
2232
2235
|
items.forEach(function(it) {
|
|
2233
2236
|
it.classList.remove('active');
|
|
2234
2237
|
it.setAttribute('aria-pressed', 'false');
|
|
@@ -2289,33 +2292,33 @@ const SoftUI = (() => {
|
|
|
2289
2292
|
// Seamless loop: clone slides at both ends
|
|
2290
2293
|
let cloneCount = 0;
|
|
2291
2294
|
if (isSeamless && totalReal > visible) {
|
|
2292
|
-
for (
|
|
2293
|
-
|
|
2295
|
+
for (let i = totalReal - 1; i >= totalReal - visible; i--) {
|
|
2296
|
+
const clone = realItems[i].cloneNode(true);
|
|
2294
2297
|
clone.setAttribute('aria-hidden', 'true');
|
|
2295
2298
|
track.insertBefore(clone, track.firstChild);
|
|
2296
2299
|
}
|
|
2297
|
-
for (
|
|
2298
|
-
|
|
2300
|
+
for (let i = 0; i < visible; i++) {
|
|
2301
|
+
const clone = realItems[i].cloneNode(true);
|
|
2299
2302
|
clone.setAttribute('aria-hidden', 'true');
|
|
2300
2303
|
track.appendChild(clone);
|
|
2301
2304
|
}
|
|
2302
2305
|
cloneCount = visible;
|
|
2303
2306
|
}
|
|
2304
2307
|
|
|
2305
|
-
|
|
2308
|
+
const allItems = Array.from(track.children);
|
|
2306
2309
|
|
|
2307
2310
|
function moveTo(displayIndex, animate) {
|
|
2308
2311
|
if (animate === false) track.style.transition = 'none';
|
|
2309
2312
|
|
|
2310
2313
|
if (isVertical) {
|
|
2311
|
-
|
|
2312
|
-
|
|
2314
|
+
const vh = track.parentElement.offsetHeight;
|
|
2315
|
+
const itemH = vh / visible;
|
|
2313
2316
|
allItems.forEach(function(it) { it.style.height = itemH + 'px'; });
|
|
2314
2317
|
track.style.transform = 'translateY(-' + (displayIndex * itemH) + 'px)';
|
|
2315
2318
|
} else {
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
+
const item = allItems[displayIndex];
|
|
2320
|
+
const base = allItems[0];
|
|
2321
|
+
const px = (item && base) ? item.offsetLeft - base.offsetLeft : 0;
|
|
2319
2322
|
track.style.transform = 'translateX(-' + px + 'px)';
|
|
2320
2323
|
}
|
|
2321
2324
|
|
|
@@ -2326,10 +2329,10 @@ const SoftUI = (() => {
|
|
|
2326
2329
|
}
|
|
2327
2330
|
|
|
2328
2331
|
function update(animate) {
|
|
2329
|
-
|
|
2332
|
+
const displayIndex = current + cloneCount;
|
|
2330
2333
|
moveTo(displayIndex, animate);
|
|
2331
2334
|
|
|
2332
|
-
|
|
2335
|
+
const dotIndex = ((current % totalReal) + totalReal) % totalReal;
|
|
2333
2336
|
dots.forEach(function(d, i) { d.classList.toggle('active', i === dotIndex); });
|
|
2334
2337
|
|
|
2335
2338
|
if (!isLoop) {
|
|
@@ -2435,45 +2438,45 @@ const SoftUI = (() => {
|
|
|
2435
2438
|
document.querySelectorAll('.sui-chart-bar-col').forEach(function(col) {
|
|
2436
2439
|
// Skip grouped bars (handled separately)
|
|
2437
2440
|
if (col.querySelector('.sui-chart-bar-group')) return;
|
|
2438
|
-
|
|
2441
|
+
const fill = col.querySelector('.sui-chart-bar-fill');
|
|
2439
2442
|
if (!fill) return;
|
|
2440
|
-
|
|
2443
|
+
const val = parseFloat(fill.getAttribute('data-value'));
|
|
2441
2444
|
if (isNaN(val)) return;
|
|
2442
|
-
|
|
2443
|
-
|
|
2445
|
+
const max = parseFloat(fill.getAttribute('data-max')) || 100;
|
|
2446
|
+
const pct = Math.min(100, Math.max(0, (val / max) * 100));
|
|
2444
2447
|
fill.style.height = pct + '%';
|
|
2445
2448
|
});
|
|
2446
2449
|
|
|
2447
2450
|
// Grouped bars — set heights for each fill in a group
|
|
2448
2451
|
document.querySelectorAll('.sui-chart-bar-group').forEach(function(group) {
|
|
2449
2452
|
group.querySelectorAll('.sui-chart-bar-fill').forEach(function(fill) {
|
|
2450
|
-
|
|
2453
|
+
const val = parseFloat(fill.getAttribute('data-value'));
|
|
2451
2454
|
if (isNaN(val)) return;
|
|
2452
|
-
|
|
2453
|
-
|
|
2455
|
+
const max = parseFloat(fill.getAttribute('data-max')) || 100;
|
|
2456
|
+
const pct = Math.min(100, Math.max(0, (val / max) * 100));
|
|
2454
2457
|
fill.style.height = pct + '%';
|
|
2455
2458
|
});
|
|
2456
2459
|
});
|
|
2457
2460
|
|
|
2458
2461
|
// Horizontal bars
|
|
2459
2462
|
document.querySelectorAll('.sui-chart-bar-row').forEach(function(row) {
|
|
2460
|
-
|
|
2463
|
+
const fill = row.querySelector('.sui-chart-bar-fill');
|
|
2461
2464
|
if (!fill) return;
|
|
2462
|
-
|
|
2465
|
+
const val = parseFloat(fill.getAttribute('data-value'));
|
|
2463
2466
|
if (isNaN(val)) return;
|
|
2464
|
-
|
|
2465
|
-
|
|
2467
|
+
const max = parseFloat(fill.getAttribute('data-max')) || 100;
|
|
2468
|
+
const pct = Math.min(100, Math.max(0, (val / max) * 100));
|
|
2466
2469
|
fill.style.width = pct + '%';
|
|
2467
2470
|
});
|
|
2468
2471
|
|
|
2469
2472
|
// Stacked bars
|
|
2470
2473
|
document.querySelectorAll('.sui-chart-bar-track-stacked').forEach(function(track) {
|
|
2471
|
-
|
|
2472
|
-
|
|
2474
|
+
const fills = track.querySelectorAll('.sui-chart-bar-fill');
|
|
2475
|
+
let total = 0;
|
|
2473
2476
|
fills.forEach(function(f) { total += parseFloat(f.getAttribute('data-value')) || 0; });
|
|
2474
|
-
|
|
2477
|
+
const max = parseFloat(track.getAttribute('data-max')) || total || 100;
|
|
2475
2478
|
fills.forEach(function(f) {
|
|
2476
|
-
|
|
2479
|
+
const v = parseFloat(f.getAttribute('data-value')) || 0;
|
|
2477
2480
|
f.style.height = ((v / max) * 100) + '%';
|
|
2478
2481
|
});
|
|
2479
2482
|
});
|
|
@@ -2481,25 +2484,25 @@ const SoftUI = (() => {
|
|
|
2481
2484
|
// Donut / Pie charts — build conic-gradient from data-segments
|
|
2482
2485
|
document.querySelectorAll('.sui-chart-donut[data-segments]').forEach(function(donut) {
|
|
2483
2486
|
try {
|
|
2484
|
-
|
|
2485
|
-
|
|
2487
|
+
const segments = JSON.parse(donut.getAttribute('data-segments'));
|
|
2488
|
+
let total = 0;
|
|
2486
2489
|
segments.forEach(function(s) { total += s.value; });
|
|
2487
|
-
|
|
2488
|
-
|
|
2490
|
+
const stops = [];
|
|
2491
|
+
let cumulative = 0;
|
|
2489
2492
|
segments.forEach(function(s) {
|
|
2490
|
-
|
|
2493
|
+
const start = (cumulative / total) * 100;
|
|
2491
2494
|
cumulative += s.value;
|
|
2492
|
-
|
|
2495
|
+
const end = (cumulative / total) * 100;
|
|
2493
2496
|
stops.push(s.color + ' ' + start + '% ' + end + '%');
|
|
2494
2497
|
});
|
|
2495
2498
|
donut.style.background = 'conic-gradient(' + stops.join(', ') + ')';
|
|
2496
|
-
} catch(
|
|
2499
|
+
} catch(_) {}
|
|
2497
2500
|
});
|
|
2498
2501
|
|
|
2499
2502
|
// Line / Area charts — measure path length for animation
|
|
2500
2503
|
document.querySelectorAll('.sui-chart-line-wrap .chart-line').forEach(function(path) {
|
|
2501
2504
|
if (path.getTotalLength) {
|
|
2502
|
-
|
|
2505
|
+
const len = path.getTotalLength();
|
|
2503
2506
|
path.style.setProperty('--line-length', len);
|
|
2504
2507
|
path.style.strokeDasharray = len;
|
|
2505
2508
|
path.style.strokeDashoffset = len;
|
|
@@ -2508,27 +2511,27 @@ const SoftUI = (() => {
|
|
|
2508
2511
|
|
|
2509
2512
|
// SVG dot tooltips
|
|
2510
2513
|
document.querySelectorAll('.sui-chart-line-wrap').forEach(function(wrap) {
|
|
2511
|
-
|
|
2514
|
+
const dots = wrap.querySelectorAll('.chart-dot[data-value]');
|
|
2512
2515
|
if (!dots.length) return;
|
|
2513
2516
|
|
|
2514
|
-
|
|
2517
|
+
const tip = document.createElement('div');
|
|
2515
2518
|
tip.className = 'sui-chart-tooltip';
|
|
2516
2519
|
wrap.appendChild(tip);
|
|
2517
2520
|
|
|
2518
2521
|
dots.forEach(function(dot) {
|
|
2519
2522
|
dot.addEventListener('mouseenter', function() {
|
|
2520
|
-
|
|
2523
|
+
const val = dot.getAttribute('data-value');
|
|
2521
2524
|
tip.textContent = val;
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2525
|
+
const svg = wrap.querySelector('svg');
|
|
2526
|
+
const svgRect = svg.getBoundingClientRect();
|
|
2527
|
+
const wrapRect = wrap.getBoundingClientRect();
|
|
2528
|
+
const cx = parseFloat(dot.getAttribute('cx'));
|
|
2529
|
+
const cy = parseFloat(dot.getAttribute('cy'));
|
|
2530
|
+
const viewBox = svg.viewBox.baseVal;
|
|
2531
|
+
const scaleX = svgRect.width / viewBox.width;
|
|
2532
|
+
const scaleY = svgRect.height / viewBox.height;
|
|
2533
|
+
const px = (cx * scaleX) + (svgRect.left - wrapRect.left);
|
|
2534
|
+
const py = (cy * scaleY) + (svgRect.top - wrapRect.top);
|
|
2532
2535
|
tip.style.left = px + 'px';
|
|
2533
2536
|
tip.style.top = (py - 8) + 'px';
|
|
2534
2537
|
tip.classList.add('visible');
|
|
@@ -2542,7 +2545,7 @@ const SoftUI = (() => {
|
|
|
2542
2545
|
|
|
2543
2546
|
function initSelectablePricing() {
|
|
2544
2547
|
document.querySelectorAll('.sui-pricing-selectable').forEach(function(container) {
|
|
2545
|
-
|
|
2548
|
+
const cards = container.querySelectorAll('.sui-pricing-card');
|
|
2546
2549
|
cards.forEach(function(card) {
|
|
2547
2550
|
card.addEventListener('click', function() {
|
|
2548
2551
|
cards.forEach(function(c) { c.classList.remove('selected'); });
|
|
@@ -2556,17 +2559,17 @@ const SoftUI = (() => {
|
|
|
2556
2559
|
|
|
2557
2560
|
function initStyledSelects() {
|
|
2558
2561
|
document.querySelectorAll('.sui-styled-select').forEach(function(sel) {
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2562
|
+
const trigger = sel.querySelector('.sui-styled-select-trigger');
|
|
2563
|
+
const menu = sel.querySelector('.sui-styled-select-menu');
|
|
2564
|
+
const valueEl = sel.querySelector('.sui-styled-select-value');
|
|
2565
|
+
const options = sel.querySelectorAll('.sui-styled-select-option');
|
|
2566
|
+
const placeholder = sel.getAttribute('data-placeholder') || '';
|
|
2567
|
+
let focusIdx = -1;
|
|
2565
2568
|
|
|
2566
2569
|
if (!trigger || !menu) return;
|
|
2567
2570
|
|
|
2568
2571
|
// Set initial value
|
|
2569
|
-
|
|
2572
|
+
const selected = sel.querySelector('.sui-styled-select-option.selected');
|
|
2570
2573
|
if (selected && valueEl) {
|
|
2571
2574
|
valueEl.textContent = selected.textContent;
|
|
2572
2575
|
valueEl.classList.remove('sui-styled-select-placeholder');
|
|
@@ -2609,7 +2612,7 @@ const SoftUI = (() => {
|
|
|
2609
2612
|
|
|
2610
2613
|
// Keyboard navigation
|
|
2611
2614
|
trigger.addEventListener('keydown', function(e) {
|
|
2612
|
-
|
|
2615
|
+
const isOpen = sel.classList.contains('open');
|
|
2613
2616
|
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
2614
2617
|
e.preventDefault();
|
|
2615
2618
|
if (!isOpen) { sel.classList.add('open'); focusIdx = -1; }
|
|
@@ -2643,58 +2646,129 @@ const SoftUI = (() => {
|
|
|
2643
2646
|
});
|
|
2644
2647
|
}
|
|
2645
2648
|
|
|
2649
|
+
function initDrawers() {
|
|
2650
|
+
document.querySelectorAll('.sui-drawer').forEach(function(backdrop) {
|
|
2651
|
+
const panel = backdrop.querySelector('.sui-sheet-bottom');
|
|
2652
|
+
const handle = backdrop.querySelector('.sui-drawer-handle');
|
|
2653
|
+
if (!panel || !handle) return;
|
|
2654
|
+
|
|
2655
|
+
let startY = 0;
|
|
2656
|
+
let startHeight = 0;
|
|
2657
|
+
let dragging = false;
|
|
2658
|
+
|
|
2659
|
+
function onStart(e) {
|
|
2660
|
+
dragging = true;
|
|
2661
|
+
startY = e.touches ? e.touches[0].clientY : e.clientY;
|
|
2662
|
+
startHeight = panel.getBoundingClientRect().height;
|
|
2663
|
+
panel.style.transition = 'none';
|
|
2664
|
+
document.body.style.userSelect = 'none';
|
|
2665
|
+
}
|
|
2666
|
+
|
|
2667
|
+
function onMove(e) {
|
|
2668
|
+
if (!dragging) return;
|
|
2669
|
+
const clientY = e.touches ? e.touches[0].clientY : e.clientY;
|
|
2670
|
+
const delta = startY - clientY;
|
|
2671
|
+
const newHeight = Math.max(0, startHeight + delta);
|
|
2672
|
+
const maxHeight = window.innerHeight * 0.85;
|
|
2673
|
+
panel.style.height = Math.min(newHeight, maxHeight) + 'px';
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
function onEnd() {
|
|
2677
|
+
if (!dragging) return;
|
|
2678
|
+
dragging = false;
|
|
2679
|
+
panel.style.transition = '';
|
|
2680
|
+
document.body.style.userSelect = '';
|
|
2681
|
+
|
|
2682
|
+
const currentHeight = panel.getBoundingClientRect().height;
|
|
2683
|
+
const vh = window.innerHeight;
|
|
2684
|
+
|
|
2685
|
+
// Snap points or dismiss
|
|
2686
|
+
const snapPoints = backdrop.getAttribute('data-snap');
|
|
2687
|
+
if (snapPoints) {
|
|
2688
|
+
const points = snapPoints.split(',').map(function(p) { return parseFloat(p) / 100 * vh; });
|
|
2689
|
+
points.push(0); // dismiss point
|
|
2690
|
+
let closest = points[0];
|
|
2691
|
+
let minDist = Math.abs(currentHeight - closest);
|
|
2692
|
+
points.forEach(function(p) {
|
|
2693
|
+
const dist = Math.abs(currentHeight - p);
|
|
2694
|
+
if (dist < minDist) { minDist = dist; closest = p; }
|
|
2695
|
+
});
|
|
2696
|
+
if (closest === 0) {
|
|
2697
|
+
SoftUI.sheet(backdrop).close();
|
|
2698
|
+
panel.style.height = '';
|
|
2699
|
+
} else {
|
|
2700
|
+
panel.style.height = closest + 'px';
|
|
2701
|
+
}
|
|
2702
|
+
} else {
|
|
2703
|
+
// No snap points — dismiss if dragged below 30% of starting height
|
|
2704
|
+
if (currentHeight < startHeight * 0.3) {
|
|
2705
|
+
SoftUI.sheet(backdrop).close();
|
|
2706
|
+
panel.style.height = '';
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
handle.addEventListener('mousedown', onStart);
|
|
2712
|
+
handle.addEventListener('touchstart', onStart, { passive: true });
|
|
2713
|
+
document.addEventListener('mousemove', onMove);
|
|
2714
|
+
document.addEventListener('touchmove', onMove, { passive: false });
|
|
2715
|
+
document.addEventListener('mouseup', onEnd);
|
|
2716
|
+
document.addEventListener('touchend', onEnd);
|
|
2717
|
+
});
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2646
2720
|
function initDataTables() {
|
|
2647
2721
|
document.querySelectorAll('.sui-datatable').forEach(function(dt) {
|
|
2648
|
-
|
|
2722
|
+
const table = dt.querySelector('.sui-table');
|
|
2649
2723
|
if (!table) return;
|
|
2650
2724
|
|
|
2651
|
-
|
|
2725
|
+
const tbody = table.querySelector('tbody');
|
|
2652
2726
|
if (!tbody) return;
|
|
2653
2727
|
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2728
|
+
const allRows = Array.prototype.slice.call(tbody.querySelectorAll('tr'));
|
|
2729
|
+
let filteredRows = allRows.slice();
|
|
2730
|
+
let currentPage = 1;
|
|
2657
2731
|
|
|
2658
2732
|
// Per-page selector (supports native <select> and .sui-styled-select)
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2733
|
+
const perpageNative = dt.querySelector('.sui-datatable-perpage select');
|
|
2734
|
+
const perpageStyled = dt.querySelector('.sui-datatable-perpage .sui-styled-select');
|
|
2735
|
+
const perpageSelect = perpageNative || perpageStyled;
|
|
2662
2736
|
function getPerpageValue() {
|
|
2663
2737
|
if (perpageNative) return parseInt(perpageNative.value, 10);
|
|
2664
2738
|
if (perpageStyled) return parseInt(perpageStyled.getAttribute('data-value') || '', 10);
|
|
2665
2739
|
return allRows.length;
|
|
2666
2740
|
}
|
|
2667
|
-
|
|
2741
|
+
let perPage = perpageSelect ? getPerpageValue() : allRows.length;
|
|
2668
2742
|
|
|
2669
2743
|
// Info & pagination elements
|
|
2670
|
-
|
|
2671
|
-
|
|
2744
|
+
const infoEl = dt.querySelector('.sui-datatable-info');
|
|
2745
|
+
const paginationEl = dt.querySelector('.sui-datatable-pagination');
|
|
2672
2746
|
|
|
2673
2747
|
// Search input
|
|
2674
|
-
|
|
2748
|
+
const searchInput = dt.querySelector('.sui-datatable-search input');
|
|
2675
2749
|
|
|
2676
2750
|
function render() {
|
|
2677
|
-
|
|
2678
|
-
|
|
2751
|
+
const total = filteredRows.length;
|
|
2752
|
+
const totalPages = perPage > 0 ? Math.ceil(total / perPage) : 1;
|
|
2679
2753
|
if (currentPage > totalPages) currentPage = totalPages;
|
|
2680
2754
|
if (currentPage < 1) currentPage = 1;
|
|
2681
2755
|
|
|
2682
|
-
|
|
2683
|
-
|
|
2756
|
+
const start = (currentPage - 1) * perPage;
|
|
2757
|
+
const end = Math.min(start + perPage, total);
|
|
2684
2758
|
|
|
2685
2759
|
// Hide all rows, then show only current page
|
|
2686
2760
|
allRows.forEach(function(row) { row.style.display = 'none'; });
|
|
2687
|
-
for (
|
|
2761
|
+
for (let i = start; i < end; i++) {
|
|
2688
2762
|
filteredRows[i].style.display = '';
|
|
2689
2763
|
}
|
|
2690
2764
|
|
|
2691
2765
|
// Show empty message if no results
|
|
2692
|
-
|
|
2766
|
+
let emptyRow = tbody.querySelector('.sui-datatable-empty-row');
|
|
2693
2767
|
if (total === 0) {
|
|
2694
2768
|
if (!emptyRow) {
|
|
2695
2769
|
emptyRow = document.createElement('tr');
|
|
2696
2770
|
emptyRow.className = 'sui-datatable-empty-row';
|
|
2697
|
-
|
|
2771
|
+
const td = document.createElement('td');
|
|
2698
2772
|
td.className = 'sui-datatable-empty';
|
|
2699
2773
|
td.colSpan = table.querySelectorAll('thead th').length;
|
|
2700
2774
|
td.textContent = 'No matching records found.';
|
|
@@ -2719,7 +2793,7 @@ const SoftUI = (() => {
|
|
|
2719
2793
|
if (paginationEl) {
|
|
2720
2794
|
paginationEl.innerHTML = '';
|
|
2721
2795
|
|
|
2722
|
-
|
|
2796
|
+
const prevBtn = document.createElement('button');
|
|
2723
2797
|
prevBtn.textContent = '\u2039';
|
|
2724
2798
|
prevBtn.disabled = currentPage <= 1;
|
|
2725
2799
|
prevBtn.addEventListener('click', function() {
|
|
@@ -2727,9 +2801,9 @@ const SoftUI = (() => {
|
|
|
2727
2801
|
});
|
|
2728
2802
|
paginationEl.appendChild(prevBtn);
|
|
2729
2803
|
|
|
2730
|
-
for (
|
|
2804
|
+
for (let p = 1; p <= totalPages; p++) {
|
|
2731
2805
|
(function(page) {
|
|
2732
|
-
|
|
2806
|
+
const btn = document.createElement('button');
|
|
2733
2807
|
btn.textContent = page;
|
|
2734
2808
|
if (page === currentPage) btn.className = 'active';
|
|
2735
2809
|
btn.addEventListener('click', function() {
|
|
@@ -2740,7 +2814,7 @@ const SoftUI = (() => {
|
|
|
2740
2814
|
})(p);
|
|
2741
2815
|
}
|
|
2742
2816
|
|
|
2743
|
-
|
|
2817
|
+
const nextBtn = document.createElement('button');
|
|
2744
2818
|
nextBtn.textContent = '\u203A';
|
|
2745
2819
|
nextBtn.disabled = currentPage >= totalPages;
|
|
2746
2820
|
nextBtn.addEventListener('click', function() {
|
|
@@ -2751,23 +2825,23 @@ const SoftUI = (() => {
|
|
|
2751
2825
|
}
|
|
2752
2826
|
|
|
2753
2827
|
// Filter elements (supports <select> and .sui-dropdown)
|
|
2754
|
-
|
|
2828
|
+
const filterEls = dt.querySelectorAll('.sui-datatable-filter');
|
|
2755
2829
|
|
|
2756
2830
|
function getFilterValue(el) {
|
|
2757
2831
|
if (el.tagName === 'SELECT') return el.value;
|
|
2758
2832
|
// Dropdown-based filter: read from active item
|
|
2759
|
-
|
|
2833
|
+
const active = el.querySelector('.sui-dropdown-item.active');
|
|
2760
2834
|
return active ? (active.getAttribute('data-value') || '') : '';
|
|
2761
2835
|
}
|
|
2762
2836
|
|
|
2763
2837
|
function applyFilters() {
|
|
2764
|
-
|
|
2838
|
+
const query = searchInput ? searchInput.value.toLowerCase().trim() : '';
|
|
2765
2839
|
filteredRows = allRows.filter(function(row) {
|
|
2766
2840
|
if (query && row.textContent.toLowerCase().indexOf(query) === -1) return false;
|
|
2767
|
-
|
|
2841
|
+
let pass = true;
|
|
2768
2842
|
filterEls.forEach(function(el) {
|
|
2769
|
-
|
|
2770
|
-
|
|
2843
|
+
const attr = el.getAttribute('data-filter-attr') || 'data-status';
|
|
2844
|
+
const val = getFilterValue(el);
|
|
2771
2845
|
if (val && row.getAttribute(attr) !== val) pass = false;
|
|
2772
2846
|
});
|
|
2773
2847
|
return pass;
|
|
@@ -2793,11 +2867,11 @@ const SoftUI = (() => {
|
|
|
2793
2867
|
el.querySelectorAll('.sui-dropdown-item').forEach(function(i) { i.classList.remove('active'); });
|
|
2794
2868
|
item.classList.add('active');
|
|
2795
2869
|
// Update label
|
|
2796
|
-
|
|
2870
|
+
const label = el.querySelector('.sui-datatable-filter-label');
|
|
2797
2871
|
if (label) label.textContent = item.textContent;
|
|
2798
2872
|
// Close dropdown
|
|
2799
2873
|
el.classList.remove('open');
|
|
2800
|
-
|
|
2874
|
+
const toggle = el.querySelector('.sui-dropdown-toggle');
|
|
2801
2875
|
if (toggle) toggle.setAttribute('aria-expanded', 'false');
|
|
2802
2876
|
applyFilters();
|
|
2803
2877
|
});
|
|
@@ -2815,14 +2889,14 @@ const SoftUI = (() => {
|
|
|
2815
2889
|
}
|
|
2816
2890
|
|
|
2817
2891
|
// Sortable headers (unsorted → asc → desc → unsorted)
|
|
2818
|
-
|
|
2892
|
+
const ths = table.querySelectorAll('th[data-sort]');
|
|
2819
2893
|
ths.forEach(function(th) {
|
|
2820
2894
|
th.addEventListener('click', function() {
|
|
2821
|
-
|
|
2822
|
-
|
|
2895
|
+
const colIndex = Array.prototype.indexOf.call(th.parentElement.children, th);
|
|
2896
|
+
const type = th.getAttribute('data-sort');
|
|
2823
2897
|
|
|
2824
2898
|
// Cycle: unsorted → asc → desc → unsorted
|
|
2825
|
-
|
|
2899
|
+
let dir;
|
|
2826
2900
|
if (th.classList.contains('sort-asc')) {
|
|
2827
2901
|
dir = 'desc';
|
|
2828
2902
|
} else if (th.classList.contains('sort-desc')) {
|
|
@@ -2840,12 +2914,12 @@ const SoftUI = (() => {
|
|
|
2840
2914
|
} else {
|
|
2841
2915
|
th.classList.add(dir === 'asc' ? 'sort-asc' : 'sort-desc');
|
|
2842
2916
|
filteredRows.sort(function(a, b) {
|
|
2843
|
-
|
|
2844
|
-
|
|
2917
|
+
const aText = a.children[colIndex] ? a.children[colIndex].textContent.trim() : '';
|
|
2918
|
+
const bText = b.children[colIndex] ? b.children[colIndex].textContent.trim() : '';
|
|
2845
2919
|
|
|
2846
2920
|
if (type === 'number') {
|
|
2847
|
-
|
|
2848
|
-
|
|
2921
|
+
const aNum = parseFloat(aText.replace(/[^0-9.\-]/g, '')) || 0;
|
|
2922
|
+
const bNum = parseFloat(bText.replace(/[^0-9.\-]/g, '')) || 0;
|
|
2849
2923
|
return dir === 'asc' ? aNum - bNum : bNum - aNum;
|
|
2850
2924
|
}
|
|
2851
2925
|
|
|
@@ -2868,11 +2942,11 @@ const SoftUI = (() => {
|
|
|
2868
2942
|
function initDragDrop() {
|
|
2869
2943
|
// ── Sortable Lists ──
|
|
2870
2944
|
document.querySelectorAll('.sui-sortable').forEach(function(list) {
|
|
2871
|
-
|
|
2945
|
+
let dragItem = null;
|
|
2872
2946
|
|
|
2873
2947
|
list.querySelectorAll('.sui-sortable-item').forEach(function(item) {
|
|
2874
|
-
|
|
2875
|
-
|
|
2948
|
+
const handle = item.querySelector('.sui-sortable-handle');
|
|
2949
|
+
const dragTarget = handle || item;
|
|
2876
2950
|
|
|
2877
2951
|
dragTarget.setAttribute('draggable', 'true');
|
|
2878
2952
|
if (handle) item.classList.add('has-handle');
|
|
@@ -2899,9 +2973,9 @@ const SoftUI = (() => {
|
|
|
2899
2973
|
e.preventDefault();
|
|
2900
2974
|
item.classList.remove('drag-over');
|
|
2901
2975
|
if (!dragItem || dragItem === item) return;
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2976
|
+
const items = Array.prototype.slice.call(list.children);
|
|
2977
|
+
const fromIndex = items.indexOf(dragItem);
|
|
2978
|
+
const toIndex = items.indexOf(item);
|
|
2905
2979
|
if (fromIndex < toIndex) {
|
|
2906
2980
|
list.insertBefore(dragItem, item.nextSibling);
|
|
2907
2981
|
} else {
|
|
@@ -2921,7 +2995,7 @@ const SoftUI = (() => {
|
|
|
2921
2995
|
|
|
2922
2996
|
// ── Kanban ──
|
|
2923
2997
|
document.querySelectorAll('.sui-kanban').forEach(function(kanban) {
|
|
2924
|
-
|
|
2998
|
+
let dragCard = null;
|
|
2925
2999
|
|
|
2926
3000
|
kanban.querySelectorAll('.sui-kanban-card').forEach(function(card) {
|
|
2927
3001
|
card.setAttribute('draggable', 'true');
|
|
@@ -2949,7 +3023,7 @@ const SoftUI = (() => {
|
|
|
2949
3023
|
|
|
2950
3024
|
// Position among siblings
|
|
2951
3025
|
if (!dragCard) return;
|
|
2952
|
-
|
|
3026
|
+
const afterEl = getDragAfterElement(col, e.clientY);
|
|
2953
3027
|
if (afterEl) {
|
|
2954
3028
|
col.insertBefore(dragCard, afterEl);
|
|
2955
3029
|
} else {
|
|
@@ -2971,14 +3045,14 @@ const SoftUI = (() => {
|
|
|
2971
3045
|
});
|
|
2972
3046
|
|
|
2973
3047
|
function getDragAfterElement(container, y) {
|
|
2974
|
-
|
|
3048
|
+
const els = Array.prototype.slice.call(
|
|
2975
3049
|
container.querySelectorAll('.sui-kanban-card:not(.dragging)')
|
|
2976
3050
|
);
|
|
2977
|
-
|
|
2978
|
-
|
|
3051
|
+
let closest = null;
|
|
3052
|
+
let closestOffset = Number.NEGATIVE_INFINITY;
|
|
2979
3053
|
els.forEach(function(el) {
|
|
2980
|
-
|
|
2981
|
-
|
|
3054
|
+
const box = el.getBoundingClientRect();
|
|
3055
|
+
const offset = y - box.top - box.height / 2;
|
|
2982
3056
|
if (offset < 0 && offset > closestOffset) {
|
|
2983
3057
|
closestOffset = offset;
|
|
2984
3058
|
closest = el;
|
|
@@ -2990,7 +3064,7 @@ const SoftUI = (() => {
|
|
|
2990
3064
|
// ── Drop Zone ──
|
|
2991
3065
|
document.querySelectorAll('.sui-dropzone').forEach(function(zone) {
|
|
2992
3066
|
// Click-to-upload: create hidden file input
|
|
2993
|
-
|
|
3067
|
+
const fileInput = document.createElement('input');
|
|
2994
3068
|
fileInput.type = 'file';
|
|
2995
3069
|
fileInput.multiple = true;
|
|
2996
3070
|
fileInput.style.display = 'none';
|
|
@@ -3002,16 +3076,16 @@ const SoftUI = (() => {
|
|
|
3002
3076
|
});
|
|
3003
3077
|
|
|
3004
3078
|
fileInput.addEventListener('change', function() {
|
|
3005
|
-
|
|
3079
|
+
const files = fileInput.files;
|
|
3006
3080
|
if (!files.length) return;
|
|
3007
|
-
|
|
3081
|
+
let fileList = zone.querySelector('.sui-dropzone-files');
|
|
3008
3082
|
if (!fileList) {
|
|
3009
3083
|
fileList = document.createElement('div');
|
|
3010
3084
|
fileList.className = 'sui-dropzone-files';
|
|
3011
3085
|
zone.appendChild(fileList);
|
|
3012
3086
|
}
|
|
3013
3087
|
Array.prototype.slice.call(files).forEach(function(file) {
|
|
3014
|
-
|
|
3088
|
+
const item = document.createElement('div');
|
|
3015
3089
|
item.className = 'sui-dropzone-file';
|
|
3016
3090
|
item.innerHTML = '<span>' + file.name + '</span><button class="sui-dropzone-file-remove" type="button">×</button>';
|
|
3017
3091
|
item.querySelector('.sui-dropzone-file-remove').addEventListener('click', function(ev) {
|
|
@@ -3038,16 +3112,16 @@ const SoftUI = (() => {
|
|
|
3038
3112
|
zone.addEventListener('drop', function(e) {
|
|
3039
3113
|
e.preventDefault();
|
|
3040
3114
|
zone.classList.remove('drag-over');
|
|
3041
|
-
|
|
3115
|
+
const files = e.dataTransfer.files;
|
|
3042
3116
|
if (!files.length) return;
|
|
3043
|
-
|
|
3117
|
+
let fileList = zone.querySelector('.sui-dropzone-files');
|
|
3044
3118
|
if (!fileList) {
|
|
3045
3119
|
fileList = document.createElement('div');
|
|
3046
3120
|
fileList.className = 'sui-dropzone-files';
|
|
3047
3121
|
zone.appendChild(fileList);
|
|
3048
3122
|
}
|
|
3049
3123
|
Array.prototype.slice.call(files).forEach(function(file) {
|
|
3050
|
-
|
|
3124
|
+
const item = document.createElement('div');
|
|
3051
3125
|
item.className = 'sui-dropzone-file';
|
|
3052
3126
|
item.innerHTML = '<span>' + file.name + '</span><button class="sui-dropzone-file-remove" type="button">×</button>';
|
|
3053
3127
|
item.querySelector('.sui-dropzone-file-remove').addEventListener('click', function(ev) {
|
|
@@ -3064,16 +3138,16 @@ const SoftUI = (() => {
|
|
|
3064
3138
|
// Sidebar — collapsible toggle
|
|
3065
3139
|
// =========================================
|
|
3066
3140
|
document.addEventListener('click', function(e) {
|
|
3067
|
-
|
|
3141
|
+
const btn = e.target.closest('[data-sidebar-toggle]');
|
|
3068
3142
|
if (!btn) return;
|
|
3069
|
-
|
|
3143
|
+
const sidebar = btn.closest('.sui-sidebar');
|
|
3070
3144
|
if (sidebar) {
|
|
3071
3145
|
sidebar.classList.toggle('sui-sidebar-collapsed');
|
|
3072
3146
|
}
|
|
3073
3147
|
});
|
|
3074
3148
|
|
|
3075
3149
|
function sidebar(selector) {
|
|
3076
|
-
|
|
3150
|
+
const el = typeof selector === 'string' ? document.querySelector(selector) : selector;
|
|
3077
3151
|
if (!el) return null;
|
|
3078
3152
|
|
|
3079
3153
|
function collapse() { el.classList.add('sui-sidebar-collapsed'); }
|
|
@@ -3088,34 +3162,34 @@ const SoftUI = (() => {
|
|
|
3088
3162
|
// Rating
|
|
3089
3163
|
// =========================================
|
|
3090
3164
|
function ratingIsHalf(star, e) {
|
|
3091
|
-
|
|
3165
|
+
const rect = star.getBoundingClientRect();
|
|
3092
3166
|
return e.clientX < rect.left + rect.width / 2;
|
|
3093
3167
|
}
|
|
3094
3168
|
|
|
3095
3169
|
function ratingEnsureDualSvg(star) {
|
|
3096
|
-
|
|
3170
|
+
const svgs = star.querySelectorAll('svg');
|
|
3097
3171
|
if (svgs.length < 2) {
|
|
3098
|
-
|
|
3172
|
+
const clone = svgs[0].cloneNode(true);
|
|
3099
3173
|
star.appendChild(clone);
|
|
3100
3174
|
}
|
|
3101
3175
|
}
|
|
3102
3176
|
|
|
3103
3177
|
function ratingResetSvg(star) {
|
|
3104
|
-
|
|
3178
|
+
const svgs = star.querySelectorAll('svg');
|
|
3105
3179
|
if (svgs.length > 1) {
|
|
3106
|
-
for (
|
|
3180
|
+
for (let i = svgs.length - 1; i > 0; i--) { svgs[i].remove(); }
|
|
3107
3181
|
}
|
|
3108
3182
|
}
|
|
3109
3183
|
|
|
3110
3184
|
document.addEventListener('click', function(e) {
|
|
3111
|
-
|
|
3185
|
+
const star = e.target.closest('.sui-rating:not(.sui-rating-readonly) .sui-rating-star');
|
|
3112
3186
|
if (!star) return;
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3187
|
+
const rating = star.closest('.sui-rating');
|
|
3188
|
+
const stars = Array.from(rating.querySelectorAll('.sui-rating-star'));
|
|
3189
|
+
const index = stars.indexOf(star);
|
|
3190
|
+
const allowHalf = rating.classList.contains('sui-rating-half');
|
|
3191
|
+
const isHalf = allowHalf && ratingIsHalf(star, e);
|
|
3192
|
+
const value = isHalf ? index + 0.5 : index + 1;
|
|
3119
3193
|
stars.forEach(function(s, i) {
|
|
3120
3194
|
s.classList.remove('active', 'half', 'hover', 'hover-half');
|
|
3121
3195
|
ratingResetSvg(s);
|
|
@@ -3135,13 +3209,13 @@ const SoftUI = (() => {
|
|
|
3135
3209
|
});
|
|
3136
3210
|
|
|
3137
3211
|
document.addEventListener('mousemove', function(e) {
|
|
3138
|
-
|
|
3212
|
+
const star = e.target.closest('.sui-rating:not(.sui-rating-readonly) .sui-rating-star');
|
|
3139
3213
|
if (!star) return;
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3214
|
+
const rating = star.closest('.sui-rating');
|
|
3215
|
+
const stars = Array.from(rating.querySelectorAll('.sui-rating-star'));
|
|
3216
|
+
const index = stars.indexOf(star);
|
|
3217
|
+
const allowHalf = rating.classList.contains('sui-rating-half');
|
|
3218
|
+
const isHalf = allowHalf && ratingIsHalf(star, e);
|
|
3145
3219
|
stars.forEach(function(s, i) {
|
|
3146
3220
|
s.classList.remove('hover', 'hover-half');
|
|
3147
3221
|
ratingResetSvg(s);
|
|
@@ -3159,10 +3233,10 @@ const SoftUI = (() => {
|
|
|
3159
3233
|
});
|
|
3160
3234
|
|
|
3161
3235
|
document.addEventListener('mouseout', function(e) {
|
|
3162
|
-
|
|
3236
|
+
const star = e.target.closest('.sui-rating:not(.sui-rating-readonly) .sui-rating-star');
|
|
3163
3237
|
if (!star) return;
|
|
3164
|
-
|
|
3165
|
-
|
|
3238
|
+
const rating = star.closest('.sui-rating');
|
|
3239
|
+
const stars = Array.from(rating.querySelectorAll('.sui-rating-star'));
|
|
3166
3240
|
stars.forEach(function(s) {
|
|
3167
3241
|
s.classList.remove('hover', 'hover-half');
|
|
3168
3242
|
if (!s.classList.contains('half')) { ratingResetSvg(s); }
|
|
@@ -3173,14 +3247,14 @@ const SoftUI = (() => {
|
|
|
3173
3247
|
// Color Picker
|
|
3174
3248
|
// =========================================
|
|
3175
3249
|
document.addEventListener('click', function(e) {
|
|
3176
|
-
|
|
3250
|
+
const swatch = e.target.closest('.sui-color-picker .sui-color-swatch');
|
|
3177
3251
|
if (!swatch) return;
|
|
3178
|
-
|
|
3252
|
+
const picker = swatch.closest('.sui-color-picker');
|
|
3179
3253
|
picker.querySelectorAll('.sui-color-swatch').forEach(function(s) {
|
|
3180
3254
|
s.classList.remove('active');
|
|
3181
3255
|
});
|
|
3182
3256
|
swatch.classList.add('active');
|
|
3183
|
-
|
|
3257
|
+
const color = swatch.getAttribute('data-color') || swatch.style.background || swatch.style.backgroundColor;
|
|
3184
3258
|
picker.setAttribute('data-value', color);
|
|
3185
3259
|
picker.dispatchEvent(new CustomEvent('sui-color-change', { detail: { color: color } }));
|
|
3186
3260
|
});
|
|
@@ -3189,24 +3263,24 @@ const SoftUI = (() => {
|
|
|
3189
3263
|
// Color Spectrum Picker
|
|
3190
3264
|
// =========================================
|
|
3191
3265
|
function initSpectrumPickers() {
|
|
3192
|
-
|
|
3266
|
+
const pickers = document.querySelectorAll('.sui-color-spectrum');
|
|
3193
3267
|
pickers.forEach(function(picker) { initSpectrum(picker); });
|
|
3194
3268
|
}
|
|
3195
3269
|
|
|
3196
3270
|
function initSpectrum(picker) {
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3271
|
+
const canvasWrap = picker.querySelector('.sui-color-spectrum-canvas');
|
|
3272
|
+
const canvas = canvasWrap.querySelector('canvas');
|
|
3273
|
+
const ctx = canvas.getContext('2d');
|
|
3274
|
+
const cursor = canvasWrap.querySelector('.sui-color-spectrum-cursor');
|
|
3275
|
+
const hueBar = picker.querySelector('.sui-color-spectrum-hue');
|
|
3276
|
+
const hueCursor = picker.querySelector('.sui-color-spectrum-hue-cursor');
|
|
3277
|
+
const preview = picker.querySelector('.sui-color-spectrum-preview');
|
|
3278
|
+
const hexInput = picker.querySelector('.sui-color-spectrum-hex input');
|
|
3279
|
+
const rInput = picker.querySelector('input[data-channel="r"]');
|
|
3280
|
+
const gInput = picker.querySelector('input[data-channel="g"]');
|
|
3281
|
+
const bInput = picker.querySelector('input[data-channel="b"]');
|
|
3282
|
+
|
|
3283
|
+
let hue = 0, sat = 1, val = 1;
|
|
3210
3284
|
|
|
3211
3285
|
function resizeCanvas() {
|
|
3212
3286
|
canvas.width = canvasWrap.offsetWidth;
|
|
@@ -3215,12 +3289,12 @@ const SoftUI = (() => {
|
|
|
3215
3289
|
}
|
|
3216
3290
|
|
|
3217
3291
|
function hsvToRgb(h, s, v) {
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3292
|
+
const i = Math.floor(h / 60) % 6;
|
|
3293
|
+
const f = h / 60 - Math.floor(h / 60);
|
|
3294
|
+
const p = v * (1 - s);
|
|
3295
|
+
const q = v * (1 - f * s);
|
|
3296
|
+
const t = v * (1 - (1 - f) * s);
|
|
3297
|
+
let r, g, b;
|
|
3224
3298
|
switch (i) {
|
|
3225
3299
|
case 0: r = v; g = t; b = p; break;
|
|
3226
3300
|
case 1: r = q; g = v; b = p; break;
|
|
@@ -3239,16 +3313,17 @@ const SoftUI = (() => {
|
|
|
3239
3313
|
function hexToRgb(hex) {
|
|
3240
3314
|
hex = hex.replace('#', '');
|
|
3241
3315
|
if (hex.length === 3) hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
|
|
3242
|
-
|
|
3316
|
+
const n = parseInt(hex, 16);
|
|
3243
3317
|
return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
|
|
3244
3318
|
}
|
|
3245
3319
|
|
|
3246
3320
|
function rgbToHsv(r, g, b) {
|
|
3247
3321
|
r /= 255; g /= 255; b /= 255;
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3322
|
+
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
3323
|
+
let h;
|
|
3324
|
+
const v = max;
|
|
3325
|
+
const d = max - min;
|
|
3326
|
+
const s = max === 0 ? 0 : d / max;
|
|
3252
3327
|
if (max === min) { h = 0; }
|
|
3253
3328
|
else {
|
|
3254
3329
|
switch (max) {
|
|
@@ -3262,17 +3337,17 @@ const SoftUI = (() => {
|
|
|
3262
3337
|
}
|
|
3263
3338
|
|
|
3264
3339
|
function drawSatVal() {
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3340
|
+
const w = canvas.width, h = canvas.height;
|
|
3341
|
+
const hueRgb = hsvToRgb(hue, 1, 1);
|
|
3342
|
+
const hueColor = 'rgb(' + hueRgb[0] + ',' + hueRgb[1] + ',' + hueRgb[2] + ')';
|
|
3268
3343
|
ctx.fillStyle = hueColor;
|
|
3269
3344
|
ctx.fillRect(0, 0, w, h);
|
|
3270
|
-
|
|
3345
|
+
const whiteGrad = ctx.createLinearGradient(0, 0, w, 0);
|
|
3271
3346
|
whiteGrad.addColorStop(0, 'rgba(255,255,255,1)');
|
|
3272
3347
|
whiteGrad.addColorStop(1, 'rgba(255,255,255,0)');
|
|
3273
3348
|
ctx.fillStyle = whiteGrad;
|
|
3274
3349
|
ctx.fillRect(0, 0, w, h);
|
|
3275
|
-
|
|
3350
|
+
const blackGrad = ctx.createLinearGradient(0, 0, 0, h);
|
|
3276
3351
|
blackGrad.addColorStop(0, 'rgba(0,0,0,0)');
|
|
3277
3352
|
blackGrad.addColorStop(1, 'rgba(0,0,0,1)');
|
|
3278
3353
|
ctx.fillStyle = blackGrad;
|
|
@@ -3280,8 +3355,8 @@ const SoftUI = (() => {
|
|
|
3280
3355
|
}
|
|
3281
3356
|
|
|
3282
3357
|
function updateUI() {
|
|
3283
|
-
|
|
3284
|
-
|
|
3358
|
+
const rgb = hsvToRgb(hue, sat, val);
|
|
3359
|
+
const hex = rgbToHex(rgb[0], rgb[1], rgb[2]);
|
|
3285
3360
|
if (preview) preview.style.background = hex;
|
|
3286
3361
|
if (hexInput) hexInput.value = hex.toUpperCase().slice(1);
|
|
3287
3362
|
if (rInput) rInput.value = rgb[0];
|
|
@@ -3291,7 +3366,7 @@ const SoftUI = (() => {
|
|
|
3291
3366
|
cursor.style.left = (sat * 100) + '%';
|
|
3292
3367
|
cursor.style.top = ((1 - val) * 100) + '%';
|
|
3293
3368
|
|
|
3294
|
-
|
|
3369
|
+
const hueRgb = hsvToRgb(hue, 1, 1);
|
|
3295
3370
|
hueCursor.style.left = (hue / 360 * 100) + '%';
|
|
3296
3371
|
hueCursor.style.background = 'rgb(' + hueRgb[0] + ',' + hueRgb[1] + ',' + hueRgb[2] + ')';
|
|
3297
3372
|
|
|
@@ -3301,9 +3376,9 @@ const SoftUI = (() => {
|
|
|
3301
3376
|
|
|
3302
3377
|
// Canvas drag
|
|
3303
3378
|
function onCanvasMove(e) {
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3379
|
+
const rect = canvasWrap.getBoundingClientRect();
|
|
3380
|
+
const x = (e.touches ? e.touches[0].clientX : e.clientX) - rect.left;
|
|
3381
|
+
const y = (e.touches ? e.touches[0].clientY : e.clientY) - rect.top;
|
|
3307
3382
|
sat = Math.max(0, Math.min(1, x / rect.width));
|
|
3308
3383
|
val = Math.max(0, Math.min(1, 1 - y / rect.height));
|
|
3309
3384
|
updateUI();
|
|
@@ -3329,8 +3404,8 @@ const SoftUI = (() => {
|
|
|
3329
3404
|
|
|
3330
3405
|
// Hue drag
|
|
3331
3406
|
function onHueMove(e) {
|
|
3332
|
-
|
|
3333
|
-
|
|
3407
|
+
const rect = hueBar.getBoundingClientRect();
|
|
3408
|
+
const x = (e.touches ? e.touches[0].clientX : e.clientX) - rect.left;
|
|
3334
3409
|
hue = Math.max(0, Math.min(360, x / rect.width * 360));
|
|
3335
3410
|
drawSatVal();
|
|
3336
3411
|
updateUI();
|
|
@@ -3357,10 +3432,10 @@ const SoftUI = (() => {
|
|
|
3357
3432
|
// Hex input
|
|
3358
3433
|
if (hexInput) {
|
|
3359
3434
|
hexInput.addEventListener('input', function() {
|
|
3360
|
-
|
|
3435
|
+
const v = hexInput.value.replace('#', '');
|
|
3361
3436
|
if (v.length === 6 && /^[0-9A-Fa-f]{6}$/.test(v)) {
|
|
3362
|
-
|
|
3363
|
-
|
|
3437
|
+
const rgb = hexToRgb(v);
|
|
3438
|
+
const hsv = rgbToHsv(rgb[0], rgb[1], rgb[2]);
|
|
3364
3439
|
hue = hsv[0]; sat = hsv[1]; val = hsv[2];
|
|
3365
3440
|
drawSatVal();
|
|
3366
3441
|
updateUI();
|
|
@@ -3370,13 +3445,13 @@ const SoftUI = (() => {
|
|
|
3370
3445
|
|
|
3371
3446
|
// RGB inputs
|
|
3372
3447
|
function onRgbInput() {
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3448
|
+
let r = parseInt(rInput.value) || 0;
|
|
3449
|
+
let g = parseInt(gInput.value) || 0;
|
|
3450
|
+
let b = parseInt(bInput.value) || 0;
|
|
3376
3451
|
r = Math.max(0, Math.min(255, r));
|
|
3377
3452
|
g = Math.max(0, Math.min(255, g));
|
|
3378
3453
|
b = Math.max(0, Math.min(255, b));
|
|
3379
|
-
|
|
3454
|
+
const hsv = rgbToHsv(r, g, b);
|
|
3380
3455
|
hue = hsv[0]; sat = hsv[1]; val = hsv[2];
|
|
3381
3456
|
drawSatVal();
|
|
3382
3457
|
updateUI();
|
|
@@ -3387,9 +3462,9 @@ const SoftUI = (() => {
|
|
|
3387
3462
|
if (bInput) bInput.addEventListener('input', onRgbInput);
|
|
3388
3463
|
|
|
3389
3464
|
// Init from data-color attribute or default
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3465
|
+
const initColor = picker.getAttribute('data-color') || '#5B54E0';
|
|
3466
|
+
const initRgb = hexToRgb(initColor);
|
|
3467
|
+
const initHsv = rgbToHsv(initRgb[0], initRgb[1], initRgb[2]);
|
|
3393
3468
|
hue = initHsv[0]; sat = initHsv[1]; val = initHsv[2];
|
|
3394
3469
|
|
|
3395
3470
|
resizeCanvas();
|
|
@@ -3412,7 +3487,7 @@ const SoftUI = (() => {
|
|
|
3412
3487
|
return (bytes / 1048576).toFixed(1) + ' MB';
|
|
3413
3488
|
}
|
|
3414
3489
|
|
|
3415
|
-
|
|
3490
|
+
const fileIcons = {
|
|
3416
3491
|
file: '<svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>',
|
|
3417
3492
|
image: '<svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>',
|
|
3418
3493
|
pdf: '<svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="9" y1="15" x2="15" y2="15"/><line x1="9" y1="18" x2="13" y2="18"/><line x1="9" y1="12" x2="11" y2="12"/></svg>',
|
|
@@ -3424,8 +3499,8 @@ const SoftUI = (() => {
|
|
|
3424
3499
|
};
|
|
3425
3500
|
|
|
3426
3501
|
function getFileType(file) {
|
|
3427
|
-
|
|
3428
|
-
|
|
3502
|
+
const type = file.type || '';
|
|
3503
|
+
const ext = file.name.split('.').pop().toLowerCase();
|
|
3429
3504
|
if (type.startsWith('image/')) return 'image';
|
|
3430
3505
|
if (type === 'application/pdf' || ext === 'pdf') return 'pdf';
|
|
3431
3506
|
if (type.startsWith('video/')) return 'video';
|
|
@@ -3445,14 +3520,14 @@ const SoftUI = (() => {
|
|
|
3445
3520
|
}
|
|
3446
3521
|
|
|
3447
3522
|
function getOrCreateContainer(zone, cls) {
|
|
3448
|
-
|
|
3523
|
+
let wrap = zone.closest('.sui-file-upload-wrap');
|
|
3449
3524
|
if (!wrap) {
|
|
3450
3525
|
wrap = document.createElement('div');
|
|
3451
3526
|
wrap.className = 'sui-file-upload-wrap';
|
|
3452
3527
|
zone.parentNode.insertBefore(wrap, zone);
|
|
3453
3528
|
wrap.appendChild(zone);
|
|
3454
3529
|
}
|
|
3455
|
-
|
|
3530
|
+
let container = wrap.querySelector('.' + cls);
|
|
3456
3531
|
if (!container) {
|
|
3457
3532
|
container = document.createElement('div');
|
|
3458
3533
|
container.className = cls;
|
|
@@ -3462,11 +3537,11 @@ const SoftUI = (() => {
|
|
|
3462
3537
|
}
|
|
3463
3538
|
|
|
3464
3539
|
function renderFileList(zone, files, append) {
|
|
3465
|
-
|
|
3540
|
+
const container = getOrCreateContainer(zone, 'sui-file-list');
|
|
3466
3541
|
if (!append) container.innerHTML = '';
|
|
3467
|
-
for (
|
|
3468
|
-
|
|
3469
|
-
|
|
3542
|
+
for (let i = 0; i < files.length; i++) {
|
|
3543
|
+
const f = files[i];
|
|
3544
|
+
const item = document.createElement('div');
|
|
3470
3545
|
item.className = 'sui-file-item';
|
|
3471
3546
|
item.innerHTML =
|
|
3472
3547
|
'<div class="sui-file-item-icon ' + getFileIconClass(f) + '">' + getFileIcon(f) + '</div>' +
|
|
@@ -3480,11 +3555,11 @@ const SoftUI = (() => {
|
|
|
3480
3555
|
}
|
|
3481
3556
|
|
|
3482
3557
|
function renderFileProgress(zone, files, append) {
|
|
3483
|
-
|
|
3558
|
+
const container = getOrCreateContainer(zone, 'sui-file-list');
|
|
3484
3559
|
if (!append) container.innerHTML = '';
|
|
3485
|
-
for (
|
|
3486
|
-
|
|
3487
|
-
|
|
3560
|
+
for (let i = 0; i < files.length; i++) {
|
|
3561
|
+
const f = files[i];
|
|
3562
|
+
const item = document.createElement('div');
|
|
3488
3563
|
item.className = 'sui-file-item';
|
|
3489
3564
|
item.innerHTML =
|
|
3490
3565
|
'<div class="sui-file-item-icon ' + getFileIconClass(f) + '">' + getFileIcon(f) + '</div>' +
|
|
@@ -3502,10 +3577,10 @@ const SoftUI = (() => {
|
|
|
3502
3577
|
}
|
|
3503
3578
|
|
|
3504
3579
|
function simulateProgress(item) {
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3580
|
+
const bar = item.querySelector('.sui-progress-bar');
|
|
3581
|
+
const status = item.querySelector('.sui-file-item-status');
|
|
3582
|
+
let pct = 0;
|
|
3583
|
+
const interval = setInterval(function() {
|
|
3509
3584
|
pct += Math.floor(Math.random() * 15) + 5;
|
|
3510
3585
|
if (pct >= 100) {
|
|
3511
3586
|
pct = 100;
|
|
@@ -3522,19 +3597,19 @@ const SoftUI = (() => {
|
|
|
3522
3597
|
}
|
|
3523
3598
|
|
|
3524
3599
|
function renderFilePreview(zone, files, append) {
|
|
3525
|
-
|
|
3600
|
+
const container = getOrCreateContainer(zone, 'sui-file-preview-grid');
|
|
3526
3601
|
if (!append) container.innerHTML = '';
|
|
3527
|
-
for (
|
|
3528
|
-
|
|
3602
|
+
for (let i = 0; i < files.length; i++) {
|
|
3603
|
+
const f = files[i];
|
|
3529
3604
|
if (!f.type || !f.type.startsWith('image/')) continue;
|
|
3530
|
-
|
|
3605
|
+
const item = document.createElement('div');
|
|
3531
3606
|
item.className = 'sui-file-preview-item';
|
|
3532
3607
|
item.innerHTML =
|
|
3533
3608
|
'<img alt="' + f.name + '">' +
|
|
3534
3609
|
'<button class="sui-file-preview-item-remove" aria-label="Remove">×</button>';
|
|
3535
3610
|
container.appendChild(item);
|
|
3536
3611
|
(function(img, file) {
|
|
3537
|
-
|
|
3612
|
+
const reader = new FileReader();
|
|
3538
3613
|
reader.onload = function(e) { img.src = e.target.result; };
|
|
3539
3614
|
reader.readAsDataURL(file);
|
|
3540
3615
|
})(item.querySelector('img'), f);
|
|
@@ -3544,12 +3619,12 @@ const SoftUI = (() => {
|
|
|
3544
3619
|
// Delegated change on file inputs
|
|
3545
3620
|
document.addEventListener('change', function(e) {
|
|
3546
3621
|
if (!e.target.matches('.sui-file-upload input[type="file"]')) return;
|
|
3547
|
-
|
|
3548
|
-
|
|
3622
|
+
const input = e.target;
|
|
3623
|
+
const zone = input.closest('.sui-file-upload');
|
|
3549
3624
|
if (!zone) return;
|
|
3550
|
-
|
|
3625
|
+
const files = Array.from(input.files);
|
|
3551
3626
|
if (!files.length) return;
|
|
3552
|
-
|
|
3627
|
+
const mode = zone.getAttribute('data-sui-upload') || 'list';
|
|
3553
3628
|
if (mode === 'preview') {
|
|
3554
3629
|
renderFilePreview(zone, files);
|
|
3555
3630
|
} else if (mode === 'progress') {
|
|
@@ -3562,34 +3637,34 @@ const SoftUI = (() => {
|
|
|
3562
3637
|
|
|
3563
3638
|
// Delegated remove clicks
|
|
3564
3639
|
document.addEventListener('click', function(e) {
|
|
3565
|
-
|
|
3640
|
+
const removeBtn = e.target.closest('.sui-file-item-remove, .sui-file-preview-item-remove');
|
|
3566
3641
|
if (!removeBtn) return;
|
|
3567
|
-
|
|
3642
|
+
const item = removeBtn.closest('.sui-file-item, .sui-file-preview-item');
|
|
3568
3643
|
if (item) item.remove();
|
|
3569
3644
|
});
|
|
3570
3645
|
|
|
3571
3646
|
// Dragover styling
|
|
3572
3647
|
document.addEventListener('dragover', function(e) {
|
|
3573
|
-
|
|
3648
|
+
const zone = e.target.closest('.sui-file-upload');
|
|
3574
3649
|
if (!zone) return;
|
|
3575
3650
|
e.preventDefault();
|
|
3576
3651
|
zone.classList.add('sui-file-upload-dragover');
|
|
3577
3652
|
});
|
|
3578
3653
|
|
|
3579
3654
|
document.addEventListener('dragleave', function(e) {
|
|
3580
|
-
|
|
3655
|
+
const zone = e.target.closest('.sui-file-upload');
|
|
3581
3656
|
if (!zone) return;
|
|
3582
3657
|
zone.classList.remove('sui-file-upload-dragover');
|
|
3583
3658
|
});
|
|
3584
3659
|
|
|
3585
3660
|
document.addEventListener('drop', function(e) {
|
|
3586
|
-
|
|
3661
|
+
const zone = e.target.closest('.sui-file-upload');
|
|
3587
3662
|
if (!zone) return;
|
|
3588
3663
|
e.preventDefault();
|
|
3589
3664
|
zone.classList.remove('sui-file-upload-dragover');
|
|
3590
|
-
|
|
3665
|
+
const files = Array.from(e.dataTransfer.files);
|
|
3591
3666
|
if (!files.length) return;
|
|
3592
|
-
|
|
3667
|
+
const mode = zone.getAttribute('data-sui-upload') || 'list';
|
|
3593
3668
|
if (mode === 'preview') {
|
|
3594
3669
|
renderFilePreview(zone, files, true);
|
|
3595
3670
|
} else if (mode === 'progress') {
|
|
@@ -3603,33 +3678,33 @@ const SoftUI = (() => {
|
|
|
3603
3678
|
// Radial Progress
|
|
3604
3679
|
// =========================================
|
|
3605
3680
|
function initRadialProgress() {
|
|
3606
|
-
|
|
3681
|
+
const radials = document.querySelectorAll('.sui-radial[data-value]');
|
|
3607
3682
|
radials.forEach(function(el) {
|
|
3608
|
-
|
|
3683
|
+
const fill = el.querySelector('.sui-radial-fill');
|
|
3609
3684
|
if (!fill) return;
|
|
3610
|
-
|
|
3685
|
+
let value = parseFloat(el.getAttribute('data-value')) || 0;
|
|
3611
3686
|
value = Math.max(0, Math.min(100, value));
|
|
3612
|
-
|
|
3687
|
+
let circumference = parseFloat(fill.getAttribute('stroke-dasharray') || fill.style.strokeDasharray);
|
|
3613
3688
|
if (!circumference) {
|
|
3614
|
-
|
|
3689
|
+
const r = fill.getAttribute('r');
|
|
3615
3690
|
circumference = 2 * Math.PI * parseFloat(r);
|
|
3616
3691
|
}
|
|
3617
3692
|
fill.style.strokeDasharray = circumference;
|
|
3618
3693
|
fill.style.strokeDashoffset = circumference;
|
|
3619
|
-
|
|
3620
|
-
|
|
3694
|
+
const valueEl = el.querySelector('.sui-radial-value');
|
|
3695
|
+
const duration = el.classList.contains('sui-radial-animated') ? 1200 : 600;
|
|
3621
3696
|
|
|
3622
3697
|
requestAnimationFrame(function() {
|
|
3623
3698
|
requestAnimationFrame(function() {
|
|
3624
|
-
|
|
3699
|
+
const offset = circumference - (value / 100) * circumference;
|
|
3625
3700
|
fill.style.strokeDashoffset = offset;
|
|
3626
3701
|
|
|
3627
3702
|
if (valueEl) {
|
|
3628
|
-
|
|
3703
|
+
const start = performance.now();
|
|
3629
3704
|
function tick(now) {
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3705
|
+
const elapsed = now - start;
|
|
3706
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
3707
|
+
const current = Math.round(progress * value);
|
|
3633
3708
|
valueEl.textContent = current + '%';
|
|
3634
3709
|
if (progress < 1) requestAnimationFrame(tick);
|
|
3635
3710
|
}
|
|
@@ -3650,15 +3725,15 @@ const SoftUI = (() => {
|
|
|
3650
3725
|
// Number Input
|
|
3651
3726
|
// =========================================
|
|
3652
3727
|
document.addEventListener('click', function(e) {
|
|
3653
|
-
|
|
3728
|
+
const btn = e.target.closest('.sui-number-input-btn');
|
|
3654
3729
|
if (!btn) return;
|
|
3655
|
-
|
|
3656
|
-
|
|
3730
|
+
const wrap = btn.closest('.sui-number-input');
|
|
3731
|
+
const input = wrap.querySelector('input[type="number"]');
|
|
3657
3732
|
if (!input) return;
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3733
|
+
const step = parseFloat(input.step) || 1;
|
|
3734
|
+
const min = input.min !== '' ? parseFloat(input.min) : -Infinity;
|
|
3735
|
+
const max = input.max !== '' ? parseFloat(input.max) : Infinity;
|
|
3736
|
+
let val = parseFloat(input.value) || 0;
|
|
3662
3737
|
if (btn.getAttribute('data-action') === 'decrement') {
|
|
3663
3738
|
val = Math.max(min, val - step);
|
|
3664
3739
|
} else {
|
|
@@ -3672,13 +3747,13 @@ const SoftUI = (() => {
|
|
|
3672
3747
|
// Password Toggle
|
|
3673
3748
|
// =========================================
|
|
3674
3749
|
document.addEventListener('click', function(e) {
|
|
3675
|
-
|
|
3750
|
+
const btn = e.target.closest('.sui-password-toggle');
|
|
3676
3751
|
if (!btn) return;
|
|
3677
3752
|
e.stopPropagation();
|
|
3678
|
-
|
|
3679
|
-
|
|
3753
|
+
const wrap = btn.closest('.sui-password-input');
|
|
3754
|
+
const input = wrap.querySelector('input');
|
|
3680
3755
|
if (!input) return;
|
|
3681
|
-
|
|
3756
|
+
const isPassword = input.type === 'password';
|
|
3682
3757
|
input.type = isPassword ? 'text' : 'password';
|
|
3683
3758
|
btn.classList.toggle('active');
|
|
3684
3759
|
}, true);
|
|
@@ -3687,37 +3762,37 @@ const SoftUI = (() => {
|
|
|
3687
3762
|
// Tags Input
|
|
3688
3763
|
// =========================================
|
|
3689
3764
|
document.addEventListener('keydown', function(e) {
|
|
3690
|
-
|
|
3765
|
+
const input = e.target.closest('.sui-tags-input-field');
|
|
3691
3766
|
if (!input) return;
|
|
3692
|
-
|
|
3767
|
+
const wrap = input.closest('.sui-tags-input');
|
|
3693
3768
|
if (e.key === 'Enter' || e.key === ',') {
|
|
3694
3769
|
e.preventDefault();
|
|
3695
|
-
|
|
3770
|
+
const val = input.value.trim().replace(/,$/, '');
|
|
3696
3771
|
if (!val) return;
|
|
3697
|
-
|
|
3772
|
+
const tag = document.createElement('span');
|
|
3698
3773
|
tag.className = 'sui-chip';
|
|
3699
3774
|
tag.textContent = val;
|
|
3700
|
-
|
|
3775
|
+
const closeBtn = document.createElement('button');
|
|
3701
3776
|
closeBtn.className = 'sui-chip-close';
|
|
3702
3777
|
closeBtn.setAttribute('aria-label', 'Remove');
|
|
3703
3778
|
tag.appendChild(closeBtn);
|
|
3704
3779
|
wrap.insertBefore(tag, input);
|
|
3705
3780
|
input.value = '';
|
|
3706
3781
|
} else if (e.key === 'Backspace' && !input.value) {
|
|
3707
|
-
|
|
3782
|
+
const tags = wrap.querySelectorAll('.sui-chip');
|
|
3708
3783
|
if (tags.length) tags[tags.length - 1].remove();
|
|
3709
3784
|
}
|
|
3710
3785
|
});
|
|
3711
3786
|
|
|
3712
3787
|
document.addEventListener('click', function(e) {
|
|
3713
|
-
|
|
3788
|
+
const dismiss = e.target.closest('.sui-tags-input .sui-chip-close');
|
|
3714
3789
|
if (dismiss) {
|
|
3715
3790
|
dismiss.closest('.sui-chip').remove();
|
|
3716
3791
|
return;
|
|
3717
3792
|
}
|
|
3718
|
-
|
|
3793
|
+
const wrap = e.target.closest('.sui-tags-input');
|
|
3719
3794
|
if (wrap) {
|
|
3720
|
-
|
|
3795
|
+
const input = wrap.querySelector('.sui-tags-input-field');
|
|
3721
3796
|
if (input) input.focus();
|
|
3722
3797
|
}
|
|
3723
3798
|
});
|
|
@@ -3729,10 +3804,10 @@ const SoftUI = (() => {
|
|
|
3729
3804
|
function initSlideSwaps() {
|
|
3730
3805
|
document.querySelectorAll('.sui-swap-slide, .sui-swap-slide-x').forEach(function(swap) {
|
|
3731
3806
|
if (swap.dataset.suiSlideInit) return;
|
|
3732
|
-
|
|
3733
|
-
|
|
3807
|
+
const children = swap.querySelectorAll('.sui-swap-on, .sui-swap-off, .sui-swap-state');
|
|
3808
|
+
let maxW = 0, maxH = 0;
|
|
3734
3809
|
children.forEach(function(c) {
|
|
3735
|
-
|
|
3810
|
+
const prev = c.style.cssText;
|
|
3736
3811
|
c.style.position = 'relative';
|
|
3737
3812
|
c.style.opacity = '1';
|
|
3738
3813
|
c.style.transform = 'none';
|
|
@@ -3753,12 +3828,12 @@ const SoftUI = (() => {
|
|
|
3753
3828
|
}
|
|
3754
3829
|
|
|
3755
3830
|
document.addEventListener('click', function(e) {
|
|
3756
|
-
|
|
3831
|
+
const swap = e.target.closest('.sui-swap');
|
|
3757
3832
|
if (!swap) return;
|
|
3758
3833
|
if (swap.classList.contains('sui-swap-cycle')) {
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3834
|
+
const states = Array.from(swap.querySelectorAll('.sui-swap-state'));
|
|
3835
|
+
const current = states.findIndex(function(s) { return s.classList.contains('active'); });
|
|
3836
|
+
const next = (current + 1) % states.length;
|
|
3762
3837
|
states.forEach(function(s) { s.classList.remove('active'); });
|
|
3763
3838
|
states[next].classList.add('active');
|
|
3764
3839
|
swap.setAttribute('data-state', next);
|
|
@@ -3772,36 +3847,36 @@ const SoftUI = (() => {
|
|
|
3772
3847
|
// =========================================
|
|
3773
3848
|
// Dock — magnification effect
|
|
3774
3849
|
// =========================================
|
|
3775
|
-
|
|
3776
|
-
|
|
3850
|
+
const dockMaxScale = 1.5;
|
|
3851
|
+
const dockRange = 3;
|
|
3777
3852
|
|
|
3778
3853
|
document.addEventListener('mousemove', function(e) {
|
|
3779
|
-
|
|
3854
|
+
const dock = e.target.closest('.sui-dock');
|
|
3780
3855
|
if (!dock) return;
|
|
3781
3856
|
if (dock.classList.contains('sui-dock-no-scale')) return;
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3857
|
+
const iconOnly = dock.classList.contains('sui-dock-icon-scale');
|
|
3858
|
+
const items = Array.from(dock.querySelectorAll('.sui-dock-item'));
|
|
3859
|
+
const isVertical = dock.classList.contains('sui-dock-vertical');
|
|
3785
3860
|
|
|
3786
3861
|
items.forEach(function(item) {
|
|
3787
|
-
|
|
3788
|
-
|
|
3862
|
+
const rect = item.getBoundingClientRect();
|
|
3863
|
+
const center = isVertical
|
|
3789
3864
|
? rect.top + rect.height / 2
|
|
3790
3865
|
: rect.left + rect.width / 2;
|
|
3791
|
-
|
|
3792
|
-
|
|
3866
|
+
const mouse = isVertical ? e.clientY : e.clientX;
|
|
3867
|
+
const baseSize = dock.classList.contains('sui-dock-sm') ? 32
|
|
3793
3868
|
: dock.classList.contains('sui-dock-lg') ? 52 : 40;
|
|
3794
|
-
|
|
3869
|
+
const dist = Math.abs(mouse - center) / baseSize;
|
|
3795
3870
|
|
|
3796
3871
|
if (dist < dockRange) {
|
|
3797
|
-
|
|
3872
|
+
const scale = dockMaxScale - (dist / dockRange) * (dockMaxScale - 1);
|
|
3798
3873
|
if (iconOnly) {
|
|
3799
3874
|
item.style.width = '';
|
|
3800
3875
|
item.style.height = '';
|
|
3801
|
-
|
|
3876
|
+
const svg = item.querySelector('svg');
|
|
3802
3877
|
if (svg) svg.style.transform = 'scale(' + scale + ')';
|
|
3803
3878
|
} else {
|
|
3804
|
-
|
|
3879
|
+
const newSize = Math.round(baseSize * scale);
|
|
3805
3880
|
item.style.width = newSize + 'px';
|
|
3806
3881
|
item.style.height = newSize + 'px';
|
|
3807
3882
|
}
|
|
@@ -3809,7 +3884,7 @@ const SoftUI = (() => {
|
|
|
3809
3884
|
item.style.width = '';
|
|
3810
3885
|
item.style.height = '';
|
|
3811
3886
|
if (iconOnly) {
|
|
3812
|
-
|
|
3887
|
+
const svg = item.querySelector('svg');
|
|
3813
3888
|
if (svg) svg.style.transform = '';
|
|
3814
3889
|
}
|
|
3815
3890
|
}
|
|
@@ -3818,13 +3893,13 @@ const SoftUI = (() => {
|
|
|
3818
3893
|
|
|
3819
3894
|
document.addEventListener('mouseleave', function(e) {
|
|
3820
3895
|
if (!e.target.classList || !e.target.classList.contains('sui-dock')) return;
|
|
3821
|
-
|
|
3822
|
-
|
|
3896
|
+
const iconOnly = e.target.classList.contains('sui-dock-icon-scale');
|
|
3897
|
+
const items = e.target.querySelectorAll('.sui-dock-item');
|
|
3823
3898
|
items.forEach(function(item) {
|
|
3824
3899
|
item.style.width = '';
|
|
3825
3900
|
item.style.height = '';
|
|
3826
3901
|
if (iconOnly) {
|
|
3827
|
-
|
|
3902
|
+
const svg = item.querySelector('svg');
|
|
3828
3903
|
if (svg) svg.style.transform = '';
|
|
3829
3904
|
}
|
|
3830
3905
|
});
|
|
@@ -3833,9 +3908,9 @@ const SoftUI = (() => {
|
|
|
3833
3908
|
// =========================================
|
|
3834
3909
|
// Image Lightbox
|
|
3835
3910
|
// =========================================
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3911
|
+
let lightboxOverlay = null;
|
|
3912
|
+
let lightboxImages = [];
|
|
3913
|
+
let lightboxIndex = 0;
|
|
3839
3914
|
|
|
3840
3915
|
function createLightbox() {
|
|
3841
3916
|
if (lightboxOverlay) return;
|
|
@@ -3875,7 +3950,7 @@ const SoftUI = (() => {
|
|
|
3875
3950
|
lightboxOverlay.classList.add('open');
|
|
3876
3951
|
lightboxOverlay.classList.remove('zoomed');
|
|
3877
3952
|
document.body.style.overflow = 'hidden';
|
|
3878
|
-
|
|
3953
|
+
const hasMultiple = images.length > 1;
|
|
3879
3954
|
lightboxOverlay.querySelector('.sui-lightbox-prev').style.display = hasMultiple ? '' : 'none';
|
|
3880
3955
|
lightboxOverlay.querySelector('.sui-lightbox-next').style.display = hasMultiple ? '' : 'none';
|
|
3881
3956
|
lightboxOverlay.querySelector('.sui-lightbox-counter').style.display = hasMultiple ? '' : 'none';
|
|
@@ -3891,10 +3966,10 @@ const SoftUI = (() => {
|
|
|
3891
3966
|
function showLightboxImage(idx) {
|
|
3892
3967
|
if (lightboxImages.length === 0) return;
|
|
3893
3968
|
lightboxIndex = (idx + lightboxImages.length) % lightboxImages.length;
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3969
|
+
const item = lightboxImages[lightboxIndex];
|
|
3970
|
+
const img = lightboxOverlay.querySelector('img');
|
|
3971
|
+
const caption = lightboxOverlay.querySelector('.sui-lightbox-caption');
|
|
3972
|
+
const counter = lightboxOverlay.querySelector('.sui-lightbox-counter');
|
|
3898
3973
|
img.src = item.src;
|
|
3899
3974
|
img.alt = item.alt || '';
|
|
3900
3975
|
caption.textContent = item.caption || '';
|
|
@@ -3905,11 +3980,11 @@ const SoftUI = (() => {
|
|
|
3905
3980
|
|
|
3906
3981
|
// Vertical gallery — click side thumb to update main
|
|
3907
3982
|
document.addEventListener('click', function(e) {
|
|
3908
|
-
|
|
3983
|
+
const thumb = e.target.closest('.sui-lightbox-vertical-strip .sui-lightbox-thumb');
|
|
3909
3984
|
if (!thumb) return;
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3985
|
+
const gallery = thumb.closest('.sui-lightbox-vertical');
|
|
3986
|
+
const main = gallery.querySelector('.sui-lightbox-vertical-main img');
|
|
3987
|
+
const img = thumb.querySelector('img');
|
|
3913
3988
|
if (main && img) {
|
|
3914
3989
|
main.src = thumb.getAttribute('data-src') || img.src;
|
|
3915
3990
|
main.alt = thumb.getAttribute('data-alt') || img.alt;
|
|
@@ -3920,39 +3995,39 @@ const SoftUI = (() => {
|
|
|
3920
3995
|
|
|
3921
3996
|
// Click main image in vertical gallery to open lightbox
|
|
3922
3997
|
document.addEventListener('click', function(e) {
|
|
3923
|
-
|
|
3998
|
+
const main = e.target.closest('.sui-lightbox-vertical-main');
|
|
3924
3999
|
if (!main) return;
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
4000
|
+
const gallery = main.closest('.sui-lightbox-vertical');
|
|
4001
|
+
const thumbs = Array.from(gallery.querySelectorAll('.sui-lightbox-vertical-strip .sui-lightbox-thumb'));
|
|
4002
|
+
const images = thumbs.map(function(t) {
|
|
4003
|
+
const img = t.querySelector('img');
|
|
3929
4004
|
return {
|
|
3930
4005
|
src: t.getAttribute('data-src') || (img ? img.src : ''),
|
|
3931
4006
|
alt: t.getAttribute('data-alt') || (img ? img.alt : ''),
|
|
3932
4007
|
caption: t.getAttribute('data-caption') || ''
|
|
3933
4008
|
};
|
|
3934
4009
|
});
|
|
3935
|
-
|
|
4010
|
+
const activeIdx = thumbs.findIndex(function(t) { return t.classList.contains('active'); });
|
|
3936
4011
|
openLightbox(images, activeIdx >= 0 ? activeIdx : 0);
|
|
3937
4012
|
});
|
|
3938
4013
|
|
|
3939
4014
|
// Click on thumbnail
|
|
3940
4015
|
document.addEventListener('click', function(e) {
|
|
3941
|
-
|
|
4016
|
+
const thumb = e.target.closest('.sui-lightbox-thumb');
|
|
3942
4017
|
if (!thumb) return;
|
|
3943
4018
|
// Skip if inside vertical strip (handled above)
|
|
3944
4019
|
if (thumb.closest('.sui-lightbox-vertical-strip')) return;
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
4020
|
+
const grid = thumb.closest('.sui-lightbox-grid');
|
|
4021
|
+
const thumbs = grid ? Array.from(grid.querySelectorAll('.sui-lightbox-thumb')) : [thumb];
|
|
4022
|
+
const images = thumbs.map(function(t) {
|
|
4023
|
+
const img = t.querySelector('img');
|
|
3949
4024
|
return {
|
|
3950
4025
|
src: t.getAttribute('data-src') || (img ? img.src : ''),
|
|
3951
4026
|
alt: t.getAttribute('data-alt') || (img ? img.alt : ''),
|
|
3952
4027
|
caption: t.getAttribute('data-caption') || ''
|
|
3953
4028
|
};
|
|
3954
4029
|
});
|
|
3955
|
-
|
|
4030
|
+
const index = thumbs.indexOf(thumb);
|
|
3956
4031
|
openLightbox(images, index);
|
|
3957
4032
|
});
|
|
3958
4033
|
|
|
@@ -3963,21 +4038,21 @@ const SoftUI = (() => {
|
|
|
3963
4038
|
document.querySelectorAll('[data-sui-typewriter]').forEach(function(el) {
|
|
3964
4039
|
if (el.dataset.suiTypewriterInit) return;
|
|
3965
4040
|
el.dataset.suiTypewriterInit = '1';
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
4041
|
+
const words = el.getAttribute('data-words');
|
|
4042
|
+
const speed = parseInt(el.getAttribute('data-speed')) || 80;
|
|
4043
|
+
const deleteSpeed = parseInt(el.getAttribute('data-delete-speed')) || 40;
|
|
4044
|
+
const pause = parseInt(el.getAttribute('data-pause')) || 1500;
|
|
4045
|
+
const loop = el.hasAttribute('data-loop');
|
|
3971
4046
|
|
|
3972
4047
|
if (words) {
|
|
3973
4048
|
// Multiple phrases mode
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
4049
|
+
const phrases = words.split('|').map(function(s) { return s.trim(); });
|
|
4050
|
+
let phraseIdx = 0;
|
|
4051
|
+
let charIdx = 0;
|
|
4052
|
+
let deleting = false;
|
|
3978
4053
|
|
|
3979
4054
|
function tick() {
|
|
3980
|
-
|
|
4055
|
+
const current = phrases[phraseIdx];
|
|
3981
4056
|
if (!deleting) {
|
|
3982
4057
|
charIdx++;
|
|
3983
4058
|
el.textContent = current.substring(0, charIdx);
|
|
@@ -4004,9 +4079,9 @@ const SoftUI = (() => {
|
|
|
4004
4079
|
setTimeout(tick, 500);
|
|
4005
4080
|
} else {
|
|
4006
4081
|
// Single text mode — type out existing content
|
|
4007
|
-
|
|
4082
|
+
const text = el.textContent;
|
|
4008
4083
|
el.textContent = '';
|
|
4009
|
-
|
|
4084
|
+
let i = 0;
|
|
4010
4085
|
function typeChar() {
|
|
4011
4086
|
if (i < text.length) {
|
|
4012
4087
|
el.textContent += text[i];
|
|
@@ -4032,15 +4107,15 @@ const SoftUI = (() => {
|
|
|
4032
4107
|
document.querySelectorAll('[data-sui-text-rotate]').forEach(function(el) {
|
|
4033
4108
|
if (el.dataset.suiRotateInit) return;
|
|
4034
4109
|
el.dataset.suiRotateInit = '1';
|
|
4035
|
-
|
|
4110
|
+
const words = el.querySelectorAll('.sui-text-rotate-word');
|
|
4036
4111
|
if (words.length < 2) return;
|
|
4037
|
-
|
|
4038
|
-
|
|
4112
|
+
const interval = parseInt(el.getAttribute('data-interval')) || 2000;
|
|
4113
|
+
let index = 0;
|
|
4039
4114
|
|
|
4040
4115
|
words[0].classList.add('active');
|
|
4041
4116
|
|
|
4042
4117
|
setInterval(function() {
|
|
4043
|
-
|
|
4118
|
+
const current = words[index];
|
|
4044
4119
|
current.classList.remove('active');
|
|
4045
4120
|
current.classList.add('exit');
|
|
4046
4121
|
setTimeout(function() { current.classList.remove('exit'); }, 400);
|
|
@@ -4060,23 +4135,23 @@ const SoftUI = (() => {
|
|
|
4060
4135
|
// =========================================
|
|
4061
4136
|
// Copy Button
|
|
4062
4137
|
// =========================================
|
|
4063
|
-
|
|
4064
|
-
|
|
4138
|
+
const clipboardSvg = '<svg viewBox="0 0 24 24"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>';
|
|
4139
|
+
const checkSvg = '<svg viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg>';
|
|
4065
4140
|
|
|
4066
4141
|
document.addEventListener('click', function(e) {
|
|
4067
|
-
|
|
4142
|
+
const btn = e.target.closest('[data-sui-copy]');
|
|
4068
4143
|
if (!btn) return;
|
|
4069
|
-
|
|
4144
|
+
let text = btn.getAttribute('data-sui-copy');
|
|
4070
4145
|
if (!text) {
|
|
4071
|
-
|
|
4146
|
+
const wrap = btn.closest('.sui-copy, .sui-copy-input');
|
|
4072
4147
|
if (wrap) {
|
|
4073
|
-
|
|
4074
|
-
|
|
4148
|
+
const textEl = wrap.querySelector('.sui-copy-text');
|
|
4149
|
+
const inputEl = wrap.querySelector('.sui-input');
|
|
4075
4150
|
text = textEl ? textEl.textContent : inputEl ? inputEl.value : '';
|
|
4076
4151
|
}
|
|
4077
4152
|
}
|
|
4078
4153
|
if (!text) return;
|
|
4079
|
-
try { navigator.clipboard.writeText(text.trim()); } catch (
|
|
4154
|
+
try { navigator.clipboard.writeText(text.trim()); } catch (_) {}
|
|
4080
4155
|
btn.classList.add('copied');
|
|
4081
4156
|
btn.innerHTML = checkSvg;
|
|
4082
4157
|
setTimeout(function() {
|
|
@@ -4092,25 +4167,25 @@ const SoftUI = (() => {
|
|
|
4092
4167
|
document.querySelectorAll('.sui-diff[data-sui-diff]').forEach(function(diff) {
|
|
4093
4168
|
if (diff.dataset.suiDiffInit) return;
|
|
4094
4169
|
diff.dataset.suiDiffInit = '1';
|
|
4095
|
-
|
|
4096
|
-
|
|
4170
|
+
const handle = diff.querySelector('.sui-diff-handle');
|
|
4171
|
+
const before = diff.querySelector('.sui-diff-before');
|
|
4097
4172
|
if (!handle || !before) return;
|
|
4098
|
-
|
|
4173
|
+
const isVertical = diff.classList.contains('sui-diff-vertical');
|
|
4099
4174
|
|
|
4100
4175
|
function onMove(e) {
|
|
4101
4176
|
e.preventDefault();
|
|
4102
|
-
|
|
4103
|
-
|
|
4177
|
+
const rect = diff.getBoundingClientRect();
|
|
4178
|
+
let pos;
|
|
4104
4179
|
if (isVertical) {
|
|
4105
|
-
|
|
4180
|
+
const clientY = e.touches ? e.touches[0].clientY : e.clientY;
|
|
4106
4181
|
pos = Math.max(0, Math.min(1, (clientY - rect.top) / rect.height));
|
|
4107
|
-
|
|
4182
|
+
const pct = (pos * 100);
|
|
4108
4183
|
before.style.clipPath = 'inset(0 0 ' + (100 - pct) + '% 0)';
|
|
4109
4184
|
handle.style.top = pct + '%';
|
|
4110
4185
|
} else {
|
|
4111
|
-
|
|
4186
|
+
const clientX = e.touches ? e.touches[0].clientX : e.clientX;
|
|
4112
4187
|
pos = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
|
|
4113
|
-
|
|
4188
|
+
const pct = (pos * 100);
|
|
4114
4189
|
before.style.clipPath = 'inset(0 ' + (100 - pct) + '% 0 0)';
|
|
4115
4190
|
handle.style.left = pct + '%';
|
|
4116
4191
|
}
|
|
@@ -4147,15 +4222,15 @@ const SoftUI = (() => {
|
|
|
4147
4222
|
// Speed Dial
|
|
4148
4223
|
// =========================================
|
|
4149
4224
|
document.addEventListener('click', function(e) {
|
|
4150
|
-
|
|
4225
|
+
const trigger = e.target.closest('.sui-speed-dial-trigger');
|
|
4151
4226
|
if (trigger) {
|
|
4152
|
-
|
|
4227
|
+
const dial = trigger.closest('.sui-speed-dial');
|
|
4153
4228
|
dial.classList.toggle('open');
|
|
4154
4229
|
return;
|
|
4155
4230
|
}
|
|
4156
|
-
|
|
4231
|
+
const action = e.target.closest('.sui-speed-dial-action');
|
|
4157
4232
|
if (action) {
|
|
4158
|
-
|
|
4233
|
+
const dial = action.closest('.sui-speed-dial');
|
|
4159
4234
|
dial.classList.remove('open');
|
|
4160
4235
|
return;
|
|
4161
4236
|
}
|
|
@@ -4168,13 +4243,13 @@ const SoftUI = (() => {
|
|
|
4168
4243
|
// Hover mode
|
|
4169
4244
|
document.addEventListener('mouseenter', function(e) {
|
|
4170
4245
|
if (!e.target.closest) return;
|
|
4171
|
-
|
|
4246
|
+
const dial = e.target.closest('.sui-speed-dial-hover');
|
|
4172
4247
|
if (dial) dial.classList.add('open');
|
|
4173
4248
|
}, true);
|
|
4174
4249
|
|
|
4175
4250
|
document.addEventListener('mouseleave', function(e) {
|
|
4176
4251
|
if (!e.target.closest) return;
|
|
4177
|
-
|
|
4252
|
+
const dial = e.target.closest('.sui-speed-dial-hover');
|
|
4178
4253
|
if (dial) dial.classList.remove('open');
|
|
4179
4254
|
}, true);
|
|
4180
4255
|
|
|
@@ -4182,11 +4257,11 @@ const SoftUI = (() => {
|
|
|
4182
4257
|
// Tree View
|
|
4183
4258
|
// =========================================
|
|
4184
4259
|
document.addEventListener('click', function(e) {
|
|
4185
|
-
|
|
4260
|
+
const label = e.target.closest('.sui-tree-label');
|
|
4186
4261
|
if (!label) return;
|
|
4187
4262
|
if (e.target.closest('.sui-checkbox')) return;
|
|
4188
|
-
|
|
4189
|
-
|
|
4263
|
+
const item = label.closest('.sui-tree-item');
|
|
4264
|
+
const children = item.querySelector('.sui-tree-children');
|
|
4190
4265
|
if (children) {
|
|
4191
4266
|
item.classList.toggle('expanded');
|
|
4192
4267
|
}
|
|
@@ -4195,12 +4270,12 @@ const SoftUI = (() => {
|
|
|
4195
4270
|
// Tree checkbox propagation
|
|
4196
4271
|
document.addEventListener('change', function(e) {
|
|
4197
4272
|
if (!e.target.closest('.sui-tree .sui-checkbox input')) return;
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4273
|
+
const checkbox = e.target;
|
|
4274
|
+
const item = checkbox.closest('.sui-tree-item');
|
|
4275
|
+
const checked = checkbox.checked;
|
|
4201
4276
|
|
|
4202
4277
|
// Propagate down — check/uncheck all children
|
|
4203
|
-
|
|
4278
|
+
const childBoxes = item.querySelectorAll('.sui-tree-children .sui-checkbox input');
|
|
4204
4279
|
childBoxes.forEach(function(cb) {
|
|
4205
4280
|
cb.checked = checked;
|
|
4206
4281
|
cb.indeterminate = false;
|
|
@@ -4211,16 +4286,16 @@ const SoftUI = (() => {
|
|
|
4211
4286
|
});
|
|
4212
4287
|
|
|
4213
4288
|
function updateTreeParent(item) {
|
|
4214
|
-
|
|
4289
|
+
const parentChildren = item.closest('.sui-tree-children');
|
|
4215
4290
|
if (!parentChildren) return;
|
|
4216
|
-
|
|
4291
|
+
const parentItem = parentChildren.closest('.sui-tree-item');
|
|
4217
4292
|
if (!parentItem) return;
|
|
4218
|
-
|
|
4293
|
+
const parentCb = parentItem.querySelector(':scope > .sui-tree-label .sui-checkbox input');
|
|
4219
4294
|
if (!parentCb) return;
|
|
4220
4295
|
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4296
|
+
const siblings = parentChildren.querySelectorAll(':scope > .sui-tree-item > .sui-tree-label .sui-checkbox input');
|
|
4297
|
+
const total = siblings.length;
|
|
4298
|
+
let checkedCount = 0;
|
|
4224
4299
|
siblings.forEach(function(cb) { if (cb.checked) checkedCount++; });
|
|
4225
4300
|
|
|
4226
4301
|
if (checkedCount === 0) {
|
|
@@ -4243,10 +4318,10 @@ const SoftUI = (() => {
|
|
|
4243
4318
|
// =========================================
|
|
4244
4319
|
function tour(steps, options) {
|
|
4245
4320
|
options = options || {};
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4321
|
+
let currentStep = 0;
|
|
4322
|
+
let overlay, backdrop, spotlight, tooltip;
|
|
4323
|
+
const padding = options.padding || 8;
|
|
4324
|
+
const noOverlay = options.noOverlay || false;
|
|
4250
4325
|
|
|
4251
4326
|
function create() {
|
|
4252
4327
|
overlay = document.createElement('div');
|
|
@@ -4265,12 +4340,12 @@ const SoftUI = (() => {
|
|
|
4265
4340
|
backdrop.addEventListener('click', close);
|
|
4266
4341
|
}
|
|
4267
4342
|
|
|
4268
|
-
|
|
4343
|
+
let firstShow = true;
|
|
4269
4344
|
|
|
4270
4345
|
function show(idx) {
|
|
4271
4346
|
currentStep = idx;
|
|
4272
|
-
|
|
4273
|
-
|
|
4347
|
+
const step = steps[idx];
|
|
4348
|
+
const target = document.querySelector(step.target);
|
|
4274
4349
|
|
|
4275
4350
|
// Only hide on first show to avoid flash between steps
|
|
4276
4351
|
if (firstShow) {
|
|
@@ -4280,16 +4355,16 @@ const SoftUI = (() => {
|
|
|
4280
4355
|
}
|
|
4281
4356
|
|
|
4282
4357
|
// Scroll if element isn't comfortably in view (with margin for tooltip)
|
|
4283
|
-
|
|
4358
|
+
let needsScroll = false;
|
|
4284
4359
|
if (target) {
|
|
4285
|
-
|
|
4286
|
-
|
|
4360
|
+
const r = target.getBoundingClientRect();
|
|
4361
|
+
const margin = 120;
|
|
4287
4362
|
needsScroll = r.top < margin || r.bottom > window.innerHeight - margin;
|
|
4288
4363
|
if (needsScroll) target.scrollIntoView({ block: 'center', behavior: 'smooth' });
|
|
4289
4364
|
}
|
|
4290
4365
|
setTimeout(function() {
|
|
4291
4366
|
if (target) {
|
|
4292
|
-
|
|
4367
|
+
const rect = target.getBoundingClientRect();
|
|
4293
4368
|
spotlight.style.top = (rect.top - padding) + 'px';
|
|
4294
4369
|
spotlight.style.left = (rect.left - padding) + 'px';
|
|
4295
4370
|
spotlight.style.width = (rect.width + padding * 2) + 'px';
|
|
@@ -4297,10 +4372,10 @@ const SoftUI = (() => {
|
|
|
4297
4372
|
}
|
|
4298
4373
|
|
|
4299
4374
|
// Build tooltip content
|
|
4300
|
-
|
|
4375
|
+
let dotsHtml = '';
|
|
4301
4376
|
if (steps.length > 1) {
|
|
4302
4377
|
dotsHtml = '<div class="sui-tour-dots">';
|
|
4303
|
-
for (
|
|
4378
|
+
for (let i = 0; i < steps.length; i++) {
|
|
4304
4379
|
dotsHtml += '<span class="sui-tour-dot' + (i === idx ? ' active' : '') + '"></span>';
|
|
4305
4380
|
}
|
|
4306
4381
|
dotsHtml += '</div>';
|
|
@@ -4318,10 +4393,10 @@ const SoftUI = (() => {
|
|
|
4318
4393
|
'</div>';
|
|
4319
4394
|
|
|
4320
4395
|
// Button handlers
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4396
|
+
const nextBtn = tooltip.querySelector('.sui-tour-next');
|
|
4397
|
+
const prevBtn = tooltip.querySelector('.sui-tour-prev');
|
|
4398
|
+
const skipBtn = tooltip.querySelector('.sui-tour-skip');
|
|
4399
|
+
const doneBtn = tooltip.querySelector('.sui-tour-done');
|
|
4325
4400
|
if (nextBtn) nextBtn.addEventListener('click', function() { show(currentStep + 1); });
|
|
4326
4401
|
if (prevBtn) prevBtn.addEventListener('click', function() { show(currentStep - 1); });
|
|
4327
4402
|
if (skipBtn) skipBtn.addEventListener('click', close);
|
|
@@ -4329,11 +4404,11 @@ const SoftUI = (() => {
|
|
|
4329
4404
|
|
|
4330
4405
|
// Position tooltip after scroll settles
|
|
4331
4406
|
if (target) {
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4407
|
+
const rect = target.getBoundingClientRect();
|
|
4408
|
+
const pos = step.position || 'bottom';
|
|
4409
|
+
const tooltipW = 300;
|
|
4410
|
+
const centerX = rect.left + rect.width / 2 - tooltipW / 2;
|
|
4411
|
+
let top, left;
|
|
4337
4412
|
|
|
4338
4413
|
tooltip.style.transform = '';
|
|
4339
4414
|
|