softui-css 1.12.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/softui.css +1061 -1
- package/dist/softui.js +313 -0
- package/dist/softui.min.css +1 -1
- package/dist/softui.min.js +1 -1
- package/package.json +1 -1
package/dist/softui.js
CHANGED
|
@@ -303,6 +303,21 @@ const SoftUI = (() => {
|
|
|
303
303
|
// Drawers
|
|
304
304
|
initDrawers();
|
|
305
305
|
|
|
306
|
+
// Editable Text
|
|
307
|
+
initEditable();
|
|
308
|
+
|
|
309
|
+
// Scrollspy
|
|
310
|
+
initScrollspy();
|
|
311
|
+
|
|
312
|
+
// Countdowns
|
|
313
|
+
initCountdowns();
|
|
314
|
+
|
|
315
|
+
// Segmented Controls
|
|
316
|
+
initSegmented();
|
|
317
|
+
|
|
318
|
+
// Navigation Menu
|
|
319
|
+
initNavMenu();
|
|
320
|
+
|
|
306
321
|
// Data Tables
|
|
307
322
|
initDataTables();
|
|
308
323
|
|
|
@@ -2646,6 +2661,304 @@ const SoftUI = (() => {
|
|
|
2646
2661
|
});
|
|
2647
2662
|
}
|
|
2648
2663
|
|
|
2664
|
+
function initEditable() {
|
|
2665
|
+
document.querySelectorAll('.sui-editable').forEach(function(el) {
|
|
2666
|
+
const valueEl = el.querySelector('.sui-editable-value');
|
|
2667
|
+
if (!valueEl) return;
|
|
2668
|
+
|
|
2669
|
+
el.addEventListener('click', function() {
|
|
2670
|
+
if (el.querySelector('.sui-editable-input')) return; // Already editing
|
|
2671
|
+
|
|
2672
|
+
const currentText = valueEl.textContent;
|
|
2673
|
+
const input = document.createElement('input');
|
|
2674
|
+
input.className = 'sui-editable-input';
|
|
2675
|
+
input.type = 'text';
|
|
2676
|
+
input.value = currentText;
|
|
2677
|
+
input.style.fontSize = getComputedStyle(valueEl).fontSize;
|
|
2678
|
+
input.style.fontWeight = getComputedStyle(valueEl).fontWeight;
|
|
2679
|
+
|
|
2680
|
+
valueEl.style.display = 'none';
|
|
2681
|
+
const icon = el.querySelector('.sui-editable-icon');
|
|
2682
|
+
if (icon) icon.style.display = 'none';
|
|
2683
|
+
|
|
2684
|
+
el.insertBefore(input, valueEl);
|
|
2685
|
+
input.focus();
|
|
2686
|
+
input.select();
|
|
2687
|
+
|
|
2688
|
+
let cancelled = false;
|
|
2689
|
+
|
|
2690
|
+
function save() {
|
|
2691
|
+
if (cancelled) return;
|
|
2692
|
+
const newVal = input.value.trim() || currentText;
|
|
2693
|
+
valueEl.textContent = newVal;
|
|
2694
|
+
valueEl.style.display = '';
|
|
2695
|
+
if (icon) icon.style.display = '';
|
|
2696
|
+
input.remove();
|
|
2697
|
+
el.dispatchEvent(new CustomEvent('editable:save', { detail: { value: newVal, previous: currentText } }));
|
|
2698
|
+
}
|
|
2699
|
+
|
|
2700
|
+
function cancel() {
|
|
2701
|
+
cancelled = true;
|
|
2702
|
+
valueEl.style.display = '';
|
|
2703
|
+
if (icon) icon.style.display = '';
|
|
2704
|
+
input.remove();
|
|
2705
|
+
el.dispatchEvent(new CustomEvent('editable:cancel'));
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
input.addEventListener('keydown', function(e) {
|
|
2709
|
+
if (e.key === 'Enter') { e.preventDefault(); save(); }
|
|
2710
|
+
if (e.key === 'Escape') { e.preventDefault(); cancel(); }
|
|
2711
|
+
});
|
|
2712
|
+
|
|
2713
|
+
input.addEventListener('blur', save);
|
|
2714
|
+
});
|
|
2715
|
+
});
|
|
2716
|
+
}
|
|
2717
|
+
|
|
2718
|
+
function initScrollspy() {
|
|
2719
|
+
document.querySelectorAll('[data-sui-scrollspy]').forEach(function(nav) {
|
|
2720
|
+
const links = nav.querySelectorAll('a[href^="#"]');
|
|
2721
|
+
if (!links.length) return;
|
|
2722
|
+
|
|
2723
|
+
const targetIds = [];
|
|
2724
|
+
links.forEach(function(link) {
|
|
2725
|
+
const id = link.getAttribute('href').slice(1);
|
|
2726
|
+
if (id) targetIds.push(id);
|
|
2727
|
+
});
|
|
2728
|
+
|
|
2729
|
+
// Find scroll container — either specified or auto-detect from first target's scrollable parent
|
|
2730
|
+
const firstTarget = document.getElementById(targetIds[0]);
|
|
2731
|
+
let scrollRoot = null;
|
|
2732
|
+
if (firstTarget) {
|
|
2733
|
+
let parent = firstTarget.parentElement;
|
|
2734
|
+
while (parent && parent !== document.body) {
|
|
2735
|
+
const style = getComputedStyle(parent);
|
|
2736
|
+
if (style.overflowY === 'auto' || style.overflowY === 'scroll') {
|
|
2737
|
+
scrollRoot = parent;
|
|
2738
|
+
break;
|
|
2739
|
+
}
|
|
2740
|
+
parent = parent.parentElement;
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
|
|
2744
|
+
const visibleSections = new Set();
|
|
2745
|
+
let clickLock = false;
|
|
2746
|
+
|
|
2747
|
+
const observer = new IntersectionObserver(function(entries) {
|
|
2748
|
+
if (clickLock) return;
|
|
2749
|
+
entries.forEach(function(entry) {
|
|
2750
|
+
if (entry.isIntersecting) {
|
|
2751
|
+
visibleSections.add(entry.target.id);
|
|
2752
|
+
} else {
|
|
2753
|
+
visibleSections.delete(entry.target.id);
|
|
2754
|
+
}
|
|
2755
|
+
});
|
|
2756
|
+
// Check if scrolled to bottom of container
|
|
2757
|
+
let atBottom = false;
|
|
2758
|
+
if (scrollRoot) {
|
|
2759
|
+
atBottom = scrollRoot.scrollTop + scrollRoot.clientHeight >= scrollRoot.scrollHeight - 5;
|
|
2760
|
+
} else {
|
|
2761
|
+
atBottom = window.innerHeight + window.scrollY >= document.body.scrollHeight - 5;
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
if (atBottom && visibleSections.size > 0) {
|
|
2765
|
+
// At bottom — pick the last visible section
|
|
2766
|
+
for (let i = targetIds.length - 1; i >= 0; i--) {
|
|
2767
|
+
if (visibleSections.has(targetIds[i])) {
|
|
2768
|
+
links.forEach(function(l) { l.classList.remove('active'); });
|
|
2769
|
+
const active = nav.querySelector('a[href="#' + targetIds[i] + '"]');
|
|
2770
|
+
if (active) active.classList.add('active');
|
|
2771
|
+
break;
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
} else {
|
|
2775
|
+
// Pick the first visible section in document order
|
|
2776
|
+
for (let i = 0; i < targetIds.length; i++) {
|
|
2777
|
+
if (visibleSections.has(targetIds[i])) {
|
|
2778
|
+
links.forEach(function(l) { l.classList.remove('active'); });
|
|
2779
|
+
const active = nav.querySelector('a[href="#' + targetIds[i] + '"]');
|
|
2780
|
+
if (active) active.classList.add('active');
|
|
2781
|
+
break;
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
}, {
|
|
2786
|
+
root: scrollRoot,
|
|
2787
|
+
rootMargin: '0px 0px -30% 0px',
|
|
2788
|
+
threshold: 0
|
|
2789
|
+
});
|
|
2790
|
+
|
|
2791
|
+
targetIds.forEach(function(id) {
|
|
2792
|
+
const el = document.getElementById(id);
|
|
2793
|
+
if (el) observer.observe(el);
|
|
2794
|
+
});
|
|
2795
|
+
|
|
2796
|
+
// Click to scroll and activate
|
|
2797
|
+
links.forEach(function(link) {
|
|
2798
|
+
link.addEventListener('click', function(e) {
|
|
2799
|
+
e.preventDefault();
|
|
2800
|
+
const id = link.getAttribute('href').slice(1);
|
|
2801
|
+
const el = document.getElementById(id);
|
|
2802
|
+
if (!el) return;
|
|
2803
|
+
clickLock = true;
|
|
2804
|
+
links.forEach(function(l) { l.classList.remove('active'); });
|
|
2805
|
+
link.classList.add('active');
|
|
2806
|
+
if (scrollRoot) {
|
|
2807
|
+
scrollRoot.scrollTo({ top: el.offsetTop - scrollRoot.offsetTop, behavior: 'smooth' });
|
|
2808
|
+
} else {
|
|
2809
|
+
el.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
2810
|
+
}
|
|
2811
|
+
setTimeout(function() { clickLock = false; }, 600);
|
|
2812
|
+
});
|
|
2813
|
+
});
|
|
2814
|
+
});
|
|
2815
|
+
}
|
|
2816
|
+
|
|
2817
|
+
function initCountdowns() {
|
|
2818
|
+
document.querySelectorAll('.sui-countdown[data-date]').forEach(function(el) {
|
|
2819
|
+
const dateStr = el.getAttribute('data-date');
|
|
2820
|
+
|
|
2821
|
+
// Support relative dates: "+2y", "+30d", "+2y5d", "+6h30m"
|
|
2822
|
+
let target;
|
|
2823
|
+
if (dateStr.startsWith('+')) {
|
|
2824
|
+
target = new Date();
|
|
2825
|
+
const parts = dateStr.slice(1).matchAll(/(\d+)([ydhms])/g);
|
|
2826
|
+
for (const p of parts) {
|
|
2827
|
+
const val = parseInt(p[1], 10);
|
|
2828
|
+
const unit = p[2];
|
|
2829
|
+
if (unit === 'y') target.setFullYear(target.getFullYear() + val);
|
|
2830
|
+
else if (unit === 'd') target.setDate(target.getDate() + val);
|
|
2831
|
+
else if (unit === 'h') target.setHours(target.getHours() + val);
|
|
2832
|
+
else if (unit === 'm') target.setMinutes(target.getMinutes() + val);
|
|
2833
|
+
else if (unit === 's') target.setSeconds(target.getSeconds() + val);
|
|
2834
|
+
}
|
|
2835
|
+
target = target.getTime();
|
|
2836
|
+
} else {
|
|
2837
|
+
target = new Date(dateStr).getTime();
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2840
|
+
const yearsEl = el.querySelector('[data-years]');
|
|
2841
|
+
const daysEl = el.querySelector('[data-days]');
|
|
2842
|
+
const hoursEl = el.querySelector('[data-hours]');
|
|
2843
|
+
const minsEl = el.querySelector('[data-minutes]');
|
|
2844
|
+
const secsEl = el.querySelector('[data-seconds]');
|
|
2845
|
+
|
|
2846
|
+
function update() {
|
|
2847
|
+
const now = Date.now();
|
|
2848
|
+
const diff = Math.max(0, target - now);
|
|
2849
|
+
let remaining = diff;
|
|
2850
|
+
const y = Math.floor(remaining / 31536000000);
|
|
2851
|
+
remaining %= 31536000000;
|
|
2852
|
+
const d = Math.floor(remaining / 86400000);
|
|
2853
|
+
remaining %= 86400000;
|
|
2854
|
+
const h = Math.floor(remaining / 3600000);
|
|
2855
|
+
remaining %= 3600000;
|
|
2856
|
+
const m = Math.floor(remaining / 60000);
|
|
2857
|
+
remaining %= 60000;
|
|
2858
|
+
const s = Math.floor(remaining / 1000);
|
|
2859
|
+
if (yearsEl) yearsEl.textContent = String(y).padStart(2, '0');
|
|
2860
|
+
if (daysEl) daysEl.textContent = String(d).padStart(2, '0');
|
|
2861
|
+
if (hoursEl) hoursEl.textContent = String(h).padStart(2, '0');
|
|
2862
|
+
if (minsEl) minsEl.textContent = String(m).padStart(2, '0');
|
|
2863
|
+
if (secsEl) secsEl.textContent = String(s).padStart(2, '0');
|
|
2864
|
+
if (diff === 0) {
|
|
2865
|
+
clearInterval(timer);
|
|
2866
|
+
el.dispatchEvent(new Event('countdown:end'));
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
update();
|
|
2871
|
+
const timer = setInterval(update, 1000);
|
|
2872
|
+
});
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
function initSegmented() {
|
|
2876
|
+
document.querySelectorAll('.sui-segmented').forEach(function(seg) {
|
|
2877
|
+
const indicator = seg.querySelector('.sui-segmented-indicator');
|
|
2878
|
+
if (!indicator) return;
|
|
2879
|
+
|
|
2880
|
+
function updateIndicator() {
|
|
2881
|
+
const checked = seg.querySelector('input:checked');
|
|
2882
|
+
if (!checked) return;
|
|
2883
|
+
const label = checked.nextElementSibling;
|
|
2884
|
+
if (!label) return;
|
|
2885
|
+
indicator.style.left = label.offsetLeft + 'px';
|
|
2886
|
+
indicator.style.width = label.offsetWidth + 'px';
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
// Initial position
|
|
2890
|
+
updateIndicator();
|
|
2891
|
+
|
|
2892
|
+
// Listen for changes
|
|
2893
|
+
seg.querySelectorAll('input').forEach(function(input) {
|
|
2894
|
+
input.addEventListener('change', updateIndicator);
|
|
2895
|
+
});
|
|
2896
|
+
|
|
2897
|
+
// Recalculate on resize
|
|
2898
|
+
window.addEventListener('resize', updateIndicator);
|
|
2899
|
+
});
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2902
|
+
function initNavMenu() {
|
|
2903
|
+
// Toggle on click
|
|
2904
|
+
document.addEventListener('click', function(e) {
|
|
2905
|
+
if (!e.target.closest) return;
|
|
2906
|
+
const trigger = e.target.closest('.sui-nav-menu-trigger');
|
|
2907
|
+
if (trigger && !trigger.hasAttribute('href')) {
|
|
2908
|
+
const item = trigger.closest('.sui-nav-menu-item');
|
|
2909
|
+
if (!item) return;
|
|
2910
|
+
// Close other open items
|
|
2911
|
+
document.querySelectorAll('.sui-nav-menu-item.open').forEach(function(i) {
|
|
2912
|
+
if (i !== item) i.classList.remove('open');
|
|
2913
|
+
});
|
|
2914
|
+
item.classList.toggle('open');
|
|
2915
|
+
e.stopPropagation();
|
|
2916
|
+
return;
|
|
2917
|
+
}
|
|
2918
|
+
// Click on sub-menu trigger (click mode)
|
|
2919
|
+
const subLink = e.target.closest('.sui-nav-menu-sub > .sui-nav-menu-link');
|
|
2920
|
+
if (subLink) {
|
|
2921
|
+
const sub = subLink.closest('.sui-nav-menu-sub');
|
|
2922
|
+
const panel = sub.closest('.sui-nav-menu-panel');
|
|
2923
|
+
if (panel && panel.closest('.sui-nav-menu-sub-click')) {
|
|
2924
|
+
e.preventDefault();
|
|
2925
|
+
e.stopPropagation();
|
|
2926
|
+
// Close sibling subs
|
|
2927
|
+
panel.querySelectorAll('.sui-nav-menu-sub.open').forEach(function(s) {
|
|
2928
|
+
if (s !== sub) s.classList.remove('open');
|
|
2929
|
+
});
|
|
2930
|
+
sub.classList.toggle('open');
|
|
2931
|
+
return;
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
// Click on a nav-menu link closes everything
|
|
2936
|
+
const link = e.target.closest('.sui-nav-menu-link');
|
|
2937
|
+
if (link && link.closest('.sui-nav-menu-item')) {
|
|
2938
|
+
document.querySelectorAll('.sui-nav-menu-item.open').forEach(function(i) {
|
|
2939
|
+
i.classList.remove('open');
|
|
2940
|
+
});
|
|
2941
|
+
return;
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
// Click outside closes all
|
|
2945
|
+
if (!e.target.closest('.sui-nav-menu-item')) {
|
|
2946
|
+
document.querySelectorAll('.sui-nav-menu-item.open').forEach(function(i) {
|
|
2947
|
+
i.classList.remove('open');
|
|
2948
|
+
});
|
|
2949
|
+
}
|
|
2950
|
+
});
|
|
2951
|
+
|
|
2952
|
+
// Escape closes
|
|
2953
|
+
document.addEventListener('keydown', function(e) {
|
|
2954
|
+
if (e.key === 'Escape') {
|
|
2955
|
+
document.querySelectorAll('.sui-nav-menu-item.open').forEach(function(i) {
|
|
2956
|
+
i.classList.remove('open');
|
|
2957
|
+
});
|
|
2958
|
+
}
|
|
2959
|
+
});
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2649
2962
|
function initDrawers() {
|
|
2650
2963
|
document.querySelectorAll('.sui-drawer').forEach(function(backdrop) {
|
|
2651
2964
|
const panel = backdrop.querySelector('.sui-sheet-bottom');
|