@particle-academy/react-fancy 4.13.1 → 4.14.1
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 +1 -1
- package/dist/index.cjs +134 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -5
- package/dist/index.d.ts +65 -5
- package/dist/index.js +134 -36
- package/dist/index.js.map +1 -1
- package/docs/Editor.md +55 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -162,7 +162,7 @@ npx vite build # Build demo app (verifies imports work)
|
|
|
162
162
|
|-----------|-------------|------|
|
|
163
163
|
| Composer | Chat-style message input composing textarea + actions | [docs](docs/Composer.md) |
|
|
164
164
|
| Chart | SVG-based Bar, Line, Area, Pie, Donut, and Sparkline charts | [docs](docs/Chart.md) |
|
|
165
|
-
| Editor | Toolbar chrome wrapper for contentEditable | [docs](docs/Editor.md) |
|
|
165
|
+
| Editor | Toolbar chrome wrapper for contentEditable, with a Source toggle to edit raw HTML/Markdown | [docs](docs/Editor.md) |
|
|
166
166
|
| Kanban | Drag-and-drop board with columns and cards | [docs](docs/Kanban.md) |
|
|
167
167
|
| Canvas | Interactive node canvas with pan, zoom, and connections | [docs](docs/Canvas.md) |
|
|
168
168
|
| Diagram | Entity-relationship diagram with draggable nodes and relation lines | [docs](docs/Diagram.md) |
|
package/dist/index.cjs
CHANGED
|
@@ -11344,6 +11344,31 @@ function useEditor() {
|
|
|
11344
11344
|
}
|
|
11345
11345
|
return ctx;
|
|
11346
11346
|
}
|
|
11347
|
+
function EditorSourceToggle({
|
|
11348
|
+
className,
|
|
11349
|
+
icon = "</>",
|
|
11350
|
+
title = "Source",
|
|
11351
|
+
activeTitle = "Rich text"
|
|
11352
|
+
}) {
|
|
11353
|
+
const { showSource, setShowSource } = useEditor();
|
|
11354
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
11355
|
+
"button",
|
|
11356
|
+
{
|
|
11357
|
+
type: "button",
|
|
11358
|
+
onClick: () => setShowSource(!showSource),
|
|
11359
|
+
title: showSource ? activeTitle : title,
|
|
11360
|
+
"aria-pressed": showSource,
|
|
11361
|
+
"data-react-fancy-editor-source-toggle": "",
|
|
11362
|
+
className: cn(
|
|
11363
|
+
"inline-flex h-8 items-center justify-center rounded-md px-2 font-mono text-xs transition-colors",
|
|
11364
|
+
showSource ? "bg-zinc-200 dark:bg-zinc-700" : "hover:bg-zinc-100 dark:hover:bg-zinc-800",
|
|
11365
|
+
className
|
|
11366
|
+
),
|
|
11367
|
+
children: icon
|
|
11368
|
+
}
|
|
11369
|
+
);
|
|
11370
|
+
}
|
|
11371
|
+
EditorSourceToggle.displayName = "EditorSourceToggle";
|
|
11347
11372
|
var DEFAULT_ACTIONS = [
|
|
11348
11373
|
{ icon: "B", label: "Bold", command: "bold" },
|
|
11349
11374
|
{ icon: "I", label: "Italic", command: "italic" },
|
|
@@ -11354,9 +11379,10 @@ function EditorToolbar({
|
|
|
11354
11379
|
actions = DEFAULT_ACTIONS,
|
|
11355
11380
|
onAction,
|
|
11356
11381
|
children,
|
|
11357
|
-
className
|
|
11382
|
+
className,
|
|
11383
|
+
showSourceToggle = true
|
|
11358
11384
|
}) {
|
|
11359
|
-
const { exec } = useEditor();
|
|
11385
|
+
const { exec, showSource } = useEditor();
|
|
11360
11386
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
11361
11387
|
"div",
|
|
11362
11388
|
{
|
|
@@ -11365,26 +11391,31 @@ function EditorToolbar({
|
|
|
11365
11391
|
"flex items-center gap-0.5 border-b border-zinc-200 px-2 py-1.5 dark:border-zinc-700",
|
|
11366
11392
|
className
|
|
11367
11393
|
),
|
|
11368
|
-
children: children ??
|
|
11369
|
-
|
|
11370
|
-
|
|
11371
|
-
|
|
11372
|
-
|
|
11373
|
-
|
|
11374
|
-
|
|
11375
|
-
|
|
11376
|
-
|
|
11377
|
-
|
|
11394
|
+
children: children ?? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
11395
|
+
actions.map((action) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
11396
|
+
"button",
|
|
11397
|
+
{
|
|
11398
|
+
type: "button",
|
|
11399
|
+
disabled: showSource,
|
|
11400
|
+
onClick: () => {
|
|
11401
|
+
if (onAction) {
|
|
11402
|
+
onAction(action.command);
|
|
11403
|
+
} else {
|
|
11404
|
+
exec(action.command, action.commandArg);
|
|
11405
|
+
}
|
|
11406
|
+
},
|
|
11407
|
+
title: action.label,
|
|
11408
|
+
className: cn(
|
|
11409
|
+
"inline-flex h-8 w-8 items-center justify-center rounded-md text-sm transition-colors",
|
|
11410
|
+
action.active ? "bg-zinc-200 dark:bg-zinc-700" : "hover:bg-zinc-100 dark:hover:bg-zinc-800",
|
|
11411
|
+
showSource && "cursor-not-allowed opacity-40 hover:bg-transparent"
|
|
11412
|
+
),
|
|
11413
|
+
children: action.icon
|
|
11378
11414
|
},
|
|
11379
|
-
|
|
11380
|
-
|
|
11381
|
-
|
|
11382
|
-
|
|
11383
|
-
),
|
|
11384
|
-
children: action.icon
|
|
11385
|
-
},
|
|
11386
|
-
`${action.command}-${action.commandArg ?? ""}`
|
|
11387
|
-
))
|
|
11415
|
+
`${action.command}-${action.commandArg ?? ""}`
|
|
11416
|
+
)),
|
|
11417
|
+
showSourceToggle && /* @__PURE__ */ jsxRuntime.jsx(EditorSourceToggle, { className: "ml-auto" })
|
|
11418
|
+
] })
|
|
11388
11419
|
}
|
|
11389
11420
|
);
|
|
11390
11421
|
}
|
|
@@ -11590,20 +11621,58 @@ function extensionEditorClasses(extensions) {
|
|
|
11590
11621
|
].join(" ");
|
|
11591
11622
|
}).join(" ");
|
|
11592
11623
|
}
|
|
11593
|
-
function EditorContent({ className, maxHeight }) {
|
|
11594
|
-
const {
|
|
11595
|
-
|
|
11624
|
+
function EditorContent({ className, maxHeight, sourceClassName }) {
|
|
11625
|
+
const {
|
|
11626
|
+
contentRef,
|
|
11627
|
+
lineSpacing,
|
|
11628
|
+
placeholder,
|
|
11629
|
+
extensions,
|
|
11630
|
+
outputFormat,
|
|
11631
|
+
showSource,
|
|
11632
|
+
_initialHtml,
|
|
11633
|
+
_onInput,
|
|
11634
|
+
_sourceValue,
|
|
11635
|
+
_onSourceInput
|
|
11636
|
+
} = useEditor();
|
|
11637
|
+
const seededHtml = react.useRef(null);
|
|
11596
11638
|
react.useEffect(() => {
|
|
11639
|
+
if (showSource) {
|
|
11640
|
+
seededHtml.current = null;
|
|
11641
|
+
return;
|
|
11642
|
+
}
|
|
11597
11643
|
const el = contentRef.current;
|
|
11598
|
-
if (el &&
|
|
11644
|
+
if (el && seededHtml.current !== _initialHtml) {
|
|
11599
11645
|
el.innerHTML = _initialHtml;
|
|
11600
|
-
|
|
11646
|
+
seededHtml.current = _initialHtml;
|
|
11601
11647
|
}
|
|
11602
|
-
}, [contentRef, _initialHtml]);
|
|
11648
|
+
}, [contentRef, _initialHtml, showSource]);
|
|
11603
11649
|
const extClasses = react.useMemo(
|
|
11604
11650
|
() => extensionEditorClasses(extensions),
|
|
11605
11651
|
[extensions]
|
|
11606
11652
|
);
|
|
11653
|
+
if (showSource) {
|
|
11654
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
11655
|
+
"textarea",
|
|
11656
|
+
{
|
|
11657
|
+
"data-react-fancy-editor-source": "",
|
|
11658
|
+
value: _sourceValue,
|
|
11659
|
+
onChange: (e) => _onSourceInput(e.target.value),
|
|
11660
|
+
spellCheck: false,
|
|
11661
|
+
placeholder,
|
|
11662
|
+
"aria-label": `${outputFormat === "markdown" ? "Markdown" : "HTML"} source`,
|
|
11663
|
+
style: {
|
|
11664
|
+
maxHeight: maxHeight ? `${maxHeight}px` : void 0
|
|
11665
|
+
},
|
|
11666
|
+
className: cn(
|
|
11667
|
+
"block min-h-[120px] w-full resize-y px-4 py-3 font-mono text-xs leading-relaxed outline-none",
|
|
11668
|
+
"bg-zinc-50 text-zinc-800 focus:outline-none dark:bg-zinc-900 dark:text-zinc-200",
|
|
11669
|
+
"placeholder:text-zinc-400",
|
|
11670
|
+
maxHeight && "overflow-y-auto",
|
|
11671
|
+
sourceClassName
|
|
11672
|
+
)
|
|
11673
|
+
}
|
|
11674
|
+
);
|
|
11675
|
+
}
|
|
11607
11676
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
11608
11677
|
"div",
|
|
11609
11678
|
{
|
|
@@ -11795,20 +11864,29 @@ function EditorRoot({
|
|
|
11795
11864
|
placeholder,
|
|
11796
11865
|
mode: modeProp,
|
|
11797
11866
|
extensions: instanceExtensions,
|
|
11798
|
-
unsafe = false
|
|
11867
|
+
unsafe = false,
|
|
11868
|
+
showSource: showSourceProp,
|
|
11869
|
+
defaultShowSource = false,
|
|
11870
|
+
onShowSourceChange
|
|
11799
11871
|
}) {
|
|
11800
11872
|
const contentRef = react.useRef(null);
|
|
11801
11873
|
const [value, setValue] = useControllableState(controlledValue, defaultValue, onChange);
|
|
11874
|
+
const [showSource, setShowSource] = useControllableState(
|
|
11875
|
+
showSourceProp,
|
|
11876
|
+
defaultShowSource,
|
|
11877
|
+
onShowSourceChange
|
|
11878
|
+
);
|
|
11802
11879
|
const mode = useFieldMode(modeProp);
|
|
11803
11880
|
const isView = mode === "view";
|
|
11804
11881
|
const initialHtml = react.useMemo(
|
|
11805
11882
|
() => toHtml(value, outputFormat, unsafe, valueFormat),
|
|
11806
|
-
// Seed the contentEditable from the LIVE value when (re)entering edit mode
|
|
11807
|
-
//
|
|
11808
|
-
//
|
|
11809
|
-
//
|
|
11883
|
+
// Seed the contentEditable from the LIVE value when (re)entering edit mode —
|
|
11884
|
+
// and when returning from the raw-source view, which may have rewritten the
|
|
11885
|
+
// value out from under the rich-text surface. EditorContent re-seeds when
|
|
11886
|
+
// this recomputes; it does NOT re-run on every keystroke (`value` is not a
|
|
11887
|
+
// dep, so typing in the rich-text surface never re-seeds it).
|
|
11810
11888
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
11811
|
-
[isView, outputFormat, unsafe, valueFormat]
|
|
11889
|
+
[isView, outputFormat, unsafe, valueFormat, showSource]
|
|
11812
11890
|
);
|
|
11813
11891
|
const extensions = react.useMemo(
|
|
11814
11892
|
() => mergeExtensions(instanceExtensions),
|
|
@@ -11845,6 +11923,12 @@ function EditorRoot({
|
|
|
11845
11923
|
},
|
|
11846
11924
|
[handleInput]
|
|
11847
11925
|
);
|
|
11926
|
+
const handleSourceInput = react.useCallback(
|
|
11927
|
+
(next) => {
|
|
11928
|
+
setValue(next);
|
|
11929
|
+
},
|
|
11930
|
+
[setValue]
|
|
11931
|
+
);
|
|
11848
11932
|
const wrapSelection = react.useCallback(
|
|
11849
11933
|
(before, after) => {
|
|
11850
11934
|
const sel = window.getSelection();
|
|
@@ -11868,8 +11952,12 @@ function EditorRoot({
|
|
|
11868
11952
|
lineSpacing,
|
|
11869
11953
|
placeholder,
|
|
11870
11954
|
extensions,
|
|
11955
|
+
showSource,
|
|
11956
|
+
setShowSource,
|
|
11871
11957
|
_initialHtml: initialHtml,
|
|
11872
|
-
_onInput: handleInput
|
|
11958
|
+
_onInput: handleInput,
|
|
11959
|
+
_sourceValue: value,
|
|
11960
|
+
_onSourceInput: handleSourceInput
|
|
11873
11961
|
};
|
|
11874
11962
|
if (isView) {
|
|
11875
11963
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -11913,7 +12001,8 @@ var ToolbarWithSeparator = Object.assign(EditorToolbar, {
|
|
|
11913
12001
|
});
|
|
11914
12002
|
var Editor = Object.assign(EditorRoot, {
|
|
11915
12003
|
Toolbar: ToolbarWithSeparator,
|
|
11916
|
-
Content: EditorContent
|
|
12004
|
+
Content: EditorContent,
|
|
12005
|
+
SourceToggle: EditorSourceToggle
|
|
11917
12006
|
});
|
|
11918
12007
|
var MenuContext = react.createContext(null);
|
|
11919
12008
|
function useMenu() {
|
|
@@ -12874,6 +12963,7 @@ function TreeNode({ node, depth }) {
|
|
|
12874
12963
|
indentSize,
|
|
12875
12964
|
showIcons,
|
|
12876
12965
|
draggable,
|
|
12966
|
+
dragCursor,
|
|
12877
12967
|
dragState,
|
|
12878
12968
|
setDragState,
|
|
12879
12969
|
onNodeMove,
|
|
@@ -13004,7 +13094,12 @@ function TreeNode({ node, depth }) {
|
|
|
13004
13094
|
isSelected ? "bg-blue-500/15 text-blue-600 dark:text-blue-400" : "text-zinc-700 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800",
|
|
13005
13095
|
node.disabled && "pointer-events-none opacity-40",
|
|
13006
13096
|
isDragging && "opacity-50",
|
|
13007
|
-
|
|
13097
|
+
// Grab affordance. By default (`dragCursor="none"`) the row keeps its
|
|
13098
|
+
// normal click cursor on hover — no open-hand "move me" cursor before
|
|
13099
|
+
// any drag — and only shows the closed-hand `grabbing` cursor while a
|
|
13100
|
+
// drag is actually underway (mousedown/drag). `dragCursor="grab"` opts
|
|
13101
|
+
// back into the always-on grab cursor for whole-row drag handles (#14).
|
|
13102
|
+
canDrag && (dragCursor === "grab" ? "cursor-grab active:cursor-grabbing" : "active:cursor-grabbing"),
|
|
13008
13103
|
isDropTarget && dropPosition === "inside" && "bg-blue-500/10 ring-1 ring-blue-500/30 ring-inset"
|
|
13009
13104
|
),
|
|
13010
13105
|
style: { paddingLeft },
|
|
@@ -13060,6 +13155,7 @@ function TreeNavRoot({
|
|
|
13060
13155
|
defaultExpandAll = false,
|
|
13061
13156
|
indentSize = 16,
|
|
13062
13157
|
showIcons = true,
|
|
13158
|
+
dragCursor = "none",
|
|
13063
13159
|
className
|
|
13064
13160
|
}) {
|
|
13065
13161
|
const [internalExpanded, setInternalExpanded] = react.useState(() => {
|
|
@@ -13104,6 +13200,7 @@ function TreeNavRoot({
|
|
|
13104
13200
|
indentSize,
|
|
13105
13201
|
showIcons,
|
|
13106
13202
|
draggable,
|
|
13203
|
+
dragCursor,
|
|
13107
13204
|
dragState,
|
|
13108
13205
|
setDragState,
|
|
13109
13206
|
onNodeMove,
|
|
@@ -13121,6 +13218,7 @@ function TreeNavRoot({
|
|
|
13121
13218
|
indentSize,
|
|
13122
13219
|
showIcons,
|
|
13123
13220
|
draggable,
|
|
13221
|
+
dragCursor,
|
|
13124
13222
|
dragState,
|
|
13125
13223
|
onNodeMove,
|
|
13126
13224
|
acceptExternalDrops,
|