@kne/system-layout 0.2.0-alpha.23 → 0.2.0-alpha.25

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.
@@ -10,7 +10,7 @@ import Icon from '@kne/react-icon';
10
10
  import ensureSlash from '@kne/ensure-slash';
11
11
  import isPlainObject from 'lodash/isPlainObject';
12
12
  import VirtualList from 'rc-virtual-list';
13
- import { useScrollElement, usePopupContainer, useResponsiveContext, useIsMobile, RESPONSIVE_BOUNDARY_CLASS, RESPONSIVE_CONTAINER_CLASS, RESPONSIVE_SCROLL_CLASS, findResponsiveScroll, getDefaultScrollElement, ResponsiveProvider, defaultResponsiveContextValue, IS_MOBILE_QUERY } from '@kne/responsive-utils';
13
+ import { useScrollElement, usePopupContainer, useResponsiveContext, useIsMobile, RESPONSIVE_BOUNDARY_CLASS, RESPONSIVE_CONTAINER_CLASS, RESPONSIVE_SCROLL_CLASS, findScrollParent, findResponsiveScroll, getDefaultScrollElement, ResponsiveProvider, IS_MOBILE_QUERY } from '@kne/responsive-utils';
14
14
  export { MOBILE_BREAKPOINT, RESPONSIVE_BOUNDARY_CLASS, RESPONSIVE_CONTAINER_CLASS, RESPONSIVE_SCROLL_CLASS, ResponsiveProvider, MOBILE_BREAKPOINT as mobileBreakpoint, useBreakpoint, useIsMobile, useMediaQuery, usePopupContainer, useResponsiveContext, useScrollElement } from '@kne/responsive-utils';
15
15
  import defaultLogo from './defaultLogo~beHinxEU.svg';
16
16
  import { ReactComponent } from './expand~bdCTnjYJ.svg';
@@ -535,19 +535,48 @@ const ErrorBoundary = props => {
535
535
  }));
536
536
  };
537
537
 
538
- const hasParentResponsiveProvider = parent => {
539
- return parent.getBoundaryElement !== defaultResponsiveContextValue.getBoundaryElement || parent.getScrollElement !== defaultResponsiveContextValue.getScrollElement;
538
+ const normalizeMarkedScrollElement = marked => {
539
+ if (!marked) {
540
+ return null;
541
+ }
542
+ if (typeof marked.getScrollElement === 'function') {
543
+ const fromApi = marked.getScrollElement();
544
+ if (fromApi) {
545
+ return fromApi;
546
+ }
547
+ }
548
+ if (marked.nodeType === 1) {
549
+ const wrapper = marked.querySelector('.simplebar-content-wrapper');
550
+ if (wrapper) {
551
+ return wrapper;
552
+ }
553
+ }
554
+ return marked;
555
+ };
556
+ const resolveDocumentScrollElement = () => {
557
+ if (typeof document === 'undefined') {
558
+ return null;
559
+ }
560
+ // 真实移动端滚 document,不要走 getDefaultScrollElement:
561
+ // 它会 document.querySelector(.kne-responsive-scroll),容易绑到宿主页无关节点。
562
+ return document.scrollingElement || document.documentElement || document.body || null;
540
563
  };
541
564
  const resolvePageScrollElement = (pageScrollRef, openScrollbar, deviceIsMobile) => {
542
565
  if (deviceIsMobile && !openScrollbar) {
543
566
  const _anchor = pageScrollRef.current;
544
- if (_anchor && _anchor.parentElement) {
545
- const parentScroll = findResponsiveScroll(_anchor.parentElement);
546
- if (parentScroll) {
547
- return parentScroll;
567
+ if (_anchor) {
568
+ // 优先真实可滚祖先(example 手机外框 SimpleBar content-wrapper)
569
+ const scrollParent = findScrollParent(_anchor);
570
+ if (scrollParent) {
571
+ return scrollParent;
572
+ }
573
+ // class 标记的外层可能不是滚动节点,需归一到 content-wrapper
574
+ const marked = normalizeMarkedScrollElement(findResponsiveScroll(_anchor.parentElement || _anchor));
575
+ if (marked) {
576
+ return marked;
548
577
  }
549
578
  }
550
- return getDefaultScrollElement();
579
+ return resolveDocumentScrollElement();
551
580
  }
552
581
  const anchor = pageScrollRef.current;
553
582
  if (anchor && typeof anchor.getScrollElement === 'function') {
@@ -561,6 +590,10 @@ const resolvePageScrollElement = (pageScrollRef, openScrollbar, deviceIsMobile)
561
590
  }
562
591
  return findResponsiveScroll(anchor) || getDefaultScrollElement();
563
592
  };
593
+
594
+ // 始终覆盖本页 scroller(否则嵌套在 Global 下 sticky 会绑到 body)。
595
+ // container 模式(example 手机外框)保留外层 boundary,toolbar/menu 才能 portal 到
596
+ // .example-driver-device-scroll(非滚动内容区),absolute/fixed 相对手机视口生效。
564
597
  const LayoutResponsiveScope = ({
565
598
  boundaryRef,
566
599
  scrollRef,
@@ -568,13 +601,17 @@ const LayoutResponsiveScope = ({
568
601
  children
569
602
  }) => {
570
603
  const parent = useResponsiveContext();
571
- if (hasParentResponsiveProvider(parent)) {
572
- return children;
573
- }
604
+ const inheritBoundary = parent.mode === 'container';
574
605
  return /*#__PURE__*/jsx(ResponsiveProvider, {
606
+ mode: parent.mode,
607
+ containerWidth: parent.containerWidth,
575
608
  boundaryRef: boundaryRef,
576
609
  scrollRef: scrollRef,
577
610
  getScrollElement: getScrollElement,
611
+ getBoundaryElement: inheritBoundary ? () => {
612
+ const fromParent = typeof parent.getBoundaryElement === 'function' ? parent.getBoundaryElement() : null;
613
+ return fromParent || null;
614
+ } : undefined,
578
615
  children: children
579
616
  });
580
617
  };
@@ -957,7 +994,7 @@ const Layout = ({
957
994
  });
958
995
  };
959
996
 
960
- var style = {"page":"kne-system-layout_c1kXt","page-header":"kne-system-layout_h-YXH","is-scrolled":"kne-system-layout_dEXMy","page-title-outer":"kne-system-layout_jhcGo","capsule-click":"kne-system-layout_Ps1FA","page-title-extra":"kne-system-layout_mKsLu","page-title":"kne-system-layout_IOVGr","page-top":"kne-system-layout_Y1y7o","page-inner":"kne-system-layout_wHWje","no-padding":"kne-system-layout_tBnAD","is-mobile":"kne-system-layout_yRZMx","page-loading":"kne-system-layout_vcf3J","page-bottom":"kne-system-layout_vXk1s","back-icon":"kne-system-layout_07Mna"};
997
+ var style = {"page":"kne-system-layout_c1kXt","page-header":"kne-system-layout_h-YXH","is-viewport":"kne-system-layout_XjOj5","is-boundary":"kne-system-layout_Q-5NT","page-title-outer":"kne-system-layout_jhcGo","page-title":"kne-system-layout_IOVGr","page-title-extra":"kne-system-layout_mKsLu","page-title-actions":"kne-system-layout_XTAI-","is-scrolled":"kne-system-layout_dEXMy","capsule-click":"kne-system-layout_Ps1FA","back-icon":"kne-system-layout_07Mna","has-float":"kne-system-layout_SD6-j","page-top":"kne-system-layout_Y1y7o","page-inner":"kne-system-layout_wHWje","no-padding":"kne-system-layout_tBnAD","is-mobile":"kne-system-layout_yRZMx","page-header-boundary-overlay":"kne-system-layout_4pjtx","page-loading":"kne-system-layout_vcf3J","page-bottom":"kne-system-layout_vXk1s"};
961
998
 
962
999
  const PageLoading = () => {
963
1000
  return /*#__PURE__*/jsx(Flex, {
@@ -991,8 +1028,15 @@ const Page = ({
991
1028
  } = useContext();
992
1029
  const navigate = useNavigate();
993
1030
  const getScrollElement = useScrollElement();
1031
+ const getBoundaryElement = usePopupContainer();
1032
+ const {
1033
+ mode
1034
+ } = useResponsiveContext();
994
1035
  const navbarRef = useRef(null);
995
1036
  const [isScrolled, setIsScrolled] = useState(false);
1037
+ const useBoundaryMount = deviceIsMobile && mode === 'container';
1038
+ const useViewportFixed = deviceIsMobile && mode !== 'container';
1039
+ const boundaryTarget = useBoundaryMount ? getBoundaryElement() : null;
996
1040
  useEffect(() => {
997
1041
  setToolbarShow && setToolbarShow(!!_toolbar);
998
1042
  setNavbarShow && setNavbarShow(!!_navbar);
@@ -1001,35 +1045,120 @@ const Page = ({
1001
1045
  if (!deviceIsMobile || !_navbar) {
1002
1046
  return;
1003
1047
  }
1004
- const scrollEl = getScrollElement();
1005
- if (!scrollEl || typeof scrollEl.addEventListener !== 'function') {
1006
- return;
1007
- }
1008
- const handleScroll = () => {
1009
- const threshold = navbarRef.current ? navbarRef.current.offsetHeight : 48;
1010
- setIsScrolled(scrollEl.scrollTop > threshold);
1048
+ let boundKey = null;
1049
+ let remove = null;
1050
+ const bind = () => {
1051
+ const scrollEl = getScrollElement();
1052
+ if (!scrollEl) {
1053
+ return;
1054
+ }
1055
+ const isDocumentScroll = typeof document !== 'undefined' && (scrollEl === document.scrollingElement || scrollEl === document.documentElement || scrollEl === document.body);
1056
+ // document 滚动在部分环境听 element 不稳定,统一挂 window
1057
+ const listenTarget = isDocumentScroll ? window : scrollEl;
1058
+ if (!listenTarget || typeof listenTarget.addEventListener !== 'function') {
1059
+ return;
1060
+ }
1061
+ const nextKey = isDocumentScroll ? 'window' : scrollEl;
1062
+ if (nextKey === boundKey) {
1063
+ return;
1064
+ }
1065
+ if (typeof remove === 'function') {
1066
+ remove();
1067
+ }
1068
+ boundKey = nextKey;
1069
+ const readScrollTop = () => {
1070
+ if (isDocumentScroll) {
1071
+ var _ref, _document$scrollingEl, _document$scrollingEl2;
1072
+ return (_ref = (_document$scrollingEl = (_document$scrollingEl2 = document.scrollingElement) == null ? void 0 : _document$scrollingEl2.scrollTop) != null ? _document$scrollingEl : window.scrollY) != null ? _ref : 0;
1073
+ }
1074
+ return scrollEl.scrollTop || 0;
1075
+ };
1076
+ const handleScroll = () => {
1077
+ const threshold = navbarRef.current ? navbarRef.current.offsetHeight : 48;
1078
+ setIsScrolled(readScrollTop() > threshold);
1079
+ };
1080
+ handleScroll();
1081
+ listenTarget.addEventListener('scroll', handleScroll, {
1082
+ passive: true
1083
+ });
1084
+ remove = () => listenTarget.removeEventListener('scroll', handleScroll);
1085
+ };
1086
+ bind();
1087
+ // SimpleBar / kne-responsive-scroll 可能在下一帧才就绪,补一次绑定
1088
+ const raf = requestAnimationFrame(bind);
1089
+ return () => {
1090
+ cancelAnimationFrame(raf);
1091
+ if (typeof remove === 'function') {
1092
+ remove();
1093
+ }
1011
1094
  };
1012
- handleScroll();
1013
- scrollEl.addEventListener('scroll', handleScroll, {
1014
- passive: true
1015
- });
1016
- return () => scrollEl.removeEventListener('scroll', handleScroll);
1017
1095
  }, [deviceIsMobile, _navbar, getScrollElement, scrollReady]);
1018
- const navbarEl = _navbar ? /*#__PURE__*/jsxs(Flex, {
1096
+ const titleExtraContent = (() => {
1097
+ if (_extra) {
1098
+ if (deviceIsMobile && userAvatar) {
1099
+ return /*#__PURE__*/jsxs(Flex, {
1100
+ gap: 4,
1101
+ align: "center",
1102
+ children: [_extra, /*#__PURE__*/jsx(Image.Avatar, {
1103
+ id: userAvatar,
1104
+ src: userAvatar,
1105
+ shape: "circle",
1106
+ size: 30,
1107
+ alt: "avatar"
1108
+ })]
1109
+ });
1110
+ }
1111
+ return _extra;
1112
+ }
1113
+ if (deviceIsMobile && (buttonProps || userAvatar)) {
1114
+ const actions = buttonProps ? /*#__PURE__*/jsx("div", {
1115
+ className: classnames('page-title-actions', style['page-title-actions']),
1116
+ children: /*#__PURE__*/jsx(ButtonGroup, _extends({}, Object.assign({}, buttonProps, {
1117
+ showLength: 0,
1118
+ moreType: 'link'
1119
+ })))
1120
+ }) : null;
1121
+ const avatar = userAvatar ? /*#__PURE__*/jsx(Image.Avatar, {
1122
+ id: userAvatar,
1123
+ src: userAvatar,
1124
+ shape: "circle",
1125
+ size: 30,
1126
+ alt: "avatar"
1127
+ }) : null;
1128
+ // 仅更多按钮时不要套带 gap 的 Flex,否则 ButtonGroup 测宽空节点也会吃 gap,导致不居中
1129
+ if (actions && avatar) {
1130
+ return /*#__PURE__*/jsxs(Flex, {
1131
+ gap: 4,
1132
+ align: "center",
1133
+ children: [actions, avatar]
1134
+ });
1135
+ }
1136
+ return actions || avatar;
1137
+ }
1138
+ if (buttonProps) {
1139
+ return /*#__PURE__*/jsx(ButtonGroup, _extends({}, buttonProps));
1140
+ }
1141
+ return null;
1142
+ })();
1143
+ const navbarInner = _navbar ? /*#__PURE__*/jsxs(Flex, {
1019
1144
  ref: navbarRef,
1020
1145
  justify: "space-between",
1021
1146
  align: "center",
1022
1147
  gap: 20,
1023
1148
  className: classnames('page-header', style['page-header'], {
1149
+ ['is-mobile']: deviceIsMobile,
1150
+ [style['is-mobile']]: deviceIsMobile,
1024
1151
  ['is-scrolled']: isScrolled,
1025
- [style['is-scrolled']]: isScrolled
1152
+ [style['is-scrolled']]: isScrolled,
1153
+ [style['is-boundary']]: useBoundaryMount && !!boundaryTarget,
1154
+ [style['is-viewport']]: useViewportFixed || useBoundaryMount && !boundaryTarget
1026
1155
  }),
1027
1156
  children: [/*#__PURE__*/jsxs(Flex, {
1028
1157
  className: classnames('page-title-outer', style['page-title-outer']),
1029
1158
  align: "center",
1030
1159
  gap: 4,
1031
1160
  children: [back && /*#__PURE__*/jsx(Button, {
1032
- type: "text",
1161
+ type: "link",
1033
1162
  icon: /*#__PURE__*/jsx(Icon, {
1034
1163
  className: style['back-icon'],
1035
1164
  type: "a-Typearrow_forward_ios",
@@ -1052,24 +1181,19 @@ const Page = ({
1052
1181
  className: classnames('page-title', style['page-title']),
1053
1182
  children: title
1054
1183
  })]
1055
- }), /*#__PURE__*/jsx("div", {
1056
- className: classnames('page-title-extra', style['page-title-extra']),
1057
- children: _extra ? _extra : deviceIsMobile ? /*#__PURE__*/jsxs(Flex, {
1058
- gap: 4,
1059
- align: "center",
1060
- children: [buttonProps && /*#__PURE__*/jsx(ButtonGroup, _extends({}, Object.assign({}, buttonProps, deviceIsMobile ? {
1061
- showLength: 0,
1062
- moreType: 'link'
1063
- } : {}))), /*#__PURE__*/jsx(Image.Avatar, {
1064
- id: userAvatar,
1065
- src: userAvatar,
1066
- shape: "circle",
1067
- size: 30,
1068
- alt: "avatar"
1069
- })]
1070
- }) : buttonProps && /*#__PURE__*/jsx(ButtonGroup, _extends({}, buttonProps))
1071
- })]
1184
+ }), titleExtraContent ? /*#__PURE__*/jsx("div", {
1185
+ className: classnames('page-title-extra', style['page-title-extra'], style['has-float']),
1186
+ children: titleExtraContent
1187
+ }) : null]
1072
1188
  }) : null;
1189
+ const navbarEl = navbarInner && useBoundaryMount && boundaryTarget ? /*#__PURE__*/createPortal(/*#__PURE__*/jsx("div", {
1190
+ className: classnames('page-header-boundary-overlay', style['page-header-boundary-overlay']),
1191
+ style: {
1192
+ '--safe-area-inset-top': 'env(safe-area-inset-top)',
1193
+ '--safe-area-inset-bottom': 'env(safe-area-inset-bottom)'
1194
+ },
1195
+ children: navbarInner
1196
+ }), boundaryTarget) : navbarInner;
1073
1197
  const pageInnerClassNameOrigin = classnames('page-inner', style['page-inner']);
1074
1198
  const pageInnerClassName = classnames(pageInnerClassNameOrigin, {
1075
1199
  ['no-padding']: noPadding,
@@ -1127,7 +1251,7 @@ const isMobile = () => {
1127
1251
  const transparentBgContainer = {
1128
1252
  colorBgContainer: 'transparent'
1129
1253
  };
1130
- const transparentBgContainerComponents = ['Input', 'InputNumber', 'Card', 'Tree', 'Select', 'DatePicker'];
1254
+ const transparentBgContainerComponents = ['Input', 'InputNumber', 'Card', 'Tree', 'Select', 'DatePicker', 'Tabs'];
1131
1255
  const themeToken = {
1132
1256
  components: merge({}, transform(transparentBgContainerComponents, (result, component) => {
1133
1257
  result[component] = transparentBgContainer;