UNPKG

@svar-ui/react-editor

Version:

React component for building inline, popup, or sidebar forms to edit structured data

640 lines (639 loc) 17 kB
import { jsxs as $, jsx as u } from "react/jsx-runtime"; import { context as F, Field as le, Locale as ae, ModalArea as ye, SideArea as be, Material as te, Willow as ne, WillowDark as oe, Text as ve, TextArea as xe, Checkbox as ke } from "@svar-ui/react-core"; import { useRef as ue, useEffect as L, useContext as P, useMemo as j, useState as G, useCallback as Ce } from "react"; import { uid as Ee, isSame as fe, deepCopy as ie } from "@svar-ui/lib-state"; import { useWritableProp as Ne } from "@svar-ui/lib-react"; import { Toolbar as Se } from "@svar-ui/react-toolbar"; import { setEnv as je, env as Ae } from "@svar-ui/lib-dom"; const Y = {}; function se(t) { return typeof t < "u" ? Y[t] || t : Y.text; } function K(t, e) { Y[t] = e; } const de = { editor: {} }; function H(t) { const { editors: e, data: i, css: l = "", errors: o, focus: a = !1, onClick: n, children: s, onChange: c } = t, d = ue(null); L(() => { if (a) { const f = document.activeElement; if (f && d.current && d.current.contains(f)) return; const p = d.current ? d.current.querySelector( "input:not([disabled]), textarea:not([disabled]), select:not([disabled])" ) : null; p && setTimeout(() => { typeof p.select == "function" && p.select(), typeof p.focus == "function" && p.focus(); }, 300); } }, []); const r = P(F.i18n), w = j(() => r.getGroup("editor"), [r]), g = j( () => e.config[0].comp === "readonly" && e.config.every((f) => !Object.keys(i).includes(f.key)), [e, i] ); return /* @__PURE__ */ $("div", { className: "wx-s2aE1xdZ wx-sections " + l, ref: d, children: [ s, g ? /* @__PURE__ */ u("div", { className: "wx-s2aE1xdZ wx-overlay", children: w("No data") }) : null, e.config.map((f) => { if (!f.hidden) { const { key: p, onChange: k, ...v } = f; if (f.comp === "readonly" || f.comp === "section") { const x = se(f.comp); return /* @__PURE__ */ u( x, { fieldKey: p, label: f.label, value: i[p], ...v, onClick: n }, p ); } else { const x = se(f.comp); return /* @__PURE__ */ $("div", { children: [ /* @__PURE__ */ u( le, { label: f.labelTemplate ? f.labelTemplate(i[p]) : f.label ?? "", required: f.required, children: ({ id: D }) => /* @__PURE__ */ u( x, { fieldKey: p, ...v, onChange: k || ((y) => { c && c({ value: y.value, key: p, input: y.input }); }), id: D, label: void 0, error: o && o[p], value: i[p] }, p ) } ), o && o[p] && f.validationMessage ? /* @__PURE__ */ u("div", { className: "wx-s2aE1xdZ wx-message", children: f.validationMessage }) : null ] }, p); } } return null; }) ] }); } function De(t) { if (typeof t == "string" && t.includes(".")) { const e = t.split("."); return (i) => { let l = i; return e.forEach((o) => { l = l[o]; }), l; }; } return (e) => e[t]; } function Me(t) { if (typeof t == "string" && t.includes(".")) { const e = t.split("."); return (i, l) => { let o = i; e.forEach((a, n) => { n === e.length - 1 ? o[a] = l : o = o[a]; }); }; } return (e, i) => e[t] = i; } function Te(t) { const e = t.map((n) => { const s = { ...n }; return n.config && Object.assign(s, n.config), s.key = n.key || Ee(), s.setter = n.setter || Me(n.key), s.getter = n.getter || De(n.key), s; }), i = (n) => { const s = {}; return e.forEach((c) => { c.comp !== "section" && (c.getter ? s[c.key] = c.getter(n) : s[c.key] = n[c.key]); }), s; }, l = (n, s, c) => ((c.length ? c.map((r) => e.find((w) => w.key === r)) : e).forEach((r) => { r.setter ? r.setter(n, s[r.key]) : n[r.key] = s[r.key]; }), n), o = (n, s) => { const c = i(n), d = []; return e.forEach((r) => { const w = c[r.key], g = s[r.key]; !fe(w, g) && (w !== void 0 || g) && d.push(r.key); }), d; }, a = (n, s, c) => { let d = 0; const r = {}; return (s?.length ? s.map((g) => e.find((f) => f.key === g)) : e).forEach((g) => { g.required && !n[g.key] ? (r[g.key] = { errorType: "required" }, g.validationMessage = g.validationMessage ?? c("This field is required"), d++) : g.validation && !g.validation(n[g.key]) && (r[g.key] = { errorType: "validation" }, g.validationMessage = g.validationMessage ?? c("Invalid value"), d++); }), d > 0 ? r : null; }; return { config: e.filter((n) => n.comp !== "hidden"), getValues: i, setValues: l, diff: o, validateValues: a }; } function me({ values: t, items: e, css: i, activeBatch: l, autoSave: o, focus: a, readonly: n, topBar: s = !0, bottomBar: c = !0, layout: d = "default", placement: r = "inline", view: w, children: g, onChange: f, onSave: p, onAction: k, onValidation: v }) { const D = P(F.i18n).getGroup("editor"), [y, T] = Ne(t), [M, O] = G(null), b = j(() => { const h = Te(e); M && h.config.forEach((m) => { m.comp === "section" && m.key === M && (m.sectionMode === "accordion" ? m.activeSection || (h.config.forEach((V) => { V.comp === "section" && V.key !== m.key && (V.activeSection = !1); }), m.activeSection = !0) : m.activeSection = !m.activeSection); }); let E = /* @__PURE__ */ new Set(), S = null; return h.config.forEach((m) => { m.sectionMode === "exclusive" && m.activeSection && (S = m.key), m.activeSection && E.add(m.key); }), h.config.forEach((m) => { m.hidden = m.hidden || l && l !== m.batch || S && m.key != S && m.section !== S || m.section && !E.has(m.section); }), n ? { ...h, config: h.config.map((m) => ({ ...m, comp: "readonly" })), diff: () => [] } : h; }, [e, M, l, n]), [C, N] = G({}), [W, J] = G({}); L(() => { y !== void 0 && (N(ie(y)), J(ie(y))); }, [y]); const A = y, [z, B] = G([]); L(() => { y && B([]); }, [y]); function X(h) { return [...new Set(h)]; } function U(h) { const E = b.validateValues(C, h, D); return fe(E, A.errors) || v && v({ errors: E, values: C }), E; } function pe(h, E) { if (o && !A.errors) { const S = b.setValues(y, E ?? W, h); T(S), p && p({ changes: h, values: S }); } else B(h); } function he({ value: h, key: E, input: S }) { let m = { ...W || {}, [E]: h }; const V = { key: E, value: h, update: m }; if (S && (V.input = S), f && f(V), !y) return; m = V.update, J(m); const I = b.diff(y, m), we = b.setValues( { ...C || {} }, m, X([...I, E]) ); if (N(we), I.length) { const R = o ? [] : X([...I, ...Object.keys(A.errors ?? {}), E]); A.errors = U(R), pe(I, m); } else { const R = Object.keys(A.errors ?? {}); R.length && (A.errors = U(R)), B([]); } } function ge() { if (z.length && (o || (A.errors = U()), !A.errors)) { p && p({ changes: z, values: C }); const h = b.setValues(y, W, z); T(h), B([]), T({ ...C }); } } function ee({ item: h }) { h.id === "save" ? ge() : h.id === "toggle-section" && O(h.key), k && k({ item: h, values: C, changes: z }); } return /* @__PURE__ */ u( w, { topBar: s, bottomBar: c, placement: r, layout: d, readonly: n, autoSave: o, css: i, data: W, editors: b, focus: a, errors: A.errors, onClick: ee, onKeyDown: ee, onChange: he, children: g } ); } function Le({ values: t = {}, items: e = [], css: i = "", activeBatch: l = null, focus: o = !1, autoSave: a = !0, readonly: n = !1, children: s, ...c }) { const d = {}; for (const r in c) { if (/^on[a-z]/.test(r)) { const w = "on" + r.charAt(2).toUpperCase() + r.slice(3); if (w in c) continue; d[w] = c[r]; continue; } d[r] = c[r]; } return /* @__PURE__ */ u(ae, { words: de, optional: !0, children: /* @__PURE__ */ u("div", { className: "wx-H902AF2Y wx-content", children: /* @__PURE__ */ u( me, { view: H, values: t, items: e, css: i, focus: o, activeBatch: l, autoSave: a, readonly: n, ...d, children: s } ) }) }); } function Ve(t, { keys: e, action: i }) { function l(o) { let a = o.code.replace("Key", "").toLowerCase(); const n = `${o.ctrlKey || o.metaKey ? "ctrl+" : ""}${a}`, s = e[n]; if (!s) return; const c = o.target.closest(".wx-combo") || o.target.closest(".wx-multicombo") || o.target.closest(".wx-richselect"); c && c.querySelector(".wx-list") || (o.preventDefault(), i(s)); } return t.addEventListener("keydown", l), { destroy: () => { t.removeEventListener("keydown", l); } }; } function $e(t) { const { editors: e, data: i, layout: l, errors: o, focus: a, onClick: n, onChange: s } = t, c = j(() => { let d = []; if (l === "columns" && (d = [ { ...e, config: [] }, { ...e, config: [] } ], e.config.forEach((r) => { const w = r.column === "left" ? 0 : 1; d[w].config.push(r); }), d[0].config.length)) { const r = d[0].config[0]; r.comp === "text" && (d[0][0] = { ...r, css: "title", label: "" }); } return d; }, [l, e]); return l === "columns" ? /* @__PURE__ */ $("div", { className: "wx-bNrSbszs wx-cols", children: [ /* @__PURE__ */ u("div", { className: "wx-bNrSbszs wx-left", children: /* @__PURE__ */ u( H, { editors: c[0], data: i, errors: o, onClick: n, onChange: s } ) }), /* @__PURE__ */ u("div", { className: "wx-bNrSbszs wx-right", children: /* @__PURE__ */ u( H, { editors: c[1], data: i, focus: a, errors: o, onClick: n, onChange: s } ) }) ] }) : /* @__PURE__ */ u( H, { editors: e, data: i, focus: a, errors: o, onClick: n, onChange: s } ); } function re({ items: t, values: e = null, top: i = !0, onClick: l, onChange: o }) { const a = Ce( ({ item: n, value: s }) => { o && o({ key: n.key, value: s }); }, [o] ); return t.length ? /* @__PURE__ */ u( "div", { className: `wx-66OW1j0R wx-editor-toolbar ${i ? "wx-topbar" : "wx-bottom"}`, children: /* @__PURE__ */ u( Se, { items: t, values: e, onClick: l, onChange: a } ) } ) : null; } const q = () => ({ comp: "spacer" }), Z = (t) => ({ comp: "button", text: t("Cancel"), id: "cancel" }), _ = (t) => ({ type: "primary", comp: "button", text: t("Save"), id: "save" }), ce = () => ({ comp: "icon", icon: "wxi-close", id: "close" }); function Q(t) { const { data: e, editors: i, focus: l, css: o, topBar: a, bottomBar: n, layout: s, placement: c, errors: d, readonly: r, autoSave: w, children: g, onClick: f, onKeyDown: p, onChange: k } = t, v = P(F.i18n), x = j(() => v.getGroup("editor"), [v]), D = j( () => a === !0 && n === !0, [a, n] ), y = j(() => { let b = a && a.items ? a.items.map((C) => ({ ...C })) : []; return D && (r ? b = [q(), ce()] : (w ? b = [q(), ce()] : c !== "modal" && (b = [q(), Z(x), _(x)]), s === "columns" && !b.length && (b = [q(), _(x), Z(x)]))), b; }, [a, D, r, w, c, s, x]), T = j(() => { let b = n && n.items ? n.items.map((C) => ({ ...C })) : []; return D && (r || (c === "modal" && !w && (b = [q(), _(x), Z(x)]), s === "columns" && y.length && (b = []))), b; }, [n, D, r, c, w, s, y, x]), M = j(() => [...y, ...T], [y, T]), O = ue(null); return L(() => { const b = O.current; if (!b) return; const C = Ve(b, { keys: { "ctrl+s": M.find((N) => N.id === "save"), escape: M.find((N) => N.id === "cancel" || N.id === "close"), "ctrl+d": M.find((N) => N.id === "delete") }, action: (N) => { p && p({ item: N }); } }); return () => { typeof C == "function" && C(); }; }, [M, p]), /* @__PURE__ */ $("div", { className: o ? "wx-85HDaNoA " + o : "wx-85HDaNoA", ref: O, children: [ /* @__PURE__ */ u( re, { ...a && typeof a == "object" ? a : {}, items: y, values: e, onClick: f, onChange: k } ), /* @__PURE__ */ $( "div", { className: `wx-85HDaNoA wx-content${s === "columns" ? " wx-layout-columns" : ""}`, children: [ g, /* @__PURE__ */ u( $e, { editors: i, layout: s, data: e, focus: l, errors: d, onClick: f, onChange: k } ), /* @__PURE__ */ u( re, { ...n && typeof n == "object" ? n : {}, items: T, values: e, top: !1, onClick: f, onChange: k } ) ] } ) ] }); } function qe(t) { const { css: e, onClick: i, placement: l, ...o } = t; function a() { i && i({ item: { id: "close" } }); } return l === "modal" ? /* @__PURE__ */ u(ye, { children: /* @__PURE__ */ u( Q, { css: `wx-panel ${e}`, onClick: i, placement: l, ...o } ) }) : l === "sidebar" ? /* @__PURE__ */ u(be, { onCancel: a, children: /* @__PURE__ */ u( Q, { css: `wx-panel ${e}`, onClick: i, placement: l, ...o } ) }) : /* @__PURE__ */ u( Q, { css: `wx-inline-form ${e}`, onClick: i, placement: l, ...o } ); } function Fe(t) { const { values: e = {}, items: i = [], css: l = "", activeBatch: o = null, topBar: a = !0, bottomBar: n = !0, focus: s = !1, autoSave: c = !1, layout: d = "default", readonly: r = !1, placement: w = "inline", children: g, ...f } = t, p = Object.keys(f).reduce((k, v) => { if (/^on[a-z]/.test(v)) { const x = "on" + v.charAt(2).toUpperCase() + v.slice(3); x in f ? k[v] = f[v] : k[x] = f[v]; } else k[v] = f[v]; return k; }, {}); return /* @__PURE__ */ u(ae, { words: de, optional: !0, children: /* @__PURE__ */ u( me, { view: qe, values: e, items: i, css: l, activeBatch: o, topBar: a, bottomBar: n, focus: s, autoSave: c, layout: d, readonly: r, placement: w, ...p, children: g } ) }); } function Pe({ fonts: t = !0, children: e }) { return e ? /* @__PURE__ */ u(te, { fonts: t, children: e }) : /* @__PURE__ */ u(te, { fonts: t }); } function Ue({ fonts: t = !0, children: e }) { return e ? /* @__PURE__ */ u(ne, { fonts: t, children: e }) : /* @__PURE__ */ u(ne, { fonts: t }); } function Ze(t) { const { fonts: e = !0, children: i } = t; return i ? /* @__PURE__ */ u(oe, { fonts: e, children: i }) : /* @__PURE__ */ u(oe, { fonts: e }); } function Ke({ value: t, options: e, label: i }) { const o = P(F.i18n).getGroup("editor"), a = j(() => { let n = t; if (typeof t == "boolean" && (n = o(t ? "Yes" : "No")), e) { const s = e.find((c) => c.id === t); s && (n = s.label); } return n; }, [t, e, o]); return a || a === 0 ? /* @__PURE__ */ u(le, { label: i, children: a }) : null; } function Oe({ fieldKey: t, label: e, activeSection: i, onClick: l }) { return /* @__PURE__ */ $( "div", { className: `wx-OmgQq65I wx-section${i ? " wx-section-active" : ""}`, onClick: () => l && l({ item: { id: "toggle-section", key: i ? null : t } }), children: [ /* @__PURE__ */ u("h3", { children: e }), /* @__PURE__ */ u( "i", { className: `wx-OmgQq65I wxi-angle-${i ? "down" : "right"} wx-icon` } ) ] } ); } K("text", ve); K("textarea", xe); K("checkbox", ke); K("readonly", Ke); K("section", Oe); je(Ae); export { Fe as Editor, Le as Form, Pe as Material, Ue as Willow, Ze as WillowDark, K as registerEditorItem };