@fluffjs/fluff 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/bundle.min.js +1 -0
  2. package/decorators/Component.d.ts +0 -8
  3. package/decorators/Component.js +0 -11
  4. package/decorators/Input.d.ts +1 -1
  5. package/decorators/InputOutputHelper.d.ts +2 -1
  6. package/decorators/InputOutputHelper.js +3 -1
  7. package/decorators/Output.d.ts +1 -1
  8. package/decorators/Pipe.d.ts +1 -0
  9. package/decorators/Pipe.js +1 -1
  10. package/decorators/Reactive.d.ts +2 -1
  11. package/decorators/Reactive.js +1 -1
  12. package/index.d.ts +2 -1
  13. package/index.js +1 -1
  14. package/interfaces/FluffHostElement.d.ts +13 -3
  15. package/interfaces/ReactiveOptions.d.ts +6 -0
  16. package/interfaces/ReactiveOptions.js +1 -0
  17. package/package.json +1 -1
  18. package/runtime/FluffBase.d.ts +11 -1
  19. package/runtime/FluffBase.js +50 -17
  20. package/runtime/FluffElementImpl.d.ts +9 -21
  21. package/runtime/FluffElementImpl.js +48 -149
  22. package/runtime/ForController.js +1 -2
  23. package/runtime/IfController.js +1 -2
  24. package/runtime/MarkerConfigGuards.d.ts +0 -2
  25. package/runtime/MarkerConfigGuards.js +0 -3
  26. package/runtime/MarkerController.d.ts +1 -9
  27. package/runtime/MarkerController.js +28 -144
  28. package/runtime/MarkerManager.d.ts +2 -1
  29. package/runtime/MarkerManager.js +1 -2
  30. package/runtime/MarkerManagerInterface.d.ts +3 -1
  31. package/runtime/SwitchController.js +1 -2
  32. package/runtime/TextController.d.ts +0 -1
  33. package/runtime/TextController.js +5 -19
  34. package/runtime/tests/TestChildTasksListComponent.js +2 -2
  35. package/runtime/tests/TestForComponent.js +2 -2
  36. package/runtime/tests/TestForReinsertBindsInputParentComponent.js +2 -2
  37. package/runtime/tests/TestForTextMarkerCollisionNoTrackParentComponent.js +2 -2
  38. package/runtime/tests/TestForTextMarkerCollisionParentComponent.js +2 -2
  39. package/runtime/tests/TestForUnsubscribeNestedParentComponent.js +2 -2
  40. package/runtime/tests/TestGetterReactivityComponent.js +2 -2
  41. package/runtime/tests/TestHarness.d.ts +9 -0
  42. package/runtime/tests/TestHarness.js +29 -0
  43. package/runtime/tests/TestIfReinsertBindsInputChildComponent.js +2 -2
  44. package/runtime/tests/TestIfReinsertBindsInputParentComponent.js +2 -2
  45. package/runtime/tests/TestIfUnsubscribeNestedParentComponent.js +2 -2
  46. package/runtime/tests/TestInterpolationNestedPropertyComponentBase.js +2 -2
  47. package/runtime/tests/TestLateDefineForComponent.js +2 -2
  48. package/runtime/tests/TestNullInputTextComponent.js +2 -2
  49. package/runtime/tests/TestOutputBindingParentComponent.js +2 -2
  50. package/runtime/tests/TestParentBindsTasksComponent.js +2 -2
  51. package/runtime/tests/TestSwitchReinsertBindsInputChildComponent.js +2 -2
  52. package/runtime/tests/TestSwitchReinsertBindsInputParentComponent.js +2 -2
  53. package/runtime/tests/TestSwitchUnsubscribeNestedParentComponent.js +2 -2
  54. package/runtime/tests/TestTemplateNestedMarkersComponent.js +2 -2
  55. package/runtime/tests/TestUnsubscribeNestedGrandchildComponent.js +2 -2
  56. package/runtime/tests/createPipeUnwrapTestComponent.js +2 -2
  57. package/runtime/tests/createTestInterpolationPipeComponent.js +2 -2
  58. package/runtime/tests/createTestInterpolationPipeWithArgsComponent.js +2 -2
  59. package/utils/Property.d.ts +8 -2
  60. package/utils/Property.js +48 -8
  61. package/utils/DomUtils.d.ts +0 -3
  62. package/utils/DomUtils.js +0 -6
package/bundle.min.js ADDED
@@ -0,0 +1 @@
1
+ var V=new Map;function N(a){return function(t){let e={...a,inputs:new Map,outputs:new Map};return V.set(t,e),t}}function $(a){return V.get(a)}var C=class{static getOrCreateArray(t,e){let n=Reflect.get(t,e);if(this.isTypedArray(n))return n;let r=[];return Reflect.set(t,e,r),r}static isTypedArray(t){return Array.isArray(t)}};function H(a){return function(t,e){let n=t.constructor;C.getOrCreateArray(n,"__hostBindings").push({property:String(e),hostProperty:a})}}function W(a){return function(t,e,n){let r=t.constructor;return C.getOrCreateArray(r,"__hostListeners").push({method:String(e),event:a}),n}}function z(a){return typeof a=="function"}function I(a){return function(t){return function(e,n){let{constructor:r}=e;if(!z(r))return;let s=$(r);if(s){let i=typeof t=="string"?t:t?.alias??String(n);s[a].set(String(n),i)}}}}var D=I("inputs");function j(a){return(t,e,n)=>{}}var q=I("outputs");var B=new Map;function R(a){let t=B.get(a);if(!t)return;let e=new t;return(n,...r)=>e.transform(n,...r)}function J(a){return function(t){return t.__pipeName=a,B.set(a,t),t}}function G(a){return(t,e)=>{}}function K(a){return function(t,e){let n=t.constructor;C.getOrCreateArray(n,"__viewChildren").push({property:String(e),selector:a})}}function X(...a){return function(t,e,n){let r=n.value;return typeof r=="function"&&Reflect.set(t,`__watch_${String(e)}`,r),n}}var _;(function(a){a[a.Any=0]="Any",a[a.Inbound=1]="Inbound",a[a.Outbound=2]="Outbound"})(_||(_={}));var g=class{callbacks=new Set;emit(t){for(let e of this.callbacks)try{let n=e(t);n instanceof Promise&&n.catch(r=>{console.error(r)})}catch(n){console.error(n)}}async emitAsync(t){for(let e of this.callbacks)try{await e(t)}catch(n){console.error(n)}}subscribe(t){return this.callbacks.add(t),{unsubscribe:()=>{this.callbacks.delete(t)}}}subscribeOnce(t){let e=async n=>{this.callbacks.delete(e),await t(n)};return this.callbacks.add(e),{unsubscribe:()=>{this.callbacks.delete(e)}}}};function T(a){let t=new WeakSet;return JSON.stringify(a,(e,n)=>{if(typeof n=="object"&&n!==null){if(t.has(n))return"[Circular]";t.add(n)}return n})}var p=class a{onChange=new g;onInboundChange=new g;onOutboundChange=new g;onMetadataChange=new g;value;committed=!0;_isChanging=!1;_parentProperty;_parentSubscription;_commitTriggerSub;_pendingCommitValue;_options;constructor(t){this.isOptionsObject(t)?(this._options=t,t.initialValue!==void 0&&(this.value=t.initialValue),t.commitTrigger!==void 0&&this.setCommitTrigger(t.commitTrigger)):this.value=t}prop(){return this._parentProperty}setValue(t,e=!1,n=!0){if(t instanceof a){let s=this._options?.linkHandler;s&&s(t),this.linkToParent(t);return}if(this._isChanging){let s=this._options?.propertyName;console.error((s?s+": ":"")+"Binding loop detected: setValue called while change is in progress");return}if(!(!(t!==this.value&&T(t)!==T(this.value))&&!(n&&!this.committed))){this._isChanging=!0;try{this.value=t,this.onChange.emit(t),n||(this.committed=!1),e?(this.committed=!0,this.onInboundChange.emit(t)):n&&(this.committed=!0,this.value!==void 0&&this.onOutboundChange.emit(this.value)),!e&&this._parentProperty&&this.pushToParent(t)}finally{this._isChanging=!1}}}linkToParent(t){this.clearParentLink();let e=t;for(;e._parentProperty;)e=e._parentProperty;this._parentProperty=e;let n=this._options?.direction??_.Any;this._parentSubscription=e.subscribe(n,s=>{this.setValueInternal(s,!0)});let r=e.getValue();r!==null&&this.setValueInternal(r,!0)}setValueInternal(t,e){if(!(this._isChanging||!(t!==this.value&&T(t)!==T(this.value)))){this._isChanging=!0;try{this.value=t,this.onChange.emit(t),e&&this.onInboundChange.emit(t)}finally{this._isChanging=!1}}}pushToParent(t){if(!this._parentProperty)return;let e=this._options?.commitTrigger;if(e){if(!!!e.getValue()){this._pendingCommitValue={value:t},this._parentProperty.setValue(t,!1,!1);return}this._pendingCommitValue=void 0}let n=this._parentSubscription;this._parentSubscription=void 0,n?.unsubscribe(),this._parentProperty.setValue(t);let r=this._options?.direction??_.Any;this._parentSubscription=this._parentProperty.subscribe(r,s=>{this.setValueInternal(s,!0)})}clearParentLink(){this._parentSubscription&&(this._parentSubscription.unsubscribe(),this._parentSubscription=void 0),this._parentProperty=void 0}setCommitTrigger(t){this._commitTriggerSub&&(this._commitTriggerSub.unsubscribe(),this._commitTriggerSub=void 0),this._options??={},this._options.commitTrigger=t,this._commitTriggerSub=t.onChange.subscribe(e=>{if(!e||!this._parentProperty||!this._pendingCommitValue)return;let n=this._pendingCommitValue.value;this._pendingCommitValue=void 0,this._parentProperty.setValue(n,!1,!0)})}reset(){this.clearParentLink()}triggerChange(t=_.Any){this.value!==void 0&&(this.onChange.emit(this.value),t==_.Outbound&&this.onOutboundChange.emit(this.value),t==_.Inbound&&this.onOutboundChange.emit(this.value))}subscribe(t,e){return t==_.Inbound?this.onInboundChange.subscribe(n=>{e(n)}):t==_.Outbound?this.onOutboundChange.subscribe(n=>{e(n)}):this.onChange.subscribe(n=>{e(n)})}getValue(){return this.value===void 0?null:this.value}isOptionsObject(t){return typeof t=="object"&&t!==null&&"initialValue"in t}};var m=class a extends HTMLElement{static __e=[];static __h=[];static __bindings={};static __expressionsReady=!1;static __pendingInitCallbacks=[];static __setExpressionTable(t,e){a.__e=t,a.__h=e,a.__expressionsReady=!0;let n=a.__pendingInitCallbacks.splice(0,a.__pendingInitCallbacks.length);for(let r of n)r()}static __areExpressionsReady(){return a.__expressionsReady}static __addPendingInit(t){a.__pendingInitCallbacks.push(t)}__parentScope;__loopContext={};__baseSubscriptions=[];__getScope(){return{host:this,locals:this.__loopContext,parent:this.__parentScope}}__subscribeToExpression(t,e,n,r){this.__subscribeToExpressionInScope(t,e,n,r)}__evaluateExpr(t,e){let n=this.__getCompiledExprFn(t);try{return n(this,e)}catch{return}}__applyPipesForController(t,e,n){let r=t;r instanceof p&&(r=r.getValue());for(let s of e){let i=this.__getPipeFn(s.name);if(!i){console.warn(`Pipe "${s.name}" not found`);continue}let o=s.argExprIds.map(c=>this.__getCompiledExprFn(c)(this,n));r=i(r,...o)}return r}__processBindingsOnElement(t,e,n){let r=t.getAttribute("data-lid");if(!r)return;let s=this.__getBindingsForLid(r);if(!(!s||s.length===0))for(let i of s)this.__applyBindingWithScope(t,i,e,n)}__getBindingsForLid(t){let e=this.constructor;if(typeof e=="function"){let n=Reflect.get(e,"__bindings");if(this.__isBindingsMap(n))return n[t]}}__isBindingsMap(t){return!(!t||typeof t!="object")}__applyBindingWithScope(t,e,n,r){switch(e.b){case"property":this.__applyPropertyBindingWithScope(t,e,n,r);break;case"event":this.__applyEventBindingWithScope(t,e,n);break;case"two-way":this.__applyTwoWayBindingWithScope(t,e,n,r);break;case"class":this.__applyClassBindingWithScope(t,e,n,r);break;case"style":this.__applyStyleBindingWithScope(t,e,n,r);break;case"ref":break}}__getCompiledExprFn(t){let e=a.__e[t];if(typeof e!="function")throw new Error(`Missing compiled expression function for exprId ${t}`);return e}__getCompiledHandlerFn(t){let e=a.__h[t];if(typeof e!="function")throw new Error(`Missing compiled handler function for handlerId ${t}`);return e}__applyPipes(t,e,n){return this.__applyPipesForController(t,e.map(r=>({name:r.n,argExprIds:r.a})),n)}__getPipeFn(t){}__subscribeToExpressionInScope(t,e,n,r){if(!t)return;let s=i=>{r?r.push(i):this.__baseSubscriptions.push(i)};for(let i of t)if(Array.isArray(i))this.__subscribeToPropertyChain(i,e,n,s);else{let o=this.__getReactivePropFromScope(i,e);o?s(o.onChange.subscribe(n)):!(i in e.locals)&&!(i in e.host)&&console.warn(`Binding dependency "${i}" not found on component ${e.host.constructor.name}`)}}__subscribeToPropertyChain(t,e,n,r){if(t.length===0)return;let[s,...i]=t,o=this.__getReactivePropFromScope(s,e);if(o)if(i.length===0)r(o.onChange.subscribe(n));else{let c=[],u=()=>{for(let f of c)f.unsubscribe();c=[];let l=o.getValue();l!=null&&this.__subscribeToNestedChain(l,i,n,f=>{c.push(f),r(f)}),n()};r(o.onChange.subscribe(u));let h=o.getValue();h!=null&&this.__subscribeToNestedChain(h,i,n,l=>{c.push(l),r(l)})}else if(s in e.locals){let c=e.locals[s];c!=null&&i.length>0&&this.__subscribeToNestedChain(c,i,n,r)}else s in e.host||console.warn(`Binding dependency "${s}" not found on component ${e.host.constructor.name}`)}__subscribeToNestedChain(t,e,n,r){if(e.length===0||t===null||t===void 0)return;let[s,...i]=e,o=Reflect.get(t,s);if(o instanceof p)if(i.length===0)r(o.onChange.subscribe(n));else{let c=[],u=()=>{for(let f of c)f.unsubscribe();c=[];let l=o.getValue();l!=null&&this.__subscribeToNestedChain(l,i,n,f=>{c.push(f),r(f)}),n()};r(o.onChange.subscribe(u));let h=o.getValue();h!=null&&this.__subscribeToNestedChain(h,i,n,l=>{c.push(l),r(l)})}else i.length>0&&o!==null&&o!==void 0&&this.__subscribeToNestedChain(o,i,n,r)}__getReactivePropFromScope(t,e){if(t in e.locals)return;let n=`__${t}`;if(n in e.host){let r=Reflect.get(e.host,n);if(r instanceof p)return r}if(e.parent)return this.__getReactivePropFromScope(t,e.parent)}__setChildProperty(t,e,n){let r=Reflect.get(t,e);r instanceof p?r.setValue(n,!0):t instanceof a?Reflect.set(t,e,n):e in t&&t instanceof HTMLElement?Reflect.set(t,e,this.__unwrap(n)):t.setAttribute(e,String(this.__unwrap(n)))}__unwrap(t){return t instanceof p?t.getValue():t}__applyPropertyBindingWithScope(t,e,n,r){let s=t.tagName.toLowerCase(),i=customElements.get(s)!==void 0,o=()=>{try{if(typeof e.e!="number")throw new Error(`Binding for ${e.n} is missing exprId`);let u=this.__getCompiledExprFn(e.e)(this,n.locals);e.p&&e.p.length>0&&(u=this.__applyPipes(u,e.p,n.locals)),this.__setChildProperty(t,e.n,u)}catch(c){console.error("Property binding error:",c)}};this.__subscribeToExpressionInScope(e.d,n,o,r),e.s&&this.__subscribeToExpressionInScope([e.s],n,o,r),i?t instanceof a?o():this.__whenDefined(s,()=>{t instanceof a?o():console.warn(`Element <${s}> is not a FluffBase instance after whenDefined - binding for "${e.n}" skipped`)}):o()}__applyEventBindingWithScope(t,e,n){let r=`__fluff_event_${e.n}`;if(Reflect.has(t,r))return;if(Reflect.set(t,r,!0),typeof e.h!="number")throw new Error(`Event binding for ${e.n} is missing handlerId`);let s=this.__getCompiledHandlerFn(e.h);t.hasAttribute("x-fluff-component")?this.__applyOutputBinding(t,e.n,s,n):t.addEventListener(e.n,i=>{try{s(this,n.locals,i)}catch(o){console.error("Event binding error:",o)}})}__applyOutputBinding(t,e,n,r){let s=()=>{let i=Reflect.get(t,e);if(i instanceof g){let o=i.subscribe(c=>{try{n(this,r.locals,c)}catch(u){console.error("Output binding error:",u)}});return this.__baseSubscriptions.push(o),!0}return!1};s()||this.__whenDefined(t.tagName.toLowerCase(),()=>{s()})}__whenDefined(t,e){customElements.whenDefined(t).then(e).catch(console.error)}__applyTwoWayBindingWithScope(t,e,n,r){if(this.__applyPropertyBindingWithScope(t,e,n,r),typeof e.t!="string"||e.t.length===0)throw new Error(`Two-way binding for ${e.n} is missing targetProp`);let s=this.__getReactivePropFromScope(e.t,n),i=Reflect.get(t,e.n);if(s&&i instanceof p){let o=i.onChange.subscribe(c=>{s.setValue(c,!0)});r?r.push(o):this.__baseSubscriptions.push(o)}}__applyClassBindingWithScope(t,e,n,r){let s=()=>{try{if(typeof e.e!="number")throw new Error(`Class binding for ${e.n} is missing exprId`);let i=this.__getCompiledExprFn(e.e);this.__unwrap(i(this,n.locals))?t.classList.add(e.n):t.classList.remove(e.n)}catch(i){console.error("Class binding error:",i)}};this.__subscribeToExpressionInScope(e.d,n,s,r),s()}__applyStyleBindingWithScope(t,e,n,r){let s=()=>{try{if(typeof e.e!="number")throw new Error(`Style binding for ${e.n} is missing exprId`);let i=this.__getCompiledExprFn(e.e),o=this.__unwrap(i(this,n.locals));this.__hasStyle(t)&&t.style.setProperty(e.n,String(o))}catch(i){console.error("Style binding error:",i)}};this.__subscribeToExpressionInScope(e.d,n,s,r),s()}__hasStyle(t){return"style"in t}};var L=new Map,Q=0;function A(a){let t=`scope_${Q++}`;return L.set(t,a),t}function F(a){return L.get(a)}var v=/^fluff:(if|for|switch|text|break):(\d+)$/,d=class{id;startMarker;endMarker;subscriptions=[];host;shadowRoot;parentScope;loopContext={};markerManager;constructor(t,e,n,r,s){this.id=t,this.startMarker=e,this.endMarker=n,this.host=r,this.shadowRoot=s}setParentScope(t){this.parentScope=t}setLoopContext(t){this.loopContext=t}setMarkerManager(t){this.markerManager=t}cleanup(){for(let t of this.subscriptions)t.unsubscribe();this.subscriptions.length=0}updateRenderContext(t){}evaluateExpr(t){let e=this.getScope(),n=this.collectLocalsFromScope(e);if(e.host.__evaluateExpr)return e.host.__evaluateExpr(t,n);let r=m.__e[t];if(typeof r=="function")try{return r(e.host,n)}catch{return}}getScope(){if(this.parentScope)return this.parentScope;let t=this.__getFluffElementHost();return t?t.__getScope():{host:this.host,locals:this.loopContext,parent:void 0}}collectLocalsFromScope(t){let e={};return t.parent&&Object.assign(e,this.collectLocalsFromScope(t.parent)),Object.assign(e,t.locals),e}subscribeAndRun(t,e){let n=this.getScope(),r=t.filter(s=>Array.isArray(s)?s.length>0&&!s[0].startsWith("["):!s.startsWith("["));n.host.__subscribeToExpression&&n.host.__subscribeToExpression(r,n,e,this.subscriptions),e()}createChildScope(t){return{host:this.host,locals:t,parent:this.parentScope}}clearContentBetweenMarkersWithCleanup(t){for(let r of t)r.unsubscribe();if(t.length=0,!this.endMarker)return;let e=this.startMarker.parentNode;if(!e)return;let n=this.startMarker.nextSibling;for(;n&&n!==this.endMarker;){let r=n.nextSibling;if(n instanceof Comment){let s=v.exec(n.data);if(s&&this.markerManager?.cleanupController){let i=parseInt(s[2],10);this.markerManager.cleanupController(i,n)}}n instanceof HTMLTemplateElement||e.removeChild(n),n=r}}insertBeforeEndMarker(t){if(!this.endMarker)return;let e=this.endMarker.parentNode;e&&e.insertBefore(t,this.endMarker)}refreshParentBindings(){let t=this.startMarker.parentNode;if(!(t instanceof HTMLElement)||!t.hasAttribute("data-lid"))return;let e=this.__getFluffElementHost();if(!e)return;let n=this.getScope();e.__processBindingsOnElementPublic(t,n)}__getFluffElementHost(){return this.host instanceof y?this.host:null}setScopeOnChildren(t,e,n,r,s){if(t instanceof Comment){let i=v.exec(t.data);if(i&&r){let[,o,c]=i,u=parseInt(c,10),h=`/fluff:${o}:${u}`,l=r.getController(u,t),f=l===void 0;if(!l&&r.ensureController){let O=null,x=t.nextSibling;for(;x;){if(x instanceof Comment&&x.data===h){O=x;break}x=x.nextSibling}l=r.ensureController(u,o,t,O)}l&&(l.setParentScope(e),l.setLoopContext(e.locals),l.updateRenderContext(n),f&&l.initialize())}}else if(t instanceof y)t.__loopContext=e.locals,t.__parentScope=e,this.processBindingsOnNode(t,e,s);else if(t instanceof HTMLElement&&customElements.get(t.tagName.toLowerCase())!==void 0){let i=A(e);t.setAttribute("data-fluff-scope-id",i),this.processBindingsOnNode(t,e,s)}else t instanceof HTMLElement&&t.hasAttribute("data-lid")&&this.processBindingsOnNode(t,e,s);for(let i of Array.from(t.childNodes))this.setScopeOnChildren(i,e,n,r,s)}cloneAndInsertTemplate(t,e,n,r){let s=t.content.cloneNode(!0);if(!(s instanceof DocumentFragment))throw new Error("Expected DocumentFragment from template clone");let i=Array.from(s.childNodes),o=this.createChildScope(e);this.insertBeforeEndMarker(s);for(let c of i)this.setScopeOnChildren(c,o,n,this.markerManager,r)}processBindingsOnNode(t,e,n){let r=this.__getFluffElementHost();r&&r.__processBindingsOnElementPublic(t,e,n)}};var w=class extends d{constructor(t,e,n,r,s,i){super(t,e,n,r,s)}initialize(){}updateRenderContext(t){t&&(t.shouldBreak=!0)}};var k=class extends d{config;itemTemplate=null;emptyTemplate=null;bindingsSubscriptions=[];constructor(t,e,n,r,s,i){super(t,e,n,r,s),this.config=i}initialize(){let e=`${this.host.tagName.toLowerCase()}-${this.id}`;this.itemTemplate=this.shadowRoot.querySelector(`template[data-fluff-tpl="${e}"]`),this.emptyTemplate=this.shadowRoot.querySelector(`template[data-fluff-empty="${e}"]`);let n=this.config.deps??[],r=()=>{this.clearContentBetweenMarkersWithCleanup(this.bindingsSubscriptions);let s=this.evaluateExpr(this.config.iterableExprId);if(!Array.isArray(s)||s.length===0){this.emptyTemplate&&this.cloneAndInsertTemplate(this.emptyTemplate,this.loopContext,void 0,this.bindingsSubscriptions);return}if(!this.itemTemplate)return;let i={shouldBreak:!1};for(let o=0;o<s.length&&!i.shouldBreak;o++){let c={...this.loopContext,[this.config.iterator]:s[o],$index:o};this.cloneAndInsertTemplate(this.itemTemplate,c,i,this.bindingsSubscriptions)}this.refreshParentBindings()};this.subscribeAndRun(n,r)}};var P=class extends d{config;templates=[];currentBranchIndex=-1;bindingsSubscriptions=[];constructor(t,e,n,r,s,i){super(t,e,n,r,s),this.config=i}initialize(){let e=`${this.host.tagName.toLowerCase()}-${this.id}-`;this.templates=Array.from(this.shadowRoot.querySelectorAll(`template[data-fluff-branch^="${e}"]`));let n=[];for(let s of this.config.branches)s.deps&&n.push(...s.deps);let r=()=>{let s=-1;for(let i=0;i<this.config.branches.length;i++){let o=this.config.branches[i];if(o.exprId===void 0){s=i;break}if(this.evaluateExpr(o.exprId)){s=i;break}}s!==this.currentBranchIndex&&(this.clearContentBetweenMarkersWithCleanup(this.bindingsSubscriptions),this.currentBranchIndex=s,s>=0&&s<this.templates.length&&this.cloneAndInsertTemplate(this.templates[s],this.loopContext,void 0,this.bindingsSubscriptions),this.refreshParentBindings())};this.subscribeAndRun(n,r)}};var b=class{static isIfConfig(t){return t.type==="if"}static isForConfig(t){return t.type==="for"}static isSwitchConfig(t){return t.type==="switch"}static isTextConfig(t){return t.type==="text"}static isBreakConfig(t){return t.type==="break"}};var E=class extends d{config;templates=[];bindingsSubscriptions=[];constructor(t,e,n,r,s,i){super(t,e,n,r,s),this.config=i}initialize(){let e=`${this.host.tagName.toLowerCase()}-${this.id}-`;this.templates=Array.from(this.shadowRoot.querySelectorAll(`template[data-fluff-case^="${e}"]`));let n=this.config.deps??[],r=()=>{this.clearContentBetweenMarkersWithCleanup(this.bindingsSubscriptions);let s=this.evaluateExpr(this.config.expressionExprId),i=!1,o=!1,c={shouldBreak:!1};for(let u=0;u<this.config.cases.length&&!c.shouldBreak;u++){let h=this.config.cases[u],l=this.templates[u];if(!l)continue;let f=h.isDefault||h.valueExprId!==void 0&&this.evaluateExpr(h.valueExprId)===s;(o||!i&&f)&&(i=!0,this.cloneAndInsertTemplate(l,this.loopContext,c,this.bindingsSubscriptions),o=h.fallthrough)}this.refreshParentBindings()};this.subscribeAndRun(n,r)}};var M=class extends d{config;textNode=null;constructor(t,e,n,r,s,i){super(t,e,n,r,s),this.config=i}initialize(){this.textNode=document.createTextNode(""),this.insertBeforeEndMarker(this.textNode);let t=this.config.deps??[],e=this.config.pipes??[],n=()=>{let r=this.evaluateExpr(this.config.exprId);if(this.host.__applyPipesForController){let s=this.getScope(),i=this.collectLocalsFromScope(s);r=this.host.__applyPipesForController(r,e,i)}this.textNode&&(this.textNode.textContent=this.formatValue(r))};this.subscribeAndRun(t,n)}formatValue(t){return t==null?"":typeof t=="object"?JSON.stringify(t):typeof t=="string"?t:typeof t=="number"||typeof t=="boolean"?String(t):""}};var S=class{controllers=new Map;configs=new Map;host;shadowRoot;constructor(t,e){this.host=t,this.shadowRoot=e}initializeFromConfig(t){this.configs.clear();for(let[n,r]of t)this.configs.set(n,r);for(let[n,r]of t){let{startMarker:s,endMarker:i}=this.findMarkers(n,r.type);if(!s)continue;let o=this.createController(n,s,i,r);o&&(o.setMarkerManager(this),this.registerController(n,s,o))}let e=[];for(let n of this.controllers.values())e.push(...n.values());for(let n of e)n.initialize()}ensureController(t,e,n,r){let s=this.getController(t,n);if(s)return s;let i=this.configs.get(t);if(i?.type!==e)return;let o=this.createController(t,n,r,i);if(o)return o.setMarkerManager(this),this.registerController(t,n,o),o}getController(t,e){return this.controllers.get(t)?.get(e)}cleanupController(t,e){let n=this.controllers.get(t);if(n){if(e){let r=n.get(e);if(!r)return;r.cleanup(),n.delete(e),n.size===0&&this.controllers.delete(t);return}for(let r of n.values())r.cleanup();this.controllers.delete(t)}}cleanup(){for(let t of this.controllers.values())for(let e of t.values())e.cleanup();this.controllers.clear()}registerController(t,e,n){let r=this.controllers.get(t);r||(r=new Map,this.controllers.set(t,r)),r.set(e,n)}findMarkers(t,e){let n=`fluff:${e}:${t}`,r=`/fluff:${e}:${t}`,s=null,i=null,o=document.createTreeWalker(this.shadowRoot,NodeFilter.SHOW_COMMENT,null),c=o.nextNode();for(;c;)c instanceof Comment&&(c.data===n?s=c:c.data===r&&(i=c)),c=o.nextNode();return{startMarker:s,endMarker:i}}createController(t,e,n,r){return b.isIfConfig(r)?new P(t,e,n,this.host,this.shadowRoot,r):b.isForConfig(r)?new k(t,e,n,this.host,this.shadowRoot,r):b.isSwitchConfig(r)?new E(t,e,n,this.host,this.shadowRoot,r):b.isTextConfig(r)?new M(t,e,n,this.host,this.shadowRoot,r):b.isBreakConfig(r)?new w(t,e,n,this.host,this.shadowRoot,r):null}};var y=class extends m{__pipes={};_shadowRoot;_subscriptions=[];_initialized=!1;_pendingInit=!1;_markerManager=null;_markerConfigEntries=null;_MarkerManagerClass=null;constructor(){super(),this._shadowRoot=this.attachShadow({mode:"open"})}connectedCallback(){if(!this._initialized){if(!m.__areExpressionsReady()){this._pendingInit||(this._pendingInit=!0,m.__addPendingInit(()=>{this._pendingInit=!1,this.connectedCallback()}));return}let t=this.getAttribute("data-fluff-scope-id");t&&!this.__parentScope&&(this.__parentScope=F(t),this.__parentScope&&(this.__loopContext=this.__parentScope.locals));let e=this.getAttribute("data-fluff-loop-context");if(e&&Object.keys(this.__loopContext).length===0)try{this.__loopContext=JSON.parse(e)}catch{}this.__applyPendingProps(),this.__render(),this.__setupBindings(),this.getAttribute("data-fluff-scope-id")&&this.__processBindings(),this._initialized=!0,"onInit"in this&&typeof this.onInit=="function"&&this.onInit()}}disconnectedCallback(){"onDestroy"in this&&typeof this.onDestroy=="function"&&this.onDestroy(),this._markerManager&&(this._markerManager.cleanup(),this._markerManager=null);for(let t of this._subscriptions)t.unsubscribe();this._subscriptions=[];for(let t of this.__baseSubscriptions)t.unsubscribe();this.__baseSubscriptions=[]}$watch=(t,e)=>(e(""),{unsubscribe:()=>{}});__processBindingsOnElementPublic(t,e,n){this.__processBindingsOnElement(t,e,n)}__setupBindings(){this.__initializeMarkers(S),this.__processBindings(),this.__initializeMarkersInternal()}__pipe(t,e,...n){let r=this.__pipes[t];return r?r(e,...n):(console.warn(`Pipe "${t}" not found`),e)}__getPipeFn(t){return this.__pipes[t]??R(t)}__getShadowRoot(){return this._shadowRoot}__createProp(t,e){let n=new p(e);return Object.defineProperty(this,t,{get(){return n.getValue()},set(r){n.setValue(r)},enumerable:!0,configurable:!0}),n}__defineClassHostBinding(t,e,n){Object.defineProperty(this,t,{get:()=>!!Reflect.get(this,n),set:r=>{Reflect.set(this,n,r),r?this.classList.add(e):this.classList.remove(e)},enumerable:!0,configurable:!0})}__setMarkerConfigs(t){this._markerConfigEntries=t}__initializeMarkers(t){this._MarkerManagerClass=t}__initializeMarkersInternal(){!this._markerConfigEntries||!this._MarkerManagerClass||(this._markerManager=new this._MarkerManagerClass(this,this._shadowRoot),this._markerManager.initializeFromConfig(this._markerConfigEntries))}__setChildProperty(t,e,n){if(t instanceof HTMLElement&&t.hasAttribute("x-fluff-component")){let r=t.tagName.toLowerCase();if(customElements.get(r)===void 0){this.__whenDefined(r,()=>{this.__setChildProperty(t,e,n)});return}}super.__setChildProperty(t,e,n)}__getReactivePropFromScope(t,e){let n=`__${t}`;if(n in e.host){let r=Reflect.get(e.host,n);if(r instanceof p)return r}if(e.parent)return this.__getReactivePropFromScope(t,e.parent)}__processBindings(){let t=this._shadowRoot.querySelectorAll("[data-lid]"),e=this.__getScope();for(let n of Array.from(t)){let r=n.closest("[x-fluff-component]");r&&r!==n||this.__processBindingsOnElement(n,e)}}__applyPendingProps(){let t=Reflect.get(this,"__pendingProps");if(this.isRecord(t)){for(let[e,n]of Object.entries(t)){console.log("apply-pending-prop",{propName:e,value:n,el:this.tagName});let r=`__${e}`;if(r in this){let s=Reflect.get(this,r);s instanceof p&&s.setValue(n,!0)}else e in this&&Reflect.set(this,e,n)}Reflect.deleteProperty(this,"__pendingProps")}}isRecord(t){return t!==null&&typeof t=="object"&&!Array.isArray(t)}};export{N as Component,_ as Direction,m as FluffBase,y as FluffElement,H as HostBinding,W as HostListener,D as Input,j as LinkedProperty,S as MarkerManager,q as Output,J as Pipe,p as Property,g as Publisher,G as Reactive,K as ViewChild,X as Watch,R as getPipeTransform,B as pipeRegistry};
@@ -1,10 +1,3 @@
1
- interface PipeInstance {
2
- transform: (value: unknown, ...args: unknown[]) => unknown;
3
- }
4
- interface PipeClass {
5
- __pipeName?: string;
6
- new (): PipeInstance;
7
- }
8
1
  type Constructor = new (...args: unknown[]) => object;
9
2
  export interface ComponentConfig {
10
3
  selector: string;
@@ -12,7 +5,6 @@ export interface ComponentConfig {
12
5
  template?: string;
13
6
  styleUrl?: string;
14
7
  styles?: string;
15
- pipes?: PipeClass[];
16
8
  }
17
9
  export interface ComponentMetadata extends ComponentConfig {
18
10
  inputs: Map<string, string>;
@@ -5,17 +5,6 @@ export function Component(config) {
5
5
  ...config, inputs: new Map(), outputs: new Map()
6
6
  };
7
7
  componentRegistry.set(target, metadata);
8
- if (config.pipes && config.pipes.length > 0) {
9
- const pipesObj = {};
10
- for (const PipeClassItem of config.pipes) {
11
- const pipeName = PipeClassItem.__pipeName;
12
- if (pipeName) {
13
- const instance = new PipeClassItem();
14
- pipesObj[pipeName] = (value, ...args) => instance.transform(value, ...args);
15
- }
16
- }
17
- Reflect.set(target.prototype, '__pipes', pipesObj);
18
- }
19
8
  return target;
20
9
  };
21
10
  }
@@ -1 +1 @@
1
- export declare const Input: (bindingName?: string) => PropertyDecorator;
1
+ export declare const Input: (bindingName?: string | import("../index.js").ReactiveOptions) => PropertyDecorator;
@@ -1 +1,2 @@
1
- export declare function createInputOutputDecorator(type: 'inputs' | 'outputs'): (bindingName?: string) => PropertyDecorator;
1
+ import type { ReactiveOptions } from '../interfaces/ReactiveOptions.js';
2
+ export declare function createInputOutputDecorator(type: 'inputs' | 'outputs'): (bindingName?: string | ReactiveOptions) => PropertyDecorator;
@@ -10,7 +10,9 @@ export function createInputOutputDecorator(type) {
10
10
  return;
11
11
  const metadata = getComponentMetadata(constructor);
12
12
  if (metadata) {
13
- const name = bindingName ?? String(propertyKey);
13
+ const name = typeof bindingName === 'string'
14
+ ? bindingName
15
+ : bindingName?.alias ?? String(propertyKey);
14
16
  metadata[type].set(String(propertyKey), name);
15
17
  }
16
18
  };
@@ -1 +1 @@
1
- export declare const Output: (bindingName?: string) => PropertyDecorator;
1
+ export declare const Output: (bindingName?: string | import("../index.js").ReactiveOptions) => PropertyDecorator;
@@ -4,6 +4,7 @@ interface PipeInstance {
4
4
  type PipeConstructor = (new () => PipeInstance) & {
5
5
  __pipeName?: string;
6
6
  };
7
+ export declare const pipeRegistry: Map<string, PipeConstructor>;
7
8
  export declare function getPipeTransform(name: string): ((value: unknown, ...args: unknown[]) => unknown) | undefined;
8
9
  export declare function Pipe(name: string): <T extends PipeConstructor>(target: T) => T;
9
10
  export interface PipeTransform<TArgs extends unknown[] = unknown[], TReturn = unknown> {
@@ -1,4 +1,4 @@
1
- const pipeRegistry = new Map();
1
+ export const pipeRegistry = new Map();
2
2
  export function getPipeTransform(name) {
3
3
  const PipeClass = pipeRegistry.get(name);
4
4
  if (!PipeClass)
@@ -1 +1,2 @@
1
- export declare function Reactive(): PropertyDecorator;
1
+ import type { ReactiveOptions } from '../interfaces/ReactiveOptions.js';
2
+ export declare function Reactive(_options?: ReactiveOptions): PropertyDecorator;
@@ -1,4 +1,4 @@
1
- export function Reactive() {
1
+ export function Reactive(_options) {
2
2
  return (_target, _propertyKey) => {
3
3
  // Dummy decorator, replaced by compiler
4
4
  };
package/index.d.ts CHANGED
@@ -5,7 +5,7 @@ export { HostListener } from './decorators/HostListener.js';
5
5
  export { Input } from './decorators/Input.js';
6
6
  export { LinkedProperty } from './decorators/LinkedProperty.js';
7
7
  export { Output } from './decorators/Output.js';
8
- export { getPipeTransform, Pipe } from './decorators/Pipe.js';
8
+ export { getPipeTransform, Pipe, pipeRegistry } from './decorators/Pipe.js';
9
9
  export type { PipeTransform } from './decorators/Pipe.js';
10
10
  export { Reactive } from './decorators/Reactive.js';
11
11
  export { ViewChild } from './decorators/ViewChild.js';
@@ -16,6 +16,7 @@ export { MarkerManager } from './runtime/FluffMarkers.js';
16
16
  export { Property } from './utils/Property.js';
17
17
  export { Publisher } from './utils/Publisher.js';
18
18
  export { Direction } from './enums/Direction.js';
19
+ export type { ReactiveOptions } from './interfaces/ReactiveOptions.js';
19
20
  export type { OnInit } from './interfaces/OnInit.js';
20
21
  export type { OnDestroy } from './interfaces/OnDestroy.js';
21
22
  export type { Subscription } from './interfaces/Subscription.js';
package/index.js CHANGED
@@ -4,7 +4,7 @@ export { HostListener } from './decorators/HostListener.js';
4
4
  export { Input } from './decorators/Input.js';
5
5
  export { LinkedProperty } from './decorators/LinkedProperty.js';
6
6
  export { Output } from './decorators/Output.js';
7
- export { getPipeTransform, Pipe } from './decorators/Pipe.js';
7
+ export { getPipeTransform, Pipe, pipeRegistry } from './decorators/Pipe.js';
8
8
  export { Reactive } from './decorators/Reactive.js';
9
9
  export { ViewChild } from './decorators/ViewChild.js';
10
10
  export { Watch } from './decorators/Watch.js';
@@ -1,5 +1,15 @@
1
- import type { Property } from '../utils/Property.js';
1
+ import type { PropertyChain } from './PropertyChain.js';
2
+ import type { Subscription } from './Subscription.js';
3
+ export interface FluffHostScope {
4
+ host: FluffHostElement;
5
+ locals: Record<string, unknown>;
6
+ parent?: FluffHostScope;
7
+ }
2
8
  export interface FluffHostElement extends HTMLElement {
3
- __getReactiveProp?: (name: string) => Property<unknown> | undefined;
4
- __pipes?: Record<string, (value: unknown, ...args: unknown[]) => unknown>;
9
+ __subscribeToExpression?: (deps: PropertyChain[], scope: FluffHostScope, callback: () => void, subscriptions: Subscription[]) => void;
10
+ __evaluateExpr?: (exprId: number, locals: Record<string, unknown>) => unknown;
11
+ __applyPipesForController?: (value: unknown, pipes: {
12
+ name: string;
13
+ argExprIds: number[];
14
+ }[], locals: Record<string, unknown>) => unknown;
5
15
  }
@@ -0,0 +1,6 @@
1
+ import type { Direction } from '../enums/Direction.js';
2
+ export interface ReactiveOptions {
3
+ alias?: string;
4
+ direction?: Direction;
5
+ commitTrigger?: string;
6
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluffjs/fluff",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "module": "./index.js",
@@ -21,10 +21,21 @@ export declare abstract class FluffBase extends HTMLElement {
21
21
  static __e: ExpressionFn[];
22
22
  static __h: HandlerFn[];
23
23
  static __bindings: Record<string, BindingInfo[]>;
24
+ private static __expressionsReady;
25
+ private static readonly __pendingInitCallbacks;
26
+ static __setExpressionTable(expressions: ExpressionFn[], handlers: HandlerFn[]): void;
27
+ static __areExpressionsReady(): boolean;
28
+ static __addPendingInit(callback: () => void): void;
24
29
  __parentScope?: Scope;
25
30
  __loopContext: Record<string, unknown>;
26
31
  protected __baseSubscriptions: Subscription[];
27
32
  __getScope(): Scope;
33
+ __subscribeToExpression(deps: PropertyChain[], scope: Scope, callback: () => void, subscriptions: Subscription[]): void;
34
+ __evaluateExpr(exprId: number, locals: Record<string, unknown>): unknown;
35
+ __applyPipesForController(value: unknown, pipes: {
36
+ name: string;
37
+ argExprIds: number[];
38
+ }[], locals: Record<string, unknown>): unknown;
28
39
  protected __processBindingsOnElement(el: Element, scope: Scope, subscriptions?: Subscription[]): void;
29
40
  private __getBindingsForLid;
30
41
  private __isBindingsMap;
@@ -35,7 +46,6 @@ export declare abstract class FluffBase extends HTMLElement {
35
46
  n: string;
36
47
  a: number[];
37
48
  }[], locals: Record<string, unknown>): unknown;
38
- private __applyPipe;
39
49
  protected __getPipeFn(_name: string): ((value: unknown, ...args: unknown[]) => unknown) | undefined;
40
50
  protected __subscribeToExpressionInScope(deps: PropertyChain[] | undefined, scope: Scope, callback: () => void, subscriptions?: Subscription[]): void;
41
51
  private __subscribeToPropertyChain;
@@ -4,6 +4,23 @@ export class FluffBase extends HTMLElement {
4
4
  static __e = [];
5
5
  static __h = [];
6
6
  static __bindings = {};
7
+ static __expressionsReady = false;
8
+ static __pendingInitCallbacks = [];
9
+ static __setExpressionTable(expressions, handlers) {
10
+ FluffBase.__e = expressions;
11
+ FluffBase.__h = handlers;
12
+ FluffBase.__expressionsReady = true;
13
+ const pending = FluffBase.__pendingInitCallbacks.splice(0, FluffBase.__pendingInitCallbacks.length);
14
+ for (const callback of pending) {
15
+ callback();
16
+ }
17
+ }
18
+ static __areExpressionsReady() {
19
+ return FluffBase.__expressionsReady;
20
+ }
21
+ static __addPendingInit(callback) {
22
+ FluffBase.__pendingInitCallbacks.push(callback);
23
+ }
7
24
  __parentScope;
8
25
  __loopContext = {};
9
26
  __baseSubscriptions = [];
@@ -14,6 +31,34 @@ export class FluffBase extends HTMLElement {
14
31
  parent: this.__parentScope
15
32
  };
16
33
  }
34
+ __subscribeToExpression(deps, scope, callback, subscriptions) {
35
+ this.__subscribeToExpressionInScope(deps, scope, callback, subscriptions);
36
+ }
37
+ __evaluateExpr(exprId, locals) {
38
+ const fn = this.__getCompiledExprFn(exprId);
39
+ try {
40
+ return fn(this, locals);
41
+ }
42
+ catch {
43
+ return undefined;
44
+ }
45
+ }
46
+ __applyPipesForController(value, pipes, locals) {
47
+ let result = value;
48
+ if (result instanceof Property) {
49
+ result = result.getValue();
50
+ }
51
+ for (const pipe of pipes) {
52
+ const pipeFn = this.__getPipeFn(pipe.name);
53
+ if (!pipeFn) {
54
+ console.warn(`Pipe "${pipe.name}" not found`);
55
+ continue;
56
+ }
57
+ const args = pipe.argExprIds.map(id => this.__getCompiledExprFn(id)(this, locals));
58
+ result = pipeFn(result, ...args);
59
+ }
60
+ return result;
61
+ }
17
62
  __processBindingsOnElement(el, scope, subscriptions) {
18
63
  const lid = el.getAttribute('data-lid');
19
64
  if (!lid)
@@ -74,23 +119,7 @@ export class FluffBase extends HTMLElement {
74
119
  return fn;
75
120
  }
76
121
  __applyPipes(value, pipes, locals) {
77
- let result = value;
78
- if (result instanceof Property) {
79
- result = result.getValue();
80
- }
81
- for (const pipe of pipes) {
82
- result = this.__applyPipe(pipe.n, result, pipe.a, locals);
83
- }
84
- return result;
85
- }
86
- __applyPipe(name, value, argExprIds, locals) {
87
- const pipeFn = this.__getPipeFn(name);
88
- if (!pipeFn) {
89
- console.warn(`Pipe "${name}" not found`);
90
- return value;
91
- }
92
- const args = argExprIds.map(id => this.__getCompiledExprFn(id)(this, locals));
93
- return pipeFn(value, ...args);
122
+ return this.__applyPipesForController(value, pipes.map(p => ({ name: p.n, argExprIds: p.a })), locals);
94
123
  }
95
124
  __getPipeFn(_name) {
96
125
  return undefined;
@@ -285,6 +314,10 @@ export class FluffBase extends HTMLElement {
285
314
  }
286
315
  }
287
316
  __applyEventBindingWithScope(el, binding, scope) {
317
+ const boundKey = `__fluff_event_${binding.n}`;
318
+ if (Reflect.has(el, boundKey))
319
+ return;
320
+ Reflect.set(el, boundKey, true);
288
321
  if (typeof binding.h !== 'number') {
289
322
  throw new Error(`Event binding for ${binding.n} is missing handlerId`);
290
323
  }
@@ -1,16 +1,16 @@
1
1
  import type { Subscription } from '../interfaces/Subscription.js';
2
2
  import { Property } from '../utils/Property.js';
3
- import { Publisher } from '../utils/Publisher.js';
4
3
  import { FluffBase } from './FluffBase.js';
5
- import type { MarkerManagerInterface } from './MarkerManagerInterface.js';
4
+ import type { MarkerConfigEntries, MarkerManagerInterface } from './MarkerManagerInterface.js';
6
5
  import { type Scope } from './ScopeRegistry.js';
7
6
  export declare abstract class FluffElement extends FluffBase {
8
7
  protected __pipes: Record<string, (value: unknown, ...args: unknown[]) => unknown>;
9
8
  protected readonly _shadowRoot: ShadowRoot;
10
9
  private _subscriptions;
11
10
  private _initialized;
11
+ private _pendingInit;
12
12
  private _markerManager;
13
- private _markerConfigJson;
13
+ private _markerConfigEntries;
14
14
  private _MarkerManagerClass;
15
15
  constructor();
16
16
  connectedCallback(): void;
@@ -19,32 +19,20 @@ export declare abstract class FluffElement extends FluffBase {
19
19
  __processBindingsOnElementPublic(el: Element, scope: Scope, subscriptions?: Subscription[]): void;
20
20
  protected abstract __render(): void;
21
21
  protected __setupBindings(): void;
22
- protected __addSubscription(sub: Subscription): void;
23
22
  protected __pipe(name: string, value: unknown, ...args: unknown[]): unknown;
24
23
  protected __getPipeFn(name: string): ((value: unknown, ...args: unknown[]) => unknown) | undefined;
25
24
  protected __getShadowRoot(): ShadowRoot;
26
- protected __setMarkerConfigs(configJson: string): void;
25
+ protected __createProp<T>(name: string, options: T | {
26
+ initialValue: T;
27
+ [key: string]: unknown;
28
+ }): Property<T>;
29
+ protected __defineClassHostBinding(name: string, className: string, privateName: string): void;
30
+ protected __setMarkerConfigs(entries: MarkerConfigEntries): void;
27
31
  protected __initializeMarkers(MarkerManagerClass: new (host: FluffElement, shadowRoot: ShadowRoot) => MarkerManagerInterface): void;
28
32
  private __initializeMarkersInternal;
29
- protected __getElement(id: string): Element | null;
30
- protected __setText(id: string, text: string): void;
31
- protected __bindText(id: string, getter: () => string): void;
32
- protected __setProperty(id: string, prop: string, value: unknown): void;
33
- protected __addClass(id: string, className: string): void;
34
- protected __removeClass(id: string, className: string): void;
35
- protected __bindEvent(id: string, event: string, handler: (e: Event) => void): void;
36
- protected __bindPropertyChange<T>(prop: Property<T>, callback: (val: T) => void): void;
37
- protected __connectProperties<T>(source: Property<T>, target: Property<T>): void;
38
- protected __connectOutput<T>(source: Publisher<T>, handler: (val: T) => void): void;
39
- protected __bindOutput(id: string, outputName: string, handler: (val: Event) => void): void;
40
33
  protected __setChildProperty(el: Element, propName: string, value: unknown): void;
41
- protected __bindToChild(id: string, propName: string, value: unknown): void;
42
- protected __setChildPropertyDeferred(el: Element, propName: string, value: unknown): void;
43
- protected __bindOutputOnElement(el: Element, outputName: string, handler: (val: Event) => void): void;
44
34
  protected __getReactivePropFromScope(propName: string, scope: Scope): Property<unknown> | undefined;
45
35
  protected __processBindings(): void;
46
36
  private __applyPendingProps;
47
- private isHTMLElement;
48
37
  private isRecord;
49
- private __getReactiveProp;
50
38
  }
@@ -1,16 +1,16 @@
1
1
  import { getPipeTransform } from '../decorators/Pipe.js';
2
- import { DomUtils } from '../utils/DomUtils.js';
3
2
  import { Property } from '../utils/Property.js';
4
- import { Publisher } from '../utils/Publisher.js';
5
3
  import { FluffBase } from './FluffBase.js';
4
+ import { MarkerManager } from './MarkerManager.js';
6
5
  import { getScope } from './ScopeRegistry.js';
7
6
  export class FluffElement extends FluffBase {
8
7
  __pipes = {};
9
8
  _shadowRoot;
10
9
  _subscriptions = [];
11
10
  _initialized = false;
11
+ _pendingInit = false;
12
12
  _markerManager = null;
13
- _markerConfigJson = null;
13
+ _markerConfigEntries = null;
14
14
  _MarkerManagerClass = null;
15
15
  constructor() {
16
16
  super();
@@ -18,6 +18,16 @@ export class FluffElement extends FluffBase {
18
18
  }
19
19
  connectedCallback() {
20
20
  if (!this._initialized) {
21
+ if (!FluffBase.__areExpressionsReady()) {
22
+ if (!this._pendingInit) {
23
+ this._pendingInit = true;
24
+ FluffBase.__addPendingInit(() => {
25
+ this._pendingInit = false;
26
+ this.connectedCallback();
27
+ });
28
+ }
29
+ return;
30
+ }
21
31
  const scopeId = this.getAttribute('data-fluff-scope-id');
22
32
  if (scopeId && !this.__parentScope) {
23
33
  this.__parentScope = getScope(scopeId);
@@ -73,12 +83,10 @@ export class FluffElement extends FluffBase {
73
83
  this.__processBindingsOnElement(el, scope, subscriptions);
74
84
  }
75
85
  __setupBindings() {
86
+ this.__initializeMarkers(MarkerManager);
76
87
  this.__processBindings();
77
88
  this.__initializeMarkersInternal();
78
89
  }
79
- __addSubscription(sub) {
80
- this._subscriptions.push(sub);
81
- }
82
90
  __pipe(name, value, ...args) {
83
91
  const pipe = this.__pipes[name];
84
92
  if (!pipe) {
@@ -93,102 +101,47 @@ export class FluffElement extends FluffBase {
93
101
  __getShadowRoot() {
94
102
  return this._shadowRoot;
95
103
  }
96
- __setMarkerConfigs(configJson) {
97
- this._markerConfigJson = configJson;
104
+ __createProp(name, options) {
105
+ const prop = new Property(options);
106
+ Object.defineProperty(this, name, {
107
+ get() {
108
+ return prop.getValue();
109
+ },
110
+ set(v) {
111
+ prop.setValue(v);
112
+ },
113
+ enumerable: true,
114
+ configurable: true
115
+ });
116
+ return prop;
117
+ }
118
+ __defineClassHostBinding(name, className, privateName) {
119
+ Object.defineProperty(this, name, {
120
+ get: () => Boolean(Reflect.get(this, privateName)),
121
+ set: (v) => {
122
+ Reflect.set(this, privateName, v);
123
+ if (v) {
124
+ this.classList.add(className);
125
+ }
126
+ else {
127
+ this.classList.remove(className);
128
+ }
129
+ },
130
+ enumerable: true,
131
+ configurable: true
132
+ });
133
+ }
134
+ __setMarkerConfigs(entries) {
135
+ this._markerConfigEntries = entries;
98
136
  }
99
137
  __initializeMarkers(MarkerManagerClass) {
100
138
  this._MarkerManagerClass = MarkerManagerClass;
101
139
  }
102
140
  __initializeMarkersInternal() {
103
- if (!this._markerConfigJson || !this._MarkerManagerClass)
141
+ if (!this._markerConfigEntries || !this._MarkerManagerClass)
104
142
  return;
105
143
  this._markerManager = new this._MarkerManagerClass(this, this._shadowRoot);
106
- this._markerManager.initializeFromConfig(this._markerConfigJson);
107
- }
108
- __getElement(id) {
109
- return this._shadowRoot.querySelector(`[data-lid="${id}"]`);
110
- }
111
- __setText(id, text) {
112
- const el = this.__getElement(id);
113
- if (el)
114
- el.textContent = text;
115
- }
116
- __bindText(id, getter) {
117
- const el = this.__getElement(id);
118
- if (!el)
119
- return;
120
- const expr = el.getAttribute('data-text-bind') ?? '';
121
- const propMatch = /this\.([a-zA-Z_][a-zA-Z0-9_]*)/.exec(expr);
122
- const update = () => {
123
- try {
124
- el.textContent = getter();
125
- }
126
- catch {
127
- el.textContent = '';
128
- }
129
- };
130
- if (propMatch) {
131
- const [, propName] = propMatch;
132
- const reactiveProp = this.__getReactiveProp(propName);
133
- if (reactiveProp) {
134
- this.__bindPropertyChange(reactiveProp, update);
135
- }
136
- }
137
- update();
138
- }
139
- __setProperty(id, prop, value) {
140
- const el = this.__getElement(id);
141
- if (this.isHTMLElement(el)) {
142
- if (prop in el) {
143
- Reflect.set(el, prop, value);
144
- }
145
- else {
146
- el.setAttribute(prop, String(value));
147
- }
148
- }
149
- }
150
- __addClass(id, className) {
151
- const el = this.__getElement(id);
152
- if (this.isHTMLElement(el))
153
- el.classList.add(className);
154
- }
155
- __removeClass(id, className) {
156
- const el = this.__getElement(id);
157
- if (this.isHTMLElement(el))
158
- el.classList.remove(className);
159
- }
160
- __bindEvent(id, event, handler) {
161
- const el = this.__getElement(id);
162
- if (el) {
163
- el.addEventListener(event, handler);
164
- }
165
- }
166
- __bindPropertyChange(prop, callback) {
167
- const sub = prop.onChange.subscribe(callback);
168
- this._subscriptions.push(sub);
169
- const currentVal = prop.getValue();
170
- if (currentVal !== null) {
171
- callback(currentVal);
172
- }
173
- }
174
- __connectProperties(source, target) {
175
- const sub = source.onChange.subscribe((val) => {
176
- target.setValue(val, true);
177
- });
178
- this._subscriptions.push(sub);
179
- const currentVal = source.getValue();
180
- if (currentVal !== null) {
181
- target.setValue(currentVal, true);
182
- }
183
- }
184
- __connectOutput(source, handler) {
185
- const sub = source.subscribe(handler);
186
- this._subscriptions.push(sub);
187
- }
188
- __bindOutput(id, outputName, handler) {
189
- const el = this.__getElement(id);
190
- if (el)
191
- this.__bindOutputOnElement(el, outputName, handler);
144
+ this._markerManager.initializeFromConfig(this._markerConfigEntries);
192
145
  }
193
146
  __setChildProperty(el, propName, value) {
194
147
  if (el instanceof HTMLElement && el.hasAttribute('x-fluff-component')) {
@@ -202,47 +155,6 @@ export class FluffElement extends FluffBase {
202
155
  }
203
156
  super.__setChildProperty(el, propName, value);
204
157
  }
205
- __bindToChild(id, propName, value) {
206
- const el = this.__getElement(id);
207
- if (!el)
208
- return;
209
- this.__setChildPropertyDeferred(el, propName, value);
210
- }
211
- __setChildPropertyDeferred(el, propName, value) {
212
- if (Reflect.get(el, propName) !== undefined) {
213
- this.__setChildProperty(el, propName, value);
214
- return;
215
- }
216
- if (DomUtils.isCustomElement(el)) {
217
- this.__whenDefined(el.tagName.toLowerCase(), () => {
218
- this.__setChildProperty(el, propName, value);
219
- });
220
- }
221
- else {
222
- this.__setChildProperty(el, propName, value);
223
- }
224
- }
225
- __bindOutputOnElement(el, outputName, handler) {
226
- const maybeOutput = Reflect.get(el, outputName);
227
- if (maybeOutput instanceof Publisher) {
228
- this.__connectOutput(maybeOutput, handler);
229
- return;
230
- }
231
- if (DomUtils.isCustomElement(el)) {
232
- this.__whenDefined(el.tagName.toLowerCase(), () => {
233
- const innerOutput = Reflect.get(el, outputName);
234
- if (innerOutput instanceof Publisher) {
235
- this.__connectOutput(innerOutput, handler);
236
- }
237
- else {
238
- el.addEventListener(outputName, handler);
239
- }
240
- });
241
- }
242
- else {
243
- el.addEventListener(outputName, handler);
244
- }
245
- }
246
158
  __getReactivePropFromScope(propName, scope) {
247
159
  const key = `__${propName}`;
248
160
  if (key in scope.host) {
@@ -286,20 +198,7 @@ export class FluffElement extends FluffBase {
286
198
  }
287
199
  Reflect.deleteProperty(this, '__pendingProps');
288
200
  }
289
- isHTMLElement(el) {
290
- return el !== null;
291
- }
292
201
  isRecord(value) {
293
202
  return value !== null && typeof value === 'object' && !Array.isArray(value);
294
203
  }
295
- __getReactiveProp(propName) {
296
- const key = `__${propName}`;
297
- if (key in this) {
298
- const candidate = Reflect.get(this, key);
299
- if (candidate instanceof Property) {
300
- return candidate;
301
- }
302
- }
303
- return undefined;
304
- }
305
204
  }