UNPKG

payload-lexical-typography

Version:
1,428 lines (1,399 loc) 76 kB
"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