@vielzeug/ore 26.11.6 → 26.11.8
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/ore.cjs +1 -1
- package/dist/ore.cjs.map +1 -1
- package/dist/ore.iife.js +1 -1
- package/dist/ore.iife.js.map +1 -1
- package/dist/ore.js +15 -16
- package/dist/ore.js.map +1 -1
- package/dist/template/binding-types.d.ts +10 -1
- package/dist/template/binding-types.d.ts.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 +1 -1
- package/dist/template/instantiator.cjs.map +1 -1
- package/dist/template/instantiator.d.ts.map +1 -1
- package/dist/template/instantiator.js +1 -1
- package/dist/template/instantiator.js.map +1 -1
- package/package.json +5 -5
|
@@ -67,7 +67,16 @@ export type RefBinding = {
|
|
|
67
67
|
export type HtmlBindingValue = HTMLResult | string | number | boolean | null | undefined;
|
|
68
68
|
export type HtmlBinding = {
|
|
69
69
|
anchor: Comment;
|
|
70
|
-
|
|
70
|
+
/**
|
|
71
|
+
* The raw reactive source, read inside the render effect rather than through a
|
|
72
|
+
* memo computed: a reactive value is usually a function that builds nested
|
|
73
|
+
* `html` results, and building them allocates their per-node binding computeds.
|
|
74
|
+
* Ripple attaches a computed to the scope active when it is created, so a memo
|
|
75
|
+
* computed would let any effect that happens to trigger its eager refresh become
|
|
76
|
+
* the owner - and dispose those bindings on its next run while this slot still
|
|
77
|
+
* shows the nodes they drive.
|
|
78
|
+
*/
|
|
79
|
+
source: (() => unknown) | Readable<unknown>;
|
|
71
80
|
type: 'html';
|
|
72
81
|
};
|
|
73
82
|
export type DirectiveBinding = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"binding-types.d.ts","sourceRoot":"","sources":["../../src/template/binding-types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAI9E,KAAK,cAAc,GAAG;IACpB,EAAE,EAAE,WAAW,CAAC;IAChB;;;;OAIG;IACH,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,yDAAyD;AACzD,MAAM,MAAM,iBAAiB,GAAG,cAAc,GAAG;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAEpE,qDAAqD;AACrD,MAAM,MAAM,mBAAmB,GAAG,cAAc,GAAG;IAAE,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC;AAEjF,MAAM,MAAM,WAAW,GAAG,iBAAiB,GAAG,mBAAmB,CAAC;AAElE,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,WAAW,CAAC;IAChB,OAAO,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;CACf,CAAC;AAEF,kGAAkG;AAClG,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,WAAW,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,CAAC;IAAE,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAEzD;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,WAAW,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,OAAO,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IACxB,IAAI,EAAE,OAAO,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,WAAW,CAAC;IAChB,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACzC,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,oDAAoD;AACpD,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;AAEzF,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"binding-types.d.ts","sourceRoot":"","sources":["../../src/template/binding-types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAEzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAI9E,KAAK,cAAc,GAAG;IACpB,EAAE,EAAE,WAAW,CAAC;IAChB;;;;OAIG;IACH,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,yDAAyD;AACzD,MAAM,MAAM,iBAAiB,GAAG,cAAc,GAAG;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAEpE,qDAAqD;AACrD,MAAM,MAAM,mBAAmB,GAAG,cAAc,GAAG;IAAE,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC;AAEjF,MAAM,MAAM,WAAW,GAAG,iBAAiB,GAAG,mBAAmB,CAAC;AAElE,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,WAAW,CAAC;IAChB,OAAO,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;CACf,CAAC;AAEF,kGAAkG;AAClG,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,WAAW,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,CAAC;IAAE,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAEzD;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,WAAW,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,OAAO,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;IACxB,IAAI,EAAE,OAAO,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,WAAW,CAAC;IAChB,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACzC,IAAI,EAAE,KAAK,CAAC;CACb,CAAC;AAEF,oDAAoD;AACpD,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;AAEzF,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,OAAO,CAAC;IAChB;;;;;;;;OAQG;IACH,MAAM,EAAE,CAAC,MAAM,OAAO,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC5C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,eAAe,CAAC;IAC3B,IAAI,EAAE,WAAW,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,OAAO,GACf,WAAW,GACX,gBAAgB,GAChB,YAAY,GACZ,WAAW,GACX,YAAY,GACZ,WAAW,GACX,UAAU,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../directives/live.cjs"),t=require("../errors.cjs"),n=require("../props.cjs"),r=require("../utils/dom.cjs"),i=require("./result.cjs");let a=require("@vielzeug/ripple");var o=(e,t,n)=>{let r=(0,a.effect)(()=>{t(e.value)});n(()=>r.dispose())},s=e=>e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement,c=e=>e instanceof HTMLInputElement&&(e.type===`checkbox`||e.type===`radio`),l=(e,t,n,r={last:void 0})=>{let i=c(e),a=i?!!t:t==null?``:String(t),o=i?e.checked:e.value;(!n||r.last===void 0||Object.is(o,r.last)||Object.is(o,a))&&(i?e.checked=a:e.value=a,n&&(r.last=a))},u=(e,t,n,i)=>{let o=r.isStructuredValue(i)?i:t.parse(n.mode===`bool`?i?``:null:i==null||i===!1?null:String(i));if(Object.is((0,a.untrack)(()=>t.signal.value),o)||(t.signal.value=o),!t.reflect){if(r.isStructuredValue(i))return;n.mode===`bool`?e.toggleAttribute(n.name,!!i):r.setAttr(e,n.name,i)}},d=(e,t)=>{let{el:i,mode:a,name:c}=e,d={last:void 0},f=t=>{let o=n.getPropMeta(i,c);o?u(i,o,e,t):c===`value`&&s(i)||c===`checked`&&i instanceof HTMLInputElement?l(i,t,e.live,d):c===`indeterminate`&&i instanceof HTMLInputElement?i.indeterminate=!!t:a===`bool`?i.toggleAttribute(c,!!t):r.setAttr(i,c,t)};`signal`in e?o(e.signal,f,t):f(e.value)},f=(e,t)=>{let{modifiers:n}=e,i=n?.stop||n?.prevent?t=>{n.stop&&t.stopPropagation(),n.prevent&&t.preventDefault(),e.handler(t)}:e.handler,a=n?.once||n?.capture?{capture:n.capture,once:n.once}:void 0;t(r.listen(e.el,e.name,i,a))},p=(e,t)=>{let n=t=>{e.el[e.name]=t};`signal`in e?o(e.signal,n,t):n(e.value)},m=(e,t)=>{let{el:n,get:i,signal:o}=e,c={last:void 0},u=e=>{s(n)?l(n,e,!0,c):n.value=e},d=(0,a.effect)(()=>{u(o.value)});t(()=>d.dispose()),t(r.listen(n,e.event,()=>{let e=i(n);Object.is((0,a.untrack)(()=>o.value),e)||(o.value=e)}))},h=(e,t)=>{let{el:n,ref:r}=e;typeof r==`function`?(r(n),t(()=>r(null))):(r.value=n,t(()=>{r.value=null}))},g=(e,n,r)=>{let a=[],o=n.parentNode;t.invariant(o,`html binding anchor has no parent node`);for(let t of e)if(i.isHtmlResult(t)){let e=Array.from(t.fragment.childNodes);o.insertBefore(t.fragment,n),t.apply(r),a.push(...e)}else if(t!=null&&t!==!1){let e=document.createTextNode(String(t));o.insertBefore(e,n),a.push(e)}return a},_=(e,t)=>{let{anchor:n,
|
|
1
|
+
const e=require("../directives/live.cjs"),t=require("../errors.cjs"),n=require("../props.cjs"),r=require("../utils/dom.cjs"),i=require("./result.cjs");let a=require("@vielzeug/ripple");var o=(e,t,n)=>{let r=(0,a.effect)(()=>{t(e.value)});n(()=>r.dispose())},s=e=>e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement,c=e=>e instanceof HTMLInputElement&&(e.type===`checkbox`||e.type===`radio`),l=(e,t,n,r={last:void 0})=>{let i=c(e),a=i?!!t:t==null?``:String(t),o=i?e.checked:e.value;(!n||r.last===void 0||Object.is(o,r.last)||Object.is(o,a))&&(i?e.checked=a:e.value=a,n&&(r.last=a))},u=(e,t,n,i)=>{let o=r.isStructuredValue(i)?i:t.parse(n.mode===`bool`?i?``:null:i==null||i===!1?null:String(i));if(Object.is((0,a.untrack)(()=>t.signal.value),o)||(t.signal.value=o),!t.reflect){if(r.isStructuredValue(i))return;n.mode===`bool`?e.toggleAttribute(n.name,!!i):r.setAttr(e,n.name,i)}},d=(e,t)=>{let{el:i,mode:a,name:c}=e,d={last:void 0},f=t=>{let o=n.getPropMeta(i,c);o?u(i,o,e,t):c===`value`&&s(i)||c===`checked`&&i instanceof HTMLInputElement?l(i,t,e.live,d):c===`indeterminate`&&i instanceof HTMLInputElement?i.indeterminate=!!t:a===`bool`?i.toggleAttribute(c,!!t):r.setAttr(i,c,t)};`signal`in e?o(e.signal,f,t):f(e.value)},f=(e,t)=>{let{modifiers:n}=e,i=n?.stop||n?.prevent?t=>{n.stop&&t.stopPropagation(),n.prevent&&t.preventDefault(),e.handler(t)}:e.handler,a=n?.once||n?.capture?{capture:n.capture,once:n.once}:void 0;t(r.listen(e.el,e.name,i,a))},p=(e,t)=>{let n=t=>{e.el[e.name]=t};`signal`in e?o(e.signal,n,t):n(e.value)},m=(e,t)=>{let{el:n,get:i,signal:o}=e,c={last:void 0},u=e=>{s(n)?l(n,e,!0,c):n.value=e},d=(0,a.effect)(()=>{u(o.value)});t(()=>d.dispose()),t(r.listen(n,e.event,()=>{let e=i(n);Object.is((0,a.untrack)(()=>o.value),e)||(o.value=e)}))},h=(e,t)=>{let{el:n,ref:r}=e;typeof r==`function`?(r(n),t(()=>r(null))):(r.value=n,t(()=>{r.value=null}))},g=(e,n,r)=>{let a=[],o=n.parentNode;t.invariant(o,`html binding anchor has no parent node`);for(let t of e)if(i.isHtmlResult(t)){let e=Array.from(t.fragment.childNodes);o.insertBefore(t.fragment,n),t.apply(r),a.push(...e)}else if(t!=null&&t!==!1){let e=document.createTextNode(String(t));o.insertBefore(e,n),a.push(e)}return a},_=e=>Array.isArray(e)?e:[e],v=(e,t)=>{let{anchor:n,source:i}=e,o=r.createReplaceableSlot(),s=(0,a.effect)(()=>{let e=_(typeof i==`function`?i():i.value);o.clear(),e!=null&&e.length!==0&&(0,a.untrack)(()=>{o.setNodes(g(e,n,o.registerCleanup))})});t(()=>{s.dispose(),o.clear()})},y=(e,t)=>{e.directive.mount(e.anchor,t)},b=(e,t)=>{switch(e.type){case`attr`:d(e,t);break;case`directive`:y(e,t);break;case`event`:f(e,t);break;case`html`:v(e,t);break;case`model`:m(e,t);break;case`prop`:p(e,t);break;case`ref`:h(e,t)}},x=(t,n,r,i)=>e.isLiveBinding(i)?{el:t,live:!0,mode:n,name:r,signal:i.source,type:`attr`}:typeof i==`function`?{el:t,mode:n,name:r,signal:(0,a.computed)(i),type:`attr`}:(0,a.isReactive)(i)?{el:t,mode:n,name:r,signal:i,type:`attr`}:{el:t,mode:n,name:r,type:`attr`,value:i},S=e=>e==null?``:String(e);exports.applyAttrBinding=d,exports.applyBinding=b,exports.applyHtmlBinding=v,exports.createAttrBindingFromValue=x,exports.resolveStaticText=S,exports.syncFormControl=l;
|
|
2
2
|
//# sourceMappingURL=bindings.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bindings.cjs","names":["rawEffect","isStructuredValue","untrack","getPropMeta","listen","isHtmlResult","createReplaceableSlot","isLiveBinding","computed","isReactive","require_live","require_errors","require_props","require_dom","require_result","_vielzeug_ripple","signalEffect","sub","update","signal","registerCleanup","isNativeFormInput","el","isCheckableInput","syncFormControl","checkable","next","value","current","state","isLive","syncRegisteredProp","parsed","meta","binding","applyAttrBinding","liveState","propMeta","name","mode","applyEventBinding","handler","modifiers","e","options","applyPropBinding","applyModelBinding","write","get","applyRefBinding","ref","insertHtmlValues","nodes","parent","insertBefore","v","values","captured","text","applyHtmlBinding","slot","stop","raw","anchor","applyDirectiveBinding","applyBinding","createAttrBindingFromValue","resolveStaticText"],"sources":["../../src/template/bindings.ts"],"sourcesContent":["/**\n * template/bindings.ts: Runtime binding appliers.\n *\n * Responsibilities:\n * - Apply each Binding variant to the live DOM (attr, event, html, model,\n * prop, ref, directive).\n * - Manage reactive effects and cleanup registration.\n * - Expose `applyBinding()` as the single dispatch entry point.\n * - Own the signal-to-form-control write path (`syncFormControl`) for regular\n * attribute bindings, and the two-way `model()` write-back loop.\n */\n\nimport { computed, isReactive, type Readable, effect as rawEffect, type Signal, untrack } from '@vielzeug/ripple';\n\nimport { isLiveBinding } from '../directives/live';\nimport { invariant } from '../errors';\nimport { getPropMeta, type PropMeta } from '../props';\nimport { createReplaceableSlot, isStructuredValue, listen, setAttr } from '../utils/dom';\nimport type {\n AttrBinding,\n Binding,\n DirectiveBinding,\n EventBinding,\n HtmlBinding,\n HtmlBindingValue,\n ModelBinding,\n PropBinding,\n RefBinding,\n} from './binding-types';\nimport { isHtmlResult } from './result';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type RegisterCleanup = (fn: () => void) => void;\n\n// ─── Signal helpers ───────────────────────────────────────────────────────────\n\nconst signalEffect = (\n signal: Readable<unknown>,\n update: (v: unknown) => void,\n registerCleanup: RegisterCleanup,\n): void => {\n const sub = rawEffect(() => {\n update(signal.value);\n return undefined;\n });\n\n registerCleanup(() => sub.dispose());\n};\n\n// ─── Form control value sync ──────────────────────────────────────────────────\n\nconst isNativeFormInput = (el: HTMLElement): el is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement =>\n el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement;\n\nconst isCheckableInput = (el: HTMLElement): el is HTMLInputElement =>\n el instanceof HTMLInputElement && (el.type === 'checkbox' || el.type === 'radio');\n\ntype LiveWriteState = { last: unknown };\n\n/**\n * The single write path from a reactive value to a form control's `value`/`checked`\n * property for regular attribute bindings.\n *\n * Live-write: when the binding was created from `live(source)`, a write is skipped\n * if the DOM value has diverged from this binding's last write (in-progress user\n * input): unless the incoming value already matches the DOM (write would be a no-op).\n */\nexport const syncFormControl = (\n el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n value: unknown,\n isLive?: boolean,\n state: LiveWriteState = { last: undefined },\n): void => {\n const checkable = isCheckableInput(el);\n const next: boolean | string = checkable ? Boolean(value) : value == null ? '' : String(value);\n const current: boolean | string = checkable ? (el as HTMLInputElement).checked : el.value;\n\n if (isLive && state.last !== undefined && !Object.is(current, state.last) && !Object.is(current, next)) return;\n\n if (checkable) (el as HTMLInputElement).checked = next as boolean;\n else el.value = next as string;\n\n if (isLive) state.last = next;\n};\n\n// ─── Attributes ───────────────────────────────────────────────────────────────\n\nconst syncRegisteredProp = (el: HTMLElement, meta: PropMeta, binding: AttrBinding, value: unknown): void => {\n const parsed = isStructuredValue(value)\n ? value\n : meta.parse(\n binding.mode === 'bool' ? (value ? '' : null) : value == null || value === false ? null : String(value),\n );\n\n if (\n !Object.is(\n untrack(() => meta.signal.value),\n parsed,\n )\n ) {\n meta.signal.value = parsed;\n }\n\n if (!meta.reflect) {\n if (isStructuredValue(value)) return;\n\n if (binding.mode === 'bool') el.toggleAttribute(binding.name, Boolean(value));\n else setAttr(el, binding.name, value);\n }\n};\n\nexport const applyAttrBinding = (binding: AttrBinding, registerCleanup: RegisterCleanup): void => {\n const { el, mode, name } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const update = (value: unknown): void => {\n // Resolved per update, not at instantiation: a nested custom element only\n // registers its props when it is connected, which happens when this result\n // mounts: later than binding construction. By update time the registry entry\n // exists, so a structured value (array/object/callback) reaches a registered\n // prop through its signal instead of an attribute round-trip that could not\n // represent it. Unregistered targets get no such pass: structured data to\n // them is an explicit `.prop=` binding.\n const propMeta = getPropMeta(el, name);\n\n if (propMeta) {\n syncRegisteredProp(el, propMeta, binding, value);\n\n return;\n }\n\n if ((name === 'value' && isNativeFormInput(el)) || (name === 'checked' && el instanceof HTMLInputElement)) {\n syncFormControl(el as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement, value, binding.live, liveState);\n\n return;\n }\n\n // `indeterminate` is a property-only concept on checkboxes (no attribute exists):\n // bind it like any other element property so reactive indeterminate state works\n // through the same `indeterminate=\"${sig}\"` syntax as `checked`.\n if (name === 'indeterminate' && el instanceof HTMLInputElement) {\n el.indeterminate = Boolean(value);\n\n return;\n }\n\n if (mode === 'bool') el.toggleAttribute(name, Boolean(value));\n else setAttr(el, name, value);\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Events ───────────────────────────────────────────────────────────────────\n\nconst applyEventBinding = (binding: EventBinding, registerCleanup: RegisterCleanup): void => {\n const { modifiers } = binding;\n\n const handler =\n modifiers?.stop || modifiers?.prevent\n ? (e: Event): void => {\n if (modifiers.stop) e.stopPropagation();\n if (modifiers.prevent) e.preventDefault();\n binding.handler(e);\n }\n : binding.handler;\n\n const options =\n modifiers?.once || modifiers?.capture ? { capture: modifiers.capture, once: modifiers.once } : undefined;\n\n registerCleanup(listen(binding.el, binding.name, handler, options));\n};\n\n// ─── Explicit property bindings ───────────────────────────────────────────────\n\n/**\n * `.prop=${value}` binding: always writes the element property (never an\n * attribute). This is the explicit channel for structured values (arrays,\n * objects, callbacks): the old implicit \"value happens to be an object, so set\n * a property\" fallback in the attr path is gone, so intent lives in the markup.\n * Prototype-polluting names are rejected at compile time (see compiler.ts\n * PROP_RE), not silently dropped here.\n */\nconst applyPropBinding = (binding: PropBinding, registerCleanup: RegisterCleanup): void => {\n const update = (value: unknown): void => {\n (binding.el as unknown as Record<string, unknown>)[binding.name] = value;\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Two-way model bindings ──────────────────────────────────────────────────\n\n/**\n * `model()` binding: signal → control property (through the same live-aware\n * `syncFormControl` write path as `live()`, so an in-progress user edit is never\n * clobbered) and control event → signal (the write-back that makes it two-way).\n */\nconst applyModelBinding = (binding: ModelBinding, registerCleanup: RegisterCleanup): void => {\n const { el, get, signal } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const write = (value: unknown): void => {\n if (isNativeFormInput(el)) {\n syncFormControl(el, value, true, liveState);\n } else {\n // Non-native target: plain property write (custom controls expose `value`).\n (el as unknown as { value: unknown }).value = value;\n }\n };\n\n const sub = rawEffect(() => {\n write(signal.value);\n return undefined;\n });\n\n const onEvent: EventListener = () => {\n const next = get(el);\n\n if (\n !Object.is(\n untrack(() => signal.value),\n next,\n )\n ) {\n (signal as Signal<unknown>).value = next;\n }\n };\n\n registerCleanup(() => sub.dispose());\n registerCleanup(listen(el, binding.event, onEvent));\n};\n\n// ─── Refs ─────────────────────────────────────────────────────────────────────\n\nconst applyRefBinding = (binding: RefBinding, registerCleanup: RegisterCleanup): void => {\n const { el, ref } = binding;\n\n if (typeof ref === 'function') {\n ref(el as never);\n registerCleanup(() => ref(null as never));\n\n return;\n }\n\n ref.value = el as never;\n registerCleanup(() => {\n ref.value = null as never;\n });\n};\n\n// ─── HTML (reactive) ─────────────────────────────────────────────────────────\n\nconst insertHtmlValues = (\n values: HtmlBindingValue[],\n insertBefore: ChildNode,\n registerCleanup: RegisterCleanup,\n): Node[] => {\n const nodes: Node[] = [];\n const parent = insertBefore.parentNode;\n\n invariant(parent, 'html binding anchor has no parent node');\n\n for (const v of values) {\n if (isHtmlResult(v)) {\n const captured = Array.from(v.fragment.childNodes);\n\n parent.insertBefore(v.fragment, insertBefore);\n v.apply(registerCleanup);\n nodes.push(...captured);\n } else if (v != null && v !== false) {\n const text = document.createTextNode(String(v));\n\n parent.insertBefore(text, insertBefore);\n nodes.push(text);\n }\n }\n\n return nodes;\n};\n\nexport const applyHtmlBinding = (binding: HtmlBinding, registerCleanup: RegisterCleanup): void => {\n const { anchor, signal } = binding;\n\n const slot = createReplaceableSlot();\n\n const stop = rawEffect(() => {\n const raw = signal.value;\n\n slot.clear();\n\n if (raw == null || raw.length === 0) return;\n\n untrack(() => {\n slot.setNodes(insertHtmlValues(raw, anchor, slot.registerCleanup));\n });\n });\n\n registerCleanup(() => {\n stop.dispose();\n slot.clear();\n });\n};\n\n// ─── Directives ───────────────────────────────────────────────────────────────\n\nconst applyDirectiveBinding = (binding: DirectiveBinding, registerCleanup: RegisterCleanup): void => {\n binding.directive.mount(binding.anchor, registerCleanup);\n};\n\n// ─── Binding dispatch ─────────────────────────────────────────────────────────\n\nexport const applyBinding = (binding: Binding, registerCleanup: RegisterCleanup): void => {\n switch (binding.type) {\n case 'attr':\n applyAttrBinding(binding, registerCleanup);\n break;\n case 'directive':\n applyDirectiveBinding(binding, registerCleanup);\n break;\n case 'event':\n applyEventBinding(binding, registerCleanup);\n break;\n case 'html':\n applyHtmlBinding(binding, registerCleanup);\n break;\n case 'model':\n applyModelBinding(binding, registerCleanup);\n break;\n case 'prop':\n applyPropBinding(binding, registerCleanup);\n break;\n case 'ref':\n applyRefBinding(binding, registerCleanup);\n break;\n }\n};\n\n// ─── Attr binding factory (used by instantiator) ──────────────────────────────\n\nexport const createAttrBindingFromValue = (\n el: HTMLElement,\n mode: 'attr' | 'bool',\n name: string,\n value: unknown,\n): AttrBinding => {\n if (isLiveBinding(value)) {\n return { el, live: true, mode, name, signal: value.source, type: 'attr' };\n }\n\n if (typeof value === 'function') {\n return { el, mode, name, signal: computed(value as () => unknown), type: 'attr' };\n }\n\n if (isReactive(value)) {\n return { el, mode, name, signal: value as Readable<unknown>, type: 'attr' };\n }\n\n return { el, mode, name, type: 'attr', value };\n};\n\nexport const resolveStaticText = (value: unknown): string => {\n if (value == null) return '';\n\n return String(value);\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bindings.cjs","names":["rawEffect","isStructuredValue","untrack","getPropMeta","listen","isHtmlResult","createReplaceableSlot","isLiveBinding","computed","isReactive","require_live","require_errors","require_props","require_dom","require_result","_vielzeug_ripple","signalEffect","sub","update","signal","registerCleanup","isNativeFormInput","el","isCheckableInput","syncFormControl","checkable","next","value","current","state","isLive","syncRegisteredProp","parsed","meta","binding","applyAttrBinding","liveState","propMeta","name","mode","applyEventBinding","handler","modifiers","e","options","applyPropBinding","applyModelBinding","write","get","applyRefBinding","ref","insertHtmlValues","nodes","parent","insertBefore","v","values","captured","text","toHtmlValues","raw","applyHtmlBinding","slot","stop","source","anchor","applyDirectiveBinding","applyBinding","createAttrBindingFromValue","resolveStaticText"],"sources":["../../src/template/bindings.ts"],"sourcesContent":["/**\n * template/bindings.ts: Runtime binding appliers.\n *\n * Responsibilities:\n * - Apply each Binding variant to the live DOM (attr, event, html, model,\n * prop, ref, directive).\n * - Manage reactive effects and cleanup registration.\n * - Expose `applyBinding()` as the single dispatch entry point.\n * - Own the signal-to-form-control write path (`syncFormControl`) for regular\n * attribute bindings, and the two-way `model()` write-back loop.\n */\n\nimport { computed, isReactive, type Readable, effect as rawEffect, type Signal, untrack } from '@vielzeug/ripple';\n\nimport { isLiveBinding } from '../directives/live';\nimport { invariant } from '../errors';\nimport { getPropMeta, type PropMeta } from '../props';\nimport { createReplaceableSlot, isStructuredValue, listen, setAttr } from '../utils/dom';\nimport type {\n AttrBinding,\n Binding,\n DirectiveBinding,\n EventBinding,\n HtmlBinding,\n HtmlBindingValue,\n ModelBinding,\n PropBinding,\n RefBinding,\n} from './binding-types';\nimport { isHtmlResult } from './result';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type RegisterCleanup = (fn: () => void) => void;\n\n// ─── Signal helpers ───────────────────────────────────────────────────────────\n\nconst signalEffect = (\n signal: Readable<unknown>,\n update: (v: unknown) => void,\n registerCleanup: RegisterCleanup,\n): void => {\n const sub = rawEffect(() => {\n update(signal.value);\n return undefined;\n });\n\n registerCleanup(() => sub.dispose());\n};\n\n// ─── Form control value sync ──────────────────────────────────────────────────\n\nconst isNativeFormInput = (el: HTMLElement): el is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement =>\n el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement;\n\nconst isCheckableInput = (el: HTMLElement): el is HTMLInputElement =>\n el instanceof HTMLInputElement && (el.type === 'checkbox' || el.type === 'radio');\n\ntype LiveWriteState = { last: unknown };\n\n/**\n * The single write path from a reactive value to a form control's `value`/`checked`\n * property for regular attribute bindings.\n *\n * Live-write: when the binding was created from `live(source)`, a write is skipped\n * if the DOM value has diverged from this binding's last write (in-progress user\n * input): unless the incoming value already matches the DOM (write would be a no-op).\n */\nexport const syncFormControl = (\n el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n value: unknown,\n isLive?: boolean,\n state: LiveWriteState = { last: undefined },\n): void => {\n const checkable = isCheckableInput(el);\n const next: boolean | string = checkable ? Boolean(value) : value == null ? '' : String(value);\n const current: boolean | string = checkable ? (el as HTMLInputElement).checked : el.value;\n\n if (isLive && state.last !== undefined && !Object.is(current, state.last) && !Object.is(current, next)) return;\n\n if (checkable) (el as HTMLInputElement).checked = next as boolean;\n else el.value = next as string;\n\n if (isLive) state.last = next;\n};\n\n// ─── Attributes ───────────────────────────────────────────────────────────────\n\nconst syncRegisteredProp = (el: HTMLElement, meta: PropMeta, binding: AttrBinding, value: unknown): void => {\n const parsed = isStructuredValue(value)\n ? value\n : meta.parse(\n binding.mode === 'bool' ? (value ? '' : null) : value == null || value === false ? null : String(value),\n );\n\n if (\n !Object.is(\n untrack(() => meta.signal.value),\n parsed,\n )\n ) {\n meta.signal.value = parsed;\n }\n\n if (!meta.reflect) {\n if (isStructuredValue(value)) return;\n\n if (binding.mode === 'bool') el.toggleAttribute(binding.name, Boolean(value));\n else setAttr(el, binding.name, value);\n }\n};\n\nexport const applyAttrBinding = (binding: AttrBinding, registerCleanup: RegisterCleanup): void => {\n const { el, mode, name } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const update = (value: unknown): void => {\n // Resolved per update, not at instantiation: a nested custom element only\n // registers its props when it is connected, which happens when this result\n // mounts: later than binding construction. By update time the registry entry\n // exists, so a structured value (array/object/callback) reaches a registered\n // prop through its signal instead of an attribute round-trip that could not\n // represent it. Unregistered targets get no such pass: structured data to\n // them is an explicit `.prop=` binding.\n const propMeta = getPropMeta(el, name);\n\n if (propMeta) {\n syncRegisteredProp(el, propMeta, binding, value);\n\n return;\n }\n\n if ((name === 'value' && isNativeFormInput(el)) || (name === 'checked' && el instanceof HTMLInputElement)) {\n syncFormControl(el as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement, value, binding.live, liveState);\n\n return;\n }\n\n // `indeterminate` is a property-only concept on checkboxes (no attribute exists):\n // bind it like any other element property so reactive indeterminate state works\n // through the same `indeterminate=\"${sig}\"` syntax as `checked`.\n if (name === 'indeterminate' && el instanceof HTMLInputElement) {\n el.indeterminate = Boolean(value);\n\n return;\n }\n\n if (mode === 'bool') el.toggleAttribute(name, Boolean(value));\n else setAttr(el, name, value);\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Events ───────────────────────────────────────────────────────────────────\n\nconst applyEventBinding = (binding: EventBinding, registerCleanup: RegisterCleanup): void => {\n const { modifiers } = binding;\n\n const handler =\n modifiers?.stop || modifiers?.prevent\n ? (e: Event): void => {\n if (modifiers.stop) e.stopPropagation();\n if (modifiers.prevent) e.preventDefault();\n binding.handler(e);\n }\n : binding.handler;\n\n const options =\n modifiers?.once || modifiers?.capture ? { capture: modifiers.capture, once: modifiers.once } : undefined;\n\n registerCleanup(listen(binding.el, binding.name, handler, options));\n};\n\n// ─── Explicit property bindings ───────────────────────────────────────────────\n\n/**\n * `.prop=${value}` binding: always writes the element property (never an\n * attribute). This is the explicit channel for structured values (arrays,\n * objects, callbacks): the old implicit \"value happens to be an object, so set\n * a property\" fallback in the attr path is gone, so intent lives in the markup.\n * Prototype-polluting names are rejected at compile time (see compiler.ts\n * PROP_RE), not silently dropped here.\n */\nconst applyPropBinding = (binding: PropBinding, registerCleanup: RegisterCleanup): void => {\n const update = (value: unknown): void => {\n (binding.el as unknown as Record<string, unknown>)[binding.name] = value;\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Two-way model bindings ──────────────────────────────────────────────────\n\n/**\n * `model()` binding: signal → control property (through the same live-aware\n * `syncFormControl` write path as `live()`, so an in-progress user edit is never\n * clobbered) and control event → signal (the write-back that makes it two-way).\n */\nconst applyModelBinding = (binding: ModelBinding, registerCleanup: RegisterCleanup): void => {\n const { el, get, signal } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const write = (value: unknown): void => {\n if (isNativeFormInput(el)) {\n syncFormControl(el, value, true, liveState);\n } else {\n // Non-native target: plain property write (custom controls expose `value`).\n (el as unknown as { value: unknown }).value = value;\n }\n };\n\n const sub = rawEffect(() => {\n write(signal.value);\n return undefined;\n });\n\n const onEvent: EventListener = () => {\n const next = get(el);\n\n if (\n !Object.is(\n untrack(() => signal.value),\n next,\n )\n ) {\n (signal as Signal<unknown>).value = next;\n }\n };\n\n registerCleanup(() => sub.dispose());\n registerCleanup(listen(el, binding.event, onEvent));\n};\n\n// ─── Refs ─────────────────────────────────────────────────────────────────────\n\nconst applyRefBinding = (binding: RefBinding, registerCleanup: RegisterCleanup): void => {\n const { el, ref } = binding;\n\n if (typeof ref === 'function') {\n ref(el as never);\n registerCleanup(() => ref(null as never));\n\n return;\n }\n\n ref.value = el as never;\n registerCleanup(() => {\n ref.value = null as never;\n });\n};\n\n// ─── HTML (reactive) ─────────────────────────────────────────────────────────\n\nconst insertHtmlValues = (\n values: HtmlBindingValue[],\n insertBefore: ChildNode,\n registerCleanup: RegisterCleanup,\n): Node[] => {\n const nodes: Node[] = [];\n const parent = insertBefore.parentNode;\n\n invariant(parent, 'html binding anchor has no parent node');\n\n for (const v of values) {\n if (isHtmlResult(v)) {\n const captured = Array.from(v.fragment.childNodes);\n\n parent.insertBefore(v.fragment, insertBefore);\n v.apply(registerCleanup);\n nodes.push(...captured);\n } else if (v != null && v !== false) {\n const text = document.createTextNode(String(v));\n\n parent.insertBefore(text, insertBefore);\n nodes.push(text);\n }\n }\n\n return nodes;\n};\n\n/** Normalize a reactive node-slot value to the render effect's array shape. */\nconst toHtmlValues = (raw: unknown): HtmlBindingValue[] =>\n Array.isArray(raw) ? (raw as HtmlBindingValue[]) : [raw as HtmlBindingValue];\n\nexport const applyHtmlBinding = (binding: HtmlBinding, registerCleanup: RegisterCleanup): void => {\n const { anchor, source } = binding;\n\n const slot = createReplaceableSlot();\n\n // The source is read here, not through a memo computed, so this effect is the\n // observer of its dependencies and the owner of every computed the value builds:\n // nested bindings are disposed exactly when this slot's nodes are replaced.\n const stop = rawEffect(() => {\n const raw = toHtmlValues(typeof source === 'function' ? source() : source.value);\n\n slot.clear();\n\n if (raw == null || raw.length === 0) return;\n\n untrack(() => {\n slot.setNodes(insertHtmlValues(raw, anchor, slot.registerCleanup));\n });\n });\n\n registerCleanup(() => {\n stop.dispose();\n slot.clear();\n });\n};\n\n// ─── Directives ───────────────────────────────────────────────────────────────\n\nconst applyDirectiveBinding = (binding: DirectiveBinding, registerCleanup: RegisterCleanup): void => {\n binding.directive.mount(binding.anchor, registerCleanup);\n};\n\n// ─── Binding dispatch ─────────────────────────────────────────────────────────\n\nexport const applyBinding = (binding: Binding, registerCleanup: RegisterCleanup): void => {\n switch (binding.type) {\n case 'attr':\n applyAttrBinding(binding, registerCleanup);\n break;\n case 'directive':\n applyDirectiveBinding(binding, registerCleanup);\n break;\n case 'event':\n applyEventBinding(binding, registerCleanup);\n break;\n case 'html':\n applyHtmlBinding(binding, registerCleanup);\n break;\n case 'model':\n applyModelBinding(binding, registerCleanup);\n break;\n case 'prop':\n applyPropBinding(binding, registerCleanup);\n break;\n case 'ref':\n applyRefBinding(binding, registerCleanup);\n break;\n }\n};\n\n// ─── Attr binding factory (used by instantiator) ──────────────────────────────\n\nexport const createAttrBindingFromValue = (\n el: HTMLElement,\n mode: 'attr' | 'bool',\n name: string,\n value: unknown,\n): AttrBinding => {\n if (isLiveBinding(value)) {\n return { el, live: true, mode, name, signal: value.source, type: 'attr' };\n }\n\n if (typeof value === 'function') {\n return { el, mode, name, signal: computed(value as () => unknown), type: 'attr' };\n }\n\n if (isReactive(value)) {\n return { el, mode, name, signal: value as Readable<unknown>, type: 'attr' };\n }\n\n return { el, mode, name, type: 'attr', value };\n};\n\nexport const resolveStaticText = (value: unknown): string => {\n if (value == null) return '';\n\n return String(value);\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../../src/template/bindings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH,OAAO,KAAK,EACV,WAAW,EACX,OAAO,EAGP,WAAW,EAKZ,MAAM,iBAAiB,CAAC;AAKzB,MAAM,MAAM,eAAe,GAAG,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;AAyBvD,KAAK,cAAc,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAExC;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAC1B,IAAI,gBAAgB,GAAG,mBAAmB,GAAG,iBAAiB,EAC9D,OAAO,OAAO,EACd,SAAS,OAAO,EAChB,QAAO,cAAoC,KAC1C,IAWF,CAAC;AA4BF,eAAO,MAAM,gBAAgB,GAAI,SAAS,WAAW,EAAE,iBAAiB,eAAe,KAAG,IA4CzF,CAAC;
|
|
1
|
+
{"version":3,"file":"bindings.d.ts","sourceRoot":"","sources":["../../src/template/bindings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH,OAAO,KAAK,EACV,WAAW,EACX,OAAO,EAGP,WAAW,EAKZ,MAAM,iBAAiB,CAAC;AAKzB,MAAM,MAAM,eAAe,GAAG,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;AAyBvD,KAAK,cAAc,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAExC;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAC1B,IAAI,gBAAgB,GAAG,mBAAmB,GAAG,iBAAiB,EAC9D,OAAO,OAAO,EACd,SAAS,OAAO,EAChB,QAAO,cAAoC,KAC1C,IAWF,CAAC;AA4BF,eAAO,MAAM,gBAAgB,GAAI,SAAS,WAAW,EAAE,iBAAiB,eAAe,KAAG,IA4CzF,CAAC;AA0IF,eAAO,MAAM,gBAAgB,GAAI,SAAS,WAAW,EAAE,iBAAiB,eAAe,KAAG,IAwBzF,CAAC;AAUF,eAAO,MAAM,YAAY,GAAI,SAAS,OAAO,EAAE,iBAAiB,eAAe,KAAG,IAwBjF,CAAC;AAIF,eAAO,MAAM,0BAA0B,GACrC,IAAI,WAAW,EACf,MAAM,MAAM,GAAG,MAAM,EACrB,MAAM,MAAM,EACZ,OAAO,OAAO,KACb,WAcF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,OAAO,OAAO,KAAG,MAIlD,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{isLiveBinding as e}from"../directives/live.js";import{invariant as t}from"../errors.js";import{getPropMeta as n}from"../props.js";import{createReplaceableSlot as r,isStructuredValue as i,listen as a,setAttr as o}from"../utils/dom.js";import{isHtmlResult as s}from"./result.js";import{computed as c,effect as l,isReactive as u,untrack as d}from"@vielzeug/ripple";var f=(e,t,n)=>{let r=l(()=>{t(e.value)});n(()=>r.dispose())},p=e=>e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement,m=e=>e instanceof HTMLInputElement&&(e.type===`checkbox`||e.type===`radio`),h=(e,t,n,r={last:void 0})=>{let i=m(e),a=i?!!t:t==null?``:String(t),o=i?e.checked:e.value;(!n||r.last===void 0||Object.is(o,r.last)||Object.is(o,a))&&(i?e.checked=a:e.value=a,n&&(r.last=a))},g=(e,t,n,r)=>{let a=i(r)?r:t.parse(n.mode===`bool`?r?``:null:r==null||r===!1?null:String(r));if(Object.is(d(()=>t.signal.value),a)||(t.signal.value=a),!t.reflect){if(i(r))return;n.mode===`bool`?e.toggleAttribute(n.name,!!r):o(e,n.name,r)}},_=(e,t)=>{let{el:r,mode:i,name:a}=e,s={last:void 0},c=t=>{let c=n(r,a);c?g(r,c,e,t):a===`value`&&p(r)||a===`checked`&&r instanceof HTMLInputElement?h(r,t,e.live,s):a===`indeterminate`&&r instanceof HTMLInputElement?r.indeterminate=!!t:i===`bool`?r.toggleAttribute(a,!!t):o(r,a,t)};`signal`in e?f(e.signal,c,t):c(e.value)},v=(e,t)=>{let{modifiers:n}=e,r=n?.stop||n?.prevent?t=>{n.stop&&t.stopPropagation(),n.prevent&&t.preventDefault(),e.handler(t)}:e.handler,i=n?.once||n?.capture?{capture:n.capture,once:n.once}:void 0;t(a(e.el,e.name,r,i))},y=(e,t)=>{let n=t=>{e.el[e.name]=t};`signal`in e?f(e.signal,n,t):n(e.value)},b=(e,t)=>{let{el:n,get:r,signal:i}=e,o={last:void 0},s=e=>{p(n)?h(n,e,!0,o):n.value=e},c=l(()=>{s(i.value)});t(()=>c.dispose()),t(a(n,e.event,()=>{let e=r(n);Object.is(d(()=>i.value),e)||(i.value=e)}))},x=(e,t)=>{let{el:n,ref:r}=e;typeof r==`function`?(r(n),t(()=>r(null))):(r.value=n,t(()=>{r.value=null}))},S=(e,n,r)=>{let i=[],a=n.parentNode;t(a,`html binding anchor has no parent node`);for(let t of e)if(s(t)){let e=Array.from(t.fragment.childNodes);a.insertBefore(t.fragment,n),t.apply(r),i.push(...e)}else if(t!=null&&t!==!1){let e=document.createTextNode(String(t));a.insertBefore(e,n),i.push(e)}return i},C=(e,t)=>{let{anchor:n,
|
|
1
|
+
import{isLiveBinding as e}from"../directives/live.js";import{invariant as t}from"../errors.js";import{getPropMeta as n}from"../props.js";import{createReplaceableSlot as r,isStructuredValue as i,listen as a,setAttr as o}from"../utils/dom.js";import{isHtmlResult as s}from"./result.js";import{computed as c,effect as l,isReactive as u,untrack as d}from"@vielzeug/ripple";var f=(e,t,n)=>{let r=l(()=>{t(e.value)});n(()=>r.dispose())},p=e=>e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement||e instanceof HTMLSelectElement,m=e=>e instanceof HTMLInputElement&&(e.type===`checkbox`||e.type===`radio`),h=(e,t,n,r={last:void 0})=>{let i=m(e),a=i?!!t:t==null?``:String(t),o=i?e.checked:e.value;(!n||r.last===void 0||Object.is(o,r.last)||Object.is(o,a))&&(i?e.checked=a:e.value=a,n&&(r.last=a))},g=(e,t,n,r)=>{let a=i(r)?r:t.parse(n.mode===`bool`?r?``:null:r==null||r===!1?null:String(r));if(Object.is(d(()=>t.signal.value),a)||(t.signal.value=a),!t.reflect){if(i(r))return;n.mode===`bool`?e.toggleAttribute(n.name,!!r):o(e,n.name,r)}},_=(e,t)=>{let{el:r,mode:i,name:a}=e,s={last:void 0},c=t=>{let c=n(r,a);c?g(r,c,e,t):a===`value`&&p(r)||a===`checked`&&r instanceof HTMLInputElement?h(r,t,e.live,s):a===`indeterminate`&&r instanceof HTMLInputElement?r.indeterminate=!!t:i===`bool`?r.toggleAttribute(a,!!t):o(r,a,t)};`signal`in e?f(e.signal,c,t):c(e.value)},v=(e,t)=>{let{modifiers:n}=e,r=n?.stop||n?.prevent?t=>{n.stop&&t.stopPropagation(),n.prevent&&t.preventDefault(),e.handler(t)}:e.handler,i=n?.once||n?.capture?{capture:n.capture,once:n.once}:void 0;t(a(e.el,e.name,r,i))},y=(e,t)=>{let n=t=>{e.el[e.name]=t};`signal`in e?f(e.signal,n,t):n(e.value)},b=(e,t)=>{let{el:n,get:r,signal:i}=e,o={last:void 0},s=e=>{p(n)?h(n,e,!0,o):n.value=e},c=l(()=>{s(i.value)});t(()=>c.dispose()),t(a(n,e.event,()=>{let e=r(n);Object.is(d(()=>i.value),e)||(i.value=e)}))},x=(e,t)=>{let{el:n,ref:r}=e;typeof r==`function`?(r(n),t(()=>r(null))):(r.value=n,t(()=>{r.value=null}))},S=(e,n,r)=>{let i=[],a=n.parentNode;t(a,`html binding anchor has no parent node`);for(let t of e)if(s(t)){let e=Array.from(t.fragment.childNodes);a.insertBefore(t.fragment,n),t.apply(r),i.push(...e)}else if(t!=null&&t!==!1){let e=document.createTextNode(String(t));a.insertBefore(e,n),i.push(e)}return i},C=e=>Array.isArray(e)?e:[e],w=(e,t)=>{let{anchor:n,source:i}=e,a=r(),o=l(()=>{let e=C(typeof i==`function`?i():i.value);a.clear(),e!=null&&e.length!==0&&d(()=>{a.setNodes(S(e,n,a.registerCleanup))})});t(()=>{o.dispose(),a.clear()})},T=(e,t)=>{e.directive.mount(e.anchor,t)},E=(e,t)=>{switch(e.type){case`attr`:_(e,t);break;case`directive`:T(e,t);break;case`event`:v(e,t);break;case`html`:w(e,t);break;case`model`:b(e,t);break;case`prop`:y(e,t);break;case`ref`:x(e,t)}},D=(t,n,r,i)=>e(i)?{el:t,live:!0,mode:n,name:r,signal:i.source,type:`attr`}:typeof i==`function`?{el:t,mode:n,name:r,signal:c(i),type:`attr`}:u(i)?{el:t,mode:n,name:r,signal:i,type:`attr`}:{el:t,mode:n,name:r,type:`attr`,value:i},O=e=>e==null?``:String(e);export{_ as applyAttrBinding,E as applyBinding,w as applyHtmlBinding,D as createAttrBindingFromValue,O as resolveStaticText,h as syncFormControl};
|
|
2
2
|
//# sourceMappingURL=bindings.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bindings.js","names":["rawEffect","signalEffect","sub","effect","update","signal","registerCleanup","isNativeFormInput","el","isCheckableInput","syncFormControl","checkable","next","value","current","state","isLive","syncRegisteredProp","parsed","isStructuredValue","meta","binding","untrack","setAttr","applyAttrBinding","liveState","propMeta","getPropMeta","name","mode","applyEventBinding","handler","modifiers","e","options","listen","applyPropBinding","applyModelBinding","write","get","applyRefBinding","ref","insertHtmlValues","nodes","parent","insertBefore","invariant","v","values","isHtmlResult","captured","text","applyHtmlBinding","slot","createReplaceableSlot","stop","raw","anchor","applyDirectiveBinding","applyBinding","createAttrBindingFromValue","isLiveBinding","computed","isReactive","resolveStaticText"],"sources":["../../src/template/bindings.ts"],"sourcesContent":["/**\n * template/bindings.ts: Runtime binding appliers.\n *\n * Responsibilities:\n * - Apply each Binding variant to the live DOM (attr, event, html, model,\n * prop, ref, directive).\n * - Manage reactive effects and cleanup registration.\n * - Expose `applyBinding()` as the single dispatch entry point.\n * - Own the signal-to-form-control write path (`syncFormControl`) for regular\n * attribute bindings, and the two-way `model()` write-back loop.\n */\n\nimport { computed, isReactive, type Readable, effect as rawEffect, type Signal, untrack } from '@vielzeug/ripple';\n\nimport { isLiveBinding } from '../directives/live';\nimport { invariant } from '../errors';\nimport { getPropMeta, type PropMeta } from '../props';\nimport { createReplaceableSlot, isStructuredValue, listen, setAttr } from '../utils/dom';\nimport type {\n AttrBinding,\n Binding,\n DirectiveBinding,\n EventBinding,\n HtmlBinding,\n HtmlBindingValue,\n ModelBinding,\n PropBinding,\n RefBinding,\n} from './binding-types';\nimport { isHtmlResult } from './result';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type RegisterCleanup = (fn: () => void) => void;\n\n// ─── Signal helpers ───────────────────────────────────────────────────────────\n\nconst signalEffect = (\n signal: Readable<unknown>,\n update: (v: unknown) => void,\n registerCleanup: RegisterCleanup,\n): void => {\n const sub = rawEffect(() => {\n update(signal.value);\n return undefined;\n });\n\n registerCleanup(() => sub.dispose());\n};\n\n// ─── Form control value sync ──────────────────────────────────────────────────\n\nconst isNativeFormInput = (el: HTMLElement): el is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement =>\n el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement;\n\nconst isCheckableInput = (el: HTMLElement): el is HTMLInputElement =>\n el instanceof HTMLInputElement && (el.type === 'checkbox' || el.type === 'radio');\n\ntype LiveWriteState = { last: unknown };\n\n/**\n * The single write path from a reactive value to a form control's `value`/`checked`\n * property for regular attribute bindings.\n *\n * Live-write: when the binding was created from `live(source)`, a write is skipped\n * if the DOM value has diverged from this binding's last write (in-progress user\n * input): unless the incoming value already matches the DOM (write would be a no-op).\n */\nexport const syncFormControl = (\n el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n value: unknown,\n isLive?: boolean,\n state: LiveWriteState = { last: undefined },\n): void => {\n const checkable = isCheckableInput(el);\n const next: boolean | string = checkable ? Boolean(value) : value == null ? '' : String(value);\n const current: boolean | string = checkable ? (el as HTMLInputElement).checked : el.value;\n\n if (isLive && state.last !== undefined && !Object.is(current, state.last) && !Object.is(current, next)) return;\n\n if (checkable) (el as HTMLInputElement).checked = next as boolean;\n else el.value = next as string;\n\n if (isLive) state.last = next;\n};\n\n// ─── Attributes ───────────────────────────────────────────────────────────────\n\nconst syncRegisteredProp = (el: HTMLElement, meta: PropMeta, binding: AttrBinding, value: unknown): void => {\n const parsed = isStructuredValue(value)\n ? value\n : meta.parse(\n binding.mode === 'bool' ? (value ? '' : null) : value == null || value === false ? null : String(value),\n );\n\n if (\n !Object.is(\n untrack(() => meta.signal.value),\n parsed,\n )\n ) {\n meta.signal.value = parsed;\n }\n\n if (!meta.reflect) {\n if (isStructuredValue(value)) return;\n\n if (binding.mode === 'bool') el.toggleAttribute(binding.name, Boolean(value));\n else setAttr(el, binding.name, value);\n }\n};\n\nexport const applyAttrBinding = (binding: AttrBinding, registerCleanup: RegisterCleanup): void => {\n const { el, mode, name } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const update = (value: unknown): void => {\n // Resolved per update, not at instantiation: a nested custom element only\n // registers its props when it is connected, which happens when this result\n // mounts: later than binding construction. By update time the registry entry\n // exists, so a structured value (array/object/callback) reaches a registered\n // prop through its signal instead of an attribute round-trip that could not\n // represent it. Unregistered targets get no such pass: structured data to\n // them is an explicit `.prop=` binding.\n const propMeta = getPropMeta(el, name);\n\n if (propMeta) {\n syncRegisteredProp(el, propMeta, binding, value);\n\n return;\n }\n\n if ((name === 'value' && isNativeFormInput(el)) || (name === 'checked' && el instanceof HTMLInputElement)) {\n syncFormControl(el as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement, value, binding.live, liveState);\n\n return;\n }\n\n // `indeterminate` is a property-only concept on checkboxes (no attribute exists):\n // bind it like any other element property so reactive indeterminate state works\n // through the same `indeterminate=\"${sig}\"` syntax as `checked`.\n if (name === 'indeterminate' && el instanceof HTMLInputElement) {\n el.indeterminate = Boolean(value);\n\n return;\n }\n\n if (mode === 'bool') el.toggleAttribute(name, Boolean(value));\n else setAttr(el, name, value);\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Events ───────────────────────────────────────────────────────────────────\n\nconst applyEventBinding = (binding: EventBinding, registerCleanup: RegisterCleanup): void => {\n const { modifiers } = binding;\n\n const handler =\n modifiers?.stop || modifiers?.prevent\n ? (e: Event): void => {\n if (modifiers.stop) e.stopPropagation();\n if (modifiers.prevent) e.preventDefault();\n binding.handler(e);\n }\n : binding.handler;\n\n const options =\n modifiers?.once || modifiers?.capture ? { capture: modifiers.capture, once: modifiers.once } : undefined;\n\n registerCleanup(listen(binding.el, binding.name, handler, options));\n};\n\n// ─── Explicit property bindings ───────────────────────────────────────────────\n\n/**\n * `.prop=${value}` binding: always writes the element property (never an\n * attribute). This is the explicit channel for structured values (arrays,\n * objects, callbacks): the old implicit \"value happens to be an object, so set\n * a property\" fallback in the attr path is gone, so intent lives in the markup.\n * Prototype-polluting names are rejected at compile time (see compiler.ts\n * PROP_RE), not silently dropped here.\n */\nconst applyPropBinding = (binding: PropBinding, registerCleanup: RegisterCleanup): void => {\n const update = (value: unknown): void => {\n (binding.el as unknown as Record<string, unknown>)[binding.name] = value;\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Two-way model bindings ──────────────────────────────────────────────────\n\n/**\n * `model()` binding: signal → control property (through the same live-aware\n * `syncFormControl` write path as `live()`, so an in-progress user edit is never\n * clobbered) and control event → signal (the write-back that makes it two-way).\n */\nconst applyModelBinding = (binding: ModelBinding, registerCleanup: RegisterCleanup): void => {\n const { el, get, signal } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const write = (value: unknown): void => {\n if (isNativeFormInput(el)) {\n syncFormControl(el, value, true, liveState);\n } else {\n // Non-native target: plain property write (custom controls expose `value`).\n (el as unknown as { value: unknown }).value = value;\n }\n };\n\n const sub = rawEffect(() => {\n write(signal.value);\n return undefined;\n });\n\n const onEvent: EventListener = () => {\n const next = get(el);\n\n if (\n !Object.is(\n untrack(() => signal.value),\n next,\n )\n ) {\n (signal as Signal<unknown>).value = next;\n }\n };\n\n registerCleanup(() => sub.dispose());\n registerCleanup(listen(el, binding.event, onEvent));\n};\n\n// ─── Refs ─────────────────────────────────────────────────────────────────────\n\nconst applyRefBinding = (binding: RefBinding, registerCleanup: RegisterCleanup): void => {\n const { el, ref } = binding;\n\n if (typeof ref === 'function') {\n ref(el as never);\n registerCleanup(() => ref(null as never));\n\n return;\n }\n\n ref.value = el as never;\n registerCleanup(() => {\n ref.value = null as never;\n });\n};\n\n// ─── HTML (reactive) ─────────────────────────────────────────────────────────\n\nconst insertHtmlValues = (\n values: HtmlBindingValue[],\n insertBefore: ChildNode,\n registerCleanup: RegisterCleanup,\n): Node[] => {\n const nodes: Node[] = [];\n const parent = insertBefore.parentNode;\n\n invariant(parent, 'html binding anchor has no parent node');\n\n for (const v of values) {\n if (isHtmlResult(v)) {\n const captured = Array.from(v.fragment.childNodes);\n\n parent.insertBefore(v.fragment, insertBefore);\n v.apply(registerCleanup);\n nodes.push(...captured);\n } else if (v != null && v !== false) {\n const text = document.createTextNode(String(v));\n\n parent.insertBefore(text, insertBefore);\n nodes.push(text);\n }\n }\n\n return nodes;\n};\n\nexport const applyHtmlBinding = (binding: HtmlBinding, registerCleanup: RegisterCleanup): void => {\n const { anchor, signal } = binding;\n\n const slot = createReplaceableSlot();\n\n const stop = rawEffect(() => {\n const raw = signal.value;\n\n slot.clear();\n\n if (raw == null || raw.length === 0) return;\n\n untrack(() => {\n slot.setNodes(insertHtmlValues(raw, anchor, slot.registerCleanup));\n });\n });\n\n registerCleanup(() => {\n stop.dispose();\n slot.clear();\n });\n};\n\n// ─── Directives ───────────────────────────────────────────────────────────────\n\nconst applyDirectiveBinding = (binding: DirectiveBinding, registerCleanup: RegisterCleanup): void => {\n binding.directive.mount(binding.anchor, registerCleanup);\n};\n\n// ─── Binding dispatch ─────────────────────────────────────────────────────────\n\nexport const applyBinding = (binding: Binding, registerCleanup: RegisterCleanup): void => {\n switch (binding.type) {\n case 'attr':\n applyAttrBinding(binding, registerCleanup);\n break;\n case 'directive':\n applyDirectiveBinding(binding, registerCleanup);\n break;\n case 'event':\n applyEventBinding(binding, registerCleanup);\n break;\n case 'html':\n applyHtmlBinding(binding, registerCleanup);\n break;\n case 'model':\n applyModelBinding(binding, registerCleanup);\n break;\n case 'prop':\n applyPropBinding(binding, registerCleanup);\n break;\n case 'ref':\n applyRefBinding(binding, registerCleanup);\n break;\n }\n};\n\n// ─── Attr binding factory (used by instantiator) ──────────────────────────────\n\nexport const createAttrBindingFromValue = (\n el: HTMLElement,\n mode: 'attr' | 'bool',\n name: string,\n value: unknown,\n): AttrBinding => {\n if (isLiveBinding(value)) {\n return { el, live: true, mode, name, signal: value.source, type: 'attr' };\n }\n\n if (typeof value === 'function') {\n return { el, mode, name, signal: computed(value as () => unknown), type: 'attr' };\n }\n\n if (isReactive(value)) {\n return { el, mode, name, signal: value as Readable<unknown>, type: 'attr' };\n }\n\n return { el, mode, name, type: 'attr', value };\n};\n\nexport const resolveStaticText = (value: unknown): string => {\n if (value == null) return '';\n\n return String(value);\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"bindings.js","names":["rawEffect","signalEffect","sub","effect","update","signal","registerCleanup","isNativeFormInput","el","isCheckableInput","syncFormControl","checkable","next","value","current","state","isLive","syncRegisteredProp","parsed","isStructuredValue","meta","binding","untrack","setAttr","applyAttrBinding","liveState","propMeta","getPropMeta","name","mode","applyEventBinding","handler","modifiers","e","options","listen","applyPropBinding","applyModelBinding","write","get","applyRefBinding","ref","insertHtmlValues","nodes","parent","insertBefore","invariant","v","values","isHtmlResult","captured","text","toHtmlValues","raw","applyHtmlBinding","slot","createReplaceableSlot","stop","source","anchor","applyDirectiveBinding","applyBinding","createAttrBindingFromValue","isLiveBinding","computed","isReactive","resolveStaticText"],"sources":["../../src/template/bindings.ts"],"sourcesContent":["/**\n * template/bindings.ts: Runtime binding appliers.\n *\n * Responsibilities:\n * - Apply each Binding variant to the live DOM (attr, event, html, model,\n * prop, ref, directive).\n * - Manage reactive effects and cleanup registration.\n * - Expose `applyBinding()` as the single dispatch entry point.\n * - Own the signal-to-form-control write path (`syncFormControl`) for regular\n * attribute bindings, and the two-way `model()` write-back loop.\n */\n\nimport { computed, isReactive, type Readable, effect as rawEffect, type Signal, untrack } from '@vielzeug/ripple';\n\nimport { isLiveBinding } from '../directives/live';\nimport { invariant } from '../errors';\nimport { getPropMeta, type PropMeta } from '../props';\nimport { createReplaceableSlot, isStructuredValue, listen, setAttr } from '../utils/dom';\nimport type {\n AttrBinding,\n Binding,\n DirectiveBinding,\n EventBinding,\n HtmlBinding,\n HtmlBindingValue,\n ModelBinding,\n PropBinding,\n RefBinding,\n} from './binding-types';\nimport { isHtmlResult } from './result';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type RegisterCleanup = (fn: () => void) => void;\n\n// ─── Signal helpers ───────────────────────────────────────────────────────────\n\nconst signalEffect = (\n signal: Readable<unknown>,\n update: (v: unknown) => void,\n registerCleanup: RegisterCleanup,\n): void => {\n const sub = rawEffect(() => {\n update(signal.value);\n return undefined;\n });\n\n registerCleanup(() => sub.dispose());\n};\n\n// ─── Form control value sync ──────────────────────────────────────────────────\n\nconst isNativeFormInput = (el: HTMLElement): el is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement =>\n el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement;\n\nconst isCheckableInput = (el: HTMLElement): el is HTMLInputElement =>\n el instanceof HTMLInputElement && (el.type === 'checkbox' || el.type === 'radio');\n\ntype LiveWriteState = { last: unknown };\n\n/**\n * The single write path from a reactive value to a form control's `value`/`checked`\n * property for regular attribute bindings.\n *\n * Live-write: when the binding was created from `live(source)`, a write is skipped\n * if the DOM value has diverged from this binding's last write (in-progress user\n * input): unless the incoming value already matches the DOM (write would be a no-op).\n */\nexport const syncFormControl = (\n el: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement,\n value: unknown,\n isLive?: boolean,\n state: LiveWriteState = { last: undefined },\n): void => {\n const checkable = isCheckableInput(el);\n const next: boolean | string = checkable ? Boolean(value) : value == null ? '' : String(value);\n const current: boolean | string = checkable ? (el as HTMLInputElement).checked : el.value;\n\n if (isLive && state.last !== undefined && !Object.is(current, state.last) && !Object.is(current, next)) return;\n\n if (checkable) (el as HTMLInputElement).checked = next as boolean;\n else el.value = next as string;\n\n if (isLive) state.last = next;\n};\n\n// ─── Attributes ───────────────────────────────────────────────────────────────\n\nconst syncRegisteredProp = (el: HTMLElement, meta: PropMeta, binding: AttrBinding, value: unknown): void => {\n const parsed = isStructuredValue(value)\n ? value\n : meta.parse(\n binding.mode === 'bool' ? (value ? '' : null) : value == null || value === false ? null : String(value),\n );\n\n if (\n !Object.is(\n untrack(() => meta.signal.value),\n parsed,\n )\n ) {\n meta.signal.value = parsed;\n }\n\n if (!meta.reflect) {\n if (isStructuredValue(value)) return;\n\n if (binding.mode === 'bool') el.toggleAttribute(binding.name, Boolean(value));\n else setAttr(el, binding.name, value);\n }\n};\n\nexport const applyAttrBinding = (binding: AttrBinding, registerCleanup: RegisterCleanup): void => {\n const { el, mode, name } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const update = (value: unknown): void => {\n // Resolved per update, not at instantiation: a nested custom element only\n // registers its props when it is connected, which happens when this result\n // mounts: later than binding construction. By update time the registry entry\n // exists, so a structured value (array/object/callback) reaches a registered\n // prop through its signal instead of an attribute round-trip that could not\n // represent it. Unregistered targets get no such pass: structured data to\n // them is an explicit `.prop=` binding.\n const propMeta = getPropMeta(el, name);\n\n if (propMeta) {\n syncRegisteredProp(el, propMeta, binding, value);\n\n return;\n }\n\n if ((name === 'value' && isNativeFormInput(el)) || (name === 'checked' && el instanceof HTMLInputElement)) {\n syncFormControl(el as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement, value, binding.live, liveState);\n\n return;\n }\n\n // `indeterminate` is a property-only concept on checkboxes (no attribute exists):\n // bind it like any other element property so reactive indeterminate state works\n // through the same `indeterminate=\"${sig}\"` syntax as `checked`.\n if (name === 'indeterminate' && el instanceof HTMLInputElement) {\n el.indeterminate = Boolean(value);\n\n return;\n }\n\n if (mode === 'bool') el.toggleAttribute(name, Boolean(value));\n else setAttr(el, name, value);\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Events ───────────────────────────────────────────────────────────────────\n\nconst applyEventBinding = (binding: EventBinding, registerCleanup: RegisterCleanup): void => {\n const { modifiers } = binding;\n\n const handler =\n modifiers?.stop || modifiers?.prevent\n ? (e: Event): void => {\n if (modifiers.stop) e.stopPropagation();\n if (modifiers.prevent) e.preventDefault();\n binding.handler(e);\n }\n : binding.handler;\n\n const options =\n modifiers?.once || modifiers?.capture ? { capture: modifiers.capture, once: modifiers.once } : undefined;\n\n registerCleanup(listen(binding.el, binding.name, handler, options));\n};\n\n// ─── Explicit property bindings ───────────────────────────────────────────────\n\n/**\n * `.prop=${value}` binding: always writes the element property (never an\n * attribute). This is the explicit channel for structured values (arrays,\n * objects, callbacks): the old implicit \"value happens to be an object, so set\n * a property\" fallback in the attr path is gone, so intent lives in the markup.\n * Prototype-polluting names are rejected at compile time (see compiler.ts\n * PROP_RE), not silently dropped here.\n */\nconst applyPropBinding = (binding: PropBinding, registerCleanup: RegisterCleanup): void => {\n const update = (value: unknown): void => {\n (binding.el as unknown as Record<string, unknown>)[binding.name] = value;\n };\n\n if ('signal' in binding) {\n signalEffect(binding.signal, update, registerCleanup);\n } else {\n update(binding.value);\n }\n};\n\n// ─── Two-way model bindings ──────────────────────────────────────────────────\n\n/**\n * `model()` binding: signal → control property (through the same live-aware\n * `syncFormControl` write path as `live()`, so an in-progress user edit is never\n * clobbered) and control event → signal (the write-back that makes it two-way).\n */\nconst applyModelBinding = (binding: ModelBinding, registerCleanup: RegisterCleanup): void => {\n const { el, get, signal } = binding;\n const liveState: LiveWriteState = { last: undefined };\n\n const write = (value: unknown): void => {\n if (isNativeFormInput(el)) {\n syncFormControl(el, value, true, liveState);\n } else {\n // Non-native target: plain property write (custom controls expose `value`).\n (el as unknown as { value: unknown }).value = value;\n }\n };\n\n const sub = rawEffect(() => {\n write(signal.value);\n return undefined;\n });\n\n const onEvent: EventListener = () => {\n const next = get(el);\n\n if (\n !Object.is(\n untrack(() => signal.value),\n next,\n )\n ) {\n (signal as Signal<unknown>).value = next;\n }\n };\n\n registerCleanup(() => sub.dispose());\n registerCleanup(listen(el, binding.event, onEvent));\n};\n\n// ─── Refs ─────────────────────────────────────────────────────────────────────\n\nconst applyRefBinding = (binding: RefBinding, registerCleanup: RegisterCleanup): void => {\n const { el, ref } = binding;\n\n if (typeof ref === 'function') {\n ref(el as never);\n registerCleanup(() => ref(null as never));\n\n return;\n }\n\n ref.value = el as never;\n registerCleanup(() => {\n ref.value = null as never;\n });\n};\n\n// ─── HTML (reactive) ─────────────────────────────────────────────────────────\n\nconst insertHtmlValues = (\n values: HtmlBindingValue[],\n insertBefore: ChildNode,\n registerCleanup: RegisterCleanup,\n): Node[] => {\n const nodes: Node[] = [];\n const parent = insertBefore.parentNode;\n\n invariant(parent, 'html binding anchor has no parent node');\n\n for (const v of values) {\n if (isHtmlResult(v)) {\n const captured = Array.from(v.fragment.childNodes);\n\n parent.insertBefore(v.fragment, insertBefore);\n v.apply(registerCleanup);\n nodes.push(...captured);\n } else if (v != null && v !== false) {\n const text = document.createTextNode(String(v));\n\n parent.insertBefore(text, insertBefore);\n nodes.push(text);\n }\n }\n\n return nodes;\n};\n\n/** Normalize a reactive node-slot value to the render effect's array shape. */\nconst toHtmlValues = (raw: unknown): HtmlBindingValue[] =>\n Array.isArray(raw) ? (raw as HtmlBindingValue[]) : [raw as HtmlBindingValue];\n\nexport const applyHtmlBinding = (binding: HtmlBinding, registerCleanup: RegisterCleanup): void => {\n const { anchor, source } = binding;\n\n const slot = createReplaceableSlot();\n\n // The source is read here, not through a memo computed, so this effect is the\n // observer of its dependencies and the owner of every computed the value builds:\n // nested bindings are disposed exactly when this slot's nodes are replaced.\n const stop = rawEffect(() => {\n const raw = toHtmlValues(typeof source === 'function' ? source() : source.value);\n\n slot.clear();\n\n if (raw == null || raw.length === 0) return;\n\n untrack(() => {\n slot.setNodes(insertHtmlValues(raw, anchor, slot.registerCleanup));\n });\n });\n\n registerCleanup(() => {\n stop.dispose();\n slot.clear();\n });\n};\n\n// ─── Directives ───────────────────────────────────────────────────────────────\n\nconst applyDirectiveBinding = (binding: DirectiveBinding, registerCleanup: RegisterCleanup): void => {\n binding.directive.mount(binding.anchor, registerCleanup);\n};\n\n// ─── Binding dispatch ─────────────────────────────────────────────────────────\n\nexport const applyBinding = (binding: Binding, registerCleanup: RegisterCleanup): void => {\n switch (binding.type) {\n case 'attr':\n applyAttrBinding(binding, registerCleanup);\n break;\n case 'directive':\n applyDirectiveBinding(binding, registerCleanup);\n break;\n case 'event':\n applyEventBinding(binding, registerCleanup);\n break;\n case 'html':\n applyHtmlBinding(binding, registerCleanup);\n break;\n case 'model':\n applyModelBinding(binding, registerCleanup);\n break;\n case 'prop':\n applyPropBinding(binding, registerCleanup);\n break;\n case 'ref':\n applyRefBinding(binding, registerCleanup);\n break;\n }\n};\n\n// ─── Attr binding factory (used by instantiator) ──────────────────────────────\n\nexport const createAttrBindingFromValue = (\n el: HTMLElement,\n mode: 'attr' | 'bool',\n name: string,\n value: unknown,\n): AttrBinding => {\n if (isLiveBinding(value)) {\n return { el, live: true, mode, name, signal: value.source, type: 'attr' };\n }\n\n if (typeof value === 'function') {\n return { el, mode, name, signal: computed(value as () => unknown), type: 'attr' };\n }\n\n if (isReactive(value)) {\n return { el, mode, name, signal: value as Readable<unknown>, type: 'attr' };\n }\n\n return { el, mode, name, type: 'attr', value };\n};\n\nexport const resolveStaticText = (value: unknown): string => {\n if (value == null) return '';\n\n return String(value);\n};\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../directives/model.cjs"),t=require("../errors.cjs"),n=require("./bindings.cjs"),r=require("./compiler.cjs"),i=require("./result.cjs");let a=require("@vielzeug/ripple");var o="html`...`: node-slot comment anchor has no parent node",s=
|
|
1
|
+
const e=require("../directives/model.cjs"),t=require("../errors.cjs"),n=require("./bindings.cjs"),r=require("./compiler.cjs"),i=require("./result.cjs");let a=require("@vielzeug/ripple");var o="html`...`: node-slot comment anchor has no parent node",s=(e,n,r)=>{let i=n.parentNode;for(t.invariant(i,o);e.fragment.firstChild;)i.insertBefore(e.fragment.firstChild,n);r.push(e.apply.bind(e))},c=(c,l)=>{let u=r.getStaticTemplate(c),d=u.element.content.cloneNode(!0),f=[],p=[],m=u.slots.map((e,n)=>{let i=l[n];if(e.kind===r.SlotKind.NODE){let n=e.commentId===void 0?void 0:u.commentPaths.get(e.commentId);return t.invariant(n,`compiled template is missing a comment path for node slot ${e.commentId}`),{comment:r.followPath(d,n),slot:e,value:i}}let a=e.elementId===void 0?void 0:u.elementPaths.get(e.elementId);return t.invariant(a,`compiled template is missing an element path for slot ${e.elementId}`),{el:r.followPath(d,a),slot:e,value:i}});for(let{comment:c,el:l,slot:u,value:d}of m)if(u.kind===r.SlotKind.NODE){let e=c;if(t.invariant(e,`compiled template produced a node slot without a comment anchor`),i.isDirectiveResult(d)){f.push({anchor:e,directive:d,type:`directive`});continue}if(i.isHtmlResult(d)){s(d,e,p),e.remove();continue}if(typeof d==`function`||(0,a.isReactive)(d)){f.push({anchor:e,source:d,type:`html`});continue}if(Array.isArray(d)){for(let r of d)if(i.isHtmlResult(r))s(r,e,p);else{let i=e.parentNode;t.invariant(i,o),i.insertBefore(document.createTextNode(n.resolveStaticText(r)),e)}e.remove();continue}e.replaceWith(document.createTextNode(n.resolveStaticText(d)))}else if(t.invariant(l,`compiled template produced an element slot without an element`),u.kind===r.SlotKind.EVENT){let e=u.name;if(t.invariant(e,`compiled template produced an event slot without an event name`),typeof d==`function`)f.push({el:l,handler:d,modifiers:u.eventModifiers,name:e,type:`event`});else if((0,a.isReactive)(d)){let t=d;f.push({el:l,handler:e=>{let n=t.value;typeof n==`function`&&n(e)},modifiers:u.eventModifiers,name:e,type:`event`})}}else if(u.kind===r.SlotKind.PROP){let e=u.name;t.invariant(e,`compiled template produced a prop slot without a property name`),typeof d==`function`?f.push({el:l,name:e,signal:(0,a.computed)(d),type:`prop`}):(0,a.isReactive)(d)?f.push({el:l,name:e,signal:d,type:`prop`}):f.push({el:l,name:e,type:`prop`,value:d})}else if(u.kind===r.SlotKind.REF)d&&f.push({el:l,ref:d,type:`ref`});else if(t.invariant(u.name,`compiled template produced an attr slot without an attribute name`),e.isModelSource(d)){if(u.mode!==`attr`)throw new t.OreApiError(t.ORE_ERRORS.modelRequiresValueBinding(u.name));f.push({el:l,event:d.event??e.modelDefaultEvent(l),get:d.get??e.modelDefaultGet,signal:d.source,type:`model`})}else f.push(n.createAttrBindingFromValue(l,u.mode??`attr`,u.name,d));return i.createHtmlResult(d,e=>{for(let t of f)n.applyBinding(t,e);for(let t of p)t(e)})},l=(e,...t)=>c(e,t);exports.compileTemplate=c,exports.html=l;
|
|
2
2
|
//# sourceMappingURL=instantiator.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instantiator.cjs","names":["getStaticTemplate","SlotKind","followPath","isDirectiveResult","isHtmlResult","isReactive","computed","resolveStaticText","isModelSource","OreApiError","ORE_ERRORS","modelDefaultEvent","modelDefaultGet","createAttrBindingFromValue","createHtmlResult","require_model","require_errors","require_bindings","require_compiler","require_result","_vielzeug_ripple","NODE_SLOT_NO_PARENT_MSG","toHtmlValues","raw","embedStaticResult","parent","anchor","result","chainedApplies","compileTemplate","compiled","strings","fragment","bindings","boundSlots","value","values","i","slot","commentPath","elementPath","comment","sig","item","el","name","signalValue","h","binding","registerCleanup","chainedApply","html"],"sources":["../../src/template/instantiator.ts"],"sourcesContent":["/**\n * template/instantiator.ts: Template instantiation and the `html` tagged literal.\n *\n * Responsibilities:\n * - Clone a compiled static template and wire up live bindings.\n * - Expose `compileTemplate()` and `html` as the public authoring API.\n */\n\nimport { computed, isReactive, type Readable, type Signal } from '@vielzeug/ripple';\n\nimport { isModelSource, modelDefaultEvent, modelDefaultGet } from '../directives/model';\nimport { invariant, ORE_ERRORS, OreApiError } from '../errors';\nimport type { Binding, HtmlBindingValue } from './binding-types';\nimport { applyBinding, createAttrBindingFromValue, resolveStaticText } from './bindings';\nimport { followPath, getStaticTemplate, SlotKind } from './compiler';\nimport {\n type CompiledHTMLResult,\n createHtmlResult,\n type HTMLResult,\n isDirectiveResult,\n isHtmlResult,\n type Ref,\n type RefCallback,\n} from './result';\n\n// ─── Template instantiation ──────────────────────────────────────────────────\n\nconst NODE_SLOT_NO_PARENT_MSG = 'html`...`: node-slot comment anchor has no parent node';\n\n/** Normalize a reactive node-slot value to the HtmlBinding signal's array shape. */\nconst toHtmlValues = (raw: unknown): HtmlBindingValue[] =>\n Array.isArray(raw) ? (raw as HtmlBindingValue[]) : [raw as HtmlBindingValue];\n\n/**\n * Static-embed an already-created HTMLResult at a node-slot anchor: move its\n * fragment children into place and chain its apply into the outer apply phase\n * (so embedded reactive wiring starts when the host template mounts, not now).\n */\nconst embedStaticResult = (\n result: CompiledHTMLResult,\n anchor: Comment,\n chainedApplies: Array<(rc: (fn: () => void) => void) => void>,\n): void => {\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n\n while (result.fragment.firstChild) parent.insertBefore(result.fragment.firstChild, anchor);\n\n chainedApplies.push(result.apply.bind(result));\n};\n\n/**\n * Instantiate a compiled template: clone the cached DOM template, navigate\n * to each binding target using pre-recorded paths, and build bindings with\n * direct node references. Returns an HTMLResult ready to mount.\n */\nexport const compileTemplate = (strings: TemplateStringsArray, values: unknown[]): HTMLResult => {\n const compiled = getStaticTemplate(strings);\n const fragment = compiled.element.content.cloneNode(true) as DocumentFragment;\n const bindings: Binding[] = [];\n // For static HTMLResult embeds: chain their apply calls\n const chainedApplies: Array<(rc: (fn: () => void) => void) => void> = [];\n\n // Phase 1: Resolve all binding targets BEFORE any DOM modifications\n type BoundSlot = { comment?: Comment; el?: HTMLElement; slot: (typeof compiled.slots)[number]; value: unknown };\n\n const boundSlots: BoundSlot[] = compiled.slots.map((slot, i) => {\n const value = values[i];\n\n if (slot.kind === SlotKind.NODE) {\n const commentPath = slot.commentId !== undefined ? compiled.commentPaths.get(slot.commentId) : undefined;\n\n invariant(commentPath, `compiled template is missing a comment path for node slot ${slot.commentId}`);\n\n return { comment: followPath(fragment, commentPath) as Comment, slot, value };\n }\n\n const elementPath = slot.elementId !== undefined ? compiled.elementPaths.get(slot.elementId) : undefined;\n\n invariant(elementPath, `compiled template is missing an element path for slot ${slot.elementId}`);\n\n return { el: followPath(fragment, elementPath) as HTMLElement, slot, value };\n });\n\n // Phase 2: Build bindings (may modify DOM for static content).\n for (const { comment, el, slot, value } of boundSlots) {\n if (slot.kind === SlotKind.NODE) {\n const anchor = comment;\n\n invariant(anchor, 'compiled template produced a node slot without a comment anchor');\n\n if (isDirectiveResult(value)) {\n bindings.push({ anchor, directive: value, type: 'directive' });\n continue;\n }\n\n if (isHtmlResult(value)) {\n // Static embed: move fragment children into place, chain apply\n embedStaticResult(value, anchor, chainedApplies);\n anchor.remove();\n continue;\n }\n\n if (typeof value === 'function' || isReactive(value)) {\n // Always use the html binding for reactive values: it handles both text\n // values and HTMLResult values, preventing silent \"[object Object]\"\n // corruption when a signal's runtime type changes from null/string to HTMLResult.\n const sig =\n typeof value === 'function'\n ? computed(() => toHtmlValues((value as () => unknown)()))\n : computed(() => toHtmlValues((value as Readable<unknown>).value));\n\n bindings.push({ anchor, signal: sig, type: 'html' });\n continue;\n }\n\n if (Array.isArray(value)) {\n for (const item of value) {\n if (isHtmlResult(item)) {\n embedStaticResult(item, anchor, chainedApplies);\n } else {\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n parent.insertBefore(document.createTextNode(resolveStaticText(item)), anchor);\n }\n }\n anchor.remove();\n continue;\n }\n\n // Static primitive: replace with text node, no binding\n anchor.replaceWith(document.createTextNode(resolveStaticText(value)));\n continue;\n }\n\n // Element slot\n invariant(el, 'compiled template produced an element slot without an element');\n\n if (slot.kind === SlotKind.EVENT) {\n const name = slot.name;\n\n invariant(name, 'compiled template produced an event slot without an event name');\n\n if (typeof value === 'function') {\n bindings.push({\n el,\n handler: value as (e: Event) => void,\n modifiers: slot.eventModifiers,\n name,\n type: 'event',\n });\n } else if (isReactive(value)) {\n const signalValue = value as Readable<unknown>;\n const handler = (e: Event) => {\n const h = signalValue.value;\n\n if (typeof h === 'function') (h as (e: Event) => void)(e);\n };\n\n bindings.push({ el, handler, modifiers: slot.eventModifiers, name, type: 'event' });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.PROP) {\n const name = slot.name;\n\n invariant(name, 'compiled template produced a prop slot without a property name');\n\n if (typeof value === 'function') {\n bindings.push({ el, name, signal: computed(value as () => unknown), type: 'prop' });\n } else if (isReactive(value)) {\n bindings.push({ el, name, signal: value as Readable<unknown>, type: 'prop' });\n } else {\n bindings.push({ el, name, type: 'prop', value });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.REF) {\n if (value) {\n bindings.push({ el, ref: value as Ref<Element> | RefCallback<Element>, type: 'ref' });\n }\n\n continue;\n }\n\n // attr / boolAttr\n invariant(slot.name, 'compiled template produced an attr slot without an attribute name');\n\n if (isModelSource(value)) {\n if (slot.mode !== 'attr') throw new OreApiError(ORE_ERRORS.modelRequiresValueBinding(slot.name));\n\n bindings.push({\n el,\n event: value.event ?? modelDefaultEvent(el),\n get: (value.get ?? modelDefaultGet) as (el: HTMLElement) => unknown,\n signal: value.source as Signal<unknown>,\n type: 'model',\n });\n continue;\n }\n\n bindings.push(createAttrBindingFromValue(el, slot.mode ?? 'attr', slot.name, value));\n }\n\n return createHtmlResult(fragment, (registerCleanup) => {\n for (const binding of bindings) applyBinding(binding, registerCleanup);\n for (const chainedApply of chainedApplies) chainedApply(registerCleanup);\n });\n};\n\nexport const html = (strings: TemplateStringsArray, ...values: unknown[]): HTMLResult =>\n compileTemplate(strings, values);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"instantiator.cjs","names":["getStaticTemplate","SlotKind","followPath","isDirectiveResult","isHtmlResult","isReactive","resolveStaticText","computed","isModelSource","OreApiError","ORE_ERRORS","modelDefaultEvent","modelDefaultGet","createAttrBindingFromValue","createHtmlResult","require_model","require_errors","require_bindings","require_compiler","require_result","_vielzeug_ripple","NODE_SLOT_NO_PARENT_MSG","embedStaticResult","parent","anchor","result","chainedApplies","compileTemplate","compiled","strings","fragment","bindings","boundSlots","value","values","i","slot","commentPath","elementPath","comment","item","el","name","signalValue","h","binding","registerCleanup","chainedApply","html"],"sources":["../../src/template/instantiator.ts"],"sourcesContent":["/**\n * template/instantiator.ts: Template instantiation and the `html` tagged literal.\n *\n * Responsibilities:\n * - Clone a compiled static template and wire up live bindings.\n * - Expose `compileTemplate()` and `html` as the public authoring API.\n */\n\nimport { computed, isReactive, type Readable, type Signal } from '@vielzeug/ripple';\n\nimport { isModelSource, modelDefaultEvent, modelDefaultGet } from '../directives/model';\nimport { invariant, ORE_ERRORS, OreApiError } from '../errors';\nimport type { Binding } from './binding-types';\nimport { applyBinding, createAttrBindingFromValue, resolveStaticText } from './bindings';\nimport { followPath, getStaticTemplate, SlotKind } from './compiler';\nimport {\n type CompiledHTMLResult,\n createHtmlResult,\n type HTMLResult,\n isDirectiveResult,\n isHtmlResult,\n type Ref,\n type RefCallback,\n} from './result';\n\n// ─── Template instantiation ──────────────────────────────────────────────────\n\nconst NODE_SLOT_NO_PARENT_MSG = 'html`...`: node-slot comment anchor has no parent node';\n\n/**\n * Static-embed an already-created HTMLResult at a node-slot anchor: move its\n * fragment children into place and chain its apply into the outer apply phase\n * (so embedded reactive wiring starts when the host template mounts, not now).\n */\nconst embedStaticResult = (\n result: CompiledHTMLResult,\n anchor: Comment,\n chainedApplies: Array<(rc: (fn: () => void) => void) => void>,\n): void => {\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n\n while (result.fragment.firstChild) parent.insertBefore(result.fragment.firstChild, anchor);\n\n chainedApplies.push(result.apply.bind(result));\n};\n\n/**\n * Instantiate a compiled template: clone the cached DOM template, navigate\n * to each binding target using pre-recorded paths, and build bindings with\n * direct node references. Returns an HTMLResult ready to mount.\n */\nexport const compileTemplate = (strings: TemplateStringsArray, values: unknown[]): HTMLResult => {\n const compiled = getStaticTemplate(strings);\n const fragment = compiled.element.content.cloneNode(true) as DocumentFragment;\n const bindings: Binding[] = [];\n // For static HTMLResult embeds: chain their apply calls\n const chainedApplies: Array<(rc: (fn: () => void) => void) => void> = [];\n\n // Phase 1: Resolve all binding targets BEFORE any DOM modifications\n type BoundSlot = { comment?: Comment; el?: HTMLElement; slot: (typeof compiled.slots)[number]; value: unknown };\n\n const boundSlots: BoundSlot[] = compiled.slots.map((slot, i) => {\n const value = values[i];\n\n if (slot.kind === SlotKind.NODE) {\n const commentPath = slot.commentId !== undefined ? compiled.commentPaths.get(slot.commentId) : undefined;\n\n invariant(commentPath, `compiled template is missing a comment path for node slot ${slot.commentId}`);\n\n return { comment: followPath(fragment, commentPath) as Comment, slot, value };\n }\n\n const elementPath = slot.elementId !== undefined ? compiled.elementPaths.get(slot.elementId) : undefined;\n\n invariant(elementPath, `compiled template is missing an element path for slot ${slot.elementId}`);\n\n return { el: followPath(fragment, elementPath) as HTMLElement, slot, value };\n });\n\n // Phase 2: Build bindings (may modify DOM for static content).\n for (const { comment, el, slot, value } of boundSlots) {\n if (slot.kind === SlotKind.NODE) {\n const anchor = comment;\n\n invariant(anchor, 'compiled template produced a node slot without a comment anchor');\n\n if (isDirectiveResult(value)) {\n bindings.push({ anchor, directive: value, type: 'directive' });\n continue;\n }\n\n if (isHtmlResult(value)) {\n // Static embed: move fragment children into place, chain apply\n embedStaticResult(value, anchor, chainedApplies);\n anchor.remove();\n continue;\n }\n\n if (typeof value === 'function' || isReactive(value)) {\n // Always use the html binding for reactive values: it handles both text\n // values and HTMLResult values, preventing silent \"[object Object]\"\n // corruption when a signal's runtime type changes from null/string to HTMLResult.\n // The source is handed over raw, without a memo computed, so the render effect\n // itself is the observer and the owner of anything the value builds (see HtmlBinding).\n bindings.push({ anchor, source: value as (() => unknown) | Readable<unknown>, type: 'html' });\n continue;\n }\n\n if (Array.isArray(value)) {\n for (const item of value) {\n if (isHtmlResult(item)) {\n embedStaticResult(item, anchor, chainedApplies);\n } else {\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n parent.insertBefore(document.createTextNode(resolveStaticText(item)), anchor);\n }\n }\n anchor.remove();\n continue;\n }\n\n // Static primitive: replace with text node, no binding\n anchor.replaceWith(document.createTextNode(resolveStaticText(value)));\n continue;\n }\n\n // Element slot\n invariant(el, 'compiled template produced an element slot without an element');\n\n if (slot.kind === SlotKind.EVENT) {\n const name = slot.name;\n\n invariant(name, 'compiled template produced an event slot without an event name');\n\n if (typeof value === 'function') {\n bindings.push({\n el,\n handler: value as (e: Event) => void,\n modifiers: slot.eventModifiers,\n name,\n type: 'event',\n });\n } else if (isReactive(value)) {\n const signalValue = value as Readable<unknown>;\n const handler = (e: Event) => {\n const h = signalValue.value;\n\n if (typeof h === 'function') (h as (e: Event) => void)(e);\n };\n\n bindings.push({ el, handler, modifiers: slot.eventModifiers, name, type: 'event' });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.PROP) {\n const name = slot.name;\n\n invariant(name, 'compiled template produced a prop slot without a property name');\n\n if (typeof value === 'function') {\n bindings.push({ el, name, signal: computed(value as () => unknown), type: 'prop' });\n } else if (isReactive(value)) {\n bindings.push({ el, name, signal: value as Readable<unknown>, type: 'prop' });\n } else {\n bindings.push({ el, name, type: 'prop', value });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.REF) {\n if (value) {\n bindings.push({ el, ref: value as Ref<Element> | RefCallback<Element>, type: 'ref' });\n }\n\n continue;\n }\n\n // attr / boolAttr\n invariant(slot.name, 'compiled template produced an attr slot without an attribute name');\n\n if (isModelSource(value)) {\n if (slot.mode !== 'attr') throw new OreApiError(ORE_ERRORS.modelRequiresValueBinding(slot.name));\n\n bindings.push({\n el,\n event: value.event ?? modelDefaultEvent(el),\n get: (value.get ?? modelDefaultGet) as (el: HTMLElement) => unknown,\n signal: value.source as Signal<unknown>,\n type: 'model',\n });\n continue;\n }\n\n bindings.push(createAttrBindingFromValue(el, slot.mode ?? 'attr', slot.name, value));\n }\n\n return createHtmlResult(fragment, (registerCleanup) => {\n for (const binding of bindings) applyBinding(binding, registerCleanup);\n for (const chainedApply of chainedApplies) chainedApply(registerCleanup);\n });\n};\n\nexport const html = (strings: TemplateStringsArray, ...values: unknown[]): HTMLResult =>\n compileTemplate(strings, values);\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instantiator.d.ts","sourceRoot":"","sources":["../../src/template/instantiator.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,OAAO,EAGL,KAAK,UAAU,EAKhB,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"instantiator.d.ts","sourceRoot":"","sources":["../../src/template/instantiator.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,OAAO,EAGL,KAAK,UAAU,EAKhB,MAAM,UAAU,CAAC;AAyBlB;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,SAAS,oBAAoB,EAAE,QAAQ,OAAO,EAAE,KAAG,UA0JlF,CAAC;AAEF,eAAO,MAAM,IAAI,GAAI,SAAS,oBAAoB,EAAE,GAAG,QAAQ,OAAO,EAAE,KAAG,UACzC,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{isModelSource as e,modelDefaultEvent as t,modelDefaultGet as n}from"../directives/model.js";import{ORE_ERRORS as r,OreApiError as i,invariant as a}from"../errors.js";import{applyBinding as o,createAttrBindingFromValue as s,resolveStaticText as c}from"./bindings.js";import{SlotKind as l,followPath as u,getStaticTemplate as d}from"./compiler.js";import{createHtmlResult as f,isDirectiveResult as p,isHtmlResult as m}from"./result.js";import{computed as h,isReactive as g}from"@vielzeug/ripple";var _="html`...`: node-slot comment anchor has no parent node",v=
|
|
1
|
+
import{isModelSource as e,modelDefaultEvent as t,modelDefaultGet as n}from"../directives/model.js";import{ORE_ERRORS as r,OreApiError as i,invariant as a}from"../errors.js";import{applyBinding as o,createAttrBindingFromValue as s,resolveStaticText as c}from"./bindings.js";import{SlotKind as l,followPath as u,getStaticTemplate as d}from"./compiler.js";import{createHtmlResult as f,isDirectiveResult as p,isHtmlResult as m}from"./result.js";import{computed as h,isReactive as g}from"@vielzeug/ripple";var _="html`...`: node-slot comment anchor has no parent node",v=(e,t,n)=>{let r=t.parentNode;for(a(r,_);e.fragment.firstChild;)r.insertBefore(e.fragment.firstChild,t);n.push(e.apply.bind(e))},y=(y,b)=>{let x=d(y),S=x.element.content.cloneNode(!0),C=[],w=[],T=x.slots.map((e,t)=>{let n=b[t];if(e.kind===l.NODE){let t=e.commentId===void 0?void 0:x.commentPaths.get(e.commentId);return a(t,`compiled template is missing a comment path for node slot ${e.commentId}`),{comment:u(S,t),slot:e,value:n}}let r=e.elementId===void 0?void 0:x.elementPaths.get(e.elementId);return a(r,`compiled template is missing an element path for slot ${e.elementId}`),{el:u(S,r),slot:e,value:n}});for(let{comment:o,el:u,slot:d,value:f}of T)if(d.kind===l.NODE){let e=o;if(a(e,`compiled template produced a node slot without a comment anchor`),p(f)){C.push({anchor:e,directive:f,type:`directive`});continue}if(m(f)){v(f,e,w),e.remove();continue}if(typeof f==`function`||g(f)){C.push({anchor:e,source:f,type:`html`});continue}if(Array.isArray(f)){for(let t of f)if(m(t))v(t,e,w);else{let n=e.parentNode;a(n,_),n.insertBefore(document.createTextNode(c(t)),e)}e.remove();continue}e.replaceWith(document.createTextNode(c(f)))}else if(a(u,`compiled template produced an element slot without an element`),d.kind===l.EVENT){let e=d.name;if(a(e,`compiled template produced an event slot without an event name`),typeof f==`function`)C.push({el:u,handler:f,modifiers:d.eventModifiers,name:e,type:`event`});else if(g(f)){let t=f;C.push({el:u,handler:e=>{let n=t.value;typeof n==`function`&&n(e)},modifiers:d.eventModifiers,name:e,type:`event`})}}else if(d.kind===l.PROP){let e=d.name;a(e,`compiled template produced a prop slot without a property name`),typeof f==`function`?C.push({el:u,name:e,signal:h(f),type:`prop`}):g(f)?C.push({el:u,name:e,signal:f,type:`prop`}):C.push({el:u,name:e,type:`prop`,value:f})}else if(d.kind===l.REF)f&&C.push({el:u,ref:f,type:`ref`});else if(a(d.name,`compiled template produced an attr slot without an attribute name`),e(f)){if(d.mode!==`attr`)throw new i(r.modelRequiresValueBinding(d.name));C.push({el:u,event:f.event??t(u),get:f.get??n,signal:f.source,type:`model`})}else C.push(s(u,d.mode??`attr`,d.name,f));return f(S,e=>{for(let t of C)o(t,e);for(let t of w)t(e)})},b=(e,...t)=>y(e,t);export{y as compileTemplate,b as html};
|
|
2
2
|
//# sourceMappingURL=instantiator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"instantiator.js","names":["NODE_SLOT_NO_PARENT_MSG","toHtmlValues","raw","embedStaticResult","parent","anchor","invariant","result","chainedApplies","compileTemplate","compiled","getStaticTemplate","strings","fragment","bindings","boundSlots","value","values","i","slot","SlotKind","commentPath","followPath","elementPath","comment","isDirectiveResult","isHtmlResult","isReactive","sig","computed","item","resolveStaticText","el","name","signalValue","h","isModelSource","OreApiError","ORE_ERRORS","modelDefaultEvent","modelDefaultGet","createAttrBindingFromValue","createHtmlResult","binding","applyBinding","registerCleanup","chainedApply","html"],"sources":["../../src/template/instantiator.ts"],"sourcesContent":["/**\n * template/instantiator.ts: Template instantiation and the `html` tagged literal.\n *\n * Responsibilities:\n * - Clone a compiled static template and wire up live bindings.\n * - Expose `compileTemplate()` and `html` as the public authoring API.\n */\n\nimport { computed, isReactive, type Readable, type Signal } from '@vielzeug/ripple';\n\nimport { isModelSource, modelDefaultEvent, modelDefaultGet } from '../directives/model';\nimport { invariant, ORE_ERRORS, OreApiError } from '../errors';\nimport type { Binding, HtmlBindingValue } from './binding-types';\nimport { applyBinding, createAttrBindingFromValue, resolveStaticText } from './bindings';\nimport { followPath, getStaticTemplate, SlotKind } from './compiler';\nimport {\n type CompiledHTMLResult,\n createHtmlResult,\n type HTMLResult,\n isDirectiveResult,\n isHtmlResult,\n type Ref,\n type RefCallback,\n} from './result';\n\n// ─── Template instantiation ──────────────────────────────────────────────────\n\nconst NODE_SLOT_NO_PARENT_MSG = 'html`...`: node-slot comment anchor has no parent node';\n\n/** Normalize a reactive node-slot value to the HtmlBinding signal's array shape. */\nconst toHtmlValues = (raw: unknown): HtmlBindingValue[] =>\n Array.isArray(raw) ? (raw as HtmlBindingValue[]) : [raw as HtmlBindingValue];\n\n/**\n * Static-embed an already-created HTMLResult at a node-slot anchor: move its\n * fragment children into place and chain its apply into the outer apply phase\n * (so embedded reactive wiring starts when the host template mounts, not now).\n */\nconst embedStaticResult = (\n result: CompiledHTMLResult,\n anchor: Comment,\n chainedApplies: Array<(rc: (fn: () => void) => void) => void>,\n): void => {\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n\n while (result.fragment.firstChild) parent.insertBefore(result.fragment.firstChild, anchor);\n\n chainedApplies.push(result.apply.bind(result));\n};\n\n/**\n * Instantiate a compiled template: clone the cached DOM template, navigate\n * to each binding target using pre-recorded paths, and build bindings with\n * direct node references. Returns an HTMLResult ready to mount.\n */\nexport const compileTemplate = (strings: TemplateStringsArray, values: unknown[]): HTMLResult => {\n const compiled = getStaticTemplate(strings);\n const fragment = compiled.element.content.cloneNode(true) as DocumentFragment;\n const bindings: Binding[] = [];\n // For static HTMLResult embeds: chain their apply calls\n const chainedApplies: Array<(rc: (fn: () => void) => void) => void> = [];\n\n // Phase 1: Resolve all binding targets BEFORE any DOM modifications\n type BoundSlot = { comment?: Comment; el?: HTMLElement; slot: (typeof compiled.slots)[number]; value: unknown };\n\n const boundSlots: BoundSlot[] = compiled.slots.map((slot, i) => {\n const value = values[i];\n\n if (slot.kind === SlotKind.NODE) {\n const commentPath = slot.commentId !== undefined ? compiled.commentPaths.get(slot.commentId) : undefined;\n\n invariant(commentPath, `compiled template is missing a comment path for node slot ${slot.commentId}`);\n\n return { comment: followPath(fragment, commentPath) as Comment, slot, value };\n }\n\n const elementPath = slot.elementId !== undefined ? compiled.elementPaths.get(slot.elementId) : undefined;\n\n invariant(elementPath, `compiled template is missing an element path for slot ${slot.elementId}`);\n\n return { el: followPath(fragment, elementPath) as HTMLElement, slot, value };\n });\n\n // Phase 2: Build bindings (may modify DOM for static content).\n for (const { comment, el, slot, value } of boundSlots) {\n if (slot.kind === SlotKind.NODE) {\n const anchor = comment;\n\n invariant(anchor, 'compiled template produced a node slot without a comment anchor');\n\n if (isDirectiveResult(value)) {\n bindings.push({ anchor, directive: value, type: 'directive' });\n continue;\n }\n\n if (isHtmlResult(value)) {\n // Static embed: move fragment children into place, chain apply\n embedStaticResult(value, anchor, chainedApplies);\n anchor.remove();\n continue;\n }\n\n if (typeof value === 'function' || isReactive(value)) {\n // Always use the html binding for reactive values: it handles both text\n // values and HTMLResult values, preventing silent \"[object Object]\"\n // corruption when a signal's runtime type changes from null/string to HTMLResult.\n const sig =\n typeof value === 'function'\n ? computed(() => toHtmlValues((value as () => unknown)()))\n : computed(() => toHtmlValues((value as Readable<unknown>).value));\n\n bindings.push({ anchor, signal: sig, type: 'html' });\n continue;\n }\n\n if (Array.isArray(value)) {\n for (const item of value) {\n if (isHtmlResult(item)) {\n embedStaticResult(item, anchor, chainedApplies);\n } else {\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n parent.insertBefore(document.createTextNode(resolveStaticText(item)), anchor);\n }\n }\n anchor.remove();\n continue;\n }\n\n // Static primitive: replace with text node, no binding\n anchor.replaceWith(document.createTextNode(resolveStaticText(value)));\n continue;\n }\n\n // Element slot\n invariant(el, 'compiled template produced an element slot without an element');\n\n if (slot.kind === SlotKind.EVENT) {\n const name = slot.name;\n\n invariant(name, 'compiled template produced an event slot without an event name');\n\n if (typeof value === 'function') {\n bindings.push({\n el,\n handler: value as (e: Event) => void,\n modifiers: slot.eventModifiers,\n name,\n type: 'event',\n });\n } else if (isReactive(value)) {\n const signalValue = value as Readable<unknown>;\n const handler = (e: Event) => {\n const h = signalValue.value;\n\n if (typeof h === 'function') (h as (e: Event) => void)(e);\n };\n\n bindings.push({ el, handler, modifiers: slot.eventModifiers, name, type: 'event' });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.PROP) {\n const name = slot.name;\n\n invariant(name, 'compiled template produced a prop slot without a property name');\n\n if (typeof value === 'function') {\n bindings.push({ el, name, signal: computed(value as () => unknown), type: 'prop' });\n } else if (isReactive(value)) {\n bindings.push({ el, name, signal: value as Readable<unknown>, type: 'prop' });\n } else {\n bindings.push({ el, name, type: 'prop', value });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.REF) {\n if (value) {\n bindings.push({ el, ref: value as Ref<Element> | RefCallback<Element>, type: 'ref' });\n }\n\n continue;\n }\n\n // attr / boolAttr\n invariant(slot.name, 'compiled template produced an attr slot without an attribute name');\n\n if (isModelSource(value)) {\n if (slot.mode !== 'attr') throw new OreApiError(ORE_ERRORS.modelRequiresValueBinding(slot.name));\n\n bindings.push({\n el,\n event: value.event ?? modelDefaultEvent(el),\n get: (value.get ?? modelDefaultGet) as (el: HTMLElement) => unknown,\n signal: value.source as Signal<unknown>,\n type: 'model',\n });\n continue;\n }\n\n bindings.push(createAttrBindingFromValue(el, slot.mode ?? 'attr', slot.name, value));\n }\n\n return createHtmlResult(fragment, (registerCleanup) => {\n for (const binding of bindings) applyBinding(binding, registerCleanup);\n for (const chainedApply of chainedApplies) chainedApply(registerCleanup);\n });\n};\n\nexport const html = (strings: TemplateStringsArray, ...values: unknown[]): HTMLResult =>\n compileTemplate(strings, values);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"instantiator.js","names":["NODE_SLOT_NO_PARENT_MSG","embedStaticResult","parent","anchor","invariant","result","chainedApplies","compileTemplate","compiled","getStaticTemplate","strings","fragment","bindings","boundSlots","value","values","i","slot","SlotKind","commentPath","followPath","elementPath","comment","isDirectiveResult","isHtmlResult","isReactive","item","resolveStaticText","el","name","signalValue","h","computed","isModelSource","OreApiError","ORE_ERRORS","modelDefaultEvent","modelDefaultGet","createAttrBindingFromValue","createHtmlResult","binding","applyBinding","registerCleanup","chainedApply","html"],"sources":["../../src/template/instantiator.ts"],"sourcesContent":["/**\n * template/instantiator.ts: Template instantiation and the `html` tagged literal.\n *\n * Responsibilities:\n * - Clone a compiled static template and wire up live bindings.\n * - Expose `compileTemplate()` and `html` as the public authoring API.\n */\n\nimport { computed, isReactive, type Readable, type Signal } from '@vielzeug/ripple';\n\nimport { isModelSource, modelDefaultEvent, modelDefaultGet } from '../directives/model';\nimport { invariant, ORE_ERRORS, OreApiError } from '../errors';\nimport type { Binding } from './binding-types';\nimport { applyBinding, createAttrBindingFromValue, resolveStaticText } from './bindings';\nimport { followPath, getStaticTemplate, SlotKind } from './compiler';\nimport {\n type CompiledHTMLResult,\n createHtmlResult,\n type HTMLResult,\n isDirectiveResult,\n isHtmlResult,\n type Ref,\n type RefCallback,\n} from './result';\n\n// ─── Template instantiation ──────────────────────────────────────────────────\n\nconst NODE_SLOT_NO_PARENT_MSG = 'html`...`: node-slot comment anchor has no parent node';\n\n/**\n * Static-embed an already-created HTMLResult at a node-slot anchor: move its\n * fragment children into place and chain its apply into the outer apply phase\n * (so embedded reactive wiring starts when the host template mounts, not now).\n */\nconst embedStaticResult = (\n result: CompiledHTMLResult,\n anchor: Comment,\n chainedApplies: Array<(rc: (fn: () => void) => void) => void>,\n): void => {\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n\n while (result.fragment.firstChild) parent.insertBefore(result.fragment.firstChild, anchor);\n\n chainedApplies.push(result.apply.bind(result));\n};\n\n/**\n * Instantiate a compiled template: clone the cached DOM template, navigate\n * to each binding target using pre-recorded paths, and build bindings with\n * direct node references. Returns an HTMLResult ready to mount.\n */\nexport const compileTemplate = (strings: TemplateStringsArray, values: unknown[]): HTMLResult => {\n const compiled = getStaticTemplate(strings);\n const fragment = compiled.element.content.cloneNode(true) as DocumentFragment;\n const bindings: Binding[] = [];\n // For static HTMLResult embeds: chain their apply calls\n const chainedApplies: Array<(rc: (fn: () => void) => void) => void> = [];\n\n // Phase 1: Resolve all binding targets BEFORE any DOM modifications\n type BoundSlot = { comment?: Comment; el?: HTMLElement; slot: (typeof compiled.slots)[number]; value: unknown };\n\n const boundSlots: BoundSlot[] = compiled.slots.map((slot, i) => {\n const value = values[i];\n\n if (slot.kind === SlotKind.NODE) {\n const commentPath = slot.commentId !== undefined ? compiled.commentPaths.get(slot.commentId) : undefined;\n\n invariant(commentPath, `compiled template is missing a comment path for node slot ${slot.commentId}`);\n\n return { comment: followPath(fragment, commentPath) as Comment, slot, value };\n }\n\n const elementPath = slot.elementId !== undefined ? compiled.elementPaths.get(slot.elementId) : undefined;\n\n invariant(elementPath, `compiled template is missing an element path for slot ${slot.elementId}`);\n\n return { el: followPath(fragment, elementPath) as HTMLElement, slot, value };\n });\n\n // Phase 2: Build bindings (may modify DOM for static content).\n for (const { comment, el, slot, value } of boundSlots) {\n if (slot.kind === SlotKind.NODE) {\n const anchor = comment;\n\n invariant(anchor, 'compiled template produced a node slot without a comment anchor');\n\n if (isDirectiveResult(value)) {\n bindings.push({ anchor, directive: value, type: 'directive' });\n continue;\n }\n\n if (isHtmlResult(value)) {\n // Static embed: move fragment children into place, chain apply\n embedStaticResult(value, anchor, chainedApplies);\n anchor.remove();\n continue;\n }\n\n if (typeof value === 'function' || isReactive(value)) {\n // Always use the html binding for reactive values: it handles both text\n // values and HTMLResult values, preventing silent \"[object Object]\"\n // corruption when a signal's runtime type changes from null/string to HTMLResult.\n // The source is handed over raw, without a memo computed, so the render effect\n // itself is the observer and the owner of anything the value builds (see HtmlBinding).\n bindings.push({ anchor, source: value as (() => unknown) | Readable<unknown>, type: 'html' });\n continue;\n }\n\n if (Array.isArray(value)) {\n for (const item of value) {\n if (isHtmlResult(item)) {\n embedStaticResult(item, anchor, chainedApplies);\n } else {\n const parent = anchor.parentNode;\n\n invariant(parent, NODE_SLOT_NO_PARENT_MSG);\n parent.insertBefore(document.createTextNode(resolveStaticText(item)), anchor);\n }\n }\n anchor.remove();\n continue;\n }\n\n // Static primitive: replace with text node, no binding\n anchor.replaceWith(document.createTextNode(resolveStaticText(value)));\n continue;\n }\n\n // Element slot\n invariant(el, 'compiled template produced an element slot without an element');\n\n if (slot.kind === SlotKind.EVENT) {\n const name = slot.name;\n\n invariant(name, 'compiled template produced an event slot without an event name');\n\n if (typeof value === 'function') {\n bindings.push({\n el,\n handler: value as (e: Event) => void,\n modifiers: slot.eventModifiers,\n name,\n type: 'event',\n });\n } else if (isReactive(value)) {\n const signalValue = value as Readable<unknown>;\n const handler = (e: Event) => {\n const h = signalValue.value;\n\n if (typeof h === 'function') (h as (e: Event) => void)(e);\n };\n\n bindings.push({ el, handler, modifiers: slot.eventModifiers, name, type: 'event' });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.PROP) {\n const name = slot.name;\n\n invariant(name, 'compiled template produced a prop slot without a property name');\n\n if (typeof value === 'function') {\n bindings.push({ el, name, signal: computed(value as () => unknown), type: 'prop' });\n } else if (isReactive(value)) {\n bindings.push({ el, name, signal: value as Readable<unknown>, type: 'prop' });\n } else {\n bindings.push({ el, name, type: 'prop', value });\n }\n\n continue;\n }\n\n if (slot.kind === SlotKind.REF) {\n if (value) {\n bindings.push({ el, ref: value as Ref<Element> | RefCallback<Element>, type: 'ref' });\n }\n\n continue;\n }\n\n // attr / boolAttr\n invariant(slot.name, 'compiled template produced an attr slot without an attribute name');\n\n if (isModelSource(value)) {\n if (slot.mode !== 'attr') throw new OreApiError(ORE_ERRORS.modelRequiresValueBinding(slot.name));\n\n bindings.push({\n el,\n event: value.event ?? modelDefaultEvent(el),\n get: (value.get ?? modelDefaultGet) as (el: HTMLElement) => unknown,\n signal: value.source as Signal<unknown>,\n type: 'model',\n });\n continue;\n }\n\n bindings.push(createAttrBindingFromValue(el, slot.mode ?? 'attr', slot.name, value));\n }\n\n return createHtmlResult(fragment, (registerCleanup) => {\n for (const binding of bindings) applyBinding(binding, registerCleanup);\n for (const chainedApply of chainedApplies) chainedApply(registerCleanup);\n });\n};\n\nexport const html = (strings: TemplateStringsArray, ...values: unknown[]): HTMLResult =>\n compileTemplate(strings, values);\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vielzeug/ore",
|
|
3
|
-
"version": "26.11.
|
|
3
|
+
"version": "26.11.8",
|
|
4
4
|
"description": "Functional web-component authoring on top of ripple",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
"test:types": "tsc -p tsconfig.json --noEmit"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
|
-
"@vielzeug/assay": "^26.11.
|
|
58
|
-
"@vielzeug/ripple": "^26.11.
|
|
57
|
+
"@vielzeug/assay": "^26.11.8",
|
|
58
|
+
"@vielzeug/ripple": "^26.11.8"
|
|
59
59
|
},
|
|
60
60
|
"peerDependenciesMeta": {
|
|
61
61
|
"@vielzeug/assay": {
|
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@types/node": "^26.6.6",
|
|
67
|
-
"@vielzeug/assay": "26.11.
|
|
68
|
-
"@vielzeug/ripple": "26.11.
|
|
67
|
+
"@vielzeug/assay": "26.11.8",
|
|
68
|
+
"@vielzeug/ripple": "26.11.8",
|
|
69
69
|
"axe-core": "^4.14.0",
|
|
70
70
|
"jsdom": "^31.0.0",
|
|
71
71
|
"typescript": "^6.0.3",
|