@gcforms/editor 1.0.6 → 1.0.8
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/CHANGELOG.md +10 -0
- package/package.json +2 -2
- package/src/Editor.tsx +0 -2
- package/src/i18n/en.json +5 -1
- package/src/i18n/fr.json +5 -1
- package/src/icons/IndentIcon.tsx +21 -0
- package/src/icons/OutdentIcon.tsx +21 -0
- package/src/plugins/ShortcutsPlugin/index.tsx +10 -0
- package/src/plugins/ShortcutsPlugin/shortcuts.ts +33 -8
- package/src/plugins/ToolbarPlugin/index.tsx +74 -8
- package/src/plugins/ToolbarPlugin/utils.ts +9 -0
- package/src/tests/Editor.cy.tsx +18 -0
- package/src/tests/Editor.test.js +5 -3
- package/src/ui/ToolTip.tsx +10 -1
- package/src/plugins/TabControlPlugin/index.tsx +0 -28
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.0.8] - 2025-09-09
|
|
9
|
+
|
|
10
|
+
- Bump yarn
|
|
11
|
+
|
|
12
|
+
## [1.0.7] - 2025-08-25
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Update indent and outdent to have buttons and new shortcuts
|
|
17
|
+
|
|
8
18
|
## [1.0.6] - 2025-07-21
|
|
9
19
|
|
|
10
20
|
### Changed
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gcforms/editor",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.8",
|
|
4
4
|
"author": "Canadian Digital Service",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
8
8
|
},
|
|
9
|
-
"packageManager": "yarn@4.9.
|
|
9
|
+
"packageManager": "yarn@4.9.4",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@lexical/react": "^0.33.1",
|
|
12
12
|
"lexical": "^0.33.1"
|
package/src/Editor.tsx
CHANGED
|
@@ -14,7 +14,6 @@ import { ListPlugin } from "@lexical/react/LexicalListPlugin";
|
|
|
14
14
|
import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin";
|
|
15
15
|
|
|
16
16
|
import ContentEditable from "./ui/ContentEditable";
|
|
17
|
-
import TabControlPlugin from "./plugins/TabControlPlugin";
|
|
18
17
|
import ToolbarPlugin from "./plugins/ToolbarPlugin";
|
|
19
18
|
import TreeViewPlugin from "./plugins/TreeViewPlugin";
|
|
20
19
|
import ListMaxIndentLevelPlugin from "./plugins/ListMaxIndentPlugin";
|
|
@@ -116,7 +115,6 @@ export const Editor = ({
|
|
|
116
115
|
/>
|
|
117
116
|
<ListPlugin />
|
|
118
117
|
<LinkPlugin />
|
|
119
|
-
<TabControlPlugin />
|
|
120
118
|
<ListMaxIndentLevelPlugin maxDepth={5} />
|
|
121
119
|
|
|
122
120
|
{floatingAnchorElem && (
|
package/src/i18n/en.json
CHANGED
|
@@ -18,5 +18,9 @@
|
|
|
18
18
|
"saveLink": "Save link",
|
|
19
19
|
"editLink": "Edit link",
|
|
20
20
|
"removeLink": "Remove link",
|
|
21
|
-
"pressEnterToEditLink": "Press Enter to edit link"
|
|
21
|
+
"pressEnterToEditLink": "Press Enter to edit link",
|
|
22
|
+
"tooltipIndent": "Indent",
|
|
23
|
+
"tooltipOutdent": "Outdent",
|
|
24
|
+
"indent": "Indent",
|
|
25
|
+
"outdent": "Outdent"
|
|
22
26
|
}
|
package/src/i18n/fr.json
CHANGED
|
@@ -18,5 +18,9 @@
|
|
|
18
18
|
"saveLink": "Enregistrer le lien",
|
|
19
19
|
"editLink": "Modifier le lien",
|
|
20
20
|
"removeLink": "Supprimer le lien",
|
|
21
|
-
"pressEnterToEditLink": "Appuyez sur Entrée pour modifier le lien"
|
|
21
|
+
"pressEnterToEditLink": "Appuyez sur Entrée pour modifier le lien",
|
|
22
|
+
"tooltipIndent": "Indentation",
|
|
23
|
+
"tooltipOutdent": "Suppression d'indentation",
|
|
24
|
+
"indent": "Indentation",
|
|
25
|
+
"outdent": "Suppression d'indentation"
|
|
22
26
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const IndentIcon = ({
|
|
2
|
+
className,
|
|
3
|
+
title = "Indent",
|
|
4
|
+
}: {
|
|
5
|
+
className?: string;
|
|
6
|
+
title?: string;
|
|
7
|
+
}) => (
|
|
8
|
+
<svg
|
|
9
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
10
|
+
width="24"
|
|
11
|
+
height="24"
|
|
12
|
+
viewBox="0 -960 960 960"
|
|
13
|
+
className={className}
|
|
14
|
+
focusable="false"
|
|
15
|
+
aria-hidden={title ? false : true}
|
|
16
|
+
role={title ? "img" : "presentation"}
|
|
17
|
+
>
|
|
18
|
+
{title && <title>{title}</title>}
|
|
19
|
+
<path d="M120-120v-80h720v80H120Zm320-160v-80h400v80H440Zm0-160v-80h400v80H440Zm0-160v-80h400v80H440ZM120-760v-80h720v80H120Zm0 440v-320l160 160-160 160Z" />
|
|
20
|
+
</svg>
|
|
21
|
+
);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const OutdentIcon = ({
|
|
2
|
+
className,
|
|
3
|
+
title = "Outdent",
|
|
4
|
+
}: {
|
|
5
|
+
className?: string;
|
|
6
|
+
title?: string;
|
|
7
|
+
}) => (
|
|
8
|
+
<svg
|
|
9
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
10
|
+
width="24"
|
|
11
|
+
height="24"
|
|
12
|
+
viewBox="0 -960 960 960"
|
|
13
|
+
className={className}
|
|
14
|
+
focusable="false"
|
|
15
|
+
aria-hidden={title ? false : true}
|
|
16
|
+
role={title ? "img" : "presentation"}
|
|
17
|
+
>
|
|
18
|
+
{title && <title>{title}</title>}
|
|
19
|
+
<path d="M120-120v-80h720v80H120Zm320-160v-80h400v80H440Zm0-160v-80h400v80H440Zm0-160v-80h400v80H440ZM120-760v-80h720v80H120Zm160 440L120-480l160-160v320Z" />
|
|
20
|
+
</svg>
|
|
21
|
+
);
|
|
@@ -15,7 +15,9 @@ import { useToolbarState } from "../../context/ToolbarContext";
|
|
|
15
15
|
import {
|
|
16
16
|
formatBulletList,
|
|
17
17
|
formatHeading,
|
|
18
|
+
formatIndent,
|
|
18
19
|
formatNumberedList,
|
|
20
|
+
formatOutdent,
|
|
19
21
|
formatParagraph,
|
|
20
22
|
} from "../ToolbarPlugin/utils";
|
|
21
23
|
import {
|
|
@@ -23,7 +25,9 @@ import {
|
|
|
23
25
|
isFormatHeading,
|
|
24
26
|
isFormatNumberedList,
|
|
25
27
|
isFormatParagraph,
|
|
28
|
+
isIndent,
|
|
26
29
|
isInsertLink,
|
|
30
|
+
isOutdent,
|
|
27
31
|
} from "./shortcuts";
|
|
28
32
|
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
29
33
|
|
|
@@ -62,6 +66,12 @@ export default function ShortcutsPlugin({
|
|
|
62
66
|
setIsLinkEditMode(!toolbarState.isLink);
|
|
63
67
|
|
|
64
68
|
editor.dispatchCommand(TOGGLE_LINK_COMMAND, url);
|
|
69
|
+
} else if (isIndent(event)) {
|
|
70
|
+
event.preventDefault();
|
|
71
|
+
formatIndent(editor);
|
|
72
|
+
} else if (isOutdent(event)) {
|
|
73
|
+
event.preventDefault();
|
|
74
|
+
formatOutdent(editor);
|
|
65
75
|
}
|
|
66
76
|
|
|
67
77
|
return false;
|
|
@@ -11,19 +11,32 @@ import { IS_APPLE } from "@lexical/utils";
|
|
|
11
11
|
//disable eslint sorting rule for quick reference to shortcuts
|
|
12
12
|
export const SHORTCUTS = Object.freeze({
|
|
13
13
|
// (Ctrl|⌘) + (Alt|Option) + <key> shortcuts
|
|
14
|
-
NORMAL: IS_APPLE ? "
|
|
15
|
-
HEADING2: IS_APPLE ? "
|
|
16
|
-
HEADING3: IS_APPLE ? "
|
|
17
|
-
BULLET_LIST: IS_APPLE ? "
|
|
18
|
-
NUMBERED_LIST: IS_APPLE ? "
|
|
14
|
+
NORMAL: IS_APPLE ? "⌘ + Opt + 0" : "Ctrl + Alt + 0",
|
|
15
|
+
HEADING2: IS_APPLE ? "⌘ + Opt + 2" : "Ctrl + Alt + 2",
|
|
16
|
+
HEADING3: IS_APPLE ? "⌘ + Opt + 3" : "Ctrl + Alt + 3",
|
|
17
|
+
BULLET_LIST: IS_APPLE ? "⌘ + Opt + 4" : "Ctrl + Alt + 4",
|
|
18
|
+
NUMBERED_LIST: IS_APPLE ? "⌘ + Opt + 5" : "Ctrl + Alt + 5",
|
|
19
19
|
|
|
20
20
|
// (Ctrl|⌘) + Shift + <key> shortcuts
|
|
21
21
|
//
|
|
22
22
|
|
|
23
23
|
// (Ctrl|⌘) + <key> shortcuts
|
|
24
|
-
BOLD: IS_APPLE ? "
|
|
25
|
-
ITALIC: IS_APPLE ? "
|
|
26
|
-
INSERT_LINK: IS_APPLE ? "
|
|
24
|
+
BOLD: IS_APPLE ? "⌘ + B" : "Ctrl + B",
|
|
25
|
+
ITALIC: IS_APPLE ? "⌘ + I" : "Ctrl + I",
|
|
26
|
+
INSERT_LINK: IS_APPLE ? "⌘ + K" : "Ctrl + K",
|
|
27
|
+
|
|
28
|
+
// Why the "⌘ + ]/[" was chosen:
|
|
29
|
+
// "⌘+Opt+]" prevents overriding the key "Opt+[" “curly quotes” on macOS
|
|
30
|
+
// "⌘+Opt+]" prevents overriding the key "⌘+[" to go back in browser history on macOS
|
|
31
|
+
// But "⌘+Opt+]" is cumbersome and not very discoverable. Making an expection
|
|
32
|
+
// to override the default browser history keys for better usability in the editor.
|
|
33
|
+
//
|
|
34
|
+
// Note: screenreaders will ignore special characeters like brackets so there is a
|
|
35
|
+
// version for for sited users and alt version for screenreader users.
|
|
36
|
+
INDENT: IS_APPLE ? "⌘ + ]" : "Ctrl + ]",
|
|
37
|
+
INDENT_ALT: IS_APPLE ? "⌘ + Right bracket" : "Ctrl + Right bracket",
|
|
38
|
+
OUTDENT: IS_APPLE ? "⌘ + [" : "Ctrl + [",
|
|
39
|
+
OUTDENT_ALT: IS_APPLE ? "⌘ + Left bracket" : "Ctrl + Left bracket",
|
|
27
40
|
});
|
|
28
41
|
|
|
29
42
|
export function controlOrMeta(metaKey: boolean, ctrlKey: boolean): boolean {
|
|
@@ -74,3 +87,15 @@ export function isInsertLink(event: KeyboardEvent): boolean {
|
|
|
74
87
|
const { code, shiftKey, altKey, metaKey, ctrlKey } = event;
|
|
75
88
|
return code === "KeyK" && !shiftKey && !altKey && controlOrMeta(metaKey, ctrlKey);
|
|
76
89
|
}
|
|
90
|
+
|
|
91
|
+
export const isIndent = (event: KeyboardEvent) => {
|
|
92
|
+
const { code, shiftKey, altKey, metaKey, ctrlKey } = event;
|
|
93
|
+
// ⌘ + ] (macOS) or Ctrl + ] (Win/Linux)
|
|
94
|
+
return code === "BracketRight" && !shiftKey && !altKey && controlOrMeta(metaKey, ctrlKey);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export function isOutdent(event: KeyboardEvent): boolean {
|
|
98
|
+
const { code, shiftKey, altKey, metaKey, ctrlKey } = event;
|
|
99
|
+
// ⌘ + [ (macOS) or Ctrl + [ (Win/Linux)
|
|
100
|
+
return code === "BracketLeft" && !shiftKey && !altKey && controlOrMeta(metaKey, ctrlKey);
|
|
101
|
+
}
|
|
@@ -29,11 +29,19 @@ import { BulletListIcon } from "../../icons/BulletListIcon";
|
|
|
29
29
|
import { NumberedListIcon } from "../../icons/NumberedListIcon";
|
|
30
30
|
import { LinkIcon } from "../../icons/LinkIcon";
|
|
31
31
|
import { useTranslation } from "../../hooks/useTranslation";
|
|
32
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
formatBulletList,
|
|
34
|
+
formatHeading,
|
|
35
|
+
formatIndent,
|
|
36
|
+
formatNumberedList,
|
|
37
|
+
formatOutdent,
|
|
38
|
+
} from "./utils";
|
|
33
39
|
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
34
40
|
import "./styles.css";
|
|
35
41
|
import { SHORTCUTS } from "../ShortcutsPlugin/shortcuts";
|
|
36
42
|
import { blockTypeToBlockName, useToolbarState } from "../../context/ToolbarContext";
|
|
43
|
+
import { IndentIcon } from "../../icons/IndentIcon";
|
|
44
|
+
import { OutdentIcon } from "../../icons/OutdentIcon";
|
|
37
45
|
|
|
38
46
|
export default function ToolbarPlugin({
|
|
39
47
|
editorId,
|
|
@@ -69,6 +77,8 @@ export default function ToolbarPlugin({
|
|
|
69
77
|
{ id: 5, txt: "bulletedList" },
|
|
70
78
|
{ id: 6, txt: "numberedList" },
|
|
71
79
|
{ id: 7, txt: "link" },
|
|
80
|
+
{ id: 8, txt: "indent" },
|
|
81
|
+
{ id: 9, txt: "outdent" },
|
|
72
82
|
]);
|
|
73
83
|
|
|
74
84
|
const itemsRef = useRef<[HTMLButtonElement] | []>([]);
|
|
@@ -210,7 +220,7 @@ export default function ToolbarPlugin({
|
|
|
210
220
|
onKeyDown={handleNav}
|
|
211
221
|
data-testid="toolbar"
|
|
212
222
|
>
|
|
213
|
-
<ToolTip text={t("tooltipFormatH2")
|
|
223
|
+
<ToolTip text={t("tooltipFormatH2")} element={keyboardShortcut(SHORTCUTS.HEADING2)}>
|
|
214
224
|
<button
|
|
215
225
|
tabIndex={currentFocusIndex == 0 ? 0 : -1}
|
|
216
226
|
ref={(el) => {
|
|
@@ -233,7 +243,7 @@ export default function ToolbarPlugin({
|
|
|
233
243
|
</button>
|
|
234
244
|
</ToolTip>
|
|
235
245
|
|
|
236
|
-
<ToolTip text={t("tooltipFormatH3")
|
|
246
|
+
<ToolTip text={t("tooltipFormatH3")} element={keyboardShortcut(SHORTCUTS.HEADING3)}>
|
|
237
247
|
<button
|
|
238
248
|
tabIndex={currentFocusIndex == 1 ? 0 : -1}
|
|
239
249
|
ref={(el) => {
|
|
@@ -256,7 +266,7 @@ export default function ToolbarPlugin({
|
|
|
256
266
|
</button>
|
|
257
267
|
</ToolTip>
|
|
258
268
|
|
|
259
|
-
<ToolTip text={t("tooltipFormatBold")
|
|
269
|
+
<ToolTip text={t("tooltipFormatBold")} element={keyboardShortcut(SHORTCUTS.BOLD)}>
|
|
260
270
|
<button
|
|
261
271
|
tabIndex={currentFocusIndex == 2 ? 0 : -1}
|
|
262
272
|
ref={(el) => {
|
|
@@ -277,7 +287,7 @@ export default function ToolbarPlugin({
|
|
|
277
287
|
</button>
|
|
278
288
|
</ToolTip>
|
|
279
289
|
|
|
280
|
-
<ToolTip text={t("tooltipFormatItalic")
|
|
290
|
+
<ToolTip text={t("tooltipFormatItalic")} element={keyboardShortcut(SHORTCUTS.ITALIC)}>
|
|
281
291
|
<button
|
|
282
292
|
tabIndex={currentFocusIndex == 3 ? 0 : -1}
|
|
283
293
|
ref={(el) => {
|
|
@@ -298,7 +308,10 @@ export default function ToolbarPlugin({
|
|
|
298
308
|
</button>
|
|
299
309
|
</ToolTip>
|
|
300
310
|
|
|
301
|
-
<ToolTip
|
|
311
|
+
<ToolTip
|
|
312
|
+
text={t("tooltipFormatBulletList")}
|
|
313
|
+
element={keyboardShortcut(SHORTCUTS.BULLET_LIST)}
|
|
314
|
+
>
|
|
302
315
|
<button
|
|
303
316
|
tabIndex={currentFocusIndex == 4 ? 0 : -1}
|
|
304
317
|
ref={(el) => {
|
|
@@ -320,7 +333,10 @@ export default function ToolbarPlugin({
|
|
|
320
333
|
</button>
|
|
321
334
|
</ToolTip>
|
|
322
335
|
|
|
323
|
-
<ToolTip
|
|
336
|
+
<ToolTip
|
|
337
|
+
text={t("tooltipFormatNumberedList")}
|
|
338
|
+
element={keyboardShortcut(SHORTCUTS.NUMBERED_LIST)}
|
|
339
|
+
>
|
|
324
340
|
<button
|
|
325
341
|
tabIndex={currentFocusIndex == 5 ? 0 : -1}
|
|
326
342
|
ref={(el) => {
|
|
@@ -342,7 +358,7 @@ export default function ToolbarPlugin({
|
|
|
342
358
|
</button>
|
|
343
359
|
</ToolTip>
|
|
344
360
|
|
|
345
|
-
<ToolTip text={t("tooltipInsertLink")
|
|
361
|
+
<ToolTip text={t("tooltipInsertLink")} element={keyboardShortcut(SHORTCUTS.INSERT_LINK)}>
|
|
346
362
|
<button
|
|
347
363
|
tabIndex={currentFocusIndex == 6 ? 0 : -1}
|
|
348
364
|
ref={(el) => {
|
|
@@ -361,7 +377,57 @@ export default function ToolbarPlugin({
|
|
|
361
377
|
<LinkIcon />
|
|
362
378
|
</button>
|
|
363
379
|
</ToolTip>
|
|
380
|
+
|
|
381
|
+
<ToolTip
|
|
382
|
+
text={t("tooltipIndent")}
|
|
383
|
+
element={keyboardShortcut(SHORTCUTS.INDENT, SHORTCUTS.INDENT_ALT)}
|
|
384
|
+
>
|
|
385
|
+
<button
|
|
386
|
+
tabIndex={currentFocusIndex == 7 ? 0 : -1}
|
|
387
|
+
ref={(el) => {
|
|
388
|
+
const index = "button-7" as unknown as number;
|
|
389
|
+
if (el && itemsRef.current) {
|
|
390
|
+
itemsRef.current[index] = el;
|
|
391
|
+
}
|
|
392
|
+
}}
|
|
393
|
+
onClick={() => formatIndent(editor)}
|
|
394
|
+
className={"toolbar-item"}
|
|
395
|
+
aria-label={t("indent")}
|
|
396
|
+
data-testid="indent-button"
|
|
397
|
+
>
|
|
398
|
+
<IndentIcon />
|
|
399
|
+
</button>
|
|
400
|
+
</ToolTip>
|
|
401
|
+
|
|
402
|
+
<ToolTip
|
|
403
|
+
text={t("tooltipOutdent")}
|
|
404
|
+
element={keyboardShortcut(SHORTCUTS.OUTDENT, SHORTCUTS.OUTDENT_ALT)}
|
|
405
|
+
>
|
|
406
|
+
<button
|
|
407
|
+
tabIndex={currentFocusIndex == 8 ? 0 : -1}
|
|
408
|
+
ref={(el) => {
|
|
409
|
+
const index = "button-8" as unknown as number;
|
|
410
|
+
if (el && itemsRef.current) {
|
|
411
|
+
itemsRef.current[index] = el;
|
|
412
|
+
}
|
|
413
|
+
}}
|
|
414
|
+
onClick={() => formatOutdent(editor)}
|
|
415
|
+
className={"toolbar-item"}
|
|
416
|
+
aria-label={t("outdent")}
|
|
417
|
+
data-testid="outdent-button"
|
|
418
|
+
>
|
|
419
|
+
<OutdentIcon />
|
|
420
|
+
</button>
|
|
421
|
+
</ToolTip>
|
|
364
422
|
</div>
|
|
365
423
|
</>
|
|
366
424
|
);
|
|
367
425
|
}
|
|
426
|
+
|
|
427
|
+
const keyboardShortcut = (text: string, ariaLabel?: string) => {
|
|
428
|
+
return (
|
|
429
|
+
<div>
|
|
430
|
+
<kbd {...(ariaLabel && { "aria-label": ariaLabel })}>{text}</kbd>
|
|
431
|
+
</div>
|
|
432
|
+
);
|
|
433
|
+
};
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
$isTextNode,
|
|
30
30
|
LexicalEditor,
|
|
31
31
|
} from "lexical";
|
|
32
|
+
import { INDENT_CONTENT_COMMAND, OUTDENT_CONTENT_COMMAND } from "lexical";
|
|
32
33
|
|
|
33
34
|
export const formatParagraph = (editor: LexicalEditor) => {
|
|
34
35
|
editor.update(() => {
|
|
@@ -170,3 +171,11 @@ export const clearFormatting = (editor: LexicalEditor) => {
|
|
|
170
171
|
}
|
|
171
172
|
});
|
|
172
173
|
};
|
|
174
|
+
|
|
175
|
+
export const formatIndent = (editor: LexicalEditor) => {
|
|
176
|
+
editor.dispatchCommand(INDENT_CONTENT_COMMAND, undefined);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export const formatOutdent = (editor: LexicalEditor) => {
|
|
180
|
+
editor.dispatchCommand(OUTDENT_CONTENT_COMMAND, undefined);
|
|
181
|
+
};
|
package/src/tests/Editor.cy.tsx
CHANGED
|
@@ -82,5 +82,23 @@ describe("<RichTextEditor />", () => {
|
|
|
82
82
|
);
|
|
83
83
|
cy.get('[id^="editor-"] a').first().contains("will be a link");
|
|
84
84
|
cy.get('[id^="editor-"] a').first().should("have.attr", "href", "https://example.com");
|
|
85
|
+
|
|
86
|
+
// Add a new numbered list to test the indent and outdent buttons
|
|
87
|
+
cy.get('[id^="editor-"]').type(
|
|
88
|
+
"{moveToEnd}{enter}{enter}{enter}This is a numbered list item 2"
|
|
89
|
+
);
|
|
90
|
+
cy.get('[data-testid="numbered-list-button"]').first().click();
|
|
91
|
+
cy.get('[id^="editor-"]')
|
|
92
|
+
.setCursorAfter("This is a numbered list item 2")
|
|
93
|
+
.type("{enter}This is another numbered list item 2");
|
|
94
|
+
cy.get('[id^="editor-"] ol li').last().contains("This is another numbered list item 2");
|
|
95
|
+
// Indent the last list item
|
|
96
|
+
cy.get('[data-testid="indent-button"]').click();
|
|
97
|
+
cy.get('[id^="editor-"] ol li').eq(2).contains("This is a numbered list item 2");
|
|
98
|
+
cy.get('[id^="editor-"] ol li ol li').last().contains("This is another numbered list item 2");
|
|
99
|
+
// Outdent the last list item (revert)
|
|
100
|
+
cy.get('[data-testid="outdent-button"]').click();
|
|
101
|
+
cy.get('[id^="editor-"] ol li').eq(2).contains("This is a numbered list item 2");
|
|
102
|
+
cy.get('[id^="editor-"] ol li').last().contains("This is another numbered list item 2");
|
|
85
103
|
});
|
|
86
104
|
});
|
package/src/tests/Editor.test.js
CHANGED
|
@@ -27,7 +27,7 @@ describe("Lexical Editor", () => {
|
|
|
27
27
|
|
|
28
28
|
const contentArea = rendered.container.querySelector('[id^="editor-"]');
|
|
29
29
|
const toolbar = screen.getByTestId("toolbar");
|
|
30
|
-
const [h2, h3, bold, italic, bulletList, numberedList, link] =
|
|
30
|
+
const [h2, h3, bold, italic, bulletList, numberedList, link, indent, outdent] =
|
|
31
31
|
within(toolbar).getAllByRole("button");
|
|
32
32
|
|
|
33
33
|
const toolbarButtons = within(toolbar).getAllByRole("button");
|
|
@@ -39,12 +39,14 @@ describe("Lexical Editor", () => {
|
|
|
39
39
|
expect(bulletList).toHaveAttribute("tabindex", "-1");
|
|
40
40
|
expect(numberedList).toHaveAttribute("tabindex", "-1");
|
|
41
41
|
expect(link).toHaveAttribute("tabindex", "-1");
|
|
42
|
+
expect(indent).toHaveAttribute("tabindex", "-1");
|
|
43
|
+
expect(outdent).toHaveAttribute("tabindex", "-1");
|
|
42
44
|
|
|
43
45
|
// Toolbar has aria-controls attribute
|
|
44
46
|
expect(toolbar).toHaveAttribute("aria-controls", contentArea.id);
|
|
45
47
|
|
|
46
|
-
// Toolbar contains
|
|
47
|
-
expect(toolbarButtons).toHaveLength(
|
|
48
|
+
// Toolbar contains 9 formatting buttons
|
|
49
|
+
expect(toolbarButtons).toHaveLength(9);
|
|
48
50
|
|
|
49
51
|
// Content area has default content and attributes
|
|
50
52
|
expect(contentArea).toHaveAttribute("aria-label", "AriaLabel");
|
package/src/ui/ToolTip.tsx
CHANGED
|
@@ -2,7 +2,15 @@
|
|
|
2
2
|
import React, { useId, Children } from "react";
|
|
3
3
|
import "./ToolTip.css";
|
|
4
4
|
|
|
5
|
-
export const ToolTip = ({
|
|
5
|
+
export const ToolTip = ({
|
|
6
|
+
children,
|
|
7
|
+
text,
|
|
8
|
+
element = null,
|
|
9
|
+
}: {
|
|
10
|
+
children: React.ReactElement;
|
|
11
|
+
text: string;
|
|
12
|
+
element?: React.JSX.Element | null;
|
|
13
|
+
}) => {
|
|
6
14
|
const id = `tooltip-${useId()}`;
|
|
7
15
|
|
|
8
16
|
return (
|
|
@@ -18,6 +26,7 @@ export const ToolTip = ({ children, text }: { children: React.ReactElement; text
|
|
|
18
26
|
})}
|
|
19
27
|
<span id={id} className="gc-tooltip">
|
|
20
28
|
{text}
|
|
29
|
+
{element}
|
|
21
30
|
</span>
|
|
22
31
|
</span>
|
|
23
32
|
);
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
2
|
-
import {
|
|
3
|
-
COMMAND_PRIORITY_EDITOR,
|
|
4
|
-
INDENT_CONTENT_COMMAND,
|
|
5
|
-
KEY_TAB_COMMAND,
|
|
6
|
-
OUTDENT_CONTENT_COMMAND,
|
|
7
|
-
} from "lexical";
|
|
8
|
-
import { useEffect } from "react";
|
|
9
|
-
|
|
10
|
-
export default function TabControlPlugin() {
|
|
11
|
-
const [editor] = useLexicalComposerContext();
|
|
12
|
-
|
|
13
|
-
useEffect(() => {
|
|
14
|
-
return editor.registerCommand(
|
|
15
|
-
KEY_TAB_COMMAND,
|
|
16
|
-
(payload) => {
|
|
17
|
-
const event: KeyboardEvent = payload;
|
|
18
|
-
event.preventDefault();
|
|
19
|
-
return editor.dispatchCommand(
|
|
20
|
-
event.shiftKey ? OUTDENT_CONTENT_COMMAND : INDENT_CONTENT_COMMAND,
|
|
21
|
-
undefined
|
|
22
|
-
);
|
|
23
|
-
},
|
|
24
|
-
COMMAND_PRIORITY_EDITOR
|
|
25
|
-
);
|
|
26
|
-
}, [editor]);
|
|
27
|
-
return null;
|
|
28
|
-
}
|