payload-lexical-typography
Version:
PayloadCMS lexical-editor typography extension plugin
1,428 lines (1,399 loc) • 76 kB
JavaScript
"use client";
// src/features/textColor/feature.client.tsx
import { createClientFeature } from "@payloadcms/richtext-lexical/client";
import { COMMAND_PRIORITY_CRITICAL as COMMAND_PRIORITY_CRITICAL2 } from "@payloadcms/richtext-lexical/lexical";
import { useLexicalComposerContext as useLexicalComposerContext2 } from "@payloadcms/richtext-lexical/lexical/react/LexicalComposerContext";
import {
$getSelectionStyleValueForProperty as $getSelectionStyleValueForProperty2,
$patchStyleText as $patchStyleText2
} from "@payloadcms/richtext-lexical/lexical/selection";
import { useEffect as useEffect5 } from "react";
// src/features/textColor/command.ts
import { createCommand } from "@payloadcms/richtext-lexical/lexical";
var TEXT_COLOR_COMMAND = createCommand("TEXT_COLOR_COMMAND");
// src/features/textColor/components/TextColorDropdown.tsx
import { useEffect as useEffect3, useState as useState2 } from "react";
// src/features/textColor/components/TextColorPicker.tsx
import { useEffect as useEffect2, useState } from "react";
import { HexColorPicker } from "react-colorful";
// src/utils/usePreventInlineToolbarClose.ts
import { useEffect, useRef } from "react";
var usePreventInlineToolbarClose = () => {
const containerRef = useRef(null);
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const handleSelectionChange = (e) => {
const activeElement = document.activeElement;
if (activeElement && container.contains(activeElement)) {
e.stopImmediatePropagation();
}
};
const handleMouseUp = (e) => {
if (container.contains(e.target)) {
e.stopImmediatePropagation();
}
};
document.addEventListener("selectionchange", handleSelectionChange, true);
document.addEventListener("mouseup", handleMouseUp, true);
return () => {
document.removeEventListener("selectionchange", handleSelectionChange, true);
document.removeEventListener("mouseup", handleMouseUp, true);
};
}, []);
const handleInputInteraction = (e) => {
e.stopPropagation();
const selection = window.getSelection();
if (selection && selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
e.currentTarget.dataset.editorRange = JSON.stringify({
startContainer: range.startContainer.textContent,
startOffset: range.startOffset,
endContainer: range.endContainer.textContent,
endOffset: range.endOffset
});
}
};
const handleInputBlur = (e) => {
setTimeout(() => {
const activeElement = document.activeElement;
if (!containerRef.current?.contains(activeElement)) {
const rangeData = e.currentTarget.dataset.editorRange;
if (rangeData) {
delete e.currentTarget.dataset.editorRange;
}
}
}, 10);
};
const containerProps = {
ref: containerRef,
onMouseDown: (e) => e.stopPropagation(),
onMouseMove: (e) => e.stopPropagation()
};
const inputProps = {
onFocus: handleInputInteraction,
onBlur: handleInputBlur,
onMouseDown: handleInputInteraction
};
return {
containerProps,
inputProps
};
};
// src/features/textColor/components/TextColorPicker.tsx
import { jsx, jsxs } from "react/jsx-runtime";
var injectStyles = () => {
const style = document.createElement("style");
style.innerHTML = `
div.react-colorful .react-colorful__pointer {
width: 20px;
height: 20px;
}
div.react-colorful .react-colorful__hue {
height: 22px;
}
`;
document.head.appendChild(style);
};
var TextColorPicker = ({
color,
applyColor,
onChange,
colors = [],
hideAttribution = false,
colorPicker = true,
listView,
handleReset
}) => {
const { containerProps, inputProps } = usePreventInlineToolbarClose();
const [predefinedColors, setPredefinedColors] = useState(true);
useEffect2(() => {
injectStyles();
}, []);
const isGridView = listView === false || listView !== true && typeof colors[0] === "string";
return /* @__PURE__ */ jsxs(
"div",
{
...containerProps,
style: {
display: "flex",
flexDirection: "column",
maxWidth: "165px",
width: "100%"
},
children: [
!predefinedColors && colorPicker ? /* @__PURE__ */ jsxs(
"div",
{
onClick: (e) => {
e.preventDefault();
e.stopPropagation();
},
style: {
width: "100%",
paddingTop: "8px",
paddingLeft: "8px",
paddingRight: "8px",
paddingBottom: "0px",
display: "flex",
flexDirection: "column",
alignItems: "center"
},
children: [
/* @__PURE__ */ jsx(
HexColorPicker,
{
style: {
maxWidth: "100%",
height: "min-content",
aspectRatio: "1"
},
color,
onChange
}
),
/* @__PURE__ */ jsx("div", { className: "field-type text", children: /* @__PURE__ */ jsx(
"input",
{
style: {
width: "100%",
margin: "8px 0",
height: "25px",
paddingTop: "0",
paddingBottom: "1px",
paddingLeft: "10px"
},
type: "text",
value: color,
onChange: (e) => {
e.preventDefault();
e.stopPropagation();
onChange(e.target.value);
},
...inputProps
}
) })
]
}
) : /* @__PURE__ */ jsxs(
"div",
{
style: {
display: isGridView ? "grid" : "flex",
gridTemplateColumns: isGridView ? "repeat(5, 1fr)" : void 0,
flexDirection: isGridView ? void 0 : "column",
gap: isGridView ? "4px" : "6px",
padding: "8px",
overflowY: isGridView ? void 0 : "auto",
maxHeight: isGridView ? void 0 : "266px"
},
children: [
colors.map((unionC) => {
const c = typeof unionC === "string" ? unionC : unionC.value;
return /* @__PURE__ */ jsxs(
"button",
{
onClick: () => {
applyColor(c);
},
style: {
display: "flex",
gap: isGridView ? "4px" : "6px",
alignItems: "center",
cursor: "pointer",
background: "transparent",
border: "none",
padding: "0"
},
children: [
/* @__PURE__ */ jsx(
"div",
{
style: {
backgroundColor: c,
width: "26px",
height: "26px",
borderRadius: "50%",
border: color === c ? "2px solid var(--theme-elevation-900)" : "2px solid var(--theme-elevation-150)"
}
}
),
!isGridView && /* @__PURE__ */ jsx("span", { children: typeof unionC === "string" ? unionC : unionC.label })
]
},
c
);
}),
/* @__PURE__ */ jsxs(
"button",
{
onClick: () => handleReset(),
style: {
display: "flex",
gap: isGridView ? "4px" : "6px",
alignItems: "center",
cursor: "pointer",
background: "transparent",
border: "none",
padding: "0"
},
children: [
/* @__PURE__ */ jsx(
"div",
{
style: {
width: "26px",
height: "26px",
borderRadius: "50%",
border: "2px solid var(--theme-elevation-150)",
position: "relative",
cursor: "pointer"
},
children: /* @__PURE__ */ jsx(
"div",
{
style: {
position: "absolute",
width: "100%",
height: "2px",
backgroundColor: "#FF0000",
top: "50%",
left: "50%",
transform: "translate(-50%,-50%) rotate(45deg)"
}
}
)
}
),
!isGridView && /* @__PURE__ */ jsx("span", { children: "Reset" })
]
}
)
]
}
),
!predefinedColors && /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "8px" }, children: [
/* @__PURE__ */ jsx(
"button",
{
onClick: (e) => {
e.preventDefault();
e.stopPropagation();
handleReset();
},
className: "btn btn--icon-style-without-border btn--size-small btn--withoutPopup btn--style-pill btn--withoutPopup",
style: { marginLeft: "auto", margin: "0", cursor: "pointer", flex: 1 },
children: "Reset"
}
),
/* @__PURE__ */ jsx(
"button",
{
onClick: (e) => {
e.preventDefault();
e.stopPropagation();
applyColor();
},
className: "btn btn--icon-style-without-border btn--size-small btn--withoutPopup btn--style-pill btn--withoutPopup",
style: { marginLeft: "auto", margin: "0", cursor: "pointer", flex: 1 },
children: "Apply"
}
)
] }),
/* @__PURE__ */ jsxs(
"div",
{
style: {
width: "100%",
padding: "8px",
display: "flex",
gap: "8px",
flexDirection: "column",
alignItems: "center"
},
children: [
colorPicker && /* @__PURE__ */ jsx(
"button",
{
onClick: (e) => {
e.preventDefault();
e.stopPropagation();
setPredefinedColors((prev) => !prev);
},
className: "btn btn--icon-style-without-border btn--size-small btn--withoutPopup btn--style-pill btn--withoutPopup",
style: {
margin: 0
},
children: predefinedColors ? "Color picker" : "Predefined colors"
}
),
!hideAttribution && /* @__PURE__ */ jsxs(
"p",
{
style: {
color: "var(--theme-elevation-650)",
fontSize: "10px"
},
children: [
"Made with \u2764\uFE0F by",
" ",
/* @__PURE__ */ jsx("a", { target: "_blank", href: "https://github.com/AdrianMaj", children: "@AdrianMaj" })
]
}
)
]
}
)
]
}
);
};
// src/features/textColor/components/TextColorDropdown.tsx
import { jsx as jsx2 } from "react/jsx-runtime";
var TextColorDropdown = ({ editor, item }) => {
const [activeColor, setActiveColor] = useState2("");
const onChange = (color) => {
setActiveColor(color || "");
};
const applyColor = (color) => {
editor.dispatchCommand(item.command, { color: color ?? activeColor });
};
const handleReset = () => {
editor.dispatchCommand(item.command, { color: "" });
setActiveColor("");
};
useEffect3(() => {
editor.read(() => {
const current = item.current ? item.current() : null;
if (current) setActiveColor(current);
});
}, [editor, item]);
return /* @__PURE__ */ jsx2(
TextColorPicker,
{
color: activeColor,
applyColor,
onChange,
colors: item.colors,
hideAttribution: item.hideAttribution,
colorPicker: item.colorPicker,
listView: item.listView,
handleReset
}
);
};
// src/features/textColor/components/TextColorIcon.tsx
import { COMMAND_PRIORITY_CRITICAL, SELECTION_CHANGE_COMMAND } from "@payloadcms/richtext-lexical/lexical";
import { useLexicalComposerContext } from "@payloadcms/richtext-lexical/lexical/react/LexicalComposerContext";
import {
$getSelectionStyleValueForProperty,
$patchStyleText
} from "@payloadcms/richtext-lexical/lexical/selection";
import { useEffect as useEffect4, useState as useState3 } from "react";
// src/utils/getSelection.ts
import { $getSelection, $isRangeSelection } from "@payloadcms/richtext-lexical/lexical";
var getSelection = (selection = $getSelection()) => {
if ($isRangeSelection(selection)) {
return selection;
}
return null;
};
// src/features/textColor/components/TextColorIcon.tsx
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
var TextColorIcon = () => {
const [color, setColor] = useState3("");
const [editor] = useLexicalComposerContext();
const updateCurrentColor = () => {
const selection = getSelection();
if (selection) setColor($getSelectionStyleValueForProperty(selection, "color", ""));
return false;
};
useEffect4(() => {
return editor.registerCommand(
TEXT_COLOR_COMMAND,
(payload) => {
setColor(payload.color);
editor.update(() => {
const selection = getSelection();
if (selection) $patchStyleText(selection, { color: payload.color || "" });
});
return false;
},
COMMAND_PRIORITY_CRITICAL
);
}, [editor]);
useEffect4(() => {
setTimeout(() => {
return editor.read(updateCurrentColor);
});
return editor.registerCommand(SELECTION_CHANGE_COMMAND, updateCurrentColor, COMMAND_PRIORITY_CRITICAL);
}, [editor]);
return /* @__PURE__ */ jsxs2(
"svg",
{
xmlns: "http://www.w3.org/2000/svg",
width: "16",
height: "16",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
strokeWidth: "2",
strokeLinecap: "round",
strokeLinejoin: "round",
children: [
/* @__PURE__ */ jsx3("path", { d: "M4 20h16", style: { color } }),
/* @__PURE__ */ jsx3("path", { d: "m6 16 6-12 6 12" }),
/* @__PURE__ */ jsx3("path", { d: "M8 12h8" })
]
}
);
};
// src/features/textColor/feature.client.tsx
var TextColorClientFeature = createClientFeature(
({ props }) => {
const colors = props?.colors && props?.colors.length > 0 ? props.colors : ["#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#FF00FF"];
const DropdownComponent = {
type: "dropdown",
ChildComponent: TextColorIcon,
isEnabled({ selection }) {
return !!getSelection(selection);
},
items: [
{
Component: () => {
const [editor] = useLexicalComposerContext2();
return TextColorDropdown({
editor,
item: {
command: TEXT_COLOR_COMMAND,
current() {
const selection = getSelection();
return selection ? $getSelectionStyleValueForProperty2(selection, "color", "") : null;
},
colors,
listView: props?.listView,
hideAttribution: props?.hideAttribution,
colorPicker: props?.colorPicker,
key: "textColor"
}
});
},
key: "textColor"
}
],
key: "textColorDropdown",
order: 60
};
return {
plugins: [
{
Component: () => {
const [editor] = useLexicalComposerContext2();
useEffect5(() => {
return editor.registerCommand(
TEXT_COLOR_COMMAND,
(payload) => {
editor.update(() => {
const selection = getSelection();
if (selection) {
$patchStyleText2(selection, { color: payload.color || "" });
}
});
return true;
},
COMMAND_PRIORITY_CRITICAL2
);
}, [editor]);
return null;
},
position: "normal"
}
],
toolbarFixed: {
groups: [DropdownComponent]
},
toolbarInline: {
groups: [DropdownComponent]
}
};
}
);
// src/features/textSize/feature.client.tsx
import { createClientFeature as createClientFeature2 } from "@payloadcms/richtext-lexical/client";
import { COMMAND_PRIORITY_CRITICAL as COMMAND_PRIORITY_CRITICAL4 } from "@payloadcms/richtext-lexical/lexical";
import { useLexicalComposerContext as useLexicalComposerContext4 } from "@payloadcms/richtext-lexical/lexical/react/LexicalComposerContext";
import {
$getSelectionStyleValueForProperty as $getSelectionStyleValueForProperty3,
$patchStyleText as $patchStyleText4
} from "@payloadcms/richtext-lexical/lexical/selection";
import { useEffect as useEffect9 } from "react";
// src/features/textSize/command.ts
import { createCommand as createCommand2 } from "@payloadcms/richtext-lexical/lexical";
var TEXT_SIZE_COMMAND = createCommand2("TEXT_SIZE_COMMAND");
// src/features/textSize/components/TextSizeDropdown.tsx
import { useEffect as useEffect7, useState as useState5 } from "react";
// src/features/textSize/components/TextSizePicker.tsx
import { useState as useState4, useEffect as useEffect6, useRef as useRef2 } from "react";
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
var SizePicker = ({
size,
onChange,
hideAttribution,
sizes,
method = "replace",
scroll = true,
customSize = true
}) => {
const isEditingRef = useRef2(false);
const { containerProps, inputProps } = usePreventInlineToolbarClose();
const defaultSizeOptions = [
{ value: "0.875rem", label: "Small" },
{ value: "1.25rem", label: "Normal" },
{ value: "1.875rem", label: "Large" },
{ value: "3rem", label: "Huge" }
];
const options = method === "replace" ? sizes ?? defaultSizeOptions : [...defaultSizeOptions, ...sizes ?? []];
const units = ["px", "rem", "em", "vh", "vw", "%"];
const [displayValue, setDisplayValue] = useState4(size || "");
const [appliedValue, setAppliedValue] = useState4(size || "");
const [isCustomMode, setIsCustomMode] = useState4(false);
const [customNumberValue, setCustomNumberValue] = useState4("");
const [customUnit, setCustomUnit] = useState4("px");
const parseSizeValue = (sizeVal) => {
const numericPart = parseFloat(sizeVal.replace(/[^0-9.]/g, ""));
const unitPart = sizeVal.replace(/[0-9.]/g, "");
return {
number: isNaN(numericPart) ? "" : numericPart.toString(),
unit: units.includes(unitPart) ? unitPart : "px"
};
};
useEffect6(() => {
if (isEditingRef.current) return;
if (!size) {
setDisplayValue("");
setAppliedValue("");
setIsCustomMode(true);
setCustomNumberValue("");
setCustomUnit("px");
return;
}
setDisplayValue(size);
setAppliedValue(size);
const { number, unit } = parseSizeValue(size);
setCustomNumberValue(number);
setCustomUnit(unit);
const matchingOption = options.find((option) => option.value === size);
setIsCustomMode(!matchingOption);
}, [size, options]);
const handleSizeSelect = (value) => {
setDisplayValue(value);
setAppliedValue(value);
onChange(value);
setIsCustomMode(false);
const { number, unit } = parseSizeValue(value);
setCustomNumberValue(number);
setCustomUnit(unit);
};
const handleCustomNumberChange = (e) => {
e.preventDefault();
e.stopPropagation();
isEditingRef.current = true;
const numValue = e.target.value;
setCustomNumberValue(numValue);
const newValue = `${numValue}${customUnit}`;
setDisplayValue(newValue);
setIsCustomMode(true);
};
const handleCustomUnitChange = (e) => {
e.preventDefault();
e.stopPropagation();
isEditingRef.current = true;
const unitValue = e.target.value;
setCustomUnit(unitValue);
const newValue = `${customNumberValue}${unitValue}`;
setDisplayValue(newValue);
setIsCustomMode(true);
};
const applyCustomSize = () => {
isEditingRef.current = false;
setAppliedValue(displayValue);
onChange(displayValue);
};
const handleReset = () => {
isEditingRef.current = false;
setDisplayValue("");
setAppliedValue("");
setCustomNumberValue("");
setCustomUnit("px");
onChange("");
};
return /* @__PURE__ */ jsxs3(
"div",
{
...containerProps,
style: {
padding: "8px",
display: "flex",
flexDirection: "column",
gap: "8px"
},
children: [
/* @__PURE__ */ jsx4(
"div",
{
style: {
display: "grid",
gridTemplateColumns: "1fr 1fr",
gap: "12px",
maxHeight: scroll && options.length > 4 ? "64px" : "none",
overflowY: scroll && options.length > 4 ? "auto" : "visible",
paddingRight: scroll && options.length > 4 ? "8px" : "0"
},
children: options.map((option, index) => /* @__PURE__ */ jsx4(
"button",
{
className: "btn btn--icon-style-without-border btn--size-small btn--withoutPopup btn--style-pill btn--withoutPopup",
style: {
cursor: "pointer",
margin: "0",
border: appliedValue === option.value && !isCustomMode ? "1px solid var(--theme-elevation-900)" : "1px solid transparent"
},
onClick: (e) => {
e.preventDefault();
e.stopPropagation();
handleSizeSelect(option.value);
},
children: option.label
},
`${option.value}-${index}`
))
}
),
customSize && /* @__PURE__ */ jsxs3("div", { style: { display: "flex", alignItems: "center" }, children: [
/* @__PURE__ */ jsx4("div", { style: { marginRight: "8px" }, children: "Custom: " }),
/* @__PURE__ */ jsxs3(
"div",
{
style: {
display: "flex",
alignItems: "center",
width: "140px"
},
children: [
/* @__PURE__ */ jsx4(
"div",
{
className: "field-type number",
onClick: (e) => {
e.stopPropagation();
},
style: { flex: 1 },
children: /* @__PURE__ */ jsx4(
"input",
{
style: {
width: "100%",
margin: "8px 0",
borderRight: "0",
height: "25px",
borderTopRightRadius: "0",
borderBottomRightRadius: "0",
paddingTop: "0",
paddingBottom: "1px",
paddingLeft: "4px",
paddingRight: "4px"
},
type: "number",
min: 1,
max: 999,
value: customNumberValue,
onChange: handleCustomNumberChange,
onClick: (e) => e.stopPropagation(),
...inputProps
}
)
}
),
/* @__PURE__ */ jsx4(
"select",
{
value: customUnit,
onChange: handleCustomUnitChange,
onClick: (e) => e.stopPropagation(),
style: {
paddingLeft: "4px",
paddingRight: "4px",
width: "56px",
boxShadow: "0 2px 2px -1px #0000001a",
fontFamily: "var(--font-body)",
border: "1px solid var(--theme-elevation-150)",
borderRadius: "var(--style-radius-s)",
background: "var(--theme-input-bg)",
color: "var(--theme-elevation-800)",
fontSize: "1rem",
height: "25px",
lineHeight: "20px",
transitionProperty: "border, box-shadow, background-color",
transitionDuration: ".1s, .1s, .5s",
transitionTimingFunction: "cubic-bezier(0,.2,.2,1)",
borderLeft: "0",
transform: "translateX(-1px)",
borderTopLeftRadius: "0",
borderBottomLeftRadius: "0",
outline: "none"
},
children: units.map((unit, index) => /* @__PURE__ */ jsx4("option", { value: unit, children: unit }, `${unit}-${index}`))
}
)
]
}
)
] }),
/* @__PURE__ */ jsxs3("div", { style: { display: "flex", gap: "8px" }, children: [
/* @__PURE__ */ jsx4(
"button",
{
onClick: (e) => {
e.preventDefault();
e.stopPropagation();
handleReset();
},
className: "btn btn--icon-style-without-border btn--size-small btn--withoutPopup btn--style-pill btn--withoutPopup",
style: { marginLeft: "auto", margin: "0", cursor: "pointer", flex: 1 },
children: "Reset"
}
),
customSize && /* @__PURE__ */ jsx4(
"button",
{
onClick: (e) => {
e.preventDefault();
e.stopPropagation();
applyCustomSize();
},
className: "btn btn--icon-style-without-border btn--size-small btn--withoutPopup btn--style-pill btn--withoutPopup",
style: { marginLeft: "auto", margin: "0", cursor: "pointer", flex: 1 },
children: "Apply"
}
)
] }),
!hideAttribution && /* @__PURE__ */ jsxs3(
"p",
{
style: {
color: "var(--theme-elevation-650)",
fontSize: "10px",
textAlign: "center"
},
children: [
"Made with \u2764\uFE0F by",
" ",
/* @__PURE__ */ jsx4("a", { target: "_blank", href: "https://github.com/AdrianMaj", children: "@AdrianMaj" })
]
}
)
]
}
);
};
// src/features/textSize/components/TextSizeDropdown.tsx
import { jsx as jsx5 } from "react/jsx-runtime";
var Dropdown = ({ editor, item }) => {
const [activeSize, setActiveSize] = useState5("");
const onChange = (size) => {
editor.dispatchCommand(item.command, { size });
setActiveSize(size || "");
};
useEffect7(() => {
editor.read(() => {
const current = item.current ? item.current() : null;
if (current) setActiveSize(current);
});
}, [editor, item]);
return /* @__PURE__ */ jsx5(
SizePicker,
{
size: activeSize,
onChange,
hideAttribution: item.hideAttribution,
method: item.method,
scroll: item.scroll,
sizes: item.sizes,
customSize: item.customSize
}
);
};
// src/features/textSize/components/TextSizeIcon.tsx
import { COMMAND_PRIORITY_CRITICAL as COMMAND_PRIORITY_CRITICAL3 } from "@payloadcms/richtext-lexical/lexical";
import { useLexicalComposerContext as useLexicalComposerContext3 } from "@payloadcms/richtext-lexical/lexical/react/LexicalComposerContext";
import { $patchStyleText as $patchStyleText3 } from "@payloadcms/richtext-lexical/lexical/selection";
import { useEffect as useEffect8 } from "react";
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
var TextSizeIcon = () => {
const [editor] = useLexicalComposerContext3();
useEffect8(() => {
return editor.registerCommand(
TEXT_SIZE_COMMAND,
(payload) => {
editor.update(() => {
const selection = getSelection();
if (selection) $patchStyleText3(selection, { size: payload.size || "" });
});
return false;
},
COMMAND_PRIORITY_CRITICAL3
);
}, [editor]);
return /* @__PURE__ */ jsxs4(
"svg",
{
xmlns: "http://www.w3.org/2000/svg",
width: "16",
height: "16",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
strokeWidth: "2",
strokeLinecap: "round",
strokeLinejoin: "round",
children: [
/* @__PURE__ */ jsx6("path", { d: "M21 14h-5" }),
/* @__PURE__ */ jsx6("path", { d: "M16 16v-3.5a2.5 2.5 0 0 1 5 0V16" }),
/* @__PURE__ */ jsx6("path", { d: "M4.5 13h6" }),
/* @__PURE__ */ jsx6("path", { d: "m3 16 4.5-9 4.5 9" })
]
}
);
};
// src/features/textSize/feature.client.tsx
var TextSizeClientFeature = createClientFeature2(({ props }) => {
const DropdownComponent = {
type: "dropdown",
ChildComponent: TextSizeIcon,
isEnabled({ selection }) {
return !!getSelection(selection);
},
items: [
{
Component: () => {
const [editor] = useLexicalComposerContext4();
return Dropdown({
editor,
item: {
command: TEXT_SIZE_COMMAND,
current() {
const selection = getSelection();
return selection ? $getSelectionStyleValueForProperty3(selection, "font-size", "") : null;
},
hideAttribution: props?.hideAttribution,
sizes: props?.sizes,
method: props?.method,
scroll: props?.scroll,
customSize: props?.customSize,
key: "textSize"
}
});
},
key: "textSize"
}
],
key: "textSizeDropdown",
order: 60
};
return {
plugins: [
{
Component: () => {
const [editor] = useLexicalComposerContext4();
useEffect9(() => {
return editor.registerCommand(
TEXT_SIZE_COMMAND,
(payload) => {
editor.update(() => {
const selection = getSelection();
if (selection) {
$patchStyleText4(selection, { "font-size": payload.size || "" });
}
});
return true;
},
COMMAND_PRIORITY_CRITICAL4
);
}, [editor]);
return null;
},
position: "normal"
}
],
toolbarFixed: {
groups: [DropdownComponent]
},
toolbarInline: {
groups: [DropdownComponent]
}
};
});
// src/features/textLetterSpacing/feature.client.tsx
import { createClientFeature as createClientFeature3 } from "@payloadcms/richtext-lexical/client";
import { COMMAND_PRIORITY_CRITICAL as COMMAND_PRIORITY_CRITICAL6 } from "@payloadcms/richtext-lexical/lexical";
import { useLexicalComposerContext as useLexicalComposerContext6 } from "@payloadcms/richtext-lexical/lexical/react/LexicalComposerContext";
import {
$getSelectionStyleValueForProperty as $getSelectionStyleValueForProperty4,
$patchStyleText as $patchStyleText6
} from "@payloadcms/richtext-lexical/lexical/selection";
import { useEffect as useEffect13 } from "react";
// src/features/textLetterSpacing/command.ts
import { createCommand as createCommand3 } from "@payloadcms/richtext-lexical/lexical";
var TEXT_LETTER_SPACING_COMMAND = createCommand3("TEXT_LETTER_SPACING_COMMAND");
// src/features/textLetterSpacing/components/TextLetterSpacingDropdown.tsx
import { useEffect as useEffect11, useState as useState7 } from "react";
// src/features/textLetterSpacing/components/TextLetterSpacingPicker.tsx
import { useState as useState6, useEffect as useEffect10, useRef as useRef3 } from "react";
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
var SpacingPicker = ({
spacing,
onChange,
hideAttribution,
spacings,
method = "replace",
scroll = true,
customSpacing = true
}) => {
const { containerProps, inputProps } = usePreventInlineToolbarClose();
const isEditingRef = useRef3(false);
const defaultSpacingOptions = [
{ value: "-0.05em", label: "Tighter" },
{ value: "-0.025em", label: "Tight" },
{ value: "0em", label: "Normal" },
{ value: "0.025em", label: "Wide" },
{ value: "0.05em", label: "Wider" },
{ value: "0.1em", label: "Widest" }
];
const options = method === "replace" ? spacings ?? defaultSpacingOptions : [...defaultSpacingOptions, ...spacings ?? []];
const units = ["px", "rem", "em", "%"];
const [displayValue, setDisplayValue] = useState6(spacing || "");
const [appliedValue, setAppliedValue] = useState6(spacing || "");
const [isCustomMode, setIsCustomMode] = useState6(false);
const [customNumberValue, setCustomNumberValue] = useState6("");
const [customUnit, setCustomUnit] = useState6("em");
const parseSpacingValue = (spacingVal) => {
const sign = spacingVal.startsWith("-") ? "-" : "";
const cleanedVal = spacingVal.replace(/^-?/, "").replace(/[^0-9.]/g, "");
const numericPart = parseFloat(sign + cleanedVal);
const unitPart = spacingVal.replace(/^-?[0-9.]/g, "");
return {
number: isNaN(numericPart) ? "" : numericPart.toString(),
unit: units.includes(unitPart) ? unitPart : "em"
};
};
useEffect10(() => {
if (isEditingRef.current) return;
if (!spacing) {
setDisplayValue("");
setAppliedValue("");
setIsCustomMode(true);
setCustomNumberValue("");
setCustomUnit("em");
return;
}
setDisplayValue(spacing);
setAppliedValue(spacing);
const { number, unit } = parseSpacingValue(spacing);
setCustomNumberValue(number);
setCustomUnit(unit);
const matchingOption = options.find((option) => option.value === spacing);
setIsCustomMode(!matchingOption);
}, [spacing, options]);
const handleSpacingSelect = (value) => {
setDisplayValue(value);
setAppliedValue(value);
onChange(value);
setIsCustomMode(false);
const { number, unit } = parseSpacingValue(value);
setCustomNumberValue(number);
setCustomUnit(unit);
};
const handleCustomNumberChange = (e) => {
e.preventDefault();
e.stopPropagation();
isEditingRef.current = true;
const numValue = e.target.value;
setCustomNumberValue(numValue);
const newValue = `${numValue}${customUnit}`;
setDisplayValue(newValue);
setIsCustomMode(true);
};
const handleCustomUnitChange = (e) => {
e.preventDefault();
e.stopPropagation();
isEditingRef.current = true;
const unitValue = e.target.value;
setCustomUnit(unitValue);
const newValue = `${customNumberValue}${unitValue}`;
setDisplayValue(newValue);
setIsCustomMode(true);
};
const applyCustomSpacing = () => {
isEditingRef.current = false;
setAppliedValue(displayValue);
onChange(displayValue);
};
const handleReset = () => {
isEditingRef.current = false;
setDisplayValue("");
setAppliedValue("");
setCustomNumberValue("");
setCustomUnit("em");
onChange("");
};
return /* @__PURE__ */ jsxs5(
"div",
{
...containerProps,
style: {
padding: "8px",
display: "flex",
flexDirection: "column",
gap: "8px"
},
children: [
/* @__PURE__ */ jsx7(
"div",
{
style: {
display: "grid",
gridTemplateColumns: "1fr 1fr",
gap: "12px",
maxHeight: scroll && options.length > 4 ? "64px" : "none",
overflowY: scroll && options.length > 4 ? "auto" : "visible",
paddingRight: scroll && options.length > 4 ? "8px" : "0"
},
children: options.map((option, index) => /* @__PURE__ */ jsx7(
"button",
{
className: "btn btn--icon-style-without-border btn--size-small btn--withoutPopup btn--style-pill btn--withoutPopup",
style: {
cursor: "pointer",
margin: "0",
border: appliedValue === option.value && !isCustomMode ? "1px solid var(--theme-elevation-900)" : "1px solid transparent"
},
onClick: (e) => {
e.preventDefault();
e.stopPropagation();
handleSpacingSelect(option.value);
},
children: option.label
},
`${option.value}-${index}`
))
}
),
customSpacing && /* @__PURE__ */ jsxs5("div", { style: { display: "flex", alignItems: "center" }, children: [
/* @__PURE__ */ jsx7("div", { style: { marginRight: "8px" }, children: "Custom: " }),
/* @__PURE__ */ jsxs5(
"div",
{
style: {
display: "flex",
alignItems: "center",
width: "140px"
},
children: [
/* @__PURE__ */ jsx7(
"div",
{
className: "field-type number",
onClick: (e) => {
e.stopPropagation();
},
style: { flex: 1 },
children: /* @__PURE__ */ jsx7(
"input",
{
style: {
width: "100%",
margin: "8px 0",
borderRight: "0",
height: "25px",
borderTopRightRadius: "0",
borderBottomRightRadius: "0",
paddingTop: "0",
paddingBottom: "1px",
paddingLeft: "4px",
paddingRight: "4px"
},
type: "number",
min: 0,
step: 0.01,
max: 10,
value: customNumberValue,
onChange: handleCustomNumberChange,
onClick: (e) => e.stopPropagation(),
...inputProps
}
)
}
),
/* @__PURE__ */ jsx7(
"select",
{
value: customUnit,
onChange: handleCustomUnitChange,
onClick: (e) => e.stopPropagation(),
style: {
paddingLeft: "4px",
paddingRight: "4px",
width: "56px",
boxShadow: "0 2px 2px -1px #0000001a",
fontFamily: "var(--font-body)",
border: "1px solid var(--theme-elevation-150)",
borderRadius: "var(--style-radius-s)",
background: "var(--theme-input-bg)",
color: "var(--theme-elevation-800)",
fontSize: "1rem",
height: "25px",
lineHeight: "20px",
transitionProperty: "border, box-shadow, background-color",
transitionDuration: ".1s, .1s, .5s",
transitionTimingFunction: "cubic-bezier(0,.2,.2,1)",
borderLeft: "0",
transform: "translateX(-1px)",
borderTopLeftRadius: "0",
borderBottomLeftRadius: "0",
outline: "none"
},
children: units.map((unit, index) => /* @__PURE__ */ jsx7("option", { value: unit, children: unit }, `${unit}-${index}`))
}
)
]
}
)
] }),
/* @__PURE__ */ jsxs5("div", { style: { display: "flex", gap: "8px" }, children: [
/* @__PURE__ */ jsx7(
"button",
{
onClick: (e) => {
e.preventDefault();
e.stopPropagation();
handleReset();
},
className: "btn btn--icon-style-without-border btn--size-small btn--withoutPopup btn--style-pill btn--withoutPopup",
style: { marginLeft: "auto", margin: "0", cursor: "pointer", flex: 1 },
children: "Reset"
}
),
customSpacing && /* @__PURE__ */ jsx7(
"button",
{
onClick: (e) => {
e.preventDefault();
e.stopPropagation();
applyCustomSpacing();
},
className: "btn btn--icon-style-without-border btn--size-small btn--withoutPopup btn--style-pill btn--withoutPopup",
style: { marginLeft: "auto", margin: "0", cursor: "pointer", flex: 1 },
children: "Apply"
}
)
] }),
!hideAttribution && /* @__PURE__ */ jsxs5(
"p",
{
style: {
color: "var(--theme-elevation-650)",
fontSize: "10px",
textAlign: "center"
},
children: [
"Made with \u2764\uFE0F by",
" ",
/* @__PURE__ */ jsx7("a", { target: "_blank", href: "https://github.com/AdrianMaj", children: "@AdrianMaj" })
]
}
)
]
}
);
};
// src/features/textLetterSpacing/components/TextLetterSpacingDropdown.tsx
import { jsx as jsx8 } from "react/jsx-runtime";
var Dropdown2 = ({ editor, item }) => {
const [activeSpacing, setActiveSpacing] = useState7("");
const onChange = (spacing) => {
editor.dispatchCommand(item.command, { spacing });
setActiveSpacing(spacing || "");
};
useEffect11(() => {
editor.read(() => {
const current = item.current ? item.current() : null;
if (current) setActiveSpacing(current);
});
}, [editor, item]);
return /* @__PURE__ */ jsx8(
SpacingPicker,
{
spacing: activeSpacing,
onChange,
hideAttribution: item.hideAttribution,
method: item.method,
scroll: item.scroll,
spacings: item.spacings,
customSpacing: item.customSpacing
}
);
};
// src/features/textLetterSpacing/components/TextLetterSpacingIcon.tsx
import { COMMAND_PRIORITY_CRITICAL as COMMAND_PRIORITY_CRITICAL5 } from "@payloadcms/richtext-lexical/lexical";
import { useLexicalComposerContext as useLexicalComposerContext5 } from "@payloadcms/richtext-lexical/lexical/react/LexicalComposerContext";
import { $patchStyleText as $patchStyleText5 } from "@payloadcms/richtext-lexical/lexical/selection";
import { useEffect as useEffect12 } from "react";
import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
var TextLetterSpacingIcon = () => {
const [editor] = useLexicalComposerContext5();
useEffect12(() => {
return editor.registerCommand(
TEXT_LETTER_SPACING_COMMAND,
(payload) => {
editor.update(() => {
const selection = getSelection();
if (selection) $patchStyleText5(selection, { "letter-spacing": payload.spacing || "" });
});
return false;
},
COMMAND_PRIORITY_CRITICAL5
);
}, [editor]);
return /* @__PURE__ */ jsxs6(
"svg",
{
xmlns: "http://www.w3.org/2000/svg",
width: "16",
height: "16",
viewBox: "0 0 24 24",
fill: "none",
stroke: "currentColor",
strokeWidth: "2",
strokeLinecap: "round",
strokeLinejoin: "round",
children: [
/* @__PURE__ */ jsx9("path", { d: "M2 18h2" }),
/* @__PURE__ */ jsx9("path", { d: "M20 18h2" }),
/* @__PURE__ */ jsx9("path", { d: "M4 7v11" }),
/* @__PURE__ */ jsx9("path", { d: "M20 7v11" }),
/* @__PURE__ */ jsx9("path", { d: "M12 20v2" }),
/* @__PURE__ */ jsx9("path", { d: "M12 14v2" }),
/* @__PURE__ */ jsx9("path", { d: "M12 8v2" }),
/* @__PURE__ */ jsx9("path", { d: "M12 2v2" })
]
}
);
};
// src/features/textLetterSpacing/feature.client.tsx
var TextLetterSpacingClientFeature = createClientFeature3(({ props }) => {
const DropdownComponent = {
type: "dropdown",
ChildComponent: TextLetterSpacingIcon,
isEnabled({ selection }) {
return !!getSelection(selection);
},
items: [
{
Component: () => {
const [editor] = useLexicalComposerContext6();
return Dropdown2({
editor,
item: {
command: TEXT_LETTER_SPACING_COMMAND,
current() {
const selection = getSelection();
return selection ? $getSelectionStyleValueForProperty4(selection, "letter-spacing", "") : null;
},
hideAttribution: props?.hideAttribution,
spacings: props?.spacings,
method: props?.method,
scroll: props?.scroll,
customSpacing: props?.customSpacing,
key: "textLetterSpacing"
}
});
},
key: "textLetterSpacing"
}
],
key: "textLetterSpacingDropdown",
order: 62
};
return {
plugins: [
{
Component: () => {
const [editor] = useLexicalComposerContext6();
useEffect13(() => {
return editor.registerCommand(
TEXT_LETTER_SPACING_COMMAND,
(payload) => {
editor.update(() => {
const selection = getSelection();
if (selection) {
$patchStyleText6(selection, { "letter-spacing": payload.spacing || "" });
}
});
return true;
},
COMMAND_PRIORITY_CRITICAL6
);
}, [editor]);
return null;
},
position: "normal"
}
],
toolbarFixed: {
groups: [DropdownComponent]
},
toolbarInline: {
groups: [DropdownComponent]
}
};
});
// src/features/textLineHeight/feature.client.tsx
import { createClientFeature as createClientFeature4 } from "@payloadcms/richtext-lexical/client";
import { COMMAND_PRIORITY_CRITICAL as COMMAND_PRIORITY_CRITICAL7 } from "@payloadcms/richtext-lexical/lexical";
import { useLexicalComposerContext as useLexicalComposerContext7 } from "@payloadcms/richtext-lexical/lexical/react/LexicalComposerContext";
import {
$getSelectionStyleValueForProperty as $getSelectionStyleValueForProperty5,
$patchStyleText as $patchStyleText7
} from "@payloadcms/richtext-lexical/lexical/selection";
import { useEffect as useEffect16 } from "react";
// src/features/textLineHeight/command.ts
import { createCommand as createCommand4 } from "@payloadcms/richtext-lexical/lexical";
var TEXT_LINE_HEIGHT_COMMAND = createCommand4("TEXT_LINE_HEIGHT_COMMAND");
// src/features/textLineHeight/components/TextLineHeightDropdown.tsx
import { useEffect as useEffect15, useState as useState9 } from "react";
// src/features/textLineHeight/components/TextLineHeightPicker.tsx
import { useState as useState8, useEffect as useEffect14, useRef as useRef4 } from "react";
import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
var TextLineHeightPicker = ({
currentValue,
onChange,
lineHeights,
customLineHeight,
hideAttribution,
scroll = true,
method = "replace"
}) => {
const { containerProps, inputProps } = usePreventInlineToolbarClose();
const isEditingRef = useRef4(false);
const defaultLineHeights = [
{ value: "1", label: "1" },
{ value: "1.5", label: "1.5" },
{ value: "2", label: "2" },
{ value: "2.5", label: "2.5" }
];
const options = method === "replace" ? lineHeights ?? defaultLineHeights : [...defaultLineHeights, ...lineHeights ?? []];
const units = ["", "px", "rem", "em", "vh", "vw", "%"];
const [displayValue, setDisplayValue] = useState8(currentValue || "");
const [appliedValue, setAppliedValue] = useState8(currentValue || "");
const [isCustomMode, setIsCustomMode] = useState8(false);
const [customNumb