Monaco editor prevent line decorations from expanding - javascript

My objective is to instantiate a Monaco editor line decoration that does not expand to the lower rows when I hit enter at the end of the created decoration.
For example, when I create a Monaco editor(in React) and instantiate a line decoration with the following code:
`js
import { createStyles } from "#mantine/styles";
import Editor from "#monaco-editor/react";
import monaco from "monaco-editor/esm/vs/editor/editor.api";
import { useRef, useState } from "react";
const DecoratedEditor = () => {
const { classes } = useStyles();
const [code, setCode] = useState(`#include <iostream>
using namespace std;
//press enter on my end to see decoration expand
int main() {
cout << "Hello World!";
return 0;
}`);
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null);
return (
<Editor
value={code}
language="cpp"
theme="vs-dark"
onChange={(newValue) => {
if (!newValue) return;
setCode(newValue);
}}
beforeMount={(monaco) => {}}
onMount={(editor, monaco) => {
editorRef.current = editor;
editor.getModel()?.deltaDecorations(
[],
[
{
range: new monaco.Range(4, 1, 4, 47),
options: {
inlineClassName: classes.lineDecoration,
},
},
]
);
}}
height={400}
/>
);
};
export default DecoratedEditor;
const useStyles = createStyles((theme) => {
return {
lineDecoration: {
width: "10px !important ",
backgroundColor: "rgb(255, 0, 0, 0.4)",
},
};
});
I get the following output: Normal editor with normal decoration
But, if I press "Enter" at the end of the decoration at line 4 and write on the following line I get this: New decoration
Is there a way to prevent the decoration from expanding itself? Thank you.
I already searched for options in Monaco editor documentation to prevent this from happening, but I didn't find out anythigh that could satisfy my needs.

I have been there.
It would help you get the job done as well.
That is
stickiness: 1
In you case, you can add one more option to the list.
options: {
inlineClassName: classes.lineDecoration,
stickiness: 1
}
stickness 1 is AlwaysGrowsWhenTypingAtEdges
/**
* Describes the behavior of decorations when typing/editing near their edges.
* Note: Please do not edit the values, as they very carefully match `DecorationRangeBehavior`
*/
export enum TrackedRangeStickiness {
AlwaysGrowsWhenTypingAtEdges = 0,
NeverGrowsWhenTypingAtEdges = 1,
GrowsOnlyWhenTypingBefore = 2,
GrowsOnlyWhenTypingAfter = 3
}
In case, a code snippet goes like this (you can use it as you would like) :
decorations = editor.deltaDecorations([],
[
{
range: new monaco.Range(1, 1, 4, 4),
options: {
stickiness: 1, // NeverGrowsWhenTypingAtEdges
}
}
])
}

Related

CKEditor 5 plugin development can't edit content

I am trying to develop a simple plugin for CKEditor 5. My ultimate goal is to have the plugin emit the following HTML:
<div class="icms_note">
<div class="span-6 module-note">
<p>User can edit here</p>
</div>
<div class="clear"></div>
</div>
I was having issues with the user editable area being in the wrong spot so I reduced the output to just this:
<div class="icms_note">
<p>User can edit here</p>
</div>
and the data model looks like this:
<icms_note>
<paragraph>User can edit here</paragraph>
</icms_note>
So I can get the data model created correctly and the HTML ends up in the editor the way I expect but I can't edit the text in the paragraph. I click on the text and the cursor just jumps out. I've tried looking at other examples and the tutorials but I can't seem to get it to work right. My plugin code is below. Any help would be appreciated.
note.js
import Plugin from "#ckeditor/ckeditor5-core/src/plugin";
import NoteEditing from "./noteediting";
import NoteUI from "./noteui";
export default class Note extends Plugin {
static get requires() {
return [ NoteEditing, NoteUI ];
}
static get pluginName() {
return "IcmsNote";
}
}
noteui.js
import Plugin from "#ckeditor/ckeditor5-core/src/plugin";
import { ButtonView } from "#ckeditor/ckeditor5-ui";
export default class NoteUI extends Plugin {
init() {
const editor = this.editor;
editor.ui.componentFactory.add( "icms_note", (locale) => {
const button = new ButtonView(locale);
button.set({
label: "Note",
withText: true,
tooltip: true
});
button.on( "execute", () => {
editor.execute("insertNote");
});
return button;
} );
}
}
noteediting.js
import Position from "#ckeditor/ckeditor5-engine/src/view/position";
import NoteCommand from "./notecommand";
export default class NoteEditing extends Plugin {
init() {
this._defineSchema();
this._defineConverters();
this.editor.commands.add("insertNote", new NoteCommand(this.editor));
}
_defineSchema() {
const schema = this.editor.model.schema;
schema.register( "icms_note", {
inheritAllFrom: "$text",
allowIn: [ "$root", "$container" ],
isInline: true
});
}
_defineConverters() {
const conversion = this.editor.conversion;
conversion.for( "downcast" ).elementToElement({
model: "icms_note",
view: ( modelElementValue, conversionApi ) => {
const { writer } = conversionApi;
const outerDivElement = writer.createEditableElement("div", {class: "icms_note"});
return outerDivElement;
}
});//<div><div class=\"span-6 module-note\"><p>Enter note</p></div><div class=\"clear\"></div></div>
conversion.for( "upcast" ).elementToElement({
view: {
name: "div",
attributes: {
classes: [ "icms_note" ]
}
},
model: {
key: "icms_note",
value: viewElement => {
const val = viewElement.getChildren()[0].getChildren()[0].data;
return val;
}
}
});
}
}
notecommand.js
import Command from "#ckeditor/ckeditor5-core/src/command";
export default class NoteCommand extends Command {
constructor(editor) {
super(editor);
}
execute() {
console.log("NoteCommand#execute");
const model = this.editor.model;
const selection = model.document.selection;
model.change( modelWriter => {
let position = selection.getFirstPosition();
const icmsNote = modelWriter.createElement("icms_note");
const paragraph = modelWriter.createElement("paragraph");
modelWriter.insert(paragraph, icmsNote);
modelWriter.insertText("User can edit here", paragraph);
let positionElementName = position.parent.name;
while (positionElementName != "$root" && positionElementName != "$container") {
position = model.createPositionAfter(position.parent);
positionElementName = position.parent.name;
}
model.insertContent(icmsNote, position, null, {
setSelection: "after"
});
});
}
}

ToolTip does not disappear on scroll

I have a button on the site and a ToolTip to it, which describes the action of the button.
But there is one bug that I can not solve (and I'm already starting to doubt if there is a solution to this problem).
Description of the problem: when the user hovers over the icon, a tooltip appears - everything works fine here. But if at this moment the table is scrolling, then the tooltip flies out of bounds. It's hard to describe, take a look
Pay attention to how the tooltip (if the cursor is hovered over) flies up or down when scrolling.
Tell me how to solve this problem?
<div>
<Tooltip
title="Delete"
arrow
componentsProps={{
tooltip: {
sx: {
bgcolor: '#a3a3a3',
'& .MuiTooltip-arrow': {
color: '#a3a3a3',
},
},
},
}}
PopperProps={{
modifiers: [
{
name: "offset",
options: {
offset: [0, -8],
},
},
],
}}>
<DeleteForeverIcon/>
</Tooltip>
</div>
Instruction: hover over any cell from the first column, wait for the tooltip to appear. Then scroll the wheel up or down and see how the tooltip goes outside the table
P.s. Please note that this question has already been answered. And in principle this solution is working. But I had a lot of problems when adding this solution to my real code. Probably a simple solution for me here would be to simply cancel the scrolling when you hover over the button. Tell me how this can be done (but keep in mind that position: fixed is not suitable in this case)
My approach is different, where each tooltip maintains its own state. It is using IntersectionObserver to determine if the ToolTip component is viewable. When the component is no longer viewable, it will hide the Popper (the tooltip popup) by setting the CSS to display: 'none' via the sx prop on PopperProps.
Codesandbox Example: Here
Here is the modified file FileDownloadButton.jsx:
import React from "react";
import FileDownloadIcon from "#mui/icons-material/FileDownload";
import { ButtonGroup, Tooltip } from "#mui/material";
export default function FileDownloadButton() {
const tipRef = React.useRef(null);
const [inView, setInView] = React.useState(false);
const cb = (entries) => {
const [entry] = entries;
entry.isIntersecting ? setInView(true) : setInView(false);
};
React.useEffect(() => {
const options = {
root: null,
rootMargin: "0px"
};
const ref = tipRef.current;
const observer = new IntersectionObserver(cb, options);
if (ref) observer.observe(ref);
return () => {
if (ref) observer.unobserve(ref);
};
}, [tipRef]);
return (
<ButtonGroup>
<div>
<Tooltip
ref={tipRef}
title="Download record "
arrow
componentsProps={{
tooltip: {
sx: {
bgcolor: "#a3a3a3",
"& .MuiTooltip-arrow": {
color: "#a3a3a3"
}
}
}
}}
PopperProps={{
sx: { display: inView ? "block" : "none" },
modifiers: [
{
name: "offset",
options: {
offset: [0, -8]
}
}
]
}}
>
<FileDownloadIcon />
</Tooltip>
</div>
</ButtonGroup>
);
}
Changes for reference
Change 1
export default function FileDownloadButton() {
const tipRef = React.useRef(null);
const [inView, setInView] = React.useState(false);
const cb = (entries) => {
const [entry] = entries;
entry.isIntersecting ? setInView(true) : setInView(false);
};
React.useEffect(() => {
const options = {
root: null,
rootMargin: "0px"
};
const ref = tipRef.current;
const observer = new IntersectionObserver(cb, options);
if (ref) observer.observe(ref);
return () => {
if (ref) observer.unobserve(ref);
};
}, [tipRef]);
Change 2
PopperProps={{
sx: { display: inView ? "block" : "none" },
Update 1
Original poster wants toggle
Codesandbox example
import React, { useState } from "react";
import FileDownloadIcon from "#mui/icons-material/FileDownload";
import { ButtonGroup, IconButton, Tooltip } from "#mui/material";
import VisibilityOffIcon from "#mui/icons-material/VisibilityOff";
import VisibilityIcon from "#mui/icons-material/Visibility";
export default function FileDownloadButton() {
const [click, setClick] = useState(true);
const tipRef = React.useRef(null);
const [inView, setInView] = React.useState(false);
const cb = (entries) => {
const [entry] = entries;
entry.isIntersecting ? setInView(true) : setInView(false);
};
React.useEffect(() => {
const options = {
root: null,
rootMargin: "0px"
};
const ref = tipRef.current;
const observer = new IntersectionObserver(cb, options);
if (ref) observer.observe(ref);
return () => {
if (ref) observer.unobserve(ref);
};
}, [tipRef]);
return (
<ButtonGroup>
<div>
<Tooltip
ref={tipRef}
title={click ? "Show item" : "Hide Item"}
arrow
componentsProps={{
tooltip: {
sx: {
bgcolor: "#a3a3a3",
"& .MuiTooltip-arrow": {
color: "#a3a3a3"
}
}
}
}}
PopperProps={{
sx: { display: inView ? "block" : "none" },
modifiers: [
{
name: "offset",
options: {
offset: [0, -8]
}
}
]
}}
>
<IconButton onClick={() => setClick(!click)}>
{click ? <VisibilityOffIcon /> : <VisibilityIcon />}
</IconButton>
</Tooltip>
</div>
</ButtonGroup>
);
}
I think this is browser specific issue. When I checked the given url( https://codesandbox.io/s/silly-grass-1lb3qw) in firefox browser it was working fine(but not in the chrome). Later figured that out hover while scrolling on element will work differently in the chrome compare to other browsers since latest versions.
I made following changes to make it work in chrome. Basically whenever we hover any item then the material tooltip is being added to the document. So what I did was I have attached an scroll event and if there is any material tooltip element is present I just simply removed it.
DeviceTable.jsx
export default function DevicesTable() {
const tableRef = useRef();
function removeElementsByClass(className){
const elements = document.getElementsByClassName(className);
while(elements.length > 0){
elements[0].remove();
}
}
useEffect(() => {
if (tableRef.current) {
tableRef.current.addEventListener("scroll", (e) => {
// CLASS NAME OF THE TOOLTIP ATTACHED TO THE DOM. THERE ARE MULTIPLE CLASSES BUT I FOUND FOLLOWING CLASSNAME TO BE UNIQUE. PLEASE CROSS CHECK FROM YOUR END AS WELL.
//YOU CAN CHECK THIS BY PASSING open={true} attribute on <Tooltip> AND INSPECT DOM
removeElementsByClass("css-yk351k-MuiTooltip-tooltip")
});
}
return () => {
if(tableRef.current) {
tableRef.current.removeEventListener("scroll", ()=>{});
}
}
}, []);
return (
<TableContainer className="TableContainerGridStyle">
<Table className="TableStyle">
<DevicesTableHeader />
// CHANGED LINE
<TableBody ref={tableRef} className="TableBodyStyle">
<DevicesTableCell />
<DevicesTableCell />
<DevicesTableCell />
<DevicesTableCell />
<DevicesTableCell />
<DevicesTableCell />
<DevicesTableCell />
<DevicesTableCell />
<DevicesTableCell />
<DevicesTableCell />
<DevicesTableCell />
</TableBody>
</Table>
</TableContainer>
);
}
Apart from the above I think you can use another alternatives like followCursor, setting the position relative attribute to the table cell(TableCellStyle) or body. But these don't solve the problem fully.
As you are passing Table component as props children to the StateLabel component so in order to display/render we need to update StateLabel component to use props.children
export default function StateLabel({children}) {
return <div>{children}</div>;
}
Div hover not working when scrolling in chrome

How to use useSprings?

I'm trying to create an animated text char by char to bounce from right to left, but I need to add a different delay to each char.
So far the animation looks nice the problem is that the delay is not updating for each char.
This is my code:
import React from "react";
import { useSprings, animated } from "react-spring"
import s from '../../styles/pages/home.module.scss'
// HERE CREATE AN ARRAY CHAR BY CHAR INCLUDING " ", FROM A STRING
let textString = "Random text for this example."
let textArray = Array.from(textString)
const HeroText = () => {
const springs = useSprings(
textArray.length,
textArray.map((item, i)=> ({
id : i,
from : {opacity: 0, translateX : '1000px'},
to: {opacity: 1, translateX : '0px'},
delay: (0.5 + i / 10),
config: { mass: 4, tension: 200, friction: 30}
}))
)
let elements = springs.map((spring, i) => {
console.log(spring)
return(
<animated.span key={i} style={{...spring}}>
{textArray[i] === ' ' ? <span> </span> : textArray[i]}
</animated.span>
)
})
return(
<div className={s.heroText}>
<h1 className={"my-heading divided-heading"}>
{elements}
</h1>
</div>
)
}
export default HeroText
On the console.log(spring), I can actually see that all the objects have different "delay" values, but on render they all animate at the same time, so it does not look like the text is animated char by char.
I have read the react-spring documentation but I did not find it to be very helpful, so if someone can help me understand what I'm missing I would be glad.
Thanks!
so after allot of research I found out that the best way to do what I was trying to do was to use "useTransition".
This was the final code, working properly.
import React from "react";
import { animated, useTransition } from "react-spring"
import s from '../../styles/pages/home.module.scss'
// HERE CREATE AN ARRAY CHAR BY CHAR INCLUDING " ", FROM A STRING
let textString = "Hi, I'm Stephane Ribeiro. Want to como on a full stack journey trough my mind?!"
let textArray = Array.from(textString)
let objArray = textArray.map((item, i) => {
return ({
char : item,
key: i
})
})
const HeroText = () => {
const tranConfig = {
from : {opacity: 0, translateX : '1000px'},
enter: {opacity: 1, translateX : '0px'},
config: { mass: 4, tension: 200, friction: 30},
trail: 50
}
const transition = useTransition(objArray, tranConfig)
let elements = transition((values, item) => {
return(
<animated.span key={item.key} style={values}>
{item.char === ' ' ? <span> </span> : item.char}
</animated.span>
)
})
return(
<div className={s.heroText}>
<h1 className={"my-heading divided-heading"}>
{firstElements}
</h1>
</div>
)
}
export default HeroText
How did you apply different delay time for each char. I have seen your last code there is no different delay time

Take the value of the attribute, in the map, but with names of different attributes in javascript

I'm new here on the site, I have a question regarding a function in react that I am unable to resolve. The solution is very simple but it does not work for me.
I build a social network in React, in the social network I have users, each user has a different name, it's like a key.
I have a match object, which contains matches between one user, represented by handle, and the other users represented by match_(number).
Because each of the users' names is unique, I want to use them in the key.
The problem is that for each user the name of the atribute is different, and I can not think of a way, to be able to access in a loop for all users.
match =
[
{handle: "backend4" , ...},
{match_1: "backend1" , ...},
{match_2: "backend2" , ...},
{match_3: "backend3" , ...},
{match_4: "devops1" , ...},
{match_5: "devops2" , ...},
{match_6: "mobile2" , ...},
{match_7: "test500" , ...},
{match_8: "testing100" , ...},
{match_9: "testing200" , ...},
{match_10: "testtttttt" , ...},
];
recentMatchesMarkup = match.map((singleMatch) =>
<Match key={singleMatch.id} matchInfo={singleMatch} />)
);
I'm sure the problem is very small, but I can 't think of how to deal with it
Here is how you could access the value of match_x, x being the index of the loop :
match.map((el, i) => console.log(el[match_${i}]))
(There is the special quote wrapping match_${i} above. I just can't display it since they are used to display code.. see this link if you don't know about it).
But note that is only works with a key match_x so if you are 100% sure it's ok for you, go for it.
The first key handle won't be displayed (return undefined) so you might need to add a condition to display your <Match> component.
To be sure each match correspond to the index, here is an example on Stackblitz about how you could do it, and here is the code :
import React, { Component } from "react";
import { render } from "react-dom";
import "./style.css";
const App = () => {
const [sortedMatch, setSortedMatch] = React.useState([]);
const match = [
{ handle: "backend4" },
{ match_2: "backend2" },
{ match_3: "backend3" },
{ match_1: "backend1", test: "text" },
{ match_4: "devops1" },
{ match_5: "devops2" },
{ match_6: "mobile2" },
{ match_7: "test500" },
{ match_8: "testing100" },
{ match_9: "testing200" },
{ match_10: "testtttttt" }
];
React.useEffect(() => {
const matchByIndex = [];
match.forEach(el => {
const key = Object.keys(el).find(k => k.includes("match"));
if (key) {
let nb = key.split("_");
nb = nb[nb.length - 1];
matchByIndex[nb] = el;
}
});
setSortedMatch(matchByIndex);
}, []);
if (!sortedMatch.length) return <>Loading...</>;
return (
<div>
{sortedMatch.map((el, i) => (
<>
{el[`match_${i}`]}
<br />
</>
))}
</div>
);
};
render(<App />, document.getElementById("root"));

LightningChart JS crashes with t.toFixed is not a function

I am using a LightningChart JS by Arction to plot a bar graph and it keeps crashing after adding the rectangle figures with an error message: t.toFixed is not a function
The series being used is a rectangle series and I'd like to use only one rectangle series because I need them all under one group.
Below is my code
// Core react imports
import React, { useEffect, useCallback } from 'react'
// React bootstrap imports
import { Col } from "react-bootstrap"
// Chart imports
import {
lightningChart,
SolidFill,
SolidLine,
emptyTick,
emptyLine,
FontSettings,
ColorHEX,
} from "#arction/lcjs"
import axios from "axios"
export default function Histogram() {
const createChart = useCallback(
() => {
const chart = lightningChart().ChartXY({ containerId: "myplot" });
chart
.setTitle("RR Histogram")
.setTitleFillStyle((solidFill) => solidFill.setColor(ColorHEX("#000")))
.setTitleMarginTop(0)
.setTitleMarginBottom(0)
.setChartBackgroundFillStyle((solidFill) =>
solidFill.setColor(ColorHEX("#FFF"))
)
.setBackgroundFillStyle((solidFill) =>
solidFill.setColor(ColorHEX("#FFF"))
)
.setZoomingRectangleStrokeStyle(
new SolidLine({
fillStyle: new SolidFill({ color: ColorHEX("#000") }),
})
)
.setTitleFont(new FontSettings({ size: 20 }));
// Configure X-axis of chart to be progressive and have nice interval.
chart
.getDefaultAxisX();
// .setTickStyle(emptyTick)
// .setNibStyle(emptyLine)
// .setTitleFont(new FontSettings({ size: 12 }))
// .setStrokeStyle(emptyLine);
chart
.getDefaultAxisY();
// .setTickStyle(emptyTick)
// .setNibStyle(emptyLine)
// .setStrokeStyle(emptyLine);
let rectSeries = chart
.addRectangleSeries()
.setDefaultStyle(figure => figure.setFillStyle(new SolidFill({
color: ColorHEX("#000")
})));
let rr_hist = {};
axios
.get("Api url here")
.then((res) => {
console.log(res)
rr_hist = res.data;
})
.catch((err) => console.log(err));
setTimeout(() => {
for (let point in rr_hist) {
let insert_Point = {
height: rr_hist[point],
y: 0,
x: point,
width: 1
}
let bar = rectSeries.add(insert_Point);
bar.setDimensions(insert_Point);
bar.setFillStyle(new SolidFill({ color: ColorHEX("#000") }));
bar.setStrokeStyle(new SolidLine({
fillStyle: new SolidFill({ color: ColorHEX("#000") }),
}))
}
console.log(rr_hist)
}, 2000)
},
[],
)
useEffect(() => {
createChart()
}, [createChart])
return (
<Col xs={12} style={{ height: "100%", width: "100%" }}>
<div id="myplot" style={{ height: "100%", width: "100%" }}></div>
</Col>
)
}
Also could you please let me know how to improve the styling?
Most likely reason for the crash is that your height or x field for the new rectangle figure is not a number. LightningChart JS doesn't do type conversions for input values.
So when adding new rectangles to rectangle series make sure to do the type conversion from string to number yourself.
let insert_Point = {
height: Number(rr_hist[point]),
y: 0,
x: Number(point),
width: 1
}
let bar = rectSeries.add(insert_Point);
Instead of using Number for the conversion you could use parseFloat or parseInt depending on the type of data you use. See https://stackoverflow.com/a/13676265/6198227 that answer for more detailed differences between Number and parseFloat.
For styling, it looks like you would benefit from using a light colored theme. When creating the chart with ChartXY you can specify theme option.
const chart = lightningChart().ChartXY({
theme: Themes.light
})
You can see the available themes in our docs Themes

Categories

Resources