diff --git a/src/main/resources/META-INF/resources/obsidian/livecomponents.js b/src/main/resources/META-INF/resources/obsidian/livecomponents.js index 73b67d3..0942985 100644 --- a/src/main/resources/META-INF/resources/obsidian/livecomponents.js +++ b/src/main/resources/META-INF/resources/obsidian/livecomponents.js @@ -1,3 +1,9 @@ +/** + * Morphdom - Fast DOM diffing/patching (CDN inline) + * @see https://github.com/patrick-steele-idem/morphdom + */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).morphdom=t()}(this,(function(){"use strict";var e,t,n,o,r,i="http://www.w3.org/1999/xhtml",a=typeof document>"u"?void 0:document,l=!!a&&"content"in a.createElement("template"),s=!!a&&a.createRange&&"createContextualFragment"in a.createRange();function c(e){var t=a.createElement("template");return t.innerHTML=e,t.content.childNodes[0]}function u(e){return(l?c:s?function(e){return n||(n=a.createRange()).selectNode(a.body),n.createContextualFragment(e).childNodes[0]}:function(e){return(o||(o=a.createElement("body"))).innerHTML=e,o.childNodes[0]})(e)}function d(e,t){var n,o,r=e.nodeName,i=t.nodeName;return r===i||(n=r.charCodeAt(0),o=i.charCodeAt(0),n<=90&&o>=97?r===i.toUpperCase():o<=90&&n>=97&&i===r.toUpperCase())}function f(e,t,n){e[n]!==t[n]&&(e[n]=t[n],e[n]?e.setAttribute(n,""):e.removeAttribute(n))}var p={OPTION:function(e,t){var n=e.parentNode;if(n){var o=n.nodeName.toUpperCase();"OPTGROUP"===o&&(o=(n=n.parentNode)&&n.nodeName.toUpperCase()),"SELECT"!==o||n.hasAttribute("multiple")||(e.hasAttribute("selected")&&!t.selected&&(e.setAttribute("selected","selected"),e.removeAttribute("selected")),n.selectedIndex=-1)}f(e,t,"selected")},INPUT:function(e,t){f(e,t,"checked"),f(e,t,"disabled"),e.value!==t.value&&(e.value=t.value),t.hasAttribute("value")||e.removeAttribute("value")},TEXTAREA:function(e,t){var n=t.value;e.value!==n&&(e.value=n);var o=e.firstChild;if(o){var r=o.nodeValue;if(r==n||!n&&r==e.placeholder)return;o.nodeValue=n}},SELECT:function(e,t){if(!t.hasAttribute("multiple")){for(var n,o,r=-1,i=0,a=e.firstChild;a;)if("OPTGROUP"===(o=a.nodeName&&a.nodeName.toUpperCase()))a=(n=a).firstChild;else{if("OPTION"===o){if(a.hasAttribute("selected")){r=i;break}i++}!(a=a.nextSibling)&&n&&(a=n.nextSibling,n=null)}e.selectedIndex=r}}};function m(){}function h(e){if(e)return e.getAttribute&&e.getAttribute("id")||e.id}var g=(e=function(e){return function(t,n,o){var a,l,s=o||{};if("string"==typeof n)if("#document"===t.nodeName||"HTML"===t.nodeName||"BODY"===t.nodeName){var c=n;(n=a.createElement("html")).innerHTML=c}else n=u(n);else 11===n.nodeType&&(n=n.firstElementChild);var c=s.getNodeKey||h,d=s.onBeforeNodeAdded||m,f=s.onNodeAdded||m,g=s.onBeforeElUpdated||m,v=s.onElUpdated||m,b=s.onBeforeNodeDiscarded||m,y=s.onNodeDiscarded||m,w=s.onBeforeElChildrenUpdated||m,k=!1!==s.childrenOnly,E=Object.create(null),C=[];function x(e){C.push(e)}function S(e,t){if(1===e.nodeType)for(var n=e.firstChild;n;){var o=void 0;t&&(o=c(n))?x(o):(y(n),n.firstChild&&S(n,t)),n=n.nextSibling}}function A(e,t,n){!1!==b(e)&&(t&&t.removeChild(e),y(e),S(e,n))}function N(e){f(e);for(var t=e.firstChild;t;){var n=t.nextSibling,o=c(t);if(o){var r=E[o];r&&d(t,r)?(t.parentNode.replaceChild(r,t),T(r,t)):N(t)}else N(t);t=n}}function T(t,n,o){var r,a=c(n);if(a&&delete E[a],!o){if(!1===g(t,n))return;if(t.actualize&&(t=t.actualize(t.ownerDocument||a)),e(t,n),v(t),!1===w(t,n))return}"TEXTAREA"!==t.nodeName?function(e,t,n,o,r){var a,l,s,u,f,m=t.firstChild,h=e.firstChild;e:for(;m;){for(u=m.nextSibling,a=c(m);h;){if(s=h.nextSibling,m.isSameNode&&m.isSameNode(h)){m=u,h=s;continue e}var g=c(h),v=l,b=m.nodeType,y=void 0;if(b===h.nodeType&&(1===b?(a?a!==g&&((f=E[a])?s===f?y=!1:(e.insertBefore(f,h),g?x(g):A(h,e,!0),h=f):y=!1):g&&(y=!1),(y=!1!==y&&d(h,m))&&T(h,m)):3!==b&&8!==b||(y=!0,h.nodeValue!==m.nodeValue&&(h.nodeValue=m.nodeValue))),y){m=u,h=s;continue e}g?x(g):A(h,e,!0),h=s}if(a&&(f=E[a])&&d(f,m))e.appendChild(f),T(f,m);else{var w=r(m);!1!==w&&(w&&(m=w),m.actualize&&(m=m.actualize(e.ownerDocument||i)),e.appendChild(m),N(m))}m=u,h=s}!function(e,t,n){for(;t;){var o=t.nextSibling;(n=c(t))?x(n):A(t,e,!0),t=o}}(e,h,0);var k=p[e.nodeName];k&&k(e,o)}(t,n,0,0,d):p.TEXTAREA(t,n)}(l=t).nodeType;return function(e){for(var t=e.firstChild;t;){var n=c(t);n&&(E[n]=t),t=t.nextSibling}}(l),function(e,t,n){1===t.nodeType&&(n=n||a,e===a?e=i:"string"==typeof e&&(e=n.createElement(e)),T(e,t,k));return e}(t,n,s.document||a)}}.call(t={},function(e,t){var n,o,r,i,a=t.attributes;if(11!==t.nodeType&&11!==e.nodeType){for(var l=a.length-1;l>=0;l--)o=(n=a[l]).name,r=n.namespaceURI,i=n.value,r?(o=n.localName||o,e.getAttributeNS(r,o)!==i&&("xmlns"===n.prefix&&(o=n.name),e.setAttributeNS(r,o,i))):e.getAttribute(o)!==i&&e.setAttribute(o,i);for(var s=e.attributes,c=s.length-1;c>=0;c--)o=(n=s[c]).name,(r=n.namespaceURI)?(o=n.localName||o,t.hasAttributeNS(r,o)||e.removeAttributeNS(r,o)):t.hasAttribute(o)||e.removeAttribute(o)}}),t.exports);return g})); + /** * Obsidian LiveComponents - Client-side reactive component system * @@ -5,7 +11,7 @@ * real-time validation, and seamless state synchronization. * * @class ObsidianComponents - * @version 1.0.0 + * @version 1.1.0 — Now powered by morphdom for surgical DOM patching. * * Features: * - live:click - Click event handling with server actions @@ -79,9 +85,6 @@ class ObsidianComponents { const componentName = placeholder.getAttribute('live:lazy'); const propsAttr = placeholder.getAttribute('live:props'); - // Keep existing innerHTML as skeleton — if empty, nothing is shown - const skeleton = placeholder.innerHTML.trim(); - let url = `/obsidian/components/mount?component=${encodeURIComponent(componentName)}`; if (propsAttr) { url += `&props=${encodeURIComponent(propsAttr)}`; @@ -164,22 +167,23 @@ class ObsidianComponents { * @param {string} componentId - Component identifier */ attachModelBindings(element, componentId) { - // Find all inputs with live:model const modelInputs = element.querySelectorAll('[live\\:model]'); modelInputs.forEach(input => { + // Skip inputs that already have bindings attached + if (input._liveModelBound) return; + input._liveModelBound = true; + const fieldName = input.getAttribute('live:model'); const debounceTime = parseInt(input.getAttribute('live:debounce')) || 300; const updateOnBlur = input.hasAttribute('live:blur'); const updateOnEnter = input.hasAttribute('live:enter'); if (updateOnBlur) { - // Update only on blur input.addEventListener('blur', () => { this.updateModel(componentId, fieldName, input.value); }); } else if (updateOnEnter) { - // Update only on Enter key input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -187,7 +191,6 @@ class ObsidianComponents { } }); } else { - // Update on input with debounce (default) const debouncedUpdate = this.debounce((value) => { this.updateModel(componentId, fieldName, value); }, debounceTime); @@ -204,24 +207,22 @@ class ObsidianComponents { * Prevents default form submission and calls server action. * Clears validation errors before submission. * - * Usage: