@startlibs/components 1.0.3 → 1.1.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/lib/BasicBulletList.mjs +9 -0
- package/lib/BasicBulletList.mjs.map +1 -0
- package/lib/Button.mjs +95 -0
- package/lib/Button.mjs.map +1 -0
- package/lib/ContextMenu.mjs +452 -0
- package/lib/ContextMenu.mjs.map +1 -0
- package/lib/Dialog.mjs +232 -0
- package/lib/Dialog.mjs.map +1 -0
- package/lib/Draggable.mjs +880 -0
- package/lib/Draggable.mjs.map +1 -0
- package/lib/ErrorBoundary.mjs +108 -0
- package/lib/ErrorBoundary.mjs.map +1 -0
- package/lib/FillLastLineResizer.mjs +238 -0
- package/lib/FillLastLineResizer.mjs.map +1 -0
- package/lib/Flip.mjs +604 -0
- package/lib/Flip.mjs.map +1 -0
- package/lib/FlipList.mjs +186 -0
- package/lib/FlipList.mjs.map +1 -0
- package/lib/FormatDate.mjs +25 -0
- package/lib/FormatDate.mjs.map +1 -0
- package/lib/Icon.mjs +50 -0
- package/lib/Icon.mjs.map +1 -0
- package/lib/Image.mjs +41 -0
- package/lib/Image.mjs.map +1 -0
- package/lib/Link.mjs +116 -0
- package/lib/Link.mjs.map +1 -0
- package/lib/Loading.mjs +10 -0
- package/lib/Loading.mjs.map +1 -0
- package/lib/LoadingDiv.mjs +83 -0
- package/lib/LoadingDiv.mjs.map +1 -0
- package/lib/Notifications.mjs +63 -0
- package/lib/Notifications.mjs.map +1 -0
- package/lib/PageLoader.mjs +42 -0
- package/lib/PageLoader.mjs.map +1 -0
- package/lib/PersistentNotifications.mjs +130 -0
- package/lib/PersistentNotifications.mjs.map +1 -0
- package/lib/Popup.mjs +49 -0
- package/lib/Popup.mjs.map +1 -0
- package/lib/SplitColumnsContainer.mjs +43 -0
- package/lib/SplitColumnsContainer.mjs.map +1 -0
- package/lib/SwitchDiv.mjs +61 -0
- package/lib/SwitchDiv.mjs.map +1 -0
- package/lib/TextButton.mjs +41 -0
- package/lib/TextButton.mjs.map +1 -0
- package/lib/ToastNotifications.mjs +215 -0
- package/lib/ToastNotifications.mjs.map +1 -0
- package/lib/Tooltip.mjs +160 -0
- package/lib/Tooltip.mjs.map +1 -0
- package/lib/TransitionDiv.mjs +56 -0
- package/lib/TransitionDiv.mjs.map +1 -0
- package/lib/WarningDialog.mjs +84 -0
- package/lib/WarningDialog.mjs.map +1 -0
- package/lib/alt/buttons/OnClickLoadingButton.mjs +36 -0
- package/lib/alt/buttons/OnClickLoadingButton.mjs.map +1 -0
- package/lib/alt/dialogs/DialogWithImageHeader.mjs +14 -0
- package/lib/alt/dialogs/DialogWithImageHeader.mjs.map +1 -0
- package/lib/alt/dialogs/DialogWithSidebar.mjs +106 -0
- package/lib/alt/dialogs/DialogWithSidebar.mjs.map +1 -0
- package/lib/alt/dialogs/MobileBubbleDialog.mjs +49 -0
- package/lib/alt/dialogs/MobileBubbleDialog.mjs.map +1 -0
- package/lib/containers/DialogContainer.mjs +413 -0
- package/lib/containers/DialogContainer.mjs.map +1 -0
- package/lib/containers/Reposition.mjs +35 -0
- package/lib/containers/Reposition.mjs.map +1 -0
- package/lib/containers/usePositioning.mjs +294 -0
- package/lib/containers/usePositioning.mjs.map +1 -0
- package/lib/index.cjs +5000 -0
- package/lib/index.cjs.map +1 -0
- package/lib/index.mjs +37 -0
- package/lib/index.mjs.map +1 -0
- package/lib/useDialogWithToggle.mjs +18 -0
- package/lib/useDialogWithToggle.mjs.map +1 -0
- package/lib/willUseIsSticky.mjs +123 -0
- package/lib/willUseIsSticky.mjs.map +1 -0
- package/package.json +44 -29
- package/src/BasicBulletList.jsx +21 -0
- package/src/Button.jsx +288 -0
- package/src/ContextMenu.jsx +621 -0
- package/src/Dialog.jsx +413 -0
- package/src/Draggable.jsx +801 -0
- package/src/ErrorBoundary.jsx +75 -0
- package/src/FillLastLineResizer.jsx +226 -0
- package/src/Flip.jsx +560 -0
- package/src/FlipList.jsx +136 -0
- package/src/FormatDate.jsx +9 -0
- package/src/Icon.jsx +49 -0
- package/src/Image.jsx +23 -0
- package/src/Link.jsx +68 -0
- package/src/Loading.jsx +29 -0
- package/src/LoadingDiv.jsx +75 -0
- package/src/Notifications.jsx +89 -0
- package/src/PageLoader.jsx +48 -0
- package/src/PersistentNotifications.jsx +182 -0
- package/src/Popup.jsx +49 -0
- package/src/SplitColumnsContainer.jsx +63 -0
- package/src/SwitchDiv.jsx +30 -0
- package/src/TextButton.jsx +34 -0
- package/src/ToastNotifications.jsx +298 -0
- package/src/Tooltip.jsx +158 -0
- package/src/TransitionDiv.jsx +34 -0
- package/src/WarningDialog.jsx +70 -0
- package/src/alt/buttons/OnClickLoadingButton.jsx +16 -0
- package/src/alt/dialogs/DialogWithImageHeader.jsx +42 -0
- package/src/alt/dialogs/DialogWithSidebar.jsx +135 -0
- package/src/alt/dialogs/MobileBubbleDialog.jsx +56 -0
- package/src/containers/DialogContainer.jsx +423 -0
- package/src/containers/Reposition.jsx +24 -0
- package/src/containers/usePositioning.jsx +305 -0
- package/src/index.js +35 -0
- package/src/useDialogWithToggle.jsx +12 -0
- package/src/willUseIsSticky.jsx +113 -0
- package/.babelrc +0 -27
- package/lib/index.js +0 -5196
- package/rollup.config.js +0 -52
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import React, {useRef} from 'react'
|
|
2
|
+
import styled from 'styled-components'
|
|
3
|
+
import {media, callIfFunction, getColor, twoWayAnimation, oneWayAnimation} from '@startlibs/utils'
|
|
4
|
+
import {useToggle, usePopupToggle, useLazyConstant} from '@startlibs/core'
|
|
5
|
+
import {CloseIcon, Dialog} from '../../Dialog'
|
|
6
|
+
import {willUseFlipSwitch} from '../../Flip'
|
|
7
|
+
import {Loading} from '../../Loading'
|
|
8
|
+
import {DialogContainer} from '../../containers/DialogContainer'
|
|
9
|
+
|
|
10
|
+
export const SideMenu = styled.div`
|
|
11
|
+
background-color: ${getColor('lightBluishGray')};
|
|
12
|
+
border-top-left-radius: 18px;
|
|
13
|
+
border-bottom-left-radius: 18px;
|
|
14
|
+
width: 19rem;
|
|
15
|
+
flex-shrink: 0;
|
|
16
|
+
padding: 1.25rem 0;
|
|
17
|
+
${props => !props.visibleOnDesktop && 'display:none;'}
|
|
18
|
+
${media.mobile`
|
|
19
|
+
display:block;
|
|
20
|
+
position:fixed;
|
|
21
|
+
top:0;
|
|
22
|
+
left:0;
|
|
23
|
+
bottom:0;
|
|
24
|
+
z-index:1000;
|
|
25
|
+
transition: 0.25s transform ease-out;
|
|
26
|
+
background-color: ${getColor('gray240')};
|
|
27
|
+
box-shadow: -5px 0 20px 5px rgba(0,0,0,0.4);
|
|
28
|
+
border-radius: 0;
|
|
29
|
+
${props => !props.isOpen && `
|
|
30
|
+
transform: translateX(-100%);
|
|
31
|
+
box-shadow: none;
|
|
32
|
+
`}
|
|
33
|
+
`}
|
|
34
|
+
`
|
|
35
|
+
export const DialogTabMenu = styled.ul`
|
|
36
|
+
cursor: default;
|
|
37
|
+
overflow: auto;
|
|
38
|
+
text-align: left;
|
|
39
|
+
color: inherit;
|
|
40
|
+
user-select: none;
|
|
41
|
+
li {
|
|
42
|
+
position: relative;
|
|
43
|
+
padding: 1rem 2rem 1rem 4.1rem;
|
|
44
|
+
font-size: 14px;
|
|
45
|
+
${media.mobile`
|
|
46
|
+
font-size: 16px;
|
|
47
|
+
`}
|
|
48
|
+
:hover {
|
|
49
|
+
background-color: ${getColor('bluishGray')};
|
|
50
|
+
cursor: pointer;
|
|
51
|
+
}
|
|
52
|
+
&.selected {
|
|
53
|
+
color: ${getColor('main')};
|
|
54
|
+
background: rgba(0, 132, 214, 0.125);
|
|
55
|
+
pointer-events: none;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
.icon{
|
|
59
|
+
position: absolute;
|
|
60
|
+
left: 1rem;
|
|
61
|
+
top: 50%;
|
|
62
|
+
transform: translateY(-50%);
|
|
63
|
+
width: 3rem;
|
|
64
|
+
text-align: center;
|
|
65
|
+
line-height: 0;
|
|
66
|
+
font-size: 26px;
|
|
67
|
+
}
|
|
68
|
+
${props => props.menuTabIsOpen && `
|
|
69
|
+
li {
|
|
70
|
+
padding: 1.25rem 2rem 1.25rem 3.25rem;
|
|
71
|
+
border-radius: 6px
|
|
72
|
+
}
|
|
73
|
+
.icon{
|
|
74
|
+
left: 0;
|
|
75
|
+
}
|
|
76
|
+
`}
|
|
77
|
+
`
|
|
78
|
+
|
|
79
|
+
const [fadeIn, relativeFadeout] = twoWayAnimation('opacity: 0;','opacity: 0;', 'opacity: 1;')
|
|
80
|
+
const absoluteOnLeave = oneWayAnimation('position: absolute;', 'position: absolute;')
|
|
81
|
+
const fadeOut = [absoluteOnLeave,relativeFadeout]
|
|
82
|
+
|
|
83
|
+
export const MENU_TAB = '$menu$'
|
|
84
|
+
|
|
85
|
+
// ponytail: sem footer — padrão do dialog com barra lateral; fecha pelo X ou por fora
|
|
86
|
+
export const DialogWithSidebarPresentation = ({className, tabIsOpen, menuIsOpen, sidebar, Content, openTab, children, title, ...props}) => {
|
|
87
|
+
const tab = useToggle(tabIsOpen)
|
|
88
|
+
const sideMenu = usePopupToggle(menuIsOpen)
|
|
89
|
+
const ref = useRef()
|
|
90
|
+
const switchAnimation = useLazyConstant(willUseFlipSwitch)
|
|
91
|
+
|
|
92
|
+
const menuTabIsOpen = tab.isOpen === MENU_TAB
|
|
93
|
+
|
|
94
|
+
return <DialogContainer {...props} ref={ref}>{({confirmCloseDialog}) =>
|
|
95
|
+
<div className={className + " JS-has-wc"} ref={ref}>
|
|
96
|
+
{
|
|
97
|
+
props.closeDialog &&
|
|
98
|
+
<CloseIcon onClick={confirmCloseDialog}/>
|
|
99
|
+
}
|
|
100
|
+
<SideMenu isOpen={sideMenu.isOpen} visibleOnDesktop>
|
|
101
|
+
{sidebar(tab)}
|
|
102
|
+
</SideMenu>
|
|
103
|
+
|
|
104
|
+
<div className={(menuTabIsOpen ? "menuTabIsOpen " : "") + "main-container"}>
|
|
105
|
+
<h2>{
|
|
106
|
+
menuTabIsOpen ? "Configurações gerais" : <><span className="icon icon-menu mobile-only" onClick={sideMenu.open}/>
|
|
107
|
+
{title(tab)}</>}</h2>
|
|
108
|
+
<React.Suspense fallback={<TabContent key="loading" switchAnimation={switchAnimation} tab={tab} children={<Loading absolute/>} />}>
|
|
109
|
+
<TabContent key={tab.isOpen} switchAnimation={switchAnimation} tab={tab} Content={Content} children={children} />
|
|
110
|
+
</React.Suspense>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
}</DialogContainer>
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const TabContent = ({Content,children,tab,switchAnimation}) => {
|
|
117
|
+
const contentRef = useRef()
|
|
118
|
+
switchAnimation(contentRef,fadeIn,fadeOut)
|
|
119
|
+
|
|
120
|
+
return <div ref={contentRef} className="dialog-content">
|
|
121
|
+
{callIfFunction(Content,tab)}
|
|
122
|
+
{callIfFunction(children,tab)}
|
|
123
|
+
</div>
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export const DialogWithSidebar = styled((props) => <Dialog fullscreenOnMobile as={DialogWithSidebarPresentation} {...props} />)`
|
|
127
|
+
max-width: 59rem;
|
|
128
|
+
display: flex;
|
|
129
|
+
padding: 0;
|
|
130
|
+
flex-direction: row;
|
|
131
|
+
${media.mobile`
|
|
132
|
+
min-height: 100%;
|
|
133
|
+
overflow: hidden;
|
|
134
|
+
`}
|
|
135
|
+
`
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import React, {useRef} from 'react'
|
|
2
|
+
|
|
3
|
+
import {callIfFunction, getColor} from '@startlibs/utils'
|
|
4
|
+
import styled from 'styled-components'
|
|
5
|
+
|
|
6
|
+
import {DialogErrorContent} from '../../Dialog'
|
|
7
|
+
import {DialogContainer} from '../../containers/DialogContainer'
|
|
8
|
+
|
|
9
|
+
const MobileBubbleDialogFooter = styled.div `
|
|
10
|
+
background-color: ${getColor('gray240')};
|
|
11
|
+
min-height: 5rem;
|
|
12
|
+
color: ${getColor('main')};
|
|
13
|
+
font-size: 13px;
|
|
14
|
+
font-weight: 500;
|
|
15
|
+
text-transform: uppercase;
|
|
16
|
+
padding: 1rem;
|
|
17
|
+
user-select: none;
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
justify-content: center;
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
border-bottom-left-radius: 10px;
|
|
23
|
+
border-bottom-right-radius: 10px;
|
|
24
|
+
:active {
|
|
25
|
+
background: rgb(232,232,232);
|
|
26
|
+
}
|
|
27
|
+
`
|
|
28
|
+
|
|
29
|
+
export const MobileBubbleDialog = styled(({className, footer, Content, children, title, illustration, icon, auxiliarActions, ...props}) => {
|
|
30
|
+
const ref = useRef()
|
|
31
|
+
return <DialogContainer {...props} closeOnClick ref={ref} DialogErrorContent={DialogErrorContent} title={title}>{({confirmCloseDialog}) =>
|
|
32
|
+
<>
|
|
33
|
+
<div className={className} ref={ref}>
|
|
34
|
+
{
|
|
35
|
+
children && callIfFunction(children)
|
|
36
|
+
}
|
|
37
|
+
<MobileBubbleDialogFooter onClick={confirmCloseDialog}>
|
|
38
|
+
Fechar
|
|
39
|
+
</MobileBubbleDialogFooter>
|
|
40
|
+
</div>
|
|
41
|
+
</>
|
|
42
|
+
}</DialogContainer>
|
|
43
|
+
})`
|
|
44
|
+
border-radius: 10px;
|
|
45
|
+
background-color: white;
|
|
46
|
+
width: 100%;
|
|
47
|
+
margin: auto;
|
|
48
|
+
position: relative;
|
|
49
|
+
align-self: baseline;
|
|
50
|
+
overflow: hidden;
|
|
51
|
+
@supports (flex-wrap: wrap) {
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-direction: column;
|
|
54
|
+
}
|
|
55
|
+
`
|
|
56
|
+
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import React, {forwardRef, useEffect, useRef} from 'react'
|
|
2
|
+
import styled from 'styled-components'
|
|
3
|
+
import _ from 'lodash/fp'
|
|
4
|
+
import {Fill, Slot, useEnsureRef, useRefState, willUseLayers} from '@startlibs/core'
|
|
5
|
+
import {callIfFunction, identityOpacityFrames, media, oneWayAnimation, twoWayAnimation} from '@startlibs/utils'
|
|
6
|
+
import {ErrorBoundary} from '../ErrorBoundary'
|
|
7
|
+
import {useFlipAnimation} from '../Flip'
|
|
8
|
+
|
|
9
|
+
export const dialogFadeInDown = oneWayAnimation('opacity: 0; transform: translateY(50px);', 'opacity: 1; transform: translateY(0);')
|
|
10
|
+
const [dialogModalIn, dialogModalOut] = twoWayAnimation('opacity: 0;', 'opacity: 1;')
|
|
11
|
+
export {dialogModalIn, dialogModalOut}
|
|
12
|
+
|
|
13
|
+
export const Modal = styled.div`
|
|
14
|
+
position: fixed;
|
|
15
|
+
top:0;
|
|
16
|
+
bottom:0;
|
|
17
|
+
left:0;
|
|
18
|
+
right:0;
|
|
19
|
+
will-change: opacity;
|
|
20
|
+
background-color: rgba(0,0,0,0.7);
|
|
21
|
+
${props => props.$blurBackdrop && `backdrop-filter: blur(10px);`}
|
|
22
|
+
`
|
|
23
|
+
|
|
24
|
+
export const ModalContainer = styled.div`
|
|
25
|
+
position: fixed;
|
|
26
|
+
top:0;
|
|
27
|
+
bottom:0;
|
|
28
|
+
left:0;
|
|
29
|
+
right:0;
|
|
30
|
+
/* Barra invisível mantendo a LARGURA NATIVA da calha (scrollbar-color não
|
|
31
|
+
colapsa a largura como scrollbar-width:none / ::-webkit-scrollbar faziam).
|
|
32
|
+
Essa calha compensa a remoção da scrollbar do body ao abrir a dialog (body
|
|
33
|
+
vira overflow:hidden e o viewport alarga ~15px no fim da animação); sem ela,
|
|
34
|
+
a dialog centralizada dá um pulinho lateral. Aqui: sem barra visível e sem salto. */
|
|
35
|
+
scrollbar-color: transparent transparent;
|
|
36
|
+
`
|
|
37
|
+
|
|
38
|
+
const DialogCenter = styled.div`
|
|
39
|
+
@supports (position:sticky) {
|
|
40
|
+
will-change: transform, opacity;
|
|
41
|
+
&.JS-wc-off {
|
|
42
|
+
will-change: auto;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
display: -webkit-box;
|
|
46
|
+
display: -ms-flexbox;
|
|
47
|
+
display: flex;
|
|
48
|
+
overflow-y: ${props => props.withScroll ? "scroll" : "hidden"};
|
|
49
|
+
overflow-x:hidden;
|
|
50
|
+
/* idem ModalContainer: calha nativa reservada (sem salto), track invisível — mas o
|
|
51
|
+
polegar é visível, senão dialog mais alta que a tela rola sem nenhuma pista.
|
|
52
|
+
Claro porque a calha fica sobre o backdrop escuro. Sem overflow, o Chromium não
|
|
53
|
+
desenha polegar → não sobra track fantasma. */
|
|
54
|
+
scrollbar-color: rgba(255,255,255,0.6) transparent;
|
|
55
|
+
position:fixed;
|
|
56
|
+
top:0;
|
|
57
|
+
bottom:0;
|
|
58
|
+
left:0;
|
|
59
|
+
right:0;
|
|
60
|
+
align-items: center;
|
|
61
|
+
${media.desktop`
|
|
62
|
+
padding: ${props => props.fullscreen ? '1rem' : '3rem 0.5rem;'};
|
|
63
|
+
`}
|
|
64
|
+
${media.mobile`
|
|
65
|
+
padding: ${props => props.fullscreenOnMobile ? '0' : '0.5rem'};
|
|
66
|
+
`}
|
|
67
|
+
`
|
|
68
|
+
|
|
69
|
+
const PopupSlot = styled(Slot)`
|
|
70
|
+
position: absolute;
|
|
71
|
+
left:0;
|
|
72
|
+
top: 0;
|
|
73
|
+
width: 100%;
|
|
74
|
+
`
|
|
75
|
+
const RelativeSlot = styled(Slot)`
|
|
76
|
+
width:100%;
|
|
77
|
+
position: absolute;
|
|
78
|
+
`
|
|
79
|
+
const editableSelector = 'textarea,input,[contenteditable]'
|
|
80
|
+
const focusableSelector = 'button,a,'+editableSelector
|
|
81
|
+
|
|
82
|
+
const shouldPreventResolvers = []
|
|
83
|
+
let shouldPreventDialogPop = Promise.resolve()
|
|
84
|
+
export const preventDialogPop = (cb) => {
|
|
85
|
+
shouldPreventDialogPop = shouldPreventDialogPop.then(() => {
|
|
86
|
+
new Promise((res) => {
|
|
87
|
+
shouldPreventResolvers.push(res)
|
|
88
|
+
cb()
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
export const waitForPop = () => {
|
|
93
|
+
return shouldPreventDialogPop
|
|
94
|
+
}
|
|
95
|
+
const preventDialogPopState = (e) => {
|
|
96
|
+
const resolver = shouldPreventResolvers.shift()
|
|
97
|
+
if (resolver) {
|
|
98
|
+
e.stopImmediatePropagation()
|
|
99
|
+
resolver()
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
window.addEventListener("popstate",preventDialogPopState)
|
|
104
|
+
|
|
105
|
+
const fixPosition = (query, fn) => {
|
|
106
|
+
const elements = document.querySelectorAll(query)
|
|
107
|
+
if (elements.length) {
|
|
108
|
+
Array.prototype.slice.call(elements).forEach(fn)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let openLayers = 0
|
|
113
|
+
let fakedScrollTop = 0
|
|
114
|
+
export const isFakeScrolled = () => openLayers>0 && fakedScrollTop > 0
|
|
115
|
+
export const removeWindowScroll = (opening, keepDisabledScroll) => {
|
|
116
|
+
if (opening) {
|
|
117
|
+
openLayers = Math.max(openLayers+1,1)
|
|
118
|
+
if (openLayers>1) {
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
} else {
|
|
122
|
+
openLayers = Math.min(openLayers-1,0)
|
|
123
|
+
if (openLayers<0) {
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const finishReset = (marginRight, scrollTop) => {
|
|
128
|
+
// body.style.paddingRight = marginRight + 'px'
|
|
129
|
+
window.scroll(window.scrollLeft || window.scrollX || 0, scrollTop)
|
|
130
|
+
if (keepDisabledScroll) { return }
|
|
131
|
+
fixPosition('.navbar,.JS-fix-dialog-scroll', n => {
|
|
132
|
+
if (opening) {
|
|
133
|
+
const {right} = window.getComputedStyle(n)
|
|
134
|
+
n.style.right = (parseInt(right) + marginRight) + 'px'
|
|
135
|
+
} else {
|
|
136
|
+
n.style.right = null
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
fixPosition('.JS-fix-dialog-scroll-transform', n => {
|
|
140
|
+
if (marginRight) {
|
|
141
|
+
n.style.transform = window.getComputedStyle(n).transform + ` translateX(-${marginRight / 2}px)`
|
|
142
|
+
} else {
|
|
143
|
+
n.style.transform = null
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
fixPosition('.JS-on-open-dialog', n => {
|
|
148
|
+
if (opening) {
|
|
149
|
+
n.classList.add('JS-dialog-open')
|
|
150
|
+
} else {
|
|
151
|
+
n.classList.remove('JS-dialog-open')
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
window.requestAnimationFrame(()=>window.dispatchEvent(new CustomEvent('resize', {detail: {dialog: true}})))
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const body = document.body
|
|
158
|
+
let bodyWidth = body.offsetWidth
|
|
159
|
+
if (opening) {
|
|
160
|
+
const scrollTop = window.pageYOffset
|
|
161
|
+
fakedScrollTop = scrollTop
|
|
162
|
+
if (keepDisabledScroll) {
|
|
163
|
+
body.style.overflowY = 'scroll'
|
|
164
|
+
} else {
|
|
165
|
+
body.style.overflow = 'hidden'
|
|
166
|
+
}
|
|
167
|
+
const marginRight = (body.offsetWidth - bodyWidth)
|
|
168
|
+
|
|
169
|
+
body.style.position = 'fixed'
|
|
170
|
+
body.style.top = -scrollTop + "px"
|
|
171
|
+
body.style.left = '0'
|
|
172
|
+
if (!keepDisabledScroll) {
|
|
173
|
+
body.style.right = marginRight + 'px'
|
|
174
|
+
} else {
|
|
175
|
+
body.style.right = '0'
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
finishReset(marginRight,scrollTop)
|
|
179
|
+
} else {
|
|
180
|
+
fakedScrollTop = 0
|
|
181
|
+
// setTimeout(() => {
|
|
182
|
+
const scrollTop = parseInt(body.style.top)
|
|
183
|
+
body.style.overflow = ''
|
|
184
|
+
|
|
185
|
+
body.style.position = ''
|
|
186
|
+
body.style.top = ''
|
|
187
|
+
body.style.left = ''
|
|
188
|
+
body.style.right = ''
|
|
189
|
+
finishReset(0,-scrollTop)
|
|
190
|
+
// },250)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const {href: originalHref, hash: originalHash} = window.location
|
|
195
|
+
|
|
196
|
+
const [useDialogLayers, getDialogLayersSize] = willUseLayers(React.useLayoutEffect)
|
|
197
|
+
export {useDialogLayers, getDialogLayersSize}
|
|
198
|
+
|
|
199
|
+
export const DialogContainer = forwardRef((
|
|
200
|
+
{
|
|
201
|
+
children, title, closeDialog, closeOnClick, dontRefocus, onConfirm, prev, next, form, noFormWrap, withFormWrap, placement = 'Dialog',
|
|
202
|
+
DialogErrorContent, customHref, animation = dialogFadeInDown, animationTiming, animationLeaveEffect, animationEnterEffect, skipKeys,fullscreenOnMobile, fullscreen, confirmChanges = false, skipDom, modalRef, skipCheckFocus,
|
|
203
|
+
autoComplete, blurBackdrop
|
|
204
|
+
},
|
|
205
|
+
forwardedRef) => {
|
|
206
|
+
|
|
207
|
+
const dialogRef = useEnsureRef(forwardedRef)
|
|
208
|
+
const prevActiveElement = useRefState(document.activeElement)
|
|
209
|
+
const modal = useEnsureRef(modalRef)
|
|
210
|
+
const modalScroll = useRef()
|
|
211
|
+
const mouseDownInModal = useRefState()
|
|
212
|
+
const previousHash = useRefState(window.location.hash)
|
|
213
|
+
|
|
214
|
+
const [layerIndex, {isTop, getSize}] = useDialogLayers({
|
|
215
|
+
onFirst: () => {
|
|
216
|
+
waitForPop().then(() => {
|
|
217
|
+
if (customHref && encodeURI(customHref) === window.location.hash) {
|
|
218
|
+
window.history.replaceState(history.state, '', window.location.pathname)
|
|
219
|
+
window.history.pushState({dialog: true}, '', customHref)
|
|
220
|
+
} else {
|
|
221
|
+
window.history.pushState({dialog: true}, '', window.location.href + (customHref || ""))
|
|
222
|
+
}
|
|
223
|
+
})
|
|
224
|
+
// clearPreventDialogPop()
|
|
225
|
+
if (document.body.scrollHeight === document.body.clientHeight) {
|
|
226
|
+
modal.current.style.overflowY = "scroll"
|
|
227
|
+
if (modalScroll.current) {
|
|
228
|
+
modalScroll.current.style.overflowY = "scroll"
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// resetNavbar(true)
|
|
232
|
+
},
|
|
233
|
+
onRemove: (layers) => {
|
|
234
|
+
if (
|
|
235
|
+
(layers === 0 && _.get('dialog', window.history.state) === true) ||
|
|
236
|
+
(customHref && window.location.hash === encodeURI(customHref))
|
|
237
|
+
) {
|
|
238
|
+
preventDialogPop(() => {
|
|
239
|
+
window.history.back()
|
|
240
|
+
})
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
onEmpty: () => {
|
|
244
|
+
if (document.body.scrollHeight > document.body.clientHeight) {
|
|
245
|
+
if (modalScroll.current) {
|
|
246
|
+
modalScroll.current.style.overflowY = "hidden"
|
|
247
|
+
}
|
|
248
|
+
} else if (modal.current) {
|
|
249
|
+
modal.current.style.overflowY = "scroll"
|
|
250
|
+
}
|
|
251
|
+
removeWindowScroll(false)
|
|
252
|
+
}
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
const confirmCloseDialog = () => {
|
|
256
|
+
if (form && form.getHasChanged() && confirmChanges) {
|
|
257
|
+
form.confirm()
|
|
258
|
+
.then(closeDialog)
|
|
259
|
+
} else {
|
|
260
|
+
callIfFunction(closeDialog)
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/*
|
|
264
|
+
useEffect(() => () => {
|
|
265
|
+
setTimeout(() => {
|
|
266
|
+
if (window.location.hash === previousHash.get() && _.get('dialog', window.history.state) === true) {
|
|
267
|
+
console.log("???")
|
|
268
|
+
}
|
|
269
|
+
}, 100)
|
|
270
|
+
}, [])*/
|
|
271
|
+
|
|
272
|
+
const pastFirstTime = React.useRef()
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
useEffect(() => {
|
|
276
|
+
if (customHref && window.location.hash !== customHref) {
|
|
277
|
+
// if (pastFirstTime.current) {
|
|
278
|
+
waitForPop().then(() => {
|
|
279
|
+
if (window.location.hash) {
|
|
280
|
+
window.history.replaceState({dialog: true}, '', customHref)
|
|
281
|
+
} else {
|
|
282
|
+
window.history.pushState({dialog: true}, '', customHref)
|
|
283
|
+
}
|
|
284
|
+
})
|
|
285
|
+
// } else {
|
|
286
|
+
// window.history.pushState({dialog: true}, '', customHref)
|
|
287
|
+
// pastFirstTime.current = true
|
|
288
|
+
// }
|
|
289
|
+
previousHash.set(encodeURI(customHref))
|
|
290
|
+
}
|
|
291
|
+
}, [customHref])
|
|
292
|
+
|
|
293
|
+
useEffect(() => {
|
|
294
|
+
window.addEventListener('keydown', checkKey)
|
|
295
|
+
window.addEventListener('focusin', checkFocus)
|
|
296
|
+
window.addEventListener('popstate', checkHistory)
|
|
297
|
+
return () => {
|
|
298
|
+
window.removeEventListener('keydown', checkKey)
|
|
299
|
+
window.removeEventListener('focusin', checkFocus)
|
|
300
|
+
window.removeEventListener('popstate', checkHistory)
|
|
301
|
+
if (!dontRefocus && document.body.contains(prevActiveElement.get()) && prevActiveElement.get().matches(focusableSelector)) {
|
|
302
|
+
setTimeout(() => prevActiveElement.get().focus(), 0)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}, [])
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
const checkHistory = (e) => {
|
|
309
|
+
if (!isTop()/* || (previousHash.get() !== window.location.hash && !customHref)*/) return
|
|
310
|
+
if (_.get('willOpenDialog', window.history.state) === true) { return }
|
|
311
|
+
if (_.get('dialog', window.history.state) !== true) {
|
|
312
|
+
window.history.pushState({dialog: true}, '', window.location.href)
|
|
313
|
+
}
|
|
314
|
+
confirmCloseDialog()
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const checkFocus = (e) => {
|
|
318
|
+
if (!isTop() || skipCheckFocus) return
|
|
319
|
+
if (modal.current && !e.target.closest('.' + modal.current.className.split(' ').join('.'))) {
|
|
320
|
+
const nextTab = modal.current.querySelector('[tabindex]')
|
|
321
|
+
if (nextTab)
|
|
322
|
+
nextTab.focus()
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const checkKey = (e) => {
|
|
327
|
+
if (!isTop() || skipKeys || e.target?.className?.indexOf("JS-dialog-skip-evts")>=0 || e.target?.closest(".JS-dialog-skip-evts")) return
|
|
328
|
+
if (modal.current && e.shiftKey && e.keyCode === 9 && modal.current.querySelector('[tabindex]') === document.activeElement) {
|
|
329
|
+
e.preventDefault()
|
|
330
|
+
const prevTab = [].slice.call(modal.current.querySelectorAll('[tabindex]')).reduce((acc, el) => el.tabIndex >= acc.tabIndex ? el : acc, {tabIndex: -1})
|
|
331
|
+
if (prevTab && prevTab.focus)
|
|
332
|
+
prevTab.focus()
|
|
333
|
+
}
|
|
334
|
+
if (e.key === 'Escape') {
|
|
335
|
+
e.preventDefault()
|
|
336
|
+
confirmCloseDialog()
|
|
337
|
+
} else if (e.key === 'Enter') {
|
|
338
|
+
if (e.target) {
|
|
339
|
+
const target = e.target.closest(focusableSelector + ',div')
|
|
340
|
+
if (target && (target.tagName.toLowerCase() !== 'input' || target.form || target.type.toLowerCase() === 'file') && target.matches(focusableSelector))
|
|
341
|
+
return
|
|
342
|
+
}
|
|
343
|
+
e.preventDefault()
|
|
344
|
+
e.stopPropagation()
|
|
345
|
+
if (onConfirm) {
|
|
346
|
+
onConfirm()
|
|
347
|
+
} else if (form) {
|
|
348
|
+
form.submitForm()
|
|
349
|
+
}
|
|
350
|
+
} else if (!e.target.matches(editableSelector)) {
|
|
351
|
+
if (e.key === 'ArrowLeft' && prev) {
|
|
352
|
+
prev()
|
|
353
|
+
} else if (e.key === 'ArrowRight' && next) {
|
|
354
|
+
next()
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const checkClick = (e) => {
|
|
360
|
+
if (closeOnClick && e.target === e.currentTarget && mouseDownInModal.get()) {
|
|
361
|
+
confirmCloseDialog()
|
|
362
|
+
}
|
|
363
|
+
mouseDownInModal.set(false)
|
|
364
|
+
}
|
|
365
|
+
const mouseDown = (e) => {
|
|
366
|
+
mouseDownInModal.set(e.target === e.currentTarget)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const usingForm = withFormWrap || (form && !noFormWrap)
|
|
370
|
+
|
|
371
|
+
useFlipAnimation(dialogRef, animation, true, {
|
|
372
|
+
nestedFlip: !skipDom,
|
|
373
|
+
onEnterEnd: () => {
|
|
374
|
+
if (getSize() === 1) {
|
|
375
|
+
removeWindowScroll(true);
|
|
376
|
+
}
|
|
377
|
+
if (document.body.scrollHeight === document.body.clientHeight) {
|
|
378
|
+
modal.current.style.overflowY = "none"
|
|
379
|
+
}
|
|
380
|
+
if (modalScroll.current) {
|
|
381
|
+
modalScroll.current.style.overflowY = "scroll"
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
leaveEffect:animationLeaveEffect,
|
|
385
|
+
enterEffect:animationEnterEffect,
|
|
386
|
+
timing:animationTiming
|
|
387
|
+
})
|
|
388
|
+
useFlipAnimation(modal, !skipDom && identityOpacityFrames, true,{timing:animationTiming})
|
|
389
|
+
|
|
390
|
+
const content = <>
|
|
391
|
+
<PopupSlot name={'Popup-' + layerIndex}/>
|
|
392
|
+
<ErrorBoundary title={callIfFunction(title)} closeDialog={closeDialog} Error={DialogErrorContent}>
|
|
393
|
+
{callIfFunction(children, {confirmCloseDialog}, dialogRef, fullscreenOnMobile)}
|
|
394
|
+
</ErrorBoundary>
|
|
395
|
+
</>
|
|
396
|
+
|
|
397
|
+
return (
|
|
398
|
+
<Fill name={placement}>
|
|
399
|
+
{
|
|
400
|
+
skipDom
|
|
401
|
+
? content
|
|
402
|
+
: <>
|
|
403
|
+
<AnimatedModal timing={animationTiming} blurBackdrop={blurBackdrop}/>
|
|
404
|
+
<ModalContainer ref={modal}>
|
|
405
|
+
|
|
406
|
+
<DialogCenter autoComplete={autoComplete} className="JS-has-wc" withScroll={getSize()>1} fullscreen={fullscreen} fullscreenOnMobile={fullscreenOnMobile} ref={modalScroll} as={usingForm ? 'form' : undefined} onClick={checkClick}
|
|
407
|
+
onSubmit={usingForm ? () => {} : undefined}
|
|
408
|
+
onMouseDown={mouseDown}>
|
|
409
|
+
{content}
|
|
410
|
+
</DialogCenter>
|
|
411
|
+
</ModalContainer>
|
|
412
|
+
</>
|
|
413
|
+
}
|
|
414
|
+
</Fill>
|
|
415
|
+
)
|
|
416
|
+
})
|
|
417
|
+
|
|
418
|
+
export const AnimatedModal = React.forwardRef(({timing,nestedFlip,enterEffect,leaveEffect,modalIn = dialogModalIn, modalOut = dialogModalOut, blurBackdrop,...props},ref) => {
|
|
419
|
+
const modal = useEnsureRef(ref)
|
|
420
|
+
useFlipAnimation(modal, modalIn, modalOut,{timing,nestedFlip,leaveEffect,enterEffect})
|
|
421
|
+
return <Modal {...props} $blurBackdrop={blurBackdrop} ref={modal}/>
|
|
422
|
+
})
|
|
423
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React, {useMemo} from 'react'
|
|
2
|
+
import {Fill, useEnsureRef} from '@startlibs/core'
|
|
3
|
+
import {usePositioning} from './usePositioning'
|
|
4
|
+
import styled from 'styled-components'
|
|
5
|
+
import {getDialogLayersSize} from './DialogContainer'
|
|
6
|
+
|
|
7
|
+
const AbsoluteContainer = styled.div`
|
|
8
|
+
position: absolute;
|
|
9
|
+
`
|
|
10
|
+
|
|
11
|
+
export const Reposition = React.forwardRef(({slot = 'Popup', className, onClick, children, ...rest},ref) => {
|
|
12
|
+
const popupRef = useEnsureRef(ref)
|
|
13
|
+
const [positionInfo, placeholder] = usePositioning(popupRef, rest)
|
|
14
|
+
|
|
15
|
+
const slotName = useMemo(() => getDialogLayersSize() === 0 ? slot : (slot + '-' + (getDialogLayersSize() - 1)), [])
|
|
16
|
+
return <>
|
|
17
|
+
{placeholder}
|
|
18
|
+
<Fill name={slotName}>
|
|
19
|
+
<AbsoluteContainer {...positionInfo} onClick={onClick} className={className} ref={popupRef}>
|
|
20
|
+
{children}
|
|
21
|
+
</AbsoluteContainer>
|
|
22
|
+
</Fill>
|
|
23
|
+
</>
|
|
24
|
+
})
|