ar-design 0.2.49 → 0.2.50
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/assets/css/components/data-display/dnd/dnd.css +1 -1
- package/dist/assets/css/components/form/text-editor/styles.css +38 -0
- package/dist/components/form/button/index.d.ts +1 -1
- package/dist/components/form/button/index.js +5 -18
- package/dist/components/form/text-editor/IProps.d.ts +9 -1
- package/dist/components/form/text-editor/index.d.ts +1 -1
- package/dist/components/form/text-editor/index.js +155 -6
- package/package.json +1 -1
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
}
|
|
37
37
|
.ar-dnd > div.end-item {
|
|
38
38
|
/* Sırasıyla; Ad, Süre, Hız, Gecikme Süresi, Tekrar Sayısı, Yön, Bitiş Süreci */
|
|
39
|
-
animation: endItem ease-in-out
|
|
39
|
+
animation: endItem ease-in-out 1s 0s 1 normal both;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
.ar-dnd > div > .move {
|
|
@@ -55,3 +55,41 @@
|
|
|
55
55
|
inset: 0;
|
|
56
56
|
cursor: ns-resize;
|
|
57
57
|
}
|
|
58
|
+
|
|
59
|
+
.ar-alias-panel {
|
|
60
|
+
visibility: hidden;
|
|
61
|
+
opacity: 0;
|
|
62
|
+
position: absolute;
|
|
63
|
+
background-color: var(--white);
|
|
64
|
+
width: 200px;
|
|
65
|
+
max-height: 250px;
|
|
66
|
+
border: solid 1px var(--gray-200);
|
|
67
|
+
border-radius: var(--border-radius-lg);
|
|
68
|
+
overflow-y: auto;
|
|
69
|
+
overflow-x: hidden;
|
|
70
|
+
box-shadow: 0 10px 15px -5px rgba(var(--black-rgb), 0.1);
|
|
71
|
+
/* Sırasıyla; Ad, Süre, Hız, Gecikme Süresi, Tekrar Sayısı, Yön, Bitiş Süreci */
|
|
72
|
+
animation: opened ease-in-out 250ms 0s 1 normal both;
|
|
73
|
+
}
|
|
74
|
+
.ar-alias-panel > ul > li {
|
|
75
|
+
display: flex;
|
|
76
|
+
align-items: center;
|
|
77
|
+
gap: 0 0.5rem;
|
|
78
|
+
padding: 0 1rem;
|
|
79
|
+
height: var(--input-height);
|
|
80
|
+
cursor: pointer;
|
|
81
|
+
}
|
|
82
|
+
.ar-alias-panel > ul > li:hover {
|
|
83
|
+
background-color: var(--gray-100);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@keyframes opened {
|
|
87
|
+
from {
|
|
88
|
+
visibility: hidden;
|
|
89
|
+
opacity: 0;
|
|
90
|
+
}
|
|
91
|
+
to {
|
|
92
|
+
visibility: visible;
|
|
93
|
+
opacity: 1;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import "../../../assets/css/components/form/button/styles.css";
|
|
3
3
|
import IProps from "./IProps";
|
|
4
|
-
declare const Button: React.
|
|
4
|
+
declare const Button: React.FC<IProps>;
|
|
5
5
|
export default Button;
|
|
@@ -1,22 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import React, {
|
|
2
|
+
import React, { useRef } from "react";
|
|
3
3
|
import "../../../assets/css/components/form/button/styles.css";
|
|
4
4
|
import Utils from "../../../libs/infrastructure/shared/Utils";
|
|
5
5
|
import Tooltip from "../../feedback/tooltip";
|
|
6
|
-
|
|
7
|
-
// children,
|
|
8
|
-
// variant = "filled",
|
|
9
|
-
// shape,
|
|
10
|
-
// status = "primary",
|
|
11
|
-
// border,
|
|
12
|
-
// size = "normal",
|
|
13
|
-
// tooltip,
|
|
14
|
-
// position,
|
|
15
|
-
// icon,
|
|
16
|
-
// upperCase,
|
|
17
|
-
// ...attributes
|
|
18
|
-
// }) => {
|
|
19
|
-
const Button = forwardRef(({ children, variant = "filled", shape, status = "primary", border, size = "normal", tooltip, position, icon, upperCase, ...attributes }, ref) => {
|
|
6
|
+
const Button = ({ children, variant = "filled", shape, status = "primary", border, size = "normal", tooltip, position, icon, upperCase, ...attributes }) => {
|
|
20
7
|
// refs
|
|
21
8
|
const _button = useRef(null);
|
|
22
9
|
const _buttonClassName = ["ar-button"];
|
|
@@ -29,7 +16,7 @@ const Button = forwardRef(({ children, variant = "filled", shape, status = "prim
|
|
|
29
16
|
_buttonClassName.push(position.type);
|
|
30
17
|
_buttonClassName.push(position.inset.map((_inset) => _inset).join(" "));
|
|
31
18
|
}
|
|
32
|
-
const buttonElement = (React.createElement("button", { ref:
|
|
19
|
+
const buttonElement = (React.createElement("button", { ref: _button, ...attributes, className: _buttonClassName.map((c) => c).join(" "), onClick: (event) => {
|
|
33
20
|
// Disabled gelmesi durumunda işlem yapmasına izin verme...
|
|
34
21
|
if (attributes.disabled)
|
|
35
22
|
return;
|
|
@@ -39,7 +26,7 @@ const Button = forwardRef(({ children, variant = "filled", shape, status = "prim
|
|
|
39
26
|
if (_current && !_current.classList.contains(addClass)) {
|
|
40
27
|
// Sınıf ekleniyor...
|
|
41
28
|
_current.classList.add(addClass);
|
|
42
|
-
// Sınıf
|
|
29
|
+
// Sınıf 500 milisaniye sonra kaldırlacak.
|
|
43
30
|
setTimeout(() => _current.classList.remove(addClass), 750);
|
|
44
31
|
}
|
|
45
32
|
})();
|
|
@@ -49,6 +36,6 @@ const Button = forwardRef(({ children, variant = "filled", shape, status = "prim
|
|
|
49
36
|
icon?.element,
|
|
50
37
|
React.createElement("span", null, !shape ? (typeof children === "string" && upperCase ? children.toLocaleUpperCase() : children) : ""))));
|
|
51
38
|
return !tooltip ? (buttonElement) : (React.createElement(Tooltip, { text: tooltip.text, direction: tooltip.direction }, buttonElement));
|
|
52
|
-
}
|
|
39
|
+
};
|
|
53
40
|
Button.displayName = "Button";
|
|
54
41
|
export default Button;
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import { IValidation } from "../../../libs/types/IGlobalProps";
|
|
2
|
-
interface IProps extends IValidation {
|
|
2
|
+
interface IProps<T> extends IValidation {
|
|
3
3
|
name?: string;
|
|
4
4
|
value?: string;
|
|
5
5
|
onChange: (value?: string) => void;
|
|
6
|
+
dynamicList?: {
|
|
7
|
+
render: {
|
|
8
|
+
display: keyof T;
|
|
9
|
+
items: T[];
|
|
10
|
+
};
|
|
11
|
+
triggerKey?: string;
|
|
12
|
+
onTagged?: (tagged: any[]) => void;
|
|
13
|
+
};
|
|
6
14
|
placeholder?: string;
|
|
7
15
|
height?: number;
|
|
8
16
|
multilang?: boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "../../../assets/css/components/form/text-editor/styles.css";
|
|
2
2
|
import IProps from "./IProps";
|
|
3
3
|
import React from "react";
|
|
4
|
-
declare const TextEditor: React.
|
|
4
|
+
declare const TextEditor: <T extends object>({ name, value, onChange, dynamicList, height, validation, }: IProps<T>) => React.JSX.Element;
|
|
5
5
|
export default TextEditor;
|
|
@@ -4,15 +4,28 @@ import { ARIcon } from "../../icons";
|
|
|
4
4
|
import Button from "../button";
|
|
5
5
|
import React, { useEffect, useRef, useState } from "react";
|
|
6
6
|
import Utils from "../../../libs/infrastructure/shared/Utils";
|
|
7
|
-
|
|
7
|
+
import ReactDOM from "react-dom";
|
|
8
|
+
const TextEditor = ({ name, value, onChange, dynamicList,
|
|
9
|
+
// placeholder,
|
|
10
|
+
height,
|
|
11
|
+
// multilang,
|
|
12
|
+
validation, }) => {
|
|
8
13
|
// refs
|
|
9
14
|
const _container = useRef(null);
|
|
10
15
|
const _arIframe = useRef(null);
|
|
11
16
|
const _onChange = useRef(onChange);
|
|
12
17
|
const _onChangeTimeOut = useRef(null);
|
|
18
|
+
// refs -> Alias Panel
|
|
19
|
+
const _target = useRef(null);
|
|
20
|
+
const _arAliasPanel = useRef(null);
|
|
13
21
|
// states
|
|
14
22
|
const [iframe, setIframe] = useState(null);
|
|
15
23
|
const [iframeDocument, setIframeDocument] = useState(undefined);
|
|
24
|
+
// states -> Data
|
|
25
|
+
const [tagged, setTagged] = useState([]);
|
|
26
|
+
// states -> Alias Panel
|
|
27
|
+
const [atRect, setAtRect] = useState(null);
|
|
28
|
+
const [filtered, setFiltered] = useState(null);
|
|
16
29
|
// variables
|
|
17
30
|
const toolbarButtons = [
|
|
18
31
|
{ command: "bold", icon: "Bold", tooltip: `Bold (${Utils.GetOSShortCutIcons()} + B)` },
|
|
@@ -35,7 +48,10 @@ const TextEditor = ({ name, value, onChange, placeholder, height, multilang, val
|
|
|
35
48
|
iframeDoc.execCommand(command, true, undefined);
|
|
36
49
|
};
|
|
37
50
|
const handleFocus = () => _arIframe.current?.classList.add("focused");
|
|
38
|
-
const handleBlur = () =>
|
|
51
|
+
const handleBlur = () => {
|
|
52
|
+
_arIframe.current?.classList.remove("focused");
|
|
53
|
+
// setAtRect(null);
|
|
54
|
+
};
|
|
39
55
|
const handleMouseDown = () => {
|
|
40
56
|
// Resizebar a tıklandığında iframe içerisinde bulunan window'un event listenerı olmadığı için orada resize çalışmayacaktır.
|
|
41
57
|
// Bu yüzden önüne bir duvar örüyoruz ve mevcut sayfanın window'unda işlem yapmaya devam ediyor.
|
|
@@ -55,6 +71,44 @@ const TextEditor = ({ name, value, onChange, placeholder, height, multilang, val
|
|
|
55
71
|
_arIframe.current.style.height = `${height}px`;
|
|
56
72
|
}
|
|
57
73
|
};
|
|
74
|
+
// methods -> Alias Panel
|
|
75
|
+
const handleBackSpaceKeydown = (event) => {
|
|
76
|
+
const key = event.key;
|
|
77
|
+
if (key === "Backspace" || key === "Delete") {
|
|
78
|
+
const selection = _arIframe.current?.contentDocument?.getSelection();
|
|
79
|
+
if (!selection || selection.rangeCount === 0)
|
|
80
|
+
return;
|
|
81
|
+
const range = selection.getRangeAt(0);
|
|
82
|
+
// 1. Çoklu seçim varsa: clone edip span'ları bul.
|
|
83
|
+
const contents = range.cloneContents();
|
|
84
|
+
const multiSpans = contents.querySelectorAll("span[data-tag]");
|
|
85
|
+
if (multiSpans.length > 0) {
|
|
86
|
+
event.preventDefault();
|
|
87
|
+
const tagsToRemove = [];
|
|
88
|
+
multiSpans.forEach((span) => {
|
|
89
|
+
const tag = span.getAttribute("data-tag");
|
|
90
|
+
if (tag)
|
|
91
|
+
tagsToRemove.push(tag);
|
|
92
|
+
});
|
|
93
|
+
// DOM'dan temizle
|
|
94
|
+
range.deleteContents();
|
|
95
|
+
// State'ten temizle
|
|
96
|
+
setTagged((prev) => prev.filter((x) => tagsToRemove.every((tag) => !JSON.stringify(x).includes(tag))));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// 2. Tekli seçim: caret bir span içindeyse sil
|
|
100
|
+
const node = selection.anchorNode;
|
|
101
|
+
const container = node?.parentElement;
|
|
102
|
+
if (container?.tagName === "SPAN" && container.dataset.tag) {
|
|
103
|
+
event.preventDefault();
|
|
104
|
+
const tag = container.dataset.tag;
|
|
105
|
+
// DOM'dan sil
|
|
106
|
+
container.remove();
|
|
107
|
+
// State'ten kaldır
|
|
108
|
+
setTagged((prev) => prev.filter((x) => !JSON.stringify(x).includes(tag ?? "")));
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
58
112
|
// useEffects
|
|
59
113
|
useEffect(() => {
|
|
60
114
|
// Iframe Document yüklendikten sonra çalışacaktır.
|
|
@@ -94,7 +148,32 @@ const TextEditor = ({ name, value, onChange, placeholder, height, multilang, val
|
|
|
94
148
|
if (_onChangeTimeOut.current)
|
|
95
149
|
clearTimeout(_onChangeTimeOut.current);
|
|
96
150
|
_onChangeTimeOut.current = setTimeout(() => {
|
|
97
|
-
mutationsList.forEach(() => {
|
|
151
|
+
mutationsList.forEach((record) => {
|
|
152
|
+
const target = record.target;
|
|
153
|
+
_target.current = record.target;
|
|
154
|
+
if (dynamicList) {
|
|
155
|
+
if (target.nodeType === Node.TEXT_NODE) {
|
|
156
|
+
const text = target.textContent ?? "";
|
|
157
|
+
const atIndex = text.lastIndexOf(dynamicList?.triggerKey ?? "@");
|
|
158
|
+
if (atIndex !== -1) {
|
|
159
|
+
const afterAt = text.slice(atIndex + 1); // @ sonrası metin.
|
|
160
|
+
const hasWhitespace = /\s/.test(afterAt);
|
|
161
|
+
if (!hasWhitespace) {
|
|
162
|
+
const selection = _iframeDocument?.getSelection();
|
|
163
|
+
if (selection && selection.rangeCount > 0) {
|
|
164
|
+
const range = selection.getRangeAt(0).cloneRange();
|
|
165
|
+
const rect = range.getBoundingClientRect();
|
|
166
|
+
range.collapse(true);
|
|
167
|
+
setAtRect(rect);
|
|
168
|
+
setFiltered(afterAt);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Eğer @ yoksa ya da boşluk varsa paneli kapat.
|
|
174
|
+
setAtRect(null);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
98
177
|
_iframeDocument?.body.innerHTML === "<br>"
|
|
99
178
|
? _onChange.current(undefined)
|
|
100
179
|
: _onChange.current(_iframeDocument.body.innerHTML);
|
|
@@ -105,12 +184,21 @@ const TextEditor = ({ name, value, onChange, placeholder, height, multilang, val
|
|
|
105
184
|
observer.observe(_iframeDocument.body, { childList: true, subtree: true, characterData: true, attributes: true });
|
|
106
185
|
_iframeDocument.body.addEventListener("focus", handleFocus);
|
|
107
186
|
_iframeDocument.body.addEventListener("blur", handleBlur);
|
|
187
|
+
if (dynamicList) {
|
|
188
|
+
_iframeDocument.body.addEventListener("keydown", handleBackSpaceKeydown);
|
|
189
|
+
}
|
|
108
190
|
return () => {
|
|
109
191
|
observer.disconnect();
|
|
110
|
-
_iframeDocument
|
|
111
|
-
_iframeDocument
|
|
192
|
+
_iframeDocument.body.removeEventListener("focus", handleFocus);
|
|
193
|
+
_iframeDocument.body.removeEventListener("blur", handleBlur);
|
|
194
|
+
if (dynamicList) {
|
|
195
|
+
_iframeDocument.body.removeEventListener("keydown", handleBackSpaceKeydown);
|
|
196
|
+
}
|
|
112
197
|
};
|
|
113
198
|
}, [iframe]);
|
|
199
|
+
useEffect(() => {
|
|
200
|
+
dynamicList?.onTagged && dynamicList?.onTagged(tagged);
|
|
201
|
+
}, [tagged]);
|
|
114
202
|
useEffect(() => {
|
|
115
203
|
if (!_arIframe.current)
|
|
116
204
|
return;
|
|
@@ -128,6 +216,67 @@ const TextEditor = ({ name, value, onChange, placeholder, height, multilang, val
|
|
|
128
216
|
text: tooltip,
|
|
129
217
|
}, onClick: () => execCommand(command) })))),
|
|
130
218
|
React.createElement("div", { className: "resize", onMouseDown: handleMouseDown }),
|
|
131
|
-
validation?.text && React.createElement("span", { className: "validation" }, validation.text)
|
|
219
|
+
validation?.text && React.createElement("span", { className: "validation" }, validation.text),
|
|
220
|
+
dynamicList &&
|
|
221
|
+
atRect &&
|
|
222
|
+
ReactDOM.createPortal(React.createElement("div", { ref: _arAliasPanel, className: "ar-alias-panel", style: {
|
|
223
|
+
top: (_arIframe.current?.getBoundingClientRect().top ?? 0) + atRect.top + 20,
|
|
224
|
+
left: (_arIframe.current?.getBoundingClientRect().left ?? 0) + atRect.left,
|
|
225
|
+
}, onClick: () => {
|
|
226
|
+
setAtRect(null);
|
|
227
|
+
} },
|
|
228
|
+
React.createElement("ul", null, dynamicList &&
|
|
229
|
+
dynamicList.render.items
|
|
230
|
+
// .filter((fItem) => !tagged.some((t: T) => JSON.stringify(fItem) === JSON.stringify(t)))
|
|
231
|
+
.filter((item) => {
|
|
232
|
+
const displayText = item[dynamicList.render.display] ?? "";
|
|
233
|
+
return displayText.toLowerCase().includes(filtered.toLowerCase());
|
|
234
|
+
})
|
|
235
|
+
.map((item, index) => (React.createElement("li", { key: index, onClick: (event) => {
|
|
236
|
+
event.stopPropagation();
|
|
237
|
+
const selection = iframeDocument?.getSelection();
|
|
238
|
+
const target = _target.current;
|
|
239
|
+
if (selection && selection.rangeCount > 0 && target && target.nodeType === Node.TEXT_NODE) {
|
|
240
|
+
const text = target.textContent ?? "";
|
|
241
|
+
const atIndex = text.lastIndexOf(dynamicList?.triggerKey ?? "@");
|
|
242
|
+
if (atIndex !== -1) {
|
|
243
|
+
const range = selection.getRangeAt(0).cloneRange();
|
|
244
|
+
range.setStart(target, atIndex);
|
|
245
|
+
range.setEnd(target, text.length);
|
|
246
|
+
range.deleteContents();
|
|
247
|
+
const itemText = item[dynamicList.render.display] ?? "";
|
|
248
|
+
const span = iframeDocument?.createElement("span");
|
|
249
|
+
const spaceNode = iframeDocument?.createTextNode(" \u200B");
|
|
250
|
+
if (span && spaceNode && iframeDocument) {
|
|
251
|
+
span.setAttribute("data-tag", `${itemText}`);
|
|
252
|
+
span.style.backgroundColor = "rgba(114, 15, 103, .1)";
|
|
253
|
+
span.style.padding = "0 5px";
|
|
254
|
+
span.style.borderRadius = "2px";
|
|
255
|
+
span.style.color = "#720f67";
|
|
256
|
+
span.style.fontWeight = "bold";
|
|
257
|
+
span.textContent = `@${itemText}`;
|
|
258
|
+
// Yeni bir wrapper fragment oluştur
|
|
259
|
+
const fragment = iframeDocument.createDocumentFragment();
|
|
260
|
+
fragment.appendChild(span);
|
|
261
|
+
fragment.appendChild(spaceNode); // görünmez boş node, ama yazılabilir
|
|
262
|
+
range.insertNode(fragment);
|
|
263
|
+
// Cursor'u spaceNode’un sonuna yerleştir
|
|
264
|
+
const newRange = iframeDocument.createRange();
|
|
265
|
+
newRange.setStart(spaceNode, spaceNode.length);
|
|
266
|
+
newRange.collapse(true);
|
|
267
|
+
selection.removeAllRanges();
|
|
268
|
+
selection.addRange(newRange);
|
|
269
|
+
// Focus zaten varsa sorun olmayacak
|
|
270
|
+
const activeEl = iframeDocument?.activeElement;
|
|
271
|
+
activeEl?.focus();
|
|
272
|
+
}
|
|
273
|
+
setAtRect(null);
|
|
274
|
+
setTagged((prev) => {
|
|
275
|
+
const exists = prev.some((i) => JSON.stringify(i) === JSON.stringify(item));
|
|
276
|
+
return exists ? prev : [...prev, item];
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
} }, item[dynamicList.render.display] ?? ""))))), document.body)));
|
|
132
281
|
};
|
|
133
282
|
export default TextEditor;
|