@tarviks/lexical-rich-editor 1.3.10 → 1.3.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -906,6 +906,7 @@ var init_InlineImageComponent = __esm({
906
906
  "data-position": position,
907
907
  style: {
908
908
  display: "block",
909
+ maxWidth: "100%",
909
910
  height,
910
911
  width
911
912
  },
@@ -1095,14 +1096,17 @@ var init_InlineImageComponent = __esm({
1095
1096
  InlineImageComponent_default = InlineImageComponent;
1096
1097
  }
1097
1098
  });
1098
- var InlineImageComponent2, $convertInlineImageElement, InlineImageNode, $createInlineImageNode, $isInlineImageNode;
1099
+ var InlineImageComponent2, INLINE_IMAGE_MARKER_ATTR, $convertInlineImageElement, InlineImageNode, $createInlineImageNode, $isInlineImageNode;
1099
1100
  var init_InlineImageNode = __esm({
1100
1101
  "src/Nodes/InlineImageNode.tsx"() {
1101
1102
  InlineImageComponent2 = React9__namespace.lazy(() => Promise.resolve().then(() => (init_InlineImageComponent(), InlineImageComponent_exports)));
1103
+ INLINE_IMAGE_MARKER_ATTR = "data-lexical-inline-image";
1102
1104
  $convertInlineImageElement = (domNode) => {
1103
1105
  if (lexical.isHTMLElement(domNode) && domNode.nodeName === "IMG") {
1104
1106
  const { alt: altText, src, width, height } = domNode;
1105
- const node = $createInlineImageNode({ altText, height, src, width });
1107
+ const positionAttr = domNode.getAttribute("data-position");
1108
+ const position = positionAttr === "left" || positionAttr === "right" || positionAttr === "full" ? positionAttr : void 0;
1109
+ const node = $createInlineImageNode({ altText, height, position, src, width });
1106
1110
  return { node };
1107
1111
  }
1108
1112
  return null;
@@ -1146,10 +1150,15 @@ var init_InlineImageNode = __esm({
1146
1150
  }
1147
1151
  static importDOM() {
1148
1152
  return {
1149
- img: (node) => ({
1150
- conversion: $convertInlineImageElement,
1151
- priority: 0
1152
- })
1153
+ img: (node) => {
1154
+ if (lexical.isHTMLElement(node) && node.hasAttribute(INLINE_IMAGE_MARKER_ATTR)) {
1155
+ return {
1156
+ conversion: $convertInlineImageElement,
1157
+ priority: 3
1158
+ };
1159
+ }
1160
+ return null;
1161
+ }
1153
1162
  };
1154
1163
  }
1155
1164
  constructor(src, altText, position, width, height, showCaption, caption, key) {
@@ -1168,6 +1177,11 @@ var init_InlineImageNode = __esm({
1168
1177
  element.setAttribute("alt", this.__altText);
1169
1178
  element.setAttribute("width", this.__width.toString());
1170
1179
  element.setAttribute("height", this.__height.toString());
1180
+ element.setAttribute(INLINE_IMAGE_MARKER_ATTR, "true");
1181
+ if (this.__position) {
1182
+ element.setAttribute("data-position", this.__position);
1183
+ _InlineImageNode.applyPositionStyle(element, this.__position);
1184
+ }
1171
1185
  return { element };
1172
1186
  }
1173
1187
  exportJSON() {
@@ -1225,12 +1239,34 @@ var init_InlineImageNode = __esm({
1225
1239
  }
1226
1240
  }
1227
1241
  // View
1242
+ // The position-left/right/full behavior is also defined in the package's
1243
+ // CSS file as `.inline-editor-image.position-*` rules, but that file is a
1244
+ // separate import (`@tarviks/lexical-rich-editor/dist/index.css`) a
1245
+ // consuming app can forget to include. Applying it as inline styles here
1246
+ // too means positioning still works even when that stylesheet isn't
1247
+ // loaded, instead of silently no-op'ing back to normal block flow.
1248
+ static applyPositionStyle(span, position) {
1249
+ span.style.removeProperty("float");
1250
+ span.style.removeProperty("display");
1251
+ span.style.removeProperty("margin");
1252
+ if (position === "left") {
1253
+ span.style.float = "left";
1254
+ span.style.margin = "4px 8px 4px 0";
1255
+ } else if (position === "right") {
1256
+ span.style.float = "right";
1257
+ span.style.margin = "4px 0 4px 8px";
1258
+ } else if (position === "full") {
1259
+ span.style.display = "block";
1260
+ span.style.margin = "8px 0";
1261
+ }
1262
+ }
1228
1263
  createDOM(config) {
1229
1264
  const span = document.createElement("span");
1230
1265
  const className = `${config.theme.inlineImage} position-${this.__position}`;
1231
1266
  if (className !== void 0) {
1232
1267
  span.className = className;
1233
1268
  }
1269
+ _InlineImageNode.applyPositionStyle(span, this.__position);
1234
1270
  return span;
1235
1271
  }
1236
1272
  updateDOM(prevNode, dom, config) {
@@ -1240,6 +1276,7 @@ var init_InlineImageNode = __esm({
1240
1276
  if (className !== void 0) {
1241
1277
  dom.className = className;
1242
1278
  }
1279
+ _InlineImageNode.applyPositionStyle(dom, position);
1243
1280
  }
1244
1281
  return false;
1245
1282
  }
@@ -2193,6 +2230,48 @@ function $createYouTubeNode(videoID, width = DEFAULT_WIDTH, height = DEFAULT_HEI
2193
2230
  function $isYouTubeNode(node) {
2194
2231
  return node instanceof YouTubeNode;
2195
2232
  }
2233
+ var PRESERVED_STYLE_PROPS = [
2234
+ ["fontFamily", "font-family"],
2235
+ ["fontSize", "font-size"],
2236
+ ["color", "color"],
2237
+ ["backgroundColor", "background-color"]
2238
+ ];
2239
+ var $convertSpanElementWithStyle = (domNode) => {
2240
+ const style = domNode.style;
2241
+ const cssParts = [];
2242
+ for (const [domProp, cssProp] of PRESERVED_STYLE_PROPS) {
2243
+ const value = style[domProp];
2244
+ if (value) cssParts.push(`${cssProp}: ${value}`);
2245
+ }
2246
+ const cssText = cssParts.length > 0 ? `${cssParts.join("; ")};` : "";
2247
+ const fontWeight = style.fontWeight;
2248
+ const textDecoration = style.textDecoration.split(" ");
2249
+ const hasBoldFontWeight = fontWeight === "700" || fontWeight === "bold";
2250
+ const hasLinethroughTextDecoration = textDecoration.includes("line-through");
2251
+ const hasItalicFontStyle = style.fontStyle === "italic";
2252
+ const hasUnderlineTextDecoration = textDecoration.includes("underline");
2253
+ const verticalAlign = style.verticalAlign;
2254
+ return {
2255
+ node: null,
2256
+ forChild: (lexicalNode) => {
2257
+ if (!lexical.$isTextNode(lexicalNode)) return lexicalNode;
2258
+ if (cssText) lexicalNode.setStyle(cssText);
2259
+ if (hasBoldFontWeight && !lexicalNode.hasFormat("bold")) lexicalNode.toggleFormat("bold");
2260
+ if (hasLinethroughTextDecoration && !lexicalNode.hasFormat("strikethrough")) lexicalNode.toggleFormat("strikethrough");
2261
+ if (hasItalicFontStyle && !lexicalNode.hasFormat("italic")) lexicalNode.toggleFormat("italic");
2262
+ if (hasUnderlineTextDecoration && !lexicalNode.hasFormat("underline")) lexicalNode.toggleFormat("underline");
2263
+ if (verticalAlign === "sub" && !lexicalNode.hasFormat("subscript")) lexicalNode.toggleFormat("subscript");
2264
+ if (verticalAlign === "super" && !lexicalNode.hasFormat("superscript")) lexicalNode.toggleFormat("superscript");
2265
+ return lexicalNode;
2266
+ }
2267
+ };
2268
+ };
2269
+ var preserveTextStyleImportMap = {
2270
+ span: () => ({
2271
+ conversion: $convertSpanElementWithStyle,
2272
+ priority: 0
2273
+ })
2274
+ };
2196
2275
  var normalize = (s) => (s ?? "").replace(/\s+/g, " ").trim();
2197
2276
  function readContextWindow(maxChars = 300, cursorIndex) {
2198
2277
  const full = normalize(lexical.$getRoot().getTextContent());
@@ -2966,6 +3045,8 @@ var ALLOWED_ATTRS = /* @__PURE__ */ new Set([
2966
3045
  "data-lex-block",
2967
3046
  "data-kind",
2968
3047
  "data-lexical-decorator",
3048
+ "data-lexical-inline-image",
3049
+ "data-position",
2969
3050
  // Misc
2970
3051
  "title",
2971
3052
  "allowfullscreen"
@@ -5120,11 +5201,37 @@ function SpellCheckPlugin({
5120
5201
  }
5121
5202
  ) : null;
5122
5203
  }
5204
+ function getScrollClipAncestor(el) {
5205
+ let node = el.parentElement;
5206
+ while (node) {
5207
+ const style = getComputedStyle(node);
5208
+ if ((style.overflowY === "auto" || style.overflowY === "scroll") && node.scrollHeight > node.clientHeight) {
5209
+ return node;
5210
+ }
5211
+ node = node.parentElement;
5212
+ }
5213
+ return null;
5214
+ }
5215
+ var BUTTON_VERTICAL_FOOTPRINT = 34;
5216
+ function isRectVisible(rect, cellDom) {
5217
+ if (rect.width === 0 && rect.height === 0) return false;
5218
+ const clipAncestor = getScrollClipAncestor(cellDom);
5219
+ const containerRect = clipAncestor?.getBoundingClientRect();
5220
+ const visibleTop = containerRect ? Math.max(0, containerRect.top) : 0;
5221
+ const visibleBottom = containerRect ? Math.min(window.innerHeight, containerRect.bottom) : window.innerHeight;
5222
+ const visibleLeft = containerRect ? Math.max(0, containerRect.left) : 0;
5223
+ const visibleRight = containerRect ? Math.min(window.innerWidth, containerRect.right) : window.innerWidth;
5224
+ if (rect.top < visibleTop) return false;
5225
+ if (rect.top + BUTTON_VERTICAL_FOOTPRINT > visibleBottom) return false;
5226
+ if (rect.left >= visibleRight || rect.right <= visibleLeft) return false;
5227
+ return true;
5228
+ }
5123
5229
  function TableActionMenuPlugin({ disabled = false }) {
5124
5230
  const [editor] = LexicalComposerContext.useLexicalComposerContext();
5125
5231
  const portalContainer = useFloatingPortalContainer(editor);
5126
5232
  const [isInTable, setIsInTable] = React9__namespace.useState(false);
5127
5233
  const [anchorRect, setAnchorRect] = React9__namespace.useState(null);
5234
+ const [isAnchorVisible, setIsAnchorVisible] = React9__namespace.useState(false);
5128
5235
  const [contentRight, setContentRight] = React9__namespace.useState(null);
5129
5236
  const [open, setOpen] = React9__namespace.useState(false);
5130
5237
  const openRef = React9__namespace.useRef(false);
@@ -5150,8 +5257,10 @@ function TableActionMenuPlugin({ disabled = false }) {
5150
5257
  if (tableNode) {
5151
5258
  const dom = editor.getElementByKey(tableNode.getKey());
5152
5259
  if (dom) {
5260
+ const rect = dom.getBoundingClientRect();
5153
5261
  setIsInTable(true);
5154
- setAnchorRect(dom.getBoundingClientRect());
5262
+ setAnchorRect(rect);
5263
+ setIsAnchorVisible(isRectVisible(rect, dom));
5155
5264
  setContentRight(null);
5156
5265
  return;
5157
5266
  }
@@ -5160,6 +5269,7 @@ function TableActionMenuPlugin({ disabled = false }) {
5160
5269
  if (!lexical.$isRangeSelection(selection)) {
5161
5270
  setIsInTable(false);
5162
5271
  setAnchorRect(null);
5272
+ setIsAnchorVisible(false);
5163
5273
  setContentRight(null);
5164
5274
  return;
5165
5275
  }
@@ -5168,6 +5278,7 @@ function TableActionMenuPlugin({ disabled = false }) {
5168
5278
  if (!cellNode || !table.$isTableCellNode(cellNode)) {
5169
5279
  setIsInTable(false);
5170
5280
  setAnchorRect(null);
5281
+ setIsAnchorVisible(false);
5171
5282
  setContentRight(null);
5172
5283
  return;
5173
5284
  }
@@ -5175,11 +5286,14 @@ function TableActionMenuPlugin({ disabled = false }) {
5175
5286
  if (!cellDom) {
5176
5287
  setIsInTable(false);
5177
5288
  setAnchorRect(null);
5289
+ setIsAnchorVisible(false);
5178
5290
  setContentRight(null);
5179
5291
  return;
5180
5292
  }
5293
+ const cellRect = cellDom.getBoundingClientRect();
5181
5294
  setIsInTable(true);
5182
- setAnchorRect(cellDom.getBoundingClientRect());
5295
+ setAnchorRect(cellRect);
5296
+ setIsAnchorVisible(isRectVisible(cellRect, cellDom));
5183
5297
  setContentRight(measureContentRight(cellDom));
5184
5298
  if (lexical.$isRangeSelection(selection)) {
5185
5299
  savedAnchorRef.current = {
@@ -5258,7 +5372,7 @@ function TableActionMenuPlugin({ disabled = false }) {
5258
5372
  lexical.COMMAND_PRIORITY_HIGH
5259
5373
  );
5260
5374
  }, [editor, disabled]);
5261
- const canShow = isInTable && !!anchorRect && !disabled;
5375
+ const canShow = isInTable && !!anchorRect && isAnchorVisible && !disabled;
5262
5376
  const handleStyle = React9__namespace.useMemo(() => {
5263
5377
  if (!anchorRect) return void 0;
5264
5378
  const top = Math.max(8, anchorRect.top + 6);
@@ -8217,7 +8331,10 @@ var ContentEditorComponent = React9.forwardRef(
8217
8331
  AutocompleteNode,
8218
8332
  SpellErrorNode,
8219
8333
  HtmlBlockNode
8220
- ]
8334
+ ],
8335
+ html: {
8336
+ import: preserveTextStyleImportMap
8337
+ }
8221
8338
  };
8222
8339
  props.onBeforeInitialize?.(config);
8223
8340
  return config;