playbook_ui 15.7.0.pre.alpha.play2607setheightofeditor13370 → 15.7.0.pre.alpha.play258013248
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.
- checksums.yaml +4 -4
- data/app/pb_kits/playbook/pb_rich_text_editor/TipTap/Toolbar.tsx +1 -1
- data/app/pb_kits/playbook/pb_rich_text_editor/_rich_text_editor.tsx +3 -8
- data/app/pb_kits/playbook/pb_rich_text_editor/_tiptap_styles.scss +0 -53
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/example.yml +0 -2
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/index.js +1 -3
- data/app/pb_kits/playbook/pb_text_input/_text_input.tsx +3 -41
- data/app/pb_kits/playbook/pb_text_input/docs/example.yml +0 -2
- data/app/pb_kits/playbook/pb_text_input/docs/index.js +0 -1
- data/app/pb_kits/playbook/pb_text_input/index.js +8 -49
- data/app/pb_kits/playbook/pb_text_input/text_input.rb +1 -5
- data/app/pb_kits/playbook/pb_text_input/text_input.test.js +0 -53
- data/app/pb_kits/playbook/pb_textarea/_textarea.tsx +2 -38
- data/app/pb_kits/playbook/pb_textarea/docs/example.yml +0 -2
- data/app/pb_kits/playbook/pb_textarea/docs/index.js +0 -1
- data/app/pb_kits/playbook/pb_textarea/index.ts +5 -62
- data/app/pb_kits/playbook/pb_textarea/textarea.html.erb +0 -1
- data/app/pb_kits/playbook/pb_textarea/textarea.rb +0 -8
- data/app/pb_kits/playbook/pb_textarea/textarea.test.js +2 -57
- data/dist/chunks/{_typeahead-D0GNUBXn.js → _typeahead-Ckz1ce-2.js} +2 -2
- data/dist/chunks/{lib-DxCgrqqG.js → lib-DxDBrGZX.js} +1 -1
- data/dist/chunks/vendor.js +3 -3
- data/dist/playbook-rails-react-bindings.js +1 -1
- data/dist/playbook-rails.js +1 -1
- data/dist/playbook.css +1 -1
- data/lib/playbook/forms/builder/form_field_builder.rb +0 -2
- data/lib/playbook/version.rb +1 -1
- metadata +4 -15
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_height.jsx +0 -71
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_height.md +0 -1
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_min_heigh.md +0 -1
- data/app/pb_kits/playbook/pb_rich_text_editor/docs/_rich_text_editor_advanced_min_height.jsx +0 -71
- data/app/pb_kits/playbook/pb_text_input/docs/_text_input_emoji_mask.html.erb +0 -7
- data/app/pb_kits/playbook/pb_text_input/docs/_text_input_emoji_mask.jsx +0 -24
- data/app/pb_kits/playbook/pb_text_input/docs/_text_input_emoji_mask.md +0 -2
- data/app/pb_kits/playbook/pb_textarea/docs/_textarea_emoji_mask.html.erb +0 -5
- data/app/pb_kits/playbook/pb_textarea/docs/_textarea_emoji_mask.jsx +0 -24
- data/app/pb_kits/playbook/pb_textarea/docs/_textarea_emoji_mask.md +0 -1
- data/app/pb_kits/playbook/utilities/emojiMask.ts +0 -42
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a57b87c794ebb9c526328411d074cf50c56b92e554eea530bc34ab79f009b9d7
|
|
4
|
+
data.tar.gz: 14aa366eb5f3af416b537e4d51cc70fcec656cfb5b7be7fa754f0fdf45e54cf0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6b3ec5d9f16652c8cd619e3410315d7ce5559cebf78811127ebd624d33bc7987c0846fcc6c26b8daada7438c9896894b0fb4abb81816eeb98a664d725778da0d
|
|
7
|
+
data.tar.gz: d24a169b7c3712bdeb27dff6a886128a0c4c5b94f4d91a6d9beb45eaa33d9f459a262d9b80582a0e3f9d16f38f0db76586a9a26de71dfb5bf286766aeb6931bf
|
|
@@ -4,7 +4,7 @@ import Flex from "../../pb_flex/_flex";
|
|
|
4
4
|
import FlexItem from "../../pb_flex/_flex_item";
|
|
5
5
|
import SectionSeparator from "../../pb_section_separator/_section_separator";
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import EditorButton from "./EditorButton";
|
|
8
8
|
import ToolbarDropdown from "./ToolbarDropdown";
|
|
9
9
|
import ToolbarNodes from "./ToolbarNodes";
|
|
10
10
|
import { ToolbarTypes } from "./EditorTypes";
|
|
@@ -44,8 +44,6 @@ type RichTextEditorProps = {
|
|
|
44
44
|
name?: string,
|
|
45
45
|
onChange: (html: string, text: string) => void,
|
|
46
46
|
placeholder?: string,
|
|
47
|
-
inputHeight?: "sm" | "md" | "lg",
|
|
48
|
-
inputMinHeight?: "sm" | "md" | "lg",
|
|
49
47
|
simple?: boolean,
|
|
50
48
|
sticky?: boolean,
|
|
51
49
|
template: string,
|
|
@@ -70,8 +68,6 @@ const RichTextEditor = (props: RichTextEditorProps): React.ReactElement => {
|
|
|
70
68
|
name,
|
|
71
69
|
onChange = noop,
|
|
72
70
|
placeholder,
|
|
73
|
-
inputHeight,
|
|
74
|
-
inputMinHeight,
|
|
75
71
|
simple = false,
|
|
76
72
|
sticky = false,
|
|
77
73
|
template = '',
|
|
@@ -218,10 +214,9 @@ const RichTextEditor = (props: RichTextEditorProps): React.ReactElement => {
|
|
|
218
214
|
{
|
|
219
215
|
advancedEditor ? (
|
|
220
216
|
<div
|
|
221
|
-
className={classnames(
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
)}
|
|
217
|
+
className={classnames("pb_rich_text_editor_advanced_container", {
|
|
218
|
+
["toolbar-active"]: shouldShowToolbar,
|
|
219
|
+
})}
|
|
225
220
|
>
|
|
226
221
|
{shouldShowToolbar && (
|
|
227
222
|
<EditorToolbar editor={advancedEditor}
|
|
@@ -205,59 +205,6 @@
|
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
}
|
|
208
|
-
|
|
209
|
-
.pb_rich_text_editor_advanced_container.input_height_sm {
|
|
210
|
-
div {
|
|
211
|
-
.tiptap.ProseMirror {
|
|
212
|
-
height: 131px;
|
|
213
|
-
overflow: auto;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
.pb_rich_text_editor_advanced_container.input_height_md {
|
|
219
|
-
div {
|
|
220
|
-
.tiptap.ProseMirror {
|
|
221
|
-
height: 180px;
|
|
222
|
-
overflow: auto;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
.pb_rich_text_editor_advanced_container.input_height_lg {
|
|
228
|
-
div {
|
|
229
|
-
.tiptap.ProseMirror {
|
|
230
|
-
height: 230px;
|
|
231
|
-
overflow: auto;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
.pb_rich_text_editor_advanced_container.input_min_height_sm {
|
|
237
|
-
div {
|
|
238
|
-
.tiptap.ProseMirror {
|
|
239
|
-
min-height: 131px;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
.pb_rich_text_editor_advanced_container.input_min_height_md {
|
|
245
|
-
div {
|
|
246
|
-
.tiptap.ProseMirror {
|
|
247
|
-
min-height: 180px;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
.pb_rich_text_editor_advanced_container.input_min_height_lg {
|
|
253
|
-
div {
|
|
254
|
-
.tiptap.ProseMirror {
|
|
255
|
-
min-height: 230px;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
|
|
261
208
|
.tiptap-content {
|
|
262
209
|
@include preview_tiptap_first_child;
|
|
263
210
|
a {
|
|
@@ -29,7 +29,5 @@ examples:
|
|
|
29
29
|
# - rich_text_editor_toolbar_bottom: Toolbar Bottom
|
|
30
30
|
- rich_text_editor_inline: Inline
|
|
31
31
|
- rich_text_editor_advanced_inline: Advanced (Inline)
|
|
32
|
-
- rich_text_editor_advanced_height: Advanced Height
|
|
33
|
-
- rich_text_editor_advanced_min_height: Advanced Min Height
|
|
34
32
|
- rich_text_editor_preview: Preview
|
|
35
33
|
- rich_text_editor_advanced_preview: Advanced Preview
|
|
@@ -16,6 +16,4 @@ export { default as RichTextEditorAdvancedFocus } from './_rich_text_editor_adva
|
|
|
16
16
|
export { default as RichTextEditorAdvancedTemplates } from './_rich_text_editor_advanced_templates.jsx'
|
|
17
17
|
export { default as RichTextEditorAdvancedAttributes } from './_rich_text_editor_advanced_attributes.jsx'
|
|
18
18
|
export { default as RichTextEditorAdvancedSticky } from './_rich_text_editor_advanced_sticky.jsx'
|
|
19
|
-
export { default as RichTextEditorAdvancedInline } from './_rich_text_editor_advanced_inline.jsx'
|
|
20
|
-
export { default as RichTextEditorAdvancedHeight } from './_rich_text_editor_advanced_height.jsx'
|
|
21
|
-
export { default as RichTextEditorAdvancedMinHeight } from './_rich_text_editor_advanced_min_height.jsx'
|
|
19
|
+
export { default as RichTextEditorAdvancedInline } from './_rich_text_editor_advanced_inline.jsx'
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { forwardRef, ChangeEvent
|
|
1
|
+
import React, { forwardRef, ChangeEvent } from 'react'
|
|
2
2
|
import classnames from 'classnames'
|
|
3
3
|
|
|
4
4
|
import { globalProps, GlobalProps, domSafeProps } from '../utilities/globalProps'
|
|
@@ -12,7 +12,6 @@ import Icon from '../pb_icon/_icon'
|
|
|
12
12
|
import colors from '../tokens/exports/_colors.module.scss'
|
|
13
13
|
|
|
14
14
|
import { INPUTMASKS } from './inputMask'
|
|
15
|
-
import { stripEmojisForPaste, applyEmojiMask } from '../utilities/emojiMask'
|
|
16
15
|
|
|
17
16
|
type TextInputProps = {
|
|
18
17
|
aria?: { [key: string]: string },
|
|
@@ -20,7 +19,6 @@ type TextInputProps = {
|
|
|
20
19
|
data?: { [key: string]: string },
|
|
21
20
|
dark?: boolean,
|
|
22
21
|
disabled?: boolean,
|
|
23
|
-
emojiMask?: boolean,
|
|
24
22
|
error?: string,
|
|
25
23
|
htmlOptions?: {[key: string]: string | number | boolean | (() => void)},
|
|
26
24
|
id?: string,
|
|
@@ -51,7 +49,6 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement>
|
|
|
51
49
|
dark = false,
|
|
52
50
|
data = {},
|
|
53
51
|
disabled,
|
|
54
|
-
emojiMask = false,
|
|
55
52
|
error,
|
|
56
53
|
htmlOptions = {},
|
|
57
54
|
id,
|
|
@@ -105,11 +102,6 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement>
|
|
|
105
102
|
const isMaskedInput = mask && mask in INPUTMASKS
|
|
106
103
|
|
|
107
104
|
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
|
108
|
-
// Apply emoji mask if enabled using centralized helper
|
|
109
|
-
if (emojiMask) {
|
|
110
|
-
applyEmojiMask(e.target)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
105
|
if (isMaskedInput) {
|
|
114
106
|
const inputValue = e.target.value
|
|
115
107
|
|
|
@@ -142,29 +134,6 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement>
|
|
|
142
134
|
}
|
|
143
135
|
}
|
|
144
136
|
|
|
145
|
-
// Handle paste event for emoji mask - updates input value, cursor position, and calls onChange
|
|
146
|
-
const handlePaste = (e: ClipboardEvent<HTMLInputElement>) => {
|
|
147
|
-
if (emojiMask) {
|
|
148
|
-
const pastedText = e.clipboardData.getData('text')
|
|
149
|
-
const filteredText = stripEmojisForPaste(pastedText)
|
|
150
|
-
|
|
151
|
-
if (pastedText !== filteredText) {
|
|
152
|
-
e.preventDefault()
|
|
153
|
-
const input = e.currentTarget
|
|
154
|
-
const start = input.selectionStart || 0
|
|
155
|
-
const end = input.selectionEnd || 0
|
|
156
|
-
const currentValue = input.value
|
|
157
|
-
const newValue = currentValue.slice(0, start) + filteredText + currentValue.slice(end)
|
|
158
|
-
const newCursorPosition = start + filteredText.length
|
|
159
|
-
|
|
160
|
-
input.value = newValue
|
|
161
|
-
input.selectionStart = input.selectionEnd = newCursorPosition
|
|
162
|
-
|
|
163
|
-
onChange({ ...e, target: input, currentTarget: input } as unknown as ChangeEvent<HTMLInputElement>)
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
137
|
const childInput = children ? children.type === "input" : undefined
|
|
169
138
|
|
|
170
139
|
let formattedValue;
|
|
@@ -176,16 +145,10 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement>
|
|
|
176
145
|
|
|
177
146
|
const errorId = error ? `${id}-error` : undefined
|
|
178
147
|
|
|
179
|
-
// Set custom handler between emoji mask and input mask
|
|
180
|
-
const shouldUseCustomHandler = isMaskedInput || emojiMask
|
|
181
|
-
|
|
182
|
-
// Filter out emojiMask from props passed to DOM element
|
|
183
|
-
const { emojiMask: _emojiMask, ...domProps } = props
|
|
184
|
-
|
|
185
148
|
const textInput = (
|
|
186
149
|
childInput ? React.cloneElement(children, { className: "text_input" }) :
|
|
187
150
|
(<input
|
|
188
|
-
{...domSafeProps(
|
|
151
|
+
{...domSafeProps(props)}
|
|
189
152
|
aria-describedby={errorId}
|
|
190
153
|
aria-invalid={!!error}
|
|
191
154
|
autoComplete={typeof autoComplete === "string" ? autoComplete : ( autoComplete ? undefined : "off" )}
|
|
@@ -194,8 +157,7 @@ const TextInput = (props: TextInputProps, ref: React.LegacyRef<HTMLInputElement>
|
|
|
194
157
|
id={id}
|
|
195
158
|
key={id}
|
|
196
159
|
name={name}
|
|
197
|
-
onChange={
|
|
198
|
-
onPaste={emojiMask ? handlePaste : undefined}
|
|
160
|
+
onChange={isMaskedInput ? handleChange : onChange}
|
|
199
161
|
pattern={isMaskedInput ? INPUTMASKS[mask]?.pattern : undefined}
|
|
200
162
|
placeholder={placeholder || (isMaskedInput ? INPUTMASKS[mask]?.placeholder : undefined)}
|
|
201
163
|
ref={ref}
|
|
@@ -11,7 +11,6 @@ examples:
|
|
|
11
11
|
- text_input_mask: Mask
|
|
12
12
|
- text_input_autocomplete: Autocomplete
|
|
13
13
|
- text_input_required_indicator: Required Indicator
|
|
14
|
-
- text_input_emoji_mask: Emoji Mask
|
|
15
14
|
|
|
16
15
|
|
|
17
16
|
react:
|
|
@@ -26,7 +25,6 @@ examples:
|
|
|
26
25
|
- text_input_sanitize: Sanitized Masked Input
|
|
27
26
|
- text_input_autocomplete: Autocomplete
|
|
28
27
|
- text_input_required_indicator: Required Indicator
|
|
29
|
-
- text_input_emoji_mask: Emoji Mask
|
|
30
28
|
|
|
31
29
|
|
|
32
30
|
swift:
|
|
@@ -9,4 +9,3 @@ export { default as TextInputMask } from './_text_input_mask.jsx'
|
|
|
9
9
|
export { default as TextInputSanitize } from './_text_input_sanitize.jsx'
|
|
10
10
|
export { default as TextInputAutocomplete } from './_text_input_autocomplete.jsx'
|
|
11
11
|
export { default as TextInputRequiredIndicator } from './_text_input_required_indicator.jsx'
|
|
12
|
-
export { default as TextInputEmojiMask } from './_text_input_emoji_mask.jsx'
|
|
@@ -1,64 +1,26 @@
|
|
|
1
1
|
import PbEnhancedElement from "../pb_enhanced_element"
|
|
2
2
|
import { INPUTMASKS } from "./inputMask"
|
|
3
|
-
import { stripEmojisForPaste, applyEmojiMask } from "../utilities/emojiMask"
|
|
4
3
|
|
|
5
4
|
export default class PbTextInput extends PbEnhancedElement {
|
|
6
5
|
static get selector() {
|
|
7
|
-
return '[data-pb-input-mask="true"]
|
|
6
|
+
return '[data-pb-input-mask="true"]';
|
|
8
7
|
}
|
|
9
8
|
|
|
10
9
|
connect() {
|
|
11
10
|
this.handleInput = this.handleInput.bind(this);
|
|
12
|
-
this.handlePaste = this.handlePaste.bind(this);
|
|
13
11
|
this.element.addEventListener("input", this.handleInput);
|
|
14
|
-
this.element.addEventListener("paste", this.handlePaste);
|
|
15
12
|
this.handleInput();
|
|
16
13
|
}
|
|
17
14
|
|
|
18
15
|
disconnect() {
|
|
19
16
|
this.element.removeEventListener("input", this.handleInput);
|
|
20
|
-
this.element.removeEventListener("paste", this.handlePaste);
|
|
21
17
|
}
|
|
22
18
|
|
|
23
|
-
|
|
24
|
-
return this.element.dataset.pbEmojiMask === "true";
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
handlePaste(event) {
|
|
28
|
-
if (!this.hasEmojiMask()) return;
|
|
29
|
-
|
|
30
|
-
const pastedText = event.clipboardData.getData('text');
|
|
31
|
-
const filteredText = stripEmojisForPaste(pastedText);
|
|
32
|
-
|
|
33
|
-
if (pastedText !== filteredText) {
|
|
34
|
-
event.preventDefault();
|
|
35
|
-
const input = this.element;
|
|
36
|
-
const start = input.selectionStart || 0;
|
|
37
|
-
const end = input.selectionEnd || 0;
|
|
38
|
-
const currentValue = input.value;
|
|
39
|
-
const newValue = currentValue.slice(0, start) + filteredText + currentValue.slice(end);
|
|
40
|
-
const newCursor = start + filteredText.length;
|
|
41
|
-
|
|
42
|
-
input.value = newValue;
|
|
43
|
-
input.selectionStart = input.selectionEnd = newCursor;
|
|
44
|
-
|
|
45
|
-
// Continue to handleInput for mask processing, emoji filtering handled above
|
|
46
|
-
this.handleInput({ skipEmojiFilter: true });
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
handleInput({ skipEmojiFilter = false } = {}) {
|
|
51
|
-
const cursorPosition = this.element.selectionStart;
|
|
52
|
-
let baseValue = this.element.value;
|
|
53
|
-
|
|
54
|
-
// Apply emoji mask if enabled (skip if already filtered in paste handler)
|
|
55
|
-
if (this.hasEmojiMask() && !skipEmojiFilter) {
|
|
56
|
-
const result = applyEmojiMask(this.element);
|
|
57
|
-
baseValue = result.value;
|
|
58
|
-
}
|
|
59
|
-
|
|
19
|
+
handleInput() {
|
|
60
20
|
const maskType = this.element.getAttribute("mask");
|
|
61
|
-
|
|
21
|
+
const cursorPosition = this.element.selectionStart;
|
|
22
|
+
const rawValue = this.element.value;
|
|
23
|
+
let formattedValue = rawValue;
|
|
62
24
|
|
|
63
25
|
const maskKey = {
|
|
64
26
|
currency: 'currency',
|
|
@@ -70,14 +32,13 @@ export default class PbTextInput extends PbEnhancedElement {
|
|
|
70
32
|
}[maskType];
|
|
71
33
|
|
|
72
34
|
if (maskKey && INPUTMASKS[maskKey]) {
|
|
73
|
-
formattedValue = INPUTMASKS[maskKey].format(
|
|
35
|
+
formattedValue = INPUTMASKS[maskKey].format(rawValue);
|
|
74
36
|
}
|
|
75
37
|
|
|
76
38
|
const sanitizedInput = this.element
|
|
77
39
|
.closest(".text_input_wrapper")
|
|
78
40
|
?.querySelector('[data="sanitized-pb-input"]');
|
|
79
41
|
|
|
80
|
-
// Ensure sanitized input uses the already filtered value
|
|
81
42
|
if (sanitizedInput) {
|
|
82
43
|
switch (maskType) {
|
|
83
44
|
case "ssn":
|
|
@@ -94,10 +55,8 @@ export default class PbTextInput extends PbEnhancedElement {
|
|
|
94
55
|
}
|
|
95
56
|
}
|
|
96
57
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
setCursorPosition(this.element, cursorPosition, baseValue, formattedValue);
|
|
100
|
-
}
|
|
58
|
+
this.element.value = formattedValue;
|
|
59
|
+
setCursorPosition(this.element, cursorPosition, rawValue, formattedValue);
|
|
101
60
|
}
|
|
102
61
|
}
|
|
103
62
|
|
|
@@ -18,8 +18,6 @@ module Playbook
|
|
|
18
18
|
prop :autocomplete, default: true
|
|
19
19
|
prop :disabled, type: Playbook::Props::Boolean,
|
|
20
20
|
default: false
|
|
21
|
-
prop :emoji_mask, type: Playbook::Props::Boolean,
|
|
22
|
-
default: false
|
|
23
21
|
prop :error
|
|
24
22
|
prop :inline, type: Playbook::Props::Boolean,
|
|
25
23
|
default: false
|
|
@@ -119,9 +117,7 @@ module Playbook
|
|
|
119
117
|
def validation_data
|
|
120
118
|
fields = input_options.dig(:data) || {}
|
|
121
119
|
fields[:message] = validation_message unless validation_message.blank?
|
|
122
|
-
fields
|
|
123
|
-
fields[:pb_emoji_mask] = true if emoji_mask
|
|
124
|
-
fields
|
|
120
|
+
mask ? fields.merge(pb_input_mask: true) : fields
|
|
125
121
|
end
|
|
126
122
|
|
|
127
123
|
def error_class
|
|
@@ -360,56 +360,3 @@ test('renders required indicator asterisk when requiredIndicator is true', () =>
|
|
|
360
360
|
expect(label).toBeInTheDocument()
|
|
361
361
|
expect(kit).toHaveTextContent('*')
|
|
362
362
|
})
|
|
363
|
-
|
|
364
|
-
const TextInputEmojiMask = (props) => {
|
|
365
|
-
const [value, setValue] = useState('')
|
|
366
|
-
const handleOnChange = ({ target }) => {
|
|
367
|
-
setValue(target.value)
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
return (
|
|
371
|
-
<TextInput
|
|
372
|
-
emojiMask
|
|
373
|
-
onChange={handleOnChange}
|
|
374
|
-
value={value}
|
|
375
|
-
{...props}
|
|
376
|
-
/>
|
|
377
|
-
)
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
test('removes emoji characters when emojiMask is enabled', () => {
|
|
381
|
-
render(
|
|
382
|
-
<TextInputEmojiMask
|
|
383
|
-
data={{ testid: testId }}
|
|
384
|
-
/>
|
|
385
|
-
)
|
|
386
|
-
|
|
387
|
-
const kit = screen.getByTestId(testId)
|
|
388
|
-
const input = within(kit).getByRole('textbox')
|
|
389
|
-
|
|
390
|
-
fireEvent.change(input, { target: { value: 'Hello 👋 World 🌍' } })
|
|
391
|
-
expect(input.value).toBe('Hello World ')
|
|
392
|
-
|
|
393
|
-
fireEvent.change(input, { target: { value: '😀😂🎉' } })
|
|
394
|
-
expect(input.value).toBe('')
|
|
395
|
-
|
|
396
|
-
fireEvent.change(input, { target: { value: 'Hello World' } })
|
|
397
|
-
expect(input.value).toBe('Hello World')
|
|
398
|
-
})
|
|
399
|
-
|
|
400
|
-
test('allows accented characters when emojiMask is enabled', () => {
|
|
401
|
-
render(
|
|
402
|
-
<TextInputEmojiMask
|
|
403
|
-
data={{ testid: testId }}
|
|
404
|
-
/>
|
|
405
|
-
)
|
|
406
|
-
|
|
407
|
-
const kit = screen.getByTestId(testId)
|
|
408
|
-
const input = within(kit).getByRole('textbox')
|
|
409
|
-
|
|
410
|
-
fireEvent.change(input, { target: { value: 'Café résumé naïve' } })
|
|
411
|
-
expect(input.value).toBe('Café résumé naïve')
|
|
412
|
-
|
|
413
|
-
fireEvent.change(input, { target: { value: 'àëǒüñ' } })
|
|
414
|
-
expect(input.value).toBe('àëǒüñ')
|
|
415
|
-
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* eslint-disable react-hooks/rules-of-hooks */
|
|
2
2
|
|
|
3
|
-
import React, { forwardRef, useEffect, useRef
|
|
3
|
+
import React, { forwardRef, useEffect, useRef } from 'react'
|
|
4
4
|
import classnames from 'classnames'
|
|
5
5
|
|
|
6
6
|
import PbTextarea from '.'
|
|
@@ -14,8 +14,6 @@ import Caption from '../pb_caption/_caption'
|
|
|
14
14
|
import Flex from '../pb_flex/_flex'
|
|
15
15
|
import FlexItem from '../pb_flex/_flex_item'
|
|
16
16
|
|
|
17
|
-
import { stripEmojisForPaste, applyEmojiMask } from '../utilities/emojiMask'
|
|
18
|
-
|
|
19
17
|
type TextareaProps = {
|
|
20
18
|
aria?: {[key: string]: string},
|
|
21
19
|
characterCount?: string,
|
|
@@ -23,7 +21,6 @@ type TextareaProps = {
|
|
|
23
21
|
children?: React.ReactChild[],
|
|
24
22
|
data?: {[key: string]: string},
|
|
25
23
|
disabled?: boolean,
|
|
26
|
-
emojiMask?: boolean,
|
|
27
24
|
error?: string,
|
|
28
25
|
htmlOptions?: {[key: string]: string | number | boolean | (() => void)},
|
|
29
26
|
id?: string,
|
|
@@ -48,7 +45,6 @@ const Textarea = ({
|
|
|
48
45
|
children,
|
|
49
46
|
data = {},
|
|
50
47
|
disabled,
|
|
51
|
-
emojiMask = false,
|
|
52
48
|
htmlOptions = {},
|
|
53
49
|
inline = false,
|
|
54
50
|
resize = 'none',
|
|
@@ -71,37 +67,6 @@ const Textarea = ({
|
|
|
71
67
|
}
|
|
72
68
|
})
|
|
73
69
|
|
|
74
|
-
const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
|
|
75
|
-
// Apply emoji mask if enabled using centralized helper
|
|
76
|
-
if (emojiMask) {
|
|
77
|
-
applyEmojiMask(e.target)
|
|
78
|
-
}
|
|
79
|
-
onChange(e)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Handle paste event for emoji mask - updates textarea value, cursor position, and calls onChange
|
|
83
|
-
const handlePaste = (e: ClipboardEvent<HTMLTextAreaElement>) => {
|
|
84
|
-
if (emojiMask) {
|
|
85
|
-
const pastedText = e.clipboardData.getData('text')
|
|
86
|
-
const filteredText = stripEmojisForPaste(pastedText)
|
|
87
|
-
|
|
88
|
-
if (pastedText !== filteredText) {
|
|
89
|
-
e.preventDefault()
|
|
90
|
-
const textarea = e.currentTarget
|
|
91
|
-
const start = textarea.selectionStart || 0
|
|
92
|
-
const end = textarea.selectionEnd || 0
|
|
93
|
-
const currentValue = textarea.value
|
|
94
|
-
const newValue = currentValue.slice(0, start) + filteredText + currentValue.slice(end)
|
|
95
|
-
const newCursorPosition = start + filteredText.length
|
|
96
|
-
|
|
97
|
-
textarea.value = newValue
|
|
98
|
-
textarea.selectionStart = textarea.selectionEnd = newCursorPosition
|
|
99
|
-
|
|
100
|
-
onChange({ ...e, target: textarea, currentTarget: textarea } as unknown as ChangeEvent<HTMLTextAreaElement>)
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
70
|
const errorClass = error ? 'error' : null
|
|
106
71
|
const inlineClass = inline ? 'inline' : ''
|
|
107
72
|
const resizeClass = `resize_${resize}`
|
|
@@ -129,8 +94,7 @@ const Textarea = ({
|
|
|
129
94
|
<textarea
|
|
130
95
|
disabled={disabled}
|
|
131
96
|
name={name}
|
|
132
|
-
onChange={
|
|
133
|
-
onPaste={emojiMask ? handlePaste : undefined}
|
|
97
|
+
onChange={onChange}
|
|
134
98
|
placeholder={placeholder}
|
|
135
99
|
ref={ref}
|
|
136
100
|
required={required}
|
|
@@ -7,7 +7,6 @@ examples:
|
|
|
7
7
|
- textarea_error: Textarea w/ Error
|
|
8
8
|
- textarea_character_counter: Character Counter
|
|
9
9
|
- textarea_inline: Inline
|
|
10
|
-
- textarea_emoji_mask: Emoji Mask
|
|
11
10
|
|
|
12
11
|
react:
|
|
13
12
|
- textarea_default: Default
|
|
@@ -16,7 +15,6 @@ examples:
|
|
|
16
15
|
- textarea_error: Textarea w/ Error
|
|
17
16
|
- textarea_character_counter: Character Counter
|
|
18
17
|
- textarea_inline: Inline
|
|
19
|
-
- textarea_emoji_mask: Emoji Mask
|
|
20
18
|
|
|
21
19
|
swift:
|
|
22
20
|
- textarea_default_swift: Default
|
|
@@ -4,4 +4,3 @@ export { default as TextareaCustom } from './_textarea_custom.jsx'
|
|
|
4
4
|
export { default as TextareaError } from './_textarea_error.jsx'
|
|
5
5
|
export { default as TextareaCharacterCounter } from './_textarea_character_counter.jsx'
|
|
6
6
|
export { default as TextareaInline } from './_textarea_inline.jsx'
|
|
7
|
-
export { default as TextareaEmojiMask } from './_textarea_emoji_mask.jsx'
|
|
@@ -1,76 +1,19 @@
|
|
|
1
1
|
import PbEnhancedElement from '../pb_enhanced_element'
|
|
2
|
-
import { stripEmojisForPaste, applyEmojiMask } from '../utilities/emojiMask'
|
|
3
2
|
|
|
4
3
|
export default class PbTextarea extends PbEnhancedElement {
|
|
5
4
|
style: {[key: string]: string}
|
|
6
5
|
scrollHeight: string
|
|
7
|
-
private skipNextEmojiFilter = false
|
|
8
|
-
|
|
9
6
|
static get selector(): string {
|
|
10
|
-
return '.resize_auto textarea
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
hasEmojiMask(): boolean {
|
|
14
|
-
return (this.element as HTMLElement).dataset.pbEmojiMask === "true"
|
|
7
|
+
return '.resize_auto textarea'
|
|
15
8
|
}
|
|
16
9
|
|
|
17
10
|
onInput(): void {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
this.style.height = (this.scrollHeight) + 'px'
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
handleEmojiInput = (): void => {
|
|
25
|
-
if (!this.hasEmojiMask()) return
|
|
26
|
-
|
|
27
|
-
if (this.skipNextEmojiFilter) {
|
|
28
|
-
this.skipNextEmojiFilter = false
|
|
29
|
-
return
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
applyEmojiMask(this.element as HTMLTextAreaElement)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
handleEmojiPaste = (event: ClipboardEvent): void => {
|
|
36
|
-
if (!this.hasEmojiMask()) return
|
|
37
|
-
|
|
38
|
-
const pastedText = event.clipboardData?.getData('text') || ''
|
|
39
|
-
const filteredText = stripEmojisForPaste(pastedText)
|
|
40
|
-
|
|
41
|
-
if (pastedText !== filteredText) {
|
|
42
|
-
event.preventDefault()
|
|
43
|
-
const textarea = this.element as HTMLTextAreaElement
|
|
44
|
-
const start = textarea.selectionStart || 0
|
|
45
|
-
const end = textarea.selectionEnd || 0
|
|
46
|
-
const currentValue = textarea.value
|
|
47
|
-
const newValue = currentValue.slice(0, start) + filteredText + currentValue.slice(end)
|
|
48
|
-
const newCursor = start + filteredText.length
|
|
49
|
-
|
|
50
|
-
textarea.value = newValue
|
|
51
|
-
textarea.selectionStart = textarea.selectionEnd = newCursor
|
|
52
|
-
|
|
53
|
-
this.skipNextEmojiFilter = true
|
|
54
|
-
|
|
55
|
-
textarea.dispatchEvent(new Event('input', { bubbles: true }))
|
|
56
|
-
}
|
|
11
|
+
this.style.height = 'auto'
|
|
12
|
+
this.style.height = (this.scrollHeight) + 'px'
|
|
57
13
|
}
|
|
58
14
|
|
|
59
15
|
connect(): void {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
this.element.addEventListener('input', this.onInput, false)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (this.hasEmojiMask()) {
|
|
66
|
-
this.element.addEventListener('input', this.handleEmojiInput, false)
|
|
67
|
-
this.element.addEventListener('paste', this.handleEmojiPaste as EventListener, false)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
disconnect(): void {
|
|
72
|
-
this.element.removeEventListener('input', this.onInput, false)
|
|
73
|
-
this.element.removeEventListener('input', this.handleEmojiInput, false)
|
|
74
|
-
this.element.removeEventListener('paste', this.handleEmojiPaste as EventListener, false)
|
|
16
|
+
this.element.setAttribute('style', 'height:' + (this.element.scrollHeight) + 'px;overflow-y:hidden;')
|
|
17
|
+
this.element.addEventListener('input', this.onInput, false)
|
|
75
18
|
}
|
|
76
19
|
}
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
module Playbook
|
|
4
4
|
module PbTextarea
|
|
5
5
|
class Textarea < Playbook::KitBase
|
|
6
|
-
prop :emoji_mask, type: Playbook::Props::Boolean,
|
|
7
|
-
default: false
|
|
8
6
|
prop :error
|
|
9
7
|
prop :inline, type: Playbook::Props::Boolean,
|
|
10
8
|
default: false
|
|
@@ -30,12 +28,6 @@ module Playbook
|
|
|
30
28
|
max_characters && character_count ? "#{character_count} / #{max_characters}" : character_count
|
|
31
29
|
end
|
|
32
30
|
|
|
33
|
-
def textarea_options
|
|
34
|
-
{
|
|
35
|
-
data: emoji_mask ? { pb_emoji_mask: true } : {},
|
|
36
|
-
}
|
|
37
|
-
end
|
|
38
|
-
|
|
39
31
|
private
|
|
40
32
|
|
|
41
33
|
def error_class
|