@startlibs/form 1.0.12 → 1.1.1
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/AutoComplete.mjs +550 -0
- package/lib/AutoComplete.mjs.map +1 -0
- package/lib/Checkbox.mjs +76 -0
- package/lib/Checkbox.mjs.map +1 -0
- package/lib/Combobox.mjs +544 -0
- package/lib/Combobox.mjs.map +1 -0
- package/lib/ComboboxWithCustom.mjs +77 -0
- package/lib/ComboboxWithCustom.mjs.map +1 -0
- package/lib/DatePicker.mjs +625 -0
- package/lib/DatePicker.mjs.map +1 -0
- package/lib/EditableBox.mjs +496 -0
- package/lib/EditableBox.mjs.map +1 -0
- package/lib/Errors.mjs +176 -0
- package/lib/Errors.mjs.map +1 -0
- package/lib/ExpandableBox.mjs +209 -0
- package/lib/ExpandableBox.mjs.map +1 -0
- package/lib/Field.mjs +65 -0
- package/lib/Field.mjs.map +1 -0
- package/lib/FieldBox.mjs +80 -0
- package/lib/FieldBox.mjs.map +1 -0
- package/lib/Form.mjs +322 -0
- package/lib/Form.mjs.map +1 -0
- package/lib/FormValue.mjs +111 -0
- package/lib/FormValue.mjs.map +1 -0
- package/lib/InputboxGroup.mjs +104 -0
- package/lib/InputboxGroup.mjs.map +1 -0
- package/lib/Radiobox.mjs +67 -0
- package/lib/Radiobox.mjs.map +1 -0
- package/lib/RichText.mjs +634 -0
- package/lib/RichText.mjs.map +1 -0
- package/lib/TextInput.mjs +359 -0
- package/lib/TextInput.mjs.map +1 -0
- package/lib/alt/checkbox/SimpleCheckbox.mjs +82 -0
- package/lib/alt/checkbox/SimpleCheckbox.mjs.map +1 -0
- package/lib/alt/checkbox/ToggleCheckbox.mjs +68 -0
- package/lib/alt/checkbox/ToggleCheckbox.mjs.map +1 -0
- package/lib/alt/radiobox/CleanTabRadiobox.mjs +44 -0
- package/lib/alt/radiobox/CleanTabRadiobox.mjs.map +1 -0
- package/lib/alt/radiobox/IconRadioBox.mjs +52 -0
- package/lib/alt/radiobox/IconRadioBox.mjs.map +1 -0
- package/lib/alt/radiobox/SimpleRadiobox.mjs +61 -0
- package/lib/alt/radiobox/SimpleRadiobox.mjs.map +1 -0
- package/lib/alt/radiobox/TabRadiobox.mjs +54 -0
- package/lib/alt/radiobox/TabRadiobox.mjs.map +1 -0
- package/lib/enhanceInput.mjs +131 -0
- package/lib/enhanceInput.mjs.map +1 -0
- package/lib/index.cjs +5345 -0
- package/lib/index.cjs.map +1 -0
- package/lib/index.mjs +28 -0
- package/lib/index.mjs.map +1 -0
- package/lib/useConfirmDialog.mjs +117 -0
- package/lib/useConfirmDialog.mjs.map +1 -0
- package/lib/useDraftConfirmation.mjs +56 -0
- package/lib/useDraftConfirmation.mjs.map +1 -0
- package/lib/validation.mjs +91 -0
- package/lib/validation.mjs.map +1 -0
- package/lib/withFormImageInput.mjs +281 -0
- package/lib/withFormImageInput.mjs.map +1 -0
- package/package.json +46 -26
- package/src/AutoComplete.jsx +642 -0
- package/src/Checkbox.jsx +45 -0
- package/src/Combobox.jsx +606 -0
- package/src/ComboboxWithCustom.jsx +68 -0
- package/src/DatePicker.jsx +733 -0
- package/src/EditableBox.jsx +502 -0
- package/src/Errors.jsx +174 -0
- package/src/ExpandableBox.jsx +207 -0
- package/src/Field.jsx +76 -0
- package/src/FieldBox.jsx +136 -0
- package/src/Form.jsx +291 -0
- package/src/FormValue.jsx +67 -0
- package/src/IconRadioBoxOldHubt.jsx +93 -0
- package/src/InputboxGroup.jsx +121 -0
- package/src/Radiobox.jsx +33 -0
- package/src/RichText.jsx +812 -0
- package/src/TextInput.jsx +519 -0
- package/src/alt/checkbox/SimpleCheckbox.jsx +162 -0
- package/src/alt/checkbox/ToggleCheckbox.jsx +143 -0
- package/src/alt/radiobox/CleanTabRadiobox.jsx +46 -0
- package/src/alt/radiobox/IconRadioBox.jsx +93 -0
- package/src/alt/radiobox/SimpleRadiobox.jsx +153 -0
- package/src/alt/radiobox/TabRadiobox.jsx +73 -0
- package/src/enhanceInput.jsx +107 -0
- package/src/index.js +27 -0
- package/src/useConfirmDialog.jsx +105 -0
- package/src/useDraftConfirmation.jsx +60 -0
- package/src/validation.js +92 -0
- package/src/withFormImageInput.jsx +297 -0
- package/.babelrc +0 -28
- package/lib/index.js +0 -6258
- package/rollup.config.js +0 -43
- package/yarn-error.log +0 -1607
package/src/RichText.jsx
ADDED
|
@@ -0,0 +1,812 @@
|
|
|
1
|
+
import React, {useContext, useEffect, useRef, useState} from 'react'
|
|
2
|
+
import styled, {css} from 'styled-components'
|
|
3
|
+
import ContentEditable from 'react-contenteditable'
|
|
4
|
+
import {darken} from 'polished'
|
|
5
|
+
import _ from 'lodash/fp'
|
|
6
|
+
import {getColor, linkRegex, stopPropagation} from '@startlibs/utils'
|
|
7
|
+
import {StartlibsContext, useEnsureRef, useLazyConstant, useToggle, usePopupToggle, useRefState} from '@startlibs/core'
|
|
8
|
+
import {Errors} from './Errors'
|
|
9
|
+
import {Field} from './Field'
|
|
10
|
+
import {WithForm} from './Form'
|
|
11
|
+
import {TextInput} from './TextInput'
|
|
12
|
+
import {useInput} from './enhanceInput'
|
|
13
|
+
import {Button, Icon} from '@startlibs/components'
|
|
14
|
+
|
|
15
|
+
const InputIcon = styled(Icon)`
|
|
16
|
+
position: absolute;
|
|
17
|
+
outline: none;
|
|
18
|
+
bottom: 0;
|
|
19
|
+
font-size: 21px;
|
|
20
|
+
line-height: 3rem;
|
|
21
|
+
right: 0;
|
|
22
|
+
width: 2rem;
|
|
23
|
+
pointer-events: none;
|
|
24
|
+
color: ${getColor('gray150')};
|
|
25
|
+
`
|
|
26
|
+
|
|
27
|
+
const FORMAT_TOOLS = {
|
|
28
|
+
'bold': ['b', 'strong'],
|
|
29
|
+
'italic': 'i',
|
|
30
|
+
'underline': 'u',
|
|
31
|
+
'strikethrough': 'strike',
|
|
32
|
+
'link': 'a',
|
|
33
|
+
'insertUnorderedList': ['ul', 'li'],
|
|
34
|
+
'insertOrderedList': ['ol', 'li'],
|
|
35
|
+
'outdent': '',
|
|
36
|
+
'indent': 'blockquote',
|
|
37
|
+
'fontSizeUp': 'font',
|
|
38
|
+
'fontSizeDown': 'font'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const TAG_TO_STYLE_PROPERTY = {
|
|
42
|
+
'b': 'fontWeight',
|
|
43
|
+
'strong': 'fontWeight',
|
|
44
|
+
'u': 'textDecoration',
|
|
45
|
+
'strike': 'textDecoration',
|
|
46
|
+
'font': 'fontSize',
|
|
47
|
+
'i': 'fontStyle'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let GET_LIMITS_FONT_SIZE_PX = () => {
|
|
51
|
+
const s = document.createElement('span')
|
|
52
|
+
s.style.fontSize = 'medium'
|
|
53
|
+
document.body.appendChild(s)
|
|
54
|
+
const fontSize = parseInt(getComputedStyle(s).fontSize)
|
|
55
|
+
document.body.removeChild(s)
|
|
56
|
+
const limits = [fontSize * 3 / 5, fontSize * 3]
|
|
57
|
+
GET_LIMITS_FONT_SIZE_PX = () => limits
|
|
58
|
+
return limits
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const wrapWith = (el,wrapper) => {
|
|
62
|
+
el.parentNode.insertBefore(wrapper, el);
|
|
63
|
+
wrapper.appendChild(el)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const FIX_STYLE = {
|
|
67
|
+
'fontWeight': (v, el) => {
|
|
68
|
+
const intFontWeight = parseInt(window.getComputedStyle(el).fontWeight)
|
|
69
|
+
return intFontWeight <= 400 ? 400 : 600
|
|
70
|
+
},
|
|
71
|
+
'fontSize': (v, el) => {
|
|
72
|
+
const [min, max] = GET_LIMITS_FONT_SIZE_PX()
|
|
73
|
+
const intFontSize = parseInt(window.getComputedStyle(el).fontSize)
|
|
74
|
+
return Math.min(max, Math.max(min, intFontSize)) + 'px'
|
|
75
|
+
},
|
|
76
|
+
'textDecoration': (v,el) => {
|
|
77
|
+
const values = v.split(' ').filter(v => v === 'underline' || v === 'line-through')
|
|
78
|
+
if (values.indexOf('underline')>=0) {
|
|
79
|
+
wrapWith(el,document.createElement('u'))
|
|
80
|
+
}
|
|
81
|
+
if (values.indexOf('line-through')>=0) {
|
|
82
|
+
wrapWith(el,document.createElement('strike'))
|
|
83
|
+
}
|
|
84
|
+
return ''
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const RichTextContainer = styled.div`
|
|
89
|
+
position:relative;
|
|
90
|
+
width: 100%;
|
|
91
|
+
border: 1px solid ${getColor('gray210')};
|
|
92
|
+
border-radius: 8px;
|
|
93
|
+
background: white;
|
|
94
|
+
${props => props.hasErrors && `border-color: ${getColor(props)('alert')}`};
|
|
95
|
+
${props => props.showToolbar && `
|
|
96
|
+
${ToolsContainer} {
|
|
97
|
+
transition: 0.5s;
|
|
98
|
+
max-height: 7.5rem;
|
|
99
|
+
transition: 0.6s;
|
|
100
|
+
pointer-events: auto;
|
|
101
|
+
}
|
|
102
|
+
`};
|
|
103
|
+
${props => props.focus && `
|
|
104
|
+
border-color: ${getColor('gray180')};
|
|
105
|
+
box-shadow: 0px 0px 0px 2px rgba(0,0,0,0.1);
|
|
106
|
+
`}
|
|
107
|
+
${props => props.locked && css`
|
|
108
|
+
padding-right: 2.25rem;
|
|
109
|
+
background: rgba(0,0,0,0.075);
|
|
110
|
+
`}
|
|
111
|
+
${props => props.linkColor && css`
|
|
112
|
+
a, a:visited, a:focus, a:active {
|
|
113
|
+
color: ${props.linkColor};
|
|
114
|
+
}
|
|
115
|
+
a:hover {
|
|
116
|
+
color: ${darken(0.05, props.linkColor)};
|
|
117
|
+
}
|
|
118
|
+
`}
|
|
119
|
+
ul {
|
|
120
|
+
list-style-type: disc;
|
|
121
|
+
}
|
|
122
|
+
ul, ol {
|
|
123
|
+
padding-inline-start: 25px;
|
|
124
|
+
margin-block-start: 0.5em;
|
|
125
|
+
margin-block-end: 0.5em;
|
|
126
|
+
}
|
|
127
|
+
`
|
|
128
|
+
|
|
129
|
+
export const RichTextInput = styled.div`
|
|
130
|
+
font-size: 14px;
|
|
131
|
+
color: ${getColor('gray60')};
|
|
132
|
+
background: white;
|
|
133
|
+
border-radius: 7px;
|
|
134
|
+
> div {
|
|
135
|
+
overflow: auto;
|
|
136
|
+
width: 100%;
|
|
137
|
+
outline: none;
|
|
138
|
+
min-height: ${props=>props.minHeight || 60}px;
|
|
139
|
+
padding: 0.7rem;
|
|
140
|
+
}
|
|
141
|
+
b, strong {
|
|
142
|
+
font-weight: 600;
|
|
143
|
+
}
|
|
144
|
+
a {
|
|
145
|
+
text-decoration: underline;
|
|
146
|
+
}
|
|
147
|
+
> div[contenteditable=true]:empty:before {
|
|
148
|
+
content: attr(placeholder);
|
|
149
|
+
color: rgba(0,0,0,0.3);
|
|
150
|
+
}
|
|
151
|
+
${props => props.disabled && `
|
|
152
|
+
&[disabled] {
|
|
153
|
+
background: rgba(0, 0, 0, 0.075) !important;
|
|
154
|
+
opacity: 0.75 !important;
|
|
155
|
+
}
|
|
156
|
+
`}
|
|
157
|
+
`
|
|
158
|
+
|
|
159
|
+
const ToolsContainer = styled.div`
|
|
160
|
+
background: ${getColor('lightBluishGray')};
|
|
161
|
+
padding: 0 0.125rem;
|
|
162
|
+
border-top-left-radius: 7px;
|
|
163
|
+
border-top-right-radius: 7px;
|
|
164
|
+
transition: 0.225s;
|
|
165
|
+
max-height: 0;
|
|
166
|
+
pointer-events: none;
|
|
167
|
+
overflow: hidden;
|
|
168
|
+
display: flex;
|
|
169
|
+
align-items: center;
|
|
170
|
+
`
|
|
171
|
+
|
|
172
|
+
const ToolButton = styled(Button)`
|
|
173
|
+
min-height: 2rem;
|
|
174
|
+
min-width: 2rem;
|
|
175
|
+
background: transparent;
|
|
176
|
+
box-shadow: none;
|
|
177
|
+
margin: 0.25rem 0.125rem;
|
|
178
|
+
& ~ & {
|
|
179
|
+
margin-left: 0.125rem;
|
|
180
|
+
}
|
|
181
|
+
.icon {
|
|
182
|
+
font-size: 23px;
|
|
183
|
+
color: ${getColor('gray90')};
|
|
184
|
+
position: absolute;
|
|
185
|
+
top: 50%;
|
|
186
|
+
left: 50%;
|
|
187
|
+
-webkit-transform: translate(-50%,-50%);
|
|
188
|
+
transform: translate(-50%,-50%);
|
|
189
|
+
}
|
|
190
|
+
&:hover, &.activated:hover {
|
|
191
|
+
background: rgba(0,0,0,0.11);
|
|
192
|
+
}
|
|
193
|
+
&.activated {
|
|
194
|
+
background: rgba(0,0,0,0.09);
|
|
195
|
+
}
|
|
196
|
+
&:focus:after {
|
|
197
|
+
content: none;
|
|
198
|
+
}
|
|
199
|
+
`
|
|
200
|
+
|
|
201
|
+
const ToolSeparator = styled.span`
|
|
202
|
+
border-left: 1px solid rgba(0,0,0,0.1);
|
|
203
|
+
margin-left: 0.75rem;
|
|
204
|
+
margin-right: 0.5rem;
|
|
205
|
+
height: 1.5rem;
|
|
206
|
+
`
|
|
207
|
+
|
|
208
|
+
const JS_CLASS = "EDITOR--JS"
|
|
209
|
+
|
|
210
|
+
const getNextRegularCharIndex = (text, offset, direction) => {
|
|
211
|
+
const regex = /[A-Za-z0-9ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏàáâãäåæçèéêëìíîïÐÑÒÓÔÕÖØÙÚÛÜÝÞßðñòóôõöøùúûüýþÿ]/g
|
|
212
|
+
let cursor = offset
|
|
213
|
+
while ((text[cursor + direction] || '').match(regex)) {
|
|
214
|
+
cursor += direction
|
|
215
|
+
}
|
|
216
|
+
return cursor
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const addHttp = (s) => {
|
|
220
|
+
s = s.replace(/^\s/, '')
|
|
221
|
+
if (s.indexOf('http://') === 0 || s.indexOf('https://') === 0) {
|
|
222
|
+
return s
|
|
223
|
+
} else {
|
|
224
|
+
return 'http://' + s
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const getRightMostElement = (node) => {
|
|
229
|
+
if (node.childNodes.length) {
|
|
230
|
+
return getRightMostElement(node.childNodes[node.childNodes.length - 1])
|
|
231
|
+
} else {
|
|
232
|
+
return node
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const setSelectionRange = (input, selectionStart, selectionEnd = 0) => {
|
|
237
|
+
if (!input) return
|
|
238
|
+
let range = document.createRange()
|
|
239
|
+
let sel = window.getSelection()
|
|
240
|
+
range.setStart(input, selectionStart)
|
|
241
|
+
if (selectionEnd) {
|
|
242
|
+
range.setEnd(input, selectionEnd)
|
|
243
|
+
} else {
|
|
244
|
+
range.collapse(true)
|
|
245
|
+
}
|
|
246
|
+
sel.removeAllRanges()
|
|
247
|
+
sel.addRange(range)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const cleanEditor = (editor, acceptedTags) => {
|
|
251
|
+
Array.prototype.slice.call(editor.querySelectorAll('.Apple-interchange-newline')).forEach(el => el.remove())
|
|
252
|
+
Array.prototype.slice.call(editor.querySelectorAll('iframe')).forEach(el => el.remove())
|
|
253
|
+
|
|
254
|
+
const acceptedStyles = acceptedTags.map(t => TAG_TO_STYLE_PROPERTY[t]).filter(_.identity)
|
|
255
|
+
|
|
256
|
+
const newSpace = document.createTextNode('\u00A0')
|
|
257
|
+
if (window.getSelection().rangeCount) {
|
|
258
|
+
const container = window.getSelection().getRangeAt(0).startContainer
|
|
259
|
+
if (container.nodeType === 3) {
|
|
260
|
+
container.parentNode.insertBefore(newSpace, container.nextSibling)
|
|
261
|
+
} else {
|
|
262
|
+
container.appendChild(newSpace)
|
|
263
|
+
}
|
|
264
|
+
if (newSpace.previousSibling && newSpace.previousSibling.textContent === '\u00A0') {
|
|
265
|
+
newSpace.previousSibling.remove()
|
|
266
|
+
}
|
|
267
|
+
setSelectionRange(newSpace, 0)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const clean = (children) => {
|
|
271
|
+
Array.prototype.slice.call(children).forEach(el => {
|
|
272
|
+
try {
|
|
273
|
+
if (el.nodeType === 3)
|
|
274
|
+
return
|
|
275
|
+
if (!el.tagName) {
|
|
276
|
+
el.remove()
|
|
277
|
+
return
|
|
278
|
+
}
|
|
279
|
+
let tagName = el.tagName.toLowerCase()
|
|
280
|
+
const style = getComputedStyle(el)
|
|
281
|
+
const rect = el.getBoundingClientRect()
|
|
282
|
+
const isHidden = style.display === 'none' || rect.width === 0 || rect.height === 0
|
|
283
|
+
if (isHidden && (el.children && !el.children.length) && tagName !== 'br') {
|
|
284
|
+
el.remove()
|
|
285
|
+
} else if (acceptedTags.indexOf(tagName) >= 0) {
|
|
286
|
+
clean(el.childNodes)
|
|
287
|
+
el.removeAttribute('class')
|
|
288
|
+
if (tagName === 'a') {
|
|
289
|
+
el.setAttribute('target', '_blank')
|
|
290
|
+
}
|
|
291
|
+
if (el.getAttribute('style')) {
|
|
292
|
+
const usedStyles = _.toPairs(_.pick(acceptedStyles, el.style)).filter(([k, v]) => v !== "")
|
|
293
|
+
const fixedStyle = _.fromPairs(usedStyles.map(([k, v]) => [k, (FIX_STYLE[k] || _.identity)(v, el)]))
|
|
294
|
+
el.removeAttribute('style')
|
|
295
|
+
Object.assign(el.style, fixedStyle)
|
|
296
|
+
}
|
|
297
|
+
} else if (tagName === 'a') {
|
|
298
|
+
clean(el.childNodes)
|
|
299
|
+
if (el.textContent !== el.href || el.textContent.length < 5)
|
|
300
|
+
el.outerHTML = el.innerHTML
|
|
301
|
+
} else {
|
|
302
|
+
clean(el.childNodes)
|
|
303
|
+
if (el.hasChildNodes()) {
|
|
304
|
+
const newTagName = style.display === 'inline' ? 'span' : 'div'
|
|
305
|
+
const newEl = document.createElement(newTagName)
|
|
306
|
+
Array.prototype.slice.call(el.childNodes).forEach(child => newEl.appendChild(child))
|
|
307
|
+
el.parentNode.replaceChild(newEl, el)
|
|
308
|
+
} else {
|
|
309
|
+
el.remove()
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
} catch (e) {
|
|
313
|
+
if (el && el.remove)
|
|
314
|
+
el.remove()
|
|
315
|
+
}
|
|
316
|
+
})
|
|
317
|
+
}
|
|
318
|
+
clean(editor.childNodes)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const insertToken = (node, offset) => {
|
|
322
|
+
const parent = node.parentNode
|
|
323
|
+
const token = document.createElement("span")
|
|
324
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
325
|
+
const splitted = node.splitText(offset)
|
|
326
|
+
parent.insertBefore(token, splitted)
|
|
327
|
+
} else {
|
|
328
|
+
parent.insertBefore(token, node)
|
|
329
|
+
}
|
|
330
|
+
return token
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/*
|
|
334
|
+
* pagely reformat
|
|
335
|
+
.replace(/^[a-z]/, function (e) {
|
|
336
|
+
return e.toUpperCase()
|
|
337
|
+
})
|
|
338
|
+
* */
|
|
339
|
+
|
|
340
|
+
const parseAndExecCommand = (c) => {
|
|
341
|
+
if (c === "strikethrough" || c === "underline") {
|
|
342
|
+
document.execCommand('styleWithCSS', false, false)
|
|
343
|
+
} else {
|
|
344
|
+
document.execCommand('styleWithCSS', false, true)
|
|
345
|
+
}
|
|
346
|
+
if (c.indexOf('fontSize') === 0) {
|
|
347
|
+
const direction = c.indexOf('Up') > 0 ? 1 : -1
|
|
348
|
+
const newValue = Math.min(7, Math.max(1, parseInt(document.queryCommandValue('fontSize') || 3) + direction))
|
|
349
|
+
document.execCommand('fontSize', false, newValue)
|
|
350
|
+
} else {
|
|
351
|
+
const sanitizedCommand = (c.indexOf("List") >= 0 && document.queryCommandState(c)) ? 'outdent' : c // FIX CHROME HANGS when list is called and already active
|
|
352
|
+
|
|
353
|
+
document.execCommand(sanitizedCommand)
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export const RichText = ({path, inputRef, label, autoHideToolbar, autoFocus, disabled, minHeight, placeholder, className, formatTools = ['bold', 'italic', 'underline', 'strikethrough', 'separator', 'link'], reformatText = _.identity, mandatory, descText, helpText, linkColor, tabIndex = 1, value, setValue,raw, locked}) => {
|
|
358
|
+
|
|
359
|
+
const linkPopup = usePopupToggle()
|
|
360
|
+
|
|
361
|
+
const input = useEnsureRef(inputRef)
|
|
362
|
+
const activeLinkNode = useRef()
|
|
363
|
+
const container = useRef()
|
|
364
|
+
const linkJustCreated = useRefState()
|
|
365
|
+
|
|
366
|
+
// auto-hide needs
|
|
367
|
+
const startSelectionToken = useRefState()
|
|
368
|
+
const endSelectionToken = useRefState()
|
|
369
|
+
const commands = useRefState([])
|
|
370
|
+
const containerFocused = useToggle()
|
|
371
|
+
|
|
372
|
+
const needsNormalize = useRefState()
|
|
373
|
+
const alreadyCancelled = useRefState()
|
|
374
|
+
|
|
375
|
+
// const [formValue, setFormValue] = useInput({path})
|
|
376
|
+
const [formValue, _setFormValue, {hasErrors}] = useInput({path,value,setValue},raw)
|
|
377
|
+
|
|
378
|
+
// const tempValue = useConstant(formValue)
|
|
379
|
+
const tempValue = useRef(formValue)
|
|
380
|
+
const setFormValue = (v) => {
|
|
381
|
+
tempValue.current = v
|
|
382
|
+
_setFormValue(v)
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
useEffect(() => {
|
|
386
|
+
if((value === formValue) && (formValue !== tempValue)){
|
|
387
|
+
tempValue.current = formValue
|
|
388
|
+
}
|
|
389
|
+
},[path])
|
|
390
|
+
|
|
391
|
+
const [activatedTools, setActivatedTools] = useState({})
|
|
392
|
+
|
|
393
|
+
const validElements = useLazyConstant(() => ['br', 'div', 'p', 'span'].concat(_.flatten(formatTools.map(tool => FORMAT_TOOLS[tool] || []))))
|
|
394
|
+
|
|
395
|
+
useEffect(() => {
|
|
396
|
+
cleanEditor(input.current, validElements)
|
|
397
|
+
}, [])
|
|
398
|
+
|
|
399
|
+
const focus = () => input.current && input.current.focus()
|
|
400
|
+
|
|
401
|
+
// Foco inicial opcional (ex.: abrir a dialog já no campo de descrição). Em
|
|
402
|
+
// rAF pra rodar depois que a dialog termina de montar e não perder o foco.
|
|
403
|
+
useEffect(() => {
|
|
404
|
+
if (autoFocus) {
|
|
405
|
+
requestAnimationFrame(focus)
|
|
406
|
+
}
|
|
407
|
+
}, [])
|
|
408
|
+
|
|
409
|
+
useEffect(() => {
|
|
410
|
+
if (needsNormalize.get()) {
|
|
411
|
+
input.current.normalize()
|
|
412
|
+
needsNormalize.set(false)
|
|
413
|
+
}
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
const triggerScrollForPanel = (d) => {
|
|
417
|
+
requestAnimationFrame((time) => {
|
|
418
|
+
if (time > d + 250) return
|
|
419
|
+
window.dispatchEvent(new Event('scroll'))
|
|
420
|
+
triggerScrollForPanel(d)
|
|
421
|
+
}
|
|
422
|
+
)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const containerFocus = () => {
|
|
426
|
+
containerFocused.openWith(2)
|
|
427
|
+
triggerScrollForPanel(Date.now())
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const containerBlur = (e) => {
|
|
431
|
+
if (containerFocused.isOpen !== 1) {
|
|
432
|
+
containerFocused.close()
|
|
433
|
+
triggerScrollForPanel(Date.now())
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
const onBlur = (e) => {
|
|
439
|
+
const newValue = input.current.textContent.length === 0 ? '' : input.current.innerHTML
|
|
440
|
+
// Só reescreve se o conteúdo mudou de fato: focar/desfocar sem editar não pode
|
|
441
|
+
// marcar o form como alterado (senão dispara a confirmação de descarte à toa).
|
|
442
|
+
// textContent vazio equivale a valor ausente/'' do form.
|
|
443
|
+
if (newValue !== (formValue || '')) {
|
|
444
|
+
setFormValue(newValue)
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const keydownHandler = (e) => {
|
|
449
|
+
if (containerFocused.isOpen === 2){
|
|
450
|
+
if (formatTools.indexOf('link') < 0) {
|
|
451
|
+
return
|
|
452
|
+
}
|
|
453
|
+
if((e.key === "k" && e.ctrlKey) || (e.key === "k" && e.metaKey)){
|
|
454
|
+
e.preventDefault();
|
|
455
|
+
execCommand(null,'link')
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
useEffect(() => {
|
|
461
|
+
document.addEventListener('keydown',keydownHandler);
|
|
462
|
+
return () => document.removeEventListener('keydown',keydownHandler);
|
|
463
|
+
}, [containerFocused.isOpen])
|
|
464
|
+
|
|
465
|
+
const execCommand = (e, command) => {
|
|
466
|
+
if (command === 'link') {
|
|
467
|
+
let nodeEl
|
|
468
|
+
if (!isSelectionInsideRichTextEditor()) {
|
|
469
|
+
let anchor = document.createElement('a')
|
|
470
|
+
anchor.innerHTML = '​'
|
|
471
|
+
input.current.appendChild(anchor)
|
|
472
|
+
nodeEl = anchor
|
|
473
|
+
} else {
|
|
474
|
+
const range = window.getSelection().getRangeAt(0)
|
|
475
|
+
const selectedNode = range.startContainer
|
|
476
|
+
if (range.collapsed) {
|
|
477
|
+
const start = getNextRegularCharIndex(selectedNode.textContent, range.startOffset, -1)
|
|
478
|
+
const end = getNextRegularCharIndex(selectedNode.textContent, range.startOffset - 1, 1)
|
|
479
|
+
if (start < range.startOffset && end > range.startOffset - 1) {
|
|
480
|
+
range.setStart(selectedNode, start)
|
|
481
|
+
range.setEnd(selectedNode, end + 1)
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
const selectedEl = selectedNode.nodeType === Node.TEXT_NODE ? selectedNode.parentNode : selectedNode
|
|
485
|
+
const parent = selectedEl.closest('a,.'+JS_CLASS)
|
|
486
|
+
if (parent && parent.matches('a')) {
|
|
487
|
+
nodeEl = parent
|
|
488
|
+
} else {
|
|
489
|
+
let anchor = document.createElement('a')
|
|
490
|
+
anchor.innerHTML = '​'
|
|
491
|
+
if (range.collapsed) {
|
|
492
|
+
range.insertNode(anchor)
|
|
493
|
+
} else {
|
|
494
|
+
if (range.commonAncestorContainer === input.current) {
|
|
495
|
+
range.setStart(range.endContainer,0)
|
|
496
|
+
}
|
|
497
|
+
document.execCommand('createLink', false, '_')
|
|
498
|
+
anchor = window.getSelection().anchorNode
|
|
499
|
+
}
|
|
500
|
+
const parent = range.commonAncestorContainer.nodeType === 3 ? range.commonAncestorContainer.parentNode : range.commonAncestorContainer
|
|
501
|
+
const links = parent.querySelectorAll("a[href='_']")
|
|
502
|
+
Array.from(links).slice(0,-1).forEach(removeLinkFromNode)
|
|
503
|
+
nodeEl = _.last(links)
|
|
504
|
+
nodeEl.setAttribute('href', ' ')
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
tempValue.current = input.current.innerHTML
|
|
508
|
+
openDialog(nodeEl)
|
|
509
|
+
} else {
|
|
510
|
+
if (!isSelectionInsideRichTextEditor()) {
|
|
511
|
+
input.current.focus()
|
|
512
|
+
const last = document.createTreeWalker(input.current, NodeFilter.SHOW_TEXT).lastChild()
|
|
513
|
+
const range = document.createRange()
|
|
514
|
+
range.selectNodeContents(last || input.current)
|
|
515
|
+
range.collapse(false)
|
|
516
|
+
const sel = window.getSelection()
|
|
517
|
+
sel.removeAllRanges()
|
|
518
|
+
sel.addRange(range)
|
|
519
|
+
}
|
|
520
|
+
document.execCommand(command)
|
|
521
|
+
setFormValue(input.current.innerHTML)
|
|
522
|
+
toggleButtonsState()
|
|
523
|
+
focus()
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const isSelectionInsideRichTextEditor = () => {
|
|
528
|
+
const range = window.getSelection().getRangeAt(0)
|
|
529
|
+
if (!range) return false
|
|
530
|
+
const el = range.startContainer
|
|
531
|
+
if (!el) return false
|
|
532
|
+
const node = el.closest ? el : (el.parentNode || document.body)
|
|
533
|
+
return node.closest('.'+JS_CLASS) === input.current
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
const saveRichTextLink = ({linkUrl, linkText}) => {
|
|
538
|
+
const currentActiveLinkNode = activeLinkNode.current
|
|
539
|
+
currentActiveLinkNode.setAttribute('href', addHttp(linkUrl))
|
|
540
|
+
currentActiveLinkNode.setAttribute('target', '_blank')
|
|
541
|
+
if (currentActiveLinkNode.textContent !== linkText) {
|
|
542
|
+
currentActiveLinkNode.innerText = linkText
|
|
543
|
+
}
|
|
544
|
+
linkPopup.close()
|
|
545
|
+
setSelectionRange(currentActiveLinkNode.lastChild, currentActiveLinkNode.textContent.length, 0)
|
|
546
|
+
focus()
|
|
547
|
+
setFormValue(input.current.innerHTML)
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const cancelRichTextLink = (e) => {
|
|
551
|
+
if (alreadyCancelled.get()) return
|
|
552
|
+
const currentActiveLinkNode = activeLinkNode.current
|
|
553
|
+
const span = document.createElement('span')
|
|
554
|
+
if (currentActiveLinkNode.nextSibling)
|
|
555
|
+
(currentActiveLinkNode.parentNode).insertBefore(span, currentActiveLinkNode.nextSibling)
|
|
556
|
+
else
|
|
557
|
+
(currentActiveLinkNode.parentNode).append(span)
|
|
558
|
+
setSelectionRange(span, 0, 0)
|
|
559
|
+
focus()
|
|
560
|
+
if (currentActiveLinkNode.getAttribute('href') === ' ') {
|
|
561
|
+
removeLink(e)
|
|
562
|
+
}
|
|
563
|
+
alreadyCancelled.set(true)
|
|
564
|
+
span.remove()
|
|
565
|
+
if (input.current) {
|
|
566
|
+
input.current.normalize()
|
|
567
|
+
} else {
|
|
568
|
+
needsNormalize.set(true)
|
|
569
|
+
}
|
|
570
|
+
linkPopup.close()
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const handleKeyUp = (e) => {
|
|
574
|
+
toggleButtonsState()
|
|
575
|
+
setFormValue(reformatText(input.current.innerHTML))
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const handleKeyDown = (e) => {
|
|
579
|
+
/*if (linkJustCreated.current && e.which === 8) {
|
|
580
|
+
linkJustCreated.current.outerHTML = linkJustCreated.current.innerHTML
|
|
581
|
+
input.current.normalize()
|
|
582
|
+
return
|
|
583
|
+
}
|
|
584
|
+
commands.set([])
|
|
585
|
+
linkJustCreated.set(null)
|
|
586
|
+
const selectedRange = window.getSelection().getRangeAt(0)
|
|
587
|
+
if (!selectedRange) return
|
|
588
|
+
const possibleTextNode = selectedRange.startContainer
|
|
589
|
+
const node = possibleTextNode.nodeType === Node.TEXT_NODE ? possibleTextNode.parentNode : possibleTextNode
|
|
590
|
+
const parent = node.closest('a,.'+JS_CLASS)
|
|
591
|
+
if ((e.key === 'Enter' || e.which === 32) && parent && parent.matches('.'+JS_CLASS)) {
|
|
592
|
+
const range = document.createRange()
|
|
593
|
+
range.setEnd(selectedRange.startContainer, selectedRange.startOffset)
|
|
594
|
+
range.setStart(selectedRange.startContainer, 0)
|
|
595
|
+
traverseAndAddLink(range)
|
|
596
|
+
}*/
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const traverseAndAddLink = (range) => {
|
|
600
|
+
const match = range.toString().match(/[\s\t\n\r]([^\s\t\n\r]*)$/)
|
|
601
|
+
if (match) {
|
|
602
|
+
if ((/^(https?:\/\/|www\.)/).test(match[1]) && linkRegex.test(match[1])) {
|
|
603
|
+
range.setStart(range.startContainer, (match).index + 1);
|
|
604
|
+
(range.endContainer).splitText(range.endContainer.textContent.length - 1)
|
|
605
|
+
const newNode = document.createElement('a')
|
|
606
|
+
newNode.href = match[1]
|
|
607
|
+
newNode.appendChild(range.extractContents())
|
|
608
|
+
range.insertNode(newNode)
|
|
609
|
+
window.getSelection().getRangeAt(0).setEndAfter(newNode)
|
|
610
|
+
window.getSelection().getRangeAt(0).setStartAfter(newNode)
|
|
611
|
+
linkJustCreated.set(newNode)
|
|
612
|
+
}
|
|
613
|
+
} else {
|
|
614
|
+
const previous = getPreviousElement(range.startContainer)
|
|
615
|
+
if (previous) {
|
|
616
|
+
range.setStart(previous, 0)
|
|
617
|
+
traverseAndAddLink(range)
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const getPreviousElement = (node) => {
|
|
623
|
+
if (node.previousSibling) {
|
|
624
|
+
const rightMost = getRightMostElement(node.previousSibling)
|
|
625
|
+
if (rightMost.nodeType === Node.TEXT_NODE) {
|
|
626
|
+
return rightMost
|
|
627
|
+
} else {
|
|
628
|
+
return getPreviousElement(rightMost)
|
|
629
|
+
}
|
|
630
|
+
} else if (node.parentNode && node !== input.current) {
|
|
631
|
+
return getPreviousElement(node.parentNode)
|
|
632
|
+
} else {
|
|
633
|
+
return null
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const handlePasteDrop = (e) => setTimeout(() => pasted(e), 1)
|
|
638
|
+
|
|
639
|
+
const handleClick = (e) => {
|
|
640
|
+
toggleButtonsState()
|
|
641
|
+
const el = e.target.closest('a,.'+JS_CLASS)
|
|
642
|
+
if (el && el.matches('a') && el.getAttribute('href')) {
|
|
643
|
+
openDialog(el)
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const openDialog = (el) => {
|
|
648
|
+
activeLinkNode.current = el
|
|
649
|
+
alreadyCancelled.set(false)
|
|
650
|
+
linkPopup.openWith({linkText: el.textContent, linkUrl: (el.getAttribute('href') || '').trim()})
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
const removeLinkFromNode = (node) => {
|
|
655
|
+
node.outerHTML = node.innerHTML === '\u200b' ? '' : node.innerHTML
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const removeLink = (e) => {
|
|
659
|
+
if (e) e.preventDefault()
|
|
660
|
+
alreadyCancelled.set(true)
|
|
661
|
+
removeLinkFromNode(activeLinkNode.current)
|
|
662
|
+
linkPopup.close()
|
|
663
|
+
if (input.current) {
|
|
664
|
+
setFormValue(input.current.innerHTML)
|
|
665
|
+
input.current.normalize()
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const pasted = (e) => {
|
|
670
|
+
cleanEditor(input.current, validElements)
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
const toggleButtonsState = () => {
|
|
674
|
+
return setActivatedTools(_.zipObject(formatTools,formatTools.map((t)=>window.document.queryCommandState(t))))
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const toolButton = (tool) => <ToolButton
|
|
678
|
+
key={tool}
|
|
679
|
+
tabIndex={-1}
|
|
680
|
+
onClick={(e) => execCommand(e, tool)}
|
|
681
|
+
className={'button exec-command rich-text-button only-icon ' + (activatedTools[tool] ? 'activated' : '')}
|
|
682
|
+
type="button"
|
|
683
|
+
>
|
|
684
|
+
<span className={'icon icon-' + tool}/>
|
|
685
|
+
</ToolButton>
|
|
686
|
+
|
|
687
|
+
const richTextElement = <RichTextContainer
|
|
688
|
+
linkColor={linkColor}
|
|
689
|
+
ref={container}
|
|
690
|
+
onFocus={containerFocus}
|
|
691
|
+
onBlur={containerBlur}
|
|
692
|
+
focus={containerFocused.isOpen}
|
|
693
|
+
showToolbar={!disabled & !autoHideToolbar || containerFocused.isOpen}
|
|
694
|
+
className={className}
|
|
695
|
+
>
|
|
696
|
+
<ToolsContainer className="rich-text-tools">
|
|
697
|
+
{formatTools.map((t, i) => t === 'separator' ? <ToolSeparator className="separator"
|
|
698
|
+
key={"separator-" + i}/> : toolButton(t))}
|
|
699
|
+
</ToolsContainer>
|
|
700
|
+
<RichTextInput>
|
|
701
|
+
<ContentEditable
|
|
702
|
+
className={JS_CLASS}
|
|
703
|
+
innerRef={input}
|
|
704
|
+
html={tempValue.current || ""}
|
|
705
|
+
onBlur={onBlur}
|
|
706
|
+
onClick={handleClick}
|
|
707
|
+
onKeyUp={handleKeyUp}
|
|
708
|
+
onKeyDown={handleKeyDown}
|
|
709
|
+
onPaste={handlePasteDrop}
|
|
710
|
+
onDrop={handlePasteDrop}
|
|
711
|
+
tabIndex={tabIndex}
|
|
712
|
+
disabled={disabled}
|
|
713
|
+
minHeight={minHeight}
|
|
714
|
+
placeholder={placeholder}
|
|
715
|
+
/>
|
|
716
|
+
</RichTextInput>
|
|
717
|
+
{locked && <InputIcon icon="lock"/>}
|
|
718
|
+
{
|
|
719
|
+
linkPopup.isOpen &&
|
|
720
|
+
<EditLinkPopup
|
|
721
|
+
input={input}
|
|
722
|
+
activeLinkNode={activeLinkNode}
|
|
723
|
+
cancel={cancelRichTextLink}
|
|
724
|
+
values={linkPopup.isOpen}
|
|
725
|
+
removeLink={removeLink}
|
|
726
|
+
saveLink={saveRichTextLink}
|
|
727
|
+
/>
|
|
728
|
+
}
|
|
729
|
+
</RichTextContainer>
|
|
730
|
+
return (label
|
|
731
|
+
? <Field labelClick={focus} descText={descText} helpText={helpText} label={label} mandatory={mandatory}>
|
|
732
|
+
{richTextElement}
|
|
733
|
+
</Field>
|
|
734
|
+
: richTextElement
|
|
735
|
+
)
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
const EditLinkPopup = ({cancel, removeLink, activeLinkNode, input, saveLink, values}) => {
|
|
739
|
+
const linkPopup = useRef()
|
|
740
|
+
const formRef = useRef()
|
|
741
|
+
const linkTextRef = useRef()
|
|
742
|
+
const linkUrlRef = useRef()
|
|
743
|
+
const LABELS = useContext(StartlibsContext).labels.RichText
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
const preValidation = (formValues) => {
|
|
747
|
+
let errors
|
|
748
|
+
if (!formValues.linkText) {
|
|
749
|
+
errors = {'linkText': [LABELS.linkTextError]}
|
|
750
|
+
}
|
|
751
|
+
if (!formValues.linkUrl || formValues.linkUrl === ' ' || !linkRegex.test(formValues.linkUrl.trim())) {
|
|
752
|
+
errors = {...errors, 'linkUrl': [LABELS.linkUrlError]}
|
|
753
|
+
}
|
|
754
|
+
return errors
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
const checkEscOrEnter = (e) => {
|
|
758
|
+
if (e.keyCode === 27) {
|
|
759
|
+
e.stopPropagation()
|
|
760
|
+
cancel(e)
|
|
761
|
+
} else if (e.keyCode === 13) {
|
|
762
|
+
e.preventDefault()
|
|
763
|
+
e.stopPropagation()
|
|
764
|
+
formRef.current.submitForm()
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
useEffect(() => {
|
|
769
|
+
const currentActiveLinkNode = activeLinkNode.current
|
|
770
|
+
const currentLinkPopup = linkPopup.current
|
|
771
|
+
const offsetLeft = currentActiveLinkNode.getClientRects().length > 1 ? input.offsetLeft : currentActiveLinkNode.offsetLeft
|
|
772
|
+
const left = ((currentActiveLinkNode.offsetWidth) / 2) - (currentLinkPopup.offsetWidth / 2) + offsetLeft
|
|
773
|
+
const top = (currentActiveLinkNode.offsetHeight) + currentActiveLinkNode.offsetTop
|
|
774
|
+
Object.assign(currentLinkPopup.style, {
|
|
775
|
+
'position': 'absolute',
|
|
776
|
+
'left': left + 'px',
|
|
777
|
+
'top': top + 'px'
|
|
778
|
+
})
|
|
779
|
+
if (values.linkText) {
|
|
780
|
+
linkUrlRef.current.focus()
|
|
781
|
+
} else {
|
|
782
|
+
linkTextRef.current.focus()
|
|
783
|
+
}
|
|
784
|
+
return cancel
|
|
785
|
+
}, [])
|
|
786
|
+
|
|
787
|
+
return <WithForm skipDom ref={formRef} action={saveLink} values={values} preValidation={preValidation}>{form =>
|
|
788
|
+
<div onClick={stopPropagation()} ref={linkPopup} onKeyDown={checkEscOrEnter}
|
|
789
|
+
className="popup rich-text-link-editor ignore-link" contentEditable={false}>
|
|
790
|
+
<TextInput
|
|
791
|
+
ref={linkTextRef}
|
|
792
|
+
mandatory
|
|
793
|
+
path="linkText"
|
|
794
|
+
label={LABELS.popupLinkText}
|
|
795
|
+
/>
|
|
796
|
+
<TextInput
|
|
797
|
+
ref={linkUrlRef}
|
|
798
|
+
mandatory
|
|
799
|
+
path="linkUrl"
|
|
800
|
+
label={LABELS.popupLinkUrl}
|
|
801
|
+
/>
|
|
802
|
+
<div className="popup-footer">
|
|
803
|
+
<Button className="remove-link" icon="remove" onClick={removeLink}>
|
|
804
|
+
{LABELS.popupRemove}
|
|
805
|
+
</Button>
|
|
806
|
+
<Button onClick={cancel} tabIndex={0}>{LABELS.popupCancel}</Button>
|
|
807
|
+
<Button highlight onClick={form.willSubmitForm}>{LABELS.popupConfirm}</Button>
|
|
808
|
+
</div>
|
|
809
|
+
<Errors/>
|
|
810
|
+
</div>
|
|
811
|
+
}</WithForm>
|
|
812
|
+
}
|