@formdown/ui
Version:
Pure HTML renderer for Formdown syntax
1,180 lines (1,139 loc) • 39.5 kB
JavaScript
import { css as P, LitElement as I, html as v } from "lit";
import { extensionManager as y, registerHook as C, registerPlugin as R, FormManager as O } from "@formdown/core";
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
const z = (e) => (t, r) => {
r !== void 0 ? r.addInitializer(() => {
customElements.define(e, t);
}) : customElements.define(e, t);
};
/**
* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
const w = globalThis, _ = w.ShadowRoot && (w.ShadyCSS === void 0 || w.ShadyCSS.nativeShadow) && "adoptedStyleSheets" in Document.prototype && "replace" in CSSStyleSheet.prototype, T = Symbol(), U = /* @__PURE__ */ new WeakMap();
let q = class {
constructor(t, r, i) {
if (this._$cssResult$ = !0, i !== T) throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");
this.cssText = t, this.t = r;
}
get styleSheet() {
let t = this.o;
const r = this.t;
if (_ && t === void 0) {
const i = r !== void 0 && r.length === 1;
i && (t = U.get(r)), t === void 0 && ((this.o = t = new CSSStyleSheet()).replaceSync(this.cssText), i && U.set(r, t));
}
return t;
}
toString() {
return this.cssText;
}
};
const D = (e) => new q(typeof e == "string" ? e : e + "", void 0, T), L = (e, t) => {
if (_) e.adoptedStyleSheets = t.map((r) => r instanceof CSSStyleSheet ? r : r.styleSheet);
else for (const r of t) {
const i = document.createElement("style"), o = w.litNonce;
o !== void 0 && i.setAttribute("nonce", o), i.textContent = r.cssText, e.appendChild(i);
}
}, k = _ ? (e) => e : (e) => e instanceof CSSStyleSheet ? ((t) => {
let r = "";
for (const i of t.cssRules) r += i.cssText;
return D(r);
})(e) : e;
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
const { is: j, defineProperty: H, getOwnPropertyDescriptor: V, getOwnPropertyNames: B, getOwnPropertySymbols: N, getPrototypeOf: K } = Object, h = globalThis, A = h.trustedTypes, W = A ? A.emptyScript : "", x = h.reactiveElementPolyfillSupport, b = (e, t) => e, E = { toAttribute(e, t) {
switch (t) {
case Boolean:
e = e ? W : null;
break;
case Object:
case Array:
e = e == null ? e : JSON.stringify(e);
}
return e;
}, fromAttribute(e, t) {
let r = e;
switch (t) {
case Boolean:
r = e !== null;
break;
case Number:
r = e === null ? null : Number(e);
break;
case Object:
case Array:
try {
r = JSON.parse(e);
} catch {
r = null;
}
}
return r;
} }, $ = (e, t) => !j(e, t), F = { attribute: !0, type: String, converter: E, reflect: !1, useDefault: !1, hasChanged: $ };
Symbol.metadata ?? (Symbol.metadata = Symbol("metadata")), h.litPropertyMetadata ?? (h.litPropertyMetadata = /* @__PURE__ */ new WeakMap());
class g extends HTMLElement {
static addInitializer(t) {
this._$Ei(), (this.l ?? (this.l = [])).push(t);
}
static get observedAttributes() {
return this.finalize(), this._$Eh && [...this._$Eh.keys()];
}
static createProperty(t, r = F) {
if (r.state && (r.attribute = !1), this._$Ei(), this.prototype.hasOwnProperty(t) && ((r = Object.create(r)).wrapped = !0), this.elementProperties.set(t, r), !r.noAccessor) {
const i = Symbol(), o = this.getPropertyDescriptor(t, i, r);
o !== void 0 && H(this.prototype, t, o);
}
}
static getPropertyDescriptor(t, r, i) {
const { get: o, set: s } = V(this.prototype, t) ?? { get() {
return this[r];
}, set(n) {
this[r] = n;
} };
return { get: o, set(n) {
const a = o == null ? void 0 : o.call(this);
s == null || s.call(this, n), this.requestUpdate(t, a, i);
}, configurable: !0, enumerable: !0 };
}
static getPropertyOptions(t) {
return this.elementProperties.get(t) ?? F;
}
static _$Ei() {
if (this.hasOwnProperty(b("elementProperties"))) return;
const t = K(this);
t.finalize(), t.l !== void 0 && (this.l = [...t.l]), this.elementProperties = new Map(t.elementProperties);
}
static finalize() {
if (this.hasOwnProperty(b("finalized"))) return;
if (this.finalized = !0, this._$Ei(), this.hasOwnProperty(b("properties"))) {
const r = this.properties, i = [...B(r), ...N(r)];
for (const o of i) this.createProperty(o, r[o]);
}
const t = this[Symbol.metadata];
if (t !== null) {
const r = litPropertyMetadata.get(t);
if (r !== void 0) for (const [i, o] of r) this.elementProperties.set(i, o);
}
this._$Eh = /* @__PURE__ */ new Map();
for (const [r, i] of this.elementProperties) {
const o = this._$Eu(r, i);
o !== void 0 && this._$Eh.set(o, r);
}
this.elementStyles = this.finalizeStyles(this.styles);
}
static finalizeStyles(t) {
const r = [];
if (Array.isArray(t)) {
const i = new Set(t.flat(1 / 0).reverse());
for (const o of i) r.unshift(k(o));
} else t !== void 0 && r.push(k(t));
return r;
}
static _$Eu(t, r) {
const i = r.attribute;
return i === !1 ? void 0 : typeof i == "string" ? i : typeof t == "string" ? t.toLowerCase() : void 0;
}
constructor() {
super(), this._$Ep = void 0, this.isUpdatePending = !1, this.hasUpdated = !1, this._$Em = null, this._$Ev();
}
_$Ev() {
var t;
this._$ES = new Promise((r) => this.enableUpdating = r), this._$AL = /* @__PURE__ */ new Map(), this._$E_(), this.requestUpdate(), (t = this.constructor.l) == null || t.forEach((r) => r(this));
}
addController(t) {
var r;
(this._$EO ?? (this._$EO = /* @__PURE__ */ new Set())).add(t), this.renderRoot !== void 0 && this.isConnected && ((r = t.hostConnected) == null || r.call(t));
}
removeController(t) {
var r;
(r = this._$EO) == null || r.delete(t);
}
_$E_() {
const t = /* @__PURE__ */ new Map(), r = this.constructor.elementProperties;
for (const i of r.keys()) this.hasOwnProperty(i) && (t.set(i, this[i]), delete this[i]);
t.size > 0 && (this._$Ep = t);
}
createRenderRoot() {
const t = this.shadowRoot ?? this.attachShadow(this.constructor.shadowRootOptions);
return L(t, this.constructor.elementStyles), t;
}
connectedCallback() {
var t;
this.renderRoot ?? (this.renderRoot = this.createRenderRoot()), this.enableUpdating(!0), (t = this._$EO) == null || t.forEach((r) => {
var i;
return (i = r.hostConnected) == null ? void 0 : i.call(r);
});
}
enableUpdating(t) {
}
disconnectedCallback() {
var t;
(t = this._$EO) == null || t.forEach((r) => {
var i;
return (i = r.hostDisconnected) == null ? void 0 : i.call(r);
});
}
attributeChangedCallback(t, r, i) {
this._$AK(t, i);
}
_$ET(t, r) {
var s;
const i = this.constructor.elementProperties.get(t), o = this.constructor._$Eu(t, i);
if (o !== void 0 && i.reflect === !0) {
const n = (((s = i.converter) == null ? void 0 : s.toAttribute) !== void 0 ? i.converter : E).toAttribute(r, i.type);
this._$Em = t, n == null ? this.removeAttribute(o) : this.setAttribute(o, n), this._$Em = null;
}
}
_$AK(t, r) {
var s, n;
const i = this.constructor, o = i._$Eh.get(t);
if (o !== void 0 && this._$Em !== o) {
const a = i.getPropertyOptions(o), c = typeof a.converter == "function" ? { fromAttribute: a.converter } : ((s = a.converter) == null ? void 0 : s.fromAttribute) !== void 0 ? a.converter : E;
this._$Em = o;
const f = c.fromAttribute(r, a.type);
this[o] = f ?? ((n = this._$Ej) == null ? void 0 : n.get(o)) ?? f, this._$Em = null;
}
}
requestUpdate(t, r, i) {
var o;
if (t !== void 0) {
const s = this.constructor, n = this[t];
if (i ?? (i = s.getPropertyOptions(t)), !((i.hasChanged ?? $)(n, r) || i.useDefault && i.reflect && n === ((o = this._$Ej) == null ? void 0 : o.get(t)) && !this.hasAttribute(s._$Eu(t, i)))) return;
this.C(t, r, i);
}
this.isUpdatePending === !1 && (this._$ES = this._$EP());
}
C(t, r, { useDefault: i, reflect: o, wrapped: s }, n) {
i && !(this._$Ej ?? (this._$Ej = /* @__PURE__ */ new Map())).has(t) && (this._$Ej.set(t, n ?? r ?? this[t]), s !== !0 || n !== void 0) || (this._$AL.has(t) || (this.hasUpdated || i || (r = void 0), this._$AL.set(t, r)), o === !0 && this._$Em !== t && (this._$Eq ?? (this._$Eq = /* @__PURE__ */ new Set())).add(t));
}
async _$EP() {
this.isUpdatePending = !0;
try {
await this._$ES;
} catch (r) {
Promise.reject(r);
}
const t = this.scheduleUpdate();
return t != null && await t, !this.isUpdatePending;
}
scheduleUpdate() {
return this.performUpdate();
}
performUpdate() {
var i;
if (!this.isUpdatePending) return;
if (!this.hasUpdated) {
if (this.renderRoot ?? (this.renderRoot = this.createRenderRoot()), this._$Ep) {
for (const [s, n] of this._$Ep) this[s] = n;
this._$Ep = void 0;
}
const o = this.constructor.elementProperties;
if (o.size > 0) for (const [s, n] of o) {
const { wrapped: a } = n, c = this[s];
a !== !0 || this._$AL.has(s) || c === void 0 || this.C(s, void 0, n, c);
}
}
let t = !1;
const r = this._$AL;
try {
t = this.shouldUpdate(r), t ? (this.willUpdate(r), (i = this._$EO) == null || i.forEach((o) => {
var s;
return (s = o.hostUpdate) == null ? void 0 : s.call(o);
}), this.update(r)) : this._$EM();
} catch (o) {
throw t = !1, this._$EM(), o;
}
t && this._$AE(r);
}
willUpdate(t) {
}
_$AE(t) {
var r;
(r = this._$EO) == null || r.forEach((i) => {
var o;
return (o = i.hostUpdated) == null ? void 0 : o.call(i);
}), this.hasUpdated || (this.hasUpdated = !0, this.firstUpdated(t)), this.updated(t);
}
_$EM() {
this._$AL = /* @__PURE__ */ new Map(), this.isUpdatePending = !1;
}
get updateComplete() {
return this.getUpdateComplete();
}
getUpdateComplete() {
return this._$ES;
}
shouldUpdate(t) {
return !0;
}
update(t) {
this._$Eq && (this._$Eq = this._$Eq.forEach((r) => this._$ET(r, this[r]))), this._$EM();
}
updated(t) {
}
firstUpdated(t) {
}
}
g.elementStyles = [], g.shadowRootOptions = { mode: "open" }, g[b("elementProperties")] = /* @__PURE__ */ new Map(), g[b("finalized")] = /* @__PURE__ */ new Map(), x == null || x({ ReactiveElement: g }), (h.reactiveElementVersions ?? (h.reactiveElementVersions = [])).push("2.1.1");
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
const J = { attribute: !0, type: String, converter: E, reflect: !1, hasChanged: $ }, Y = (e = J, t, r) => {
const { kind: i, metadata: o } = r;
let s = globalThis.litPropertyMetadata.get(o);
if (s === void 0 && globalThis.litPropertyMetadata.set(o, s = /* @__PURE__ */ new Map()), i === "setter" && ((e = Object.create(e)).wrapped = !0), s.set(r.name, e), i === "accessor") {
const { name: n } = r;
return { set(a) {
const c = t.get.call(this);
t.set.call(this, a), this.requestUpdate(n, c, e);
}, init(a) {
return a !== void 0 && this.C(n, void 0, e, a), a;
} };
}
if (i === "setter") {
const { name: n } = r;
return function(a) {
const c = this[n];
t.call(this, a), this.requestUpdate(n, c, e);
};
}
throw Error("Unsupported decorator location: " + i);
};
function p(e) {
return (t, r) => typeof r == "object" ? Y(e, t, r) : ((i, o, s) => {
const n = o.hasOwnProperty(s);
return o.constructor.createProperty(s, i), n ? Object.getOwnPropertyDescriptor(o, s) : void 0;
})(e, t, r);
}
const l = class l {
constructor() {
this.initialized = !1;
}
static getInstance() {
return l.instance || (l.instance = new l()), l.instance;
}
async initialize() {
if (!this.initialized)
try {
await y.initialize(), this.setupUIHooks(), this.initialized = !0;
} catch (t) {
console.debug("Extension system already initialized or error:", t);
}
}
setupUIHooks() {
C({
name: "field-render",
priority: 1,
handler: this.handleFieldRender.bind(this)
}), C({
name: "field-validate",
priority: 1,
handler: this.handleFieldValidate.bind(this)
});
}
handleFieldRender(t, r) {
return r;
}
handleFieldValidate(t, r) {
return { valid: !0, message: "" };
}
/**
* Register a UI-specific plugin
*/
async registerUIPlugin(t) {
await R(t);
}
/**
* Get extension statistics
*/
getExtensionStats() {
return y.getStats();
}
/**
* Execute hooks for UI operations
*/
async executeUIHooks(t, r, ...i) {
return y.executeHooks(t, r, ...i);
}
};
l.instance = null;
let S = l;
const G = S.getInstance();
var Q = Object.defineProperty, X = Object.getOwnPropertyDescriptor, u = (e, t, r, i) => {
for (var o = i > 1 ? void 0 : i ? X(t, r) : t, s = e.length - 1, n; s >= 0; s--)
(n = e[s]) && (o = (i ? n(t, r, o) : n(o)) || o);
return i && o && Q(t, r, o), o;
};
let d = class extends I {
constructor() {
super(), this.content = "", this.selectOnFocus = !0, this.formId = "", this.showSubmitButton = !0, this.submitText = "Submit", this._data = {}, this.fieldRegistry = /* @__PURE__ */ new Map(), this._isUpdatingUI = !1, this._schema = null, this._uniqueFormId = this.formId || `formdown-${Math.random().toString(36).substring(2, 15)}`, this.formManager = new O(), this.fieldProcessor = this.formManager.createFieldProcessor(), this.domBinder = this.formManager.createDOMBinder(), this.setupCoreUIBridge(), this.setupFormManagerEvents();
}
// Get the form ID (user-provided or auto-generated)
getFormId() {
return this.formId || this._uniqueFormId;
}
get data() {
return this._data;
}
set data(e) {
if (this._isUpdatingUI) return;
const t = this._data;
this._data = e != null && typeof e == "object" ? { ...e } : {}, this.requestUpdate("data", t), this.formManager && this._schema && !this._isUpdatingUI && this.formManager.updateData(this._data);
}
// Public method to update data programmatically
updateData(e) {
this.data = e, this.formManager && this._schema && this.formManager.updateData(e);
}
// Public method to update single field
updateField(e, t) {
this.data = { ...this.data, [e]: t }, this.formManager && this._schema && this.formManager.setFieldValue(e, t);
}
/**
* Set up Core-UI component bridge using EventOrchestrator
*/
setupCoreUIBridge() {
const e = {
id: "formdown-ui",
type: "ui",
emit: (t, r) => {
this.dispatchEvent(new CustomEvent(t, { detail: r, bubbles: !0 }));
},
on: (t, r) => () => {
}
};
this.formManager.setupComponentBridge(e);
}
/**
* Set up event forwarding from FormManager to UI
*/
setupFormManagerEvents() {
this.formManager.on("data-change", ({ formData: e }) => {
this._isUpdatingUI || (this._isUpdatingUI = !0, this.data = e, this._isUpdatingUI = !1);
}), this.formManager.on("validation-error", ({ field: e, errors: t }) => {
this.dispatchEvent(new CustomEvent("validation-error", {
detail: { field: e, errors: t },
bubbles: !0
}));
}), this.formManager.on("form-submit", ({ formData: e }) => {
this.dispatchEvent(new CustomEvent("form-submit", {
detail: { formData: e },
bubbles: !0
}));
});
}
/**
* Public API methods to expose FormManager functionality
*/
/**
* Validate the current form data
*/
validate() {
return !this.formManager || !this._schema ? { isValid: !0, errors: [] } : this.formManager.validate();
}
/**
* Get form schema
*/
getSchema() {
return this._schema;
}
/**
* Reset form to default values
*/
reset() {
this.formManager && this._schema && (this.formManager.reset(), this.data = this.formManager.getData());
}
/**
* Check if form has unsaved changes
*/
isDirty() {
return !this.formManager || !this._schema ? !1 : this.formManager.isDirty();
}
async connectedCallback() {
var e;
super.connectedCallback();
try {
await G.initialize();
} catch (t) {
console.debug("Extension system initialization:", t);
}
!this.content && ((e = this.textContent) != null && e.trim()) && (this.content = this.textContent.trim(), this.textContent = "");
}
render() {
if (!this.content || !this.content.trim())
return v`<div class="error">No Formdown content provided</div>`;
try {
this.formManager.parse(this.content), this._schema = this.formManager.getSchema(), this.data && Object.keys(this.data).length > 0 && this.formManager.updateData(this.data);
const e = this.formManager.renderToTemplate({
container: this
});
return !e.html || !e.html.trim() ? v`<div class="error">Generated HTML is empty</div>` : v`<div id="content-container"></div>`;
} catch (e) {
const t = e instanceof Error ? e.message : String(e);
return v`<div class="error">Error rendering content: ${t}</div>`;
}
}
// Override firstUpdated to set innerHTML after the initial render
firstUpdated() {
this.updateContent(), setTimeout(() => {
this.syncUIFromData();
}, 0);
}
// Override updated to update content when properties change
updated(e) {
super.updated(e), e.has("content") && (this.updateContent(), setTimeout(() => {
this.syncUIFromData();
}, 0)), e.has("data") && this.syncUIFromData();
}
updateContent() {
var e, t;
if (!(!this.content || !this.content.trim()))
try {
const r = (e = this.shadowRoot) == null ? void 0 : e.querySelector("#content-container");
if (!r)
return;
this.formManager.parse(this.content), this._schema = this.formManager.getSchema(), this.data && Object.keys(this.data).length > 0 && this.formManager.updateData(this.data);
const i = this.formManager.renderToTemplate({
container: this
});
if (!i.html || i.html.trim() === "") {
r.innerHTML = '<div class="error">FormManager returned empty HTML</div>';
return;
}
r.innerHTML = i.html, this.injectExtensionAssets(r), this.setupFieldHandlers(r);
} catch (r) {
const i = (t = this.shadowRoot) == null ? void 0 : t.querySelector("#content-container");
if (i) {
const o = r instanceof Error ? r.message : String(r);
i.innerHTML = `<div class="error">Error: ${o}</div>`;
}
}
}
/**
* Inject extension styles and scripts into the component
*/
injectExtensionAssets(e) {
try {
const t = this.getUsedFieldTypes(e), r = y.getFieldTypeRegistry().getStylesForTypes(t);
r && this.injectStyles(r);
const i = y.getFieldTypeRegistry().getScriptsForTypes(t);
i && this.injectScripts(i);
} catch (t) {
console.debug("Extension asset injection failed:", t);
}
}
/**
* Get list of field types used in the rendered form
*/
getUsedFieldTypes(e) {
const t = /* @__PURE__ */ new Set();
return e.querySelectorAll("[data-field-type]").forEach((o) => {
const s = o.getAttribute("data-field-type");
s && t.add(s);
}), e.querySelectorAll('[class*="formdown-"][class*="-field"]').forEach((o) => {
const n = o.className.match(/formdown-(\w+)-field/);
n && t.add(n[1]);
}), Array.from(t);
}
/**
* Inject CSS styles into the shadow DOM
*/
injectStyles(e) {
var r, i;
if (!e.trim()) return;
const t = (r = this.shadowRoot) == null ? void 0 : r.querySelector("#extension-styles");
if (t)
t.textContent = e;
else {
const o = document.createElement("style");
o.id = "extension-styles", o.textContent = e, (i = this.shadowRoot) == null || i.appendChild(o);
}
}
/**
* Inject JavaScript into the document (global scope for form interactions)
*/
injectScripts(e) {
if (!e.trim()) return;
const t = "formdown-extension-scripts";
if (document.getElementById(t))
return;
const r = document.createElement("script");
r.id = t, r.textContent = e, document.head.appendChild(r);
}
setupFieldHandlers(e) {
this.fieldRegistry.clear();
const t = e.querySelectorAll('input:not([type="radio"]):not([type="checkbox"]), [contenteditable="true"]');
this.setupKeyboardNavigation(t), e.querySelectorAll('input, textarea, select, [contenteditable="true"]').forEach((i) => {
const o = i, s = this.getFieldName(o);
if (s) {
this.registerField(s, o);
const n = this.data[s];
if (n !== void 0)
this.setElementValue(o, n);
else {
const a = this.getSchemaDefaultValue(s);
a !== void 0 && this.updateDataReactively(s, a, o);
}
this.setupFieldEventHandlers(o, s), this.setupFieldSpecificBehaviors(o);
} else o.id && o.id.includes("_other_input") && this.setupOtherInputHandlers(o);
});
}
setupKeyboardNavigation(e) {
e.forEach((t, r) => {
t.addEventListener("keydown", (i) => {
const o = i;
if (o.key === "Enter") {
if (o.preventDefault(), t.tagName.toLowerCase() === "textarea")
return;
const s = r + 1;
s < e.length && e[s].focus();
}
});
});
}
setupFieldEventHandlers(e, t) {
const r = (o) => {
this.formManager.handleUIEvent(o, this.domBinder);
}, i = {
querySelector: (o) => {
var s;
return (s = this.shadowRoot) == null ? void 0 : s.querySelector(o);
},
querySelectorAll: (o) => {
var s;
return Array.from(((s = this.shadowRoot) == null ? void 0 : s.querySelectorAll(o)) || []);
}
};
this.domBinder.bindFieldToElement(t, e, i), e.hasAttribute("contenteditable") ? e.addEventListener("input", r) : (e.addEventListener("input", r), e.addEventListener("change", r));
}
setupFieldSpecificBehaviors(e) {
e.hasAttribute("contenteditable") && this.setupContentEditableBehaviors(e);
}
setupOtherInputHandlers(e) {
const t = e.id.replace("_other_input", ""), r = () => {
var s, n, a;
const i = (s = this.shadowRoot) == null ? void 0 : s.querySelector(`#${t}_other_radio`), o = (n = this.shadowRoot) == null ? void 0 : n.querySelector(`#${t}_other_checkbox`);
if (i)
i.checked = !0, this.updateDataReactively(t, e.value.trim(), e);
else if (o) {
o.checked = !0;
const c = (a = this.shadowRoot) == null ? void 0 : a.querySelectorAll(`input[type="checkbox"][name="${t}"]`), f = [];
c.forEach((m) => {
if (m.checked)
if (m.value === "" && m.id.includes("_other_checkbox")) {
const M = e.value.trim();
M && f.push(M);
} else m.value !== "" && f.push(m.value);
}), this.updateDataReactively(t, f, e);
}
};
e.addEventListener("input", r), e.addEventListener("change", r);
}
setupContentEditableBehaviors(e) {
var i;
const t = e.dataset.placeholder;
((i = e.textContent) == null ? void 0 : i.trim()) === t && (e.textContent = ""), e.addEventListener("focus", () => {
var o;
if (((o = e.textContent) == null ? void 0 : o.trim()) === t && (e.textContent = ""), this.selectOnFocus) {
const s = window.getSelection(), n = document.createRange();
n.selectNodeContents(e), s == null || s.removeAllRanges(), s == null || s.addRange(n);
}
}), e.addEventListener("blur", () => {
var o;
(o = e.textContent) != null && o.trim() || (e.textContent = t || "");
}), e.dataset.fieldType === "email" && e.addEventListener("input", () => {
var s;
const o = ((s = e.textContent) == null ? void 0 : s.trim()) || "";
o && !o.includes("@") ? e.style.color = "#dc2626" : e.style.color = "#1e40af";
});
}
// Reactive data management - delegated to FormManager
updateDataReactively(e, t, r) {
this._isUpdatingUI || (this.formManager.setFieldValue(e, t), this.data = { ...this.data, [e]: t }, this.syncUIFromData(e, r), this.emitFieldEvents(e, t));
}
syncUIFromData(e, t) {
if (!this._isUpdatingUI) {
this._isUpdatingUI = !0;
try {
const r = {
querySelector: (i) => {
var o;
return (o = this.shadowRoot) == null ? void 0 : o.querySelector(i);
},
querySelectorAll: (i) => {
var o;
return Array.from(((o = this.shadowRoot) == null ? void 0 : o.querySelectorAll(i)) || []);
}
};
if (this.domBinder.syncFormData(this.data, r), e) {
const i = this.data[e] ?? "", o = this.fieldRegistry.get(e);
o == null || o.forEach((s) => {
s !== t && this.setElementValue(s, i);
});
}
} finally {
this._isUpdatingUI = !1;
}
}
}
// Universal element value setter - delegated to Core FieldProcessor
setElementValue(e, t) {
const r = {
querySelector: (s) => {
var n;
return (n = this.shadowRoot) == null ? void 0 : n.querySelector(s);
},
querySelectorAll: (s) => {
var n;
return Array.from(((n = this.shadowRoot) == null ? void 0 : n.querySelectorAll(s)) || []);
}
}, i = this.fieldProcessor.getFieldType(e);
if (!this.fieldProcessor.setFieldValue(e, t, i, r) && e instanceof HTMLElement) {
if (e.hasAttribute("contenteditable")) {
const s = Array.isArray(t) ? t.join(", ") : String(t);
e.textContent !== s && (e.textContent = s);
} else if (e instanceof HTMLInputElement) {
if (e.type === "checkbox" && typeof t == "boolean")
e.checked = t;
else if (e.type !== "checkbox" && e.type !== "radio") {
const s = Array.isArray(t) ? t.join(", ") : String(t);
e.value !== s && (e.value = s);
}
} else if (e instanceof HTMLTextAreaElement || e instanceof HTMLSelectElement) {
const s = Array.isArray(t) ? t.join(", ") : String(t);
e.value !== s && (e.value = s);
}
}
}
// Universal field name extractor
getFieldName(e) {
return e instanceof HTMLInputElement || e instanceof HTMLTextAreaElement || e instanceof HTMLSelectElement ? e.name || e.id || null : e.dataset.fieldName || e.id || null;
}
// Universal field value extractor - delegated to FieldProcessor
// These methods were removed - functionality delegated to Core modules
// Extract initial value from HTML attributes (for HTML-standard value initialization)
// Note: This method is preserved for compatibility but not actively used
// since we now rely on schema values instead of HTML attributes
// @ts-ignore - preserved for test compatibility
getElementInitialValue(e) {
var t, r;
if (e instanceof HTMLInputElement)
return e.type === "checkbox" ? e.hasAttribute("checked") ? e.value === "true" ? !0 : e.value : null : e.type === "radio" ? e.hasAttribute("checked") ? e.value : null : e.hasAttribute("value") && e.getAttribute("value") ? e.getAttribute("value") || "" : null;
if (e instanceof HTMLTextAreaElement) {
const i = (t = e.textContent) == null ? void 0 : t.trim();
return i || null;
} else if (e instanceof HTMLSelectElement) {
const i = e.querySelector("option[selected]");
return i ? i.value : null;
} else if (e.hasAttribute("contenteditable")) {
const i = e.getAttribute("data-value");
if (i)
return i;
const o = (r = e.textContent) == null ? void 0 : r.trim();
return o && o !== e.dataset.placeholder ? o : null;
}
return null;
}
// Register field in the universal registry
registerField(e, t) {
this.fieldRegistry.has(e) || this.fieldRegistry.set(e, /* @__PURE__ */ new Set()), this.fieldRegistry.get(e).add(t);
}
// Emit standardized events
emitFieldEvents(e, t) {
const r = this.getFormData();
this.dispatchEvent(new CustomEvent("formdown-change", {
detail: { fieldName: e, value: t, formData: r },
bubbles: !0
})), this.dispatchEvent(new CustomEvent("formdown-data-update", {
detail: { formData: r },
bubbles: !0
}));
}
// Universal field synchronization method - expected by tests
syncFieldValue(e, t) {
this.data = { ...this.data, [e]: t }, this.emitFieldEvents(e, t);
}
// Update form data method - expected by tests
updateFormData(e, t) {
this.data = { ...this.data, [e]: t }, this.emitFieldEvents(e, t);
}
// Get form data programmatically - use reactive data as source of truth
getFormData() {
return { ...this.data };
}
// Get default values from schema (keeping for backward compatibility)
getDefaultValues() {
if (!this._schema) return {};
const e = {};
return Object.entries(this._schema).forEach(([t, r]) => {
r.value !== void 0 && (e[t] = r.value);
}), e;
}
// Get schema default value for a specific field
getSchemaDefaultValue(e) {
if (!(!this._schema || !this._schema[e]))
return this._schema[e].value;
}
clearValidationStates() {
var t;
const e = (t = this.shadowRoot) == null ? void 0 : t.querySelector("#content-container");
e && (e.querySelectorAll(".field-error, .field-valid").forEach((r) => {
r.classList.remove("field-error", "field-valid");
}), e.querySelectorAll(".validation-error-message").forEach((r) => {
r.remove();
}));
}
// Reset form method
resetForm() {
var r;
const e = this.getFormId(), t = (r = this.shadowRoot) == null ? void 0 : r.querySelector(`#${e}`);
t && t.reset(), this.data = {}, this.clearValidationStates();
}
};
d.styles = P`
:host {
display: block;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.5;
color: var(--theme-text-primary, #1f2937);
background: var(--theme-bg-primary, #ffffff);
max-width: 100%;
box-sizing: border-box;
overflow-y: auto;
}
* {
box-sizing: border-box;
}
.formdown-form {
max-width: 100%;
width: 100%;
margin: 0;
padding: 0;
display: none; /* Hidden form for form attribute reference */
}
.formdown-field {
margin-bottom: 1.5rem;
max-width: 100%;
}
/* Add spacing between consecutive field containers */
.formdown-field-container {
margin-bottom: 0.75rem;
}
.formdown-field-container:last-child {
margin-bottom: 0;
}
label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: var(--theme-text-primary, #374151);
font-size: 0.875rem;
line-height: 1.25;
}
input, textarea, select {
width: 100%;
max-width: 100%;
padding: 0.75rem;
border: 1px solid var(--theme-border, #d1d5db);
border-radius: 0.5rem;
font-size: 1rem;
font-family: inherit;
line-height: 1.5;
transition: all 0.15s ease-in-out;
background-color: var(--theme-bg-primary, #ffffff);
color: var(--theme-text-primary, #1f2937);
}
input:focus, textarea:focus, select:focus {
outline: none;
border-color: var(--theme-accent, #3b82f6);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
background-color: var(--theme-bg-primary, #ffffff);
}
input:hover, textarea:hover, select:hover {
border-color: var(--theme-text-secondary, #9ca3af);
}
input[type="radio"], input[type="checkbox"] {
width: auto;
max-width: none;
margin-right: 0.5rem;
margin-bottom: 0;
}
textarea {
min-height: 6rem;
resize: vertical;
}
select {
cursor: pointer;
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
background-position: right 0.5rem center;
background-repeat: no-repeat;
background-size: 1.5em 1.5em;
padding-right: 2.5rem;
}
fieldset {
border: 1px solid var(--theme-border, #d1d5db);
border-radius: 0.5rem;
padding: 1.25rem;
margin: 0 0 1.5rem 0;
max-width: 100%;
background-color: var(--theme-bg-primary, #ffffff);
}
legend {
font-weight: 600;
color: var(--theme-text-primary, #374151);
padding: 0 0.75rem;
font-size: 0.875rem;
}
fieldset label {
display: flex;
align-items: center;
margin-bottom: 0.75rem;
font-weight: normal;
font-size: 0.875rem;
} /* Enhanced inline formdown-field elements with contentEditable */
formdown-field,
[contenteditable="true"]:not(textarea) {
display: inline;
min-width: 60px;
max-width: 200px;
padding: 0.125rem 0.25rem;
border: 1px solid transparent;
background-color: var(--theme-bg-secondary, rgba(239, 246, 255, 0.6));
border-radius: 0.125rem;
font-style: normal;
color: inherit;
font-size: inherit;
line-height: inherit;
font-family: inherit;
font-weight: inherit;
cursor: text;
outline: none;
transition: all 0.15s ease-in-out;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: baseline;
box-decoration-break: clone;
}
[contenteditable="true"]:not(textarea):hover {
background-color: var(--theme-bg-secondary, rgba(219, 234, 254, 0.8));
border-color: var(--theme-border, rgba(147, 197, 253, 0.5));
}
[contenteditable="true"]:not(textarea):focus {
background-color: var(--theme-bg-primary, rgba(255, 255, 255, 0.9));
border-color: var(--theme-accent, #3b82f6);
box-shadow: 0 0 0 1px rgba(59, 130, 246, 0.2);
color: var(--theme-accent, #1e40af);
}
[contenteditable="true"]:not(textarea):empty::before {
content: attr(data-placeholder);
color: var(--theme-text-secondary, #94a3b8);
font-style: italic;
opacity: 0.7;
}
/* Enhanced typography for markdown content */
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: 1rem;
color: var(--theme-text-primary, #1f2937);
font-weight: 600;
line-height: 1.25;
}
h1 {
font-size: 2.25rem;
font-weight: 700;
}
h2 {
font-size: 1.875rem;
font-weight: 600;
}
h3 {
font-size: 1.5rem;
font-weight: 600;
}
h4 {
font-size: 1.25rem;
font-weight: 600;
}
h5 {
font-size: 1.125rem;
font-weight: 600;
}
h6 {
font-size: 1rem;
font-weight: 600;
}
p {
margin-bottom: 1rem;
line-height: 1.7;
color: var(--theme-text-secondary, #4b5563);
}
/* Responsive design */
(max-width: 768px) {
:host {
font-size: 0.875rem;
}
input, textarea, select {
padding: 0.625rem;
font-size: 0.875rem;
}
h1 { font-size: 1.875rem; }
h2 { font-size: 1.5rem; }
h3 { font-size: 1.25rem; }
}
.error {
color: var(--theme-error, #dc2626);
font-size: 0.875rem;
margin-top: 0.5rem;
display: block;
}
/* Field validation styles */
.field-error {
border-color: var(--theme-error, #dc2626) !important;
box-shadow: 0 0 0 1px rgba(220, 38, 38, 0.1) !important;
}
.field-error:focus {
border-color: var(--theme-error, #dc2626) !important;
box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.1) !important;
}
.validation-error-message {
color: var(--theme-error, #dc2626);
font-size: 0.75rem;
margin-top: 0.25rem;
display: block;
font-weight: 500;
}
/* Success state */
.field-valid {
border-color: #10b981 !important;
box-shadow: 0 0 0 1px rgba(16, 185, 129, 0.1) !important;
}
.submit-button {
background-color: #3b82f6;
color: white;
padding: 0.75rem 1.5rem;
border: none;
border-radius: 0.5rem;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: all 0.15s ease-in-out;
margin-top: 1.5rem;
width: auto;
max-width: 100%;
}
.submit-button:hover {
background-color: #2563eb;
transform: translateY(-1px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
}
.submit-button:disabled {
background-color: #9ca3af;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.submit-button:active {
transform: translateY(0);
}
/* Ensure content doesn't overflow */
#content-container {
max-width: 100%;
overflow-wrap: break-word;
word-wrap: break-word;
} /* Radio and checkbox groups */
.radio-group, .checkbox-group {
display: flex;
gap: 0.75rem;
flex-wrap: wrap;
}
/* Inline layout (default) */
.radio-group.inline, .checkbox-group.inline {
flex-direction: row;
align-items: center;
}
/* Vertical layout */
.radio-group.vertical, .checkbox-group.vertical {
flex-direction: column;
align-items: flex-start;
}
.formdown-option-label {
display: flex;
align-items: center;
margin-bottom: 0;
font-weight: normal;
cursor: pointer;
font-size: 0.875rem;
white-space: nowrap;
}
.formdown-option-label input {
margin-right: 0.5rem;
margin-bottom: 0;
}
.formdown-option-label span {
user-select: none;
}
/* Legacy support for old structure */
.radio-group label, .checkbox-group label {
display: flex;
align-items: center;
margin-bottom: 0;
font-weight: normal;
cursor: pointer;
}/* Better spacing for form elements */
.formdown-form > * + * {
margin-top: 1rem;
}
`;
u([
p()
], d.prototype, "content", 2);
u([
p({ type: Boolean, attribute: "select-on-focus" })
], d.prototype, "selectOnFocus", 2);
u([
p({ attribute: "form-id" })
], d.prototype, "formId", 2);
u([
p({ type: Boolean, attribute: "show-submit-button" })
], d.prototype, "showSubmitButton", 2);
u([
p({ attribute: "submit-text" })
], d.prototype, "submitText", 2);
u([
p({ type: Object })
], d.prototype, "data", 1);
d = u([
z("formdown-ui")
], d);
const Z = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
__proto__: null,
get FormdownUI() {
return d;
}
}, Symbol.toStringTag, { value: "Module" })), it = (e, t = {}) => {
const r = document.createElement("formdown-ui");
return t.content && (r.content = t.content), t.formId && (r.formId = t.formId), t.showSubmitButton !== void 0 && (r.showSubmitButton = t.showSubmitButton), t.submitText && (r.submitText = t.submitText), e.appendChild(r), r;
}, ot = () => {
customElements.get("formdown-ui") || Promise.resolve().then(() => Z);
};
export {
d as FormdownUI,
S as UIExtensionSupport,
it as createFormdownUI,
ot as registerFormdownUI,
G as uiExtensionSupport
};