@allxsmith/bestax-bulma 5.12.0 → 5.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/index.cjs.js +346 -50
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +346 -50
- package/dist/index.esm.js.map +1 -1
- package/dist/types/components/Dropdown.d.ts +2 -0
- package/dist/types/components/Navbar.d.ts +2 -0
- package/dist/types/components/Panel.d.ts +5 -4
- package/dist/types/elements/Icon.d.ts +30 -7
- package/dist/types/elements/IconText.d.ts +4 -4
- package/dist/types/elements/Tags.d.ts +7 -1
- package/dist/types/form/Control.d.ts +4 -4
- package/dist/types/helpers/bulmaClassHelpers.d.ts +11 -3
- package/dist/types/helpers/useVisibilityClasses.d.ts +8 -0
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -322,6 +322,7 @@ const validDisplays = [
|
|
|
322
322
|
'inline',
|
|
323
323
|
'inline-block',
|
|
324
324
|
'inline-flex',
|
|
325
|
+
'grid',
|
|
325
326
|
];
|
|
326
327
|
/**
|
|
327
328
|
* Valid Bulma visibility classes.
|
|
@@ -410,8 +411,12 @@ const validFlexGrowShrink = ['0', '1', '2', '3', '4', '5'];
|
|
|
410
411
|
const validViewports = [
|
|
411
412
|
'mobile',
|
|
412
413
|
'tablet',
|
|
414
|
+
'tablet-only',
|
|
415
|
+
'touch',
|
|
413
416
|
'desktop',
|
|
417
|
+
'desktop-only',
|
|
414
418
|
'widescreen',
|
|
419
|
+
'widescreen-only',
|
|
415
420
|
'fullhd',
|
|
416
421
|
];
|
|
417
422
|
/**
|
|
@@ -431,9 +436,17 @@ const createBulmaClassHelpers = (classPrefix, viewport) => {
|
|
|
431
436
|
classes.push(classPrefix ? `${classPrefix}${className}` : className);
|
|
432
437
|
};
|
|
433
438
|
// Helper to add class with optional viewport
|
|
434
|
-
|
|
439
|
+
//
|
|
440
|
+
// `supportedViewports` narrows which viewports may be suffixed onto this
|
|
441
|
+
// class. It defaults to every viewport, but a few Bulma helper families
|
|
442
|
+
// (notably `is-size-*`, which ships no `-only` bands) support only a subset,
|
|
443
|
+
// so passing the emitting set keeps us from producing dead classes.
|
|
444
|
+
const addClass = (prefix, value, validValues, supportsViewport = false, supportedViewports = validViewports) => {
|
|
435
445
|
if (value && validValues.includes(value)) {
|
|
436
|
-
const className = supportsViewport &&
|
|
446
|
+
const className = supportsViewport &&
|
|
447
|
+
viewport &&
|
|
448
|
+
validViewports.includes(viewport) &&
|
|
449
|
+
supportedViewports.includes(viewport)
|
|
437
450
|
? `${prefix}-${value}-${viewport}`
|
|
438
451
|
: `${prefix}-${value}`;
|
|
439
452
|
addPrefixedClass(className);
|
|
@@ -558,6 +571,19 @@ const useSpacingClasses = (props) => {
|
|
|
558
571
|
}, [classPrefix, m, mt, mr, mb, ml, mx, my, p, pt, pr, pb, pl, px, py]);
|
|
559
572
|
};
|
|
560
573
|
|
|
574
|
+
/**
|
|
575
|
+
* Viewports Bulma generates `is-size-*` classes for. Unlike text alignment,
|
|
576
|
+
* text size ships no `-only` bands (`tablet-only`/`desktop-only`/`widescreen-only`),
|
|
577
|
+
* so the `viewport` prop's suffix is limited to these on the `is-size` path.
|
|
578
|
+
*/
|
|
579
|
+
const sizeSupportedViewports = [
|
|
580
|
+
'mobile',
|
|
581
|
+
'tablet',
|
|
582
|
+
'touch',
|
|
583
|
+
'desktop',
|
|
584
|
+
'widescreen',
|
|
585
|
+
'fullhd',
|
|
586
|
+
];
|
|
561
587
|
/**
|
|
562
588
|
* A hook that generates Bulma typography helper classes, including
|
|
563
589
|
* viewport-specific text size and alignment variants.
|
|
@@ -578,8 +604,11 @@ const useTypographyClasses = (props) => {
|
|
|
578
604
|
return useMemo(() => {
|
|
579
605
|
const { classes, addPrefixedClass, addClass, addClassNoViewport } = createBulmaClassHelpers(classPrefix, viewport);
|
|
580
606
|
// Typography
|
|
581
|
-
|
|
582
|
-
|
|
607
|
+
// Bulma emits `is-size-*` for the six main/`touch` breakpoints but not the
|
|
608
|
+
// three `-only` bands, so restrict the viewport suffix to the ones it ships
|
|
609
|
+
// — otherwise `viewport="tablet-only"` would render a dead `is-size-N-tablet-only`.
|
|
610
|
+
addClass('is-size', textSize, validTextSizes, true, sizeSupportedViewports); // supports viewport (no `-only` bands)
|
|
611
|
+
addClass('has-text', textAlign, validAlignments$1, true); // supports viewport (all bands)
|
|
583
612
|
addClassNoViewport('is', textTransform, validTextTransforms); // no viewport support
|
|
584
613
|
addClassNoViewport('has-text-weight', textWeight, validTextWeights); // no viewport support
|
|
585
614
|
addClassNoViewport('is-family', fontFamily, validFontFamilies); // no viewport support
|
|
@@ -645,7 +674,7 @@ const useTypographyClasses = (props) => {
|
|
|
645
674
|
*/
|
|
646
675
|
const useVisibilityClasses = (props) => {
|
|
647
676
|
const { classPrefix } = useConfig();
|
|
648
|
-
const { visibility, visibilityMobile, visibilityTablet, visibilityDesktop, visibilityWidescreen, visibilityFullhd, display, displayMobile, displayTablet, displayDesktop, displayWidescreen, displayFullhd, viewport, } = props;
|
|
677
|
+
const { visibility, visibilityMobile, visibilityTablet, visibilityTabletOnly, visibilityTouch, visibilityDesktop, visibilityDesktopOnly, visibilityWidescreen, visibilityWidescreenOnly, visibilityFullhd, display, displayMobile, displayTablet, displayTabletOnly, displayTouch, displayDesktop, displayDesktopOnly, displayWidescreen, displayWidescreenOnly, displayFullhd, viewport, } = props;
|
|
649
678
|
return useMemo(() => {
|
|
650
679
|
const { classes, addPrefixedClass, addClass } = createBulmaClassHelpers(classPrefix, viewport);
|
|
651
680
|
// Viewport-specific visibility
|
|
@@ -662,8 +691,12 @@ const useVisibilityClasses = (props) => {
|
|
|
662
691
|
};
|
|
663
692
|
addViewportSpecificVisibilityClass(visibilityMobile, '-mobile');
|
|
664
693
|
addViewportSpecificVisibilityClass(visibilityTablet, '-tablet');
|
|
694
|
+
addViewportSpecificVisibilityClass(visibilityTabletOnly, '-tablet-only');
|
|
695
|
+
addViewportSpecificVisibilityClass(visibilityTouch, '-touch');
|
|
665
696
|
addViewportSpecificVisibilityClass(visibilityDesktop, '-desktop');
|
|
697
|
+
addViewportSpecificVisibilityClass(visibilityDesktopOnly, '-desktop-only');
|
|
666
698
|
addViewportSpecificVisibilityClass(visibilityWidescreen, '-widescreen');
|
|
699
|
+
addViewportSpecificVisibilityClass(visibilityWidescreenOnly, '-widescreen-only');
|
|
667
700
|
addViewportSpecificVisibilityClass(visibilityFullhd, '-fullhd');
|
|
668
701
|
// Visibility and Display
|
|
669
702
|
// Handle viewport-specific display properties first (they take precedence)
|
|
@@ -680,14 +713,22 @@ const useVisibilityClasses = (props) => {
|
|
|
680
713
|
// Apply viewport-specific display classes
|
|
681
714
|
addDisplayClass(displayMobile, '-mobile');
|
|
682
715
|
addDisplayClass(displayTablet, '-tablet');
|
|
716
|
+
addDisplayClass(displayTabletOnly, '-tablet-only');
|
|
717
|
+
addDisplayClass(displayTouch, '-touch');
|
|
683
718
|
addDisplayClass(displayDesktop, '-desktop');
|
|
719
|
+
addDisplayClass(displayDesktopOnly, '-desktop-only');
|
|
684
720
|
addDisplayClass(displayWidescreen, '-widescreen');
|
|
721
|
+
addDisplayClass(displayWidescreenOnly, '-widescreen-only');
|
|
685
722
|
addDisplayClass(displayFullhd, '-fullhd');
|
|
686
723
|
// Apply legacy display/viewport combination if no viewport-specific display props are set
|
|
687
724
|
const hasViewportSpecificDisplay = !!(displayMobile ||
|
|
688
725
|
displayTablet ||
|
|
726
|
+
displayTabletOnly ||
|
|
727
|
+
displayTouch ||
|
|
689
728
|
displayDesktop ||
|
|
729
|
+
displayDesktopOnly ||
|
|
690
730
|
displayWidescreen ||
|
|
731
|
+
displayWidescreenOnly ||
|
|
691
732
|
displayFullhd);
|
|
692
733
|
if (!hasViewportSpecificDisplay) {
|
|
693
734
|
// Legacy display handling
|
|
@@ -720,14 +761,22 @@ const useVisibilityClasses = (props) => {
|
|
|
720
761
|
visibility,
|
|
721
762
|
visibilityMobile,
|
|
722
763
|
visibilityTablet,
|
|
764
|
+
visibilityTabletOnly,
|
|
765
|
+
visibilityTouch,
|
|
723
766
|
visibilityDesktop,
|
|
767
|
+
visibilityDesktopOnly,
|
|
724
768
|
visibilityWidescreen,
|
|
769
|
+
visibilityWidescreenOnly,
|
|
725
770
|
visibilityFullhd,
|
|
726
771
|
display,
|
|
727
772
|
displayMobile,
|
|
728
773
|
displayTablet,
|
|
774
|
+
displayTabletOnly,
|
|
775
|
+
displayTouch,
|
|
729
776
|
displayDesktop,
|
|
777
|
+
displayDesktopOnly,
|
|
730
778
|
displayWidescreen,
|
|
779
|
+
displayWidescreenOnly,
|
|
731
780
|
displayFullhd,
|
|
732
781
|
viewport,
|
|
733
782
|
]);
|
|
@@ -756,7 +805,7 @@ const useVisibilityClasses = (props) => {
|
|
|
756
805
|
*/
|
|
757
806
|
const useFlexboxClasses = (props) => {
|
|
758
807
|
const { classPrefix } = useConfig();
|
|
759
|
-
const { flexDirection, flexWrap, justifyContent, alignContent, alignItems, alignSelf, flexGrow, flexShrink, display, displayMobile, displayTablet, displayDesktop, displayWidescreen, displayFullhd, } = props;
|
|
808
|
+
const { flexDirection, flexWrap, justifyContent, alignContent, alignItems, alignSelf, flexGrow, flexShrink, display, displayMobile, displayTablet, displayTabletOnly, displayTouch, displayDesktop, displayDesktopOnly, displayWidescreen, displayWidescreenOnly, displayFullhd, } = props;
|
|
760
809
|
return useMemo(() => {
|
|
761
810
|
const { classes, addClassNoViewport } = createBulmaClassHelpers(classPrefix);
|
|
762
811
|
// Flexbox
|
|
@@ -766,10 +815,18 @@ const useFlexboxClasses = (props) => {
|
|
|
766
815
|
displayMobile === 'inline-flex' ||
|
|
767
816
|
displayTablet === 'flex' ||
|
|
768
817
|
displayTablet === 'inline-flex' ||
|
|
818
|
+
displayTabletOnly === 'flex' ||
|
|
819
|
+
displayTabletOnly === 'inline-flex' ||
|
|
820
|
+
displayTouch === 'flex' ||
|
|
821
|
+
displayTouch === 'inline-flex' ||
|
|
769
822
|
displayDesktop === 'flex' ||
|
|
770
823
|
displayDesktop === 'inline-flex' ||
|
|
824
|
+
displayDesktopOnly === 'flex' ||
|
|
825
|
+
displayDesktopOnly === 'inline-flex' ||
|
|
771
826
|
displayWidescreen === 'flex' ||
|
|
772
827
|
displayWidescreen === 'inline-flex' ||
|
|
828
|
+
displayWidescreenOnly === 'flex' ||
|
|
829
|
+
displayWidescreenOnly === 'inline-flex' ||
|
|
773
830
|
displayFullhd === 'flex' ||
|
|
774
831
|
displayFullhd === 'inline-flex';
|
|
775
832
|
if (hasFlexDisplay) {
|
|
@@ -799,8 +856,12 @@ const useFlexboxClasses = (props) => {
|
|
|
799
856
|
display,
|
|
800
857
|
displayMobile,
|
|
801
858
|
displayTablet,
|
|
859
|
+
displayTabletOnly,
|
|
860
|
+
displayTouch,
|
|
802
861
|
displayDesktop,
|
|
862
|
+
displayDesktopOnly,
|
|
803
863
|
displayWidescreen,
|
|
864
|
+
displayWidescreenOnly,
|
|
804
865
|
displayFullhd,
|
|
805
866
|
]);
|
|
806
867
|
};
|
|
@@ -913,7 +974,7 @@ const useOtherClasses = (props) => {
|
|
|
913
974
|
* // rest: { className: 'custom-class' }
|
|
914
975
|
*/
|
|
915
976
|
const useBulmaClasses = (props) => {
|
|
916
|
-
const { color, backgroundColor, colorShade, backgroundColorShade, m, mt, mr, mb, ml, mx, my, p, pt, pr, pb, pl, px, py, textSize, textAlign, textTransform, textWeight, fontFamily, display, visibility, flexDirection, flexWrap, justifyContent, alignContent, alignItems, alignSelf, flexGrow, flexShrink, float, overflow, overlay, interaction, cursor, radius, shadow, responsive, viewport, displayMobile, displayTablet, displayDesktop, displayWidescreen, displayFullhd, textSizeMobile, textSizeTablet, textSizeDesktop, textSizeWidescreen, textSizeFullhd, textAlignMobile, textAlignTablet, textAlignDesktop, textAlignWidescreen, textAlignFullhd, visibilityMobile, visibilityTablet, visibilityDesktop, visibilityWidescreen, visibilityFullhd, skeleton, clearfix, relative, fullHeight, ...rest } = props;
|
|
977
|
+
const { color, backgroundColor, colorShade, backgroundColorShade, m, mt, mr, mb, ml, mx, my, p, pt, pr, pb, pl, px, py, textSize, textAlign, textTransform, textWeight, fontFamily, display, visibility, flexDirection, flexWrap, justifyContent, alignContent, alignItems, alignSelf, flexGrow, flexShrink, float, overflow, overlay, interaction, cursor, radius, shadow, responsive, viewport, displayMobile, displayTablet, displayTabletOnly, displayTouch, displayDesktop, displayDesktopOnly, displayWidescreen, displayWidescreenOnly, displayFullhd, textSizeMobile, textSizeTablet, textSizeDesktop, textSizeWidescreen, textSizeFullhd, textAlignMobile, textAlignTablet, textAlignDesktop, textAlignWidescreen, textAlignFullhd, visibilityMobile, visibilityTablet, visibilityTabletOnly, visibilityTouch, visibilityDesktop, visibilityDesktopOnly, visibilityWidescreen, visibilityWidescreenOnly, visibilityFullhd, skeleton, clearfix, relative, fullHeight, ...rest } = props;
|
|
917
978
|
const colorClasses = useColorClasses({
|
|
918
979
|
color,
|
|
919
980
|
colorShade,
|
|
@@ -959,14 +1020,22 @@ const useBulmaClasses = (props) => {
|
|
|
959
1020
|
visibility,
|
|
960
1021
|
visibilityMobile,
|
|
961
1022
|
visibilityTablet,
|
|
1023
|
+
visibilityTabletOnly,
|
|
1024
|
+
visibilityTouch,
|
|
962
1025
|
visibilityDesktop,
|
|
1026
|
+
visibilityDesktopOnly,
|
|
963
1027
|
visibilityWidescreen,
|
|
1028
|
+
visibilityWidescreenOnly,
|
|
964
1029
|
visibilityFullhd,
|
|
965
1030
|
display,
|
|
966
1031
|
displayMobile,
|
|
967
1032
|
displayTablet,
|
|
1033
|
+
displayTabletOnly,
|
|
1034
|
+
displayTouch,
|
|
968
1035
|
displayDesktop,
|
|
1036
|
+
displayDesktopOnly,
|
|
969
1037
|
displayWidescreen,
|
|
1038
|
+
displayWidescreenOnly,
|
|
970
1039
|
displayFullhd,
|
|
971
1040
|
viewport,
|
|
972
1041
|
});
|
|
@@ -982,8 +1051,12 @@ const useBulmaClasses = (props) => {
|
|
|
982
1051
|
display,
|
|
983
1052
|
displayMobile,
|
|
984
1053
|
displayTablet,
|
|
1054
|
+
displayTabletOnly,
|
|
1055
|
+
displayTouch,
|
|
985
1056
|
displayDesktop,
|
|
1057
|
+
displayDesktopOnly,
|
|
986
1058
|
displayWidescreen,
|
|
1059
|
+
displayWidescreenOnly,
|
|
987
1060
|
displayFullhd,
|
|
988
1061
|
});
|
|
989
1062
|
const otherClasses = useOtherClasses({
|
|
@@ -1707,6 +1780,8 @@ const isBrowser$1 = (win, doc) => typeof win !== 'undefined' && typeof doc !== '
|
|
|
1707
1780
|
const DropdownComponent = ({ label, children, className, menuClassName, active: activeProp, up, right, hoverable, disabled, onActiveChange, closeOnClick = true, id, ...props }) => {
|
|
1708
1781
|
const [active, setActive] = useState(!!activeProp);
|
|
1709
1782
|
const dropdownRef = useRef(null);
|
|
1783
|
+
const triggerRef = useRef(null);
|
|
1784
|
+
const pendingFocusRef = useRef(null);
|
|
1710
1785
|
const { bulmaHelperClasses, rest } = useBulmaClasses(props);
|
|
1711
1786
|
// Generate Bulma classes with prefix
|
|
1712
1787
|
const bulmaClasses = usePrefixedClassNames('dropdown', {
|
|
@@ -1755,8 +1830,126 @@ const DropdownComponent = ({ label, children, className, menuClassName, active:
|
|
|
1755
1830
|
onActiveChange?.(false);
|
|
1756
1831
|
}
|
|
1757
1832
|
};
|
|
1833
|
+
const getMenuItems = () => {
|
|
1834
|
+
/* istanbul ignore next: dropdownRef.current is never null once mounted */
|
|
1835
|
+
if (!dropdownRef.current)
|
|
1836
|
+
return [];
|
|
1837
|
+
return Array.from(dropdownRef.current.querySelectorAll('[role="menuitem"]')).filter(el => !el.hasAttribute('disabled') &&
|
|
1838
|
+
el.getAttribute('aria-disabled') !== 'true');
|
|
1839
|
+
};
|
|
1840
|
+
// Focus the pending menu item once the menu becomes visible; the item is
|
|
1841
|
+
// unfocusable while `display: none` applies, so this must wait for the
|
|
1842
|
+
// `is-active` class to actually land in the DOM.
|
|
1843
|
+
useEffect(() => {
|
|
1844
|
+
if (!active || !pendingFocusRef.current)
|
|
1845
|
+
return;
|
|
1846
|
+
const items = getMenuItems();
|
|
1847
|
+
if (items.length) {
|
|
1848
|
+
const index = pendingFocusRef.current === 'first' ? 0 : items.length - 1;
|
|
1849
|
+
items[index].focus();
|
|
1850
|
+
}
|
|
1851
|
+
pendingFocusRef.current = null;
|
|
1852
|
+
}, [active]);
|
|
1853
|
+
const handleTriggerKeyDown = (e) => {
|
|
1854
|
+
if (disabled)
|
|
1855
|
+
return;
|
|
1856
|
+
switch (e.key) {
|
|
1857
|
+
case 'ArrowDown':
|
|
1858
|
+
e.preventDefault();
|
|
1859
|
+
pendingFocusRef.current = 'first';
|
|
1860
|
+
if (!active) {
|
|
1861
|
+
setActive(true);
|
|
1862
|
+
onActiveChange?.(true);
|
|
1863
|
+
}
|
|
1864
|
+
else {
|
|
1865
|
+
const items = getMenuItems();
|
|
1866
|
+
if (items.length)
|
|
1867
|
+
items[0].focus();
|
|
1868
|
+
pendingFocusRef.current = null;
|
|
1869
|
+
}
|
|
1870
|
+
break;
|
|
1871
|
+
case 'ArrowUp':
|
|
1872
|
+
e.preventDefault();
|
|
1873
|
+
pendingFocusRef.current = 'last';
|
|
1874
|
+
if (!active) {
|
|
1875
|
+
setActive(true);
|
|
1876
|
+
onActiveChange?.(true);
|
|
1877
|
+
}
|
|
1878
|
+
else {
|
|
1879
|
+
const items = getMenuItems();
|
|
1880
|
+
if (items.length)
|
|
1881
|
+
items[items.length - 1].focus();
|
|
1882
|
+
pendingFocusRef.current = null;
|
|
1883
|
+
}
|
|
1884
|
+
break;
|
|
1885
|
+
case 'Enter':
|
|
1886
|
+
case ' ':
|
|
1887
|
+
e.preventDefault();
|
|
1888
|
+
if (!active) {
|
|
1889
|
+
pendingFocusRef.current = 'first';
|
|
1890
|
+
setActive(true);
|
|
1891
|
+
onActiveChange?.(true);
|
|
1892
|
+
}
|
|
1893
|
+
else {
|
|
1894
|
+
setActive(false);
|
|
1895
|
+
onActiveChange?.(false);
|
|
1896
|
+
}
|
|
1897
|
+
break;
|
|
1898
|
+
case 'Escape':
|
|
1899
|
+
if (active) {
|
|
1900
|
+
e.preventDefault();
|
|
1901
|
+
setActive(false);
|
|
1902
|
+
onActiveChange?.(false);
|
|
1903
|
+
}
|
|
1904
|
+
break;
|
|
1905
|
+
}
|
|
1906
|
+
};
|
|
1907
|
+
const handleMenuKeyDown = (e) => {
|
|
1908
|
+
// Escape and Tab close the menu regardless of whether it has any focusable
|
|
1909
|
+
// items — an empty or all-disabled menu must still honor the close contract.
|
|
1910
|
+
if (e.key === 'Escape') {
|
|
1911
|
+
e.preventDefault();
|
|
1912
|
+
setActive(false);
|
|
1913
|
+
onActiveChange?.(false);
|
|
1914
|
+
triggerRef.current?.focus();
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1917
|
+
if (e.key === 'Tab') {
|
|
1918
|
+
setActive(false);
|
|
1919
|
+
onActiveChange?.(false);
|
|
1920
|
+
return;
|
|
1921
|
+
}
|
|
1922
|
+
const items = getMenuItems();
|
|
1923
|
+
if (!items.length)
|
|
1924
|
+
return;
|
|
1925
|
+
const currentIndex = items.indexOf(document.activeElement);
|
|
1926
|
+
switch (e.key) {
|
|
1927
|
+
case 'ArrowDown': {
|
|
1928
|
+
e.preventDefault();
|
|
1929
|
+
const next = currentIndex >= 0 ? (currentIndex + 1) % items.length : 0;
|
|
1930
|
+
items[next].focus();
|
|
1931
|
+
break;
|
|
1932
|
+
}
|
|
1933
|
+
case 'ArrowUp': {
|
|
1934
|
+
e.preventDefault();
|
|
1935
|
+
const prev = currentIndex >= 0
|
|
1936
|
+
? (currentIndex - 1 + items.length) % items.length
|
|
1937
|
+
: items.length - 1;
|
|
1938
|
+
items[prev].focus();
|
|
1939
|
+
break;
|
|
1940
|
+
}
|
|
1941
|
+
case 'Home':
|
|
1942
|
+
e.preventDefault();
|
|
1943
|
+
items[0].focus();
|
|
1944
|
+
break;
|
|
1945
|
+
case 'End':
|
|
1946
|
+
e.preventDefault();
|
|
1947
|
+
items[items.length - 1].focus();
|
|
1948
|
+
break;
|
|
1949
|
+
}
|
|
1950
|
+
};
|
|
1758
1951
|
const dropdownClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
1759
|
-
return (jsxs("div", { className: dropdownClasses, ref: dropdownRef, id: id, "data-testid": "dropdown-root", ...rest, children: [jsx("div", { className: usePrefixedClassNames('dropdown-trigger'), children: jsxs("button", { className: buttonClass, "aria-haspopup": "true", "aria-controls": id ? `${id}-menu` : undefined, "aria-expanded": active, onClick: handleToggle, disabled: disabled, type: "button", children: [jsx("span", { children: label }), jsx("span", { className: usePrefixedClassNames('icon', 'is-small'), "aria-hidden": "true", children: jsx("i", { className: "fas fa-angle-down" }) })] }) }), jsx("div", { className: classNames(usePrefixedClassNames('dropdown-menu'), menuClassName), id: id ? `${id}-menu` : undefined, role: "menu", "data-testid": "dropdown-menu", children: jsx("div", { className: usePrefixedClassNames('dropdown-content'), onClick: handleMenuClick, tabIndex: -1, children: children }) })] }));
|
|
1952
|
+
return (jsxs("div", { className: dropdownClasses, ref: dropdownRef, id: id, "data-testid": "dropdown-root", ...rest, children: [jsx("div", { className: usePrefixedClassNames('dropdown-trigger'), children: jsxs("button", { ref: triggerRef, className: buttonClass, "aria-haspopup": "true", "aria-controls": id ? `${id}-menu` : undefined, "aria-expanded": active, onClick: handleToggle, onKeyDown: handleTriggerKeyDown, disabled: disabled, type: "button", children: [jsx("span", { children: label }), jsx("span", { className: usePrefixedClassNames('icon', 'is-small'), "aria-hidden": "true", children: jsx("i", { className: "fas fa-angle-down" }) })] }) }), jsx("div", { className: classNames(usePrefixedClassNames('dropdown-menu'), menuClassName), id: id ? `${id}-menu` : undefined, role: "menu", "data-testid": "dropdown-menu", onKeyDown: handleMenuKeyDown, children: jsx("div", { className: usePrefixedClassNames('dropdown-content'), onClick: handleMenuClick, tabIndex: -1, children: children }) })] }));
|
|
1760
1953
|
};
|
|
1761
1954
|
/**
|
|
1762
1955
|
* Bulma Dropdown item.
|
|
@@ -2085,6 +2278,7 @@ const Modal = withSubComponents(ModalRoot, {
|
|
|
2085
2278
|
Close: ModalClose,
|
|
2086
2279
|
}, 'Modal');
|
|
2087
2280
|
|
|
2281
|
+
const NavbarDropdownContext = React.createContext(null);
|
|
2088
2282
|
/**
|
|
2089
2283
|
* The `Navbar` component implements Bulma's powerful, responsive navigation bar for your Bulma React UI.
|
|
2090
2284
|
*
|
|
@@ -2208,9 +2402,45 @@ const NavbarLink = ({ className, as: Component = 'a', arrowless, textColor, bgCo
|
|
|
2208
2402
|
backgroundColor: bgColor,
|
|
2209
2403
|
...props,
|
|
2210
2404
|
});
|
|
2405
|
+
const dropdownContext = useContext(NavbarDropdownContext);
|
|
2406
|
+
const hasHref = rest.href !== undefined;
|
|
2407
|
+
const isNativeInteractive = Component === 'button' || hasHref;
|
|
2408
|
+
const handleKeyDown = (e) => {
|
|
2409
|
+
rest.onKeyDown?.(e);
|
|
2410
|
+
if (!dropdownContext || e.defaultPrevented)
|
|
2411
|
+
return;
|
|
2412
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
2413
|
+
e.preventDefault();
|
|
2414
|
+
dropdownContext.toggle();
|
|
2415
|
+
}
|
|
2416
|
+
else if (e.key === 'Escape' && dropdownContext.active) {
|
|
2417
|
+
e.preventDefault();
|
|
2418
|
+
dropdownContext.close();
|
|
2419
|
+
}
|
|
2420
|
+
};
|
|
2421
|
+
// When the link is not natively interactive (no `href`, not a `<button>`) it
|
|
2422
|
+
// renders as `role="button"`, so a mouse/touch click has no default action to
|
|
2423
|
+
// open the dropdown. Compose the caller's handler and toggle the dropdown
|
|
2424
|
+
// unless the caller prevented the default. Native links/buttons keep their own
|
|
2425
|
+
// click semantics (navigation / the caller's handler).
|
|
2426
|
+
const handleClick = (e) => {
|
|
2427
|
+
rest.onClick?.(e);
|
|
2428
|
+
if (!dropdownContext || isNativeInteractive || e.defaultPrevented)
|
|
2429
|
+
return;
|
|
2430
|
+
dropdownContext.toggle();
|
|
2431
|
+
};
|
|
2211
2432
|
return (jsx(Component, { className: classNames(usePrefixedClassNames('navbar-link', {
|
|
2212
2433
|
'is-arrowless': arrowless,
|
|
2213
|
-
}), bulmaHelperClasses, className), ...rest,
|
|
2434
|
+
}), bulmaHelperClasses, className), ...rest, ...(dropdownContext && {
|
|
2435
|
+
'aria-haspopup': 'true',
|
|
2436
|
+
'aria-expanded': dropdownContext.active,
|
|
2437
|
+
onKeyDown: handleKeyDown,
|
|
2438
|
+
...(!isNativeInteractive && {
|
|
2439
|
+
role: 'button',
|
|
2440
|
+
tabIndex: 0,
|
|
2441
|
+
onClick: handleClick,
|
|
2442
|
+
}),
|
|
2443
|
+
}), children: children }));
|
|
2214
2444
|
};
|
|
2215
2445
|
/**
|
|
2216
2446
|
* Dropdown parent (with options for hover, up, right, active)
|
|
@@ -2219,12 +2449,34 @@ const NavbarLink = ({ className, as: Component = 'a', arrowless, textColor, bgCo
|
|
|
2219
2449
|
* @param {NavbarDropdownProps} props - Props for the NavbarDropdown component.
|
|
2220
2450
|
* @returns {JSX.Element} The rendered dropdown.
|
|
2221
2451
|
*/
|
|
2222
|
-
const NavbarDropdown = ({ className, right, up, hoverable, active, children, ...props }) =>
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
'
|
|
2227
|
-
|
|
2452
|
+
const NavbarDropdown = ({ className, right, up, hoverable, active: activeProp, onActiveChange, children, ...props }) => {
|
|
2453
|
+
const [active, setActive] = useState(!!activeProp);
|
|
2454
|
+
useEffect(() => {
|
|
2455
|
+
// eslint-disable-next-line react-hooks/set-state-in-effect -- syncing to controlled prop
|
|
2456
|
+
if (typeof activeProp === 'boolean')
|
|
2457
|
+
setActive(activeProp);
|
|
2458
|
+
}, [activeProp]);
|
|
2459
|
+
// Keep `onActiveChange` out of the state updater (a pure function) so Strict
|
|
2460
|
+
// Mode's double-invoked updaters don't double-fire it — mirrors `Dropdown`.
|
|
2461
|
+
const toggle = () => {
|
|
2462
|
+
const next = !active;
|
|
2463
|
+
setActive(next);
|
|
2464
|
+
onActiveChange?.(next);
|
|
2465
|
+
};
|
|
2466
|
+
const close = () => {
|
|
2467
|
+
/* istanbul ignore next: close() is only invoked once already active */
|
|
2468
|
+
if (!active)
|
|
2469
|
+
return;
|
|
2470
|
+
setActive(false);
|
|
2471
|
+
onActiveChange?.(false);
|
|
2472
|
+
};
|
|
2473
|
+
return (jsx(NavbarDropdownContext.Provider, { value: { active, toggle, close }, children: jsx("div", { className: classNames(usePrefixedClassNames('navbar-item', 'has-dropdown', {
|
|
2474
|
+
'has-dropdown-up': up,
|
|
2475
|
+
'is-right': right,
|
|
2476
|
+
'is-hoverable': hoverable,
|
|
2477
|
+
'is-active': active,
|
|
2478
|
+
}), className), ...props, children: children }) }));
|
|
2479
|
+
};
|
|
2228
2480
|
/**
|
|
2229
2481
|
* Dropdown menu container
|
|
2230
2482
|
*
|
|
@@ -2534,8 +2786,37 @@ function getIconClasses(library, name, variant, features) {
|
|
|
2534
2786
|
const Icon = ({ className, textColor, bgColor, name, library, variant, features, libraryFeatures, // Deprecated but maintained for backward compatibility
|
|
2535
2787
|
size, ariaLabel = 'icon', style, icon, // Capture and exclude the deprecated 'icon' prop from DOM
|
|
2536
2788
|
color: _color, // Exclude 'color' prop if passed directly
|
|
2537
|
-
containerClassName, ...restProps }) => {
|
|
2538
|
-
//
|
|
2789
|
+
containerClassName, children, ...restProps }) => {
|
|
2790
|
+
// Get the default icon library from context, fallback to 'fa' if not set
|
|
2791
|
+
const defaultLibrary = useIconLibrary();
|
|
2792
|
+
/**
|
|
2793
|
+
* Generates Bulma helper classes and separates out remaining props.
|
|
2794
|
+
* Note: variant, features, and libraryFeatures are excluded from props spread
|
|
2795
|
+
*/
|
|
2796
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
2797
|
+
color: textColor,
|
|
2798
|
+
backgroundColor: bgColor,
|
|
2799
|
+
...restProps,
|
|
2800
|
+
});
|
|
2801
|
+
// Hoisted unconditionally to respect rules-of-hooks; the branches below
|
|
2802
|
+
// pick which result to consume.
|
|
2803
|
+
const defaultIconClasses = usePrefixedClassNames('icon', {
|
|
2804
|
+
[`is-${size}`]: size,
|
|
2805
|
+
});
|
|
2806
|
+
const sizeModifierClass = usePrefixedClassNames(size ? `is-${size}` : undefined);
|
|
2807
|
+
const bulmaClasses = containerClassName
|
|
2808
|
+
? containerClassName
|
|
2809
|
+
: defaultIconClasses;
|
|
2810
|
+
const iconContainerClasses = classNames(bulmaClasses, containerClassName && size ? sizeModifierClass : undefined, bulmaHelperClasses, className);
|
|
2811
|
+
if (children !== undefined) {
|
|
2812
|
+
// `IconChildrenProps`: render the caller's node (an inline SVG, a `react-icons`
|
|
2813
|
+
// component, …) in place of a class-based glyph. Library/variant/features don't apply.
|
|
2814
|
+
return (jsx("span", { className: iconContainerClasses, "aria-label": ariaLabel, style: style, ...rest, children: children }));
|
|
2815
|
+
}
|
|
2816
|
+
// `name` is guaranteed once `children` is absent (`IconProps` is a discriminated union of
|
|
2817
|
+
// the two, and `IconChildrenProps['children']` excludes `undefined` so `children={undefined}`
|
|
2818
|
+
// can't slip past into this branch) — the cast only matters for legacy callers that bypass
|
|
2819
|
+
// the type and rely solely on the deprecated `icon` prop below.
|
|
2539
2820
|
let finalName = name;
|
|
2540
2821
|
if (!name && icon) {
|
|
2541
2822
|
// If icon prop is provided instead of name, try to parse it
|
|
@@ -2554,31 +2835,16 @@ containerClassName, ...restProps }) => {
|
|
|
2554
2835
|
}
|
|
2555
2836
|
}
|
|
2556
2837
|
}
|
|
2557
|
-
// Get the default icon library from context, fallback to 'fa' if not set
|
|
2558
|
-
const defaultLibrary = useIconLibrary();
|
|
2559
2838
|
const finalLibrary = library || defaultLibrary || 'fa';
|
|
2560
2839
|
// Normalize a redundant leading library prefix (e.g. "fa-check" -> "check" when the
|
|
2561
2840
|
// library is 'fa'), so `name` behaves identically with or without the prefix.
|
|
2562
2841
|
finalName = stripRedundantLibraryPrefix(finalName, finalLibrary);
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
backgroundColor: bgColor,
|
|
2570
|
-
...restProps,
|
|
2571
|
-
});
|
|
2572
|
-
// Hoisted unconditionally to respect rules-of-hooks; the ternaries below
|
|
2573
|
-
// pick which result to consume.
|
|
2574
|
-
const defaultIconClasses = usePrefixedClassNames('icon', {
|
|
2575
|
-
[`is-${size}`]: size,
|
|
2576
|
-
});
|
|
2577
|
-
const sizeModifierClass = usePrefixedClassNames(size ? `is-${size}` : undefined);
|
|
2578
|
-
const bulmaClasses = containerClassName
|
|
2579
|
-
? containerClassName
|
|
2580
|
-
: defaultIconClasses;
|
|
2581
|
-
const iconContainerClasses = classNames(bulmaClasses, containerClassName && size ? sizeModifierClass : undefined, bulmaHelperClasses, className);
|
|
2842
|
+
if (!finalName) {
|
|
2843
|
+
// No glyph to name. Unreachable through the public type, but a plain-JS caller (or one
|
|
2844
|
+
// that casts) can land here, and building a class off an absent name produced a bogus
|
|
2845
|
+
// `fa-undefined` glyph. Render the bare container instead, matching the `children` branch.
|
|
2846
|
+
return (jsx("span", { className: iconContainerClasses, "aria-label": ariaLabel, style: style, ...rest }));
|
|
2847
|
+
}
|
|
2582
2848
|
// Backward compatibility: if libraryFeatures is provided, parse it for variant and features
|
|
2583
2849
|
let finalVariant = variant;
|
|
2584
2850
|
let finalFeatures = features;
|
|
@@ -2697,8 +2963,9 @@ const PanelTabs = ({ className, children, ...props }) => (jsx("p", { className:
|
|
|
2697
2963
|
*/
|
|
2698
2964
|
const PanelBlock = ({ className, active, children, ...props }) => (jsx("a", { className: classNames(usePrefixedClassNames('panel-block', { 'is-active': active }), className), ...props, children: children }));
|
|
2699
2965
|
/**
|
|
2700
|
-
* Icon wrapper with panel styling (renders as `<span class="panel-icon"
|
|
2701
|
-
*
|
|
2966
|
+
* Icon wrapper with panel styling (renders as `<span class="panel-icon">`, containing an
|
|
2967
|
+
* `<i/>` when a `name` is given, or the custom node passed as `children`).
|
|
2968
|
+
* Accepts all Icon props (`name`, `variant`, `features`, etc.), or `children` for a custom node.
|
|
2702
2969
|
*
|
|
2703
2970
|
* @function
|
|
2704
2971
|
* @param {PanelIconProps} props - Props for the PanelIcon component.
|
|
@@ -4873,6 +5140,16 @@ const Figure = withSubComponents(FigureComponent, {
|
|
|
4873
5140
|
Caption: FigureCaption,
|
|
4874
5141
|
}, 'Figure');
|
|
4875
5142
|
|
|
5143
|
+
/**
|
|
5144
|
+
* Distinguishes an `IconProps` object from a plain custom node (an inline SVG, a `react-icons`
|
|
5145
|
+
* component, …) passed to a slot that accepts either.
|
|
5146
|
+
*/
|
|
5147
|
+
function isIconProps$1(value) {
|
|
5148
|
+
return (typeof value === 'object' &&
|
|
5149
|
+
value !== null &&
|
|
5150
|
+
!React.isValidElement(value) &&
|
|
5151
|
+
('name' in value || 'children' in value));
|
|
5152
|
+
}
|
|
4876
5153
|
/**
|
|
4877
5154
|
* The `IconText` component provides a Bulma-styled horizontal arrangement of one or more `Icon` components and optional text.
|
|
4878
5155
|
*
|
|
@@ -4892,7 +5169,9 @@ const IconTextComponent = ({ className, textColor, color, bgColor, iconProps, ch
|
|
|
4892
5169
|
});
|
|
4893
5170
|
const bulmaClasses = usePrefixedClassNames('icon-text');
|
|
4894
5171
|
const iconTextClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
4895
|
-
return (jsx("span", { className: iconTextClasses, ...rest, children: items ? (items.map((item, index) => (jsxs(React.Fragment, { children: [
|
|
5172
|
+
return (jsx("span", { className: iconTextClasses, ...rest, children: items ? (items.map((item, index) => (jsxs(React.Fragment, { children: [item.iconProps &&
|
|
5173
|
+
(isIconProps$1(item.iconProps) ? (jsx(Icon, { ...item.iconProps })) : (jsx(Icon, { children: item.iconProps }))), item.text && jsx("span", { children: item.text })] }, index)))) : (jsxs(Fragment, { children: [iconProps &&
|
|
5174
|
+
(isIconProps$1(iconProps) ? (jsx(Icon, { ...iconProps })) : (jsx(Icon, { children: iconProps }))), children && jsx("span", { children: children })] })) }));
|
|
4896
5175
|
};
|
|
4897
5176
|
const IconText = withSubComponents(IconTextComponent, { Icon }, 'IconText');
|
|
4898
5177
|
|
|
@@ -5599,21 +5878,22 @@ const Tag = ({ className, color, size, isLight, isRounded, isDelete, isHoverable
|
|
|
5599
5878
|
|
|
5600
5879
|
const validTagsSizes = ['medium', 'large'];
|
|
5601
5880
|
/**
|
|
5602
|
-
* The `Tags` component groups multiple `Tag` components together in a
|
|
5881
|
+
* The `Tags` component groups multiple `Tag` components together in a Bulma-styled container that wraps onto multiple lines by default.
|
|
5603
5882
|
*
|
|
5604
5883
|
* @function
|
|
5605
5884
|
* @param {TagsProps} props - Props for the Tags component.
|
|
5606
5885
|
* @returns {JSX.Element} The rendered tags container.
|
|
5607
5886
|
* @see {@link https://bulma.io/documentation/elements/tag/#list-of-tags | Bulma Tags documentation}
|
|
5608
5887
|
*/
|
|
5609
|
-
const TagsComponent = ({ className, hasAddons,
|
|
5888
|
+
const TagsComponent = ({ className, hasAddons,
|
|
5889
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- accepted for backwards compatibility, deprecated no-op (no `are-multiline` CSS ships)
|
|
5890
|
+
isMultiline, size, children, ...props }) => {
|
|
5610
5891
|
/**
|
|
5611
5892
|
* Generates Bulma helper classes and separates out remaining props.
|
|
5612
5893
|
*/
|
|
5613
5894
|
const { bulmaHelperClasses, rest } = useBulmaClasses({ ...props });
|
|
5614
5895
|
const bulmaClasses = usePrefixedClassNames('tags', {
|
|
5615
5896
|
'has-addons': hasAddons,
|
|
5616
|
-
'are-multiline': isMultiline,
|
|
5617
5897
|
[`are-${size}`]: size && validTagsSizes.includes(size),
|
|
5618
5898
|
});
|
|
5619
5899
|
const tagsClasses = classNames(bulmaClasses, bulmaHelperClasses, className);
|
|
@@ -5802,6 +6082,16 @@ const Checkbox = forwardRef(({ color, size, className, children, textColor, disa
|
|
|
5802
6082
|
});
|
|
5803
6083
|
Checkbox.displayName = 'Checkbox';
|
|
5804
6084
|
|
|
6085
|
+
/**
|
|
6086
|
+
* Distinguishes an `IconProps` object from a plain custom node (an inline SVG, a `react-icons`
|
|
6087
|
+
* component, …) passed to a slot that accepts either.
|
|
6088
|
+
*/
|
|
6089
|
+
function isIconProps(value) {
|
|
6090
|
+
return (typeof value === 'object' &&
|
|
6091
|
+
value !== null &&
|
|
6092
|
+
!React.isValidElement(value) &&
|
|
6093
|
+
('name' in value || 'children' in value));
|
|
6094
|
+
}
|
|
5805
6095
|
const allowedColors = [...validColors, 'inherit', 'current'];
|
|
5806
6096
|
/**
|
|
5807
6097
|
* The `Control` component is a Bulma-styled wrapper for form controls (`Input`, `Select`, `TextArea`, etc.), supporting icons (left/right), loading state, expansion, size, and Bulma helper props for layout and color.
|
|
@@ -5833,15 +6123,19 @@ const Control = React.forwardRef(({ as = 'div', hasIconsLeft, hasIconsRight, isL
|
|
|
5833
6123
|
backgroundColor: safeBgColor,
|
|
5834
6124
|
...restProps,
|
|
5835
6125
|
});
|
|
5836
|
-
// Prepare icon props for the shortcut
|
|
5837
|
-
|
|
6126
|
+
// Prepare icon props for the shortcut. A truthiness test (not `!== undefined`)
|
|
6127
|
+
// so a falsy node from the idiomatic `iconLeft={cond && <Node/>}` / `iconLeft={maybe ?? null}`
|
|
6128
|
+
// pattern counts as "no icon" — otherwise `false`/`null` would reserve the icon column and
|
|
6129
|
+
// mount an empty `.icon` span. A falsy `iconLeft` also correctly falls through to the
|
|
6130
|
+
// `iconLeftName` shortcut, matching the pre-node behavior.
|
|
6131
|
+
const leftIconValue = iconLeft ||
|
|
5838
6132
|
(iconLeftName
|
|
5839
6133
|
? {
|
|
5840
6134
|
name: iconLeftName,
|
|
5841
6135
|
size: iconLeftSize,
|
|
5842
6136
|
}
|
|
5843
6137
|
: undefined);
|
|
5844
|
-
const
|
|
6138
|
+
const rightIconValue = iconRight ||
|
|
5845
6139
|
(iconRightName
|
|
5846
6140
|
? {
|
|
5847
6141
|
name: iconRightName,
|
|
@@ -5849,15 +6143,17 @@ const Control = React.forwardRef(({ as = 'div', hasIconsLeft, hasIconsRight, isL
|
|
|
5849
6143
|
}
|
|
5850
6144
|
: undefined);
|
|
5851
6145
|
const mainClass = usePrefixedClassNames('control', {
|
|
5852
|
-
'has-icons-left': hasIconsLeft || !!
|
|
5853
|
-
'has-icons-right': hasIconsRight || !!
|
|
6146
|
+
'has-icons-left': hasIconsLeft || !!leftIconValue,
|
|
6147
|
+
'has-icons-right': hasIconsRight || !!rightIconValue,
|
|
5854
6148
|
'is-loading': isLoading,
|
|
5855
6149
|
'is-expanded': isExpanded,
|
|
5856
6150
|
[`is-${size}`]: !!size,
|
|
5857
6151
|
});
|
|
5858
6152
|
const controlClass = classNames(mainClass, bulmaHelperClasses, className);
|
|
5859
6153
|
// --- FIX: Spread both restProps (for data-testid, etc) AND rest (from useBulmaClasses) ---
|
|
5860
|
-
return (jsx(ControlProvider, { value: true, children: jsxs(Component, { className: controlClass, ref: ref, ...restProps, ...rest, children: [children,
|
|
6154
|
+
return (jsx(ControlProvider, { value: true, children: jsxs(Component, { className: controlClass, ref: ref, ...restProps, ...rest, children: [children, leftIconValue &&
|
|
6155
|
+
(isIconProps(leftIconValue) ? (jsx(Icon, { ...leftIconValue, className: prefixedClassNames(classPrefix, 'is-left') })) : (jsx(Icon, { className: prefixedClassNames(classPrefix, 'is-left'), children: leftIconValue }))), rightIconValue &&
|
|
6156
|
+
(isIconProps(rightIconValue) ? (jsx(Icon, { ...rightIconValue, className: prefixedClassNames(classPrefix, 'is-right') })) : (jsx(Icon, { className: prefixedClassNames(classPrefix, 'is-right'), children: rightIconValue })))] }) }));
|
|
5861
6157
|
});
|
|
5862
6158
|
Control.displayName = 'Control';
|
|
5863
6159
|
|