@vielzeug/ore 1.2.2 → 1.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.
- package/dist/aria.cjs +1 -1
- package/dist/aria.cjs.map +1 -1
- package/dist/aria.d.ts +9 -8
- package/dist/aria.d.ts.map +1 -1
- package/dist/aria.js +1 -1
- package/dist/aria.js.map +1 -1
- package/dist/base-element.cjs +1 -1
- package/dist/base-element.cjs.map +1 -1
- package/dist/base-element.d.ts.map +1 -1
- package/dist/base-element.js +1 -1
- package/dist/base-element.js.map +1 -1
- package/dist/directives/classMap.cjs +1 -1
- package/dist/directives/classMap.cjs.map +1 -1
- package/dist/directives/classMap.d.ts.map +1 -1
- package/dist/directives/classMap.js +1 -1
- package/dist/directives/classMap.js.map +1 -1
- package/dist/directives/each.cjs +1 -1
- package/dist/directives/each.cjs.map +1 -1
- package/dist/directives/each.d.ts +8 -1
- package/dist/directives/each.d.ts.map +1 -1
- package/dist/directives/each.js +1 -1
- package/dist/directives/each.js.map +1 -1
- package/dist/directives/index.d.ts +34 -5
- package/dist/directives/index.d.ts.map +1 -1
- package/dist/directives/raw.cjs +1 -1
- package/dist/directives/raw.cjs.map +1 -1
- package/dist/directives/raw.js +1 -1
- package/dist/directives/raw.js.map +1 -1
- package/dist/directives/styleMap.cjs +1 -1
- package/dist/directives/styleMap.cjs.map +1 -1
- package/dist/directives/styleMap.d.ts.map +1 -1
- package/dist/directives/styleMap.js +1 -1
- package/dist/directives/styleMap.js.map +1 -1
- package/dist/directives/when.cjs +1 -1
- package/dist/directives/when.cjs.map +1 -1
- package/dist/directives/when.d.ts.map +1 -1
- package/dist/directives/when.js +1 -1
- package/dist/directives/when.js.map +1 -1
- package/dist/directives.cjs +1 -1
- package/dist/directives.js +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.ts +16 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/host-bind.cjs +1 -1
- package/dist/host-bind.cjs.map +1 -1
- package/dist/host-bind.d.ts +6 -0
- package/dist/host-bind.d.ts.map +1 -1
- package/dist/host-bind.js +1 -1
- package/dist/host-bind.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/ore.cjs +2 -2
- package/dist/ore.cjs.map +1 -1
- package/dist/ore.iife.js +2 -2
- package/dist/ore.iife.js.map +1 -1
- package/dist/ore.js +2 -2
- package/dist/ore.js.map +1 -1
- package/dist/props.cjs.map +1 -1
- package/dist/props.d.ts +12 -0
- package/dist/props.d.ts.map +1 -1
- package/dist/props.js.map +1 -1
- package/dist/runtime.cjs +1 -1
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +1 -1
- package/dist/runtime.js.map +1 -1
- package/dist/slots.cjs +1 -1
- package/dist/slots.cjs.map +1 -1
- package/dist/slots.d.ts.map +1 -1
- package/dist/slots.js +1 -1
- package/dist/slots.js.map +1 -1
- package/dist/template/bindings.cjs +1 -1
- package/dist/template/bindings.cjs.map +1 -1
- package/dist/template/bindings.d.ts.map +1 -1
- package/dist/template/bindings.js +1 -1
- package/dist/template/bindings.js.map +1 -1
- package/dist/template/instantiator.cjs.map +1 -1
- package/dist/template/instantiator.d.ts.map +1 -1
- package/dist/template/instantiator.js.map +1 -1
- package/dist/testing/flush.cjs +1 -1
- package/dist/testing/flush.cjs.map +1 -1
- package/dist/testing/flush.d.ts +30 -26
- package/dist/testing/flush.d.ts.map +1 -1
- package/dist/testing/flush.js +1 -1
- package/dist/testing/flush.js.map +1 -1
- package/dist/testing/index.d.ts +30 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/interactions.cjs +1 -1
- package/dist/testing/interactions.cjs.map +1 -1
- package/dist/testing/interactions.d.ts.map +1 -1
- package/dist/testing/interactions.js +1 -1
- package/dist/testing/interactions.js.map +1 -1
- package/dist/testing/mount.cjs +1 -1
- package/dist/testing/mount.cjs.map +1 -1
- package/dist/testing/mount.d.ts +8 -14
- package/dist/testing/mount.d.ts.map +1 -1
- package/dist/testing/mount.js +1 -1
- package/dist/testing/mount.js.map +1 -1
- package/dist/testing/render-hook.cjs +1 -1
- package/dist/testing/render-hook.js +1 -1
- package/dist/testing.cjs +2 -1
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.js +2 -1
- package/dist/testing.js.map +1 -0
- package/dist/utils/aria.cjs.map +1 -1
- package/dist/utils/aria.d.ts +1 -1
- package/dist/utils/aria.js.map +1 -1
- package/dist/utils/dom.cjs +1 -1
- package/dist/utils/dom.cjs.map +1 -1
- package/dist/utils/dom.d.ts +36 -0
- package/dist/utils/dom.d.ts.map +1 -1
- package/dist/utils/dom.js +1 -1
- package/dist/utils/dom.js.map +1 -1
- package/dist/utils/id.cjs +1 -1
- package/dist/utils/id.cjs.map +1 -1
- package/dist/utils/id.d.ts +11 -2
- package/dist/utils/id.d.ts.map +1 -1
- package/dist/utils/id.js +1 -1
- package/dist/utils/id.js.map +1 -1
- package/package.json +4 -9
- package/dist/devtools.cjs +0 -2
- package/dist/devtools.cjs.map +0 -1
- package/dist/devtools.d.ts +0 -32
- package/dist/devtools.d.ts.map +0 -1
- package/dist/devtools.js +0 -2
- package/dist/devtools.js.map +0 -1
- package/dist/testing/events.cjs +0 -2
- package/dist/testing/events.cjs.map +0 -1
- package/dist/testing/events.d.ts +0 -37
- package/dist/testing/events.d.ts.map +0 -1
- package/dist/testing/events.js +0 -2
- package/dist/testing/events.js.map +0 -1
- package/dist/testing/query.cjs +0 -2
- package/dist/testing/query.cjs.map +0 -1
- package/dist/testing/query.d.ts +0 -24
- package/dist/testing/query.d.ts.map +0 -1
- package/dist/testing/query.js +0 -2
- package/dist/testing/query.js.map +0 -1
- package/dist/testing/testing.cjs +0 -2
- package/dist/testing/testing.cjs.map +0 -1
- package/dist/testing/testing.d.ts +0 -32
- package/dist/testing/testing.d.ts.map +0 -1
- package/dist/testing/testing.js +0 -2
- package/dist/testing/testing.js.map +0 -1
- package/dist/testing/wait.cjs +0 -2
- package/dist/testing/wait.cjs.map +0 -1
- package/dist/testing/wait.d.ts +0 -35
- package/dist/testing/wait.d.ts.map +0 -1
- package/dist/testing/wait.js +0 -2
- package/dist/testing/wait.js.map +0 -1
package/dist/aria.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./
|
|
1
|
+
const e=require("./host-bind.cjs");var t=(t,n)=>e.bind({aria:n},{target:t});exports.aria=t;
|
|
2
2
|
//# sourceMappingURL=aria.cjs.map
|
package/dist/aria.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aria.cjs","names":[],"sources":["../src/aria.ts"],"sourcesContent":["/**\n * Reactive ARIA attribute sync.\n *\n * `aria()` is a thin wrapper over `bind({
|
|
1
|
+
{"version":3,"file":"aria.cjs","names":[],"sources":["../src/aria.ts"],"sourcesContent":["/**\n * Reactive ARIA attribute sync.\n *\n * `aria()` is a thin convenience wrapper over `bind({ aria: config }, { target })` for the\n * common \"just ARIA, on one element\" case — normalizing bare ARIA property names\n * (`expanded` → `aria-expanded`) is `HostBindConfig.aria`'s job, not this function's; both\n * this and `bind()` share the exact same write path (`applyAttribute`/`setAttr`, including\n * its `on*`/dangerous-URL-scheme hardening), so there's nothing left to duplicate here.\n *\n * A component that needs both a regular attribute and ARIA attributes on the same element\n * can skip this wrapper entirely and pass `attr`/`aria` together in one `bind()` call —\n * one call, one cleanup function, instead of two.\n */\n\nimport { bind, type HostBindingValue } from './host-bind';\n\nexport type AriaConfig = Record<string, HostBindingValue>;\n\n/**\n * Reactively sync ARIA attributes on any element, auto-cleanup on component\n * disconnect. Returns a cleanup function that removes all reactive bindings.\n */\nexport const aria = (target: Element, config: AriaConfig): (() => void) => bind({ aria: config }, { target });\n"],"mappings":"mCAsBA,IAAa,GAAQ,EAAiB,IAAqC,EAAA,KAAK,CAAE,KAAM,CAAO,EAAG,CAAE,QAAO,CAAC"}
|
package/dist/aria.d.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Reactive ARIA attribute sync.
|
|
3
3
|
*
|
|
4
|
-
* `aria()` is a thin wrapper over `bind({
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
4
|
+
* `aria()` is a thin convenience wrapper over `bind({ aria: config }, { target })` for the
|
|
5
|
+
* common "just ARIA, on one element" case — normalizing bare ARIA property names
|
|
6
|
+
* (`expanded` → `aria-expanded`) is `HostBindConfig.aria`'s job, not this function's; both
|
|
7
|
+
* this and `bind()` share the exact same write path (`applyAttribute`/`setAttr`, including
|
|
8
|
+
* its `on*`/dangerous-URL-scheme hardening), so there's nothing left to duplicate here.
|
|
9
|
+
*
|
|
10
|
+
* A component that needs both a regular attribute and ARIA attributes on the same element
|
|
11
|
+
* can skip this wrapper entirely and pass `attr`/`aria` together in one `bind()` call —
|
|
12
|
+
* one call, one cleanup function, instead of two.
|
|
12
13
|
*/
|
|
13
14
|
import { type HostBindingValue } from './host-bind';
|
|
14
15
|
export type AriaConfig = Record<string, HostBindingValue>;
|
package/dist/aria.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aria.d.ts","sourceRoot":"","sources":["../src/aria.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"aria.d.ts","sourceRoot":"","sources":["../src/aria.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAQ,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE1D,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;AAE1D;;;GAGG;AACH,eAAO,MAAM,IAAI,GAAI,QAAQ,OAAO,EAAE,QAAQ,UAAU,KAAG,CAAC,MAAM,IAAI,CAAuC,CAAC"}
|
package/dist/aria.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{bind as e}from"./host-bind.js";var t=(t,n)=>e({aria:n},{target:t});export{t as aria};
|
|
2
2
|
//# sourceMappingURL=aria.js.map
|
package/dist/aria.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aria.js","names":[],"sources":["../src/aria.ts"],"sourcesContent":["/**\n * Reactive ARIA attribute sync.\n *\n * `aria()` is a thin wrapper over `bind({
|
|
1
|
+
{"version":3,"file":"aria.js","names":[],"sources":["../src/aria.ts"],"sourcesContent":["/**\n * Reactive ARIA attribute sync.\n *\n * `aria()` is a thin convenience wrapper over `bind({ aria: config }, { target })` for the\n * common \"just ARIA, on one element\" case — normalizing bare ARIA property names\n * (`expanded` → `aria-expanded`) is `HostBindConfig.aria`'s job, not this function's; both\n * this and `bind()` share the exact same write path (`applyAttribute`/`setAttr`, including\n * its `on*`/dangerous-URL-scheme hardening), so there's nothing left to duplicate here.\n *\n * A component that needs both a regular attribute and ARIA attributes on the same element\n * can skip this wrapper entirely and pass `attr`/`aria` together in one `bind()` call —\n * one call, one cleanup function, instead of two.\n */\n\nimport { bind, type HostBindingValue } from './host-bind';\n\nexport type AriaConfig = Record<string, HostBindingValue>;\n\n/**\n * Reactively sync ARIA attributes on any element, auto-cleanup on component\n * disconnect. Returns a cleanup function that removes all reactive bindings.\n */\nexport const aria = (target: Element, config: AriaConfig): (() => void) => bind({ aria: config }, { target });\n"],"mappings":"sCAsBA,IAAa,GAAQ,EAAiB,IAAqC,EAAK,CAAE,KAAM,CAAO,EAAG,CAAE,QAAO,CAAC"}
|
package/dist/base-element.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("./_dev.cjs"),t=require("./errors.cjs");require("./types/bindings.cjs");const n=require("./runtime.cjs"),r=require("./props.cjs"),i=require("./types.cjs"),a=require("./utils/css.cjs");let o=require("@vielzeug/ripple");var s=()=>({formResetCallbacks:[],generation:0,mountCallbacks:[],phase:i.ComponentPhase.UNINITIALIZED,scope:(0,o.scope)(),templateResult:null}),c=class extends HTMLElement{static _definition;static _normalizedPropDefs;static formAssociated=!1;static observedAttributes=[];_component;constructor(){super();let e=this.constructor._definition;e?.shadow!==!1&&this.attachShadow({mode:`open`,...e?.shadow}),this._component=s()}connectedCallback(){(0,o.untrack)(()=>{this._component.phase===i.ComponentPhase.UNINITIALIZED&&this._runSetup(),this._init()}),this.dispatchEvent(new CustomEvent(i.LIFECYCLE_EVENTS.CONNECT,{bubbles:!1,composed:!1}))}attributeChangedCallback(e,t,n){if(t===n)return;let i=r.getPropMeta(this,e);if(!i)return;let a=i.parse(n);Object.is((0,o.untrack)(()=>i.signal.value),a)||(i.signal.value=a)}disconnectedCallback(){this._component.generation++,this._component.phase=i.ComponentPhase.UNMOUNTED,this.dispatchEvent(new CustomEvent(i.LIFECYCLE_EVENTS.DISCONNECT,{bubbles:!1,composed:!1})),this._component.scope.dispose(),this._component.formResetCallbacks=[],this._component.mountCallbacks=[],this._component.phase=i.ComponentPhase.UNINITIALIZED,this._component.scope=(0,o.scope)(),this._component.templateResult=null}formResetCallback(){for(let e of this._component.formResetCallbacks)try{e()}catch(e){this._handleSetupError(e,`form-reset`)}}_handleSetupError(e,n=`setup`){let r=e instanceof Error?e:Error(String(e)),i=new t.OreLifecycleError(`<${this.localName}> failed during ${this._component.phase} (${n})`,{cause:r,component:this.localName,phase:n}),a=this.constructor._definition;if(a?.onError)try{return a.onError(i,this)}catch{}t.reportRuntimeError(i,this)}_runSetup(){this._component.phase=i.ComponentPhase.SETUP_RUNNING;let e=this.constructor._definition,t=this.constructor._normalizedPropDefs,a={element:this,formResetCallbacks:[],mountCallbacks:[]};try{let o;if(this._component.scope.run(()=>{o=n.runWithContext(a,()=>{let n=t?r.createProps(this,t):{};return e.setup(n)})}),this._component.mountCallbacks.push(...a.mountCallbacks),this._component.formResetCallbacks.push(...a.formResetCallbacks),o!=null&&typeof o.then==`function`){let t=this._component.mountCallbacks.splice(0);this._component.phase=i.ComponentPhase.LOADING,e.loading&&(this._component.templateResult=e.loading()),this._runSetupAsync(o,t,this._component.generation)}else this._component.templateResult=o??null,this._component.phase=i.ComponentPhase.SETUP_DONE}catch(e){let t=this._handleSetupError(e);if(t)this._component.templateResult=t,this._component.phase=i.ComponentPhase.SETUP_DONE;else throw this._component.phase=i.ComponentPhase.UNINITIALIZED,e}}_isStale(e){return this._component.generation!==e||!this.isConnected}async _runSetupAsync(t,n,r){try{let a=await t;if(this._isStale(r)){e.warn(`<${this.localName}> async setup result discarded — element disconnected before setup resolved.`);return}this._component.templateResult=a??null,this._component.phase=i.ComponentPhase.SETUP_DONE,this._component.mountCallbacks.push(...n),a&&this._applyResult(a),this._scheduleMountCallbacks()}catch(t){if(this._isStale(r)){e.warn(`<${this.localName}> async setup error discarded — element disconnected before setup resolved.`);return}let n=this._handleSetupError(t,`async-setup`);n?(this._component.templateResult=n,this._component.phase=i.ComponentPhase.SETUP_DONE,this._applyResult(n)):this._component.phase=i.ComponentPhase.UNINITIALIZED}}_applyResult(e){e&&((this.shadowRoot??this).replaceChildren(e.fragment),this._component.scope.run(()=>{e.apply(n.onCleanup)}))}_init(){this._applyStyles(),this._mountTemplate(),this._component.phase===i.ComponentPhase.SETUP_DONE&&this._scheduleMountCallbacks()}_applyStyles(){let e=this.constructor._definition;this.shadowRoot&&e?.styles?.length&&(this.shadowRoot.adoptedStyleSheets=e.styles.map(a.loadStylesheet))}_mountTemplate(){let e=this._component.templateResult;e&&this._applyResult(e)}_scheduleMountCallbacks(){if(this._component.mountCallbacks.length===0)return;let e=this._component.generation;queueMicrotask(()=>{if(this._isStale(e))return;let t=this._component.mountCallbacks.splice(0);for(let e of t)try{let t={element:this,formResetCallbacks:[],mountCallbacks:[]};this._component.scope.run(()=>{n.runWithContext(t,()=>{let t=e();typeof t==`function`&&n.onCleanup(t)})}),t.mountCallbacks.length>0&&this._component.mountCallbacks.push(...t.mountCallbacks),t.formResetCallbacks.length>0&&this._component.formResetCallbacks.push(...t.formResetCallbacks)}catch(e){this._handleSetupError(e,`mounted`)}this._component.mountCallbacks.length>0&&this._scheduleMountCallbacks()})}};exports.BaseElement=c;
|
|
1
|
+
const e=require("./_dev.cjs"),t=require("./errors.cjs");require("./types/bindings.cjs");const n=require("./runtime.cjs"),r=require("./props.cjs"),i=require("./types.cjs"),a=require("./utils/css.cjs");let o=require("@vielzeug/ripple");var s=()=>({formResetCallbacks:[],generation:0,mountCallbacks:[],phase:i.ComponentPhase.UNINITIALIZED,scope:(0,o.scope)(),templateResult:null}),c=class extends HTMLElement{static _definition;static _normalizedPropDefs;static formAssociated=!1;static observedAttributes=[];_component;constructor(){super();let e=this.constructor._definition;e?.shadow!==!1&&this.attachShadow({mode:`open`,...e?.shadow}),this._component=s()}connectedCallback(){(0,o.untrack)(()=>{this._component.phase===i.ComponentPhase.UNINITIALIZED&&this._runSetup(),this._init()}),this.dispatchEvent(new CustomEvent(i.LIFECYCLE_EVENTS.CONNECT,{bubbles:!1,composed:!1}))}attributeChangedCallback(e,t,n){if(t===n)return;let i=r.getPropMeta(this,e);if(!i)return;let a=i.parse(n);Object.is((0,o.untrack)(()=>i.signal.value),a)||(i.signal.value=a)}disconnectedCallback(){this._component.generation++,this._component.phase=i.ComponentPhase.UNMOUNTED,this.dispatchEvent(new CustomEvent(i.LIFECYCLE_EVENTS.DISCONNECT,{bubbles:!1,composed:!1})),this._component.scope.dispose(),this._component.formResetCallbacks=[],this._component.mountCallbacks=[],this._component.phase=i.ComponentPhase.UNINITIALIZED,this._component.scope=(0,o.scope)(),this._component.templateResult=null}formResetCallback(){for(let e of this._component.formResetCallbacks)try{e()}catch(e){this._handleSetupError(e,`form-reset`)}}_handleSetupError(e,n=`setup`){let r=e instanceof Error?e:Error(String(e)),i=new t.OreLifecycleError(`<${this.localName}> failed during ${this._component.phase} (${n})`,{cause:r,component:this.localName,phase:n}),a=this.constructor._definition;if(a?.onError)try{return a.onError(i,this)}catch{}t.reportRuntimeError(i,this)}_runSetup(){this._component.phase=i.ComponentPhase.SETUP_RUNNING;let e=this.constructor._definition,t=this.constructor._normalizedPropDefs,a={element:this,formResetCallbacks:[],mountCallbacks:[]};try{let o;if(this._component.scope.run(()=>{o=n.runWithContext(a,()=>{let n=t?r.createProps(this,t):{};return e.setup(n)})}),this._component.mountCallbacks.push(...a.mountCallbacks),this._component.formResetCallbacks.push(...a.formResetCallbacks),o!=null&&typeof o.then==`function`){let t=this._component.mountCallbacks.splice(0);this._component.phase=i.ComponentPhase.LOADING,e.loading&&(this._component.templateResult=e.loading()),this._runSetupAsync(o,t,this._component.generation)}else this._component.templateResult=o??null,this._component.phase=i.ComponentPhase.SETUP_DONE}catch(e){let t=this._handleSetupError(e);if(t)this._component.templateResult=t,this._component.phase=i.ComponentPhase.SETUP_DONE;else throw this._component.phase=i.ComponentPhase.UNINITIALIZED,e}}_isStale(e){return this._component.generation!==e||!this.isConnected}async _runSetupAsync(t,n,r){try{let a=await t;if(this._isStale(r)){e.warn(`<${this.localName}> async setup result discarded — element disconnected before setup resolved.`);return}this._component.templateResult=a??null,this._component.phase=i.ComponentPhase.SETUP_DONE,this._component.mountCallbacks.push(...n),a&&this._applyResult(a),this._scheduleMountCallbacks()}catch(t){if(this._isStale(r)){e.warn(`<${this.localName}> async setup error discarded — element disconnected before setup resolved.`);return}let n=this._handleSetupError(t,`async-setup`);n?(this._component.templateResult=n,this._component.phase=i.ComponentPhase.SETUP_DONE,this._applyResult(n)):this._component.phase=i.ComponentPhase.UNINITIALIZED}}_applyResult(e){e&&((this.shadowRoot??this).replaceChildren(e.fragment),this._component.scope.run(()=>{e.apply(n.onCleanup)}))}_init(){this._applyStyles(),this._mountTemplate(),this._component.phase===i.ComponentPhase.SETUP_DONE&&this._scheduleMountCallbacks()}_applyStyles(){let e=this.constructor._definition;this.shadowRoot&&e?.styles?.length&&(this.shadowRoot.adoptedStyleSheets=e.styles.map(a.loadStylesheet))}_mountTemplate(){let e=this._component.templateResult;e&&this._applyResult(e)}_scheduleMountCallbacks(){if(this._component.mountCallbacks.length===0)return;let e=this._component.generation,t=n.beginPendingWork();queueMicrotask(()=>{try{if(this._isStale(e))return;let t=this._component.mountCallbacks.splice(0);for(let e of t)try{let t={element:this,formResetCallbacks:[],mountCallbacks:[]};this._component.scope.run(()=>{n.runWithContext(t,()=>{let t=e();typeof t==`function`&&n.onCleanup(t)})}),t.mountCallbacks.length>0&&this._component.mountCallbacks.push(...t.mountCallbacks),t.formResetCallbacks.length>0&&this._component.formResetCallbacks.push(...t.formResetCallbacks)}catch(e){this._handleSetupError(e,`mounted`)}this._component.mountCallbacks.length>0&&this._scheduleMountCallbacks()}finally{t()}})}};exports.BaseElement=c;
|
|
2
2
|
//# sourceMappingURL=base-element.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-element.cjs","names":[],"sources":["../src/base-element.ts"],"sourcesContent":["import { scope as _scope, type Scope, untrack } from '@vielzeug/ripple';\n\nimport type { ComponentDefinition } from './component-types';\n\nimport { warn } from './_dev';\nimport { type OreErrorPhase, OreLifecycleError, reportRuntimeError } from './errors';\nimport { createProps, getPropMeta, type InferProps, type PropInputDefs, type PropsDef } from './props';\nimport {\n type OnFormResetCallback,\n type OnMountedCallback,\n onCleanup,\n type RuntimeContext,\n runWithContext,\n} from './runtime';\nimport { ComponentPhase, LIFECYCLE_EVENTS } from './types';\nimport { type HTMLResult } from './types/bindings';\nimport { loadStylesheet } from './utils/css';\n\n// ─── Internal component state ─────────────────────────────────────────────────\n\ntype ComponentState = {\n /** Registered via `onFormReset()` — persists across mount callbacks, unlike `mountCallbacks`. */\n formResetCallbacks: OnFormResetCallback[];\n /** Incremented on every disconnect — guards both mount callbacks and async setup results. */\n generation: number;\n mountCallbacks: OnMountedCallback[];\n phase: ComponentPhase;\n scope: Scope;\n templateResult: HTMLResult | null;\n};\n\nconst createComponentState = (): ComponentState => ({\n formResetCallbacks: [],\n generation: 0,\n mountCallbacks: [],\n phase: ComponentPhase.UNINITIALIZED,\n scope: _scope(),\n templateResult: null,\n});\n\n// ─── BaseElement ──────────────────────────────────────────────────────────────\n\n/**\n * Phase transitions:\n *\n * ```\n * UNINITIALIZED ──_runSetup()──┬─(sync result)──► SETUP_DONE\n * └─(promise)──────► LOADING ──(resolves)──► SETUP_DONE\n * SETUP_DONE / LOADING ──disconnectedCallback()──► UNMOUNTED ──(reset)──► UNINITIALIZED\n * ```\n *\n * `generation` increments on every disconnect and is captured before an async\n * `setup()` is awaited; if it no longer matches when the promise settles, the\n * element was disconnected (and possibly reconnected) in the meantime and the\n * result is discarded (see `_isStale`).\n *\n * Why this lives on the class instead of a standalone pure reducer: every\n * transition here is triggered by running actual user code (`def.setup()`,\n * `onMounted` callbacks) inside a reactive `scope.run()` + `runWithContext()`\n * — there is no meaningful \"decide the next phase\" step that can be separated\n * from \"run the side-effecting thing that produces the phase change\" without\n * introducing a data-only effect-description layer that this package has no\n * other use for. That's why the methods below stay as direct, readable\n * procedural steps instead of a reducer + effect interpreter.\n */\nexport class BaseElement extends HTMLElement {\n static _definition: ComponentDefinition<any>;\n static _normalizedPropDefs: PropsDef<Record<never, never>> | undefined;\n static formAssociated = false;\n static observedAttributes: string[] = [];\n\n private _component: ComponentState;\n\n constructor() {\n super();\n\n const def = (this.constructor as typeof BaseElement)._definition;\n\n if (def?.shadow !== false) {\n this.attachShadow({ mode: 'open', ...(def?.shadow as Partial<ShadowRootInit> | undefined) });\n }\n\n this._component = createComponentState();\n }\n\n connectedCallback(): void {\n untrack(() => {\n if (this._component.phase === ComponentPhase.UNINITIALIZED) this._runSetup();\n\n this._init();\n });\n this.dispatchEvent(new CustomEvent(LIFECYCLE_EVENTS.CONNECT, { bubbles: false, composed: false }));\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n\n const propMeta = getPropMeta(this, name);\n\n if (!propMeta) return;\n\n const parsed = propMeta.parse(newValue);\n\n if (\n !Object.is(\n untrack(() => propMeta.signal.value),\n parsed,\n )\n )\n propMeta.signal.value = parsed as never;\n }\n\n disconnectedCallback(): void {\n this._component.generation++;\n this._component.phase = ComponentPhase.UNMOUNTED;\n this.dispatchEvent(new CustomEvent(LIFECYCLE_EVENTS.DISCONNECT, { bubbles: false, composed: false }));\n this._component.scope.dispose();\n // Reset mutable fields for next connect, keeping the same object for stable references\n this._component.formResetCallbacks = [];\n this._component.mountCallbacks = [];\n this._component.phase = ComponentPhase.UNINITIALIZED;\n this._component.scope = _scope();\n this._component.templateResult = null;\n }\n\n /**\n * Native form-association lifecycle callback — the browser calls this on every\n * `formAssociated: true` element inside a `<form>` when that form is reset.\n * Runs every `onFormReset()` callback registered during `setup()`.\n */\n formResetCallback(): void {\n for (const callback of this._component.formResetCallbacks) {\n try {\n callback();\n } catch (error) {\n this._handleSetupError(error, 'form-reset');\n }\n }\n }\n\n private _handleSetupError(error: unknown, phase: OreErrorPhase = 'setup'): HTMLResult | void {\n const err = error instanceof Error ? error : new Error(String(error));\n const oreError = new OreLifecycleError(`<${this.localName}> failed during ${this._component.phase} (${phase})`, {\n cause: err,\n component: this.localName,\n phase,\n });\n const def = (this.constructor as typeof BaseElement)._definition;\n\n if (def?.onError) {\n try {\n return def.onError(oreError, this);\n } catch {\n /* fall through */\n }\n }\n\n reportRuntimeError(oreError, this);\n }\n\n private _runSetup(): void {\n this._component.phase = ComponentPhase.SETUP_RUNNING;\n\n const def = (this.constructor as typeof BaseElement)._definition;\n const normalizedPropDefs = (this.constructor as typeof BaseElement)._normalizedPropDefs;\n const ctx: RuntimeContext = { element: this, formResetCallbacks: [], mountCallbacks: [] };\n\n try {\n let setupResult: HTMLResult | null | Promise<HTMLResult | null> | undefined;\n\n this._component.scope.run(() => {\n setupResult = runWithContext(ctx, () => {\n const setupProps = normalizedPropDefs\n ? createProps(this, normalizedPropDefs)\n : ({} as InferProps<PropInputDefs>);\n\n return def.setup(setupProps as InferProps<PropInputDefs>);\n });\n });\n this._component.mountCallbacks.push(...ctx.mountCallbacks);\n this._component.formResetCallbacks.push(...ctx.formResetCallbacks);\n\n if (setupResult != null && typeof (setupResult as Promise<HTMLResult | null>).then === 'function') {\n // Async setup: show loading template immediately, swap when resolved.\n // Store captured mount callbacks; they will be scheduled after the real template mounts.\n const pendingCallbacks = this._component.mountCallbacks.splice(0);\n\n this._component.phase = ComponentPhase.LOADING;\n\n if (def.loading) {\n this._component.templateResult = def.loading();\n }\n\n // Capture the current generation so the async handler can detect staleness:\n // if the element disconnects+reconnects before the promise resolves, generation is\n // incremented and the old promise result must be discarded.\n void this._runSetupAsync(\n setupResult as Promise<HTMLResult | null>,\n pendingCallbacks,\n this._component.generation,\n );\n } else {\n this._component.templateResult = (setupResult as HTMLResult | null) ?? null;\n this._component.phase = ComponentPhase.SETUP_DONE;\n }\n } catch (error) {\n const recoveryTemplate = this._handleSetupError(error);\n\n if (recoveryTemplate) {\n this._component.templateResult = recoveryTemplate;\n this._component.phase = ComponentPhase.SETUP_DONE;\n } else {\n this._component.phase = ComponentPhase.UNINITIALIZED;\n throw error;\n }\n }\n }\n\n /**\n * True once the element has disconnected (and possibly reconnected) since\n * `capturedGeneration` was recorded — an in-flight async `setup()` result\n * arriving after that point is for an instance state that no longer exists.\n */\n private _isStale(capturedGeneration: number): boolean {\n return this._component.generation !== capturedGeneration || !this.isConnected;\n }\n\n private async _runSetupAsync(\n promise: Promise<HTMLResult | null>,\n pendingCallbacks: OnMountedCallback[],\n capturedGeneration: number,\n ): Promise<void> {\n try {\n const result = await promise;\n\n if (this._isStale(capturedGeneration)) {\n warn(`<${this.localName}> async setup result discarded — element disconnected before setup resolved.`);\n\n return;\n }\n\n this._component.templateResult = result ?? null;\n this._component.phase = ComponentPhase.SETUP_DONE;\n this._component.mountCallbacks.push(...pendingCallbacks);\n\n if (result) this._applyResult(result);\n\n this._scheduleMountCallbacks();\n } catch (error) {\n if (this._isStale(capturedGeneration)) {\n warn(`<${this.localName}> async setup error discarded — element disconnected before setup resolved.`);\n\n return;\n }\n\n const recovery = this._handleSetupError(error, 'async-setup');\n\n if (recovery) {\n this._component.templateResult = recovery;\n this._component.phase = ComponentPhase.SETUP_DONE;\n this._applyResult(recovery);\n } else {\n this._component.phase = ComponentPhase.UNINITIALIZED;\n }\n }\n }\n\n private _applyResult(result: HTMLResult | null): void {\n if (!result) return;\n\n const host: Element | ShadowRoot = this.shadowRoot ?? this;\n\n host.replaceChildren(result.fragment);\n this._component.scope.run(() => {\n result.apply(onCleanup);\n });\n }\n\n private _init(): void {\n this._applyStyles();\n this._mountTemplate();\n\n // Only schedule mount callbacks once setup is fully complete (not in LOADING state)\n if (this._component.phase === ComponentPhase.SETUP_DONE) this._scheduleMountCallbacks();\n }\n\n private _applyStyles(): void {\n const def = (this.constructor as typeof BaseElement)._definition;\n\n if (this.shadowRoot && def?.styles?.length) {\n this.shadowRoot.adoptedStyleSheets = def.styles.map(loadStylesheet);\n }\n }\n\n private _mountTemplate(): void {\n const result = this._component.templateResult;\n\n if (!result) return;\n\n this._applyResult(result);\n }\n\n private _scheduleMountCallbacks(): void {\n if (this._component.mountCallbacks.length === 0) return;\n\n const capturedGeneration = this._component.generation;\n\n queueMicrotask(() => {\n if (this._isStale(capturedGeneration)) return;\n\n // Snapshot callbacks so in-loop registrations don't extend this iteration.\n const batch = this._component.mountCallbacks.splice(0);\n\n for (const callback of batch) {\n try {\n const nestedCtx = {\n element: this,\n formResetCallbacks: [] as typeof this._component.formResetCallbacks,\n mountCallbacks: [] as typeof this._component.mountCallbacks,\n };\n\n this._component.scope.run(() => {\n runWithContext(nestedCtx, () => {\n const cleanup = callback();\n\n if (typeof cleanup === 'function') onCleanup(cleanup);\n });\n });\n\n if (nestedCtx.mountCallbacks.length > 0) {\n this._component.mountCallbacks.push(...nestedCtx.mountCallbacks);\n }\n\n if (nestedCtx.formResetCallbacks.length > 0) {\n this._component.formResetCallbacks.push(...nestedCtx.formResetCallbacks);\n }\n } catch (error) {\n this._handleSetupError(error, 'mounted');\n }\n }\n\n // If nested onMounted calls registered new callbacks, schedule them with\n // a fresh token check in the next microtask.\n if (this._component.mountCallbacks.length > 0) {\n this._scheduleMountCallbacks();\n }\n });\n }\n}\n"],"mappings":"0OA+BA,IAAM,OAA8C,CAClD,mBAAoB,CAAC,EACrB,WAAY,EACZ,eAAgB,CAAC,EACjB,MAAO,EAAA,eAAe,cACtB,OAAA,EAAA,EAAA,MAAA,CAAc,EACd,eAAgB,IAClB,GA2Ba,EAAb,cAAiC,WAAY,CAC3C,OAAO,YACP,OAAO,oBACP,OAAO,eAAiB,GACxB,OAAO,mBAA+B,CAAC,EAEvC,WAEA,aAAc,CACZ,MAAM,EAEN,IAAM,EAAO,KAAK,YAAmC,YAEjD,GAAK,SAAW,IAClB,KAAK,aAAa,CAAE,KAAM,OAAQ,GAAI,GAAK,MAA+C,CAAC,EAG7F,KAAK,WAAa,EAAqB,CACzC,CAEA,mBAA0B,EACxB,EAAA,EAAA,QAAA,KAAc,CACR,KAAK,WAAW,QAAU,EAAA,eAAe,eAAe,KAAK,UAAU,EAE3E,KAAK,MAAM,CACb,CAAC,EACD,KAAK,cAAc,IAAI,YAAY,EAAA,iBAAiB,QAAS,CAAE,QAAS,GAAO,SAAU,EAAM,CAAC,CAAC,CACnG,CAEA,yBAAyB,EAAc,EAAyB,EAA+B,CAC7F,GAAI,IAAa,EAAU,OAE3B,IAAM,EAAW,EAAA,YAAY,KAAM,CAAI,EAEvC,GAAI,CAAC,EAAU,OAEf,IAAM,EAAS,EAAS,MAAM,CAAQ,EAGnC,OAAO,IAAA,EAAA,EAAA,QAAA,KACQ,EAAS,OAAO,KAAK,EACnC,CACF,IAEA,EAAS,OAAO,MAAQ,EAC5B,CAEA,sBAA6B,CAC3B,KAAK,WAAW,aAChB,KAAK,WAAW,MAAQ,EAAA,eAAe,UACvC,KAAK,cAAc,IAAI,YAAY,EAAA,iBAAiB,WAAY,CAAE,QAAS,GAAO,SAAU,EAAM,CAAC,CAAC,EACpG,KAAK,WAAW,MAAM,QAAQ,EAE9B,KAAK,WAAW,mBAAqB,CAAC,EACtC,KAAK,WAAW,eAAiB,CAAC,EAClC,KAAK,WAAW,MAAQ,EAAA,eAAe,cACvC,KAAK,WAAW,OAAA,EAAA,EAAA,MAAA,CAAe,EAC/B,KAAK,WAAW,eAAiB,IACnC,CAOA,mBAA0B,CACxB,IAAK,IAAM,KAAY,KAAK,WAAW,mBACrC,GAAI,CACF,EAAS,CACX,OAAS,EAAO,CACd,KAAK,kBAAkB,EAAO,YAAY,CAC5C,CAEJ,CAEA,kBAA0B,EAAgB,EAAuB,QAA4B,CAC3F,IAAM,EAAM,aAAiB,MAAQ,EAAY,MAAM,OAAO,CAAK,CAAC,EAC9D,EAAW,IAAI,EAAA,kBAAkB,IAAI,KAAK,UAAU,kBAAkB,KAAK,WAAW,MAAM,IAAI,EAAM,GAAI,CAC9G,MAAO,EACP,UAAW,KAAK,UAChB,OACF,CAAC,EACK,EAAO,KAAK,YAAmC,YAErD,GAAI,GAAK,QACP,GAAI,CACF,OAAO,EAAI,QAAQ,EAAU,IAAI,CACnC,MAAQ,CAER,CAGF,EAAA,mBAAmB,EAAU,IAAI,CACnC,CAEA,WAA0B,CACxB,KAAK,WAAW,MAAQ,EAAA,eAAe,cAEvC,IAAM,EAAO,KAAK,YAAmC,YAC/C,EAAsB,KAAK,YAAmC,oBAC9D,EAAsB,CAAE,QAAS,KAAM,mBAAoB,CAAC,EAAG,eAAgB,CAAC,CAAE,EAExF,GAAI,CACF,IAAI,EAcJ,GAZA,KAAK,WAAW,MAAM,QAAU,CAC9B,EAAc,EAAA,eAAe,MAAW,CACtC,IAAM,EAAa,EACf,EAAA,YAAY,KAAM,CAAkB,EACnC,CAAC,EAEN,OAAO,EAAI,MAAM,CAAuC,CAC1D,CAAC,CACH,CAAC,EACD,KAAK,WAAW,eAAe,KAAK,GAAG,EAAI,cAAc,EACzD,KAAK,WAAW,mBAAmB,KAAK,GAAG,EAAI,kBAAkB,EAE7D,GAAe,MAAQ,OAAQ,EAA2C,MAAS,WAAY,CAGjG,IAAM,EAAmB,KAAK,WAAW,eAAe,OAAO,CAAC,EAEhE,KAAK,WAAW,MAAQ,EAAA,eAAe,QAEnC,EAAI,UACN,KAAK,WAAW,eAAiB,EAAI,QAAQ,GAM/C,KAAU,eACR,EACA,EACA,KAAK,WAAW,UAClB,CACF,KACE,MAAK,WAAW,eAAkB,GAAqC,KACvE,KAAK,WAAW,MAAQ,EAAA,eAAe,UAE3C,OAAS,EAAO,CACd,IAAM,EAAmB,KAAK,kBAAkB,CAAK,EAErD,GAAI,EACF,KAAK,WAAW,eAAiB,EACjC,KAAK,WAAW,MAAQ,EAAA,eAAe,gBAGvC,KADA,MAAK,WAAW,MAAQ,EAAA,eAAe,cACjC,CAEV,CACF,CAOA,SAAiB,EAAqC,CACpD,OAAO,KAAK,WAAW,aAAe,GAAsB,CAAC,KAAK,WACpE,CAEA,MAAc,eACZ,EACA,EACA,EACe,CACf,GAAI,CACF,IAAM,EAAS,MAAM,EAErB,GAAI,KAAK,SAAS,CAAkB,EAAG,CACrC,EAAA,KAAK,IAAI,KAAK,UAAU,6EAA6E,EAErG,MACF,CAEA,KAAK,WAAW,eAAiB,GAAU,KAC3C,KAAK,WAAW,MAAQ,EAAA,eAAe,WACvC,KAAK,WAAW,eAAe,KAAK,GAAG,CAAgB,EAEnD,GAAQ,KAAK,aAAa,CAAM,EAEpC,KAAK,wBAAwB,CAC/B,OAAS,EAAO,CACd,GAAI,KAAK,SAAS,CAAkB,EAAG,CACrC,EAAA,KAAK,IAAI,KAAK,UAAU,4EAA4E,EAEpG,MACF,CAEA,IAAM,EAAW,KAAK,kBAAkB,EAAO,aAAa,EAExD,GACF,KAAK,WAAW,eAAiB,EACjC,KAAK,WAAW,MAAQ,EAAA,eAAe,WACvC,KAAK,aAAa,CAAQ,GAE1B,KAAK,WAAW,MAAQ,EAAA,eAAe,aAE3C,CACF,CAEA,aAAqB,EAAiC,CAC/C,KAE8B,KAAK,YAAc,KAAA,CAEjD,gBAAgB,EAAO,QAAQ,EACpC,KAAK,WAAW,MAAM,QAAU,CAC9B,EAAO,MAAM,EAAA,SAAS,CACxB,CAAC,EACH,CAEA,OAAsB,CACpB,KAAK,aAAa,EAClB,KAAK,eAAe,EAGhB,KAAK,WAAW,QAAU,EAAA,eAAe,YAAY,KAAK,wBAAwB,CACxF,CAEA,cAA6B,CAC3B,IAAM,EAAO,KAAK,YAAmC,YAEjD,KAAK,YAAc,GAAK,QAAQ,SAClC,KAAK,WAAW,mBAAqB,EAAI,OAAO,IAAI,EAAA,cAAc,EAEtE,CAEA,gBAA+B,CAC7B,IAAM,EAAS,KAAK,WAAW,eAE1B,GAEL,KAAK,aAAa,CAAM,CAC1B,CAEA,yBAAwC,CACtC,GAAI,KAAK,WAAW,eAAe,SAAW,EAAG,OAEjD,IAAM,EAAqB,KAAK,WAAW,WAE3C,mBAAqB,CACnB,GAAI,KAAK,SAAS,CAAkB,EAAG,OAGvC,IAAM,EAAQ,KAAK,WAAW,eAAe,OAAO,CAAC,EAErD,IAAK,IAAM,KAAY,EACrB,GAAI,CACF,IAAM,EAAY,CAChB,QAAS,KACT,mBAAoB,CAAC,EACrB,eAAgB,CAAC,CACnB,EAEA,KAAK,WAAW,MAAM,QAAU,CAC9B,EAAA,eAAe,MAAiB,CAC9B,IAAM,EAAU,EAAS,EAErB,OAAO,GAAY,YAAY,EAAA,UAAU,CAAO,CACtD,CAAC,CACH,CAAC,EAEG,EAAU,eAAe,OAAS,GACpC,KAAK,WAAW,eAAe,KAAK,GAAG,EAAU,cAAc,EAG7D,EAAU,mBAAmB,OAAS,GACxC,KAAK,WAAW,mBAAmB,KAAK,GAAG,EAAU,kBAAkB,CAE3E,OAAS,EAAO,CACd,KAAK,kBAAkB,EAAO,SAAS,CACzC,CAKE,KAAK,WAAW,eAAe,OAAS,GAC1C,KAAK,wBAAwB,CAEjC,CAAC,CACH,CACF"}
|
|
1
|
+
{"version":3,"file":"base-element.cjs","names":[],"sources":["../src/base-element.ts"],"sourcesContent":["import { scope as _scope, type Scope, untrack } from '@vielzeug/ripple';\n\nimport type { ComponentDefinition } from './component-types';\n\nimport { warn } from './_dev';\nimport { type OreErrorPhase, OreLifecycleError, reportRuntimeError } from './errors';\nimport { createProps, getPropMeta, type InferProps, type PropInputDefs, type PropsDef } from './props';\nimport {\n beginPendingWork,\n type OnFormResetCallback,\n type OnMountedCallback,\n onCleanup,\n type RuntimeContext,\n runWithContext,\n} from './runtime';\nimport { ComponentPhase, LIFECYCLE_EVENTS } from './types';\nimport { type HTMLResult } from './types/bindings';\nimport { loadStylesheet } from './utils/css';\n\n// ─── Internal component state ─────────────────────────────────────────────────\n\ntype ComponentState = {\n /** Registered via `onFormReset()` — persists across mount callbacks, unlike `mountCallbacks`. */\n formResetCallbacks: OnFormResetCallback[];\n /** Incremented on every disconnect — guards both mount callbacks and async setup results. */\n generation: number;\n mountCallbacks: OnMountedCallback[];\n phase: ComponentPhase;\n scope: Scope;\n templateResult: HTMLResult | null;\n};\n\nconst createComponentState = (): ComponentState => ({\n formResetCallbacks: [],\n generation: 0,\n mountCallbacks: [],\n phase: ComponentPhase.UNINITIALIZED,\n scope: _scope(),\n templateResult: null,\n});\n\n// ─── BaseElement ──────────────────────────────────────────────────────────────\n\n/**\n * Phase transitions:\n *\n * ```\n * UNINITIALIZED ──_runSetup()──┬─(sync result)──► SETUP_DONE\n * └─(promise)──────► LOADING ──(resolves)──► SETUP_DONE\n * SETUP_DONE / LOADING ──disconnectedCallback()──► UNMOUNTED ──(reset)──► UNINITIALIZED\n * ```\n *\n * `generation` increments on every disconnect and is captured before an async\n * `setup()` is awaited; if it no longer matches when the promise settles, the\n * element was disconnected (and possibly reconnected) in the meantime and the\n * result is discarded (see `_isStale`).\n *\n * Why this lives on the class instead of a standalone pure reducer: every\n * transition here is triggered by running actual user code (`def.setup()`,\n * `onMounted` callbacks) inside a reactive `scope.run()` + `runWithContext()`\n * — there is no meaningful \"decide the next phase\" step that can be separated\n * from \"run the side-effecting thing that produces the phase change\" without\n * introducing a data-only effect-description layer that this package has no\n * other use for. That's why the methods below stay as direct, readable\n * procedural steps instead of a reducer + effect interpreter.\n */\nexport class BaseElement extends HTMLElement {\n static _definition: ComponentDefinition<any>;\n static _normalizedPropDefs: PropsDef<Record<never, never>> | undefined;\n static formAssociated = false;\n static observedAttributes: string[] = [];\n\n private _component: ComponentState;\n\n constructor() {\n super();\n\n const def = (this.constructor as typeof BaseElement)._definition;\n\n if (def?.shadow !== false) {\n this.attachShadow({ mode: 'open', ...(def?.shadow as Partial<ShadowRootInit> | undefined) });\n }\n\n this._component = createComponentState();\n }\n\n connectedCallback(): void {\n untrack(() => {\n if (this._component.phase === ComponentPhase.UNINITIALIZED) this._runSetup();\n\n this._init();\n });\n this.dispatchEvent(new CustomEvent(LIFECYCLE_EVENTS.CONNECT, { bubbles: false, composed: false }));\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n\n const propMeta = getPropMeta(this, name);\n\n if (!propMeta) return;\n\n const parsed = propMeta.parse(newValue);\n\n if (\n !Object.is(\n untrack(() => propMeta.signal.value),\n parsed,\n )\n )\n propMeta.signal.value = parsed as never;\n }\n\n disconnectedCallback(): void {\n this._component.generation++;\n this._component.phase = ComponentPhase.UNMOUNTED;\n this.dispatchEvent(new CustomEvent(LIFECYCLE_EVENTS.DISCONNECT, { bubbles: false, composed: false }));\n this._component.scope.dispose();\n // Reset mutable fields for next connect, keeping the same object for stable references\n this._component.formResetCallbacks = [];\n this._component.mountCallbacks = [];\n this._component.phase = ComponentPhase.UNINITIALIZED;\n this._component.scope = _scope();\n this._component.templateResult = null;\n }\n\n /**\n * Native form-association lifecycle callback — the browser calls this on every\n * `formAssociated: true` element inside a `<form>` when that form is reset.\n * Runs every `onFormReset()` callback registered during `setup()`.\n */\n formResetCallback(): void {\n for (const callback of this._component.formResetCallbacks) {\n try {\n callback();\n } catch (error) {\n this._handleSetupError(error, 'form-reset');\n }\n }\n }\n\n private _handleSetupError(error: unknown, phase: OreErrorPhase = 'setup'): HTMLResult | void {\n const err = error instanceof Error ? error : new Error(String(error));\n const oreError = new OreLifecycleError(`<${this.localName}> failed during ${this._component.phase} (${phase})`, {\n cause: err,\n component: this.localName,\n phase,\n });\n const def = (this.constructor as typeof BaseElement)._definition;\n\n if (def?.onError) {\n try {\n return def.onError(oreError, this);\n } catch {\n /* fall through */\n }\n }\n\n reportRuntimeError(oreError, this);\n }\n\n private _runSetup(): void {\n this._component.phase = ComponentPhase.SETUP_RUNNING;\n\n const def = (this.constructor as typeof BaseElement)._definition;\n const normalizedPropDefs = (this.constructor as typeof BaseElement)._normalizedPropDefs;\n const ctx: RuntimeContext = { element: this, formResetCallbacks: [], mountCallbacks: [] };\n\n try {\n let setupResult: HTMLResult | null | Promise<HTMLResult | null> | undefined;\n\n this._component.scope.run(() => {\n setupResult = runWithContext(ctx, () => {\n const setupProps = normalizedPropDefs\n ? createProps(this, normalizedPropDefs)\n : ({} as InferProps<PropInputDefs>);\n\n return def.setup(setupProps as InferProps<PropInputDefs>);\n });\n });\n this._component.mountCallbacks.push(...ctx.mountCallbacks);\n this._component.formResetCallbacks.push(...ctx.formResetCallbacks);\n\n if (setupResult != null && typeof (setupResult as Promise<HTMLResult | null>).then === 'function') {\n // Async setup: show loading template immediately, swap when resolved.\n // Store captured mount callbacks; they will be scheduled after the real template mounts.\n const pendingCallbacks = this._component.mountCallbacks.splice(0);\n\n this._component.phase = ComponentPhase.LOADING;\n\n if (def.loading) {\n this._component.templateResult = def.loading();\n }\n\n // Capture the current generation so the async handler can detect staleness:\n // if the element disconnects+reconnects before the promise resolves, generation is\n // incremented and the old promise result must be discarded.\n //\n // Deliberately NOT tracked via beginPendingWork(): the awaited promise is\n // arbitrary, user-controlled application code (e.g. a real network fetch) with no\n // upper bound — the LOADING template it renders in the meantime is a valid, stable\n // state, not a transient one to wait out. `testing/flush()` only needs to wait for\n // ore's own bounded internal scheduling (queueMicrotask'd mount callbacks below),\n // never for how long a consumer's setup() takes to resolve.\n void this._runSetupAsync(\n setupResult as Promise<HTMLResult | null>,\n pendingCallbacks,\n this._component.generation,\n );\n } else {\n this._component.templateResult = (setupResult as HTMLResult | null) ?? null;\n this._component.phase = ComponentPhase.SETUP_DONE;\n }\n } catch (error) {\n const recoveryTemplate = this._handleSetupError(error);\n\n if (recoveryTemplate) {\n this._component.templateResult = recoveryTemplate;\n this._component.phase = ComponentPhase.SETUP_DONE;\n } else {\n this._component.phase = ComponentPhase.UNINITIALIZED;\n throw error;\n }\n }\n }\n\n /**\n * True once the element has disconnected (and possibly reconnected) since\n * `capturedGeneration` was recorded — an in-flight async `setup()` result\n * arriving after that point is for an instance state that no longer exists.\n */\n private _isStale(capturedGeneration: number): boolean {\n return this._component.generation !== capturedGeneration || !this.isConnected;\n }\n\n private async _runSetupAsync(\n promise: Promise<HTMLResult | null>,\n pendingCallbacks: OnMountedCallback[],\n capturedGeneration: number,\n ): Promise<void> {\n try {\n const result = await promise;\n\n if (this._isStale(capturedGeneration)) {\n warn(`<${this.localName}> async setup result discarded — element disconnected before setup resolved.`);\n\n return;\n }\n\n this._component.templateResult = result ?? null;\n this._component.phase = ComponentPhase.SETUP_DONE;\n this._component.mountCallbacks.push(...pendingCallbacks);\n\n if (result) this._applyResult(result);\n\n this._scheduleMountCallbacks();\n } catch (error) {\n if (this._isStale(capturedGeneration)) {\n warn(`<${this.localName}> async setup error discarded — element disconnected before setup resolved.`);\n\n return;\n }\n\n const recovery = this._handleSetupError(error, 'async-setup');\n\n if (recovery) {\n this._component.templateResult = recovery;\n this._component.phase = ComponentPhase.SETUP_DONE;\n this._applyResult(recovery);\n } else {\n this._component.phase = ComponentPhase.UNINITIALIZED;\n }\n }\n }\n\n private _applyResult(result: HTMLResult | null): void {\n if (!result) return;\n\n const host: Element | ShadowRoot = this.shadowRoot ?? this;\n\n host.replaceChildren(result.fragment);\n this._component.scope.run(() => {\n result.apply(onCleanup);\n });\n }\n\n private _init(): void {\n this._applyStyles();\n this._mountTemplate();\n\n // Only schedule mount callbacks once setup is fully complete (not in LOADING state)\n if (this._component.phase === ComponentPhase.SETUP_DONE) this._scheduleMountCallbacks();\n }\n\n private _applyStyles(): void {\n const def = (this.constructor as typeof BaseElement)._definition;\n\n if (this.shadowRoot && def?.styles?.length) {\n this.shadowRoot.adoptedStyleSheets = def.styles.map(loadStylesheet);\n }\n }\n\n private _mountTemplate(): void {\n const result = this._component.templateResult;\n\n if (!result) return;\n\n this._applyResult(result);\n }\n\n private _scheduleMountCallbacks(): void {\n if (this._component.mountCallbacks.length === 0) return;\n\n const capturedGeneration = this._component.generation;\n // Tracked as pending work for the duration of this microtask — ended in a `finally`\n // so a thrown callback (already caught per-callback below, but defensive here too)\n // never leaves the counter stuck above zero. See runtime.ts's beginPendingWork().\n const endWork = beginPendingWork();\n\n queueMicrotask(() => {\n try {\n if (this._isStale(capturedGeneration)) return;\n\n // Snapshot callbacks so in-loop registrations don't extend this iteration.\n const batch = this._component.mountCallbacks.splice(0);\n\n for (const callback of batch) {\n try {\n const nestedCtx = {\n element: this,\n formResetCallbacks: [] as typeof this._component.formResetCallbacks,\n mountCallbacks: [] as typeof this._component.mountCallbacks,\n };\n\n this._component.scope.run(() => {\n runWithContext(nestedCtx, () => {\n const cleanup = callback();\n\n if (typeof cleanup === 'function') onCleanup(cleanup);\n });\n });\n\n if (nestedCtx.mountCallbacks.length > 0) {\n this._component.mountCallbacks.push(...nestedCtx.mountCallbacks);\n }\n\n if (nestedCtx.formResetCallbacks.length > 0) {\n this._component.formResetCallbacks.push(...nestedCtx.formResetCallbacks);\n }\n } catch (error) {\n this._handleSetupError(error, 'mounted');\n }\n }\n\n // If nested onMounted calls registered new callbacks, schedule them with\n // a fresh token check in the next microtask. This happens *before* endWork()\n // below runs so the counter never dips to zero between the two schedules.\n if (this._component.mountCallbacks.length > 0) {\n this._scheduleMountCallbacks();\n }\n } finally {\n endWork();\n }\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-element.d.ts","sourceRoot":"","sources":["../src/base-element.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAI7D,OAAO,EAAiE,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"base-element.d.ts","sourceRoot":"","sources":["../src/base-element.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAI7D,OAAO,EAAiE,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AAqCvG;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,WAAY,SAAQ,WAAW;IAC1C,MAAM,CAAC,WAAW,EAAE,mBAAmB,CAAC,GAAG,CAAC,CAAC;IAC7C,MAAM,CAAC,mBAAmB,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,GAAG,SAAS,CAAC;IACvE,MAAM,CAAC,cAAc,UAAS;IAC9B,MAAM,CAAC,kBAAkB,EAAE,MAAM,EAAE,CAAM;IAEzC,OAAO,CAAC,UAAU,CAAiB;;IAcnC,iBAAiB,IAAI,IAAI;IASzB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAkB9F,oBAAoB,IAAI,IAAI;IAa5B;;;;OAIG;IACH,iBAAiB,IAAI,IAAI;IAUzB,OAAO,CAAC,iBAAiB;IAoBzB,OAAO,CAAC,SAAS;IAiEjB;;;;OAIG;IACH,OAAO,CAAC,QAAQ;YAIF,cAAc;IAwC5B,OAAO,CAAC,YAAY;IAWpB,OAAO,CAAC,KAAK;IAQb,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,uBAAuB;CAuDhC"}
|
package/dist/base-element.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{warn as e}from"./_dev.js";import{OreLifecycleError as t,reportRuntimeError as n}from"./errors.js";import"./types/bindings.js";import{
|
|
1
|
+
import{warn as e}from"./_dev.js";import{OreLifecycleError as t,reportRuntimeError as n}from"./errors.js";import"./types/bindings.js";import{beginPendingWork as r,onCleanup as i,runWithContext as a}from"./runtime.js";import{createProps as o,getPropMeta as s}from"./props.js";import{ComponentPhase as c,LIFECYCLE_EVENTS as l}from"./types.js";import{loadStylesheet as u}from"./utils/css.js";import{scope as d,untrack as f}from"@vielzeug/ripple";var p=()=>({formResetCallbacks:[],generation:0,mountCallbacks:[],phase:c.UNINITIALIZED,scope:d(),templateResult:null}),m=class extends HTMLElement{static _definition;static _normalizedPropDefs;static formAssociated=!1;static observedAttributes=[];_component;constructor(){super();let e=this.constructor._definition;e?.shadow!==!1&&this.attachShadow({mode:`open`,...e?.shadow}),this._component=p()}connectedCallback(){f(()=>{this._component.phase===c.UNINITIALIZED&&this._runSetup(),this._init()}),this.dispatchEvent(new CustomEvent(l.CONNECT,{bubbles:!1,composed:!1}))}attributeChangedCallback(e,t,n){if(t===n)return;let r=s(this,e);if(!r)return;let i=r.parse(n);Object.is(f(()=>r.signal.value),i)||(r.signal.value=i)}disconnectedCallback(){this._component.generation++,this._component.phase=c.UNMOUNTED,this.dispatchEvent(new CustomEvent(l.DISCONNECT,{bubbles:!1,composed:!1})),this._component.scope.dispose(),this._component.formResetCallbacks=[],this._component.mountCallbacks=[],this._component.phase=c.UNINITIALIZED,this._component.scope=d(),this._component.templateResult=null}formResetCallback(){for(let e of this._component.formResetCallbacks)try{e()}catch(e){this._handleSetupError(e,`form-reset`)}}_handleSetupError(e,r=`setup`){let i=e instanceof Error?e:Error(String(e)),a=new t(`<${this.localName}> failed during ${this._component.phase} (${r})`,{cause:i,component:this.localName,phase:r}),o=this.constructor._definition;if(o?.onError)try{return o.onError(a,this)}catch{}n(a,this)}_runSetup(){this._component.phase=c.SETUP_RUNNING;let e=this.constructor._definition,t=this.constructor._normalizedPropDefs,n={element:this,formResetCallbacks:[],mountCallbacks:[]};try{let r;if(this._component.scope.run(()=>{r=a(n,()=>{let n=t?o(this,t):{};return e.setup(n)})}),this._component.mountCallbacks.push(...n.mountCallbacks),this._component.formResetCallbacks.push(...n.formResetCallbacks),r!=null&&typeof r.then==`function`){let t=this._component.mountCallbacks.splice(0);this._component.phase=c.LOADING,e.loading&&(this._component.templateResult=e.loading()),this._runSetupAsync(r,t,this._component.generation)}else this._component.templateResult=r??null,this._component.phase=c.SETUP_DONE}catch(e){let t=this._handleSetupError(e);if(t)this._component.templateResult=t,this._component.phase=c.SETUP_DONE;else throw this._component.phase=c.UNINITIALIZED,e}}_isStale(e){return this._component.generation!==e||!this.isConnected}async _runSetupAsync(t,n,r){try{let i=await t;if(this._isStale(r)){e(`<${this.localName}> async setup result discarded — element disconnected before setup resolved.`);return}this._component.templateResult=i??null,this._component.phase=c.SETUP_DONE,this._component.mountCallbacks.push(...n),i&&this._applyResult(i),this._scheduleMountCallbacks()}catch(t){if(this._isStale(r)){e(`<${this.localName}> async setup error discarded — element disconnected before setup resolved.`);return}let n=this._handleSetupError(t,`async-setup`);n?(this._component.templateResult=n,this._component.phase=c.SETUP_DONE,this._applyResult(n)):this._component.phase=c.UNINITIALIZED}}_applyResult(e){e&&((this.shadowRoot??this).replaceChildren(e.fragment),this._component.scope.run(()=>{e.apply(i)}))}_init(){this._applyStyles(),this._mountTemplate(),this._component.phase===c.SETUP_DONE&&this._scheduleMountCallbacks()}_applyStyles(){let e=this.constructor._definition;this.shadowRoot&&e?.styles?.length&&(this.shadowRoot.adoptedStyleSheets=e.styles.map(u))}_mountTemplate(){let e=this._component.templateResult;e&&this._applyResult(e)}_scheduleMountCallbacks(){if(this._component.mountCallbacks.length===0)return;let e=this._component.generation,t=r();queueMicrotask(()=>{try{if(this._isStale(e))return;let t=this._component.mountCallbacks.splice(0);for(let e of t)try{let t={element:this,formResetCallbacks:[],mountCallbacks:[]};this._component.scope.run(()=>{a(t,()=>{let t=e();typeof t==`function`&&i(t)})}),t.mountCallbacks.length>0&&this._component.mountCallbacks.push(...t.mountCallbacks),t.formResetCallbacks.length>0&&this._component.formResetCallbacks.push(...t.formResetCallbacks)}catch(e){this._handleSetupError(e,`mounted`)}this._component.mountCallbacks.length>0&&this._scheduleMountCallbacks()}finally{t()}})}};export{m as BaseElement};
|
|
2
2
|
//# sourceMappingURL=base-element.js.map
|
package/dist/base-element.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-element.js","names":[],"sources":["../src/base-element.ts"],"sourcesContent":["import { scope as _scope, type Scope, untrack } from '@vielzeug/ripple';\n\nimport type { ComponentDefinition } from './component-types';\n\nimport { warn } from './_dev';\nimport { type OreErrorPhase, OreLifecycleError, reportRuntimeError } from './errors';\nimport { createProps, getPropMeta, type InferProps, type PropInputDefs, type PropsDef } from './props';\nimport {\n type OnFormResetCallback,\n type OnMountedCallback,\n onCleanup,\n type RuntimeContext,\n runWithContext,\n} from './runtime';\nimport { ComponentPhase, LIFECYCLE_EVENTS } from './types';\nimport { type HTMLResult } from './types/bindings';\nimport { loadStylesheet } from './utils/css';\n\n// ─── Internal component state ─────────────────────────────────────────────────\n\ntype ComponentState = {\n /** Registered via `onFormReset()` — persists across mount callbacks, unlike `mountCallbacks`. */\n formResetCallbacks: OnFormResetCallback[];\n /** Incremented on every disconnect — guards both mount callbacks and async setup results. */\n generation: number;\n mountCallbacks: OnMountedCallback[];\n phase: ComponentPhase;\n scope: Scope;\n templateResult: HTMLResult | null;\n};\n\nconst createComponentState = (): ComponentState => ({\n formResetCallbacks: [],\n generation: 0,\n mountCallbacks: [],\n phase: ComponentPhase.UNINITIALIZED,\n scope: _scope(),\n templateResult: null,\n});\n\n// ─── BaseElement ──────────────────────────────────────────────────────────────\n\n/**\n * Phase transitions:\n *\n * ```\n * UNINITIALIZED ──_runSetup()──┬─(sync result)──► SETUP_DONE\n * └─(promise)──────► LOADING ──(resolves)──► SETUP_DONE\n * SETUP_DONE / LOADING ──disconnectedCallback()──► UNMOUNTED ──(reset)──► UNINITIALIZED\n * ```\n *\n * `generation` increments on every disconnect and is captured before an async\n * `setup()` is awaited; if it no longer matches when the promise settles, the\n * element was disconnected (and possibly reconnected) in the meantime and the\n * result is discarded (see `_isStale`).\n *\n * Why this lives on the class instead of a standalone pure reducer: every\n * transition here is triggered by running actual user code (`def.setup()`,\n * `onMounted` callbacks) inside a reactive `scope.run()` + `runWithContext()`\n * — there is no meaningful \"decide the next phase\" step that can be separated\n * from \"run the side-effecting thing that produces the phase change\" without\n * introducing a data-only effect-description layer that this package has no\n * other use for. That's why the methods below stay as direct, readable\n * procedural steps instead of a reducer + effect interpreter.\n */\nexport class BaseElement extends HTMLElement {\n static _definition: ComponentDefinition<any>;\n static _normalizedPropDefs: PropsDef<Record<never, never>> | undefined;\n static formAssociated = false;\n static observedAttributes: string[] = [];\n\n private _component: ComponentState;\n\n constructor() {\n super();\n\n const def = (this.constructor as typeof BaseElement)._definition;\n\n if (def?.shadow !== false) {\n this.attachShadow({ mode: 'open', ...(def?.shadow as Partial<ShadowRootInit> | undefined) });\n }\n\n this._component = createComponentState();\n }\n\n connectedCallback(): void {\n untrack(() => {\n if (this._component.phase === ComponentPhase.UNINITIALIZED) this._runSetup();\n\n this._init();\n });\n this.dispatchEvent(new CustomEvent(LIFECYCLE_EVENTS.CONNECT, { bubbles: false, composed: false }));\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n\n const propMeta = getPropMeta(this, name);\n\n if (!propMeta) return;\n\n const parsed = propMeta.parse(newValue);\n\n if (\n !Object.is(\n untrack(() => propMeta.signal.value),\n parsed,\n )\n )\n propMeta.signal.value = parsed as never;\n }\n\n disconnectedCallback(): void {\n this._component.generation++;\n this._component.phase = ComponentPhase.UNMOUNTED;\n this.dispatchEvent(new CustomEvent(LIFECYCLE_EVENTS.DISCONNECT, { bubbles: false, composed: false }));\n this._component.scope.dispose();\n // Reset mutable fields for next connect, keeping the same object for stable references\n this._component.formResetCallbacks = [];\n this._component.mountCallbacks = [];\n this._component.phase = ComponentPhase.UNINITIALIZED;\n this._component.scope = _scope();\n this._component.templateResult = null;\n }\n\n /**\n * Native form-association lifecycle callback — the browser calls this on every\n * `formAssociated: true` element inside a `<form>` when that form is reset.\n * Runs every `onFormReset()` callback registered during `setup()`.\n */\n formResetCallback(): void {\n for (const callback of this._component.formResetCallbacks) {\n try {\n callback();\n } catch (error) {\n this._handleSetupError(error, 'form-reset');\n }\n }\n }\n\n private _handleSetupError(error: unknown, phase: OreErrorPhase = 'setup'): HTMLResult | void {\n const err = error instanceof Error ? error : new Error(String(error));\n const oreError = new OreLifecycleError(`<${this.localName}> failed during ${this._component.phase} (${phase})`, {\n cause: err,\n component: this.localName,\n phase,\n });\n const def = (this.constructor as typeof BaseElement)._definition;\n\n if (def?.onError) {\n try {\n return def.onError(oreError, this);\n } catch {\n /* fall through */\n }\n }\n\n reportRuntimeError(oreError, this);\n }\n\n private _runSetup(): void {\n this._component.phase = ComponentPhase.SETUP_RUNNING;\n\n const def = (this.constructor as typeof BaseElement)._definition;\n const normalizedPropDefs = (this.constructor as typeof BaseElement)._normalizedPropDefs;\n const ctx: RuntimeContext = { element: this, formResetCallbacks: [], mountCallbacks: [] };\n\n try {\n let setupResult: HTMLResult | null | Promise<HTMLResult | null> | undefined;\n\n this._component.scope.run(() => {\n setupResult = runWithContext(ctx, () => {\n const setupProps = normalizedPropDefs\n ? createProps(this, normalizedPropDefs)\n : ({} as InferProps<PropInputDefs>);\n\n return def.setup(setupProps as InferProps<PropInputDefs>);\n });\n });\n this._component.mountCallbacks.push(...ctx.mountCallbacks);\n this._component.formResetCallbacks.push(...ctx.formResetCallbacks);\n\n if (setupResult != null && typeof (setupResult as Promise<HTMLResult | null>).then === 'function') {\n // Async setup: show loading template immediately, swap when resolved.\n // Store captured mount callbacks; they will be scheduled after the real template mounts.\n const pendingCallbacks = this._component.mountCallbacks.splice(0);\n\n this._component.phase = ComponentPhase.LOADING;\n\n if (def.loading) {\n this._component.templateResult = def.loading();\n }\n\n // Capture the current generation so the async handler can detect staleness:\n // if the element disconnects+reconnects before the promise resolves, generation is\n // incremented and the old promise result must be discarded.\n void this._runSetupAsync(\n setupResult as Promise<HTMLResult | null>,\n pendingCallbacks,\n this._component.generation,\n );\n } else {\n this._component.templateResult = (setupResult as HTMLResult | null) ?? null;\n this._component.phase = ComponentPhase.SETUP_DONE;\n }\n } catch (error) {\n const recoveryTemplate = this._handleSetupError(error);\n\n if (recoveryTemplate) {\n this._component.templateResult = recoveryTemplate;\n this._component.phase = ComponentPhase.SETUP_DONE;\n } else {\n this._component.phase = ComponentPhase.UNINITIALIZED;\n throw error;\n }\n }\n }\n\n /**\n * True once the element has disconnected (and possibly reconnected) since\n * `capturedGeneration` was recorded — an in-flight async `setup()` result\n * arriving after that point is for an instance state that no longer exists.\n */\n private _isStale(capturedGeneration: number): boolean {\n return this._component.generation !== capturedGeneration || !this.isConnected;\n }\n\n private async _runSetupAsync(\n promise: Promise<HTMLResult | null>,\n pendingCallbacks: OnMountedCallback[],\n capturedGeneration: number,\n ): Promise<void> {\n try {\n const result = await promise;\n\n if (this._isStale(capturedGeneration)) {\n warn(`<${this.localName}> async setup result discarded — element disconnected before setup resolved.`);\n\n return;\n }\n\n this._component.templateResult = result ?? null;\n this._component.phase = ComponentPhase.SETUP_DONE;\n this._component.mountCallbacks.push(...pendingCallbacks);\n\n if (result) this._applyResult(result);\n\n this._scheduleMountCallbacks();\n } catch (error) {\n if (this._isStale(capturedGeneration)) {\n warn(`<${this.localName}> async setup error discarded — element disconnected before setup resolved.`);\n\n return;\n }\n\n const recovery = this._handleSetupError(error, 'async-setup');\n\n if (recovery) {\n this._component.templateResult = recovery;\n this._component.phase = ComponentPhase.SETUP_DONE;\n this._applyResult(recovery);\n } else {\n this._component.phase = ComponentPhase.UNINITIALIZED;\n }\n }\n }\n\n private _applyResult(result: HTMLResult | null): void {\n if (!result) return;\n\n const host: Element | ShadowRoot = this.shadowRoot ?? this;\n\n host.replaceChildren(result.fragment);\n this._component.scope.run(() => {\n result.apply(onCleanup);\n });\n }\n\n private _init(): void {\n this._applyStyles();\n this._mountTemplate();\n\n // Only schedule mount callbacks once setup is fully complete (not in LOADING state)\n if (this._component.phase === ComponentPhase.SETUP_DONE) this._scheduleMountCallbacks();\n }\n\n private _applyStyles(): void {\n const def = (this.constructor as typeof BaseElement)._definition;\n\n if (this.shadowRoot && def?.styles?.length) {\n this.shadowRoot.adoptedStyleSheets = def.styles.map(loadStylesheet);\n }\n }\n\n private _mountTemplate(): void {\n const result = this._component.templateResult;\n\n if (!result) return;\n\n this._applyResult(result);\n }\n\n private _scheduleMountCallbacks(): void {\n if (this._component.mountCallbacks.length === 0) return;\n\n const capturedGeneration = this._component.generation;\n\n queueMicrotask(() => {\n if (this._isStale(capturedGeneration)) return;\n\n // Snapshot callbacks so in-loop registrations don't extend this iteration.\n const batch = this._component.mountCallbacks.splice(0);\n\n for (const callback of batch) {\n try {\n const nestedCtx = {\n element: this,\n formResetCallbacks: [] as typeof this._component.formResetCallbacks,\n mountCallbacks: [] as typeof this._component.mountCallbacks,\n };\n\n this._component.scope.run(() => {\n runWithContext(nestedCtx, () => {\n const cleanup = callback();\n\n if (typeof cleanup === 'function') onCleanup(cleanup);\n });\n });\n\n if (nestedCtx.mountCallbacks.length > 0) {\n this._component.mountCallbacks.push(...nestedCtx.mountCallbacks);\n }\n\n if (nestedCtx.formResetCallbacks.length > 0) {\n this._component.formResetCallbacks.push(...nestedCtx.formResetCallbacks);\n }\n } catch (error) {\n this._handleSetupError(error, 'mounted');\n }\n }\n\n // If nested onMounted calls registered new callbacks, schedule them with\n // a fresh token check in the next microtask.\n if (this._component.mountCallbacks.length > 0) {\n this._scheduleMountCallbacks();\n }\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"base-element.js","names":[],"sources":["../src/base-element.ts"],"sourcesContent":["import { scope as _scope, type Scope, untrack } from '@vielzeug/ripple';\n\nimport type { ComponentDefinition } from './component-types';\n\nimport { warn } from './_dev';\nimport { type OreErrorPhase, OreLifecycleError, reportRuntimeError } from './errors';\nimport { createProps, getPropMeta, type InferProps, type PropInputDefs, type PropsDef } from './props';\nimport {\n beginPendingWork,\n type OnFormResetCallback,\n type OnMountedCallback,\n onCleanup,\n type RuntimeContext,\n runWithContext,\n} from './runtime';\nimport { ComponentPhase, LIFECYCLE_EVENTS } from './types';\nimport { type HTMLResult } from './types/bindings';\nimport { loadStylesheet } from './utils/css';\n\n// ─── Internal component state ─────────────────────────────────────────────────\n\ntype ComponentState = {\n /** Registered via `onFormReset()` — persists across mount callbacks, unlike `mountCallbacks`. */\n formResetCallbacks: OnFormResetCallback[];\n /** Incremented on every disconnect — guards both mount callbacks and async setup results. */\n generation: number;\n mountCallbacks: OnMountedCallback[];\n phase: ComponentPhase;\n scope: Scope;\n templateResult: HTMLResult | null;\n};\n\nconst createComponentState = (): ComponentState => ({\n formResetCallbacks: [],\n generation: 0,\n mountCallbacks: [],\n phase: ComponentPhase.UNINITIALIZED,\n scope: _scope(),\n templateResult: null,\n});\n\n// ─── BaseElement ──────────────────────────────────────────────────────────────\n\n/**\n * Phase transitions:\n *\n * ```\n * UNINITIALIZED ──_runSetup()──┬─(sync result)──► SETUP_DONE\n * └─(promise)──────► LOADING ──(resolves)──► SETUP_DONE\n * SETUP_DONE / LOADING ──disconnectedCallback()──► UNMOUNTED ──(reset)──► UNINITIALIZED\n * ```\n *\n * `generation` increments on every disconnect and is captured before an async\n * `setup()` is awaited; if it no longer matches when the promise settles, the\n * element was disconnected (and possibly reconnected) in the meantime and the\n * result is discarded (see `_isStale`).\n *\n * Why this lives on the class instead of a standalone pure reducer: every\n * transition here is triggered by running actual user code (`def.setup()`,\n * `onMounted` callbacks) inside a reactive `scope.run()` + `runWithContext()`\n * — there is no meaningful \"decide the next phase\" step that can be separated\n * from \"run the side-effecting thing that produces the phase change\" without\n * introducing a data-only effect-description layer that this package has no\n * other use for. That's why the methods below stay as direct, readable\n * procedural steps instead of a reducer + effect interpreter.\n */\nexport class BaseElement extends HTMLElement {\n static _definition: ComponentDefinition<any>;\n static _normalizedPropDefs: PropsDef<Record<never, never>> | undefined;\n static formAssociated = false;\n static observedAttributes: string[] = [];\n\n private _component: ComponentState;\n\n constructor() {\n super();\n\n const def = (this.constructor as typeof BaseElement)._definition;\n\n if (def?.shadow !== false) {\n this.attachShadow({ mode: 'open', ...(def?.shadow as Partial<ShadowRootInit> | undefined) });\n }\n\n this._component = createComponentState();\n }\n\n connectedCallback(): void {\n untrack(() => {\n if (this._component.phase === ComponentPhase.UNINITIALIZED) this._runSetup();\n\n this._init();\n });\n this.dispatchEvent(new CustomEvent(LIFECYCLE_EVENTS.CONNECT, { bubbles: false, composed: false }));\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n\n const propMeta = getPropMeta(this, name);\n\n if (!propMeta) return;\n\n const parsed = propMeta.parse(newValue);\n\n if (\n !Object.is(\n untrack(() => propMeta.signal.value),\n parsed,\n )\n )\n propMeta.signal.value = parsed as never;\n }\n\n disconnectedCallback(): void {\n this._component.generation++;\n this._component.phase = ComponentPhase.UNMOUNTED;\n this.dispatchEvent(new CustomEvent(LIFECYCLE_EVENTS.DISCONNECT, { bubbles: false, composed: false }));\n this._component.scope.dispose();\n // Reset mutable fields for next connect, keeping the same object for stable references\n this._component.formResetCallbacks = [];\n this._component.mountCallbacks = [];\n this._component.phase = ComponentPhase.UNINITIALIZED;\n this._component.scope = _scope();\n this._component.templateResult = null;\n }\n\n /**\n * Native form-association lifecycle callback — the browser calls this on every\n * `formAssociated: true` element inside a `<form>` when that form is reset.\n * Runs every `onFormReset()` callback registered during `setup()`.\n */\n formResetCallback(): void {\n for (const callback of this._component.formResetCallbacks) {\n try {\n callback();\n } catch (error) {\n this._handleSetupError(error, 'form-reset');\n }\n }\n }\n\n private _handleSetupError(error: unknown, phase: OreErrorPhase = 'setup'): HTMLResult | void {\n const err = error instanceof Error ? error : new Error(String(error));\n const oreError = new OreLifecycleError(`<${this.localName}> failed during ${this._component.phase} (${phase})`, {\n cause: err,\n component: this.localName,\n phase,\n });\n const def = (this.constructor as typeof BaseElement)._definition;\n\n if (def?.onError) {\n try {\n return def.onError(oreError, this);\n } catch {\n /* fall through */\n }\n }\n\n reportRuntimeError(oreError, this);\n }\n\n private _runSetup(): void {\n this._component.phase = ComponentPhase.SETUP_RUNNING;\n\n const def = (this.constructor as typeof BaseElement)._definition;\n const normalizedPropDefs = (this.constructor as typeof BaseElement)._normalizedPropDefs;\n const ctx: RuntimeContext = { element: this, formResetCallbacks: [], mountCallbacks: [] };\n\n try {\n let setupResult: HTMLResult | null | Promise<HTMLResult | null> | undefined;\n\n this._component.scope.run(() => {\n setupResult = runWithContext(ctx, () => {\n const setupProps = normalizedPropDefs\n ? createProps(this, normalizedPropDefs)\n : ({} as InferProps<PropInputDefs>);\n\n return def.setup(setupProps as InferProps<PropInputDefs>);\n });\n });\n this._component.mountCallbacks.push(...ctx.mountCallbacks);\n this._component.formResetCallbacks.push(...ctx.formResetCallbacks);\n\n if (setupResult != null && typeof (setupResult as Promise<HTMLResult | null>).then === 'function') {\n // Async setup: show loading template immediately, swap when resolved.\n // Store captured mount callbacks; they will be scheduled after the real template mounts.\n const pendingCallbacks = this._component.mountCallbacks.splice(0);\n\n this._component.phase = ComponentPhase.LOADING;\n\n if (def.loading) {\n this._component.templateResult = def.loading();\n }\n\n // Capture the current generation so the async handler can detect staleness:\n // if the element disconnects+reconnects before the promise resolves, generation is\n // incremented and the old promise result must be discarded.\n //\n // Deliberately NOT tracked via beginPendingWork(): the awaited promise is\n // arbitrary, user-controlled application code (e.g. a real network fetch) with no\n // upper bound — the LOADING template it renders in the meantime is a valid, stable\n // state, not a transient one to wait out. `testing/flush()` only needs to wait for\n // ore's own bounded internal scheduling (queueMicrotask'd mount callbacks below),\n // never for how long a consumer's setup() takes to resolve.\n void this._runSetupAsync(\n setupResult as Promise<HTMLResult | null>,\n pendingCallbacks,\n this._component.generation,\n );\n } else {\n this._component.templateResult = (setupResult as HTMLResult | null) ?? null;\n this._component.phase = ComponentPhase.SETUP_DONE;\n }\n } catch (error) {\n const recoveryTemplate = this._handleSetupError(error);\n\n if (recoveryTemplate) {\n this._component.templateResult = recoveryTemplate;\n this._component.phase = ComponentPhase.SETUP_DONE;\n } else {\n this._component.phase = ComponentPhase.UNINITIALIZED;\n throw error;\n }\n }\n }\n\n /**\n * True once the element has disconnected (and possibly reconnected) since\n * `capturedGeneration` was recorded — an in-flight async `setup()` result\n * arriving after that point is for an instance state that no longer exists.\n */\n private _isStale(capturedGeneration: number): boolean {\n return this._component.generation !== capturedGeneration || !this.isConnected;\n }\n\n private async _runSetupAsync(\n promise: Promise<HTMLResult | null>,\n pendingCallbacks: OnMountedCallback[],\n capturedGeneration: number,\n ): Promise<void> {\n try {\n const result = await promise;\n\n if (this._isStale(capturedGeneration)) {\n warn(`<${this.localName}> async setup result discarded — element disconnected before setup resolved.`);\n\n return;\n }\n\n this._component.templateResult = result ?? null;\n this._component.phase = ComponentPhase.SETUP_DONE;\n this._component.mountCallbacks.push(...pendingCallbacks);\n\n if (result) this._applyResult(result);\n\n this._scheduleMountCallbacks();\n } catch (error) {\n if (this._isStale(capturedGeneration)) {\n warn(`<${this.localName}> async setup error discarded — element disconnected before setup resolved.`);\n\n return;\n }\n\n const recovery = this._handleSetupError(error, 'async-setup');\n\n if (recovery) {\n this._component.templateResult = recovery;\n this._component.phase = ComponentPhase.SETUP_DONE;\n this._applyResult(recovery);\n } else {\n this._component.phase = ComponentPhase.UNINITIALIZED;\n }\n }\n }\n\n private _applyResult(result: HTMLResult | null): void {\n if (!result) return;\n\n const host: Element | ShadowRoot = this.shadowRoot ?? this;\n\n host.replaceChildren(result.fragment);\n this._component.scope.run(() => {\n result.apply(onCleanup);\n });\n }\n\n private _init(): void {\n this._applyStyles();\n this._mountTemplate();\n\n // Only schedule mount callbacks once setup is fully complete (not in LOADING state)\n if (this._component.phase === ComponentPhase.SETUP_DONE) this._scheduleMountCallbacks();\n }\n\n private _applyStyles(): void {\n const def = (this.constructor as typeof BaseElement)._definition;\n\n if (this.shadowRoot && def?.styles?.length) {\n this.shadowRoot.adoptedStyleSheets = def.styles.map(loadStylesheet);\n }\n }\n\n private _mountTemplate(): void {\n const result = this._component.templateResult;\n\n if (!result) return;\n\n this._applyResult(result);\n }\n\n private _scheduleMountCallbacks(): void {\n if (this._component.mountCallbacks.length === 0) return;\n\n const capturedGeneration = this._component.generation;\n // Tracked as pending work for the duration of this microtask — ended in a `finally`\n // so a thrown callback (already caught per-callback below, but defensive here too)\n // never leaves the counter stuck above zero. See runtime.ts's beginPendingWork().\n const endWork = beginPendingWork();\n\n queueMicrotask(() => {\n try {\n if (this._isStale(capturedGeneration)) return;\n\n // Snapshot callbacks so in-loop registrations don't extend this iteration.\n const batch = this._component.mountCallbacks.splice(0);\n\n for (const callback of batch) {\n try {\n const nestedCtx = {\n element: this,\n formResetCallbacks: [] as typeof this._component.formResetCallbacks,\n mountCallbacks: [] as typeof this._component.mountCallbacks,\n };\n\n this._component.scope.run(() => {\n runWithContext(nestedCtx, () => {\n const cleanup = callback();\n\n if (typeof cleanup === 'function') onCleanup(cleanup);\n });\n });\n\n if (nestedCtx.mountCallbacks.length > 0) {\n this._component.mountCallbacks.push(...nestedCtx.mountCallbacks);\n }\n\n if (nestedCtx.formResetCallbacks.length > 0) {\n this._component.formResetCallbacks.push(...nestedCtx.formResetCallbacks);\n }\n } catch (error) {\n this._handleSetupError(error, 'mounted');\n }\n }\n\n // If nested onMounted calls registered new callbacks, schedule them with\n // a fresh token check in the next microtask. This happens *before* endWork()\n // below runs so the counter never dips to zero between the two schedules.\n if (this._component.mountCallbacks.length > 0) {\n this._scheduleMountCallbacks();\n }\n } finally {\n endWork();\n }\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
const e=require("../utils/dom.cjs");let t=require("@vielzeug/ripple");var n=n=>(0,t.computed)(()=>Object.entries(n).filter(([,t])=>e.resolveMaybeReactive(t)).map(([e])=>e.replace(/\s+/g,``)).filter(Boolean).join(` `));exports.classMap=n;
|
|
2
2
|
//# sourceMappingURL=classMap.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"classMap.cjs","names":[],"sources":["../../src/directives/classMap.ts"],"sourcesContent":["import { computed,
|
|
1
|
+
{"version":3,"file":"classMap.cjs","names":[],"sources":["../../src/directives/classMap.ts"],"sourcesContent":["import { computed, type Readable } from '@vielzeug/ripple';\n\nimport { resolveMaybeReactive } from '../utils/dom';\n\n/**\n * Produces a reactive string of class names from an object map.\n *\n * Each key is a class name. Its value may be:\n * - a static `boolean`\n * - a `Signal<boolean>`\n * - a getter `() => boolean`\n *\n * Returns a `Reactive<string>` that can be used directly in a template\n * class attribute:\n *\n * @example\n * ```ts\n * html`<div class=\"${classMap({ active: isActive, hidden: () => !isVisible.value })}\"></div>`\n * ```\n */\nexport const classMap = (map: Record<string, (() => boolean) | Readable<boolean> | boolean>): Readable<string> => {\n return computed(() =>\n Object.entries(map)\n .filter(([, v]) => resolveMaybeReactive(v))\n // Strip whitespace from each key — spaces would inject extra class tokens.\n .map(([k]) => k.replace(/\\s+/g, ''))\n .filter(Boolean)\n .join(' '),\n );\n};\n"],"mappings":"sEAoBA,IAAa,EAAY,IACvB,EAAA,EAAA,SAAA,KACE,OAAO,QAAQ,CAAG,CAAC,CAChB,QAAQ,EAAG,KAAO,EAAA,qBAAqB,CAAC,CAAC,CAAC,CAE1C,KAAK,CAAC,KAAO,EAAE,QAAQ,OAAQ,EAAE,CAAC,CAAC,CACnC,OAAO,OAAO,CAAC,CACf,KAAK,GAAG,CACb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"classMap.d.ts","sourceRoot":"","sources":["../../src/directives/classMap.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"classMap.d.ts","sourceRoot":"","sources":["../../src/directives/classMap.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAI3D;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,QAAQ,GAAI,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC,MAAM,OAAO,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,KAAG,QAAQ,CAAC,MAAM,CAS5G,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{resolveMaybeReactive as e}from"../utils/dom.js";import{computed as t}from"@vielzeug/ripple";var n=n=>t(()=>Object.entries(n).filter(([,t])=>e(t)).map(([e])=>e.replace(/\s+/g,``)).filter(Boolean).join(` `));export{n as classMap};
|
|
2
2
|
//# sourceMappingURL=classMap.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"classMap.js","names":[],"sources":["../../src/directives/classMap.ts"],"sourcesContent":["import { computed,
|
|
1
|
+
{"version":3,"file":"classMap.js","names":[],"sources":["../../src/directives/classMap.ts"],"sourcesContent":["import { computed, type Readable } from '@vielzeug/ripple';\n\nimport { resolveMaybeReactive } from '../utils/dom';\n\n/**\n * Produces a reactive string of class names from an object map.\n *\n * Each key is a class name. Its value may be:\n * - a static `boolean`\n * - a `Signal<boolean>`\n * - a getter `() => boolean`\n *\n * Returns a `Reactive<string>` that can be used directly in a template\n * class attribute:\n *\n * @example\n * ```ts\n * html`<div class=\"${classMap({ active: isActive, hidden: () => !isVisible.value })}\"></div>`\n * ```\n */\nexport const classMap = (map: Record<string, (() => boolean) | Readable<boolean> | boolean>): Readable<string> => {\n return computed(() =>\n Object.entries(map)\n .filter(([, v]) => resolveMaybeReactive(v))\n // Strip whitespace from each key — spaces would inject extra class tokens.\n .map(([k]) => k.replace(/\\s+/g, ''))\n .filter(Boolean)\n .join(' '),\n );\n};\n"],"mappings":"mGAoBA,IAAa,EAAY,GAChB,MACL,OAAO,QAAQ,CAAG,CAAC,CAChB,QAAQ,EAAG,KAAO,EAAqB,CAAC,CAAC,CAAC,CAE1C,KAAK,CAAC,KAAO,EAAE,QAAQ,OAAQ,EAAE,CAAC,CAAC,CACnC,OAAO,OAAO,CAAC,CACf,KAAK,GAAG,CACb"}
|
package/dist/directives/each.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../
|
|
1
|
+
const e=require("../errors.cjs"),t=require("../types/bindings.cjs"),n=require("../utils/dom.cjs");let r=require("@vielzeug/ripple");var i=(e,t,n,i,a)=>{let o=(0,r.signal)(e),s=(0,r.signal)(t),c=n(o,s),l=[];return{cleanups:l,data:o,index:s,key:``,nodes:c.mount(i,a,e=>l.push(e))}},a=e=>{n.runAll(e.cleanups),n.removeNodes(e.nodes)},o=(t,n,o,s,c,l)=>{let u=[],d=new Set;for(let t=0;t<n.length;t++){let r=String(o(n[t],t));if(d.has(r))throw new e.OreApiError(e.ORE_ERRORS.eachDuplicateKey(r,t));d.add(r),u.push(r)}for(let[e,n]of t)d.has(e)||(a(n),t.delete(e));let f=[];for(let e=0;e<n.length;e++){let a=u[e],o=t.get(a);if(o)(0,r.batch)(()=>{o.data.value=n[e],o.index.value=e}),f.push(o);else{let o=(0,r.untrack)(()=>i(n[e],e,s,c,l));o.key=a,t.set(a,o),f.push(o)}}let p=l;for(let e=f.length-1;e>=0;e--){let t=f[e],n=t.nodes[0];if(n&&n!==p.previousSibling)for(let e of t.nodes)c.insertBefore(e,p);p=n??p}return f};function s(i,s,c,l){let u=Array.isArray(i)?(0,r.signal)(i):typeof i==`function`?(0,r.computed)(i):i;return t.createDirectiveResult((t,i)=>{let d=t.parentNode;e.invariant(d,`each() anchor comment has no parent node`);let f=document.createComment(`each/end`);d.insertBefore(f,t.nextSibling);let p=new Map,m=[],h=null,g=[],_=()=>{l&&(h=l().mount(d,f,e=>g.push(e)))},v=()=>{h&&(n.runAll(g),n.removeNodes(h),h=null,g=[])},y=(0,r.effect)(()=>{let n=u.value??[];if(n.length===0){for(let e of(0,r.untrack)(()=>m))a(e);p=new Map,m=[],h||(0,r.untrack)(_);return}v();try{m=(0,r.untrack)(()=>o(p,n,s,c,d,f))}catch(n){let r=n instanceof Error?n:Error(String(n));e.reportRuntimeError(new e.OreLifecycleError(`each() failed to reconcile a list update: ${r.message}`,{cause:r,component:`each()`,phase:`each-reconcile`}),t);for(let e of p.values())a(e);p=new Map,m=[]}});i(()=>y.dispose()),i(()=>{v();for(let e of m)a(e);f.remove()})})}exports.each=s;
|
|
2
2
|
//# sourceMappingURL=each.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"each.cjs","names":[],"sources":["../../src/directives/each.ts"],"sourcesContent":["import { batch, computed, effect as rawEffect, type Readable, signal, type Signal, untrack } from '@vielzeug/ripple';\n\nimport { warn } from '../_dev';\nimport { invariant, OreApiError, ORE_ERRORS } from '../errors';\nimport { createDirectiveResult, type DirectiveResult, type HTMLResult } from '../types/bindings';\nimport { removeNodes, runAll } from '../utils/dom';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\ntype MaybeReactiveArray<T> = Readable<T[]> | (() => T[]) | T[];\n\ntype ItemEntry<T> = {\n cleanups: (() => void)[];\n data: Signal<T>;\n index: Signal<number>;\n /** The key used to identify this entry. */\n key: string;\n nodes: Node[];\n};\n\n// ─── Item lifecycle ───────────────────────────────────────────────────────────\n\nconst createItem = <T>(\n item: T,\n index: number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n parent: ParentNode,\n insertBefore: Node,\n): ItemEntry<T> => {\n const dataSignal: Signal<T> = signal(item);\n const indexSignal: Signal<number> = signal(index);\n\n const result = render(dataSignal, indexSignal);\n const cleanups: (() => void)[] = [];\n const nodes = result.mount(parent, insertBefore, (fn) => cleanups.push(fn));\n\n return { cleanups, data: dataSignal, index: indexSignal, key: '', nodes };\n};\n\nconst removeItem = <T>(entry: ItemEntry<T>): void => {\n runAll(entry.cleanups);\n removeNodes(entry.nodes);\n};\n\n// ─── Reconciler ───────────────────────────────────────────────────────────────\n\n/**\n * Reconciles the live item map (mutated in-place) against the next array.\n * Stale entries are removed and destroyed; new entries are created and inserted.\n * Existing entries are updated in-place via signal writes.\n * Returns the ordered list of entries matching nextList.\n */\nconst reconcileItems = <T>(\n itemsMap: Map<string, ItemEntry<T>>,\n next: T[],\n keyFn: (item: T, index: number) => string | number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n parent: ParentNode,\n endMarker: Node,\n): ItemEntry<T>[] => {\n const nextKeys: string[] = [];\n const nextKeySet = new Set<string>();\n\n for (let i = 0; i < next.length; i++) {\n const key = String(keyFn(next[i], i));\n\n if (nextKeySet.has(key)) throw new OreApiError(ORE_ERRORS.eachDuplicateKey(key, i));\n\n nextKeySet.add(key);\n nextKeys.push(key);\n }\n\n // Remove stale entries from the map\n for (const [key, entry] of itemsMap) {\n if (!nextKeySet.has(key)) {\n removeItem(entry);\n itemsMap.delete(key);\n }\n }\n\n // Build the next ordered list: update existing items, create new ones\n const nextOrdered: ItemEntry<T>[] = [];\n\n for (let i = 0; i < next.length; i++) {\n const key = nextKeys[i];\n const existing = itemsMap.get(key);\n\n if (existing) {\n batch(() => {\n existing.data.value = next[i];\n existing.index.value = i;\n });\n nextOrdered.push(existing);\n } else {\n const entry = untrack(() => createItem(next[i], i, render, parent, endMarker));\n\n entry.key = key;\n itemsMap.set(key, entry);\n nextOrdered.push(entry);\n }\n }\n\n // DOM ordering: right-to-left pass — move any item not already adjacent to cursor.\n // O(n) DOM operations in the worst case; optimal for the typical small list sizes\n // encountered in UI components (tabs, options, menu items).\n let cursor: Node = endMarker;\n\n for (let j = nextOrdered.length - 1; j >= 0; j--) {\n const entry = nextOrdered[j];\n const firstNode = entry.nodes[0];\n\n if (firstNode && firstNode !== cursor.previousSibling) {\n for (const node of entry.nodes) parent.insertBefore(node, cursor);\n }\n\n cursor = firstNode ?? cursor;\n }\n\n return nextOrdered;\n};\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Renders a keyed list of items as a `DirectiveResult`.\n *\n * Each item is rendered by the provided render function.\n * Items are reused by key when the list changes; only stale items are destroyed.\n *\n * The render function receives a `Readable<T>` signal and a `Readable<number>` index\n * signal. Use `item.value` to read the current item inside reactive expressions:\n *\n * ```ts\n * html`${each(items, (item) => item.id, (item) => html`<li>${() => item.value.name}</li>`)}`\n * ```\n *\n * **Plain array:** when a plain `T[]` is passed (not a signal or getter), it is\n * treated as a one-time static render. Mutations to the original array are not\n * tracked. Pass a `Signal<T[]>` or `() => T[]` for reactive lists.\n *\n * **Optional fallback:** the fourth argument renders when the list is empty.\n *\n * **Key choice:** pass a stable item identifier (e.g. `item.id`), never the\n * array index — an index-based key reassigns to a different item whenever the\n * list is reordered or an item is inserted/removed before it, causing full\n * item teardown/recreation instead of the in-place update `each()` is built\n * for. Duplicate keys within one render throw immediately (see `eachDuplicateKey`).\n */\nexport function each<T>(\n list: MaybeReactiveArray<T>,\n keyFn: (item: T, index: number) => string | number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n fallback?: () => HTMLResult,\n): DirectiveResult {\n const listSignal = Array.isArray(list)\n ? signal(list as T[])\n : typeof list === 'function'\n ? computed(list as () => T[])\n : list;\n\n return createDirectiveResult((anchor, registerCleanup) => {\n const parent = anchor.parentNode;\n\n invariant(parent, 'each() anchor comment has no parent node');\n\n const endMarker = document.createComment('each/end');\n\n parent.insertBefore(endMarker, anchor.nextSibling);\n\n let itemsMap = new Map<string, ItemEntry<T>>();\n let itemsOrdered: ItemEntry<T>[] = [];\n let fallbackNodes: Node[] | null = null;\n let fallbackCleanups: (() => void)[] = [];\n\n const mountFallback = (): void => {\n if (!fallback) return;\n\n const result = fallback();\n\n fallbackNodes = result.mount(parent, endMarker, (fn) => fallbackCleanups.push(fn));\n };\n\n const clearFallback = (): void => {\n if (fallbackNodes) {\n runAll(fallbackCleanups);\n removeNodes(fallbackNodes);\n fallbackNodes = null;\n fallbackCleanups = [];\n }\n };\n\n const sub = rawEffect(() => {\n const nextList = listSignal.value ?? [];\n\n if (nextList.length === 0) {\n for (const entry of untrack(() => itemsOrdered)) removeItem(entry);\n itemsMap = new Map();\n itemsOrdered = [];\n\n if (!fallbackNodes) untrack(mountFallback);\n\n return;\n }\n\n clearFallback();\n\n try {\n itemsOrdered = untrack(() => reconcileItems(itemsMap, nextList, keyFn, render, parent, endMarker));\n } catch (err) {\n warn(`each() reconciliation error: ${err instanceof Error ? err.message : String(err)}`);\n\n for (const entry of itemsMap.values()) removeItem(entry);\n itemsMap = new Map();\n itemsOrdered = [];\n }\n });\n\n registerCleanup(() => sub.dispose());\n registerCleanup(() => {\n clearFallback();\n for (const entry of itemsOrdered) removeItem(entry);\n endMarker.remove();\n });\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"each.cjs","names":[],"sources":["../../src/directives/each.ts"],"sourcesContent":["import { batch, computed, effect as rawEffect, type Readable, signal, type Signal, untrack } from '@vielzeug/ripple';\n\nimport { invariant, OreApiError, ORE_ERRORS, OreLifecycleError, reportRuntimeError } from '../errors';\nimport { createDirectiveResult, type DirectiveResult, type HTMLResult } from '../types/bindings';\nimport { removeNodes, runAll } from '../utils/dom';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\ntype MaybeReactiveArray<T> = Readable<T[]> | (() => T[]) | T[];\n\ntype ItemEntry<T> = {\n cleanups: (() => void)[];\n data: Signal<T>;\n index: Signal<number>;\n /** The key used to identify this entry. */\n key: string;\n nodes: Node[];\n};\n\n// ─── Item lifecycle ───────────────────────────────────────────────────────────\n\nconst createItem = <T>(\n item: T,\n index: number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n parent: ParentNode,\n insertBefore: Node,\n): ItemEntry<T> => {\n const dataSignal: Signal<T> = signal(item);\n const indexSignal: Signal<number> = signal(index);\n\n const result = render(dataSignal, indexSignal);\n const cleanups: (() => void)[] = [];\n const nodes = result.mount(parent, insertBefore, (fn) => cleanups.push(fn));\n\n return { cleanups, data: dataSignal, index: indexSignal, key: '', nodes };\n};\n\nconst removeItem = <T>(entry: ItemEntry<T>): void => {\n runAll(entry.cleanups);\n removeNodes(entry.nodes);\n};\n\n// ─── Reconciler ───────────────────────────────────────────────────────────────\n\n/**\n * Reconciles the live item map (mutated in-place) against the next array.\n * Stale entries are removed and destroyed; new entries are created and inserted.\n * Existing entries are updated in-place via signal writes.\n * Returns the ordered list of entries matching nextList.\n */\nconst reconcileItems = <T>(\n itemsMap: Map<string, ItemEntry<T>>,\n next: T[],\n keyFn: (item: T, index: number) => string | number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n parent: ParentNode,\n endMarker: Node,\n): ItemEntry<T>[] => {\n const nextKeys: string[] = [];\n const nextKeySet = new Set<string>();\n\n for (let i = 0; i < next.length; i++) {\n const key = String(keyFn(next[i], i));\n\n if (nextKeySet.has(key)) throw new OreApiError(ORE_ERRORS.eachDuplicateKey(key, i));\n\n nextKeySet.add(key);\n nextKeys.push(key);\n }\n\n // Remove stale entries from the map\n for (const [key, entry] of itemsMap) {\n if (!nextKeySet.has(key)) {\n removeItem(entry);\n itemsMap.delete(key);\n }\n }\n\n // Build the next ordered list: update existing items, create new ones\n const nextOrdered: ItemEntry<T>[] = [];\n\n for (let i = 0; i < next.length; i++) {\n const key = nextKeys[i];\n const existing = itemsMap.get(key);\n\n if (existing) {\n batch(() => {\n existing.data.value = next[i];\n existing.index.value = i;\n });\n nextOrdered.push(existing);\n } else {\n const entry = untrack(() => createItem(next[i], i, render, parent, endMarker));\n\n entry.key = key;\n itemsMap.set(key, entry);\n nextOrdered.push(entry);\n }\n }\n\n // DOM ordering: right-to-left pass — move any item not already adjacent to cursor.\n // O(n) DOM operations in the worst case; optimal for the typical small list sizes\n // encountered in UI components (tabs, options, menu items).\n let cursor: Node = endMarker;\n\n for (let j = nextOrdered.length - 1; j >= 0; j--) {\n const entry = nextOrdered[j];\n const firstNode = entry.nodes[0];\n\n if (firstNode && firstNode !== cursor.previousSibling) {\n for (const node of entry.nodes) parent.insertBefore(node, cursor);\n }\n\n cursor = firstNode ?? cursor;\n }\n\n return nextOrdered;\n};\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Renders a keyed list of items as a `DirectiveResult`.\n *\n * Each item is rendered by the provided render function.\n * Items are reused by key when the list changes; only stale items are destroyed.\n *\n * The render function receives a `Readable<T>` signal and a `Readable<number>` index\n * signal. Use `item.value` to read the current item inside reactive expressions:\n *\n * ```ts\n * html`${each(items, (item) => item.id, (item) => html`<li>${() => item.value.name}</li>`)}`\n * ```\n *\n * **Plain array:** when a plain `T[]` is passed (not a signal or getter), it is\n * treated as a one-time static render. Mutations to the original array are not\n * tracked. Pass a `Signal<T[]>` or `() => T[]` for reactive lists.\n *\n * **Optional fallback:** the fourth argument renders when the list is empty.\n *\n * **Key choice:** pass a stable item identifier (e.g. `item.id`), never the\n * array index — an index-based key reassigns to a different item whenever the\n * list is reordered or an item is inserted/removed before it, causing full\n * item teardown/recreation instead of the in-place update `each()` is built\n * for.\n *\n * **Duplicate keys:** a reconciliation failure (e.g. duplicate keys, see `eachDuplicateKey`)\n * does not throw past this function — an uncaught exception inside the reactive effect that\n * drives `each()` would risk corrupting unrelated effects scheduled in the same update batch.\n * Instead the list is cleared and the failure is reported via the `ore:error` DOM event (see\n * `OreLifecycleError`, phase `'each-reconcile'`) plus a dev-only console log — listen for\n * `ore:error` on `document`/`window` to observe this in every build, including production.\n */\nexport function each<T>(\n list: MaybeReactiveArray<T>,\n keyFn: (item: T, index: number) => string | number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n fallback?: () => HTMLResult,\n): DirectiveResult {\n const listSignal = Array.isArray(list)\n ? signal(list as T[])\n : typeof list === 'function'\n ? computed(list as () => T[])\n : list;\n\n return createDirectiveResult((anchor, registerCleanup) => {\n const parent = anchor.parentNode;\n\n invariant(parent, 'each() anchor comment has no parent node');\n\n const endMarker = document.createComment('each/end');\n\n parent.insertBefore(endMarker, anchor.nextSibling);\n\n let itemsMap = new Map<string, ItemEntry<T>>();\n let itemsOrdered: ItemEntry<T>[] = [];\n let fallbackNodes: Node[] | null = null;\n let fallbackCleanups: (() => void)[] = [];\n\n const mountFallback = (): void => {\n if (!fallback) return;\n\n const result = fallback();\n\n fallbackNodes = result.mount(parent, endMarker, (fn) => fallbackCleanups.push(fn));\n };\n\n const clearFallback = (): void => {\n if (fallbackNodes) {\n runAll(fallbackCleanups);\n removeNodes(fallbackNodes);\n fallbackNodes = null;\n fallbackCleanups = [];\n }\n };\n\n const sub = rawEffect(() => {\n const nextList = listSignal.value ?? [];\n\n if (nextList.length === 0) {\n for (const entry of untrack(() => itemsOrdered)) removeItem(entry);\n itemsMap = new Map();\n itemsOrdered = [];\n\n if (!fallbackNodes) untrack(mountFallback);\n\n return;\n }\n\n clearFallback();\n\n try {\n itemsOrdered = untrack(() => reconcileItems(itemsMap, nextList, keyFn, render, parent, endMarker));\n } catch (err) {\n const cause = err instanceof Error ? err : new Error(String(err));\n\n // Dispatched on the anchor comment (always a live DOM node) rather than the enclosing\n // component's host element, which each() has no direct reference to — the event still\n // bubbles/composes up to any ancestor listener, including a global one on document.\n reportRuntimeError(\n new OreLifecycleError(`each() failed to reconcile a list update: ${cause.message}`, {\n cause,\n component: 'each()',\n phase: 'each-reconcile',\n }),\n anchor,\n );\n\n for (const entry of itemsMap.values()) removeItem(entry);\n itemsMap = new Map();\n itemsOrdered = [];\n }\n });\n\n registerCleanup(() => sub.dispose());\n registerCleanup(() => {\n clearFallback();\n for (const entry of itemsOrdered) removeItem(entry);\n endMarker.remove();\n });\n });\n}\n"],"mappings":"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"}
|
|
@@ -24,7 +24,14 @@ type MaybeReactiveArray<T> = Readable<T[]> | (() => T[]) | T[];
|
|
|
24
24
|
* array index — an index-based key reassigns to a different item whenever the
|
|
25
25
|
* list is reordered or an item is inserted/removed before it, causing full
|
|
26
26
|
* item teardown/recreation instead of the in-place update `each()` is built
|
|
27
|
-
* for.
|
|
27
|
+
* for.
|
|
28
|
+
*
|
|
29
|
+
* **Duplicate keys:** a reconciliation failure (e.g. duplicate keys, see `eachDuplicateKey`)
|
|
30
|
+
* does not throw past this function — an uncaught exception inside the reactive effect that
|
|
31
|
+
* drives `each()` would risk corrupting unrelated effects scheduled in the same update batch.
|
|
32
|
+
* Instead the list is cleared and the failure is reported via the `ore:error` DOM event (see
|
|
33
|
+
* `OreLifecycleError`, phase `'each-reconcile'`) plus a dev-only console log — listen for
|
|
34
|
+
* `ore:error` on `document`/`window` to observe this in every build, including production.
|
|
28
35
|
*/
|
|
29
36
|
export declare function each<T>(list: MaybeReactiveArray<T>, keyFn: (item: T, index: number) => string | number, render: (item: Readable<T>, index: Readable<number>) => HTMLResult, fallback?: () => HTMLResult): DirectiveResult;
|
|
30
37
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"each.d.ts","sourceRoot":"","sources":["../../src/directives/each.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwC,KAAK,QAAQ,EAAgC,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"each.d.ts","sourceRoot":"","sources":["../../src/directives/each.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwC,KAAK,QAAQ,EAAgC,MAAM,kBAAkB,CAAC;AAGrH,OAAO,EAAyB,KAAK,eAAe,EAAE,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAKjG,KAAK,kBAAkB,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;AAkH/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,IAAI,CAAC,CAAC,EACpB,IAAI,EAAE,kBAAkB,CAAC,CAAC,CAAC,EAC3B,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,EAClD,MAAM,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,UAAU,EAClE,QAAQ,CAAC,EAAE,MAAM,UAAU,GAC1B,eAAe,CAmFjB"}
|
package/dist/directives/each.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{ORE_ERRORS as e,OreApiError as t,OreLifecycleError as n,invariant as r,reportRuntimeError as i}from"../errors.js";import{createDirectiveResult as a}from"../types/bindings.js";import{removeNodes as o,runAll as s}from"../utils/dom.js";import{batch as c,computed as l,effect as u,signal as d,untrack as f}from"@vielzeug/ripple";var p=(e,t,n,r,i)=>{let a=d(e),o=d(t),s=n(a,o),c=[];return{cleanups:c,data:a,index:o,key:``,nodes:s.mount(r,i,e=>c.push(e))}},m=e=>{s(e.cleanups),o(e.nodes)},h=(n,r,i,a,o,s)=>{let l=[],u=new Set;for(let n=0;n<r.length;n++){let a=String(i(r[n],n));if(u.has(a))throw new t(e.eachDuplicateKey(a,n));u.add(a),l.push(a)}for(let[e,t]of n)u.has(e)||(m(t),n.delete(e));let d=[];for(let e=0;e<r.length;e++){let t=l[e],i=n.get(t);if(i)c(()=>{i.data.value=r[e],i.index.value=e}),d.push(i);else{let i=f(()=>p(r[e],e,a,o,s));i.key=t,n.set(t,i),d.push(i)}}let h=s;for(let e=d.length-1;e>=0;e--){let t=d[e],n=t.nodes[0];if(n&&n!==h.previousSibling)for(let e of t.nodes)o.insertBefore(e,h);h=n??h}return d};function g(e,t,c,p){let g=Array.isArray(e)?d(e):typeof e==`function`?l(e):e;return a((e,a)=>{let l=e.parentNode;r(l,`each() anchor comment has no parent node`);let d=document.createComment(`each/end`);l.insertBefore(d,e.nextSibling);let _=new Map,v=[],y=null,b=[],x=()=>{p&&(y=p().mount(l,d,e=>b.push(e)))},S=()=>{y&&(s(b),o(y),y=null,b=[])},C=u(()=>{let r=g.value??[];if(r.length===0){for(let e of f(()=>v))m(e);_=new Map,v=[],y||f(x);return}S();try{v=f(()=>h(_,r,t,c,l,d))}catch(t){let r=t instanceof Error?t:Error(String(t));i(new n(`each() failed to reconcile a list update: ${r.message}`,{cause:r,component:`each()`,phase:`each-reconcile`}),e);for(let e of _.values())m(e);_=new Map,v=[]}});a(()=>C.dispose()),a(()=>{S();for(let e of v)m(e);d.remove()})})}export{g as each};
|
|
2
2
|
//# sourceMappingURL=each.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"each.js","names":[],"sources":["../../src/directives/each.ts"],"sourcesContent":["import { batch, computed, effect as rawEffect, type Readable, signal, type Signal, untrack } from '@vielzeug/ripple';\n\nimport { warn } from '../_dev';\nimport { invariant, OreApiError, ORE_ERRORS } from '../errors';\nimport { createDirectiveResult, type DirectiveResult, type HTMLResult } from '../types/bindings';\nimport { removeNodes, runAll } from '../utils/dom';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\ntype MaybeReactiveArray<T> = Readable<T[]> | (() => T[]) | T[];\n\ntype ItemEntry<T> = {\n cleanups: (() => void)[];\n data: Signal<T>;\n index: Signal<number>;\n /** The key used to identify this entry. */\n key: string;\n nodes: Node[];\n};\n\n// ─── Item lifecycle ───────────────────────────────────────────────────────────\n\nconst createItem = <T>(\n item: T,\n index: number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n parent: ParentNode,\n insertBefore: Node,\n): ItemEntry<T> => {\n const dataSignal: Signal<T> = signal(item);\n const indexSignal: Signal<number> = signal(index);\n\n const result = render(dataSignal, indexSignal);\n const cleanups: (() => void)[] = [];\n const nodes = result.mount(parent, insertBefore, (fn) => cleanups.push(fn));\n\n return { cleanups, data: dataSignal, index: indexSignal, key: '', nodes };\n};\n\nconst removeItem = <T>(entry: ItemEntry<T>): void => {\n runAll(entry.cleanups);\n removeNodes(entry.nodes);\n};\n\n// ─── Reconciler ───────────────────────────────────────────────────────────────\n\n/**\n * Reconciles the live item map (mutated in-place) against the next array.\n * Stale entries are removed and destroyed; new entries are created and inserted.\n * Existing entries are updated in-place via signal writes.\n * Returns the ordered list of entries matching nextList.\n */\nconst reconcileItems = <T>(\n itemsMap: Map<string, ItemEntry<T>>,\n next: T[],\n keyFn: (item: T, index: number) => string | number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n parent: ParentNode,\n endMarker: Node,\n): ItemEntry<T>[] => {\n const nextKeys: string[] = [];\n const nextKeySet = new Set<string>();\n\n for (let i = 0; i < next.length; i++) {\n const key = String(keyFn(next[i], i));\n\n if (nextKeySet.has(key)) throw new OreApiError(ORE_ERRORS.eachDuplicateKey(key, i));\n\n nextKeySet.add(key);\n nextKeys.push(key);\n }\n\n // Remove stale entries from the map\n for (const [key, entry] of itemsMap) {\n if (!nextKeySet.has(key)) {\n removeItem(entry);\n itemsMap.delete(key);\n }\n }\n\n // Build the next ordered list: update existing items, create new ones\n const nextOrdered: ItemEntry<T>[] = [];\n\n for (let i = 0; i < next.length; i++) {\n const key = nextKeys[i];\n const existing = itemsMap.get(key);\n\n if (existing) {\n batch(() => {\n existing.data.value = next[i];\n existing.index.value = i;\n });\n nextOrdered.push(existing);\n } else {\n const entry = untrack(() => createItem(next[i], i, render, parent, endMarker));\n\n entry.key = key;\n itemsMap.set(key, entry);\n nextOrdered.push(entry);\n }\n }\n\n // DOM ordering: right-to-left pass — move any item not already adjacent to cursor.\n // O(n) DOM operations in the worst case; optimal for the typical small list sizes\n // encountered in UI components (tabs, options, menu items).\n let cursor: Node = endMarker;\n\n for (let j = nextOrdered.length - 1; j >= 0; j--) {\n const entry = nextOrdered[j];\n const firstNode = entry.nodes[0];\n\n if (firstNode && firstNode !== cursor.previousSibling) {\n for (const node of entry.nodes) parent.insertBefore(node, cursor);\n }\n\n cursor = firstNode ?? cursor;\n }\n\n return nextOrdered;\n};\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Renders a keyed list of items as a `DirectiveResult`.\n *\n * Each item is rendered by the provided render function.\n * Items are reused by key when the list changes; only stale items are destroyed.\n *\n * The render function receives a `Readable<T>` signal and a `Readable<number>` index\n * signal. Use `item.value` to read the current item inside reactive expressions:\n *\n * ```ts\n * html`${each(items, (item) => item.id, (item) => html`<li>${() => item.value.name}</li>`)}`\n * ```\n *\n * **Plain array:** when a plain `T[]` is passed (not a signal or getter), it is\n * treated as a one-time static render. Mutations to the original array are not\n * tracked. Pass a `Signal<T[]>` or `() => T[]` for reactive lists.\n *\n * **Optional fallback:** the fourth argument renders when the list is empty.\n *\n * **Key choice:** pass a stable item identifier (e.g. `item.id`), never the\n * array index — an index-based key reassigns to a different item whenever the\n * list is reordered or an item is inserted/removed before it, causing full\n * item teardown/recreation instead of the in-place update `each()` is built\n * for. Duplicate keys within one render throw immediately (see `eachDuplicateKey`).\n */\nexport function each<T>(\n list: MaybeReactiveArray<T>,\n keyFn: (item: T, index: number) => string | number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n fallback?: () => HTMLResult,\n): DirectiveResult {\n const listSignal = Array.isArray(list)\n ? signal(list as T[])\n : typeof list === 'function'\n ? computed(list as () => T[])\n : list;\n\n return createDirectiveResult((anchor, registerCleanup) => {\n const parent = anchor.parentNode;\n\n invariant(parent, 'each() anchor comment has no parent node');\n\n const endMarker = document.createComment('each/end');\n\n parent.insertBefore(endMarker, anchor.nextSibling);\n\n let itemsMap = new Map<string, ItemEntry<T>>();\n let itemsOrdered: ItemEntry<T>[] = [];\n let fallbackNodes: Node[] | null = null;\n let fallbackCleanups: (() => void)[] = [];\n\n const mountFallback = (): void => {\n if (!fallback) return;\n\n const result = fallback();\n\n fallbackNodes = result.mount(parent, endMarker, (fn) => fallbackCleanups.push(fn));\n };\n\n const clearFallback = (): void => {\n if (fallbackNodes) {\n runAll(fallbackCleanups);\n removeNodes(fallbackNodes);\n fallbackNodes = null;\n fallbackCleanups = [];\n }\n };\n\n const sub = rawEffect(() => {\n const nextList = listSignal.value ?? [];\n\n if (nextList.length === 0) {\n for (const entry of untrack(() => itemsOrdered)) removeItem(entry);\n itemsMap = new Map();\n itemsOrdered = [];\n\n if (!fallbackNodes) untrack(mountFallback);\n\n return;\n }\n\n clearFallback();\n\n try {\n itemsOrdered = untrack(() => reconcileItems(itemsMap, nextList, keyFn, render, parent, endMarker));\n } catch (err) {\n warn(`each() reconciliation error: ${err instanceof Error ? err.message : String(err)}`);\n\n for (const entry of itemsMap.values()) removeItem(entry);\n itemsMap = new Map();\n itemsOrdered = [];\n }\n });\n\n registerCleanup(() => sub.dispose());\n registerCleanup(() => {\n clearFallback();\n for (const entry of itemsOrdered) removeItem(entry);\n endMarker.remove();\n });\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"each.js","names":[],"sources":["../../src/directives/each.ts"],"sourcesContent":["import { batch, computed, effect as rawEffect, type Readable, signal, type Signal, untrack } from '@vielzeug/ripple';\n\nimport { invariant, OreApiError, ORE_ERRORS, OreLifecycleError, reportRuntimeError } from '../errors';\nimport { createDirectiveResult, type DirectiveResult, type HTMLResult } from '../types/bindings';\nimport { removeNodes, runAll } from '../utils/dom';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\ntype MaybeReactiveArray<T> = Readable<T[]> | (() => T[]) | T[];\n\ntype ItemEntry<T> = {\n cleanups: (() => void)[];\n data: Signal<T>;\n index: Signal<number>;\n /** The key used to identify this entry. */\n key: string;\n nodes: Node[];\n};\n\n// ─── Item lifecycle ───────────────────────────────────────────────────────────\n\nconst createItem = <T>(\n item: T,\n index: number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n parent: ParentNode,\n insertBefore: Node,\n): ItemEntry<T> => {\n const dataSignal: Signal<T> = signal(item);\n const indexSignal: Signal<number> = signal(index);\n\n const result = render(dataSignal, indexSignal);\n const cleanups: (() => void)[] = [];\n const nodes = result.mount(parent, insertBefore, (fn) => cleanups.push(fn));\n\n return { cleanups, data: dataSignal, index: indexSignal, key: '', nodes };\n};\n\nconst removeItem = <T>(entry: ItemEntry<T>): void => {\n runAll(entry.cleanups);\n removeNodes(entry.nodes);\n};\n\n// ─── Reconciler ───────────────────────────────────────────────────────────────\n\n/**\n * Reconciles the live item map (mutated in-place) against the next array.\n * Stale entries are removed and destroyed; new entries are created and inserted.\n * Existing entries are updated in-place via signal writes.\n * Returns the ordered list of entries matching nextList.\n */\nconst reconcileItems = <T>(\n itemsMap: Map<string, ItemEntry<T>>,\n next: T[],\n keyFn: (item: T, index: number) => string | number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n parent: ParentNode,\n endMarker: Node,\n): ItemEntry<T>[] => {\n const nextKeys: string[] = [];\n const nextKeySet = new Set<string>();\n\n for (let i = 0; i < next.length; i++) {\n const key = String(keyFn(next[i], i));\n\n if (nextKeySet.has(key)) throw new OreApiError(ORE_ERRORS.eachDuplicateKey(key, i));\n\n nextKeySet.add(key);\n nextKeys.push(key);\n }\n\n // Remove stale entries from the map\n for (const [key, entry] of itemsMap) {\n if (!nextKeySet.has(key)) {\n removeItem(entry);\n itemsMap.delete(key);\n }\n }\n\n // Build the next ordered list: update existing items, create new ones\n const nextOrdered: ItemEntry<T>[] = [];\n\n for (let i = 0; i < next.length; i++) {\n const key = nextKeys[i];\n const existing = itemsMap.get(key);\n\n if (existing) {\n batch(() => {\n existing.data.value = next[i];\n existing.index.value = i;\n });\n nextOrdered.push(existing);\n } else {\n const entry = untrack(() => createItem(next[i], i, render, parent, endMarker));\n\n entry.key = key;\n itemsMap.set(key, entry);\n nextOrdered.push(entry);\n }\n }\n\n // DOM ordering: right-to-left pass — move any item not already adjacent to cursor.\n // O(n) DOM operations in the worst case; optimal for the typical small list sizes\n // encountered in UI components (tabs, options, menu items).\n let cursor: Node = endMarker;\n\n for (let j = nextOrdered.length - 1; j >= 0; j--) {\n const entry = nextOrdered[j];\n const firstNode = entry.nodes[0];\n\n if (firstNode && firstNode !== cursor.previousSibling) {\n for (const node of entry.nodes) parent.insertBefore(node, cursor);\n }\n\n cursor = firstNode ?? cursor;\n }\n\n return nextOrdered;\n};\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Renders a keyed list of items as a `DirectiveResult`.\n *\n * Each item is rendered by the provided render function.\n * Items are reused by key when the list changes; only stale items are destroyed.\n *\n * The render function receives a `Readable<T>` signal and a `Readable<number>` index\n * signal. Use `item.value` to read the current item inside reactive expressions:\n *\n * ```ts\n * html`${each(items, (item) => item.id, (item) => html`<li>${() => item.value.name}</li>`)}`\n * ```\n *\n * **Plain array:** when a plain `T[]` is passed (not a signal or getter), it is\n * treated as a one-time static render. Mutations to the original array are not\n * tracked. Pass a `Signal<T[]>` or `() => T[]` for reactive lists.\n *\n * **Optional fallback:** the fourth argument renders when the list is empty.\n *\n * **Key choice:** pass a stable item identifier (e.g. `item.id`), never the\n * array index — an index-based key reassigns to a different item whenever the\n * list is reordered or an item is inserted/removed before it, causing full\n * item teardown/recreation instead of the in-place update `each()` is built\n * for.\n *\n * **Duplicate keys:** a reconciliation failure (e.g. duplicate keys, see `eachDuplicateKey`)\n * does not throw past this function — an uncaught exception inside the reactive effect that\n * drives `each()` would risk corrupting unrelated effects scheduled in the same update batch.\n * Instead the list is cleared and the failure is reported via the `ore:error` DOM event (see\n * `OreLifecycleError`, phase `'each-reconcile'`) plus a dev-only console log — listen for\n * `ore:error` on `document`/`window` to observe this in every build, including production.\n */\nexport function each<T>(\n list: MaybeReactiveArray<T>,\n keyFn: (item: T, index: number) => string | number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n fallback?: () => HTMLResult,\n): DirectiveResult {\n const listSignal = Array.isArray(list)\n ? signal(list as T[])\n : typeof list === 'function'\n ? computed(list as () => T[])\n : list;\n\n return createDirectiveResult((anchor, registerCleanup) => {\n const parent = anchor.parentNode;\n\n invariant(parent, 'each() anchor comment has no parent node');\n\n const endMarker = document.createComment('each/end');\n\n parent.insertBefore(endMarker, anchor.nextSibling);\n\n let itemsMap = new Map<string, ItemEntry<T>>();\n let itemsOrdered: ItemEntry<T>[] = [];\n let fallbackNodes: Node[] | null = null;\n let fallbackCleanups: (() => void)[] = [];\n\n const mountFallback = (): void => {\n if (!fallback) return;\n\n const result = fallback();\n\n fallbackNodes = result.mount(parent, endMarker, (fn) => fallbackCleanups.push(fn));\n };\n\n const clearFallback = (): void => {\n if (fallbackNodes) {\n runAll(fallbackCleanups);\n removeNodes(fallbackNodes);\n fallbackNodes = null;\n fallbackCleanups = [];\n }\n };\n\n const sub = rawEffect(() => {\n const nextList = listSignal.value ?? [];\n\n if (nextList.length === 0) {\n for (const entry of untrack(() => itemsOrdered)) removeItem(entry);\n itemsMap = new Map();\n itemsOrdered = [];\n\n if (!fallbackNodes) untrack(mountFallback);\n\n return;\n }\n\n clearFallback();\n\n try {\n itemsOrdered = untrack(() => reconcileItems(itemsMap, nextList, keyFn, render, parent, endMarker));\n } catch (err) {\n const cause = err instanceof Error ? err : new Error(String(err));\n\n // Dispatched on the anchor comment (always a live DOM node) rather than the enclosing\n // component's host element, which each() has no direct reference to — the event still\n // bubbles/composes up to any ancestor listener, including a global one on document.\n reportRuntimeError(\n new OreLifecycleError(`each() failed to reconcile a list update: ${cause.message}`, {\n cause,\n component: 'each()',\n phase: 'each-reconcile',\n }),\n anchor,\n );\n\n for (const entry of itemsMap.values()) removeItem(entry);\n itemsMap = new Map();\n itemsOrdered = [];\n }\n });\n\n registerCleanup(() => sub.dispose());\n registerCleanup(() => {\n clearFallback();\n for (const entry of itemsOrdered) removeItem(entry);\n endMarker.remove();\n });\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,8 +1,37 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The niche/advanced directives — everyday ones (`each`, `when`, `classMap`, `styleMap`,
|
|
3
|
+
* `model`) live in the main `@vielzeug/ore` entry instead. `raw()` is security-sensitive
|
|
4
|
+
* (renders trusted HTML unescaped) and `live()` is form-control-specific, so both stay behind
|
|
5
|
+
* a deliberate, separate import.
|
|
6
|
+
*/
|
|
3
7
|
export { live, type LiveSignal } from './live';
|
|
4
|
-
export { model } from './model';
|
|
5
8
|
export { raw, setRawSanitizer } from './raw';
|
|
6
|
-
|
|
7
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Authoring API for custom directives — building blocks `each()`/`when()`/`raw()`/`model()`
|
|
11
|
+
* are themselves built on. Use `createDirectiveResult()` to write a directive that controls
|
|
12
|
+
* DOM insertion directly (an `html\`...${myDirective(...)}...\`` slot), or
|
|
13
|
+
* `createSpreadObject()` to write a `model()`-style helper that applies several bindings
|
|
14
|
+
* (value sync, event listeners) to one element from a single template expression.
|
|
15
|
+
*
|
|
16
|
+
* Both factories stamp a runtime brand — a hand-built object matching `DirectiveResult`'s or
|
|
17
|
+
* `SpreadObject`'s shape without going through these factories is *not* recognized by the
|
|
18
|
+
* template engine and silently renders as stringified text instead. Always construct through
|
|
19
|
+
* these functions, never build the object literal yourself.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* import { createDirectiveResult, type DirectiveResult } from '@vielzeug/ore/directives';
|
|
24
|
+
*
|
|
25
|
+
* function fadeIn(): DirectiveResult {
|
|
26
|
+
* return createDirectiveResult((anchor, registerCleanup) => {
|
|
27
|
+
* const el = document.createElement('div');
|
|
28
|
+
* el.textContent = 'Hello';
|
|
29
|
+
* anchor.parentNode?.insertBefore(el, anchor);
|
|
30
|
+
* registerCleanup(() => el.remove());
|
|
31
|
+
* });
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export { createDirectiveResult, createSpreadObject, isDirectiveResult, isSpreadObject } from '../types/bindings';
|
|
36
|
+
export type { DirectiveResult, SpreadObject } from '../types/bindings';
|
|
8
37
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/directives/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/directives/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,IAAI,EAAE,KAAK,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC/C,OAAO,EAAE,GAAG,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACjH,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
|