@grafana/ui
Version:
Grafana Components Library
244 lines (236 loc) • 9.15 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var jsxRuntime = require('react/jsx-runtime');
var css = require('@emotion/css');
var react = require('@floating-ui/react');
var Prism = require('prismjs');
var React = require('react');
var reactUse = require('react-use');
var Plain = require('slate-plain-serializer');
var slateReact = require('slate-react');
var data = require('@grafana/data');
var index = require('../../slate-plugins/slate-prism/index.cjs');
var ThemeContext = require('../../themes/ThemeContext.cjs');
var mixins = require('../../themes/mixins.cjs');
var floating = require('../../utils/floating.cjs');
var slate = require('../../utils/slate.cjs');
var Input = require('../Input/Input.cjs');
var Portal = require('../Portal/Portal.cjs');
var ScrollContainer = require('../ScrollContainer/ScrollContainer.cjs');
var DataLinkSuggestions = require('./DataLinkSuggestions.cjs');
var SelectionReference = require('./SelectionReference.cjs');
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
function _interopNamespaceCompat(e) {
if (e && typeof e === 'object' && 'default' in e) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var Prism__default = /*#__PURE__*/_interopDefaultCompat(Prism);
var React__namespace = /*#__PURE__*/_interopNamespaceCompat(React);
var Plain__default = /*#__PURE__*/_interopDefaultCompat(Plain);
;
const modulo = (a, n) => a - n * Math.floor(a / n);
const datalinksSyntax = {
builtInVariable: {
pattern: /(\${\S+?})/
}
};
const plugins = [
index.SlatePrism(
{
onlyIn: (node) => "type" in node && node.type === "code_block",
getSyntax: () => "links"
},
{ ...Prism__default.default.languages, links: datalinksSyntax }
)
];
const getStyles = (theme) => ({
input: Input.getInputStyles({ theme, invalid: false }).input,
editor: css.css({
".token.builtInVariable": {
color: theme.colors.success.text
},
".token.variable": {
color: theme.colors.primary.text
}
}),
suggestionsWrapper: css.css({
boxShadow: theme.shadows.z2
}),
// Wrapper with child selector needed.
// When classnames are applied to the same element as the wrapper, it causes the suggestions to stop working
wrapperOverrides: css.css({
width: "100%",
"> .slate-query-field__wrapper": {
padding: 0,
backgroundColor: "transparent",
border: "none",
"&:focus-within": mixins.getFocusStyles(theme)
}
})
});
const DataLinkInput = React.memo(
({
value,
onChange,
suggestions,
placeholder = "http://your-grafana.com/d/000000010/annotations",
["aria-labelledby"]: ariaLabelledby
}) => {
const editorRef = React.useRef(null);
const styles = ThemeContext.useStyles2(getStyles);
const [showingSuggestions, setShowingSuggestions] = React.useState(false);
const [suggestionsIndex, setSuggestionsIndex] = React.useState(0);
const [linkUrl, setLinkUrl] = React.useState(slate.makeValue(value));
const prevLinkUrl = reactUse.usePrevious(linkUrl);
const [scrollTop, setScrollTop] = React.useState(0);
const scrollRef = React.useRef(null);
React.useEffect(() => {
var _a;
(_a = scrollRef.current) == null ? void 0 : _a.scrollTo(0, scrollTop);
}, [scrollTop]);
const middleware = [
react.offset(({ rects }) => ({
alignmentAxis: rects.reference.width
})),
...floating.getPositioningMiddleware()
];
const { refs, floatingStyles } = react.useFloating({
open: showingSuggestions,
placement: "bottom-start",
onOpenChange: setShowingSuggestions,
middleware,
whileElementsMounted: react.autoUpdate,
strategy: "fixed"
});
const stateRef = React.useRef({ showingSuggestions, suggestions, suggestionsIndex, linkUrl, onChange });
stateRef.current = { showingSuggestions, suggestions, suggestionsIndex, linkUrl, onChange };
const activeRef = React.useRef(null);
React.useEffect(() => {
setScrollTop(getElementPosition(activeRef.current, suggestionsIndex));
}, [suggestionsIndex]);
const onKeyDown = React__namespace.useCallback((event, next) => {
if (!stateRef.current.showingSuggestions) {
if (event.key === "=" || event.key === "$" || event.keyCode === 32 && event.ctrlKey) {
const selectionRef = new SelectionReference.SelectionReference();
refs.setReference(selectionRef);
return setShowingSuggestions(true);
}
return next();
}
switch (event.key) {
case "Backspace":
if (stateRef.current.linkUrl.focusText.getText().length === 1) {
next();
}
case "Escape":
setShowingSuggestions(false);
return setSuggestionsIndex(0);
case "Enter":
event.preventDefault();
return onVariableSelect(stateRef.current.suggestions[stateRef.current.suggestionsIndex]);
case "ArrowDown":
case "ArrowUp":
event.preventDefault();
const direction = event.key === "ArrowDown" ? 1 : -1;
return setSuggestionsIndex((index) => modulo(index + direction, stateRef.current.suggestions.length));
default:
return next();
}
}, []);
React.useEffect(() => {
if (prevLinkUrl && prevLinkUrl.selection.isFocused && !linkUrl.selection.isFocused) {
stateRef.current.onChange(Plain__default.default.serialize(linkUrl));
}
}, [linkUrl, prevLinkUrl]);
const onUrlChange = React__namespace.useCallback(({ value: value2 }) => {
setLinkUrl(value2);
}, []);
const onVariableSelect = (item, editor = editorRef.current) => {
const precedingChar = getCharactersAroundCaret();
const precedingDollar = precedingChar === "$";
if (item.origin !== data.VariableOrigin.Template || item.value === data.DataLinkBuiltInVars.includeVars) {
editor.insertText(`${precedingDollar ? "" : "$"}{${item.value}}`);
} else {
editor.insertText(`${precedingDollar ? "" : "$"}{${item.value}:queryparam}`);
}
setLinkUrl(editor.value);
setShowingSuggestions(false);
setSuggestionsIndex(0);
stateRef.current.onChange(Plain__default.default.serialize(editor.value));
};
const getCharactersAroundCaret = () => {
const input = document.getElementById("data-link-input");
let precedingChar = "", sel, range;
if (window.getSelection) {
sel = window.getSelection();
if (sel && sel.rangeCount > 0) {
range = sel.getRangeAt(0).cloneRange();
range.collapse(true);
range.setStart(input, 0);
precedingChar = range.toString().slice(-1);
}
}
return precedingChar;
};
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: styles.wrapperOverrides, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "slate-query-field__wrapper", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { id: "data-link-input", className: "slate-query-field", children: [
showingSuggestions && /* @__PURE__ */ jsxRuntime.jsx(Portal.Portal, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: refs.setFloating, style: floatingStyles, children: /* @__PURE__ */ jsxRuntime.jsx(
ScrollContainer.ScrollContainer,
{
maxHeight: "300px",
ref: scrollRef,
onScroll: (event) => setScrollTop(event.currentTarget.scrollTop),
children: /* @__PURE__ */ jsxRuntime.jsx(
DataLinkSuggestions.DataLinkSuggestions,
{
activeRef,
suggestions: stateRef.current.suggestions,
onSuggestionSelect: onVariableSelect,
onClose: () => setShowingSuggestions(false),
activeIndex: suggestionsIndex
}
)
}
) }) }),
/* @__PURE__ */ jsxRuntime.jsx(
slateReact.Editor,
{
schema: slate.SCHEMA,
ref: editorRef,
placeholder,
value: stateRef.current.linkUrl,
onChange: onUrlChange,
onKeyDown: (event, _editor, next) => onKeyDown(event, next),
plugins,
className: css.cx(
styles.editor,
styles.input,
css.css({
padding: "3px 8px"
})
),
"aria-labelledby": ariaLabelledby
}
)
] }) }) });
}
);
DataLinkInput.displayName = "DataLinkInput";
function getElementPosition(suggestionElement, activeIndex) {
var _a;
return ((_a = suggestionElement == null ? void 0 : suggestionElement.clientHeight) != null ? _a : 0) * activeIndex;
}
exports.DataLinkInput = DataLinkInput;
//# sourceMappingURL=DataLinkInput.cjs.map