UNPKG

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
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