payload-lexical-typography
Version:
PayloadCMS lexical-editor typography extension plugin
1,359 lines (1,332 loc) • 80.3 kB
JavaScript
"use strict";
"use client";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/client.ts
var client_exports = {};
__export(client_exports, {
TextColorClientFeature: () => TextColorClientFeature,
TextFontFamilyClientFeature: () => TextFontFamilyClientFeature,
TextLetterSpacingClientFeature: () => TextLetterSpacingClientFeature,
TextLineHeightClientFeature: () => TextLineHeightClientFeature,
TextSizeClientFeature: () => TextSizeClientFeature
});
module.exports = __toCommonJS(client_exports);
// src/features/textColor/feature.client.tsx
var import_client = require("@payloadcms/richtext-lexical/client");
var import_lexical4 = require("@payloadcms/richtext-lexical/lexical");
var import_LexicalComposerContext2 = require("@payloadcms/richtext-lexical/lexical/react/LexicalComposerContext");
var import_selection2 = require("@payloadcms/richtext-lexical/lexical/selection");
var import_react5 = require("react");
// src/features/textColor/command.ts
var import_lexical = require("@payloadcms/richtext-lexical/lexical");
var TEXT_COLOR_COMMAND = (0, import_lexical.createCommand)("TEXT_COLOR_COMMAND");
// src/features/textColor/components/TextColorDropdown.tsx
var import_react3 = require("react");
// src/features/textColor/components/TextColorPicker.tsx
var import_react2 = require("react");
var import_react_colorful = require("react-colorful");
// src/utils/usePreventInlineToolbarClose.ts
var import_react = require("react");
var usePreventInlineToolbarClose = () => {
const containerRef = (0, import_react.useRef)(null);
(0, import_react.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
var import_jsx_runtime = require("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] = (0, import_react2.useState)(true);
(0, import_react2.useEffect)(() => {
injectStyles();
}, []);
const isGridView = listView === false || listView !== true && typeof colors[0] === "string";
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
"div",
{
...containerProps,
style: {
display: "flex",
flexDirection: "column",
maxWidth: "165px",
width: "100%"
},
children: [
!predefinedColors && colorPicker ? /* @__PURE__ */ (0, import_jsx_runtime.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__ */ (0, import_jsx_runtime.jsx)(
import_react_colorful.HexColorPicker,
{
style: {
maxWidth: "100%",
height: "min-content",
aspectRatio: "1"
},
color,
onChange
}
),
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "field-type text", children: /* @__PURE__ */ (0, import_jsx_runtime.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__ */ (0, import_jsx_runtime.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__ */ (0, import_jsx_runtime.jsxs)(
"button",
{
onClick: () => {
applyColor(c);
},
style: {
display: "flex",
gap: isGridView ? "4px" : "6px",
alignItems: "center",
cursor: "pointer",
background: "transparent",
border: "none",
padding: "0"
},
children: [
/* @__PURE__ */ (0, import_jsx_runtime.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__ */ (0, import_jsx_runtime.jsx)("span", { children: typeof unionC === "string" ? unionC : unionC.label })
]
},
c
);
}),
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
"button",
{
onClick: () => handleReset(),
style: {
display: "flex",
gap: isGridView ? "4px" : "6px",
alignItems: "center",
cursor: "pointer",
background: "transparent",
border: "none",
padding: "0"
},
children: [
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
"div",
{
style: {
width: "26px",
height: "26px",
borderRadius: "50%",
border: "2px solid var(--theme-elevation-150)",
position: "relative",
cursor: "pointer"
},
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
"div",
{
style: {
position: "absolute",
width: "100%",
height: "2px",
backgroundColor: "#FF0000",
top: "50%",
left: "50%",
transform: "translate(-50%,-50%) rotate(45deg)"
}
}
)
}
),
!isGridView && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "Reset" })
]
}
)
]
}
),
!predefinedColors && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", gap: "8px" }, children: [
/* @__PURE__ */ (0, import_jsx_runtime.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__ */ (0, import_jsx_runtime.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__ */ (0, import_jsx_runtime.jsxs)(
"div",
{
style: {
width: "100%",
padding: "8px",
display: "flex",
gap: "8px",
flexDirection: "column",
alignItems: "center"
},
children: [
colorPicker && /* @__PURE__ */ (0, import_jsx_runtime.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__ */ (0, import_jsx_runtime.jsxs)(
"p",
{
style: {
color: "var(--theme-elevation-650)",
fontSize: "10px"
},
children: [
"Made with \u2764\uFE0F by",
" ",
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { target: "_blank", href: "https://github.com/AdrianMaj", children: "@AdrianMaj" })
]
}
)
]
}
)
]
}
);
};
// src/features/textColor/components/TextColorDropdown.tsx
var import_jsx_runtime2 = require("react/jsx-runtime");
var TextColorDropdown = ({ editor, item }) => {
const [activeColor, setActiveColor] = (0, import_react3.useState)("");
const onChange = (color) => {
setActiveColor(color || "");
};
const applyColor = (color) => {
editor.dispatchCommand(item.command, { color: color ?? activeColor });
};
const handleReset = () => {
editor.dispatchCommand(item.command, { color: "" });
setActiveColor("");
};
(0, import_react3.useEffect)(() => {
editor.read(() => {
const current = item.current ? item.current() : null;
if (current) setActiveColor(current);
});
}, [editor, item]);
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
TextColorPicker,
{
color: activeColor,
applyColor,
onChange,
colors: item.colors,
hideAttribution: item.hideAttribution,
colorPicker: item.colorPicker,
listView: item.listView,
handleReset
}
);
};
// src/features/textColor/components/TextColorIcon.tsx
var import_lexical3 = require("@payloadcms/richtext-lexical/lexical");
var import_LexicalComposerContext = require("@payloadcms/richtext-lexical/lexical/react/LexicalComposerContext");
var import_selection = require("@payloadcms/richtext-lexical/lexical/selection");
var import_react4 = require("react");
// src/utils/getSelection.ts
var import_lexical2 = require("@payloadcms/richtext-lexical/lexical");
var getSelection = (selection = (0, import_lexical2.$getSelection)()) => {
if ((0, import_lexical2.$isRangeSelection)(selection)) {
return selection;
}
return null;
};
// src/features/textColor/components/TextColorIcon.tsx
var import_jsx_runtime3 = require("react/jsx-runtime");
var TextColorIcon = () => {
const [color, setColor] = (0, import_react4.useState)("");
const [editor] = (0, import_LexicalComposerContext.useLexicalComposerContext)();
const updateCurrentColor = () => {
const selection = getSelection();
if (selection) setColor((0, import_selection.$getSelectionStyleValueForProperty)(selection, "color", ""));
return false;
};
(0, import_react4.useEffect)(() => {
return editor.registerCommand(
TEXT_COLOR_COMMAND,
(payload) => {
setColor(payload.color);
editor.update(() => {
const selection = getSelection();
if (selection) (0, import_selection.$patchStyleText)(selection, { color: payload.color || "" });
});
return false;
},
import_lexical3.COMMAND_PRIORITY_CRITICAL
);
}, [editor]);
(0, import_react4.useEffect)(() => {
setTimeout(() => {
return editor.read(updateCurrentColor);
});
return editor.registerCommand(import_lexical3.SELECTION_CHANGE_COMMAND, updateCurrentColor, import_lexical3.COMMAND_PRIORITY_CRITICAL);
}, [editor]);
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
"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__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M4 20h16", style: { color } }),
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "m6 16 6-12 6 12" }),
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("path", { d: "M8 12h8" })
]
}
);
};
// src/features/textColor/feature.client.tsx
var TextColorClientFeature = (0, import_client.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] = (0, import_LexicalComposerContext2.useLexicalComposerContext)();
return TextColorDropdown({
editor,
item: {
command: TEXT_COLOR_COMMAND,
current() {
const selection = getSelection();
return selection ? (0, import_selection2.$getSelectionStyleValueForProperty)(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] = (0, import_LexicalComposerContext2.useLexicalComposerContext)();
(0, import_react5.useEffect)(() => {
return editor.registerCommand(
TEXT_COLOR_COMMAND,
(payload) => {
editor.update(() => {
const selection = getSelection();
if (selection) {
(0, import_selection2.$patchStyleText)(selection, { color: payload.color || "" });
}
});
return true;
},
import_lexical4.COMMAND_PRIORITY_CRITICAL
);
}, [editor]);
return null;
},
position: "normal"
}
],
toolbarFixed: {
groups: [DropdownComponent]
},
toolbarInline: {
groups: [DropdownComponent]
}
};
}
);
// src/features/textSize/feature.client.tsx
var import_client2 = require("@payloadcms/richtext-lexical/client");
var import_lexical7 = require("@payloadcms/richtext-lexical/lexical");
var import_LexicalComposerContext4 = require("@payloadcms/richtext-lexical/lexical/react/LexicalComposerContext");
var import_selection4 = require("@payloadcms/richtext-lexical/lexical/selection");
var import_react9 = require("react");
// src/features/textSize/command.ts
var import_lexical5 = require("@payloadcms/richtext-lexical/lexical");
var TEXT_SIZE_COMMAND = (0, import_lexical5.createCommand)("TEXT_SIZE_COMMAND");
// src/features/textSize/components/TextSizeDropdown.tsx
var import_react7 = require("react");
// src/features/textSize/components/TextSizePicker.tsx
var import_react6 = require("react");
var import_jsx_runtime4 = require("react/jsx-runtime");
var SizePicker = ({
size,
onChange,
hideAttribution,
sizes,
method = "replace",
scroll = true,
customSize = true
}) => {
const isEditingRef = (0, import_react6.useRef)(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] = (0, import_react6.useState)(size || "");
const [appliedValue, setAppliedValue] = (0, import_react6.useState)(size || "");
const [isCustomMode, setIsCustomMode] = (0, import_react6.useState)(false);
const [customNumberValue, setCustomNumberValue] = (0, import_react6.useState)("");
const [customUnit, setCustomUnit] = (0, import_react6.useState)("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"
};
};
(0, import_react6.useEffect)(() => {
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__ */ (0, import_jsx_runtime4.jsxs)(
"div",
{
...containerProps,
style: {
padding: "8px",
display: "flex",
flexDirection: "column",
gap: "8px"
},
children: [
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
"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__ */ (0, import_jsx_runtime4.jsx)(
"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__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { display: "flex", alignItems: "center" }, children: [
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { marginRight: "8px" }, children: "Custom: " }),
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
"div",
{
style: {
display: "flex",
alignItems: "center",
width: "140px"
},
children: [
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
"div",
{
className: "field-type number",
onClick: (e) => {
e.stopPropagation();
},
style: { flex: 1 },
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
"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__ */ (0, import_jsx_runtime4.jsx)(
"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__ */ (0, import_jsx_runtime4.jsx)("option", { value: unit, children: unit }, `${unit}-${index}`))
}
)
]
}
)
] }),
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: { display: "flex", gap: "8px" }, children: [
/* @__PURE__ */ (0, import_jsx_runtime4.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"
}
),
customSize && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
"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__ */ (0, import_jsx_runtime4.jsxs)(
"p",
{
style: {
color: "var(--theme-elevation-650)",
fontSize: "10px",
textAlign: "center"
},
children: [
"Made with \u2764\uFE0F by",
" ",
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("a", { target: "_blank", href: "https://github.com/AdrianMaj", children: "@AdrianMaj" })
]
}
)
]
}
);
};
// src/features/textSize/components/TextSizeDropdown.tsx
var import_jsx_runtime5 = require("react/jsx-runtime");
var Dropdown = ({ editor, item }) => {
const [activeSize, setActiveSize] = (0, import_react7.useState)("");
const onChange = (size) => {
editor.dispatchCommand(item.command, { size });
setActiveSize(size || "");
};
(0, import_react7.useEffect)(() => {
editor.read(() => {
const current = item.current ? item.current() : null;
if (current) setActiveSize(current);
});
}, [editor, item]);
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
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
var import_lexical6 = require("@payloadcms/richtext-lexical/lexical");
var import_LexicalComposerContext3 = require("@payloadcms/richtext-lexical/lexical/react/LexicalComposerContext");
var import_selection3 = require("@payloadcms/richtext-lexical/lexical/selection");
var import_react8 = require("react");
var import_jsx_runtime6 = require("react/jsx-runtime");
var TextSizeIcon = () => {
const [editor] = (0, import_LexicalComposerContext3.useLexicalComposerContext)();
(0, import_react8.useEffect)(() => {
return editor.registerCommand(
TEXT_SIZE_COMMAND,
(payload) => {
editor.update(() => {
const selection = getSelection();
if (selection) (0, import_selection3.$patchStyleText)(selection, { size: payload.size || "" });
});
return false;
},
import_lexical6.COMMAND_PRIORITY_CRITICAL
);
}, [editor]);
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
"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__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M21 14h-5" }),
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M16 16v-3.5a2.5 2.5 0 0 1 5 0V16" }),
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M4.5 13h6" }),
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "m3 16 4.5-9 4.5 9" })
]
}
);
};
// src/features/textSize/feature.client.tsx
var TextSizeClientFeature = (0, import_client2.createClientFeature)(({ props }) => {
const DropdownComponent = {
type: "dropdown",
ChildComponent: TextSizeIcon,
isEnabled({ selection }) {
return !!getSelection(selection);
},
items: [
{
Component: () => {
const [editor] = (0, import_LexicalComposerContext4.useLexicalComposerContext)();
return Dropdown({
editor,
item: {
command: TEXT_SIZE_COMMAND,
current() {
const selection = getSelection();
return selection ? (0, import_selection4.$getSelectionStyleValueForProperty)(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] = (0, import_LexicalComposerContext4.useLexicalComposerContext)();
(0, import_react9.useEffect)(() => {
return editor.registerCommand(
TEXT_SIZE_COMMAND,
(payload) => {
editor.update(() => {
const selection = getSelection();
if (selection) {
(0, import_selection4.$patchStyleText)(selection, { "font-size": payload.size || "" });
}
});
return true;
},
import_lexical7.COMMAND_PRIORITY_CRITICAL
);
}, [editor]);
return null;
},
position: "normal"
}
],
toolbarFixed: {
groups: [DropdownComponent]
},
toolbarInline: {
groups: [DropdownComponent]
}
};
});
// src/features/textLetterSpacing/feature.client.tsx
var import_client3 = require("@payloadcms/richtext-lexical/client");
var import_lexical10 = require("@payloadcms/richtext-lexical/lexical");
var import_LexicalComposerContext6 = require("@payloadcms/richtext-lexical/lexical/react/LexicalComposerContext");
var import_selection6 = require("@payloadcms/richtext-lexical/lexical/selection");
var import_react13 = require("react");
// src/features/textLetterSpacing/command.ts
var import_lexical8 = require("@payloadcms/richtext-lexical/lexical");
var TEXT_LETTER_SPACING_COMMAND = (0, import_lexical8.createCommand)("TEXT_LETTER_SPACING_COMMAND");
// src/features/textLetterSpacing/components/TextLetterSpacingDropdown.tsx
var import_react11 = require("react");
// src/features/textLetterSpacing/components/TextLetterSpacingPicker.tsx
var import_react10 = require("react");
var import_jsx_runtime7 = require("react/jsx-runtime");
var SpacingPicker = ({
spacing,
onChange,
hideAttribution,
spacings,
method = "replace",
scroll = true,
customSpacing = true
}) => {
const { containerProps, inputProps } = usePreventInlineToolbarClose();
const isEditingRef = (0, import_react10.useRef)(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] = (0, import_react10.useState)(spacing || "");
const [appliedValue, setAppliedValue] = (0, import_react10.useState)(spacing || "");
const [isCustomMode, setIsCustomMode] = (0, import_react10.useState)(false);
const [customNumberValue, setCustomNumberValue] = (0, import_react10.useState)("");
const [customUnit, setCustomUnit] = (0, import_react10.useState)("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"
};
};
(0, import_react10.useEffect)(() => {
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__ */ (0, import_jsx_runtime7.jsxs)(
"div",
{
...containerProps,
style: {
padding: "8px",
display: "flex",
flexDirection: "column",
gap: "8px"
},
children: [
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
"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__ */ (0, import_jsx_runtime7.jsx)(
"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__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { display: "flex", alignItems: "center" }, children: [
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: { marginRight: "8px" }, children: "Custom: " }),
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
"div",
{
style: {
display: "flex",
alignItems: "center",
width: "140px"
},
children: [
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
"div",
{
className: "field-type number",
onClick: (e) => {
e.stopPropagation();
},
style: { flex: 1 },
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
"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__ */ (0, import_jsx_runtime7.jsx)(
"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__ */ (0, import_jsx_runtime7.jsx)("option", { value: unit, children: unit }, `${unit}-${index}`))
}
)
]
}
)
] }),
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { display: "flex", gap: "8px" }, children: [
/* @__PURE__ */ (0, import_jsx_runtime7.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"
}
),
customSpacing && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
"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__ */ (0, import_jsx_runtime7.jsxs)(
"p",
{
style: {
color: "var(--theme-elevation-650)",
fontSize: "10px",
textAlign: "center"
},
children: [
"Made with \u2764\uFE0F by",
" ",
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)("a", { target: "_blank", href: "https://github.com/AdrianMaj", children: "@AdrianMaj" })
]
}
)
]
}
);
};
// src/features/textLetterSpacing/components/TextLetterSpacingDropdown.tsx
var import_jsx_runtime8 = require("react/jsx-runtime");
var Dropdown2 = ({ editor, item }) => {
const [activeSpacing, setActiveSpacing] = (0, import_react11.useState)("");
const onChange = (spacing) => {
editor.dispatchCommand(item.command, { spacing });
setActiveSpacing(spacing || "");
};
(0, import_react11.useEffect)(() => {
editor.read(() => {
const current = item.current ? item.current() : null;
if (current) setActiveSpacing(current);
});
}, [editor, item]);
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
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
var import_lexical9 = require("@payloadcms/richtext-lexical/lexical");
var import_LexicalComposerContext5 = require("@payloadcms/richtext-lexical/lexical/react/LexicalComposerContext");
var import_selection5 = require("@payloadcms/richtext-lexical/lexical/selection");
var import_react12 = require("react");
var import_jsx_runtime9 = require("react/jsx-runtime");
var TextLetterSpacingIcon = () => {
const [editor] = (0, import_LexicalComposerContext5.useLexicalComposerContext)();
(0, import_react12.useEffect)(() => {
return editor.registerCommand(
TEXT_LETTER_SPACING_COMMAND,
(payload) => {
editor.update(() => {
const selection = getSelection();
if (selection) (0, import_selection5.$patchStyleText)(selection, { "letter-spacing": payload.spacing || "" });
});
return false;
},
import_lexical9.COMMAND_PRIORITY_CRITICAL
);
}, [editor]);
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
"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__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M2 18h2" }),
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M20 18h2" }),
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M4 7v11" }),
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M20 7v11" }),
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M12 20v2" }),
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M12 14v2" }),
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M12 8v2" }),
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)("path", { d: "M12 2v2" })
]
}
);
};
// src/features/textLetterSpacing/feature.client.tsx
var TextLetterSpacingClientFeature = (0, import_client3.createClientFeature)(({ props }) => {
const DropdownComponent = {
type: "dropdown",
ChildComponent: TextLetterSpacingIcon,
isEnabled({ selection }) {
return !!getSelection(selection);
},
items: [
{
Component: () => {
const [editor] = (0, import_LexicalComposerContext6.useLexicalComposerContext)();
return Dropdown2({
editor,
item: {
command: TEXT_LETTER_SPACING_COMMAND,
current() {
const selection = getSelection();
return selection ? (0, import_selection6.$getSelectionStyleValueForProperty)(selection, "letter-spacing", "") : null;
},
hideAttribution: props?.hideAttribution,
spacings: props?.spacings,
method: props?.method,
scroll: props?.scroll,
customSpacing: props?.customSpacing,