@particle-academy/react-fancy 4.13.1 → 4.14.0
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 +124 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +52 -4
- package/dist/index.d.ts +52 -4
- package/dist/index.js +124 -35
- 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() {
|