qlik-script-editor
Version:
A React component library for Qlik Sense script editing with syntax highlighting, autocomplete, and theme support
471 lines (470 loc) • 11.9 kB
JavaScript
import { jsx as p, jsxs as M } from "react/jsx-runtime";
import B, { useRef as H, useState as x, useCallback as y, useEffect as _, createContext as Q, useContext as V, useMemo as D } from "react";
function F(t) {
var n, e, s = "";
if (typeof t == "string" || typeof t == "number") s += t;
else if (typeof t == "object") if (Array.isArray(t)) {
var o = t.length;
for (n = 0; n < o; n++) t[n] && (e = F(t[n])) && (s && (s += " "), s += e);
} else for (e in t) t[e] && (s && (s += " "), s += e);
return s;
}
function Y() {
for (var t, n, e = 0, s = "", o = arguments.length; e < o; e++) (t = arguments[e]) && (n = F(t)) && (s && (s += " "), s += n);
return s;
}
function A(...t) {
return Y(t);
}
function z(t, n) {
let e = null;
return function(...o) {
const r = () => {
e = null, t(...o);
};
e && clearTimeout(e), e = setTimeout(r, n);
};
}
function K(t) {
const n = Math.random().toString(36).substr(2, 9);
return t ? `${t}-${n}` : n;
}
function X(t) {
return t != null;
}
function Z(t, n) {
const e = { ...t };
return n.forEach((s) => delete e[s]), e;
}
function tt(t, n) {
const e = {};
return n.forEach((s) => {
s in t && (e[s] = t[s]);
}), e;
}
const et = ({
initialScript: t = "",
onChange: n,
variables: e = [],
theme: s = "light",
height: o = "400px",
width: r = "100%",
readOnly: l = !1,
className: m,
placeholder: E = "Enter your Qlik script here..."
}) => {
const c = H(null), [u, C] = x(t), [O, i] = x([]), [L, h] = x(!1), [v, b] = x(0), T = [
"LOAD",
"FROM",
"SELECT",
"WHERE",
"GROUP BY",
"ORDER BY",
"LEFT JOIN",
"RIGHT JOIN",
"INNER JOIN",
"OUTER JOIN",
"CONCATENATE",
"MAPPING",
"QUALIFY",
"UNQUALIFY",
"DROP",
"STORE",
"LET",
"SET",
"CALL",
"SUB",
"END SUB",
"FOR",
"NEXT",
"DO",
"LOOP",
"WHILE",
"WEND",
"IF",
"THEN",
"ELSE",
"ELSEIF",
"END IF",
"SWITCH",
"CASE",
"DEFAULT",
"END SWITCH",
"EXIT SCRIPT",
"TRACE"
], I = [
"Sum",
"Count",
"Avg",
"Min",
"Max",
"Date",
"Today",
"Now",
"Year",
"Month",
"Day",
"Hour",
"Minute",
"Second",
"If",
"Alt",
"Match",
"ApplyMap",
"Lookup",
"Peek",
"Previous",
"Exists",
"FieldValue",
"FieldIndex",
"NoOfRows",
"RowNo",
"RecNo",
"Len",
"Left",
"Right",
"Mid",
"Trim",
"Upper",
"Lower",
"Replace",
"SubField",
"PurgeChar",
"KeepChar",
"Chr",
"Ord"
], S = y((a) => {
C(a), n?.(a);
}, [n]), R = (a) => {
const d = a.target.value, N = a.target.selectionStart || 0;
S(d), b(N);
const f = k(d, N);
if (f.length > 0) {
const w = [
...T.filter(
(g) => g.toLowerCase().startsWith(f.toLowerCase())
),
...I.filter(
(g) => g.toLowerCase().startsWith(f.toLowerCase())
),
...e.filter(
(g) => g.toLowerCase().startsWith(f.toLowerCase())
)
].slice(0, 10);
i(w), h(w.length > 0);
} else
h(!1);
}, k = (a, d) => {
const N = a.slice(0, d), f = a.slice(d), w = N.search(/\w+$/), g = f.search(/\W/);
if (w === -1) return "";
const q = w, $ = d + (g === -1 ? f.length : g);
return a.slice(q, $);
}, W = (a) => {
if (!c.current) return;
const d = c.current, N = k(u, v), f = u.slice(0, v).lastIndexOf(N);
if (f !== -1) {
const w = u.slice(0, f) + a + u.slice(f + N.length);
S(w), h(!1), setTimeout(() => {
d.focus();
const g = f + a.length;
d.setSelectionRange(g, g);
}, 0);
}
}, U = (a) => {
if (a.key === "Tab") {
a.preventDefault();
const d = a.currentTarget, N = d.selectionStart, f = d.selectionEnd, w = d.value, g = w.substring(0, N) + " " + w.substring(f);
S(g), setTimeout(() => {
d.selectionStart = d.selectionEnd = N + 2;
}, 0);
}
a.key === "Escape" && h(!1);
};
return _(() => {
t !== u && C(t);
}, [t, u]), /* @__PURE__ */ p("div", { className: A("qlik-script-editor", m), style: { width: r }, children: /* @__PURE__ */ M(
"div",
{
className: A(
"qlik-script-editor__container",
`qlik-script-editor--${s}`,
{
"qlik-script-editor--readonly": l
}
),
style: { height: o },
children: [
/* @__PURE__ */ p(
"textarea",
{
ref: c,
value: u,
onChange: R,
onKeyDown: U,
placeholder: E,
readOnly: l,
className: "qlik-script-editor__textarea",
spellCheck: !1
}
),
L && O.length > 0 && /* @__PURE__ */ p("div", { className: "qlik-script-editor__suggestions", children: O.map((a) => /* @__PURE__ */ p(
"button",
{
className: "qlik-script-editor__suggestion",
onClick: () => W(a),
type: "button",
children: a
},
a
)) })
]
}
) });
}, P = Q(void 0), st = ({
defaultTheme: t = "system",
storageKey: n = "qlik-script-editor-theme",
children: e
}) => {
const [s, o] = x(t), [r, l] = x("light");
_(() => {
const c = localStorage.getItem(n);
c && o(c);
}, [n]), _(() => {
if (s === "system") {
const c = window.matchMedia("(prefers-color-scheme: dark)");
l(c.matches ? "dark" : "light");
const u = (C) => {
l(C.matches ? "dark" : "light");
};
return c.addEventListener("change", u), () => c.removeEventListener("change", u);
} else
l(s);
}, [s]);
const E = {
theme: s,
actualTheme: r,
setTheme: (c) => {
o(c), localStorage.setItem(n, c);
}
};
return /* @__PURE__ */ p(P.Provider, { value: E, children: /* @__PURE__ */ p("div", { className: `qlik-script-editor-theme qlik-script-editor-theme--${r}`, children: e }) });
}, nt = () => {
const t = V(P);
if (!t)
throw new Error("useTheme must be used within a ThemeProvider");
return t;
}, j = B.forwardRef(
({
variant: t = "primary",
size: n = "md",
loading: e = !1,
leftIcon: s,
rightIcon: o,
fullWidth: r = !1,
className: l,
disabled: m,
children: E,
...c
}, u) => {
const C = m || e;
return /* @__PURE__ */ M(
"button",
{
ref: u,
className: A(
"rc-button",
`rc-button--${t}`,
`rc-button--${n}`,
{
"rc-button--loading": e,
"rc-button--full-width": r
},
l
),
disabled: C,
...c,
children: [
e && /* @__PURE__ */ p("span", { className: "rc-button__spinner", "aria-hidden": "true", children: /* @__PURE__ */ M(
"svg",
{
className: "rc-button__spinner-icon",
viewBox: "0 0 24 24",
fill: "none",
xmlns: "http://www.w3.org/2000/svg",
children: [
/* @__PURE__ */ p(
"circle",
{
className: "rc-button__spinner-circle",
cx: "12",
cy: "12",
r: "10",
stroke: "currentColor",
strokeWidth: "4"
}
),
/* @__PURE__ */ p(
"path",
{
className: "rc-button__spinner-path",
fill: "currentColor",
d: "m12 2a10 10 0 0 1 10 10h-4a6 6 0 0 0-6-6V2z"
}
)
]
}
) }),
s && !e && /* @__PURE__ */ p("span", { className: "rc-button__icon rc-button__icon--left", "aria-hidden": "true", children: s }),
E && /* @__PURE__ */ p("span", { className: "rc-button__content", children: E }),
o && !e && /* @__PURE__ */ p("span", { className: "rc-button__icon rc-button__icon--right", "aria-hidden": "true", children: o })
]
}
);
}
);
j.displayName = "Button";
const rt = ({
children: t,
title: n,
padding: e = "md",
shadow: s = !0,
className: o = ""
}) => {
const r = "bg-white rounded-lg border border-gray-200", l = {
sm: "p-4",
md: "p-6",
lg: "p-8"
}, m = s ? "shadow-sm hover:shadow-md transition-shadow" : "", E = [
r,
l[e],
m,
o
].filter(Boolean).join(" ");
return /* @__PURE__ */ M("div", { className: E, children: [
n && /* @__PURE__ */ p("div", { className: "mb-4", children: /* @__PURE__ */ p("h3", { className: "text-lg font-medium text-gray-900", children: n }) }),
t
] });
};
function ot(t = {}) {
const {
initialValue: n = 0,
min: e = -1 / 0,
max: s = 1 / 0,
step: o = 1
} = t, [r, l] = x(n), m = y(() => {
l((i) => Math.min(i + o, s));
}, [o, s]), E = y(() => {
l((i) => Math.max(i - o, e));
}, [o, e]), c = y(() => {
l(n);
}, [n]), u = y(
(i) => {
l(Math.max(e, Math.min(s, i)));
},
[e, s]
), C = r <= e, O = r >= s;
return {
count: r,
increment: m,
decrement: E,
reset: c,
set: u,
isAtMin: C,
isAtMax: O
};
}
function ct(t = {}) {
const {
initialScript: n = "",
onChange: e,
variables: s = [],
enableValidation: o = !0
} = t, [r, l] = x(n), m = y((i) => {
l(i), e?.(i);
}, [e]), E = y(() => {
m("");
}, [m]), c = D(() => {
if (!o || !r.trim()) return [];
const i = [];
return r.split(`
`).forEach((h, v) => {
const b = h.trim();
if (!b) return;
b.match(/^(LOAD|SELECT|LET|SET|STORE|DROP)\s+/i) && !b.endsWith(";") && !b.endsWith(":") && !b.includes("FROM") && i.push({
line: v + 1,
column: h.length,
message: "Missing semicolon",
type: "warning"
});
const T = (h.match(/\(/g) || []).length, I = (h.match(/\)/g) || []).length;
T !== I && i.push({
line: v + 1,
column: h.indexOf("(") + 1,
message: "Unmatched parentheses",
type: "syntax"
});
const S = h.match(/\$\([^)]+\)/g);
S && S.forEach((R) => {
const k = R.slice(2, -1);
s.includes(k) || i.push({
line: v + 1,
column: h.indexOf(R) + 1,
message: `Undefined variable: ${k}`,
type: "warning"
});
});
}), i;
}, [r, s, o]), u = D(
() => c.some((i) => i.type === "syntax"),
[c]
), C = y(() => {
const i = r.split(`
`);
let L = 0;
const h = 2, v = i.map((b) => {
const T = b.trim();
if (!T) return "";
T.match(/^(END\s+(IF|SUB|SWITCH))/i) && (L = Math.max(0, L - 1));
const I = " ".repeat(L * h) + T;
return (T.match(/^(IF\s+|FOR\s+|DO\s*$|WHILE\s+|SUB\s+|SWITCH\s+)/i) || T.match(/^(THEN\s*$|ELSE\s*$)/i)) && L++, I;
});
m(v.join(`
`));
}, [r, m]), O = y(() => {
const i = r.split(`
`).filter((S) => S.trim()).length, L = r.length, h = r.split(/\s+/).filter((S) => S).length, b = (r.match(/^(LOAD|SELECT)\s+/gmi) || []).length, I = (r.match(/^(LET|SET)\s+([^=\s]+)/gmi) || []).map((S) => S.split(/\s+/)[1] || "").filter(Boolean);
return {
lines: i,
characters: L,
words: h,
tables: b,
variables: I
};
}, [r]);
return {
script: r,
setScript: m,
clearScript: E,
errors: c,
hasErrors: u,
formatScript: C,
getScriptStats: O
};
}
export {
j as Button,
rt as Card,
et as QlikScriptEditor,
P as ThemeContext,
st as ThemeProvider,
A as cn,
z as debounce,
K as generateId,
X as isNotNullish,
Z as omit,
tt as pick,
ot as useCounter,
ct as useQlikScript,
nt as useTheme
};
//# sourceMappingURL=index.js.map