torch-glare 2.4.0 → 2.4.2
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/README.md +8 -0
- package/apps/lib/components/ActionButton.tsx +7 -12
- package/apps/lib/components/ActionsGroup.tsx +4 -15
- package/apps/lib/components/AlertDialog.tsx +173 -191
- package/apps/lib/components/Avatar.tsx +13 -16
- package/apps/lib/components/Badge.tsx +1 -3
- package/apps/lib/components/BadgeField.tsx +76 -71
- package/apps/lib/components/Breadcrumb.tsx +49 -85
- package/apps/lib/components/Button.tsx +21 -20
- package/apps/lib/components/ButtonGroup.tsx +31 -41
- package/apps/lib/components/Calendar.tsx +51 -29
- package/apps/lib/components/Card.tsx +40 -24
- package/apps/lib/components/Checkbox.tsx +9 -16
- package/apps/lib/components/ColorPicker.tsx +441 -0
- package/apps/lib/components/ConclusionHeader.tsx +148 -0
- package/apps/lib/components/ContextMenu.tsx +97 -96
- package/apps/lib/components/CountBadge.tsx +4 -6
- package/apps/lib/components/DataTable.tsx +196 -216
- package/apps/lib/components/DataViews/DataViewRadio.tsx +1 -6
- package/apps/lib/components/DataViews/DataViewsConfigPanel.tsx +13 -47
- package/apps/lib/components/DataViews/DataViewsHeader.tsx +1 -3
- package/apps/lib/components/DataViews/DataViewsLayout.tsx +5 -12
- package/apps/lib/components/DataViews/FilterPanel.tsx +155 -149
- package/apps/lib/components/DataViews/HeaderSearch.tsx +1 -5
- package/apps/lib/components/DataViews/InboxView.tsx +22 -68
- package/apps/lib/components/DataViews/InboxViewCard.tsx +68 -77
- package/apps/lib/components/DataViews/KanbanView.tsx +8 -25
- package/apps/lib/components/DataViews/PanelControls.tsx +3 -13
- package/apps/lib/components/DataViews/SettingsPanel.tsx +83 -89
- package/apps/lib/components/DataViews/TableView.tsx +8 -28
- package/apps/lib/components/DataViews/TreeView.tsx +12 -40
- package/apps/lib/components/DataViews/badgeAdapter.ts +32 -28
- package/apps/lib/components/DataViews/fieldRenderers.tsx +106 -141
- package/apps/lib/components/DataViews/filters/DatePickerRangeFilter.tsx +30 -23
- package/apps/lib/components/DataViews/filters/DateRangePopover.tsx +39 -32
- package/apps/lib/components/DataViews/filters/PresetChips.tsx +15 -15
- package/apps/lib/components/DataViews/filters/RangeSliderWithInputs.tsx +69 -58
- package/apps/lib/components/DataViews/index.ts +26 -26
- package/apps/lib/components/DataViews/tree/TreeDrawer.tsx +12 -16
- package/apps/lib/components/DataViews/tree/TreeSidebar.tsx +28 -31
- package/apps/lib/components/DataViews/types.ts +104 -115
- package/apps/lib/components/DatePicker.tsx +200 -196
- package/apps/lib/components/Dialog.tsx +92 -105
- package/apps/lib/components/Divider.tsx +21 -24
- package/apps/lib/components/Drawer.tsx +124 -142
- package/apps/lib/components/DropdownMenu.tsx +131 -171
- package/apps/lib/components/FieldHint.tsx +4 -11
- package/apps/lib/components/Form.tsx +146 -155
- package/apps/lib/components/FormBuilder/DisplayField.tsx +40 -0
- package/apps/lib/components/FormBuilder/context.ts +71 -0
- package/apps/lib/components/FormBuilder/fields/ChoiceFields.tsx +65 -0
- package/apps/lib/components/FormBuilder/fields/ColorField.tsx +76 -0
- package/apps/lib/components/FormBuilder/fields/CustomField.tsx +16 -0
- package/apps/lib/components/FormBuilder/fields/DateField.tsx +34 -0
- package/apps/lib/components/FormBuilder/fields/FieldArray.tsx +79 -0
- package/apps/lib/components/FormBuilder/fields/FieldShell.tsx +174 -0
- package/apps/lib/components/FormBuilder/fields/FileField.tsx +40 -0
- package/apps/lib/components/FormBuilder/fields/OptionListFields.tsx +139 -0
- package/apps/lib/components/FormBuilder/fields/OtpField.tsx +32 -0
- package/apps/lib/components/FormBuilder/fields/PhoneField.tsx +82 -0
- package/apps/lib/components/FormBuilder/fields/RichTextEditorField.tsx +37 -0
- package/apps/lib/components/FormBuilder/fields/SelectField.tsx +101 -0
- package/apps/lib/components/FormBuilder/fields/SignatureField.tsx +175 -0
- package/apps/lib/components/FormBuilder/fields/SliderField.tsx +72 -0
- package/apps/lib/components/FormBuilder/fields/SwitchBoxField.tsx +42 -0
- package/apps/lib/components/FormBuilder/fields/TableField.tsx +312 -0
- package/apps/lib/components/FormBuilder/fields/TextField.tsx +222 -0
- package/apps/lib/components/FormBuilder/fields/TreeSelectField.tsx +49 -0
- package/apps/lib/components/FormBuilder/fields/countries.ts +303 -0
- package/apps/lib/components/FormBuilder/fields/index.ts +25 -0
- package/apps/lib/components/FormBuilder/form-builder.tsx +292 -0
- package/apps/lib/components/FormBuilder/header.tsx +105 -0
- package/apps/lib/components/FormBuilder/index.ts +37 -0
- package/apps/lib/components/FormBuilder/numberFormat.ts +16 -0
- package/apps/lib/components/FormBuilder/stepper.tsx +217 -0
- package/apps/lib/components/FormBuilder/submit.tsx +41 -0
- package/apps/lib/components/FormBuilder/types.ts +264 -0
- package/apps/lib/components/FormBuilder/viewFormat.tsx +137 -0
- package/apps/lib/components/FormRenderer/FormDrawer.tsx +121 -0
- package/apps/lib/components/FormRenderer/form-renderer.tsx +113 -0
- package/apps/lib/components/FormRenderer/index.ts +9 -0
- package/apps/lib/components/FormRenderer/types.ts +79 -0
- package/apps/lib/components/FormStepper.tsx +9 -39
- package/apps/lib/components/FormSummary.tsx +282 -0
- package/apps/lib/components/HeaderBar.tsx +1 -9
- package/apps/lib/components/ImageAttachment.tsx +106 -93
- package/apps/lib/components/InnerLabelField.tsx +20 -16
- package/apps/lib/components/Input.tsx +171 -172
- package/apps/lib/components/InputField.tsx +44 -45
- package/apps/lib/components/InputOTP.tsx +68 -69
- package/apps/lib/components/Label.tsx +61 -58
- package/apps/lib/components/LabelField.tsx +4 -5
- package/apps/lib/components/LabeledCheckBox.tsx +8 -12
- package/apps/lib/components/LabeledRadio.tsx +7 -5
- package/apps/lib/components/LinkButton.tsx +2 -9
- package/apps/lib/components/LoginButton.tsx +4 -9
- package/apps/lib/components/PasswordLevel.tsx +5 -5
- package/apps/lib/components/Popover.tsx +15 -33
- package/apps/lib/components/ProfileMenu.tsx +67 -72
- package/apps/lib/components/Radio.tsx +1 -5
- package/apps/lib/components/RadioCard.tsx +11 -28
- package/apps/lib/components/ScrollArea.tsx +45 -43
- package/apps/lib/components/SearchField.tsx +49 -39
- package/apps/lib/components/SearchableSelect.tsx +16 -16
- package/apps/lib/components/SearchableTable.tsx +108 -118
- package/apps/lib/components/SearchableTree.tsx +469 -0
- package/apps/lib/components/SearchableTreeDialog.tsx +493 -0
- package/apps/lib/components/SectionBlock.tsx +19 -23
- package/apps/lib/components/Select.tsx +80 -186
- package/apps/lib/components/SimpleSelect.tsx +196 -187
- package/apps/lib/components/Skeleton.tsx +9 -12
- package/apps/lib/components/SlideDatePicker.tsx +215 -166
- package/apps/lib/components/SpinLoading.tsx +33 -49
- package/apps/lib/components/Stepper.tsx +13 -56
- package/apps/lib/components/Switch.tsx +8 -10
- package/apps/lib/components/TabFormItem.tsx +3 -12
- package/apps/lib/components/TabSwitch.tsx +7 -34
- package/apps/lib/components/Table.tsx +60 -75
- package/apps/lib/components/{ChartBlockTool.ts → TextEditor/ChartBlockTool.ts} +17 -52
- package/apps/lib/components/TextEditor/RichTextField.tsx +46 -0
- package/apps/lib/components/{TableDnDWrapper.ts → TextEditor/TableDnDWrapper.ts} +15 -48
- package/apps/lib/components/{TextEditor.tsx → TextEditor/TextEditor.tsx} +156 -113
- package/apps/lib/components/TextEditor/TextEditorToolbar.tsx +429 -0
- package/apps/lib/components/TextEditor/editor-tools/AlignmentTune.ts +70 -0
- package/apps/lib/components/TextEditor/editor-tools/ColorInlineTool.ts +50 -0
- package/apps/lib/components/TextEditor/editor-tools/StrikethroughInlineTool.ts +48 -0
- package/apps/lib/components/TextEditor/editor-tools/inlineFormat.ts +98 -0
- package/apps/lib/{types → components/TextEditor}/editorjs.d.ts +19 -0
- package/apps/lib/components/TextEditor/index.ts +7 -0
- package/apps/lib/{utils → components/TextEditor}/markdownParser.ts +2 -7
- package/apps/lib/components/Textarea.tsx +9 -13
- package/apps/lib/components/Timeline.tsx +21 -41
- package/apps/lib/components/Toast.tsx +83 -83
- package/apps/lib/components/Toggle.tsx +112 -114
- package/apps/lib/components/ToggleButton.tsx +1 -1
- package/apps/lib/components/Tooltip.tsx +24 -19
- package/apps/lib/components/TransparentLabel.tsx +61 -62
- package/apps/lib/components/TreeDropDown.tsx +86 -52
- package/apps/lib/components/TreeFolder/TreeFolder.tsx +15 -48
- package/apps/lib/components/TreeFolder/TreeFolderBreadcrumb.tsx +16 -21
- package/apps/lib/components/TreeFolder/TreeFolderRow.tsx +9 -20
- package/apps/lib/components/TreeFolder/TreeFolderStyles.tsx +3 -3
- package/apps/lib/components/TreeFolder/icons.tsx +14 -17
- package/apps/lib/components/TreeFolder/index.ts +15 -8
- package/apps/lib/components/TreeFolder/treeFolderUtils.ts +47 -52
- package/apps/lib/components/TreeFolder/types.ts +32 -32
- package/apps/lib/components/TreeFolder/useTreeFolderDnD.ts +117 -123
- package/apps/lib/hooks/useActiveTreeItem.ts +52 -55
- package/apps/lib/hooks/useClickOutside.ts +21 -20
- package/apps/lib/hooks/useDataViewsState.ts +86 -80
- package/apps/lib/hooks/useIsMobile.ts +12 -12
- package/apps/lib/hooks/useResize.ts +54 -65
- package/apps/lib/hooks/useTagSelection.ts +201 -201
- package/apps/lib/layouts/CNLayout.tsx +181 -140
- package/apps/lib/layouts/DataViewCard.tsx +4 -18
- package/apps/lib/layouts/FieldSection.tsx +2 -6
- package/apps/lib/layouts/TreeSubLayout.tsx +24 -38
- package/apps/lib/providers/ThemeProvider.tsx +8 -23
- package/apps/lib/registry.json +997 -0
- package/apps/lib/tsconfig.json +9 -0
- package/apps/lib/tsconfig.tsbuildinfo +1 -0
- package/apps/lib/utils/cn.ts +1 -1
- package/apps/lib/utils/color.ts +175 -0
- package/apps/lib/utils/dataViews/columnUtils.ts +46 -44
- package/apps/lib/utils/dataViews/fieldUtils.ts +70 -71
- package/apps/lib/utils/dataViews/nestedDataUtils.tsx +129 -122
- package/apps/lib/utils/dataViews/pathUtils.ts +74 -67
- package/apps/lib/utils/dataViews/rangeUtils.ts +121 -112
- package/apps/lib/utils/dataViews/treeUtils.ts +204 -211
- package/apps/lib/utils/dateFormat.ts +64 -54
- package/apps/lib/utils/resize.ts +21 -25
- package/apps/lib/utils/types.ts +13 -13
- package/dist/bin/index.js +8 -2
- package/dist/bin/index.js.map +1 -1
- package/dist/src/shared/getAvailableFiles.d.ts.map +1 -1
- package/dist/src/shared/getAvailableFiles.js +6 -1
- package/dist/src/shared/getAvailableFiles.js.map +1 -1
- package/dist/src/shared/loadRegistry.d.ts +7 -0
- package/dist/src/shared/loadRegistry.d.ts.map +1 -0
- package/dist/src/shared/loadRegistry.js +31 -0
- package/dist/src/shared/loadRegistry.js.map +1 -0
- package/dist/src/shared/resolveInstallPlan.d.ts +12 -0
- package/dist/src/shared/resolveInstallPlan.d.ts.map +1 -0
- package/dist/src/shared/resolveInstallPlan.js +39 -0
- package/dist/src/shared/resolveInstallPlan.js.map +1 -0
- package/dist/src/types/main.d.ts +24 -0
- package/dist/src/types/main.d.ts.map +1 -1
- package/docs/README.md +10 -2
- package/docs/components/action-button.md +30 -24
- package/docs/components/actions-group.md +26 -20
- package/docs/components/alert-dialog.md +45 -45
- package/docs/components/avatar.md +6 -2
- package/docs/components/badge-field.md +6 -2
- package/docs/components/badge.md +7 -1
- package/docs/components/breadcrumb.md +15 -40
- package/docs/components/button-group.md +13 -10
- package/docs/components/button.md +58 -33
- package/docs/components/calendar.md +26 -19
- package/docs/components/card.md +23 -18
- package/docs/components/chart-block-tool.md +129 -0
- package/docs/components/checkbox.md +16 -8
- package/docs/components/cn-layout.md +7 -1
- package/docs/components/color-picker.md +101 -0
- package/docs/components/conclusion-header.md +80 -0
- package/docs/components/context-menu.md +18 -25
- package/docs/components/count-badge.md +7 -1
- package/docs/components/data-table.md +45 -33
- package/docs/components/data-views-config-panel.md +15 -11
- package/docs/components/data-views-layout.md +13 -16
- package/docs/components/date-picker.md +30 -23
- package/docs/components/dialog.md +43 -42
- package/docs/components/divider.md +10 -4
- package/docs/components/drawer.md +161 -135
- package/docs/components/dropdown-menu.md +25 -34
- package/docs/components/field-hint.md +37 -25
- package/docs/components/field-section.md +9 -3
- package/docs/components/form-builder.md +270 -0
- package/docs/components/form-renderer.md +228 -0
- package/docs/components/form-stepper.md +6 -0
- package/docs/components/form-summary.md +123 -0
- package/docs/components/form.md +17 -20
- package/docs/components/header-bar.md +6 -0
- package/docs/components/image-attachment.md +43 -45
- package/docs/components/inbox-view.md +16 -5
- package/docs/components/inner-label-field.md +34 -24
- package/docs/components/input-field.md +18 -10
- package/docs/components/input-otp.md +14 -11
- package/docs/components/input.md +22 -16
- package/docs/components/kanban-view.md +15 -5
- package/docs/components/label-field.md +32 -22
- package/docs/components/label.md +43 -32
- package/docs/components/labeled-check-box.md +16 -8
- package/docs/components/labeled-radio.md +18 -11
- package/docs/components/link-button.md +7 -1
- package/docs/components/login-button.md +8 -2
- package/docs/components/password-level.md +37 -24
- package/docs/components/popover.md +26 -23
- package/docs/components/profile-menu.md +38 -27
- package/docs/components/radio-card.md +16 -9
- package/docs/components/radio.md +16 -8
- package/docs/components/scroll-area.md +6 -2
- package/docs/components/search-field.md +14 -6
- package/docs/components/searchable-select.md +19 -14
- package/docs/components/searchable-table.md +18 -13
- package/docs/components/searchable-tree-dialog.md +190 -0
- package/docs/components/searchable-tree.md +200 -0
- package/docs/components/section-block.md +12 -4
- package/docs/components/select.md +23 -18
- package/docs/components/simple-select.md +14 -10
- package/docs/components/skeleton.md +43 -33
- package/docs/components/slide-date-picker.md +37 -29
- package/docs/components/spin-loading.md +32 -23
- package/docs/components/stepper.md +6 -0
- package/docs/components/switch.md +14 -6
- package/docs/components/tab-form-item.md +7 -1
- package/docs/components/tab-switch.md +13 -6
- package/docs/components/table-dnd-wrapper.md +107 -0
- package/docs/components/table-view.md +15 -5
- package/docs/components/table.md +28 -28
- package/docs/components/text-editor.md +37 -13
- package/docs/components/textarea.md +15 -7
- package/docs/components/timeline.md +6 -0
- package/docs/components/toast.md +35 -26
- package/docs/components/toggle-button.md +24 -21
- package/docs/components/toggle.md +23 -15
- package/docs/components/tooltip.md +42 -30
- package/docs/components/transparent-label.md +29 -20
- package/docs/components/tree-drop-down.md +31 -23
- package/docs/components/tree-sub-layout.md +6 -2
- package/docs/components/tree-view.md +16 -5
- package/docs/explanation/architecture.md +54 -0
- package/docs/explanation/design-system.md +52 -0
- package/docs/how-to/data-views-from-backend-response.md +6 -4
- package/docs/how-to/form-and-list-recipes.md +26 -19
- package/docs/how-to/forms-with-form-builder.md +408 -0
- package/docs/how-to/guides.md +177 -197
- package/docs/migration/changelog.md +38 -0
- package/docs/reference/cli.md +87 -0
- package/docs/reference/components.md +11 -12
- package/docs/reference/hooks.md +8 -8
- package/docs/reference/providers.md +22 -22
- package/docs/reference/tailwind-plugins.md +2 -2
- package/docs/reference/theme.md +85 -0
- package/docs/reference/types.md +4 -4
- package/docs/reference/utilities.md +19 -26
- package/docs/tutorials/building-first-form.md +170 -172
- package/docs/tutorials/component-composition.md +34 -50
- package/docs/tutorials/getting-started.md +2 -2
- package/docs/tutorials/theming-basics.md +13 -10
- package/package.json +31 -3
- package/dist/src/commands/block.d.ts +0 -5
- package/dist/src/commands/block.d.ts.map +0 -1
- package/dist/src/commands/block.js +0 -255
- package/dist/src/commands/block.js.map +0 -1
- package/dist/src/shared/configureFonts.d.ts +0 -6
- package/dist/src/shared/configureFonts.d.ts.map +0 -1
- package/dist/src/shared/configureFonts.js +0 -106
- package/dist/src/shared/configureFonts.js.map +0 -1
- package/dist/src/shared/configureGlobalCss.d.ts +0 -6
- package/dist/src/shared/configureGlobalCss.d.ts.map +0 -1
- package/dist/src/shared/configureGlobalCss.js +0 -154
- package/dist/src/shared/configureGlobalCss.js.map +0 -1
- package/dist/src/shared/configureTailwind.d.ts +0 -7
- package/dist/src/shared/configureTailwind.d.ts.map +0 -1
- package/dist/src/shared/configureTailwind.js +0 -163
- package/dist/src/shared/configureTailwind.js.map +0 -1
- package/dist/src/shared/detectFramework.d.ts +0 -23
- package/dist/src/shared/detectFramework.d.ts.map +0 -1
- package/dist/src/shared/detectFramework.js +0 -119
- package/dist/src/shared/detectFramework.js.map +0 -1
- package/dist/src/shared/installBaseUtils.d.ts +0 -5
- package/dist/src/shared/installBaseUtils.d.ts.map +0 -1
- package/dist/src/shared/installBaseUtils.js +0 -79
- package/dist/src/shared/installBaseUtils.js.map +0 -1
- package/dist/src/shared/resolveAliases.d.ts +0 -24
- package/dist/src/shared/resolveAliases.d.ts.map +0 -1
- package/dist/src/shared/resolveAliases.js +0 -98
- package/dist/src/shared/resolveAliases.js.map +0 -1
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { lazy, Suspense, useEffect, useState } from "react";
|
|
4
|
+
import type { OutputData } from "@editorjs/editorjs";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Client-only, lazily-loaded wrapper around the EditorJS `TextEditor`.
|
|
8
|
+
*
|
|
9
|
+
* EditorJS references DOM globals (`Element`) at module-load time, which crashes
|
|
10
|
+
* server-side rendering. Statically importing `TextEditor` in the field registry
|
|
11
|
+
* would pull it into the SSR bundle; loading it lazily + only after mount keeps
|
|
12
|
+
* the builder SSR-safe — the editor is imported on the client, on demand.
|
|
13
|
+
*/
|
|
14
|
+
const TextEditorLazy = lazy(() => import("./TextEditor").then((m) => ({ default: m.TextEditor })));
|
|
15
|
+
|
|
16
|
+
export interface RichTextFieldProps {
|
|
17
|
+
data?: OutputData;
|
|
18
|
+
onChange?: (data: OutputData) => void;
|
|
19
|
+
readOnly?: boolean;
|
|
20
|
+
placeholder?: string;
|
|
21
|
+
/** Editor size — controls min-height + padding (the toolbar gutter). Default "M". */
|
|
22
|
+
size?: "S" | "M" | "L" | "XL";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const placeholderBox = (
|
|
26
|
+
<div className="min-h-[200px] w-full rounded-[6px] border border-border-presentation-action-primary bg-background-presentation-form-field-primary" />
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
export function RichTextField({ size = "M", ...props }: RichTextFieldProps) {
|
|
30
|
+
const [mounted, setMounted] = useState(false);
|
|
31
|
+
useEffect(() => setMounted(true), []);
|
|
32
|
+
|
|
33
|
+
if (!mounted) return placeholderBox;
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Suspense fallback={placeholderBox}>
|
|
37
|
+
<TextEditorLazy
|
|
38
|
+
size={size}
|
|
39
|
+
className="border border-border-presentation-action-primary"
|
|
40
|
+
{...props}
|
|
41
|
+
/>
|
|
42
|
+
</Suspense>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
RichTextField.displayName = "RichTextField";
|
|
@@ -27,9 +27,7 @@ interface DragState {
|
|
|
27
27
|
startY: number;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
export default function createTableDnDClass(
|
|
31
|
-
OriginalTable: TableClass,
|
|
32
|
-
): TableClass {
|
|
30
|
+
export default function createTableDnDClass(OriginalTable: TableClass): TableClass {
|
|
33
31
|
return class TableDnD extends OriginalTable {
|
|
34
32
|
private _drag: DragState = {
|
|
35
33
|
active: false,
|
|
@@ -101,9 +99,7 @@ export default function createTableDnDClass(
|
|
|
101
99
|
|
|
102
100
|
// Proximity-based toolbox visibility:
|
|
103
101
|
// Only show row handle near left edge, column handle near top edge
|
|
104
|
-
wrapper.addEventListener("mousemove", (e) =>
|
|
105
|
-
this._onWrapperMouseMove(e),
|
|
106
|
-
);
|
|
102
|
+
wrapper.addEventListener("mousemove", (e) => this._onWrapperMouseMove(e));
|
|
107
103
|
wrapper.addEventListener("mouseleave", () => {
|
|
108
104
|
wrapper.classList.remove("tc-dnd--show-row", "tc-dnd--show-col");
|
|
109
105
|
});
|
|
@@ -140,8 +136,7 @@ export default function createTableDnDClass(
|
|
|
140
136
|
// ─── Proximity-based toolbox visibility ──────────────────────────────
|
|
141
137
|
|
|
142
138
|
private _onWrapperMouseMove(e: MouseEvent): void {
|
|
143
|
-
const tableEl =
|
|
144
|
-
this._wrapperRef?.querySelector<HTMLElement>(".tc-table");
|
|
139
|
+
const tableEl = this._wrapperRef?.querySelector<HTMLElement>(".tc-table");
|
|
145
140
|
if (!tableEl || !this._wrapperRef) return;
|
|
146
141
|
|
|
147
142
|
const tableRect = tableEl.getBoundingClientRect();
|
|
@@ -172,20 +167,13 @@ export default function createTableDnDClass(
|
|
|
172
167
|
this._wrapperRef.classList.add("tc-dnd--show-col");
|
|
173
168
|
this._wrapperRef.classList.remove("tc-dnd--show-row");
|
|
174
169
|
} else {
|
|
175
|
-
this._wrapperRef.classList.remove(
|
|
176
|
-
"tc-dnd--show-row",
|
|
177
|
-
"tc-dnd--show-col",
|
|
178
|
-
);
|
|
170
|
+
this._wrapperRef.classList.remove("tc-dnd--show-row", "tc-dnd--show-col");
|
|
179
171
|
}
|
|
180
172
|
}
|
|
181
173
|
|
|
182
174
|
// ─── Toggler pointerdown (start pending drag) ───────────────────────
|
|
183
175
|
|
|
184
|
-
private _onTogglerDown(
|
|
185
|
-
type: "row" | "col",
|
|
186
|
-
wrapper: HTMLElement,
|
|
187
|
-
e: PointerEvent,
|
|
188
|
-
): void {
|
|
176
|
+
private _onTogglerDown(type: "row" | "col", wrapper: HTMLElement, e: PointerEvent): void {
|
|
189
177
|
// Detect which row/col is currently hovered by the table plugin
|
|
190
178
|
const index = this._detectHoveredIndex(type, wrapper);
|
|
191
179
|
if (index < 0) return;
|
|
@@ -208,10 +196,7 @@ export default function createTableDnDClass(
|
|
|
208
196
|
|
|
209
197
|
// ─── Detect hovered row/col index ───────────────────────────────────
|
|
210
198
|
|
|
211
|
-
private _detectHoveredIndex(
|
|
212
|
-
type: "row" | "col",
|
|
213
|
-
wrapper: HTMLElement,
|
|
214
|
-
): number {
|
|
199
|
+
private _detectHoveredIndex(type: "row" | "col", wrapper: HTMLElement): number {
|
|
215
200
|
const tableEl = wrapper.querySelector<HTMLElement>(".tc-table");
|
|
216
201
|
if (!tableEl) return -1;
|
|
217
202
|
|
|
@@ -230,9 +215,7 @@ export default function createTableDnDClass(
|
|
|
230
215
|
}
|
|
231
216
|
return -1;
|
|
232
217
|
} else {
|
|
233
|
-
const toolbox = wrapper.querySelector<HTMLElement>(
|
|
234
|
-
".tc-toolbox--column",
|
|
235
|
-
);
|
|
218
|
+
const toolbox = wrapper.querySelector<HTMLElement>(".tc-toolbox--column");
|
|
236
219
|
if (!toolbox) return -1;
|
|
237
220
|
const toolboxRect = toolbox.getBoundingClientRect();
|
|
238
221
|
const toolboxMid = toolboxRect.left + toolboxRect.width / 2;
|
|
@@ -265,8 +248,7 @@ export default function createTableDnDClass(
|
|
|
265
248
|
d.active = true;
|
|
266
249
|
|
|
267
250
|
// Close any open popover from the table plugin
|
|
268
|
-
const openPopover =
|
|
269
|
-
this._wrapperRef?.querySelector<HTMLElement>(".tc-popover--opened");
|
|
251
|
+
const openPopover = this._wrapperRef?.querySelector<HTMLElement>(".tc-popover--opened");
|
|
270
252
|
openPopover?.classList.remove("tc-popover--opened");
|
|
271
253
|
|
|
272
254
|
this._highlight(d.type!, d.index, true);
|
|
@@ -338,13 +320,10 @@ export default function createTableDnDClass(
|
|
|
338
320
|
// ─── Move Row / Column ──────────────────────────────────────────────
|
|
339
321
|
|
|
340
322
|
private _moveRow(from: number, to: number): void {
|
|
341
|
-
const tableEl =
|
|
342
|
-
this._wrapperRef?.querySelector<HTMLElement>(".tc-table");
|
|
323
|
+
const tableEl = this._wrapperRef?.querySelector<HTMLElement>(".tc-table");
|
|
343
324
|
if (!tableEl) return;
|
|
344
325
|
|
|
345
|
-
const rows = Array.from(
|
|
346
|
-
tableEl.querySelectorAll<HTMLElement>(".tc-row"),
|
|
347
|
-
);
|
|
326
|
+
const rows = Array.from(tableEl.querySelectorAll<HTMLElement>(".tc-row"));
|
|
348
327
|
if (from < 0 || from >= rows.length) return;
|
|
349
328
|
|
|
350
329
|
const target = to > from ? to - 1 : to;
|
|
@@ -360,17 +339,14 @@ export default function createTableDnDClass(
|
|
|
360
339
|
}
|
|
361
340
|
|
|
362
341
|
private _moveCol(from: number, to: number): void {
|
|
363
|
-
const tableEl =
|
|
364
|
-
this._wrapperRef?.querySelector<HTMLElement>(".tc-table");
|
|
342
|
+
const tableEl = this._wrapperRef?.querySelector<HTMLElement>(".tc-table");
|
|
365
343
|
if (!tableEl) return;
|
|
366
344
|
|
|
367
345
|
const target = to > from ? to - 1 : to;
|
|
368
346
|
if (target === from) return;
|
|
369
347
|
|
|
370
348
|
tableEl.querySelectorAll<HTMLElement>(".tc-row").forEach((row) => {
|
|
371
|
-
const cells = Array.from(
|
|
372
|
-
row.querySelectorAll<HTMLElement>(".tc-cell"),
|
|
373
|
-
);
|
|
349
|
+
const cells = Array.from(row.querySelectorAll<HTMLElement>(".tc-cell"));
|
|
374
350
|
if (from >= cells.length) return;
|
|
375
351
|
|
|
376
352
|
const cell = cells[from];
|
|
@@ -385,11 +361,7 @@ export default function createTableDnDClass(
|
|
|
385
361
|
|
|
386
362
|
// ─── Drop Index Calc ────────────────────────────────────────────────
|
|
387
363
|
|
|
388
|
-
private _calcDrop(
|
|
389
|
-
wrapper: HTMLElement,
|
|
390
|
-
cx: number,
|
|
391
|
-
cy: number,
|
|
392
|
-
): number {
|
|
364
|
+
private _calcDrop(wrapper: HTMLElement, cx: number, cy: number): number {
|
|
393
365
|
const tableEl = wrapper.querySelector<HTMLElement>(".tc-table");
|
|
394
366
|
if (!tableEl) return this._drag.index;
|
|
395
367
|
|
|
@@ -414,13 +386,8 @@ export default function createTableDnDClass(
|
|
|
414
386
|
|
|
415
387
|
// ─── Visual Feedback ────────────────────────────────────────────────
|
|
416
388
|
|
|
417
|
-
private _highlight(
|
|
418
|
-
|
|
419
|
-
idx: number,
|
|
420
|
-
on: boolean,
|
|
421
|
-
): void {
|
|
422
|
-
const tableEl =
|
|
423
|
-
this._wrapperRef?.querySelector<HTMLElement>(".tc-table");
|
|
389
|
+
private _highlight(type: "row" | "col", idx: number, on: boolean): void {
|
|
390
|
+
const tableEl = this._wrapperRef?.querySelector<HTMLElement>(".tc-table");
|
|
424
391
|
if (!tableEl) return;
|
|
425
392
|
|
|
426
393
|
if (type === "row") {
|
|
@@ -10,10 +10,10 @@ import React, {
|
|
|
10
10
|
useState,
|
|
11
11
|
} from "react";
|
|
12
12
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
13
|
-
import { cn } from "
|
|
14
|
-
import { Themes } from "
|
|
13
|
+
import { cn } from "../../utils/cn";
|
|
14
|
+
import { Themes } from "../../utils/types";
|
|
15
15
|
import EditorJS, { type OutputData } from "@editorjs/editorjs";
|
|
16
|
-
import { isMarkdown, parseMarkdownToBlocks } from "
|
|
16
|
+
import { isMarkdown, parseMarkdownToBlocks } from "./markdownParser";
|
|
17
17
|
|
|
18
18
|
// ─── Tool Imports ────────────────────────────────────────────────────────────
|
|
19
19
|
import Header from "@editorjs/header";
|
|
@@ -34,7 +34,14 @@ import Marker from "@editorjs/marker";
|
|
|
34
34
|
import InlineCode from "@editorjs/inline-code";
|
|
35
35
|
import Underline from "@editorjs/underline";
|
|
36
36
|
import TextVariantTune from "@editorjs/text-variant-tune";
|
|
37
|
+
// `editorjs-undo` touches DOM globals at module-eval, so it is imported dynamically
|
|
38
|
+
// (browser-only) in `onReady`; keep only the type here.
|
|
39
|
+
import type Undo from "editorjs-undo";
|
|
37
40
|
import ChartBlockTool from "./ChartBlockTool";
|
|
41
|
+
import StrikethroughInlineTool from "./editor-tools/StrikethroughInlineTool";
|
|
42
|
+
import ColorInlineTool from "./editor-tools/ColorInlineTool";
|
|
43
|
+
import AlignmentTune from "./editor-tools/AlignmentTune";
|
|
44
|
+
import { TextEditorToolbar } from "./TextEditorToolbar";
|
|
38
45
|
|
|
39
46
|
// ─── RTL Detection ───────────────────────────────────────────────────────────
|
|
40
47
|
const RTL_REGEX = /[\u0591-\u07FF\u200F\u202B\u202E\uFB1D-\uFDFD\uFE70-\uFEFC]/;
|
|
@@ -66,7 +73,10 @@ function setDirIfChanged(el: HTMLElement, dir: "rtl" | "ltr") {
|
|
|
66
73
|
}
|
|
67
74
|
}
|
|
68
75
|
|
|
69
|
-
function applyDirectionToBlock(
|
|
76
|
+
function applyDirectionToBlock(
|
|
77
|
+
block: HTMLElement,
|
|
78
|
+
fallbackDir: "rtl" | "ltr" = "ltr",
|
|
79
|
+
): "rtl" | "ltr" {
|
|
70
80
|
// Skip non-text blocks (delimiters, embeds, code)
|
|
71
81
|
if (block.querySelector(".ce-delimiter, .cdx-embed, .embed-tool")) return fallbackDir;
|
|
72
82
|
|
|
@@ -78,9 +88,7 @@ function applyDirectionToBlock(block: HTMLElement, fallbackDir: "rtl" | "ltr" =
|
|
|
78
88
|
if (listItems.length > 0) {
|
|
79
89
|
let prevDir: "rtl" | "ltr" = fallbackDir;
|
|
80
90
|
listItems.forEach((item) => {
|
|
81
|
-
const editable = item.querySelector<HTMLElement>(
|
|
82
|
-
'[contenteditable="true"]',
|
|
83
|
-
);
|
|
91
|
+
const editable = item.querySelector<HTMLElement>('[contenteditable="true"]');
|
|
84
92
|
if (!editable) return;
|
|
85
93
|
const text = (editable.textContent || "").trim();
|
|
86
94
|
const dir = text ? getTextDirection(text) : prevDir;
|
|
@@ -93,9 +101,7 @@ function applyDirectionToBlock(block: HTMLElement, fallbackDir: "rtl" | "ltr" =
|
|
|
93
101
|
|
|
94
102
|
// Handle regular blocks (paragraph, header, quote, warning, etc.)
|
|
95
103
|
let lastDir = fallbackDir;
|
|
96
|
-
const editables = block.querySelectorAll<HTMLElement>(
|
|
97
|
-
'[contenteditable="true"]',
|
|
98
|
-
);
|
|
104
|
+
const editables = block.querySelectorAll<HTMLElement>('[contenteditable="true"]');
|
|
99
105
|
editables.forEach((el) => {
|
|
100
106
|
const text = (el.textContent || "").trim();
|
|
101
107
|
const dir = text ? getTextDirection(text) : fallbackDir;
|
|
@@ -169,15 +175,25 @@ const getDefaultTools = (): Record<string, any> => ({
|
|
|
169
175
|
linkTool: { class: LinkTool },
|
|
170
176
|
image: { class: SimpleImage },
|
|
171
177
|
raw: { class: RawTool },
|
|
172
|
-
chart: { class: ChartBlockTool
|
|
178
|
+
chart: { class: ChartBlockTool },
|
|
173
179
|
marker: { class: Marker, shortcut: "CMD+SHIFT+M" },
|
|
174
180
|
inlineCode: { class: InlineCode, shortcut: "CMD+SHIFT+I" },
|
|
175
181
|
underline: { class: Underline },
|
|
182
|
+
// Registered so their `sanitize` unions into every block's save config (strike/color
|
|
183
|
+
// markup persists) and they appear in the inline toolbar; the fixed toolbar drives them too.
|
|
184
|
+
strikethrough: { class: StrikethroughInlineTool, shortcut: "CMD+SHIFT+X" },
|
|
185
|
+
color: { class: ColorInlineTool },
|
|
176
186
|
textVariant: { class: TextVariantTune },
|
|
187
|
+
// Per-block text alignment; wired into `tunes` below so every block gets it.
|
|
188
|
+
align: { class: AlignmentTune },
|
|
177
189
|
});
|
|
178
190
|
|
|
179
191
|
// ─── Auto-direction CSS ──────────────────────────────────────────────────────
|
|
180
192
|
const AUTO_DIR_STYLES = `
|
|
193
|
+
/* Block alignment (AlignmentTune sets data-align on the block content wrapper). */
|
|
194
|
+
.torch-text-editor .cdx-align-wrap[data-align="center"] { text-align: center; }
|
|
195
|
+
.torch-text-editor .cdx-align-wrap[data-align="right"] { text-align: right; }
|
|
196
|
+
.torch-text-editor .cdx-align-wrap[data-align="left"] { text-align: left; }
|
|
181
197
|
.torch-text-editor [contenteditable="true"][dir="rtl"] {
|
|
182
198
|
unicode-bidi: plaintext;
|
|
183
199
|
}
|
|
@@ -692,7 +708,8 @@ const textEditorStyles = cva(
|
|
|
692
708
|
"relative w-full rounded-[6px]",
|
|
693
709
|
"border-0 transition-all duration-200 ease-in-out",
|
|
694
710
|
"[&_.codex-editor]:min-h-[inherit]",
|
|
695
|
-
|
|
711
|
+
// Content padding lives on the redactor (not the container) so the toolbar stays full-bleed.
|
|
712
|
+
"[&_.codex-editor__redactor]:min-h-[inherit] [&_.codex-editor__redactor]:px-[16px] [&_.codex-editor__redactor]:pt-[12px] [&_.codex-editor__redactor]:pb-[100px]",
|
|
696
713
|
"[&_.ce-paragraph]:typography-body-medium-regular",
|
|
697
714
|
],
|
|
698
715
|
{
|
|
@@ -728,11 +745,13 @@ const textEditorStyles = cva(
|
|
|
728
745
|
"[&_.ce-delimiter]:border-border-presentation-action-primary",
|
|
729
746
|
],
|
|
730
747
|
},
|
|
748
|
+
// No padding on the container — the fixed toolbar must go edge-to-edge. Content
|
|
749
|
+
// padding is applied to the EditorJS redactor below instead.
|
|
731
750
|
size: {
|
|
732
|
-
S: "min-h-[200px]
|
|
733
|
-
M: "min-h-[300px]
|
|
734
|
-
L: "min-h-[400px]
|
|
735
|
-
XL: "min-h-[500px]
|
|
751
|
+
S: "min-h-[200px]",
|
|
752
|
+
M: "min-h-[300px]",
|
|
753
|
+
L: "min-h-[400px]",
|
|
754
|
+
XL: "min-h-[500px]",
|
|
736
755
|
},
|
|
737
756
|
disabled: {
|
|
738
757
|
true: "cursor-not-allowed opacity-60 pointer-events-none",
|
|
@@ -754,9 +773,7 @@ export interface TextEditorRef {
|
|
|
754
773
|
}
|
|
755
774
|
|
|
756
775
|
interface TextEditorProps
|
|
757
|
-
extends
|
|
758
|
-
Omit<HTMLAttributes<HTMLDivElement>, "onChange">,
|
|
759
|
-
VariantProps<typeof textEditorStyles> {
|
|
776
|
+
extends Omit<HTMLAttributes<HTMLDivElement>, "onChange">, VariantProps<typeof textEditorStyles> {
|
|
760
777
|
theme?: Themes;
|
|
761
778
|
data?: OutputData;
|
|
762
779
|
onChange?: (data: OutputData) => void;
|
|
@@ -767,6 +784,8 @@ interface TextEditorProps
|
|
|
767
784
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
768
785
|
tools?: Record<string, any>;
|
|
769
786
|
minHeight?: number;
|
|
787
|
+
/** Show the fixed formatting toolbar (default `true`; hidden when `readOnly`/`disabled`). */
|
|
788
|
+
toolbar?: boolean;
|
|
770
789
|
}
|
|
771
790
|
|
|
772
791
|
// ─── Component ───────────────────────────────────────────────────────────────
|
|
@@ -786,16 +805,17 @@ export const TextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
786
805
|
autofocus = false,
|
|
787
806
|
tools,
|
|
788
807
|
minHeight,
|
|
808
|
+
toolbar = true,
|
|
789
809
|
...props
|
|
790
810
|
},
|
|
791
811
|
ref,
|
|
792
812
|
) => {
|
|
793
813
|
const editorRef = useRef<EditorJS | null>(null);
|
|
814
|
+
const undoRef = useRef<Undo | null>(null);
|
|
815
|
+
const [toolbarReady, setToolbarReady] = useState(false);
|
|
794
816
|
const holderRef = useRef<HTMLDivElement>(null);
|
|
795
817
|
const observerRef = useRef<MutationObserver | null>(null);
|
|
796
|
-
const [holderId] = useState(
|
|
797
|
-
() => `torch-editor-${Math.random().toString(36).substring(2, 9)}`,
|
|
798
|
-
);
|
|
818
|
+
const [holderId] = useState(() => `torch-editor-${Math.random().toString(36).substring(2, 9)}`);
|
|
799
819
|
const isInitializing = useRef(false);
|
|
800
820
|
const pasteHandlerRef = useRef<((e: ClipboardEvent) => void) | null>(null);
|
|
801
821
|
const saveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
@@ -839,107 +859,104 @@ export const TextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
839
859
|
}, []);
|
|
840
860
|
|
|
841
861
|
// ── Markdown paste handler ──
|
|
842
|
-
const setupMarkdownPaste = useCallback(
|
|
843
|
-
(
|
|
844
|
-
|
|
845
|
-
if (readOnlyRef.current || !editorRef.current) return;
|
|
862
|
+
const setupMarkdownPaste = useCallback((holderEl: HTMLElement) => {
|
|
863
|
+
const handler = async (e: ClipboardEvent) => {
|
|
864
|
+
if (readOnlyRef.current || !editorRef.current) return;
|
|
846
865
|
|
|
847
|
-
|
|
866
|
+
const plain = e.clipboardData?.getData("text/plain") || "";
|
|
848
867
|
|
|
849
|
-
|
|
850
|
-
|
|
868
|
+
// Only intercept if the plain text is markdown
|
|
869
|
+
if (!isMarkdown(plain)) return;
|
|
851
870
|
|
|
852
|
-
|
|
853
|
-
|
|
871
|
+
e.preventDefault();
|
|
872
|
+
e.stopPropagation();
|
|
854
873
|
|
|
855
|
-
|
|
856
|
-
|
|
874
|
+
const parsedBlocks = parseMarkdownToBlocks(plain);
|
|
875
|
+
if (parsedBlocks.length === 0) return;
|
|
857
876
|
|
|
858
|
-
|
|
877
|
+
const editor = editorRef.current;
|
|
878
|
+
|
|
879
|
+
// Save existing content, merge new blocks at cursor, render once
|
|
880
|
+
// This avoids per-block insert which freezes the editor on large pastes
|
|
881
|
+
try {
|
|
882
|
+
const existingData = await editor.save();
|
|
883
|
+
const existingBlocks = existingData.blocks || [];
|
|
859
884
|
|
|
860
|
-
//
|
|
861
|
-
//
|
|
885
|
+
// Determine insertion point
|
|
886
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
887
|
+
const blocks = (editor as any).blocks;
|
|
888
|
+
let insertIdx = existingBlocks.length;
|
|
862
889
|
try {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
// Determine insertion point
|
|
867
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
868
|
-
const blocks = (editor as any).blocks;
|
|
869
|
-
let insertIdx = existingBlocks.length;
|
|
870
|
-
try {
|
|
871
|
-
if (blocks?.getCurrentBlockIndex) {
|
|
872
|
-
insertIdx = blocks.getCurrentBlockIndex() + 1;
|
|
873
|
-
}
|
|
874
|
-
} catch {
|
|
875
|
-
// Use end of document
|
|
890
|
+
if (blocks?.getCurrentBlockIndex) {
|
|
891
|
+
insertIdx = blocks.getCurrentBlockIndex() + 1;
|
|
876
892
|
}
|
|
893
|
+
} catch {
|
|
894
|
+
// Use end of document
|
|
895
|
+
}
|
|
877
896
|
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
}
|
|
897
|
+
// Convert parsed blocks to OutputData block format
|
|
898
|
+
const newBlocks = parsedBlocks.map((block) => ({
|
|
899
|
+
type: block.type,
|
|
900
|
+
data: block.data,
|
|
901
|
+
}));
|
|
902
|
+
|
|
903
|
+
// Remove empty paragraph at cursor if it's the only content there
|
|
904
|
+
const filteredExisting = [...existingBlocks];
|
|
905
|
+
if (insertIdx > 0) {
|
|
906
|
+
const cursorBlock = filteredExisting[insertIdx - 1];
|
|
907
|
+
if (
|
|
908
|
+
cursorBlock &&
|
|
909
|
+
cursorBlock.type === "paragraph" &&
|
|
910
|
+
(!cursorBlock.data.text ||
|
|
911
|
+
cursorBlock.data.text.trim() === "" ||
|
|
912
|
+
cursorBlock.data.text === "<br>")
|
|
913
|
+
) {
|
|
914
|
+
filteredExisting.splice(insertIdx - 1, 1);
|
|
915
|
+
insertIdx = Math.max(0, insertIdx - 1);
|
|
898
916
|
}
|
|
917
|
+
}
|
|
899
918
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
919
|
+
// Merge: existing blocks before cursor + new blocks + existing blocks after cursor
|
|
920
|
+
const mergedBlocks = [
|
|
921
|
+
...filteredExisting.slice(0, insertIdx),
|
|
922
|
+
...newBlocks,
|
|
923
|
+
...filteredExisting.slice(insertIdx),
|
|
924
|
+
];
|
|
925
|
+
|
|
926
|
+
// Pause MutationObserver during bulk render to prevent O(n^2) direction checks
|
|
927
|
+
observerRef.current?.disconnect();
|
|
928
|
+
|
|
929
|
+
// Single render pass — no per-block DOM thrashing
|
|
930
|
+
await editor.render({
|
|
931
|
+
time: Date.now(),
|
|
932
|
+
version: existingData.version,
|
|
933
|
+
blocks: mergedBlocks,
|
|
934
|
+
});
|
|
916
935
|
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
936
|
+
// Reconnect observer on the redactor element
|
|
937
|
+
if (observerRef.current) {
|
|
938
|
+
const redactor = holderEl.querySelector(".codex-editor__redactor") || holderEl;
|
|
939
|
+
observerRef.current.observe(redactor, {
|
|
940
|
+
childList: true,
|
|
941
|
+
subtree: false,
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
// Defer direction scan to next frame — let the browser paint first
|
|
945
|
+
requestAnimationFrame(() => applyAutoDirection(holderEl));
|
|
927
946
|
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
}
|
|
933
|
-
} catch {
|
|
934
|
-
// Fallback: if render approach fails, do nothing rather than freeze
|
|
947
|
+
// Trigger onChange with merged data
|
|
948
|
+
if (onChangeRef.current) {
|
|
949
|
+
const savedData = await editor.save();
|
|
950
|
+
onChangeRef.current(savedData);
|
|
935
951
|
}
|
|
936
|
-
}
|
|
952
|
+
} catch {
|
|
953
|
+
// Fallback: if render approach fails, do nothing rather than freeze
|
|
954
|
+
}
|
|
955
|
+
};
|
|
937
956
|
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
[],
|
|
942
|
-
);
|
|
957
|
+
holderEl.addEventListener("paste", handler as unknown as EventListener, true);
|
|
958
|
+
pasteHandlerRef.current = handler;
|
|
959
|
+
}, []);
|
|
943
960
|
|
|
944
961
|
// ── Auto-direction: watch for DOM changes & input ──
|
|
945
962
|
const inputHandlerRef = useRef<((e: Event) => void) | null>(null);
|
|
@@ -989,10 +1006,13 @@ export const TextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
989
1006
|
if (isInitializing.current || editorRef.current) return;
|
|
990
1007
|
isInitializing.current = true;
|
|
991
1008
|
|
|
1009
|
+
// Only enable the `align` tune when using the default tools (a caller-supplied `tools`
|
|
1010
|
+
// map may not register it).
|
|
1011
|
+
const usingDefaultTools = !toolsRef.current;
|
|
992
1012
|
const editor = new EditorJS({
|
|
993
1013
|
holder: holderId,
|
|
994
1014
|
tools: toolsRef.current ?? getDefaultTools(),
|
|
995
|
-
tunes: ["textVariant"],
|
|
1015
|
+
tunes: usingDefaultTools ? ["textVariant", "align"] : ["textVariant"],
|
|
996
1016
|
data: initialDataRef.current || undefined,
|
|
997
1017
|
readOnly: readOnlyRef.current,
|
|
998
1018
|
placeholder: placeholderRef.current,
|
|
@@ -1007,6 +1027,21 @@ export const TextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
1007
1027
|
setupMarkdownPaste(holderEl);
|
|
1008
1028
|
}
|
|
1009
1029
|
|
|
1030
|
+
// History (undo/redo) for the fixed toolbar + Ctrl+Z/Y. Not for readOnly.
|
|
1031
|
+
// Dynamically imported because the package evaluates DOM globals at module load.
|
|
1032
|
+
if (!readOnlyRef.current) {
|
|
1033
|
+
import("editorjs-undo")
|
|
1034
|
+
.then(({ default: UndoCls }) => {
|
|
1035
|
+
if (!editorRef.current) return;
|
|
1036
|
+
undoRef.current = new UndoCls({ editor: editorRef.current });
|
|
1037
|
+
if (initialDataRef.current) undoRef.current.initialize(initialDataRef.current);
|
|
1038
|
+
})
|
|
1039
|
+
.catch(() => {
|
|
1040
|
+
undoRef.current = null;
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
setToolbarReady(true);
|
|
1044
|
+
|
|
1010
1045
|
onReadyRef.current?.();
|
|
1011
1046
|
},
|
|
1012
1047
|
onChange: () => {
|
|
@@ -1052,13 +1087,12 @@ export const TextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
1052
1087
|
(window.cancelIdleCallback || clearTimeout)(idleCallbackRef.current);
|
|
1053
1088
|
}
|
|
1054
1089
|
|
|
1055
|
-
if (
|
|
1056
|
-
editorRef.current &&
|
|
1057
|
-
typeof editorRef.current.destroy === "function"
|
|
1058
|
-
) {
|
|
1090
|
+
if (editorRef.current && typeof editorRef.current.destroy === "function") {
|
|
1059
1091
|
editorRef.current.destroy();
|
|
1060
1092
|
editorRef.current = null;
|
|
1061
1093
|
}
|
|
1094
|
+
undoRef.current = null;
|
|
1095
|
+
setToolbarReady(false);
|
|
1062
1096
|
isInitializing.current = false;
|
|
1063
1097
|
};
|
|
1064
1098
|
}, [initEditor]);
|
|
@@ -1109,6 +1143,15 @@ export const TextEditor = forwardRef<TextEditorRef, TextEditorProps>(
|
|
|
1109
1143
|
style={minHeight ? { minHeight: `${minHeight}px` } : undefined}
|
|
1110
1144
|
{...props}
|
|
1111
1145
|
>
|
|
1146
|
+
{toolbar && !readOnly && !disabled && (
|
|
1147
|
+
<TextEditorToolbar
|
|
1148
|
+
editorRef={editorRef}
|
|
1149
|
+
undoRef={undoRef}
|
|
1150
|
+
holderRef={holderRef}
|
|
1151
|
+
ready={toolbarReady}
|
|
1152
|
+
theme={theme}
|
|
1153
|
+
/>
|
|
1154
|
+
)}
|
|
1112
1155
|
<div id={holderId} />
|
|
1113
1156
|
</div>
|
|
1114
1157
|
</>
|