@intlayer/design-system 8.4.5 → 8.4.7
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/dist/esm/components/ContentEditor/ContentEditorTextArea.mjs +1 -1
- package/dist/esm/components/DictionaryFieldEditor/ContentEditorView/TextEditor.mjs +2 -2
- package/dist/esm/components/DictionaryFieldEditor/DictionaryCreationForm/DictionaryCreationForm.mjs +1 -1
- package/dist/esm/components/DictionaryFieldEditor/DictionaryDetails/DictionaryDetailsForm.mjs +3 -3
- package/dist/esm/components/DictionaryFieldEditor/DictionaryFieldEditor.mjs +1 -1
- package/dist/esm/components/DictionaryFieldEditor/NavigationView/NavigationViewNode.mjs +1 -1
- package/dist/esm/components/DictionaryFieldEditor/SaveForm/SaveForm.mjs +2 -2
- package/dist/esm/components/DictionaryFieldEditor/StructureView/StructureView.mjs +1 -1
- package/dist/esm/components/Form/elements/OTPElement.mjs +1 -1
- package/dist/esm/components/IDE/CodeBlockShiki.mjs +12 -0
- package/dist/esm/components/IDE/CodeBlockShiki.mjs.map +1 -1
- package/dist/esm/components/LocaleSwitcherContentDropDown/LocaleSwitcherContent.mjs +1 -1
- package/dist/esm/components/MarkDownRender/MarkDownRender.mjs +23 -19
- package/dist/esm/components/MarkDownRender/MarkDownRender.mjs.map +1 -1
- package/dist/esm/components/Modal/Modal.mjs +2 -2
- package/dist/esm/components/Navbar/MobileNavbar.mjs +1 -1
- package/dist/esm/components/Pagination/Pagination.mjs +1 -1
- package/dist/esm/components/RightDrawer/RightDrawer.mjs +3 -3
- package/dist/esm/components/TextArea/AutocompleteTextArea.mjs +60 -225
- package/dist/esm/components/TextArea/AutocompleteTextArea.mjs.map +1 -1
- package/dist/esm/components/TextArea/ContentEditableTextArea.mjs +444 -0
- package/dist/esm/components/TextArea/ContentEditableTextArea.mjs.map +1 -0
- package/dist/esm/components/TextArea/index.mjs +3 -2
- package/dist/esm/components/index.mjs +3 -2
- package/dist/esm/hooks/index.mjs +9 -9
- package/dist/types/components/Badge/index.d.ts +1 -1
- package/dist/types/components/Button/Button.d.ts +3 -3
- package/dist/types/components/CollapsibleTable/CollapsibleTable.d.ts +1 -1
- package/dist/types/components/Container/index.d.ts +6 -6
- package/dist/types/components/IDE/CodeBlockShiki.d.ts.map +1 -1
- package/dist/types/components/Input/Checkbox.d.ts +1 -1
- package/dist/types/components/Link/Link.d.ts +3 -3
- package/dist/types/components/MarkDownRender/MarkDownRender.d.ts.map +1 -1
- package/dist/types/components/Pagination/Pagination.d.ts +1 -1
- package/dist/types/components/SwitchSelector/index.d.ts +1 -1
- package/dist/types/components/TabSelector/TabSelector.d.ts +1 -1
- package/dist/types/components/Tag/index.d.ts +2 -2
- package/dist/types/components/TextArea/AutocompleteTextArea.d.ts +14 -120
- package/dist/types/components/TextArea/AutocompleteTextArea.d.ts.map +1 -1
- package/dist/types/components/TextArea/ContentEditableTextArea.d.ts +65 -0
- package/dist/types/components/TextArea/ContentEditableTextArea.d.ts.map +1 -0
- package/dist/types/components/TextArea/index.d.ts +3 -2
- package/dist/types/components/index.d.ts +3 -2
- package/package.json +17 -17
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../utils/cn.mjs";
|
|
4
|
+
import { inputVariants } from "../Input/Input.mjs";
|
|
5
|
+
import { useEffect, useImperativeHandle, useRef, useState } from "react";
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
|
|
8
|
+
//#region src/components/TextArea/ContentEditableTextArea.tsx
|
|
9
|
+
const ZERO_WIDTH_SPACE = "";
|
|
10
|
+
const getTextFromContainer = (container) => {
|
|
11
|
+
const lineEls = container.querySelectorAll("[data-line]");
|
|
12
|
+
if (lineEls.length === 0) return (container.textContent ?? "").split(ZERO_WIDTH_SPACE).join("");
|
|
13
|
+
return Array.from(lineEls).map((el) => {
|
|
14
|
+
const raw = el.querySelector("[data-editable]")?.textContent ?? el.textContent ?? "";
|
|
15
|
+
return raw === ZERO_WIDTH_SPACE ? "" : raw.split(ZERO_WIDTH_SPACE).join("");
|
|
16
|
+
}).join("\n");
|
|
17
|
+
};
|
|
18
|
+
const splitLines = (text) => {
|
|
19
|
+
const lines = text.split("\n");
|
|
20
|
+
return lines.length === 0 ? [""] : lines;
|
|
21
|
+
};
|
|
22
|
+
const createGraphemeSegmenter = () => {
|
|
23
|
+
if (typeof Intl === "undefined" || !("Segmenter" in Intl)) return null;
|
|
24
|
+
const SegmenterCtor = Intl.Segmenter;
|
|
25
|
+
return new SegmenterCtor(void 0, { granularity: "grapheme" });
|
|
26
|
+
};
|
|
27
|
+
const graphemeSegmenter = createGraphemeSegmenter();
|
|
28
|
+
/**
|
|
29
|
+
* Find the previous grapheme cluster boundary for safe deletion.
|
|
30
|
+
* Falls back to code-point-aware deletion if Intl.Segmenter is unavailable.
|
|
31
|
+
*/
|
|
32
|
+
const prevGraphemeBoundary = (text, offset) => {
|
|
33
|
+
if (offset <= 0) return 0;
|
|
34
|
+
if (graphemeSegmenter) {
|
|
35
|
+
const segments = [...graphemeSegmenter.segment(text.slice(0, offset))];
|
|
36
|
+
const last = segments[segments.length - 1];
|
|
37
|
+
return last ? offset - last.segment.length : offset - 1;
|
|
38
|
+
}
|
|
39
|
+
const code = text.charCodeAt(offset - 1);
|
|
40
|
+
if (code >= 56320 && code <= 57343 && offset >= 2) return offset - 2;
|
|
41
|
+
return offset - 1;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Find the next grapheme cluster boundary for safe forward deletion.
|
|
45
|
+
*/
|
|
46
|
+
const nextGraphemeBoundary = (text, offset) => {
|
|
47
|
+
if (offset >= text.length) return text.length;
|
|
48
|
+
if (graphemeSegmenter) {
|
|
49
|
+
const first = [...graphemeSegmenter.segment(text.slice(offset))][0];
|
|
50
|
+
return first ? offset + first.segment.length : offset + 1;
|
|
51
|
+
}
|
|
52
|
+
const code = text.charCodeAt(offset);
|
|
53
|
+
if (code >= 55296 && code <= 56319 && offset + 1 < text.length) return offset + 2;
|
|
54
|
+
return offset + 1;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Find the previous word boundary for Option+Backspace.
|
|
58
|
+
*/
|
|
59
|
+
const prevWordBoundary = (text, offset) => {
|
|
60
|
+
if (offset <= 0) return 0;
|
|
61
|
+
let i = offset - 1;
|
|
62
|
+
while (i > 0 && /\s/.test(text[i - 1])) i--;
|
|
63
|
+
while (i > 0 && /\S/.test(text[i - 1])) i--;
|
|
64
|
+
return i;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Find the next word boundary for Option+Delete.
|
|
68
|
+
*/
|
|
69
|
+
const nextWordBoundary = (text, offset) => {
|
|
70
|
+
if (offset >= text.length) return text.length;
|
|
71
|
+
let i = offset;
|
|
72
|
+
while (i < text.length && /\S/.test(text[i])) i++;
|
|
73
|
+
while (i < text.length && /\s/.test(text[i])) i++;
|
|
74
|
+
return i;
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Find the start of the current line (for Cmd+Backspace).
|
|
78
|
+
*/
|
|
79
|
+
const lineStart = (text, offset) => {
|
|
80
|
+
return text.slice(0, offset).lastIndexOf("\n") + 1;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Find the end of the current line (for Cmd+Delete).
|
|
84
|
+
*/
|
|
85
|
+
const lineEnd = (text, offset) => {
|
|
86
|
+
const nextNewline = text.indexOf("\n", offset);
|
|
87
|
+
return nextNewline === -1 ? text.length : nextNewline;
|
|
88
|
+
};
|
|
89
|
+
const useContentEditable = ({ value, defaultValue, onChange, disabled = false }) => {
|
|
90
|
+
const initialValue = value ?? defaultValue ?? "";
|
|
91
|
+
const [lines, setLines] = useState(() => splitLines(initialValue));
|
|
92
|
+
const containerRef = useRef(null);
|
|
93
|
+
const pendingCaretRef = useRef(null);
|
|
94
|
+
const isControlled = value !== void 0;
|
|
95
|
+
const linesRef = useRef(lines);
|
|
96
|
+
linesRef.current = lines;
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (isControlled && value !== void 0) setLines(splitLines(value));
|
|
99
|
+
}, [value, isControlled]);
|
|
100
|
+
const getText = () => linesRef.current.join("\n");
|
|
101
|
+
const getCaretPosition = () => {
|
|
102
|
+
const sel = window.getSelection();
|
|
103
|
+
if (!sel?.rangeCount || !containerRef.current) return null;
|
|
104
|
+
const range = sel.getRangeAt(0);
|
|
105
|
+
const lineEls = containerRef.current.querySelectorAll("[data-line]");
|
|
106
|
+
for (let i = 0; i < lineEls.length; i++) if (lineEls[i].contains(range.startContainer)) return {
|
|
107
|
+
line: i,
|
|
108
|
+
offset: range.startOffset
|
|
109
|
+
};
|
|
110
|
+
return null;
|
|
111
|
+
};
|
|
112
|
+
const getSelectionOffsets = () => {
|
|
113
|
+
const sel = window.getSelection();
|
|
114
|
+
if (!sel?.rangeCount || !containerRef.current) return null;
|
|
115
|
+
const range = sel.getRangeAt(0);
|
|
116
|
+
const lineEls = containerRef.current.querySelectorAll("[data-line]");
|
|
117
|
+
const currentLines = linesRef.current;
|
|
118
|
+
const findOffset = (node, nodeOffset) => {
|
|
119
|
+
for (let i = 0; i < lineEls.length; i++) if (lineEls[i].contains(node)) {
|
|
120
|
+
let flat = 0;
|
|
121
|
+
for (let j = 0; j < i; j++) flat += currentLines[j].length + 1;
|
|
122
|
+
return flat + Math.min(nodeOffset, currentLines[i]?.length ?? 0);
|
|
123
|
+
}
|
|
124
|
+
if (node === containerRef.current) {
|
|
125
|
+
if (nodeOffset === 0) return 0;
|
|
126
|
+
return currentLines.join("\n").length;
|
|
127
|
+
}
|
|
128
|
+
return 0;
|
|
129
|
+
};
|
|
130
|
+
const start = findOffset(range.startContainer, range.startOffset);
|
|
131
|
+
const end = findOffset(range.endContainer, range.endOffset);
|
|
132
|
+
return {
|
|
133
|
+
start: Math.min(start, end),
|
|
134
|
+
end: Math.max(start, end),
|
|
135
|
+
hasSelection: !range.collapsed
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
const setCaretPosition = (pos) => {
|
|
139
|
+
if (!containerRef.current) return;
|
|
140
|
+
const lineEl = containerRef.current.querySelectorAll("[data-line]")[pos.line];
|
|
141
|
+
if (!lineEl) return;
|
|
142
|
+
const editable = lineEl.querySelector("[data-editable]");
|
|
143
|
+
const node = editable?.firstChild ?? editable ?? lineEl.firstChild ?? lineEl;
|
|
144
|
+
const sel = window.getSelection();
|
|
145
|
+
if (!sel) return;
|
|
146
|
+
const range = document.createRange();
|
|
147
|
+
const maxOff = Math.min(pos.offset, node.textContent?.length ?? 0);
|
|
148
|
+
try {
|
|
149
|
+
range.setStart(node, maxOff);
|
|
150
|
+
range.collapse(true);
|
|
151
|
+
sel.removeAllRanges();
|
|
152
|
+
sel.addRange(range);
|
|
153
|
+
} catch {
|
|
154
|
+
range.selectNodeContents(node);
|
|
155
|
+
range.collapse(false);
|
|
156
|
+
sel.removeAllRanges();
|
|
157
|
+
sel.addRange(range);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
if (pendingCaretRef.current && containerRef.current) {
|
|
162
|
+
setCaretPosition(pendingCaretRef.current);
|
|
163
|
+
pendingCaretRef.current = null;
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
const flatOffsetFromCaret = (pos) => {
|
|
167
|
+
const currentLines = linesRef.current;
|
|
168
|
+
let offset = 0;
|
|
169
|
+
for (let i = 0; i < pos.line; i++) offset += currentLines[i].length + 1;
|
|
170
|
+
return offset + pos.offset;
|
|
171
|
+
};
|
|
172
|
+
const caretFromFlatOffset = (flat, targetLines) => {
|
|
173
|
+
let rem = flat;
|
|
174
|
+
for (let i = 0; i < targetLines.length; i++) {
|
|
175
|
+
if (rem <= targetLines[i].length) return {
|
|
176
|
+
line: i,
|
|
177
|
+
offset: rem
|
|
178
|
+
};
|
|
179
|
+
rem -= targetLines[i].length + 1;
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
line: targetLines.length - 1,
|
|
183
|
+
offset: targetLines[targetLines.length - 1]?.length ?? 0
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
const getCursorOffset = () => {
|
|
187
|
+
const pos = getCaretPosition();
|
|
188
|
+
if (!pos) return 0;
|
|
189
|
+
return flatOffsetFromCaret(pos);
|
|
190
|
+
};
|
|
191
|
+
/**
|
|
192
|
+
* Applies a text mutation: computes new lines, sets pending caret, updates state.
|
|
193
|
+
*/
|
|
194
|
+
const applyTextChange = (newText, caretOffset) => {
|
|
195
|
+
const newLines = splitLines(newText);
|
|
196
|
+
pendingCaretRef.current = caretFromFlatOffset(caretOffset, newLines);
|
|
197
|
+
setLines(newLines);
|
|
198
|
+
onChange?.(newText);
|
|
199
|
+
};
|
|
200
|
+
const handleInput = () => {
|
|
201
|
+
if (pendingCaretRef.current !== null) return;
|
|
202
|
+
if (disabled || !containerRef.current) return;
|
|
203
|
+
const caretPos = getCaretPosition();
|
|
204
|
+
const newText = getTextFromContainer(containerRef.current);
|
|
205
|
+
const newLines = splitLines(newText);
|
|
206
|
+
pendingCaretRef.current = caretPos;
|
|
207
|
+
setLines(newLines);
|
|
208
|
+
onChange?.(newText);
|
|
209
|
+
};
|
|
210
|
+
const handleKeyDown = (e) => {
|
|
211
|
+
if (disabled) {
|
|
212
|
+
e.preventDefault();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (e.nativeEvent.isComposing) return;
|
|
216
|
+
if ((e.metaKey || e.ctrlKey) && e.key === "z") {
|
|
217
|
+
e.preventDefault();
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const selInfo = getSelectionOffsets();
|
|
221
|
+
if (!selInfo) return;
|
|
222
|
+
const currentText = linesRef.current.join("\n");
|
|
223
|
+
if (e.key === "Enter") {
|
|
224
|
+
e.preventDefault();
|
|
225
|
+
applyTextChange(currentText.slice(0, selInfo.start) + "\n" + currentText.slice(selInfo.end), selInfo.start + 1);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (e.key === "Backspace") {
|
|
229
|
+
e.preventDefault();
|
|
230
|
+
if (selInfo.hasSelection) applyTextChange(currentText.slice(0, selInfo.start) + currentText.slice(selInfo.end), selInfo.start);
|
|
231
|
+
else {
|
|
232
|
+
if (selInfo.start === 0) return;
|
|
233
|
+
let deleteFrom;
|
|
234
|
+
if (e.metaKey) deleteFrom = lineStart(currentText, selInfo.start);
|
|
235
|
+
else if (e.altKey) deleteFrom = prevWordBoundary(currentText, selInfo.start);
|
|
236
|
+
else deleteFrom = prevGraphemeBoundary(currentText, selInfo.start);
|
|
237
|
+
applyTextChange(currentText.slice(0, deleteFrom) + currentText.slice(selInfo.start), deleteFrom);
|
|
238
|
+
}
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (e.key === "Delete") {
|
|
242
|
+
e.preventDefault();
|
|
243
|
+
if (selInfo.hasSelection) applyTextChange(currentText.slice(0, selInfo.start) + currentText.slice(selInfo.end), selInfo.start);
|
|
244
|
+
else {
|
|
245
|
+
if (selInfo.start >= currentText.length) return;
|
|
246
|
+
let deleteTo;
|
|
247
|
+
if (e.metaKey) deleteTo = lineEnd(currentText, selInfo.start);
|
|
248
|
+
else if (e.altKey) deleteTo = nextWordBoundary(currentText, selInfo.start);
|
|
249
|
+
else deleteTo = nextGraphemeBoundary(currentText, selInfo.start);
|
|
250
|
+
applyTextChange(currentText.slice(0, selInfo.start) + currentText.slice(deleteTo), selInfo.start);
|
|
251
|
+
}
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
const handleCut = (e) => {
|
|
256
|
+
if (disabled) {
|
|
257
|
+
e.preventDefault();
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
e.preventDefault();
|
|
261
|
+
const selInfo = getSelectionOffsets();
|
|
262
|
+
if (!selInfo || !selInfo.hasSelection) return;
|
|
263
|
+
const currentText = linesRef.current.join("\n");
|
|
264
|
+
const selectedText = currentText.slice(selInfo.start, selInfo.end);
|
|
265
|
+
e.clipboardData.setData("text/plain", selectedText);
|
|
266
|
+
applyTextChange(currentText.slice(0, selInfo.start) + currentText.slice(selInfo.end), selInfo.start);
|
|
267
|
+
};
|
|
268
|
+
const handlePaste = (e) => {
|
|
269
|
+
if (disabled) {
|
|
270
|
+
e.preventDefault();
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
e.preventDefault();
|
|
274
|
+
const pastedText = e.clipboardData.getData("text/plain");
|
|
275
|
+
if (!pastedText) return;
|
|
276
|
+
const selInfo = getSelectionOffsets();
|
|
277
|
+
if (!selInfo) return;
|
|
278
|
+
const currentText = linesRef.current.join("\n");
|
|
279
|
+
applyTextChange(currentText.slice(0, selInfo.start) + pastedText + currentText.slice(selInfo.end), selInfo.start + pastedText.length);
|
|
280
|
+
};
|
|
281
|
+
const handleBeforeInput = (e) => {
|
|
282
|
+
if (disabled) return;
|
|
283
|
+
const inputEvent = e.nativeEvent;
|
|
284
|
+
if (inputEvent.isComposing) return;
|
|
285
|
+
const inputType = inputEvent.inputType;
|
|
286
|
+
if (inputType === "insertParagraph" || inputType === "insertLineBreak") return;
|
|
287
|
+
if (inputType === "deleteContentBackward" || inputType === "deleteContentForward") {
|
|
288
|
+
e.preventDefault();
|
|
289
|
+
const selInfo = getSelectionOffsets();
|
|
290
|
+
if (!selInfo) return;
|
|
291
|
+
const currentText = linesRef.current.join("\n");
|
|
292
|
+
if (selInfo.hasSelection) applyTextChange(currentText.slice(0, selInfo.start) + currentText.slice(selInfo.end), selInfo.start);
|
|
293
|
+
else if (inputType === "deleteContentBackward") {
|
|
294
|
+
if (selInfo.start === 0) return;
|
|
295
|
+
const deleteFrom = prevGraphemeBoundary(currentText, selInfo.start);
|
|
296
|
+
applyTextChange(currentText.slice(0, deleteFrom) + currentText.slice(selInfo.start), deleteFrom);
|
|
297
|
+
} else {
|
|
298
|
+
if (selInfo.start >= currentText.length) return;
|
|
299
|
+
const deleteTo = nextGraphemeBoundary(currentText, selInfo.start);
|
|
300
|
+
applyTextChange(currentText.slice(0, selInfo.start) + currentText.slice(deleteTo), selInfo.start);
|
|
301
|
+
}
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (inputType === "insertReplacementText") {
|
|
305
|
+
e.preventDefault();
|
|
306
|
+
const selInfo = getSelectionOffsets();
|
|
307
|
+
if (!selInfo) return;
|
|
308
|
+
const currentText = linesRef.current.join("\n");
|
|
309
|
+
const replacement = inputEvent.data ?? inputEvent.dataTransfer?.getData("text/plain") ?? "";
|
|
310
|
+
applyTextChange(currentText.slice(0, selInfo.start) + replacement + currentText.slice(selInfo.end), selInfo.start + replacement.length);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
if (inputType === "insertText" && inputEvent.data) {
|
|
314
|
+
e.preventDefault();
|
|
315
|
+
const selInfo = getSelectionOffsets();
|
|
316
|
+
if (!selInfo) return;
|
|
317
|
+
const currentText = linesRef.current.join("\n");
|
|
318
|
+
const inserted = inputEvent.data;
|
|
319
|
+
applyTextChange(currentText.slice(0, selInfo.start) + inserted + currentText.slice(selInfo.end), selInfo.start + inserted.length);
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
const handleDrop = (e) => {
|
|
323
|
+
e.preventDefault();
|
|
324
|
+
};
|
|
325
|
+
const handleDragOver = (e) => {
|
|
326
|
+
e.preventDefault();
|
|
327
|
+
};
|
|
328
|
+
return {
|
|
329
|
+
lines,
|
|
330
|
+
containerRef,
|
|
331
|
+
getText,
|
|
332
|
+
handleInput,
|
|
333
|
+
handleBeforeInput,
|
|
334
|
+
handleKeyDown,
|
|
335
|
+
handleCut,
|
|
336
|
+
handlePaste,
|
|
337
|
+
handleDrop,
|
|
338
|
+
handleDragOver,
|
|
339
|
+
getCaretPosition,
|
|
340
|
+
setCaretPosition,
|
|
341
|
+
getCursorOffset,
|
|
342
|
+
caretFromFlatOffset
|
|
343
|
+
};
|
|
344
|
+
};
|
|
345
|
+
const Line = ({ index, text, isLast, ghostText }) => /* @__PURE__ */ jsxs("span", {
|
|
346
|
+
"data-line": index,
|
|
347
|
+
className: "block min-h-[1.5rem]",
|
|
348
|
+
children: [
|
|
349
|
+
/* @__PURE__ */ jsx("span", {
|
|
350
|
+
"data-editable": true,
|
|
351
|
+
children: text || ""
|
|
352
|
+
}),
|
|
353
|
+
ghostText && /* @__PURE__ */ jsx("span", {
|
|
354
|
+
"data-ghost": true,
|
|
355
|
+
className: "pointer-events-none select-none text-neutral",
|
|
356
|
+
"aria-hidden": "true",
|
|
357
|
+
children: ghostText
|
|
358
|
+
}),
|
|
359
|
+
!isLast && /* @__PURE__ */ jsx("br", {})
|
|
360
|
+
]
|
|
361
|
+
});
|
|
362
|
+
const LINE_HEIGHT = 24;
|
|
363
|
+
const LINE_PADDING = 12;
|
|
364
|
+
const ContentEditableTextArea = ({ value, defaultValue, onChange, placeholder, disabled = false, minRows = 1, maxRows = 999, autoSize = true, validationStyleEnabled = false, variant, ghostText, ghostLine, ghostOffset, onClick, className, dir = "auto", ref, ...rest }) => {
|
|
365
|
+
const { lines, containerRef, getText, handleInput, handleBeforeInput, handleKeyDown, handleCut, handlePaste, handleDrop, handleDragOver, getCursorOffset, setCaretPosition, caretFromFlatOffset } = useContentEditable({
|
|
366
|
+
value,
|
|
367
|
+
defaultValue,
|
|
368
|
+
onChange,
|
|
369
|
+
disabled
|
|
370
|
+
});
|
|
371
|
+
const elRef = useRef(null);
|
|
372
|
+
const setRef = (el) => {
|
|
373
|
+
elRef.current = el;
|
|
374
|
+
containerRef.current = el;
|
|
375
|
+
};
|
|
376
|
+
useImperativeHandle(ref, () => ({
|
|
377
|
+
getContainer: () => elRef.current,
|
|
378
|
+
getText,
|
|
379
|
+
focus: () => elRef.current?.focus(),
|
|
380
|
+
getCursorOffset,
|
|
381
|
+
setCursorAtOffset: (offset) => {
|
|
382
|
+
setCaretPosition(caretFromFlatOffset(offset, lines));
|
|
383
|
+
}
|
|
384
|
+
}));
|
|
385
|
+
useEffect(() => {
|
|
386
|
+
if (!autoSize || !elRef.current) return;
|
|
387
|
+
const el = elRef.current;
|
|
388
|
+
const max = LINE_HEIGHT * maxRows + LINE_PADDING;
|
|
389
|
+
const min = LINE_HEIGHT * minRows + LINE_PADDING;
|
|
390
|
+
el.style.height = "auto";
|
|
391
|
+
const sh = el.scrollHeight;
|
|
392
|
+
el.style.height = `${Math.max(Math.min(sh, max), min)}px`;
|
|
393
|
+
el.style.overflowY = sh > max ? "auto" : "hidden";
|
|
394
|
+
}, [
|
|
395
|
+
lines,
|
|
396
|
+
autoSize,
|
|
397
|
+
maxRows,
|
|
398
|
+
minRows
|
|
399
|
+
]);
|
|
400
|
+
const isEmpty = lines.length === 1 && lines[0] === "";
|
|
401
|
+
const hasGhost = ghostText && ghostLine !== void 0 && ghostOffset !== void 0;
|
|
402
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
403
|
+
className: "relative w-full",
|
|
404
|
+
children: [isEmpty && placeholder && /* @__PURE__ */ jsx("div", {
|
|
405
|
+
className: "pointer-events-none absolute inset-0 select-none px-2 py-3 text-base text-neutral-400 leading-[1.5rem] md:py-2 md:text-sm",
|
|
406
|
+
"aria-hidden": "true",
|
|
407
|
+
children: placeholder
|
|
408
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
409
|
+
...rest,
|
|
410
|
+
ref: setRef,
|
|
411
|
+
role: "textbox",
|
|
412
|
+
"aria-multiline": "true",
|
|
413
|
+
"aria-placeholder": placeholder,
|
|
414
|
+
"aria-disabled": disabled,
|
|
415
|
+
"aria-autocomplete": hasGhost ? "inline" : void 0,
|
|
416
|
+
tabIndex: disabled ? -1 : 0,
|
|
417
|
+
contentEditable: !disabled,
|
|
418
|
+
suppressContentEditableWarning: true,
|
|
419
|
+
dir,
|
|
420
|
+
onInput: handleInput,
|
|
421
|
+
onBeforeInput: handleBeforeInput,
|
|
422
|
+
onKeyDown: handleKeyDown,
|
|
423
|
+
onCut: handleCut,
|
|
424
|
+
onPaste: handlePaste,
|
|
425
|
+
onDrop: handleDrop,
|
|
426
|
+
onDragOver: handleDragOver,
|
|
427
|
+
onClick,
|
|
428
|
+
className: cn("resize-none whitespace-pre-wrap break-words outline-none", inputVariants({
|
|
429
|
+
variant,
|
|
430
|
+
validationStyleEnabled: validationStyleEnabled ? "enabled" : "disabled"
|
|
431
|
+
}), autoSize && "overflow-y-auto", className),
|
|
432
|
+
children: lines.map((text, i) => /* @__PURE__ */ jsx(Line, {
|
|
433
|
+
index: i,
|
|
434
|
+
text,
|
|
435
|
+
isLast: i === lines.length - 1,
|
|
436
|
+
ghostText: hasGhost && ghostLine === i ? ghostText : void 0
|
|
437
|
+
}, i))
|
|
438
|
+
})]
|
|
439
|
+
});
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
//#endregion
|
|
443
|
+
export { ContentEditableTextArea, useContentEditable };
|
|
444
|
+
//# sourceMappingURL=ContentEditableTextArea.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ContentEditableTextArea.mjs","names":[],"sources":["../../../../src/components/TextArea/ContentEditableTextArea.tsx"],"sourcesContent":["'use client';\n\nimport { cn } from '@utils/cn';\nimport type { VariantProps } from 'class-variance-authority';\nimport {\n type ClipboardEvent,\n type DragEvent,\n type FC,\n type FormEvent,\n type HTMLAttributes,\n type KeyboardEvent,\n type MutableRefObject,\n type Ref,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from 'react';\nimport { type InputVariant, inputVariants } from '../Input';\n\ntype CaretPosition = {\n line: number;\n offset: number;\n};\n\ntype UseContentEditableOptions = {\n value?: string;\n defaultValue?: string;\n onChange?: (value: string) => void;\n disabled?: boolean;\n};\n\nconst ZERO_WIDTH_SPACE = '\\u200B';\n\nconst getTextFromContainer = (container: HTMLDivElement): string => {\n const lineEls = container.querySelectorAll('[data-line]');\n if (lineEls.length === 0) {\n return (container.textContent ?? '').split(ZERO_WIDTH_SPACE).join('');\n }\n\n return Array.from(lineEls)\n .map((el) => {\n const editable = el.querySelector('[data-editable]');\n const raw = editable?.textContent ?? el.textContent ?? '';\n return raw === ZERO_WIDTH_SPACE\n ? ''\n : raw.split(ZERO_WIDTH_SPACE).join('');\n })\n .join('\\n');\n};\n\nconst splitLines = (text: string): string[] => {\n const lines = text.split('\\n');\n return lines.length === 0 ? [''] : lines;\n};\n\n// Cached Intl.Segmenter for grapheme-aware deletion (emoji, CJK, etc.)\n// Intl.Segmenter is ES2022+ but we feature-detect at runtime.\ntype GraphemeSegmenter = {\n segment: (input: string) => Iterable<{ segment: string }>;\n};\n\nconst createGraphemeSegmenter = (): GraphemeSegmenter | null => {\n if (typeof Intl === 'undefined' || !('Segmenter' in Intl)) return null;\n const SegmenterCtor = (\n Intl as unknown as Record<\n string,\n new (\n ...args: unknown[]\n ) => GraphemeSegmenter\n >\n ).Segmenter;\n return new SegmenterCtor(undefined, { granularity: 'grapheme' });\n};\n\nconst graphemeSegmenter = createGraphemeSegmenter();\n\n/**\n * Find the previous grapheme cluster boundary for safe deletion.\n * Falls back to code-point-aware deletion if Intl.Segmenter is unavailable.\n */\nconst prevGraphemeBoundary = (text: string, offset: number): number => {\n if (offset <= 0) return 0;\n\n if (graphemeSegmenter) {\n const segments = [...graphemeSegmenter.segment(text.slice(0, offset))];\n const last = segments[segments.length - 1];\n return last ? offset - last.segment.length : offset - 1;\n }\n\n // Fallback: handle surrogate pairs\n const code = text.charCodeAt(offset - 1);\n if (code >= 0xdc00 && code <= 0xdfff && offset >= 2) {\n return offset - 2;\n }\n return offset - 1;\n};\n\n/**\n * Find the next grapheme cluster boundary for safe forward deletion.\n */\nconst nextGraphemeBoundary = (text: string, offset: number): number => {\n if (offset >= text.length) return text.length;\n\n if (graphemeSegmenter) {\n const segments = [...graphemeSegmenter.segment(text.slice(offset))];\n const first = segments[0];\n return first ? offset + first.segment.length : offset + 1;\n }\n\n // Fallback: handle surrogate pairs\n const code = text.charCodeAt(offset);\n if (code >= 0xd800 && code <= 0xdbff && offset + 1 < text.length) {\n return offset + 2;\n }\n return offset + 1;\n};\n\n/**\n * Find the previous word boundary for Option+Backspace.\n */\nconst prevWordBoundary = (text: string, offset: number): number => {\n if (offset <= 0) return 0;\n let i = offset - 1;\n // Skip whitespace\n while (i > 0 && /\\s/.test(text[i - 1])) i--;\n // Skip word characters\n while (i > 0 && /\\S/.test(text[i - 1])) i--;\n return i;\n};\n\n/**\n * Find the next word boundary for Option+Delete.\n */\nconst nextWordBoundary = (text: string, offset: number): number => {\n if (offset >= text.length) return text.length;\n let i = offset;\n // Skip word characters\n while (i < text.length && /\\S/.test(text[i])) i++;\n // Skip whitespace\n while (i < text.length && /\\s/.test(text[i])) i++;\n return i;\n};\n\n/**\n * Find the start of the current line (for Cmd+Backspace).\n */\nconst lineStart = (text: string, offset: number): number => {\n const before = text.slice(0, offset);\n const lastNewline = before.lastIndexOf('\\n');\n return lastNewline + 1;\n};\n\n/**\n * Find the end of the current line (for Cmd+Delete).\n */\nconst lineEnd = (text: string, offset: number): number => {\n const nextNewline = text.indexOf('\\n', offset);\n return nextNewline === -1 ? text.length : nextNewline;\n};\n\nexport const useContentEditable = ({\n value,\n defaultValue,\n onChange,\n disabled = false,\n}: UseContentEditableOptions) => {\n const initialValue = value ?? defaultValue ?? '';\n const [lines, setLines] = useState<string[]>(() => splitLines(initialValue));\n const containerRef = useRef<HTMLDivElement | null>(null);\n const pendingCaretRef = useRef<CaretPosition | null>(null);\n const isControlled = value !== undefined;\n\n // Keep a ref to the latest lines to avoid stale closures in rapid typing\n const linesRef = useRef(lines);\n linesRef.current = lines;\n\n useEffect(() => {\n if (isControlled && value !== undefined) {\n setLines(splitLines(value));\n }\n }, [value, isControlled]);\n\n const getText = () => linesRef.current.join('\\n');\n\n const getCaretPosition = (): CaretPosition | null => {\n const sel = window.getSelection();\n if (!sel?.rangeCount || !containerRef.current) return null;\n\n const range = sel.getRangeAt(0);\n const lineEls = containerRef.current.querySelectorAll('[data-line]');\n\n for (let i = 0; i < lineEls.length; i++) {\n if (lineEls[i].contains(range.startContainer)) {\n return { line: i, offset: range.startOffset };\n }\n }\n return null;\n };\n\n const getSelectionOffsets = (): {\n start: number;\n end: number;\n hasSelection: boolean;\n } | null => {\n const sel = window.getSelection();\n if (!sel?.rangeCount || !containerRef.current) return null;\n\n const range = sel.getRangeAt(0);\n const lineEls = containerRef.current.querySelectorAll('[data-line]');\n const currentLines = linesRef.current;\n\n const findOffset = (node: Node, nodeOffset: number): number => {\n for (let i = 0; i < lineEls.length; i++) {\n if (lineEls[i].contains(node)) {\n let flat = 0;\n for (let j = 0; j < i; j++) {\n flat += currentLines[j].length + 1;\n }\n return flat + Math.min(nodeOffset, currentLines[i]?.length ?? 0);\n }\n }\n // Selection is on the root container (e.g. select-all)\n if (node === containerRef.current) {\n if (nodeOffset === 0) return 0;\n return currentLines.join('\\n').length;\n }\n return 0;\n };\n\n const start = findOffset(range.startContainer, range.startOffset);\n const end = findOffset(range.endContainer, range.endOffset);\n\n return {\n start: Math.min(start, end),\n end: Math.max(start, end),\n hasSelection: !range.collapsed,\n };\n };\n\n const setCaretPosition = (pos: CaretPosition) => {\n if (!containerRef.current) return;\n\n const lineEls = containerRef.current.querySelectorAll('[data-line]');\n const lineEl = lineEls[pos.line];\n if (!lineEl) return;\n\n const editable = lineEl.querySelector('[data-editable]');\n const node =\n editable?.firstChild ?? editable ?? lineEl.firstChild ?? lineEl;\n\n const sel = window.getSelection();\n if (!sel) return;\n\n const range = document.createRange();\n const maxOff = Math.min(pos.offset, node.textContent?.length ?? 0);\n\n try {\n range.setStart(node, maxOff);\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n } catch {\n range.selectNodeContents(node);\n range.collapse(false);\n sel.removeAllRanges();\n sel.addRange(range);\n }\n };\n\n useEffect(() => {\n if (pendingCaretRef.current && containerRef.current) {\n setCaretPosition(pendingCaretRef.current);\n pendingCaretRef.current = null;\n }\n });\n\n const flatOffsetFromCaret = (pos: CaretPosition): number => {\n const currentLines = linesRef.current;\n let offset = 0;\n for (let i = 0; i < pos.line; i++) {\n offset += currentLines[i].length + 1;\n }\n return offset + pos.offset;\n };\n\n const caretFromFlatOffset = (\n flat: number,\n targetLines: string[]\n ): CaretPosition => {\n let rem = flat;\n for (let i = 0; i < targetLines.length; i++) {\n if (rem <= targetLines[i].length) {\n return { line: i, offset: rem };\n }\n rem -= targetLines[i].length + 1;\n }\n return {\n line: targetLines.length - 1,\n offset: targetLines[targetLines.length - 1]?.length ?? 0,\n };\n };\n\n const getCursorOffset = (): number => {\n const pos = getCaretPosition();\n if (!pos) return 0;\n return flatOffsetFromCaret(pos);\n };\n\n /**\n * Applies a text mutation: computes new lines, sets pending caret, updates state.\n */\n const applyTextChange = (newText: string, caretOffset: number) => {\n const newLines = splitLines(newText);\n pendingCaretRef.current = caretFromFlatOffset(caretOffset, newLines);\n setLines(newLines);\n onChange?.(newText);\n };\n\n const handleInput = () => {\n if (pendingCaretRef.current !== null) return;\n if (disabled || !containerRef.current) return;\n\n const caretPos = getCaretPosition();\n const newText = getTextFromContainer(containerRef.current);\n const newLines = splitLines(newText);\n\n pendingCaretRef.current = caretPos;\n setLines(newLines);\n onChange?.(newText);\n };\n\n const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n if (disabled) {\n e.preventDefault();\n return;\n }\n\n // Don't intercept during IME composition (CJK input)\n if (e.nativeEvent.isComposing) return;\n\n // Block undo/redo - browser would mutate DOM out of sync with React\n if ((e.metaKey || e.ctrlKey) && e.key === 'z') {\n e.preventDefault();\n return;\n }\n\n const selInfo = getSelectionOffsets();\n if (!selInfo) return;\n\n const currentText = linesRef.current.join('\\n');\n\n if (e.key === 'Enter') {\n e.preventDefault();\n const newText =\n currentText.slice(0, selInfo.start) +\n '\\n' +\n currentText.slice(selInfo.end);\n applyTextChange(newText, selInfo.start + 1);\n return;\n }\n\n if (e.key === 'Backspace') {\n e.preventDefault();\n\n if (selInfo.hasSelection) {\n const newText =\n currentText.slice(0, selInfo.start) + currentText.slice(selInfo.end);\n applyTextChange(newText, selInfo.start);\n } else {\n if (selInfo.start === 0) return;\n\n let deleteFrom: number;\n if (e.metaKey) {\n // Cmd+Backspace: delete to start of line\n deleteFrom = lineStart(currentText, selInfo.start);\n } else if (e.altKey) {\n // Option+Backspace: delete previous word\n deleteFrom = prevWordBoundary(currentText, selInfo.start);\n } else {\n // Regular backspace: delete one grapheme\n deleteFrom = prevGraphemeBoundary(currentText, selInfo.start);\n }\n\n const newText =\n currentText.slice(0, deleteFrom) + currentText.slice(selInfo.start);\n applyTextChange(newText, deleteFrom);\n }\n return;\n }\n\n if (e.key === 'Delete') {\n e.preventDefault();\n\n if (selInfo.hasSelection) {\n const newText =\n currentText.slice(0, selInfo.start) + currentText.slice(selInfo.end);\n applyTextChange(newText, selInfo.start);\n } else {\n if (selInfo.start >= currentText.length) return;\n\n let deleteTo: number;\n if (e.metaKey) {\n // Cmd+Delete: delete to end of line\n deleteTo = lineEnd(currentText, selInfo.start);\n } else if (e.altKey) {\n // Option+Delete: delete next word\n deleteTo = nextWordBoundary(currentText, selInfo.start);\n } else {\n // Regular delete: delete one grapheme\n deleteTo = nextGraphemeBoundary(currentText, selInfo.start);\n }\n\n const newText =\n currentText.slice(0, selInfo.start) + currentText.slice(deleteTo);\n applyTextChange(newText, selInfo.start);\n }\n return;\n }\n };\n\n const handleCut = (e: ClipboardEvent<HTMLDivElement>) => {\n if (disabled) {\n e.preventDefault();\n return;\n }\n\n e.preventDefault();\n const selInfo = getSelectionOffsets();\n if (!selInfo || !selInfo.hasSelection) return;\n\n const currentText = linesRef.current.join('\\n');\n const selectedText = currentText.slice(selInfo.start, selInfo.end);\n\n // Write selected text to clipboard\n e.clipboardData.setData('text/plain', selectedText);\n\n // Delete the selected text\n const newText =\n currentText.slice(0, selInfo.start) + currentText.slice(selInfo.end);\n applyTextChange(newText, selInfo.start);\n };\n\n const handlePaste = (e: ClipboardEvent<HTMLDivElement>) => {\n if (disabled) {\n e.preventDefault();\n return;\n }\n\n e.preventDefault();\n const pastedText = e.clipboardData.getData('text/plain');\n if (!pastedText) return;\n\n const selInfo = getSelectionOffsets();\n if (!selInfo) return;\n\n const currentText = linesRef.current.join('\\n');\n const newText =\n currentText.slice(0, selInfo.start) +\n pastedText +\n currentText.slice(selInfo.end);\n applyTextChange(newText, selInfo.start + pastedText.length);\n };\n\n const handleBeforeInput = (e: FormEvent<HTMLDivElement>) => {\n if (disabled) return;\n\n const inputEvent = e.nativeEvent as InputEvent;\n\n // Don't intercept during IME composition (CJK input)\n if (inputEvent.isComposing) return;\n\n const inputType = inputEvent.inputType;\n\n // Skip types handled by handleKeyDown (when keydown fires)\n if (inputType === 'insertParagraph' || inputType === 'insertLineBreak') {\n return;\n }\n\n // Handle deletions as fallback for mobile keyboards that don't fire keydown\n if (\n inputType === 'deleteContentBackward' ||\n inputType === 'deleteContentForward'\n ) {\n e.preventDefault();\n const selInfo = getSelectionOffsets();\n if (!selInfo) return;\n\n const currentText = linesRef.current.join('\\n');\n\n if (selInfo.hasSelection) {\n const newText =\n currentText.slice(0, selInfo.start) + currentText.slice(selInfo.end);\n applyTextChange(newText, selInfo.start);\n } else if (inputType === 'deleteContentBackward') {\n if (selInfo.start === 0) return;\n const deleteFrom = prevGraphemeBoundary(currentText, selInfo.start);\n const newText =\n currentText.slice(0, deleteFrom) + currentText.slice(selInfo.start);\n applyTextChange(newText, deleteFrom);\n } else {\n if (selInfo.start >= currentText.length) return;\n const deleteTo = nextGraphemeBoundary(currentText, selInfo.start);\n const newText =\n currentText.slice(0, selInfo.start) + currentText.slice(deleteTo);\n applyTextChange(newText, selInfo.start);\n }\n return;\n }\n\n // Handle spell-check replacements\n if (inputType === 'insertReplacementText') {\n e.preventDefault();\n const selInfo = getSelectionOffsets();\n if (!selInfo) return;\n\n const currentText = linesRef.current.join('\\n');\n const replacement =\n inputEvent.data ?? inputEvent.dataTransfer?.getData('text/plain') ?? '';\n const newText =\n currentText.slice(0, selInfo.start) +\n replacement +\n currentText.slice(selInfo.end);\n applyTextChange(newText, selInfo.start + replacement.length);\n return;\n }\n\n if (inputType === 'insertText' && inputEvent.data) {\n e.preventDefault();\n\n const selInfo = getSelectionOffsets();\n if (!selInfo) return;\n\n const currentText = linesRef.current.join('\\n');\n const inserted = inputEvent.data;\n const newText =\n currentText.slice(0, selInfo.start) +\n inserted +\n currentText.slice(selInfo.end);\n applyTextChange(newText, selInfo.start + inserted.length);\n }\n };\n\n const handleDrop = (e: DragEvent<HTMLDivElement>) => {\n // Block drag-and-drop to prevent uncontrolled DOM mutations\n e.preventDefault();\n };\n\n const handleDragOver = (e: DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n };\n\n return {\n lines,\n containerRef,\n getText,\n handleInput,\n handleBeforeInput,\n handleKeyDown,\n handleCut,\n handlePaste,\n handleDrop,\n handleDragOver,\n getCaretPosition,\n setCaretPosition,\n getCursorOffset,\n caretFromFlatOffset,\n };\n};\n\ntype LineProps = {\n index: number;\n text: string;\n isLast: boolean;\n ghostText?: string;\n};\n\nconst Line: FC<LineProps> = ({ index, text, isLast, ghostText }) => (\n <span data-line={index} className=\"block min-h-[1.5rem]\">\n <span data-editable>{text || '\\u200B'}</span>\n {ghostText && (\n <span\n data-ghost\n className=\"pointer-events-none select-none text-neutral\"\n aria-hidden=\"true\"\n >\n {ghostText}\n </span>\n )}\n {!isLast && <br />}\n </span>\n);\n\nexport type ContentEditableTextAreaHandle = {\n getContainer: () => HTMLDivElement | null;\n getText: () => string;\n focus: () => void;\n getCursorOffset: () => number;\n setCursorAtOffset: (offset: number) => void;\n};\n\nexport type ContentEditableTextAreaProps = Omit<\n HTMLAttributes<HTMLDivElement>,\n 'onChange' | 'defaultValue'\n> & {\n value?: string;\n defaultValue?: string;\n onChange?: (value: string) => void;\n placeholder?: string;\n disabled?: boolean;\n minRows?: number;\n maxRows?: number;\n autoSize?: boolean;\n validationStyleEnabled?: boolean;\n variant?: InputVariant | `${InputVariant}`;\n ghostText?: string;\n ghostLine?: number;\n ghostOffset?: number;\n ref?: Ref<ContentEditableTextAreaHandle>;\n dir?: 'ltr' | 'rtl' | 'auto';\n} & Omit<\n VariantProps<typeof inputVariants>,\n 'validationStyleEnabled' | 'variant'\n >;\n\nconst LINE_HEIGHT = 24;\nconst LINE_PADDING = 12;\n\nexport const ContentEditableTextArea: FC<ContentEditableTextAreaProps> = ({\n value,\n defaultValue,\n onChange,\n placeholder,\n disabled = false,\n minRows = 1,\n maxRows = 999,\n autoSize = true,\n validationStyleEnabled = false,\n variant,\n ghostText,\n ghostLine,\n ghostOffset,\n onClick,\n className,\n dir = 'auto',\n ref,\n ...rest\n}) => {\n const {\n lines,\n containerRef,\n getText,\n handleInput,\n handleBeforeInput,\n handleKeyDown,\n handleCut,\n handlePaste,\n handleDrop,\n handleDragOver,\n getCursorOffset,\n setCaretPosition,\n caretFromFlatOffset,\n } = useContentEditable({ value, defaultValue, onChange, disabled });\n\n const elRef = useRef<HTMLDivElement | null>(null);\n\n const setRef = (el: HTMLDivElement | null) => {\n elRef.current = el;\n (containerRef as MutableRefObject<HTMLDivElement | null>).current = el;\n };\n\n useImperativeHandle(ref, () => ({\n getContainer: () => elRef.current,\n getText,\n focus: () => elRef.current?.focus(),\n getCursorOffset,\n setCursorAtOffset: (offset: number) => {\n setCaretPosition(caretFromFlatOffset(offset, lines));\n },\n }));\n\n useEffect(() => {\n if (!autoSize || !elRef.current) return;\n\n const el = elRef.current;\n const max = LINE_HEIGHT * maxRows + LINE_PADDING;\n const min = LINE_HEIGHT * minRows + LINE_PADDING;\n\n el.style.height = 'auto';\n const sh = el.scrollHeight;\n el.style.height = `${Math.max(Math.min(sh, max), min)}px`;\n el.style.overflowY = sh > max ? 'auto' : 'hidden';\n }, [lines, autoSize, maxRows, minRows]);\n\n const isEmpty = lines.length === 1 && lines[0] === '';\n const hasGhost =\n ghostText && ghostLine !== undefined && ghostOffset !== undefined;\n\n return (\n <div className=\"relative w-full\">\n {isEmpty && placeholder && (\n <div\n className=\"pointer-events-none absolute inset-0 select-none px-2 py-3 text-base text-neutral-400 leading-[1.5rem] md:py-2 md:text-sm\"\n aria-hidden=\"true\"\n >\n {placeholder}\n </div>\n )}\n\n <div\n {...rest}\n ref={setRef}\n role=\"textbox\"\n aria-multiline=\"true\"\n aria-placeholder={placeholder}\n aria-disabled={disabled}\n aria-autocomplete={hasGhost ? 'inline' : undefined}\n tabIndex={disabled ? -1 : 0}\n contentEditable={!disabled}\n suppressContentEditableWarning\n dir={dir}\n onInput={handleInput}\n onBeforeInput={handleBeforeInput}\n onKeyDown={handleKeyDown}\n onCut={handleCut}\n onPaste={handlePaste}\n onDrop={handleDrop}\n onDragOver={handleDragOver}\n onClick={onClick}\n className={cn(\n 'resize-none whitespace-pre-wrap break-words outline-none',\n inputVariants({\n variant,\n validationStyleEnabled: validationStyleEnabled\n ? 'enabled'\n : 'disabled',\n }),\n autoSize && 'overflow-y-auto',\n className\n )}\n >\n {lines.map((text, i) => (\n <Line\n key={i}\n index={i}\n text={text}\n isLast={i === lines.length - 1}\n ghostText={hasGhost && ghostLine === i ? ghostText : undefined}\n />\n ))}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { ContentEditableTextArea, useContentEditable } from "./ContentEditableTextArea.mjs";
|
|
2
|
+
import { AutoCompleteTextarea } from "./AutocompleteTextArea.mjs";
|
|
1
3
|
import { TextArea } from "./TextArea.mjs";
|
|
2
4
|
import { AutoSizedTextArea } from "./AutoSizeTextArea.mjs";
|
|
3
|
-
import { AutoCompleteTextarea, useDebounce } from "./AutocompleteTextArea.mjs";
|
|
4
5
|
|
|
5
|
-
export { AutoCompleteTextarea, AutoSizedTextArea, TextArea,
|
|
6
|
+
export { AutoCompleteTextarea, AutoSizedTextArea, ContentEditableTextArea, TextArea, useContentEditable };
|
|
@@ -29,9 +29,10 @@ import { CopyButton } from "./CopyButton/index.mjs";
|
|
|
29
29
|
import { CopyToClipboard, useCopyToClipboard } from "./CopyToClipboard/index.mjs";
|
|
30
30
|
import { SwitchSelector, SwitchSelectorColor, SwitchSelectorSize } from "./SwitchSelector/index.mjs";
|
|
31
31
|
import { EditableFieldInput } from "./EditableField/EditableFieldInput.mjs";
|
|
32
|
+
import { ContentEditableTextArea, useContentEditable } from "./TextArea/ContentEditableTextArea.mjs";
|
|
33
|
+
import { AutoCompleteTextarea } from "./TextArea/AutocompleteTextArea.mjs";
|
|
32
34
|
import { TextArea } from "./TextArea/TextArea.mjs";
|
|
33
35
|
import { AutoSizedTextArea } from "./TextArea/AutoSizeTextArea.mjs";
|
|
34
|
-
import { AutoCompleteTextarea, useDebounce } from "./TextArea/AutocompleteTextArea.mjs";
|
|
35
36
|
import { EditableFieldTextArea } from "./EditableField/EditableFieldTextArea.mjs";
|
|
36
37
|
import { DictionaryEditor } from "./DictionaryEditor/DictionaryEditor.mjs";
|
|
37
38
|
import { DropDown, DropDownAlign, DropDownYAlign } from "./DropDown/index.mjs";
|
|
@@ -102,4 +103,4 @@ import { Modes } from "./ThemeSwitcherDropDown/types.mjs";
|
|
|
102
103
|
import { DesktopThemeSwitcher } from "./ThemeSwitcherDropDown/DesktopThemeSwitcher.mjs";
|
|
103
104
|
import { MobileThemeSwitcher } from "./ThemeSwitcherDropDown/MobileThemeSwitcher.mjs";
|
|
104
105
|
|
|
105
|
-
export { Accordion, AutoCompleteTextarea, AutoSizedTextArea, Avatar, Badge, BadgeColor, BadgeSize, BadgeVariant, Breadcrumb, Browser, Button, ButtonColor, ButtonSize, ButtonTextAlign, ButtonVariant, Carousel, Checkbox, CheckboxColor, CheckboxSize, ClickOutsideDiv, Code, CodeBlock, CodeDefault, CollapsibleTable, Command, CommandRoot, Container, ContainerBackground, ContainerBorderColor, ContainerGap, ContainerPadding, ContainerRoundedSize, ContainerSeparator, ContainerTransparency, ContentEditor, ContentSelector, CopyButton, CopyToClipboard, DesktopThemeSwitcher, Detail, DictionaryCreationForm, DictionaryEditor, DictionaryFieldEditor, DiscordLogo, DotPattern, DropDown, DropDownAlign, DropDownYAlign, EditableFieldInput, EditableFieldTextArea, ExpandCollapse, FacebookLogo, FileList, Flag, flags_exports as Flags, Footer, Form, GridPattern, H1, H2, H3, H4, H5, H6, HTMLRenderer, HeightResizer, HideShow, IDE, InformationTag, Input, InputIndicator, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, InputPassword, InputSize, InputVariant, InstagramLogo, KeyList, KeyPathBreadcrumb, KeyboardScreenAdapter, KeyboardShortcut, Label, LanguageBackground, LanguageSection, Link, LinkColor, LinkRoundedSize, LinkSize, LinkUnderlined, LinkVariant, LinkedInLogo, Loader, LocaleSwitcher, LocaleSwitcherContent, LocaleSwitcherContentProvider, Logo, LogoTextOnly, LogoWithText, LogoWithTextBelow, MarkdownRenderer, MaxHeightSmoother, MaxWidthSmoother, MobileThemeSwitcher, Modal, ModalSize, Modes, MultiSelect, Navbar, NumberItemsSelector, OTPInput, OTPInputContext, Pagination, PaginationSize, PaginationVariant, Popover, PopoverStatic, PopoverXAlign, PopoverYAlign, PressableSpan, ProductHuntLogo, RightDrawer, SaveForm, SearchInput, Select, SelectContent, SelectContentPosition, SelectLabel, SelectSeparator, ShowingResultsNumberItems, SocialNetworks, Spotlight, SwitchSelector, SwitchSelectorColor, SwitchSelectorSize, Tab, TabSelector, TabSelectorColor, Table, Tag, TagBackground, TagBorder, TagColor, TagRoundedSize, TagSize, Terminal, TextArea, TextEditor, TextEditorContainer, TiktokLogo, Toast, ToastAction, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport, Toaster, VersionSwitcher, VersionSwitcherProvider, WithResizer, XLogo, YoutubeLogo, badgeVariants, baseMarkdownComponents, buttonVariants, checkIsExternalLink, checkboxVariants, containerVariants, drawerManager, getCapitals, getIntlayerHTMLOptions, getIntlayerMarkdownOptions, inputSlotVariants, inputVariants, isTextChildren, linkVariants, paginationVariants, reducer, toast, traceKeys,
|
|
106
|
+
export { Accordion, AutoCompleteTextarea, AutoSizedTextArea, Avatar, Badge, BadgeColor, BadgeSize, BadgeVariant, Breadcrumb, Browser, Button, ButtonColor, ButtonSize, ButtonTextAlign, ButtonVariant, Carousel, Checkbox, CheckboxColor, CheckboxSize, ClickOutsideDiv, Code, CodeBlock, CodeDefault, CollapsibleTable, Command, CommandRoot, Container, ContainerBackground, ContainerBorderColor, ContainerGap, ContainerPadding, ContainerRoundedSize, ContainerSeparator, ContainerTransparency, ContentEditableTextArea, ContentEditor, ContentSelector, CopyButton, CopyToClipboard, DesktopThemeSwitcher, Detail, DictionaryCreationForm, DictionaryEditor, DictionaryFieldEditor, DiscordLogo, DotPattern, DropDown, DropDownAlign, DropDownYAlign, EditableFieldInput, EditableFieldTextArea, ExpandCollapse, FacebookLogo, FileList, Flag, flags_exports as Flags, Footer, Form, GridPattern, H1, H2, H3, H4, H5, H6, HTMLRenderer, HeightResizer, HideShow, IDE, InformationTag, Input, InputIndicator, InputOTP, InputOTPGroup, InputOTPSeparator, InputOTPSlot, InputPassword, InputSize, InputVariant, InstagramLogo, KeyList, KeyPathBreadcrumb, KeyboardScreenAdapter, KeyboardShortcut, Label, LanguageBackground, LanguageSection, Link, LinkColor, LinkRoundedSize, LinkSize, LinkUnderlined, LinkVariant, LinkedInLogo, Loader, LocaleSwitcher, LocaleSwitcherContent, LocaleSwitcherContentProvider, Logo, LogoTextOnly, LogoWithText, LogoWithTextBelow, MarkdownRenderer, MaxHeightSmoother, MaxWidthSmoother, MobileThemeSwitcher, Modal, ModalSize, Modes, MultiSelect, Navbar, NumberItemsSelector, OTPInput, OTPInputContext, Pagination, PaginationSize, PaginationVariant, Popover, PopoverStatic, PopoverXAlign, PopoverYAlign, PressableSpan, ProductHuntLogo, RightDrawer, SaveForm, SearchInput, Select, SelectContent, SelectContentPosition, SelectLabel, SelectSeparator, ShowingResultsNumberItems, SocialNetworks, Spotlight, SwitchSelector, SwitchSelectorColor, SwitchSelectorSize, Tab, TabSelector, TabSelectorColor, Table, Tag, TagBackground, TagBorder, TagColor, TagRoundedSize, TagSize, Terminal, TextArea, TextEditor, TextEditorContainer, TiktokLogo, Toast, ToastAction, ToastClose, ToastDescription, ToastProvider, ToastTitle, ToastViewport, Toaster, VersionSwitcher, VersionSwitcherProvider, WithResizer, XLogo, YoutubeLogo, badgeVariants, baseMarkdownComponents, buttonVariants, checkIsExternalLink, checkboxVariants, containerVariants, drawerManager, getCapitals, getIntlayerHTMLOptions, getIntlayerMarkdownOptions, inputSlotVariants, inputVariants, isTextChildren, linkVariants, paginationVariants, reducer, toast, traceKeys, useContentEditable, useCopyToClipboard, useForm, useFormField, useLocaleSwitcherContent, usePasswordManagerBadge, usePrevious, useRightDrawer, useToast, useVersionSwitcher };
|
package/dist/esm/hooks/index.mjs
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { useKeyboardDetector } from "./useKeyboardDetector.mjs";
|
|
2
|
-
import { useGetElementOrWindow } from "./useGetElementOrWindow.mjs";
|
|
3
|
-
import { useScrollY } from "./useScrollY.mjs";
|
|
4
|
-
import { usePersistedStore } from "./usePersistedStore.mjs";
|
|
5
|
-
import { useHorizontalSwipe } from "./useHorizontalSwipe.mjs";
|
|
6
|
-
import { useItemSelector } from "./useItemSelector.mjs";
|
|
7
1
|
import { calculateIsMobile, checkIsIOS, checkIsIphoneOrSafariDevice, checkIsMac, checkIsMobileScreen, checkIsMobileUserAgent, getBreakpointFromSize, useDevice } from "./useDevice.mjs";
|
|
2
|
+
import { useItemSelector } from "./useItemSelector.mjs";
|
|
3
|
+
import { usePersistedStore } from "./usePersistedStore.mjs";
|
|
8
4
|
import { useOAuth2 } from "./useAuth/useOAuth2.mjs";
|
|
9
5
|
import { useSession } from "./useAuth/useSession.mjs";
|
|
10
6
|
import { useAuth } from "./useAuth/useAuth.mjs";
|
|
11
7
|
import { useIntlayerAuth, useIntlayerOAuth } from "./useIntlayerAPI.mjs";
|
|
12
8
|
import { useAddDictionary, useAddNewAccessKey, useAddOrganization, useAddOrganizationMember, useAddPasskey, useAddProject, useAddTag, useAppQuery, useAskDocQuestion, useAskResetPassword, useAuditContentDeclaration, useAuditContentDeclarationField, useAuditContentDeclarationMetadata, useAuditScan, useAuditTag, useAutocomplete, useBitbucketAuth, useBitbucketCheckConfig, useBitbucketGetConfigFile, useBitbucketRepos, useCancelSubscription, useChangePassword, useCreateUser, useDeleteAccessKey, useDeleteDictionary, useDeleteOrganization, useDeletePasskey, useDeleteProject, useDeleteSSOProvider, useDeleteShowcaseProject, useDeleteTag, useDeleteUser, useDisableTwoFactor, useEnableTwoFactor, useGetCIConfig, useGetDictionaries, useGetDictionariesKeys, useGetDictionary, useGetDiscussions, useGetDiscussionsData, useGetEditorDictionaries, useGetNewsletterStatus, useGetOrganizations, useGetOtherShowcaseProjects, useGetPricing, useGetProjects, useGetRecursiveAuditStatus, useGetShowcaseProjectById, useGetShowcaseProjects, useGetSubscription, useGetTags, useGetUserByAccount, useGetUserById, useGetUsers, useGetVerifyEmailStatus, useGithubAuth, useGithubCheckConfig, useGithubGetAuthUrl, useGithubGetConfigFile, useGithubRepos, useGitlabAuth, useGitlabCheckConfig, useGitlabGetConfigFile, useGitlabProjects, useInfiniteGetDictionaries, useListPasskeys, useListSSOProviders, useLogin, useLogout, usePushCIConfig, usePushDictionaries, useQueryClient, useRefreshAccessKey, useRegister, useRegisterSSO, useResetPassword, useSearchDoc, useSelectOrganization, useSelectProject, useSignInMagicLink, useSignInPasskey, useSignInSSO, useStartRecursiveAudit, useSubmitShowcaseProject, useSubscribeToNewsletter, useToggleShowcaseDownvote, useToggleShowcaseUpvote, useTranslateJSONDeclaration, useTriggerBuild, useTriggerWebhook, useUnselectOrganization, useUnselectProject, useUnsubscribeFromNewsletter, useUpdateDictionary, useUpdateOrganization, useUpdateOrganizationMembers, useUpdateOrganizationMembersById, useUpdateProject, useUpdateProjectMembers, useUpdateShowcaseProject, useUpdateTag, useUpdateUser, useVerifyBackupCode, useVerifyEmail, useVerifyTotp, useWriteDictionary } from "./reactQuery.mjs";
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
9
|
+
import { useUser } from "./useUser/index.mjs";
|
|
10
|
+
import { useHorizontalSwipe } from "./useHorizontalSwipe.mjs";
|
|
15
11
|
import { useGetElementById } from "./useGetElementById.mjs";
|
|
12
|
+
import { useGetElementOrWindow } from "./useGetElementOrWindow.mjs";
|
|
16
13
|
import { useIsDarkMode } from "./useIsDarkMode.mjs";
|
|
14
|
+
import { useIsMounted } from "./useIsMounted.mjs";
|
|
15
|
+
import { useKeyboardDetector } from "./useKeyboardDetector.mjs";
|
|
17
16
|
import { useScreenWidth } from "./useScreenWidth.mjs";
|
|
18
17
|
import { useScrollBlockage } from "./useScrollBlockage/index.mjs";
|
|
19
18
|
import { useScrollDetection } from "./useScrollDetection.mjs";
|
|
20
|
-
import {
|
|
19
|
+
import { useScrollY } from "./useScrollY.mjs";
|
|
20
|
+
import { useSearch } from "./useSearch.mjs";
|
|
21
21
|
|
|
22
22
|
export { calculateIsMobile, checkIsIOS, checkIsIphoneOrSafariDevice, checkIsMac, checkIsMobileScreen, checkIsMobileUserAgent, getBreakpointFromSize, useAddDictionary, useAddNewAccessKey, useAddOrganization, useAddOrganizationMember, useAddPasskey, useAddProject, useAddTag, useAppQuery, useAskDocQuestion, useAskResetPassword, useAuditContentDeclaration, useAuditContentDeclarationField, useAuditContentDeclarationMetadata, useAuditScan, useAuditTag, useAuth, useAutocomplete, useBitbucketAuth, useBitbucketCheckConfig, useBitbucketGetConfigFile, useBitbucketRepos, useCancelSubscription, useChangePassword, useCreateUser, useDeleteAccessKey, useDeleteDictionary, useDeleteOrganization, useDeletePasskey, useDeleteProject, useDeleteSSOProvider, useDeleteShowcaseProject, useDeleteTag, useDeleteUser, useDevice, useDisableTwoFactor, useEnableTwoFactor, useGetCIConfig, useGetDictionaries, useGetDictionariesKeys, useGetDictionary, useGetDiscussions, useGetDiscussionsData, useGetEditorDictionaries, useGetElementById, useGetElementOrWindow, useGetNewsletterStatus, useGetOrganizations, useGetOtherShowcaseProjects, useGetPricing, useGetProjects, useGetRecursiveAuditStatus, useGetShowcaseProjectById, useGetShowcaseProjects, useGetSubscription, useGetTags, useGetUserByAccount, useGetUserById, useGetUsers, useGetVerifyEmailStatus, useGithubAuth, useGithubCheckConfig, useGithubGetAuthUrl, useGithubGetConfigFile, useGithubRepos, useGitlabAuth, useGitlabCheckConfig, useGitlabGetConfigFile, useGitlabProjects, useHorizontalSwipe, useInfiniteGetDictionaries, useIntlayerAuth, useIntlayerOAuth, useIsDarkMode, useIsMounted, useItemSelector, useKeyboardDetector, useListPasskeys, useListSSOProviders, useLogin, useLogout, useOAuth2, usePersistedStore, usePushCIConfig, usePushDictionaries, useQueryClient, useRefreshAccessKey, useRegister, useRegisterSSO, useResetPassword, useScreenWidth, useScrollBlockage, useScrollDetection, useScrollY, useSearch, useSearchDoc, useSelectOrganization, useSelectProject, useSession, useSignInMagicLink, useSignInPasskey, useSignInSSO, useStartRecursiveAudit, useSubmitShowcaseProject, useSubscribeToNewsletter, useToggleShowcaseDownvote, useToggleShowcaseUpvote, useTranslateJSONDeclaration, useTriggerBuild, useTriggerWebhook, useUnselectOrganization, useUnselectProject, useUnsubscribeFromNewsletter, useUpdateDictionary, useUpdateOrganization, useUpdateOrganizationMembers, useUpdateOrganizationMembersById, useUpdateProject, useUpdateProjectMembers, useUpdateShowcaseProject, useUpdateTag, useUpdateUser, useUser, useVerifyBackupCode, useVerifyEmail, useVerifyTotp, useWriteDictionary };
|
|
@@ -43,7 +43,7 @@ declare enum BadgeSize {
|
|
|
43
43
|
* @description Defines the styling variants for different badge combinations
|
|
44
44
|
*/
|
|
45
45
|
declare const badgeVariants: (props?: {
|
|
46
|
-
color?: "
|
|
46
|
+
color?: "error" | "custom" | "primary" | "secondary" | "destructive" | "neutral" | "light" | "dark" | "text" | "success";
|
|
47
47
|
variant?: "none" | "default" | "outline" | "hoverable";
|
|
48
48
|
size?: "sm" | "md" | "lg";
|
|
49
49
|
} & class_variance_authority_types0.ClassProp) => string;
|
|
@@ -61,9 +61,9 @@ declare enum ButtonTextAlign {
|
|
|
61
61
|
*/
|
|
62
62
|
declare const buttonVariants: (props?: {
|
|
63
63
|
size?: "sm" | "md" | "lg" | "xl" | "icon-sm" | "icon-md" | "icon-lg" | "icon-xl";
|
|
64
|
-
color?: "
|
|
65
|
-
roundedSize?: "
|
|
66
|
-
variant?: "input" | "none" | "default" | "outline" | "
|
|
64
|
+
color?: "error" | "custom" | "primary" | "secondary" | "destructive" | "neutral" | "card" | "light" | "dark" | "text" | "current" | "text-inverse" | "success";
|
|
65
|
+
roundedSize?: "sm" | "md" | "lg" | "xl" | "2xl" | "none" | "3xl" | "4xl" | "5xl" | "full";
|
|
66
|
+
variant?: "input" | "none" | "default" | "outline" | "link" | "invisible-link" | "hoverable" | "fade";
|
|
67
67
|
textAlign?: "left" | "center" | "right";
|
|
68
68
|
isFullWidth?: boolean;
|
|
69
69
|
} & class_variance_authority_types0.ClassProp) => string;
|
|
@@ -6,7 +6,7 @@ import { VariantProps } from "class-variance-authority";
|
|
|
6
6
|
declare const collapsibleTableVariants: (props?: {
|
|
7
7
|
size?: "sm" | "md" | "lg" | "xl" | "full";
|
|
8
8
|
variant?: "default" | "dark" | "ghost" | "outlined";
|
|
9
|
-
spacing?: "
|
|
9
|
+
spacing?: "sm" | "md" | "lg" | "none" | "auto";
|
|
10
10
|
} & class_variance_authority_types0.ClassProp) => string;
|
|
11
11
|
interface CollapsibleTableProps extends Omit<HTMLAttributes<HTMLElement>, 'title'>, VariantProps<typeof collapsibleTableVariants> {
|
|
12
12
|
/** Table title displayed in the header */
|