lupine.components 1.1.46 → 1.1.47

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lupine.components",
3
- "version": "1.1.46",
3
+ "version": "1.1.47",
4
4
  "license": "MIT",
5
5
  "author": "uuware.com",
6
6
  "homepage": "https://github.com/uuware/lupine.js",
@@ -59,6 +59,7 @@ export class MobileSideMenuHelper {
59
59
  let touchstartX = 0;
60
60
  let direction = '';
61
61
  let moveStart = false;
62
+ let pendingOpen = false;
62
63
  let isOpen = false;
63
64
  let menuWidth = 0;
64
65
 
@@ -71,6 +72,7 @@ export class MobileSideMenuHelper {
71
72
  touchstartX = e.touches[0].clientX;
72
73
  direction = '';
73
74
  moveStart = false;
75
+ pendingOpen = false;
74
76
  isOpen = maskDom.classList.contains('show');
75
77
 
76
78
  const menuDom = maskDom.querySelector('.mobile-side-menu') as HTMLDivElement;
@@ -87,35 +89,53 @@ export class MobileSideMenuHelper {
87
89
  }
88
90
  } else {
89
91
  if (touchstartX < 40) {
90
- moveStart = true;
91
- // Pre-show the mask to allow rendering the menu while dragging
92
- maskDom.classList.add('show');
93
- if (menuDom) {
94
- menuDom.style.transition = 'none'; // Disable transition for 1:1 finger tracking
95
- menuDom.style.transform = `translateX(-100%)`; // Start fully hidden left
96
- }
92
+ // Do not start the drawer immediately on touchstart. A tap on a left-edge
93
+ // header button should still receive its normal click/touchend event.
94
+ // The drawer gesture is confirmed later in touchmove after a horizontal drag.
95
+ pendingOpen = true;
97
96
  }
98
97
  }
99
98
  });
100
99
 
101
100
  document.addEventListener('touchmove', (e) => {
102
- if (!moveStart) {
103
- return;
104
- }
105
-
106
101
  const maskDom = document.querySelector('.mobile-side-menu-mask') as HTMLDivElement;
107
102
  if (!maskDom) return;
108
103
 
109
104
  const currentX = e.touches[0].clientX;
105
+ const currentY = e.touches[0].clientY;
110
106
  const deltaX = currentX - touchstartX;
107
+ const deltaY = currentY - touchstartY;
111
108
 
112
- if (direction === '') {
113
- if (Math.abs(deltaX) > 0) {
114
- direction = 'X';
115
- } else {
116
- moveStart = false;
109
+ if (!moveStart) {
110
+ if (!pendingOpen) {
111
+ return;
112
+ }
113
+
114
+ // Confirm the side-menu gesture only after the finger clearly drags
115
+ // right horizontally. Simple taps on left-edge buttons will fall through.
116
+ if (Math.abs(deltaX) < 8 && Math.abs(deltaY) < 8) {
117
117
  return;
118
118
  }
119
+ if (deltaX <= 0 || Math.abs(deltaX) <= Math.abs(deltaY)) {
120
+ pendingOpen = false;
121
+ return;
122
+ }
123
+
124
+ moveStart = true;
125
+ pendingOpen = false;
126
+ maskDom.classList.add('show');
127
+
128
+ const menuDom = maskDom.querySelector('.mobile-side-menu') as HTMLDivElement;
129
+ if (menuDom) {
130
+ menuDom.style.transition = 'none'; // Disable transition for 1:1 finger tracking
131
+ menuDom.style.transform = `translateX(-100%)`; // Start fully hidden left
132
+ }
133
+ }
134
+
135
+ e.preventDefault();
136
+
137
+ if (direction === '') {
138
+ direction = 'X';
119
139
  }
120
140
 
121
141
  const menuDom = maskDom.querySelector('.mobile-side-menu') as HTMLDivElement;
@@ -144,7 +164,7 @@ export class MobileSideMenuHelper {
144
164
  }
145
165
  }
146
166
  }
147
- });
167
+ }, { passive: false });
148
168
 
149
169
  document.addEventListener('touchend', (e) => {
150
170
  if (!moveStart) return;
@@ -29,7 +29,7 @@ import { toggleButtonDemo } from '../components/toggle-button-demo';
29
29
  import { messageBoxDemo } from '../components/message-box-demo';
30
30
  import { loadingSpinDemo } from '../components/loading-spin-demo';
31
31
  import { mobileSideMenuDemo } from '../components/mobile-components/mobile-side-menu-demo';
32
- import { sliderFrameDemo } from '../components/slider-frame-demo';
32
+ import { sliderFrameDemo } from '../frames/slider-frame-demo';
33
33
  import { rangeDemo, gaugeDemo } from '../component-pool/range';
34
34
  import { badgeDemo } from '../component-pool/badge';
35
35
  import { timelineDemo } from '../component-pool/timeline';
@@ -27,7 +27,7 @@ import { messageBoxDemo } from '../components/message-box-demo';
27
27
  import { loadingSpinDemo } from '../components/loading-spin-demo';
28
28
  import { mobileSideMenuDemo } from '../components/mobile-components/mobile-side-menu-demo';
29
29
  import { responsiveFrameDemo } from '../frames/responsive-frame-demo';
30
- import { sliderFrameDemo } from '../components/slider-frame-demo';
30
+ import { sliderFrameDemo } from '../frames/slider-frame-demo';
31
31
  import { carouselDemo } from '../component-pool/carousel';
32
32
  import { rangeDemo, gaugeDemo } from '../component-pool/range';
33
33
  import { cascaderDemo } from '../component-pool/cascader';
@@ -1,4 +1,4 @@
1
- import { bindGlobalStyle, CssProps, encodeHtml, HtmlVar, isFrontEnd, PageProps } from 'lupine.components';
1
+ import { bindGlobalStyle, CssProps, encodeHtml, HtmlVar, isFrontEnd, MediaQueryMaxWidth, PageProps } from 'lupine.components';
2
2
  import { demoRegistry } from './demo-registry';
3
3
  import { demoIconsCss } from './mock/demo-icons';
4
4
 
@@ -65,12 +65,16 @@ export const DemoRenderPage = async (props: PageProps) => {
65
65
  width: '100%',
66
66
  height: '100%',
67
67
  // Reset any body margins if they exist, though typically handled by global css
68
- margin: 0,
68
+ margin: '0 auto',
69
69
  boxSizing: 'border-box',
70
70
  display: 'flex',
71
71
  justifyContent: 'center',
72
72
  alignItems: 'center', // Center components by default
73
73
  overflow: 'auto',
74
+ transform: 'translateX(0)', // Traps position: fixed children inside this container's dimensions
75
+ maxWidth: MediaQueryMaxWidth.DesktopMax,
76
+ borderRight: '1px solid var(--primary-border-color)',
77
+ borderLeft: '1px solid var(--primary-border-color)',
74
78
  '>fragment>div': {
75
79
  width: '100%',
76
80
  height: '100%',
@@ -80,7 +84,8 @@ export const DemoRenderPage = async (props: PageProps) => {
80
84
  bindGlobalStyle('demo-icons', demoIconsCss, false, true);
81
85
 
82
86
  return (
83
- <div css={css} class='demo-render-page'>
87
+ // responsive-frame is for containning slider-frame
88
+ <div css={css} class='demo-render-page responsive-frame'>
84
89
  {dom.node}
85
90
  </div>
86
91
  );
@@ -39,6 +39,7 @@ export const demoIcons = {
39
39
  'ma-pencil-outline': `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z'/%3E%3C/svg%3E`,
40
40
  'ic-play': `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' width='24' height='24'%3E%3Cpath fill='currentColor' d='M20 0 L20 100 L90 50 Z'/%3E%3C/svg%3E`,
41
41
  'ic-pause': `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' width='24' height='24'%3E%3Cpath fill='currentColor' d='M0 0 L0 100 L33.33 100 L33.33 0 Z M66.66 0 L66.66 100 L100 100 L100 0 Z'/%3E%3C/svg%3E`,
42
+ 'ma-cog-outline': `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'%3E%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z'/%3E%3Cpath fill='none' stroke='black' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1Z'/%3E%3C/svg%3E`,
42
43
  };
43
44
 
44
45
  export const demoIconsCss: CssProps = {
@@ -17,11 +17,7 @@ const sideMenuMockCss: CssProps = {
17
17
  borderBottom: '1px solid var(--primary-border-color)',
18
18
  },
19
19
  '.msm-header-icon': {
20
- width: '32px',
21
- height: '32px',
22
20
  marginRight: '12px',
23
- borderRadius: '4px',
24
- backgroundColor: 'var(--primary-color)',
25
21
  },
26
22
  '.msm-header-title': {
27
23
  fontSize: '18px',
@@ -90,7 +86,7 @@ export const SideMenuMock = ({
90
86
  return (
91
87
  <div ref={ref} style={{ display: 'flex', flexDirection: 'column', flex: 1, height: '100%' }}>
92
88
  <div class='msm-header'>
93
- <div class='msm-header-icon'></div>
89
+ <div class='msm-header-icon'><i class='ifc-icon ma-home-outline'></i></div>
94
90
  <div class='msm-header-title'>{title}</div>
95
91
  </div>
96
92
 
@@ -153,7 +153,6 @@ const NestedDemoMockContent = () => {
153
153
  };
154
154
 
155
155
  export const NestedDemoMock = (props: { sliderFrameHook: SliderFrameHookProps }) => {
156
- props.sliderFrameHook.addClass!('desktop-slide-right');
157
156
 
158
157
  return (
159
158
  <HeaderWithBackFrame
@@ -297,7 +296,6 @@ const UserSettingsMockContent = () => {
297
296
  };
298
297
 
299
298
  export const UserSettingsMock = (props: { sliderFrameHook: SliderFrameHookProps }) => {
300
- props.sliderFrameHook.addClass!('desktop-slide-left');
301
299
 
302
300
  return (
303
301
  <HeaderWithBackFrame
@@ -1,3 +1,4 @@
1
1
  export * from './responsive-frame';
2
2
  export * from './slider-frame';
3
+ export * from './slider-helper';
3
4
  export * from './top-frame';
@@ -1,7 +1,8 @@
1
1
  import { DemoStory } from '../demo/demo-types';
2
- import { SliderFrame, SliderFrameHookProps } from '../frames/slider-frame';
3
- import { HeaderWithBackFrame } from './mobile-components/mobile-header-with-back';
4
- import { Button, ButtonSize } from './button';
2
+ import { SliderFrame, SliderFrameHookProps } from './slider-frame';
3
+ import { SliderHelper, SliderHelperCloseProps } from './slider-helper';
4
+ import { HeaderWithBackFrame } from '../components/mobile-components/mobile-header-with-back';
5
+ import { Button, ButtonSize } from '../components/button';
5
6
 
6
7
  // A dummy component to load inside the slider
7
8
  const DummyInnerPage = ({ hook }: { hook: SliderFrameHookProps }) => {
@@ -38,13 +39,36 @@ export const sliderFrameDemo: DemoStory<any> = {
38
39
  <p style={{ color: '#666', marginBottom: '20px' }}>
39
40
  This demo shows how to use SliderFrame to slide in a new full-cover page over the current view.
40
41
  </p>
41
- <Button
42
- text='Open Slider'
43
- size={ButtonSize.Medium}
44
- onClick={() => {
45
- sliderHook.load!(<DummyInnerPage hook={sliderHook} />);
46
- }}
47
- />
42
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: '10px' }}>
43
+ <Button
44
+ text='Open Slider'
45
+ size={ButtonSize.Medium}
46
+ onClick={() => {
47
+ sliderHook.load!(<DummyInnerPage hook={sliderHook} />);
48
+ }}
49
+ />
50
+ <Button
51
+ text='Open Slider Helper'
52
+ size={ButtonSize.Medium}
53
+ onClick={async () => {
54
+ let close: SliderHelperCloseProps;
55
+ close = await SliderHelper.show({
56
+ direction: args.direction,
57
+ children: (
58
+ <HeaderWithBackFrame title='Slider Helper Inner Page' onBack={() => close()}>
59
+ <div style={{ padding: '20px', display: 'flex', flexDirection: 'column', gap: '20px' }}>
60
+ <h3>This is the SliderHelper inner page content!</h3>
61
+ <p>It uses a static show function and shows a mask on larger screens.</p>
62
+ <div>
63
+ <Button text='Close SliderHelper via Button' size={ButtonSize.Medium} onClick={() => close()} />
64
+ </div>
65
+ </div>
66
+ </HeaderWithBackFrame>
67
+ ),
68
+ });
69
+ }}
70
+ />
71
+ </div>
48
72
 
49
73
  {/* The SliderFrame is placed here but initially hidden */}
50
74
  <SliderFrame hook={sliderHook} direction={args.direction} defaultContent='' />
@@ -12,7 +12,8 @@
12
12
  </div>
13
13
  );
14
14
  */
15
- import { VNode, CssProps, HtmlVar, RefProps, stopPropagation, MediaQueryRange, isFrontEnd } from 'lupine.components';
15
+ import { VNode, stopPropagation } from 'lupine.components';
16
+ import { SliderHelper, SliderHelperCloseProps } from './slider-helper';
16
17
 
17
18
  // addClass(SliderFramePosition) is used to show two SliderFrames for big screens,
18
19
  // so when the second is showing, it needs to set this on the first one
@@ -30,107 +31,39 @@ export type SliderFrameProps = {
30
31
  hook?: SliderFrameHookProps;
31
32
  afterClose?: () => void | Promise<void>;
32
33
  };
34
+ // deprecated
33
35
  export const SliderFrame = (props: SliderFrameProps) => {
36
+ let closeSlider: SliderHelperCloseProps | undefined;
37
+ let opened = false;
38
+ let className = '';
39
+
34
40
  if (props.hook) {
35
41
  props.hook.load = (children) => {
36
- dom.value = children;
37
- ref.current?.classList.remove('d-none');
38
- setTimeout(() => {
39
- ref.current?.classList.add('show');
40
- }, 100);
42
+ opened = true;
43
+ SliderHelper.show({
44
+ direction: props.direction || 'right',
45
+ children,
46
+ className,
47
+ closeEvent: () => {
48
+ opened = false;
49
+ closeSlider = undefined;
50
+ props.afterClose?.();
51
+ },
52
+ }).then((close) => {
53
+ closeSlider = close;
54
+ });
41
55
  };
42
56
  props.hook.close = (event: Event) => {
43
57
  stopPropagation(event);
44
- ref.current?.classList.remove('show');
45
- setTimeout(async () => {
46
- ref.current?.classList.add('d-none');
47
- dom.value = '';
48
- if (props.afterClose) {
49
- await props.afterClose();
50
- }
51
- }, 400);
58
+ closeSlider?.();
52
59
  };
53
- props.hook.addClass = (className) => {
54
- ref.current?.classList.add(className);
60
+ // deprecated
61
+ props.hook.addClass = (newClassName) => {
62
+ className = [className, newClassName].join(' ').trim();
55
63
  };
56
64
  props.hook.isOpened = () => {
57
- return ref.current?.classList.contains('show');
65
+ return opened;
58
66
  };
59
67
  }
60
- const dom = new HtmlVar(<div class='slider-frame-default'>{props.defaultContent || '(No Content)'}</div>);
61
- const ref: RefProps = {
62
- onLoad: async (el: Element) => {
63
- // Keep fixed sliders out of padded/top-frame layout containers on iOS.
64
- // iOS WebView can treat fixed children inside safe-area padded app frames inconsistently;
65
- // mounting the slider at body level also makes z-index compare globally.
66
- if (isFrontEnd()) {
67
- const root = (window.parent as any).document.querySelector('.lupine-root') as HTMLElement;
68
- if (root && el.parentElement !== root) {
69
- root.appendChild(el);
70
- }
71
- }
72
- },
73
- };
74
- const css: CssProps = {
75
- display: 'flex',
76
- flexDirection: 'column',
77
- position: 'fixed',
78
- top: '0',
79
- left: 'var(--auto-sidemenu-left-offset, 0px)',
80
- right: '0',
81
- bottom: '0',
82
- zIndex: 'var(--layer-slider)',
83
- transform: props.direction === 'bottom' ? 'translateY(100%)' : 'translateX(100%)',
84
- transition: 'transform 0.4s ease-in-out',
85
- backgroundColor: 'var(--primary-bg-color)',
86
- // trick: to put two padding-top properties
87
- 'padding-top ': 'constant(safe-area-inset-top)',
88
- 'padding-top': 'env(safe-area-inset-top)',
89
- '&.show': {
90
- transform: props.direction === 'bottom' ? 'translateY(0)' : 'translateX(0)',
91
- },
92
- '& > *': {
93
- '--auto-sidemenu-left-offset': '0px',
94
- },
95
- '& > fragment': {
96
- height: '100%',
97
- '--auto-sidemenu-left-offset': '0px',
98
- },
99
- '&.desktop-slide-left': {
100
- [MediaQueryRange.TabletAbove]: {
101
- '.header-back-content': {
102
- width: '30%',
103
- },
104
- },
105
- },
106
- '&.desktop-slide-right': {
107
- [MediaQueryRange.TabletAbove]: {
108
- top: '59px', // Just below DesktopHeader
109
- left: 'calc(max(var(--auto-sidemenu-left-offset, 0px), 30%))',
110
- transform: 'translateX(0)',
111
- // notice: here is connected with mobile-header-title-icon.tsx
112
- '.mobile-header-title-icon-top': {
113
- width: '100%',
114
- boxShadow: 'unset',
115
- },
116
- '.header-back-content': {
117
- width: '100%',
118
- },
119
- '.mhti-title': {
120
- fontSize: '15px',
121
- },
122
- '.mhti-left, .mhti-right': {
123
- display: 'none',
124
- },
125
- '&.d-none': {
126
- display: 'unset !important',
127
- },
128
- },
129
- },
130
- };
131
- return (
132
- <div ref={ref} css={css} class='slider-frame d-none'>
133
- {dom.node}
134
- </div>
135
- );
68
+ return <></>;
136
69
  };
@@ -0,0 +1,192 @@
1
+ import { CssProps, RefProps, VNode, mountInnerComponent } from 'lupine.web';
2
+ import { backActionHelper, stopPropagation } from '../lib';
3
+ import { MediaQueryMaxWidth, MediaQueryRange } from '../styles';
4
+
5
+ export type SliderHelperDirection = 'right' | 'left' | 'bottom' | 'top';
6
+ export type SliderHelperCloseProps = () => void;
7
+
8
+ export type SliderHelperShowProps = {
9
+ children: VNode<any>;
10
+ direction?: SliderHelperDirection;
11
+ closeEvent?: () => void;
12
+ closeWhenClickOutside?: boolean;
13
+ zIndex?: string;
14
+ maxWidth?: string;
15
+ className?: string;
16
+ contentClassName?: string;
17
+ mountTarget?: HTMLElement;
18
+ };
19
+
20
+ export class SliderHelper {
21
+ static async show({
22
+ children,
23
+ direction = 'right',
24
+ closeEvent,
25
+ closeWhenClickOutside = true,
26
+ zIndex,
27
+ maxWidth = MediaQueryMaxWidth.MobileMax,
28
+ className = '',
29
+ contentClassName = '',
30
+ mountTarget,
31
+ }: SliderHelperShowProps): Promise<SliderHelperCloseProps> {
32
+ const base = document.createElement('div');
33
+ const isHorizontal = direction === 'left' || direction === 'right';
34
+ const isFromEnd = direction === 'right' || direction === 'bottom';
35
+ const closeClassName = isFromEnd ? 'close-to-end' : 'close-to-start';
36
+
37
+ let closed = false;
38
+ const handleClose = () => {
39
+ if (closed) return;
40
+ closed = true;
41
+ closeEvent?.();
42
+ ref.current?.classList.remove('show');
43
+ ref.current?.classList.add(closeClassName);
44
+ setTimeout(() => {
45
+ base.remove();
46
+ }, 400);
47
+ };
48
+
49
+ const onClickContainer = (event: MouseEvent) => {
50
+ if (closeWhenClickOutside !== false && event.target === ref.current) {
51
+ handleClose();
52
+ }
53
+ };
54
+
55
+ const onClickContent = (event: MouseEvent) => {
56
+ stopPropagation(event);
57
+ };
58
+
59
+ const ref: RefProps = {
60
+ onLoad: async () => {
61
+ // Make sure the browser has painted the initial transform state before adding show,
62
+ // otherwise rapid close/open can skip the transition and display directly.
63
+ ref.current?.classList.remove('show', 'close-to-end', 'close-to-start');
64
+ ref.current?.getBoundingClientRect();
65
+ requestAnimationFrame(() => {
66
+ requestAnimationFrame(() => {
67
+ ref.current?.classList.add('show');
68
+ });
69
+ });
70
+ },
71
+ };
72
+
73
+ const startTransform = isHorizontal
74
+ ? direction === 'right'
75
+ ? 'translateX(100%)'
76
+ : 'translateX(-100%)'
77
+ : direction === 'bottom'
78
+ ? 'translateY(100%)'
79
+ : 'translateY(-100%)';
80
+ const showTransform = 'translate(0, 0)';
81
+ const endTransform = isHorizontal
82
+ ? direction === 'right'
83
+ ? 'translateX(100%)'
84
+ : 'translateX(-100%)'
85
+ : direction === 'bottom'
86
+ ? 'translateY(100%)'
87
+ : 'translateY(-100%)';
88
+ const oppositeTransform = isHorizontal
89
+ ? direction === 'right'
90
+ ? 'translateX(-100%)'
91
+ : 'translateX(100%)'
92
+ : direction === 'bottom'
93
+ ? 'translateY(-100%)'
94
+ : 'translateY(100%)';
95
+
96
+ const contentCss: CssProps = isHorizontal
97
+ ? {
98
+ top: '0',
99
+ bottom: '0',
100
+ width: '100%',
101
+ maxWidth,
102
+ [direction]: '0',
103
+ }
104
+ : {
105
+ top: direction === 'top' ? '0' : 'auto',
106
+ bottom: direction === 'bottom' ? '0' : 'auto',
107
+ left: 'auto',
108
+ right: '0',
109
+ width: '100%',
110
+ maxWidth,
111
+ height: '100%',
112
+ };
113
+
114
+ const cssContainer: CssProps = {
115
+ display: 'flex',
116
+ position: 'fixed',
117
+ top: '0',
118
+ left: '0',
119
+ right: '0',
120
+ bottom: '0',
121
+ zIndex: zIndex || 'var(--layer-slider)',
122
+ overflow: 'hidden',
123
+ backgroundColor: '#00000000',
124
+ transition: 'background-color 0.4s ease-in-out',
125
+ pointerEvents: 'auto',
126
+ '&.show': {
127
+ [MediaQueryRange.MobileAbove]: {
128
+ backgroundColor: 'var(--cover-mask-bg-color)',
129
+ },
130
+ '.slider-helper-content': {
131
+ transform: showTransform,
132
+ },
133
+ },
134
+ '&.close-to-end': {
135
+ '.slider-helper-content': {
136
+ transform: endTransform,
137
+ },
138
+ },
139
+ '&.close-to-start': {
140
+ '.slider-helper-content': {
141
+ transform: oppositeTransform,
142
+ },
143
+ },
144
+ '.slider-helper-content': {
145
+ display: 'flex',
146
+ flexDirection: 'column',
147
+ position: 'fixed',
148
+ boxSizing: 'border-box',
149
+ overflowX: 'hidden',
150
+ overflowY: 'hidden',
151
+ scrollbarWidth: 'none',
152
+ color: 'var(--primary-color)',
153
+ backgroundColor: 'var(--primary-bg-color)',
154
+ boxShadow: 'var(--cover-box-shadow)',
155
+ transform: startTransform,
156
+ transition: 'transform 0.4s ease-in-out',
157
+ // trick: to put two padding-top properties
158
+ 'padding-top ': 'constant(safe-area-inset-top)',
159
+ 'padding-top': 'env(safe-area-inset-top)',
160
+ ...contentCss,
161
+ '&::-webkit-scrollbar': {
162
+ display: 'none',
163
+ },
164
+ },
165
+ };
166
+
167
+ const component = (
168
+ <div
169
+ css={cssContainer}
170
+ ref={ref}
171
+ class={['slider-helper-box', `from-${direction}`, className].join(' ').trim()}
172
+ onClick={onClickContainer}
173
+ >
174
+ <div
175
+ class={['slider-helper-content', contentClassName].join(' ').trim()}
176
+ onClick={onClickContent}
177
+ data-back-action={backActionHelper.genBackActionId()}
178
+ >
179
+ {children}
180
+ </div>
181
+ </div>
182
+ );
183
+
184
+ base.style.position = 'fixed';
185
+ base.style.inset = '0';
186
+ base.style.zIndex = zIndex || 'var(--layer-slider)';
187
+ const host = mountTarget || document.querySelector('.responsive-frame') || document.querySelector('.lupine-root') || document.body;
188
+ host.appendChild(base);
189
+ await mountInnerComponent(base, component);
190
+ return handleClose;
191
+ }
192
+ }