@formkl/editor
Version:
A universal Editor for Formkl using web component API
212 lines (205 loc) • 7.02 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
const commands = require('@codemirror/commands');
const state = require('@codemirror/state');
const view = require('@codemirror/view');
const codemirror = require('codemirror');
const autocomplete = require('@codemirror/autocomplete');
const lint = require('@codemirror/lint');
const FormklParser = require('formkl');
const completions = [
{ label: "formkl", type: "class", detail: "Formkl block", info: "Start declaring a Form" },
{
label: "base",
type: "type",
detail: "Model type",
info: `Default structure form data, for ex: [ { key: "fullname", value: "Michael Jackson" } ]`
},
{
label: "flat",
type: "type",
detail: "Model type",
info: `Flatten structure form data, for ex: { fullname: "Michael Jackson" }`
},
{
label: "multiple",
type: "keyword",
detail: "Multiple responses",
info: "Declare whether a field of a section has multiple responses"
},
{
label: "includes",
type: "keyword",
detail: "Section block",
info: "Declare a group of fields"
},
{
label: "or",
type: "keyword",
detail: "OR condition operator",
info: "Use as: valid(>500 or ==300)"
},
{
label: "and",
type: "keyword",
detail: "AND condition operator",
info: "Use as: valid(>500 and <1000)"
},
{
label: "has",
type: "keyword",
detail: "HAS condition operator",
info: "Use as: valid(has 'some keyword')"
},
{
label: "as",
type: "keyword",
detail: "Field alias",
info: "Declare custom key for field in form data"
},
{ label: "require", type: "keyword", detail: "Require field" },
{ label: "valid", type: "function", detail: "Validation", info: "Declare simple validation" },
{
label: "regex",
type: "function",
detail: "RegExp",
info: "Declare a validation with Regular Expression"
},
{
label: "url",
type: "function",
detail: "Selection",
info: "Declare data source url for select field's options"
},
{ label: "get", type: "function", detail: "Submit method" },
{ label: "post", type: "function", detail: "Submit method" },
{ label: "put", type: "function", detail: "Submit method" },
{ label: "patch", type: "function", detail: "Submit method" },
{ label: "delete", type: "function", detail: "Submit method" },
{ label: "text", type: "constant", detail: "Field" },
{ label: "paragraph", type: "constant", detail: "Field" },
{ label: "number", type: "constant", detail: "Field" },
{ label: "switch", type: "constant", detail: "Field" },
{ label: "checkbox", type: "constant", detail: "Field" },
{ label: "radio", type: "constant", detail: "Field" },
{ label: "select", type: "constant", detail: "Field" },
{ label: "datetimerange", type: "constant", detail: "Field" },
{ label: "datetime", type: "constant", detail: "Field" },
{ label: "daterange", type: "constant", detail: "Field" },
{ label: "timerange", type: "constant", detail: "Field" },
{ label: "time", type: "constant", detail: "Field" },
{ label: "date", type: "constant", detail: "Field" },
{ label: "future date", type: "constant", detail: "Field" },
{ label: "past date", type: "constant", detail: "Field" },
{ label: "birthday", type: "constant", detail: "Field" },
{ label: "US phone", type: "constant", detail: "Field" },
{ label: "VN phone", type: "constant", detail: "Field" }
];
const keywordSuggestion = (context) => {
let before = context.matchBefore(/\w+/);
if (!context.explicit && !before)
return null;
return {
from: before ? before.from : context.pos,
options: completions,
validFor: /^\w*$/
};
};
const AutoCompleteExtension = autocomplete.autocompletion({ override: [keywordSuggestion] });
function lintSyntax(view) {
const diagnostics = [];
const content = view.state.doc.toString();
try {
FormklParser.parse(content);
} catch (err) {
const errorIndex = /at \d+\:\d+/.exec(err.message)[0].replace(/at /g, "").split(":").map(Number);
diagnostics.push({
from: errorIndex ? view.state.doc.line(errorIndex[0]).from : view.state.doc.length,
to: errorIndex ? view.state.doc.line(errorIndex[0]).to : view.state.doc.length,
severity: "error",
message: err.message
});
}
return diagnostics;
}
const LintExtension = lint.linter(lintSyntax);
const createEditor = (options) => {
return class FormklEditor extends HTMLElement {
constructor() {
super();
this.modified = false;
this.editor = null;
this.attachShadow({ mode: "open" });
codemirror.EditorView.theme(options?.theme || {}, { dark: Boolean(options?.dark) });
const wrapper = document.createElement("div");
wrapper.id = "formkl__editor";
this.shadowRoot.appendChild(wrapper);
this.editor = new codemirror.EditorView({
...options,
parent: this.shadowRoot.getElementById("formkl__editor"),
state: state.EditorState.create({
doc: this.value,
extensions: [
codemirror.basicSetup,
view.keymap.of([commands.indentWithTab]),
AutoCompleteExtension,
LintExtension,
codemirror.EditorView.updateListener.of((viewUpdate) => {
const eventInput = new CustomEvent("input", { detail: this.value });
const eventFocus = new CustomEvent("focus");
const eventBlur = new CustomEvent("blur");
const eventChange = new CustomEvent("change");
if (viewUpdate.docChanged) {
this.dispatchEvent(eventInput);
this.modified = true;
}
if (viewUpdate.focusChanged) {
viewUpdate.view.hasFocus ? this.dispatchEvent(eventFocus) : this.dispatchEvent(eventBlur);
if (this.modified && !viewUpdate.view.hasFocus) {
this.dispatchEvent(eventChange);
this.modified = false;
}
}
})
].concat(options?.extensions || [])
})
});
}
static get observedAttributes() {
return ["value"];
}
get value() {
return this.getContent();
}
set value(newValue) {
this.setContent(newValue);
}
connectedCallback() {
}
attributeChangedCallback(name, oldValue, newValue) {
if (name === "value") {
this.setContent(newValue);
}
}
getContent() {
return this.editor?.state.doc.toString() || "";
}
setContent(content) {
if (content !== this.getContent()) {
this.editor.dispatch({
changes: {
from: 0,
to: this.editor.state.doc.length,
insert: content
}
});
}
}
};
};
const index = { createEditor };
if (window && window.customElements) {
window.customElements.define("formkl-editor", createEditor());
}
exports.createEditor = createEditor;
exports.default = index;