@vielzeug/ore 26.10.14 → 26.10.16
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/_dev.cjs.map +1 -1
- package/dist/_dev.js.map +1 -1
- package/dist/base-element.cjs.map +1 -1
- package/dist/base-element.js.map +1 -1
- package/dist/context.cjs.map +1 -1
- package/dist/context.js +1 -1
- package/dist/context.js.map +1 -1
- package/dist/define.cjs.map +1 -1
- package/dist/define.js.map +1 -1
- package/dist/directives/classMap.cjs.map +1 -1
- package/dist/directives/classMap.js.map +1 -1
- package/dist/directives/each.cjs.map +1 -1
- package/dist/directives/each.js +1 -1
- package/dist/directives/each.js.map +1 -1
- package/dist/directives/live.cjs.map +1 -1
- package/dist/directives/live.js.map +1 -1
- package/dist/directives/model.cjs.map +1 -1
- package/dist/directives/model.js.map +1 -1
- package/dist/directives/styleMap.cjs.map +1 -1
- package/dist/directives/styleMap.js.map +1 -1
- package/dist/directives/unsafe-html.cjs.map +1 -1
- package/dist/directives/unsafe-html.js.map +1 -1
- package/dist/directives/when.cjs.map +1 -1
- package/dist/directives/when.js.map +1 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/forms/field.cjs.map +1 -1
- package/dist/forms/field.js +1 -1
- package/dist/forms/field.js.map +1 -1
- package/dist/host-bind.cjs.map +1 -1
- package/dist/host-bind.js +1 -1
- package/dist/host-bind.js.map +1 -1
- 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 +1180 -2
- package/dist/ore.js.map +1 -1
- package/dist/props.cjs.map +1 -1
- package/dist/props.js +1 -1
- package/dist/props.js.map +1 -1
- package/dist/runtime.cjs.map +1 -1
- package/dist/runtime.js.map +1 -1
- package/dist/slots.cjs.map +1 -1
- package/dist/slots.js +1 -1
- package/dist/slots.js.map +1 -1
- package/dist/template/bindings.cjs +1 -1
- package/dist/template/bindings.cjs.map +1 -1
- package/dist/template/bindings.js +1 -1
- package/dist/template/bindings.js.map +1 -1
- package/dist/template/compiler.cjs.map +1 -1
- package/dist/template/compiler.js +1 -1
- package/dist/template/compiler.js.map +1 -1
- package/dist/template/instantiator.cjs +1 -1
- package/dist/template/instantiator.cjs.map +1 -1
- package/dist/template/instantiator.js +1 -1
- package/dist/template/instantiator.js.map +1 -1
- package/dist/template/result.cjs.map +1 -1
- package/dist/template/result.js.map +1 -1
- package/dist/testing/flush.cjs.map +1 -1
- package/dist/testing/flush.js.map +1 -1
- package/dist/testing/mount.cjs.map +1 -1
- package/dist/testing/mount.js.map +1 -1
- package/dist/testing/render-hook.cjs.map +1 -1
- package/dist/testing/render-hook.js +1 -1
- package/dist/testing/render-hook.js.map +1 -1
- package/dist/testing/reset.cjs.map +1 -1
- package/dist/testing/reset.js.map +1 -1
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.js.map +1 -1
- package/dist/utils/aria.cjs.map +1 -1
- package/dist/utils/aria.js.map +1 -1
- package/dist/utils/brand.cjs.map +1 -1
- package/dist/utils/brand.js.map +1 -1
- package/dist/utils/css.cjs.map +1 -1
- package/dist/utils/css.js +1 -1
- package/dist/utils/css.js.map +1 -1
- package/dist/utils/dom.cjs +1 -1
- package/dist/utils/dom.cjs.map +1 -1
- package/dist/utils/dom.js +1 -1
- package/dist/utils/dom.js.map +1 -1
- package/dist/utils/emit.cjs.map +1 -1
- package/dist/utils/emit.js.map +1 -1
- package/dist/utils/id.cjs.map +1 -1
- package/dist/utils/id.js.map +1 -1
- package/package.json +10 -10
package/dist/_dev.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_dev.cjs","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __ORE_PROD__?: boolean }).__ORE_PROD__;\n\n/** @internal @security Messages may include user-supplied data. */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/ore] ${msg}`);\n}\n\n/** @internal */\nexport function error(msg: string, ...args: unknown[]): void {\n if (isDev) console.error(`[@vielzeug/ore] ${msg}`, ...args);\n}\n"],"mappings":"AAGA,
|
|
1
|
+
{"version":3,"file":"_dev.cjs","names":["warn","error","args"],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __ORE_PROD__?: boolean }).__ORE_PROD__;\n\n/** @internal @security Messages may include user-supplied data. */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/ore] ${msg}`);\n}\n\n/** @internal */\nexport function error(msg: string, ...args: unknown[]): void {\n if (isDev) console.error(`[@vielzeug/ore] ${msg}`, ...args);\n}\n"],"mappings":"AAGA,SAAgBA,EAAK,EAAmB,CAExC,CAGA,SAAgBC,EAAM,EAAa,GAAGC,EAAuB,CAE7D,C"}
|
package/dist/_dev.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"_dev.js","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __ORE_PROD__?: boolean }).__ORE_PROD__;\n\n/** @internal @security Messages may include user-supplied data. */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/ore] ${msg}`);\n}\n\n/** @internal */\nexport function error(msg: string, ...args: unknown[]): void {\n if (isDev) console.error(`[@vielzeug/ore] ${msg}`, ...args);\n}\n"],"mappings":"AAGA,
|
|
1
|
+
{"version":3,"file":"_dev.js","names":["warn","error","args"],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __ORE_PROD__?: boolean }).__ORE_PROD__;\n\n/** @internal @security Messages may include user-supplied data. */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/ore] ${msg}`);\n}\n\n/** @internal */\nexport function error(msg: string, ...args: unknown[]): void {\n if (isDev) console.error(`[@vielzeug/ore] ${msg}`, ...args);\n}\n"],"mappings":"AAGA,SAAgBA,EAAK,EAAmB,CAExC,CAGA,SAAgBC,EAAM,EAAa,GAAGC,EAAuB,CAE7D,C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-element.cjs","names":[],"sources":["../src/base-element.ts"],"sourcesContent":["import { createScope, type Scope, untrack } from '@vielzeug/ripple';\n\nimport type { ComponentDefinition } from './component-types';\n\nimport { ORE_ERRORS, OreApiError, type OreErrorPhase, OreLifecycleError, reportRuntimeError } from './errors';\nimport { createProps, getPropMeta, type InferProps, type PropInputDefs, type PropsDef } from './props';\nimport {\n beginPendingWork,\n createRuntimeContext,\n type OnFormResetCallback,\n type OnMountedCallback,\n onCleanup,\n runWithContext,\n} from './runtime';\nimport type { HTMLResult } from './template/result';\nimport { loadStylesheet } from './utils/css';\n\n// ─── Component phases ─────────────────────────────────────────────────────────\n// Internal to BaseElement: the only state machine in the package.\n\nconst ComponentPhase = {\n SETUP_DONE: 'setup_done',\n SETUP_RUNNING: 'setup_running',\n UNINITIALIZED: 'uninitialized',\n UNMOUNTED: 'unmounted',\n} as const;\n\ntype ComponentPhase = (typeof ComponentPhase)[keyof typeof ComponentPhase];\n\n// ─── Internal component state ─────────────────────────────────────────────────\n\ntype ComponentState = {\n /** Registered via `onFormReset()`: persists across mount callbacks, unlike `mountCallbacks`. */\n formResetCallbacks: OnFormResetCallback[];\n /** Incremented on every disconnect: invalidates queued mount callbacks. */\n generation: number;\n mountCallbacks: OnMountedCallback[];\n phase: ComponentPhase;\n scope: Scope;\n templateResult: HTMLResult | null;\n};\n\nconst createComponentState = (): ComponentState => ({\n formResetCallbacks: [],\n generation: 0,\n mountCallbacks: [],\n phase: ComponentPhase.UNINITIALIZED,\n scope: createScope(),\n templateResult: null,\n});\n\nconst isPromiseLike = (value: unknown): value is PromiseLike<unknown> =>\n (typeof value === 'object' || typeof value === 'function') &&\n value !== null &&\n 'then' in value &&\n typeof value.then === 'function';\n\n// ─── BaseElement ──────────────────────────────────────────────────────────────\n\n/**\n * Phase transitions:\n *\n * ```\n * UNINITIALIZED ──_runSetup()──► SETUP_DONE\n * SETUP_DONE ──disconnectedCallback()──► UNMOUNTED ──(reset)──► UNINITIALIZED\n * ```\n *\n * `generation` increments on every disconnect. Scheduled mount callbacks capture\n * it so callbacks belonging to a disconnected instance cannot run after a\n * reconnect (see `_isStale`).\n *\n * Why this lives on the class instead of a standalone pure reducer: every\n * transition here is triggered by running actual user code (`def.setup()`,\n * `onMounted` callbacks) inside a reactive `scope.run()` + `runWithContext()`\n *: there is no meaningful \"decide the next phase\" step that can be separated\n * from \"run the side-effecting thing that produces the phase change\" without\n * introducing a data-only effect-description layer that this package has no\n * other use for. That's why the methods below stay as direct, readable\n * procedural steps instead of a reducer + effect interpreter.\n */\nexport class BaseElement extends HTMLElement {\n static _definition: ComponentDefinition;\n static _normalizedPropDefs: PropsDef<Record<never, never>> | undefined;\n static formAssociated = false;\n static observedAttributes: string[] = [];\n\n private _component: ComponentState;\n\n constructor() {\n super();\n\n const def = (this.constructor as typeof BaseElement)._definition;\n\n if (def?.shadow !== false) {\n this.attachShadow({ mode: 'open', ...(def?.shadow as Partial<ShadowRootInit> | undefined) });\n }\n\n this._component = createComponentState();\n }\n\n connectedCallback(): void {\n untrack(() => {\n if (this._component.phase === ComponentPhase.UNINITIALIZED) this._runSetup();\n\n this._init();\n });\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n\n const propMeta = getPropMeta(this, name);\n\n if (!propMeta) return;\n\n const parsed = propMeta.parse(newValue);\n\n if (\n !Object.is(\n untrack(() => propMeta.signal.value),\n parsed,\n )\n )\n propMeta.signal.value = parsed as never;\n }\n\n disconnectedCallback(): void {\n this._component.generation++;\n this._component.phase = ComponentPhase.UNMOUNTED;\n this._resetSetupState();\n }\n\n /** Dispose one connection's resources before its state can be rebuilt. */\n private _resetSetupState(): void {\n this._component.scope.dispose();\n // Reset mutable fields for next connection, keeping the same object for stable references.\n this._component.formResetCallbacks = [];\n this._component.mountCallbacks = [];\n this._component.phase = ComponentPhase.UNINITIALIZED;\n this._component.scope = createScope();\n this._component.templateResult = null;\n }\n\n /**\n * Native form-association lifecycle callback: the browser calls this on every\n * `formAssociated: true` element inside a `<form>` when that form is reset.\n * Runs every `onFormReset()` callback registered during `setup()`.\n */\n formResetCallback(): void {\n for (const callback of this._component.formResetCallbacks) {\n try {\n callback();\n } catch (error) {\n this._reportLifecycleError(error, 'form-reset');\n }\n }\n }\n\n private _reportLifecycleError(error: unknown, phase: OreErrorPhase): void {\n const err = error instanceof Error ? error : new Error(String(error));\n const oreError = new OreLifecycleError(`<${this.localName}> failed during ${this._component.phase} (${phase})`, {\n cause: err,\n component: this.localName,\n phase,\n });\n\n reportRuntimeError(oreError, this);\n }\n\n private _runSetup(): void {\n this._component.phase = ComponentPhase.SETUP_RUNNING;\n\n const def = (this.constructor as typeof BaseElement)._definition;\n const normalizedPropDefs = (this.constructor as typeof BaseElement)._normalizedPropDefs;\n const ctx = createRuntimeContext(this);\n\n try {\n let setupResult: HTMLResult | null | undefined;\n\n this._component.scope.run(() => {\n setupResult = runWithContext(ctx, () => {\n const setupProps = normalizedPropDefs\n ? createProps(this, normalizedPropDefs)\n : ({} as InferProps<PropInputDefs>);\n\n return def.setup(setupProps as InferProps<PropInputDefs>);\n });\n });\n this._component.mountCallbacks.push(...ctx.mountCallbacks);\n this._component.formResetCallbacks.push(...ctx.formResetCallbacks);\n\n if (isPromiseLike(setupResult)) throw new OreApiError(ORE_ERRORS.asyncSetupUnsupported);\n\n this._component.templateResult = setupResult ?? null;\n this._component.phase = ComponentPhase.SETUP_DONE;\n } catch (error) {\n this._reportLifecycleError(error, 'setup');\n // Setup is atomic: a failed run must not leave partial effects or cleanups\n // live until a later disconnect.\n this._resetSetupState();\n throw error;\n }\n }\n\n private _isStale(capturedGeneration: number): boolean {\n return this._component.generation !== capturedGeneration || !this.isConnected;\n }\n\n private _applyResult(result: HTMLResult | null): void {\n if (!result) return;\n\n const host: Element | ShadowRoot = this.shadowRoot ?? this;\n\n // Mounting can register component cleanup, so preserve a runtime context here too.\n const context = createRuntimeContext(this);\n\n host.replaceChildren();\n this._component.scope.run(() => {\n runWithContext(context, () => {\n result.mount(host, null, onCleanup);\n });\n });\n }\n\n private _init(): void {\n this._applyStyles();\n this._mountTemplate();\n\n // Setup completes before the template mounts, so callbacks always observe live DOM.\n if (this._component.phase === ComponentPhase.SETUP_DONE) this._scheduleMountCallbacks();\n }\n\n private _applyStyles(): void {\n const def = (this.constructor as typeof BaseElement)._definition;\n\n if (this.shadowRoot && def?.styles?.length) {\n this.shadowRoot.adoptedStyleSheets = def.styles.map(loadStylesheet);\n }\n }\n\n private _mountTemplate(): void {\n const result = this._component.templateResult;\n\n if (!result) return;\n\n this._applyResult(result);\n }\n\n private _scheduleMountCallbacks(): void {\n if (this._component.mountCallbacks.length === 0) return;\n\n const capturedGeneration = this._component.generation;\n // Tracked as pending work for the duration of this microtask: ended in a `finally`\n // so a thrown callback (already caught per-callback below, but defensive here too)\n // never leaves the counter stuck above zero. See runtime.ts's beginPendingWork().\n const endWork = beginPendingWork();\n\n queueMicrotask(() => {\n try {\n if (this._isStale(capturedGeneration)) return;\n\n // Snapshot callbacks so in-loop registrations don't extend this iteration.\n // Nested onMounted registrations are appended to `batch` and run in the same\n // microtask: no recursive scheduling. Index-based loop because the array\n // grows as nested callbacks are discovered.\n const batch = this._component.mountCallbacks.splice(0);\n\n for (let i = 0; i < batch.length; i++) {\n const callback = batch[i];\n\n try {\n const nestedCtx = createRuntimeContext(this);\n\n this._component.scope.run(() => {\n runWithContext(nestedCtx, () => {\n const cleanup = callback();\n\n if (typeof cleanup === 'function') onCleanup(cleanup);\n });\n });\n\n if (nestedCtx.mountCallbacks.length > 0) {\n batch.push(...nestedCtx.mountCallbacks);\n }\n\n if (nestedCtx.formResetCallbacks.length > 0) {\n this._component.formResetCallbacks.push(...nestedCtx.formResetCallbacks);\n }\n } catch (error) {\n this._reportLifecycleError(error, 'mounted');\n }\n }\n } finally {\n endWork();\n }\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"base-element.cjs","names":["createScope","getPropMeta","untrack","OreLifecycleError","createRuntimeContext","runWithContext","createProps","OreApiError","ORE_ERRORS","onCleanup","loadStylesheet","beginPendingWork","require_errors","require_runtime","require_props","require_css","_vielzeug_ripple","ComponentPhase","createComponentState","isPromiseLike","value","BaseElement","def","oldValue","newValue","propMeta","name","parsed","callback","error","err","oreError","phase","normalizedPropDefs","ctx","setupResult","setupProps","capturedGeneration","result","host","context","endWork","batch","i","nestedCtx","cleanup"],"sources":["../src/base-element.ts"],"sourcesContent":["import { createScope, type Scope, untrack } from '@vielzeug/ripple';\n\nimport type { ComponentDefinition } from './component-types';\n\nimport { ORE_ERRORS, OreApiError, type OreErrorPhase, OreLifecycleError, reportRuntimeError } from './errors';\nimport { createProps, getPropMeta, type InferProps, type PropInputDefs, type PropsDef } from './props';\nimport {\n beginPendingWork,\n createRuntimeContext,\n type OnFormResetCallback,\n type OnMountedCallback,\n onCleanup,\n runWithContext,\n} from './runtime';\nimport type { HTMLResult } from './template/result';\nimport { loadStylesheet } from './utils/css';\n\n// ─── Component phases ─────────────────────────────────────────────────────────\n// Internal to BaseElement: the only state machine in the package.\n\nconst ComponentPhase = {\n SETUP_DONE: 'setup_done',\n SETUP_RUNNING: 'setup_running',\n UNINITIALIZED: 'uninitialized',\n UNMOUNTED: 'unmounted',\n} as const;\n\ntype ComponentPhase = (typeof ComponentPhase)[keyof typeof ComponentPhase];\n\n// ─── Internal component state ─────────────────────────────────────────────────\n\ntype ComponentState = {\n /** Registered via `onFormReset()`: persists across mount callbacks, unlike `mountCallbacks`. */\n formResetCallbacks: OnFormResetCallback[];\n /** Incremented on every disconnect: invalidates queued mount callbacks. */\n generation: number;\n mountCallbacks: OnMountedCallback[];\n phase: ComponentPhase;\n scope: Scope;\n templateResult: HTMLResult | null;\n};\n\nconst createComponentState = (): ComponentState => ({\n formResetCallbacks: [],\n generation: 0,\n mountCallbacks: [],\n phase: ComponentPhase.UNINITIALIZED,\n scope: createScope(),\n templateResult: null,\n});\n\nconst isPromiseLike = (value: unknown): value is PromiseLike<unknown> =>\n (typeof value === 'object' || typeof value === 'function') &&\n value !== null &&\n 'then' in value &&\n typeof value.then === 'function';\n\n// ─── BaseElement ──────────────────────────────────────────────────────────────\n\n/**\n * Phase transitions:\n *\n * ```\n * UNINITIALIZED ──_runSetup()──► SETUP_DONE\n * SETUP_DONE ──disconnectedCallback()──► UNMOUNTED ──(reset)──► UNINITIALIZED\n * ```\n *\n * `generation` increments on every disconnect. Scheduled mount callbacks capture\n * it so callbacks belonging to a disconnected instance cannot run after a\n * reconnect (see `_isStale`).\n *\n * Why this lives on the class instead of a standalone pure reducer: every\n * transition here is triggered by running actual user code (`def.setup()`,\n * `onMounted` callbacks) inside a reactive `scope.run()` + `runWithContext()`\n *: there is no meaningful \"decide the next phase\" step that can be separated\n * from \"run the side-effecting thing that produces the phase change\" without\n * introducing a data-only effect-description layer that this package has no\n * other use for. That's why the methods below stay as direct, readable\n * procedural steps instead of a reducer + effect interpreter.\n */\nexport class BaseElement extends HTMLElement {\n static _definition: ComponentDefinition;\n static _normalizedPropDefs: PropsDef<Record<never, never>> | undefined;\n static formAssociated = false;\n static observedAttributes: string[] = [];\n\n private _component: ComponentState;\n\n constructor() {\n super();\n\n const def = (this.constructor as typeof BaseElement)._definition;\n\n if (def?.shadow !== false) {\n this.attachShadow({ mode: 'open', ...(def?.shadow as Partial<ShadowRootInit> | undefined) });\n }\n\n this._component = createComponentState();\n }\n\n connectedCallback(): void {\n untrack(() => {\n if (this._component.phase === ComponentPhase.UNINITIALIZED) this._runSetup();\n\n this._init();\n });\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n\n const propMeta = getPropMeta(this, name);\n\n if (!propMeta) return;\n\n const parsed = propMeta.parse(newValue);\n\n if (\n !Object.is(\n untrack(() => propMeta.signal.value),\n parsed,\n )\n )\n propMeta.signal.value = parsed as never;\n }\n\n disconnectedCallback(): void {\n this._component.generation++;\n this._component.phase = ComponentPhase.UNMOUNTED;\n this._resetSetupState();\n }\n\n /** Dispose one connection's resources before its state can be rebuilt. */\n private _resetSetupState(): void {\n this._component.scope.dispose();\n // Reset mutable fields for next connection, keeping the same object for stable references.\n this._component.formResetCallbacks = [];\n this._component.mountCallbacks = [];\n this._component.phase = ComponentPhase.UNINITIALIZED;\n this._component.scope = createScope();\n this._component.templateResult = null;\n }\n\n /**\n * Native form-association lifecycle callback: the browser calls this on every\n * `formAssociated: true` element inside a `<form>` when that form is reset.\n * Runs every `onFormReset()` callback registered during `setup()`.\n */\n formResetCallback(): void {\n for (const callback of this._component.formResetCallbacks) {\n try {\n callback();\n } catch (error) {\n this._reportLifecycleError(error, 'form-reset');\n }\n }\n }\n\n private _reportLifecycleError(error: unknown, phase: OreErrorPhase): void {\n const err = error instanceof Error ? error : new Error(String(error));\n const oreError = new OreLifecycleError(`<${this.localName}> failed during ${this._component.phase} (${phase})`, {\n cause: err,\n component: this.localName,\n phase,\n });\n\n reportRuntimeError(oreError, this);\n }\n\n private _runSetup(): void {\n this._component.phase = ComponentPhase.SETUP_RUNNING;\n\n const def = (this.constructor as typeof BaseElement)._definition;\n const normalizedPropDefs = (this.constructor as typeof BaseElement)._normalizedPropDefs;\n const ctx = createRuntimeContext(this);\n\n try {\n let setupResult: HTMLResult | null | undefined;\n\n this._component.scope.run(() => {\n setupResult = runWithContext(ctx, () => {\n const setupProps = normalizedPropDefs\n ? createProps(this, normalizedPropDefs)\n : ({} as InferProps<PropInputDefs>);\n\n return def.setup(setupProps as InferProps<PropInputDefs>);\n });\n });\n this._component.mountCallbacks.push(...ctx.mountCallbacks);\n this._component.formResetCallbacks.push(...ctx.formResetCallbacks);\n\n if (isPromiseLike(setupResult)) throw new OreApiError(ORE_ERRORS.asyncSetupUnsupported);\n\n this._component.templateResult = setupResult ?? null;\n this._component.phase = ComponentPhase.SETUP_DONE;\n } catch (error) {\n this._reportLifecycleError(error, 'setup');\n // Setup is atomic: a failed run must not leave partial effects or cleanups\n // live until a later disconnect.\n this._resetSetupState();\n throw error;\n }\n }\n\n private _isStale(capturedGeneration: number): boolean {\n return this._component.generation !== capturedGeneration || !this.isConnected;\n }\n\n private _applyResult(result: HTMLResult | null): void {\n if (!result) return;\n\n const host: Element | ShadowRoot = this.shadowRoot ?? this;\n\n // Mounting can register component cleanup, so preserve a runtime context here too.\n const context = createRuntimeContext(this);\n\n host.replaceChildren();\n this._component.scope.run(() => {\n runWithContext(context, () => {\n result.mount(host, null, onCleanup);\n });\n });\n }\n\n private _init(): void {\n this._applyStyles();\n this._mountTemplate();\n\n // Setup completes before the template mounts, so callbacks always observe live DOM.\n if (this._component.phase === ComponentPhase.SETUP_DONE) this._scheduleMountCallbacks();\n }\n\n private _applyStyles(): void {\n const def = (this.constructor as typeof BaseElement)._definition;\n\n if (this.shadowRoot && def?.styles?.length) {\n this.shadowRoot.adoptedStyleSheets = def.styles.map(loadStylesheet);\n }\n }\n\n private _mountTemplate(): void {\n const result = this._component.templateResult;\n\n if (!result) return;\n\n this._applyResult(result);\n }\n\n private _scheduleMountCallbacks(): void {\n if (this._component.mountCallbacks.length === 0) return;\n\n const capturedGeneration = this._component.generation;\n // Tracked as pending work for the duration of this microtask: ended in a `finally`\n // so a thrown callback (already caught per-callback below, but defensive here too)\n // never leaves the counter stuck above zero. See runtime.ts's beginPendingWork().\n const endWork = beginPendingWork();\n\n queueMicrotask(() => {\n try {\n if (this._isStale(capturedGeneration)) return;\n\n // Snapshot callbacks so in-loop registrations don't extend this iteration.\n // Nested onMounted registrations are appended to `batch` and run in the same\n // microtask: no recursive scheduling. Index-based loop because the array\n // grows as nested callbacks are discovered.\n const batch = this._component.mountCallbacks.splice(0);\n\n for (let i = 0; i < batch.length; i++) {\n const callback = batch[i];\n\n try {\n const nestedCtx = createRuntimeContext(this);\n\n this._component.scope.run(() => {\n runWithContext(nestedCtx, () => {\n const cleanup = callback();\n\n if (typeof cleanup === 'function') onCleanup(cleanup);\n });\n });\n\n if (nestedCtx.mountCallbacks.length > 0) {\n batch.push(...nestedCtx.mountCallbacks);\n }\n\n if (nestedCtx.formResetCallbacks.length > 0) {\n this._component.formResetCallbacks.push(...nestedCtx.formResetCallbacks);\n }\n } catch (error) {\n this._reportLifecycleError(error, 'mounted');\n }\n }\n } finally {\n endWork();\n }\n });\n }\n}\n"],"mappings":"A,mJAoBA,IAAMiB,EAAiB,CACrB,WAAY,aACZ,cAAe,gBACf,cAAe,gBACf,UAAW,WACb,EAiBMC,OAA8C,CAClD,mBAAoB,CAAC,EACrB,WAAY,EACZ,eAAgB,CAAC,EACjB,MAAOD,EAAe,cACtB,OAAA,EAAOjB,EAAAA,YAAAA,CAAY,EACnB,eAAgB,IAClB,GAEMmB,EAAiB,IACpB,OAAOC,GAAU,UAAY,OAAOA,GAAU,aAC/CA,IAAU,MACV,SAAUA,GACV,OAAOA,EAAM,MAAS,WAyBXC,EAAb,cAAiC,WAAY,CAC3C,OAAO,YACP,OAAO,oBACP,OAAO,eAAiB,GACxB,OAAO,mBAA+B,CAAC,EAEvC,WAEA,aAAc,CACZ,MAAM,EAEN,IAAMC,EAAO,KAAK,YAAmC,YAEjDA,GAAK,SAAW,IAClB,KAAK,aAAa,CAAE,KAAM,OAAQ,GAAIA,GAAK,MAA+C,CAAC,EAG7F,KAAK,WAAaJ,EAAqB,CACzC,CAEA,mBAA0B,EACxB,EAAAF,EAAA,QAAA,KAAc,CACR,KAAK,WAAW,QAAUC,EAAe,eAAe,KAAK,UAAU,EAE3E,KAAK,MAAM,CACb,CAAC,CACH,CAEA,yBAAyB,EAAc,EAAyB,EAA+B,CAC7F,GAAIM,IAAaC,EAAU,OAE3B,IAAMC,EAAWxB,EAAAA,YAAY,KAAMyB,CAAI,EAEvC,GAAI,CAACD,EAAU,OAEf,IAAME,EAASF,EAAS,MAAMD,CAAQ,EAGnC,OAAO,IAAA,EACNtB,EAAAA,QAAAA,KAAcuB,EAAS,OAAO,KAAK,EACnCE,CACF,IAEA,EAAS,OAAO,MAAQA,EAC5B,CAEA,sBAA6B,CAC3B,KAAK,WAAW,aAChB,KAAK,WAAW,MAAQV,EAAe,UACvC,KAAK,iBAAiB,CACxB,CAGA,kBAAiC,CAC/B,KAAK,WAAW,MAAM,QAAQ,EAE9B,KAAK,WAAW,mBAAqB,CAAC,EACtC,KAAK,WAAW,eAAiB,CAAC,EAClC,KAAK,WAAW,MAAQA,EAAe,cACvC,KAAK,WAAW,OAAA,EAAQjB,EAAAA,YAAAA,CAAY,EACpC,KAAK,WAAW,eAAiB,IACnC,CAOA,mBAA0B,CACxB,IAAK,IAAM4B,KAAY,KAAK,WAAW,mBACrC,GAAI,CACFA,EAAS,CACX,OAASC,EAAO,CACd,KAAK,sBAAsBA,EAAO,YAAY,CAChD,CAEJ,CAEA,sBAA8B,EAAgB,EAA4B,CACxE,IAAMC,EAAMD,aAAiB,MAAQA,EAAY,MAAM,OAAOA,CAAK,CAAC,EAC9DE,EAAW,IAAI5B,EAAAA,kBAAkB,IAAI,KAAK,UAAU,kBAAkB,KAAK,WAAW,MAAM,IAAI6B,EAAM,GAAI,CAC9G,MAAOF,EACP,UAAW,KAAK,UAChB,OACF,CAAC,EAEDlB,EAAA,mBAAmBmB,EAAU,IAAI,CACnC,CAEA,WAA0B,CACxB,KAAK,WAAW,MAAQd,EAAe,cAEvC,IAAMK,EAAO,KAAK,YAAmC,YAC/CW,EAAsB,KAAK,YAAmC,oBAC9DC,EAAM9B,EAAAA,qBAAqB,IAAI,EAErC,GAAI,CACF,IAAI+B,EAcJ,GAZA,KAAK,WAAW,MAAM,QAAU,CAC9B,EAAc9B,EAAAA,eAAe6B,MAAW,CACtC,IAAME,EAAaH,EACf3B,EAAAA,YAAY,KAAM2B,CAAkB,EACnC,CAAC,EAEN,OAAOX,EAAI,MAAMc,CAAuC,CAC1D,CAAC,CACH,CAAC,EACD,KAAK,WAAW,eAAe,KAAK,GAAGF,EAAI,cAAc,EACzD,KAAK,WAAW,mBAAmB,KAAK,GAAGA,EAAI,kBAAkB,EAE7Df,EAAcgB,CAAW,EAAG,MAAM,IAAI5B,EAAAA,YAAYC,EAAAA,WAAW,qBAAqB,EAEtF,KAAK,WAAW,eAAiB2B,GAAe,KAChD,KAAK,WAAW,MAAQlB,EAAe,UACzC,OAASY,EAAO,CAKd,MAJA,KAAK,sBAAsBA,EAAO,OAAO,EAGzC,KAAK,iBAAiB,EAChBA,CACR,CACF,CAEA,SAAiB,EAAqC,CACpD,OAAO,KAAK,WAAW,aAAeQ,GAAsB,CAAC,KAAK,WACpE,CAEA,aAAqB,EAAiC,CACpD,GAAI,CAACC,EAAQ,OAEb,IAAMC,EAA6B,KAAK,YAAc,KAGhDC,EAAUpC,EAAAA,qBAAqB,IAAI,EAEzCmC,EAAK,gBAAgB,EACrB,KAAK,WAAW,MAAM,QAAU,CAC9B1B,EAAA,eAAe2B,MAAe,CAC5BF,EAAO,MAAMC,EAAM,KAAM9B,EAAAA,SAAS,CACpC,CAAC,CACH,CAAC,CACH,CAEA,OAAsB,CACpB,KAAK,aAAa,EAClB,KAAK,eAAe,EAGhB,KAAK,WAAW,QAAUQ,EAAe,YAAY,KAAK,wBAAwB,CACxF,CAEA,cAA6B,CAC3B,IAAMK,EAAO,KAAK,YAAmC,YAEjD,KAAK,YAAcA,GAAK,QAAQ,SAClC,KAAK,WAAW,mBAAqBA,EAAI,OAAO,IAAIZ,EAAAA,cAAc,EAEtE,CAEA,gBAA+B,CAC7B,IAAM4B,EAAS,KAAK,WAAW,eAE1BA,GAEL,KAAK,aAAaA,CAAM,CAC1B,CAEA,yBAAwC,CACtC,GAAI,KAAK,WAAW,eAAe,SAAW,EAAG,OAEjD,IAAMD,EAAqB,KAAK,WAAW,WAIrCI,EAAU9B,EAAAA,iBAAiB,EAEjC,mBAAqB,CACnB,GAAI,CACF,GAAI,KAAK,SAAS0B,CAAkB,EAAG,OAMvC,IAAMK,EAAQ,KAAK,WAAW,eAAe,OAAO,CAAC,EAErD,IAAK,IAAIC,EAAI,EAAGA,EAAID,EAAM,OAAQ,IAAK,CACrC,IAAMd,EAAWc,EAAMC,GAEvB,GAAI,CACF,IAAMC,EAAYxC,EAAAA,qBAAqB,IAAI,EAE3C,KAAK,WAAW,MAAM,QAAU,CAC9BS,EAAA,eAAe+B,MAAiB,CAC9B,IAAMC,EAAUjB,EAAS,EAErB,OAAOiB,GAAY,YAAYhC,EAAA,UAAUgC,CAAO,CACtD,CAAC,CACH,CAAC,EAEGD,EAAU,eAAe,OAAS,GACpCF,EAAM,KAAK,GAAGE,EAAU,cAAc,EAGpCA,EAAU,mBAAmB,OAAS,GACxC,KAAK,WAAW,mBAAmB,KAAK,GAAGA,EAAU,kBAAkB,CAE3E,OAASf,EAAO,CACd,KAAK,sBAAsBA,EAAO,SAAS,CAC7C,CACF,CACF,QAAU,CACRY,EAAQ,CACV,CACF,CAAC,CACH,CACF,E"}
|
package/dist/base-element.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-element.js","names":[],"sources":["../src/base-element.ts"],"sourcesContent":["import { createScope, type Scope, untrack } from '@vielzeug/ripple';\n\nimport type { ComponentDefinition } from './component-types';\n\nimport { ORE_ERRORS, OreApiError, type OreErrorPhase, OreLifecycleError, reportRuntimeError } from './errors';\nimport { createProps, getPropMeta, type InferProps, type PropInputDefs, type PropsDef } from './props';\nimport {\n beginPendingWork,\n createRuntimeContext,\n type OnFormResetCallback,\n type OnMountedCallback,\n onCleanup,\n runWithContext,\n} from './runtime';\nimport type { HTMLResult } from './template/result';\nimport { loadStylesheet } from './utils/css';\n\n// ─── Component phases ─────────────────────────────────────────────────────────\n// Internal to BaseElement: the only state machine in the package.\n\nconst ComponentPhase = {\n SETUP_DONE: 'setup_done',\n SETUP_RUNNING: 'setup_running',\n UNINITIALIZED: 'uninitialized',\n UNMOUNTED: 'unmounted',\n} as const;\n\ntype ComponentPhase = (typeof ComponentPhase)[keyof typeof ComponentPhase];\n\n// ─── Internal component state ─────────────────────────────────────────────────\n\ntype ComponentState = {\n /** Registered via `onFormReset()`: persists across mount callbacks, unlike `mountCallbacks`. */\n formResetCallbacks: OnFormResetCallback[];\n /** Incremented on every disconnect: invalidates queued mount callbacks. */\n generation: number;\n mountCallbacks: OnMountedCallback[];\n phase: ComponentPhase;\n scope: Scope;\n templateResult: HTMLResult | null;\n};\n\nconst createComponentState = (): ComponentState => ({\n formResetCallbacks: [],\n generation: 0,\n mountCallbacks: [],\n phase: ComponentPhase.UNINITIALIZED,\n scope: createScope(),\n templateResult: null,\n});\n\nconst isPromiseLike = (value: unknown): value is PromiseLike<unknown> =>\n (typeof value === 'object' || typeof value === 'function') &&\n value !== null &&\n 'then' in value &&\n typeof value.then === 'function';\n\n// ─── BaseElement ──────────────────────────────────────────────────────────────\n\n/**\n * Phase transitions:\n *\n * ```\n * UNINITIALIZED ──_runSetup()──► SETUP_DONE\n * SETUP_DONE ──disconnectedCallback()──► UNMOUNTED ──(reset)──► UNINITIALIZED\n * ```\n *\n * `generation` increments on every disconnect. Scheduled mount callbacks capture\n * it so callbacks belonging to a disconnected instance cannot run after a\n * reconnect (see `_isStale`).\n *\n * Why this lives on the class instead of a standalone pure reducer: every\n * transition here is triggered by running actual user code (`def.setup()`,\n * `onMounted` callbacks) inside a reactive `scope.run()` + `runWithContext()`\n *: there is no meaningful \"decide the next phase\" step that can be separated\n * from \"run the side-effecting thing that produces the phase change\" without\n * introducing a data-only effect-description layer that this package has no\n * other use for. That's why the methods below stay as direct, readable\n * procedural steps instead of a reducer + effect interpreter.\n */\nexport class BaseElement extends HTMLElement {\n static _definition: ComponentDefinition;\n static _normalizedPropDefs: PropsDef<Record<never, never>> | undefined;\n static formAssociated = false;\n static observedAttributes: string[] = [];\n\n private _component: ComponentState;\n\n constructor() {\n super();\n\n const def = (this.constructor as typeof BaseElement)._definition;\n\n if (def?.shadow !== false) {\n this.attachShadow({ mode: 'open', ...(def?.shadow as Partial<ShadowRootInit> | undefined) });\n }\n\n this._component = createComponentState();\n }\n\n connectedCallback(): void {\n untrack(() => {\n if (this._component.phase === ComponentPhase.UNINITIALIZED) this._runSetup();\n\n this._init();\n });\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n\n const propMeta = getPropMeta(this, name);\n\n if (!propMeta) return;\n\n const parsed = propMeta.parse(newValue);\n\n if (\n !Object.is(\n untrack(() => propMeta.signal.value),\n parsed,\n )\n )\n propMeta.signal.value = parsed as never;\n }\n\n disconnectedCallback(): void {\n this._component.generation++;\n this._component.phase = ComponentPhase.UNMOUNTED;\n this._resetSetupState();\n }\n\n /** Dispose one connection's resources before its state can be rebuilt. */\n private _resetSetupState(): void {\n this._component.scope.dispose();\n // Reset mutable fields for next connection, keeping the same object for stable references.\n this._component.formResetCallbacks = [];\n this._component.mountCallbacks = [];\n this._component.phase = ComponentPhase.UNINITIALIZED;\n this._component.scope = createScope();\n this._component.templateResult = null;\n }\n\n /**\n * Native form-association lifecycle callback: the browser calls this on every\n * `formAssociated: true` element inside a `<form>` when that form is reset.\n * Runs every `onFormReset()` callback registered during `setup()`.\n */\n formResetCallback(): void {\n for (const callback of this._component.formResetCallbacks) {\n try {\n callback();\n } catch (error) {\n this._reportLifecycleError(error, 'form-reset');\n }\n }\n }\n\n private _reportLifecycleError(error: unknown, phase: OreErrorPhase): void {\n const err = error instanceof Error ? error : new Error(String(error));\n const oreError = new OreLifecycleError(`<${this.localName}> failed during ${this._component.phase} (${phase})`, {\n cause: err,\n component: this.localName,\n phase,\n });\n\n reportRuntimeError(oreError, this);\n }\n\n private _runSetup(): void {\n this._component.phase = ComponentPhase.SETUP_RUNNING;\n\n const def = (this.constructor as typeof BaseElement)._definition;\n const normalizedPropDefs = (this.constructor as typeof BaseElement)._normalizedPropDefs;\n const ctx = createRuntimeContext(this);\n\n try {\n let setupResult: HTMLResult | null | undefined;\n\n this._component.scope.run(() => {\n setupResult = runWithContext(ctx, () => {\n const setupProps = normalizedPropDefs\n ? createProps(this, normalizedPropDefs)\n : ({} as InferProps<PropInputDefs>);\n\n return def.setup(setupProps as InferProps<PropInputDefs>);\n });\n });\n this._component.mountCallbacks.push(...ctx.mountCallbacks);\n this._component.formResetCallbacks.push(...ctx.formResetCallbacks);\n\n if (isPromiseLike(setupResult)) throw new OreApiError(ORE_ERRORS.asyncSetupUnsupported);\n\n this._component.templateResult = setupResult ?? null;\n this._component.phase = ComponentPhase.SETUP_DONE;\n } catch (error) {\n this._reportLifecycleError(error, 'setup');\n // Setup is atomic: a failed run must not leave partial effects or cleanups\n // live until a later disconnect.\n this._resetSetupState();\n throw error;\n }\n }\n\n private _isStale(capturedGeneration: number): boolean {\n return this._component.generation !== capturedGeneration || !this.isConnected;\n }\n\n private _applyResult(result: HTMLResult | null): void {\n if (!result) return;\n\n const host: Element | ShadowRoot = this.shadowRoot ?? this;\n\n // Mounting can register component cleanup, so preserve a runtime context here too.\n const context = createRuntimeContext(this);\n\n host.replaceChildren();\n this._component.scope.run(() => {\n runWithContext(context, () => {\n result.mount(host, null, onCleanup);\n });\n });\n }\n\n private _init(): void {\n this._applyStyles();\n this._mountTemplate();\n\n // Setup completes before the template mounts, so callbacks always observe live DOM.\n if (this._component.phase === ComponentPhase.SETUP_DONE) this._scheduleMountCallbacks();\n }\n\n private _applyStyles(): void {\n const def = (this.constructor as typeof BaseElement)._definition;\n\n if (this.shadowRoot && def?.styles?.length) {\n this.shadowRoot.adoptedStyleSheets = def.styles.map(loadStylesheet);\n }\n }\n\n private _mountTemplate(): void {\n const result = this._component.templateResult;\n\n if (!result) return;\n\n this._applyResult(result);\n }\n\n private _scheduleMountCallbacks(): void {\n if (this._component.mountCallbacks.length === 0) return;\n\n const capturedGeneration = this._component.generation;\n // Tracked as pending work for the duration of this microtask: ended in a `finally`\n // so a thrown callback (already caught per-callback below, but defensive here too)\n // never leaves the counter stuck above zero. See runtime.ts's beginPendingWork().\n const endWork = beginPendingWork();\n\n queueMicrotask(() => {\n try {\n if (this._isStale(capturedGeneration)) return;\n\n // Snapshot callbacks so in-loop registrations don't extend this iteration.\n // Nested onMounted registrations are appended to `batch` and run in the same\n // microtask: no recursive scheduling. Index-based loop because the array\n // grows as nested callbacks are discovered.\n const batch = this._component.mountCallbacks.splice(0);\n\n for (let i = 0; i < batch.length; i++) {\n const callback = batch[i];\n\n try {\n const nestedCtx = createRuntimeContext(this);\n\n this._component.scope.run(() => {\n runWithContext(nestedCtx, () => {\n const cleanup = callback();\n\n if (typeof cleanup === 'function') onCleanup(cleanup);\n });\n });\n\n if (nestedCtx.mountCallbacks.length > 0) {\n batch.push(...nestedCtx.mountCallbacks);\n }\n\n if (nestedCtx.formResetCallbacks.length > 0) {\n this._component.formResetCallbacks.push(...nestedCtx.formResetCallbacks);\n }\n } catch (error) {\n this._reportLifecycleError(error, 'mounted');\n }\n }\n } finally {\n endWork();\n }\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"base-element.js","names":["ComponentPhase","createComponentState","createScope","isPromiseLike","value","BaseElement","def","untrack","oldValue","newValue","propMeta","getPropMeta","name","parsed","callback","error","err","oreError","OreLifecycleError","phase","reportRuntimeError","normalizedPropDefs","ctx","createRuntimeContext","setupResult","runWithContext","setupProps","createProps","OreApiError","ORE_ERRORS","capturedGeneration","result","host","context","onCleanup","loadStylesheet","endWork","beginPendingWork","batch","i","nestedCtx","cleanup"],"sources":["../src/base-element.ts"],"sourcesContent":["import { createScope, type Scope, untrack } from '@vielzeug/ripple';\n\nimport type { ComponentDefinition } from './component-types';\n\nimport { ORE_ERRORS, OreApiError, type OreErrorPhase, OreLifecycleError, reportRuntimeError } from './errors';\nimport { createProps, getPropMeta, type InferProps, type PropInputDefs, type PropsDef } from './props';\nimport {\n beginPendingWork,\n createRuntimeContext,\n type OnFormResetCallback,\n type OnMountedCallback,\n onCleanup,\n runWithContext,\n} from './runtime';\nimport type { HTMLResult } from './template/result';\nimport { loadStylesheet } from './utils/css';\n\n// ─── Component phases ─────────────────────────────────────────────────────────\n// Internal to BaseElement: the only state machine in the package.\n\nconst ComponentPhase = {\n SETUP_DONE: 'setup_done',\n SETUP_RUNNING: 'setup_running',\n UNINITIALIZED: 'uninitialized',\n UNMOUNTED: 'unmounted',\n} as const;\n\ntype ComponentPhase = (typeof ComponentPhase)[keyof typeof ComponentPhase];\n\n// ─── Internal component state ─────────────────────────────────────────────────\n\ntype ComponentState = {\n /** Registered via `onFormReset()`: persists across mount callbacks, unlike `mountCallbacks`. */\n formResetCallbacks: OnFormResetCallback[];\n /** Incremented on every disconnect: invalidates queued mount callbacks. */\n generation: number;\n mountCallbacks: OnMountedCallback[];\n phase: ComponentPhase;\n scope: Scope;\n templateResult: HTMLResult | null;\n};\n\nconst createComponentState = (): ComponentState => ({\n formResetCallbacks: [],\n generation: 0,\n mountCallbacks: [],\n phase: ComponentPhase.UNINITIALIZED,\n scope: createScope(),\n templateResult: null,\n});\n\nconst isPromiseLike = (value: unknown): value is PromiseLike<unknown> =>\n (typeof value === 'object' || typeof value === 'function') &&\n value !== null &&\n 'then' in value &&\n typeof value.then === 'function';\n\n// ─── BaseElement ──────────────────────────────────────────────────────────────\n\n/**\n * Phase transitions:\n *\n * ```\n * UNINITIALIZED ──_runSetup()──► SETUP_DONE\n * SETUP_DONE ──disconnectedCallback()──► UNMOUNTED ──(reset)──► UNINITIALIZED\n * ```\n *\n * `generation` increments on every disconnect. Scheduled mount callbacks capture\n * it so callbacks belonging to a disconnected instance cannot run after a\n * reconnect (see `_isStale`).\n *\n * Why this lives on the class instead of a standalone pure reducer: every\n * transition here is triggered by running actual user code (`def.setup()`,\n * `onMounted` callbacks) inside a reactive `scope.run()` + `runWithContext()`\n *: there is no meaningful \"decide the next phase\" step that can be separated\n * from \"run the side-effecting thing that produces the phase change\" without\n * introducing a data-only effect-description layer that this package has no\n * other use for. That's why the methods below stay as direct, readable\n * procedural steps instead of a reducer + effect interpreter.\n */\nexport class BaseElement extends HTMLElement {\n static _definition: ComponentDefinition;\n static _normalizedPropDefs: PropsDef<Record<never, never>> | undefined;\n static formAssociated = false;\n static observedAttributes: string[] = [];\n\n private _component: ComponentState;\n\n constructor() {\n super();\n\n const def = (this.constructor as typeof BaseElement)._definition;\n\n if (def?.shadow !== false) {\n this.attachShadow({ mode: 'open', ...(def?.shadow as Partial<ShadowRootInit> | undefined) });\n }\n\n this._component = createComponentState();\n }\n\n connectedCallback(): void {\n untrack(() => {\n if (this._component.phase === ComponentPhase.UNINITIALIZED) this._runSetup();\n\n this._init();\n });\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) return;\n\n const propMeta = getPropMeta(this, name);\n\n if (!propMeta) return;\n\n const parsed = propMeta.parse(newValue);\n\n if (\n !Object.is(\n untrack(() => propMeta.signal.value),\n parsed,\n )\n )\n propMeta.signal.value = parsed as never;\n }\n\n disconnectedCallback(): void {\n this._component.generation++;\n this._component.phase = ComponentPhase.UNMOUNTED;\n this._resetSetupState();\n }\n\n /** Dispose one connection's resources before its state can be rebuilt. */\n private _resetSetupState(): void {\n this._component.scope.dispose();\n // Reset mutable fields for next connection, keeping the same object for stable references.\n this._component.formResetCallbacks = [];\n this._component.mountCallbacks = [];\n this._component.phase = ComponentPhase.UNINITIALIZED;\n this._component.scope = createScope();\n this._component.templateResult = null;\n }\n\n /**\n * Native form-association lifecycle callback: the browser calls this on every\n * `formAssociated: true` element inside a `<form>` when that form is reset.\n * Runs every `onFormReset()` callback registered during `setup()`.\n */\n formResetCallback(): void {\n for (const callback of this._component.formResetCallbacks) {\n try {\n callback();\n } catch (error) {\n this._reportLifecycleError(error, 'form-reset');\n }\n }\n }\n\n private _reportLifecycleError(error: unknown, phase: OreErrorPhase): void {\n const err = error instanceof Error ? error : new Error(String(error));\n const oreError = new OreLifecycleError(`<${this.localName}> failed during ${this._component.phase} (${phase})`, {\n cause: err,\n component: this.localName,\n phase,\n });\n\n reportRuntimeError(oreError, this);\n }\n\n private _runSetup(): void {\n this._component.phase = ComponentPhase.SETUP_RUNNING;\n\n const def = (this.constructor as typeof BaseElement)._definition;\n const normalizedPropDefs = (this.constructor as typeof BaseElement)._normalizedPropDefs;\n const ctx = createRuntimeContext(this);\n\n try {\n let setupResult: HTMLResult | null | undefined;\n\n this._component.scope.run(() => {\n setupResult = runWithContext(ctx, () => {\n const setupProps = normalizedPropDefs\n ? createProps(this, normalizedPropDefs)\n : ({} as InferProps<PropInputDefs>);\n\n return def.setup(setupProps as InferProps<PropInputDefs>);\n });\n });\n this._component.mountCallbacks.push(...ctx.mountCallbacks);\n this._component.formResetCallbacks.push(...ctx.formResetCallbacks);\n\n if (isPromiseLike(setupResult)) throw new OreApiError(ORE_ERRORS.asyncSetupUnsupported);\n\n this._component.templateResult = setupResult ?? null;\n this._component.phase = ComponentPhase.SETUP_DONE;\n } catch (error) {\n this._reportLifecycleError(error, 'setup');\n // Setup is atomic: a failed run must not leave partial effects or cleanups\n // live until a later disconnect.\n this._resetSetupState();\n throw error;\n }\n }\n\n private _isStale(capturedGeneration: number): boolean {\n return this._component.generation !== capturedGeneration || !this.isConnected;\n }\n\n private _applyResult(result: HTMLResult | null): void {\n if (!result) return;\n\n const host: Element | ShadowRoot = this.shadowRoot ?? this;\n\n // Mounting can register component cleanup, so preserve a runtime context here too.\n const context = createRuntimeContext(this);\n\n host.replaceChildren();\n this._component.scope.run(() => {\n runWithContext(context, () => {\n result.mount(host, null, onCleanup);\n });\n });\n }\n\n private _init(): void {\n this._applyStyles();\n this._mountTemplate();\n\n // Setup completes before the template mounts, so callbacks always observe live DOM.\n if (this._component.phase === ComponentPhase.SETUP_DONE) this._scheduleMountCallbacks();\n }\n\n private _applyStyles(): void {\n const def = (this.constructor as typeof BaseElement)._definition;\n\n if (this.shadowRoot && def?.styles?.length) {\n this.shadowRoot.adoptedStyleSheets = def.styles.map(loadStylesheet);\n }\n }\n\n private _mountTemplate(): void {\n const result = this._component.templateResult;\n\n if (!result) return;\n\n this._applyResult(result);\n }\n\n private _scheduleMountCallbacks(): void {\n if (this._component.mountCallbacks.length === 0) return;\n\n const capturedGeneration = this._component.generation;\n // Tracked as pending work for the duration of this microtask: ended in a `finally`\n // so a thrown callback (already caught per-callback below, but defensive here too)\n // never leaves the counter stuck above zero. See runtime.ts's beginPendingWork().\n const endWork = beginPendingWork();\n\n queueMicrotask(() => {\n try {\n if (this._isStale(capturedGeneration)) return;\n\n // Snapshot callbacks so in-loop registrations don't extend this iteration.\n // Nested onMounted registrations are appended to `batch` and run in the same\n // microtask: no recursive scheduling. Index-based loop because the array\n // grows as nested callbacks are discovered.\n const batch = this._component.mountCallbacks.splice(0);\n\n for (let i = 0; i < batch.length; i++) {\n const callback = batch[i];\n\n try {\n const nestedCtx = createRuntimeContext(this);\n\n this._component.scope.run(() => {\n runWithContext(nestedCtx, () => {\n const cleanup = callback();\n\n if (typeof cleanup === 'function') onCleanup(cleanup);\n });\n });\n\n if (nestedCtx.mountCallbacks.length > 0) {\n batch.push(...nestedCtx.mountCallbacks);\n }\n\n if (nestedCtx.formResetCallbacks.length > 0) {\n this._component.formResetCallbacks.push(...nestedCtx.formResetCallbacks);\n }\n } catch (error) {\n this._reportLifecycleError(error, 'mounted');\n }\n }\n } finally {\n endWork();\n }\n });\n }\n}\n"],"mappings":"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"}
|
package/dist/context.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.cjs","names":[],"sources":["../src/context.ts"],"sourcesContent":["/**\n * Component context injection API: `inject` / `injectStrict` / `provide` / `createContext`.\n *\n * Context values are stored on the providing element via a WeakMap registry and\n * resolved by walking up the DOM tree (including through shadow boundaries).\n * Providing is done via `provide(key, value)` inside `setup()`.\n *\n * Each `createContext()` call creates a unique key. Descriptions are diagnostic\n * labels only and never determine identity.\n */\n\nimport { warn } from './_dev';\nimport { ORE_ERRORS, OreApiError } from './errors';\nimport { onCleanup, type RuntimeContext, requireSetupContext } from './runtime';\n\nconst contextRegistry = new WeakMap<HTMLElement, Map<InjectionKey<unknown>, unknown>>();\n\nexport type InjectionKey<T> = symbol & {\n readonly __ore_injection_key?: T;\n};\n\n/**\n * Build a linear ancestor chain (including shadow host boundaries).\n * Walks `parentNode` (not `parentElement`) so non-HTML intermediate parents\n * (e.g. an `SVGElement` between child and provider) don't break the chain.\n */\nconst buildAncestorChain = (start: HTMLElement): HTMLElement[] => {\n const chain: HTMLElement[] = [];\n let node: Node | null = start;\n\n while (node) {\n if (node instanceof HTMLElement) chain.push(node);\n\n // A ShadowRoot's parentNode is null: hop to its host to keep walking.\n node = node.parentNode ?? (node instanceof ShadowRoot ? node.host : null);\n }\n\n return chain;\n};\n\n/**\n * Register a context value on a specific element.\n * @internal Backs the public `provide()`: do not call directly.\n */\nconst provideOnElement = <T>(el: HTMLElement, key: InjectionKey<T>, value: T): void => {\n const map = contextRegistry.get(el) ?? new Map<InjectionKey<unknown>, unknown>();\n\n // `inject()` memoizes its result per consumer (see resolvedCache below), so a\n // provider swapping the raw value after a descendant already read it would be\n // silently ignored downstream. Provide a `Readable` (signal/computed) instead\n // of a raw value so descendants observe updates through the value itself.\n if (map.has(key)) {\n warn(`provide(): key already provided on <${el.localName}>: overwriting. Provide a Readable to update it instead.`);\n }\n\n map.set(key, value);\n contextRegistry.set(el, map);\n};\n\n/**\n * Register a context value on the current component's host element, making it\n * available to descendant components via `inject(key)`.\n *\n * Provide a `Readable` (signal/computed) rather than a raw value if descendants\n * need to observe later changes: `inject()` resolves and caches the value once\n * per consumer, so re-calling `provide()` with a new raw value later is not seen.\n */\nexport const provide = <T>(key: InjectionKey<T>, value: T): void => {\n const el = requireSetupContext('provide').element;\n\n provideOnElement(el, key, value);\n\n onCleanup(() => {\n const map = contextRegistry.get(el);\n\n if (!map) return;\n\n map.delete(key);\n\n if (map.size === 0) contextRegistry.delete(el);\n });\n};\n\nconst NOT_FOUND_SENTINEL = Symbol('inject.not_found');\n\n/** Per-setup-context cache: avoids repeated ancestor walks for the same key. */\nconst resolvedCache = new WeakMap<object, Map<InjectionKey<unknown>, unknown>>();\n\nconst walkAndFind = <T>(element: HTMLElement, key: InjectionKey<T>): T | typeof NOT_FOUND_SENTINEL => {\n const chain = buildAncestorChain(element);\n\n for (const node of chain) {\n const map = contextRegistry.get(node);\n\n if (map?.has(key)) return map.get(key) as T;\n }\n\n return NOT_FOUND_SENTINEL;\n};\n\n/** Cached ancestor-walk lookup shared by `inject()` and `injectStrict()`. */\nconst lookup = <T>(ctx: RuntimeContext, key: InjectionKey<T>): T | typeof NOT_FOUND_SENTINEL => {\n let cache = resolvedCache.get(ctx);\n\n if (!cache) {\n cache = new Map();\n resolvedCache.set(ctx, cache);\n }\n\n const cacheKey = key as InjectionKey<unknown>;\n\n if (!cache.has(cacheKey)) cache.set(cacheKey, walkAndFind(ctx.element, key));\n\n return cache.get(cacheKey) as T | typeof NOT_FOUND_SENTINEL;\n};\n\nexport function inject<T>(key: InjectionKey<T>): T | undefined;\nexport function inject<T>(key: InjectionKey<T>, fallback: T): T;\nexport function inject<T>(key: InjectionKey<T>, ...rest: [T?]): T | undefined {\n const found = lookup(requireSetupContext('inject'), key);\n\n if (found === NOT_FOUND_SENTINEL) return rest.length > 0 ? rest[0] : undefined;\n\n return found;\n}\n\nexport const injectStrict = <T>(key: InjectionKey<T>): T => {\n const ctx = requireSetupContext('injectStrict');\n const found = lookup(ctx, key);\n\n if (found !== NOT_FOUND_SENTINEL) return found;\n\n throw new OreApiError(ORE_ERRORS.injectStrictFailed(String(key), ctx.element.localName));\n};\n\n/** Create a unique typed context key with a diagnostic description. */\nexport function createContext<T>(description: string): InjectionKey<T> {\n return Symbol(description) as InjectionKey<T>;\n}\n"],"mappings":"iFAeA,
|
|
1
|
+
{"version":3,"file":"context.cjs","names":["requireSetupContext","OreApiError","ORE_ERRORS","require_errors","require_runtime","contextRegistry","buildAncestorChain","chain","node","start","provideOnElement","map","el","key","value","provide","NOT_FOUND_SENTINEL","resolvedCache","walkAndFind","element","lookup","cache","ctx","cacheKey","inject","rest","found","injectStrict","createContext","description"],"sources":["../src/context.ts"],"sourcesContent":["/**\n * Component context injection API: `inject` / `injectStrict` / `provide` / `createContext`.\n *\n * Context values are stored on the providing element via a WeakMap registry and\n * resolved by walking up the DOM tree (including through shadow boundaries).\n * Providing is done via `provide(key, value)` inside `setup()`.\n *\n * Each `createContext()` call creates a unique key. Descriptions are diagnostic\n * labels only and never determine identity.\n */\n\nimport { warn } from './_dev';\nimport { ORE_ERRORS, OreApiError } from './errors';\nimport { onCleanup, type RuntimeContext, requireSetupContext } from './runtime';\n\nconst contextRegistry = new WeakMap<HTMLElement, Map<InjectionKey<unknown>, unknown>>();\n\nexport type InjectionKey<T> = symbol & {\n readonly __ore_injection_key?: T;\n};\n\n/**\n * Build a linear ancestor chain (including shadow host boundaries).\n * Walks `parentNode` (not `parentElement`) so non-HTML intermediate parents\n * (e.g. an `SVGElement` between child and provider) don't break the chain.\n */\nconst buildAncestorChain = (start: HTMLElement): HTMLElement[] => {\n const chain: HTMLElement[] = [];\n let node: Node | null = start;\n\n while (node) {\n if (node instanceof HTMLElement) chain.push(node);\n\n // A ShadowRoot's parentNode is null: hop to its host to keep walking.\n node = node.parentNode ?? (node instanceof ShadowRoot ? node.host : null);\n }\n\n return chain;\n};\n\n/**\n * Register a context value on a specific element.\n * @internal Backs the public `provide()`: do not call directly.\n */\nconst provideOnElement = <T>(el: HTMLElement, key: InjectionKey<T>, value: T): void => {\n const map = contextRegistry.get(el) ?? new Map<InjectionKey<unknown>, unknown>();\n\n // `inject()` memoizes its result per consumer (see resolvedCache below), so a\n // provider swapping the raw value after a descendant already read it would be\n // silently ignored downstream. Provide a `Readable` (signal/computed) instead\n // of a raw value so descendants observe updates through the value itself.\n if (map.has(key)) {\n warn(`provide(): key already provided on <${el.localName}>: overwriting. Provide a Readable to update it instead.`);\n }\n\n map.set(key, value);\n contextRegistry.set(el, map);\n};\n\n/**\n * Register a context value on the current component's host element, making it\n * available to descendant components via `inject(key)`.\n *\n * Provide a `Readable` (signal/computed) rather than a raw value if descendants\n * need to observe later changes: `inject()` resolves and caches the value once\n * per consumer, so re-calling `provide()` with a new raw value later is not seen.\n */\nexport const provide = <T>(key: InjectionKey<T>, value: T): void => {\n const el = requireSetupContext('provide').element;\n\n provideOnElement(el, key, value);\n\n onCleanup(() => {\n const map = contextRegistry.get(el);\n\n if (!map) return;\n\n map.delete(key);\n\n if (map.size === 0) contextRegistry.delete(el);\n });\n};\n\nconst NOT_FOUND_SENTINEL = Symbol('inject.not_found');\n\n/** Per-setup-context cache: avoids repeated ancestor walks for the same key. */\nconst resolvedCache = new WeakMap<object, Map<InjectionKey<unknown>, unknown>>();\n\nconst walkAndFind = <T>(element: HTMLElement, key: InjectionKey<T>): T | typeof NOT_FOUND_SENTINEL => {\n const chain = buildAncestorChain(element);\n\n for (const node of chain) {\n const map = contextRegistry.get(node);\n\n if (map?.has(key)) return map.get(key) as T;\n }\n\n return NOT_FOUND_SENTINEL;\n};\n\n/** Cached ancestor-walk lookup shared by `inject()` and `injectStrict()`. */\nconst lookup = <T>(ctx: RuntimeContext, key: InjectionKey<T>): T | typeof NOT_FOUND_SENTINEL => {\n let cache = resolvedCache.get(ctx);\n\n if (!cache) {\n cache = new Map();\n resolvedCache.set(ctx, cache);\n }\n\n const cacheKey = key as InjectionKey<unknown>;\n\n if (!cache.has(cacheKey)) cache.set(cacheKey, walkAndFind(ctx.element, key));\n\n return cache.get(cacheKey) as T | typeof NOT_FOUND_SENTINEL;\n};\n\nexport function inject<T>(key: InjectionKey<T>): T | undefined;\nexport function inject<T>(key: InjectionKey<T>, fallback: T): T;\nexport function inject<T>(key: InjectionKey<T>, ...rest: [T?]): T | undefined {\n const found = lookup(requireSetupContext('inject'), key);\n\n if (found === NOT_FOUND_SENTINEL) return rest.length > 0 ? rest[0] : undefined;\n\n return found;\n}\n\nexport const injectStrict = <T>(key: InjectionKey<T>): T => {\n const ctx = requireSetupContext('injectStrict');\n const found = lookup(ctx, key);\n\n if (found !== NOT_FOUND_SENTINEL) return found;\n\n throw new OreApiError(ORE_ERRORS.injectStrictFailed(String(key), ctx.element.localName));\n};\n\n/** Create a unique typed context key with a diagnostic description. */\nexport function createContext<T>(description: string): InjectionKey<T> {\n return Symbol(description) as InjectionKey<T>;\n}\n"],"mappings":"A,iFAeA,IAAMK,EAAkB,IAAI,QAWtBC,EAAsB,GAAsC,CAChE,IAAMC,EAAuB,CAAC,EAC1BC,EAAoBC,EAExB,KAAOD,GACDA,aAAgB,aAAaD,EAAM,KAAKC,CAAI,EAGhD,EAAOA,EAAK,aAAeA,aAAgB,WAAaA,EAAK,KAAO,MAGtE,OAAOD,CACT,EAMMG,GAAuB,EAAiB,EAAsB,IAAmB,CACrF,IAAMC,EAAMN,EAAgB,IAAIO,CAAE,GAAK,IAAI,IAMvCD,EAAI,IAAIE,CAAG,GACR,GAAuCD,EAAG,UAA1C,EAGPD,EAAI,IAAIE,EAAKC,CAAK,EAClBT,EAAgB,IAAIO,EAAID,CAAG,CAC7B,EAUaI,GAAc,EAAsB,IAAmB,CAClE,IAAMH,EAAKZ,EAAAA,oBAAoB,SAAS,CAAC,CAAC,QAE1CU,EAAiBE,EAAIC,EAAKC,CAAK,EAE/BV,EAAA,cAAgB,CACd,IAAMO,EAAMN,EAAgB,IAAIO,CAAE,EAE7BD,IAELA,EAAI,OAAOE,CAAG,EAEVF,EAAI,OAAS,GAAGN,EAAgB,OAAOO,CAAE,EAC/C,CAAC,CACH,EAEMI,EAAqB,OAAO,kBAAkB,EAG9CC,EAAgB,IAAI,QAEpBC,GAAkB,EAAsB,IAAwD,CACpG,IAAMX,EAAQD,EAAmBa,CAAO,EAExC,IAAK,IAAMX,KAAQD,EAAO,CACxB,IAAMI,EAAMN,EAAgB,IAAIG,CAAI,EAEpC,GAAIG,GAAK,IAAIE,CAAG,EAAG,OAAOF,EAAI,IAAIE,CAAG,CACvC,CAEA,OAAOG,CACT,EAGMI,GAAa,EAAqB,IAAwD,CAC9F,IAAIC,EAAQJ,EAAc,IAAIK,CAAG,EAE5BD,IACH,EAAQ,IAAI,IACZJ,EAAc,IAAIK,EAAKD,CAAK,GAG9B,IAAME,EAAWV,EAIjB,OAFKQ,EAAM,IAAIE,CAAQ,GAAGF,EAAM,IAAIE,EAAUL,EAAYI,EAAI,QAAST,CAAG,CAAC,EAEpEQ,EAAM,IAAIE,CAAQ,CAC3B,EAIA,SAAgBC,EAAU,EAAsB,GAAGC,EAA2B,CAC5E,IAAMC,EAAQN,EAAOpB,EAAAA,oBAAoB,QAAQ,EAAGa,CAAG,EAIvD,OAFIa,IAAUV,EAA2BS,EAAK,OAAS,EAAIA,EAAK,GAAK,IAAA,GAE9DC,CACT,CAEA,IAAaC,EAAmB,GAA4B,CAC1D,IAAML,EAAMtB,EAAAA,oBAAoB,cAAc,EACxC0B,EAAQN,EAAOE,EAAKT,CAAG,EAE7B,GAAIa,IAAUV,EAAoB,OAAOU,EAEzC,MAAM,IAAIzB,EAAAA,YAAYC,EAAAA,WAAW,mBAAmB,OAAOW,CAAG,EAAGS,EAAI,QAAQ,SAAS,CAAC,CACzF,EAGA,SAAgBM,EAAiB,EAAsC,CACrE,OAAO,OAAOC,CAAW,CAC3B,C"}
|
package/dist/context.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import"./_dev.js";import{ORE_ERRORS as e,OreApiError as t}from"./errors.js";import{onCleanup as n,requireSetupContext as r}from"./runtime.js";var i
|
|
1
|
+
import"./_dev.js";import{ORE_ERRORS as e,OreApiError as t}from"./errors.js";import{onCleanup as n,requireSetupContext as r}from"./runtime.js";var i=/* @__PURE__ */ new WeakMap,a=e=>{let t=[],n=e;for(;n;)n instanceof HTMLElement&&t.push(n),n=n.parentNode??(n instanceof ShadowRoot?n.host:null);return t},o=(e,t,n)=>{let r=i.get(e)??/* @__PURE__ */ new Map;r.has(t)&&`${e.localName}`,r.set(t,n),i.set(e,r)},s=(e,t)=>{let a=r(`provide`).element;o(a,e,t),n(()=>{let t=i.get(a);t&&(t.delete(e),t.size===0&&i.delete(a))})},c=Symbol(`inject.not_found`),l=/* @__PURE__ */ new WeakMap,u=(e,t)=>{let n=a(e);for(let e of n){let n=i.get(e);if(n?.has(t))return n.get(t)}return c},d=(e,t)=>{let n=l.get(e);n||(n=/* @__PURE__ */ new Map,l.set(e,n));let r=t;return n.has(r)||n.set(r,u(e.element,t)),n.get(r)};function f(e,...t){let n=d(r(`inject`),e);return n===c?t.length>0?t[0]:void 0:n}var p=n=>{let i=r(`injectStrict`),a=d(i,n);if(a!==c)return a;throw new t(e.injectStrictFailed(String(n),i.element.localName))};function m(e){return Symbol(e)}export{m as createContext,f as inject,p as injectStrict,s as provide};
|
|
2
2
|
//# sourceMappingURL=context.js.map
|
package/dist/context.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","names":[],"sources":["../src/context.ts"],"sourcesContent":["/**\n * Component context injection API: `inject` / `injectStrict` / `provide` / `createContext`.\n *\n * Context values are stored on the providing element via a WeakMap registry and\n * resolved by walking up the DOM tree (including through shadow boundaries).\n * Providing is done via `provide(key, value)` inside `setup()`.\n *\n * Each `createContext()` call creates a unique key. Descriptions are diagnostic\n * labels only and never determine identity.\n */\n\nimport { warn } from './_dev';\nimport { ORE_ERRORS, OreApiError } from './errors';\nimport { onCleanup, type RuntimeContext, requireSetupContext } from './runtime';\n\nconst contextRegistry = new WeakMap<HTMLElement, Map<InjectionKey<unknown>, unknown>>();\n\nexport type InjectionKey<T> = symbol & {\n readonly __ore_injection_key?: T;\n};\n\n/**\n * Build a linear ancestor chain (including shadow host boundaries).\n * Walks `parentNode` (not `parentElement`) so non-HTML intermediate parents\n * (e.g. an `SVGElement` between child and provider) don't break the chain.\n */\nconst buildAncestorChain = (start: HTMLElement): HTMLElement[] => {\n const chain: HTMLElement[] = [];\n let node: Node | null = start;\n\n while (node) {\n if (node instanceof HTMLElement) chain.push(node);\n\n // A ShadowRoot's parentNode is null: hop to its host to keep walking.\n node = node.parentNode ?? (node instanceof ShadowRoot ? node.host : null);\n }\n\n return chain;\n};\n\n/**\n * Register a context value on a specific element.\n * @internal Backs the public `provide()`: do not call directly.\n */\nconst provideOnElement = <T>(el: HTMLElement, key: InjectionKey<T>, value: T): void => {\n const map = contextRegistry.get(el) ?? new Map<InjectionKey<unknown>, unknown>();\n\n // `inject()` memoizes its result per consumer (see resolvedCache below), so a\n // provider swapping the raw value after a descendant already read it would be\n // silently ignored downstream. Provide a `Readable` (signal/computed) instead\n // of a raw value so descendants observe updates through the value itself.\n if (map.has(key)) {\n warn(`provide(): key already provided on <${el.localName}>: overwriting. Provide a Readable to update it instead.`);\n }\n\n map.set(key, value);\n contextRegistry.set(el, map);\n};\n\n/**\n * Register a context value on the current component's host element, making it\n * available to descendant components via `inject(key)`.\n *\n * Provide a `Readable` (signal/computed) rather than a raw value if descendants\n * need to observe later changes: `inject()` resolves and caches the value once\n * per consumer, so re-calling `provide()` with a new raw value later is not seen.\n */\nexport const provide = <T>(key: InjectionKey<T>, value: T): void => {\n const el = requireSetupContext('provide').element;\n\n provideOnElement(el, key, value);\n\n onCleanup(() => {\n const map = contextRegistry.get(el);\n\n if (!map) return;\n\n map.delete(key);\n\n if (map.size === 0) contextRegistry.delete(el);\n });\n};\n\nconst NOT_FOUND_SENTINEL = Symbol('inject.not_found');\n\n/** Per-setup-context cache: avoids repeated ancestor walks for the same key. */\nconst resolvedCache = new WeakMap<object, Map<InjectionKey<unknown>, unknown>>();\n\nconst walkAndFind = <T>(element: HTMLElement, key: InjectionKey<T>): T | typeof NOT_FOUND_SENTINEL => {\n const chain = buildAncestorChain(element);\n\n for (const node of chain) {\n const map = contextRegistry.get(node);\n\n if (map?.has(key)) return map.get(key) as T;\n }\n\n return NOT_FOUND_SENTINEL;\n};\n\n/** Cached ancestor-walk lookup shared by `inject()` and `injectStrict()`. */\nconst lookup = <T>(ctx: RuntimeContext, key: InjectionKey<T>): T | typeof NOT_FOUND_SENTINEL => {\n let cache = resolvedCache.get(ctx);\n\n if (!cache) {\n cache = new Map();\n resolvedCache.set(ctx, cache);\n }\n\n const cacheKey = key as InjectionKey<unknown>;\n\n if (!cache.has(cacheKey)) cache.set(cacheKey, walkAndFind(ctx.element, key));\n\n return cache.get(cacheKey) as T | typeof NOT_FOUND_SENTINEL;\n};\n\nexport function inject<T>(key: InjectionKey<T>): T | undefined;\nexport function inject<T>(key: InjectionKey<T>, fallback: T): T;\nexport function inject<T>(key: InjectionKey<T>, ...rest: [T?]): T | undefined {\n const found = lookup(requireSetupContext('inject'), key);\n\n if (found === NOT_FOUND_SENTINEL) return rest.length > 0 ? rest[0] : undefined;\n\n return found;\n}\n\nexport const injectStrict = <T>(key: InjectionKey<T>): T => {\n const ctx = requireSetupContext('injectStrict');\n const found = lookup(ctx, key);\n\n if (found !== NOT_FOUND_SENTINEL) return found;\n\n throw new OreApiError(ORE_ERRORS.injectStrictFailed(String(key), ctx.element.localName));\n};\n\n/** Create a unique typed context key with a diagnostic description. */\nexport function createContext<T>(description: string): InjectionKey<T> {\n return Symbol(description) as InjectionKey<T>;\n}\n"],"mappings":"8IAeA,
|
|
1
|
+
{"version":3,"file":"context.js","names":["contextRegistry","buildAncestorChain","chain","node","start","provideOnElement","map","el","key","value","provide","requireSetupContext","onCleanup","NOT_FOUND_SENTINEL","resolvedCache","walkAndFind","element","lookup","cache","ctx","cacheKey","inject","rest","found","injectStrict","OreApiError","ORE_ERRORS","createContext","description"],"sources":["../src/context.ts"],"sourcesContent":["/**\n * Component context injection API: `inject` / `injectStrict` / `provide` / `createContext`.\n *\n * Context values are stored on the providing element via a WeakMap registry and\n * resolved by walking up the DOM tree (including through shadow boundaries).\n * Providing is done via `provide(key, value)` inside `setup()`.\n *\n * Each `createContext()` call creates a unique key. Descriptions are diagnostic\n * labels only and never determine identity.\n */\n\nimport { warn } from './_dev';\nimport { ORE_ERRORS, OreApiError } from './errors';\nimport { onCleanup, type RuntimeContext, requireSetupContext } from './runtime';\n\nconst contextRegistry = new WeakMap<HTMLElement, Map<InjectionKey<unknown>, unknown>>();\n\nexport type InjectionKey<T> = symbol & {\n readonly __ore_injection_key?: T;\n};\n\n/**\n * Build a linear ancestor chain (including shadow host boundaries).\n * Walks `parentNode` (not `parentElement`) so non-HTML intermediate parents\n * (e.g. an `SVGElement` between child and provider) don't break the chain.\n */\nconst buildAncestorChain = (start: HTMLElement): HTMLElement[] => {\n const chain: HTMLElement[] = [];\n let node: Node | null = start;\n\n while (node) {\n if (node instanceof HTMLElement) chain.push(node);\n\n // A ShadowRoot's parentNode is null: hop to its host to keep walking.\n node = node.parentNode ?? (node instanceof ShadowRoot ? node.host : null);\n }\n\n return chain;\n};\n\n/**\n * Register a context value on a specific element.\n * @internal Backs the public `provide()`: do not call directly.\n */\nconst provideOnElement = <T>(el: HTMLElement, key: InjectionKey<T>, value: T): void => {\n const map = contextRegistry.get(el) ?? new Map<InjectionKey<unknown>, unknown>();\n\n // `inject()` memoizes its result per consumer (see resolvedCache below), so a\n // provider swapping the raw value after a descendant already read it would be\n // silently ignored downstream. Provide a `Readable` (signal/computed) instead\n // of a raw value so descendants observe updates through the value itself.\n if (map.has(key)) {\n warn(`provide(): key already provided on <${el.localName}>: overwriting. Provide a Readable to update it instead.`);\n }\n\n map.set(key, value);\n contextRegistry.set(el, map);\n};\n\n/**\n * Register a context value on the current component's host element, making it\n * available to descendant components via `inject(key)`.\n *\n * Provide a `Readable` (signal/computed) rather than a raw value if descendants\n * need to observe later changes: `inject()` resolves and caches the value once\n * per consumer, so re-calling `provide()` with a new raw value later is not seen.\n */\nexport const provide = <T>(key: InjectionKey<T>, value: T): void => {\n const el = requireSetupContext('provide').element;\n\n provideOnElement(el, key, value);\n\n onCleanup(() => {\n const map = contextRegistry.get(el);\n\n if (!map) return;\n\n map.delete(key);\n\n if (map.size === 0) contextRegistry.delete(el);\n });\n};\n\nconst NOT_FOUND_SENTINEL = Symbol('inject.not_found');\n\n/** Per-setup-context cache: avoids repeated ancestor walks for the same key. */\nconst resolvedCache = new WeakMap<object, Map<InjectionKey<unknown>, unknown>>();\n\nconst walkAndFind = <T>(element: HTMLElement, key: InjectionKey<T>): T | typeof NOT_FOUND_SENTINEL => {\n const chain = buildAncestorChain(element);\n\n for (const node of chain) {\n const map = contextRegistry.get(node);\n\n if (map?.has(key)) return map.get(key) as T;\n }\n\n return NOT_FOUND_SENTINEL;\n};\n\n/** Cached ancestor-walk lookup shared by `inject()` and `injectStrict()`. */\nconst lookup = <T>(ctx: RuntimeContext, key: InjectionKey<T>): T | typeof NOT_FOUND_SENTINEL => {\n let cache = resolvedCache.get(ctx);\n\n if (!cache) {\n cache = new Map();\n resolvedCache.set(ctx, cache);\n }\n\n const cacheKey = key as InjectionKey<unknown>;\n\n if (!cache.has(cacheKey)) cache.set(cacheKey, walkAndFind(ctx.element, key));\n\n return cache.get(cacheKey) as T | typeof NOT_FOUND_SENTINEL;\n};\n\nexport function inject<T>(key: InjectionKey<T>): T | undefined;\nexport function inject<T>(key: InjectionKey<T>, fallback: T): T;\nexport function inject<T>(key: InjectionKey<T>, ...rest: [T?]): T | undefined {\n const found = lookup(requireSetupContext('inject'), key);\n\n if (found === NOT_FOUND_SENTINEL) return rest.length > 0 ? rest[0] : undefined;\n\n return found;\n}\n\nexport const injectStrict = <T>(key: InjectionKey<T>): T => {\n const ctx = requireSetupContext('injectStrict');\n const found = lookup(ctx, key);\n\n if (found !== NOT_FOUND_SENTINEL) return found;\n\n throw new OreApiError(ORE_ERRORS.injectStrictFailed(String(key), ctx.element.localName));\n};\n\n/** Create a unique typed context key with a diagnostic description. */\nexport function createContext<T>(description: string): InjectionKey<T> {\n return Symbol(description) as InjectionKey<T>;\n}\n"],"mappings":"A,8IAeA,IAAMA,kBAAkB,IAAI,QAWtBC,EAAsB,GAAsC,CAChE,IAAMC,EAAuB,CAAC,EAC1BC,EAAoBC,EAExB,KAAOD,GACDA,aAAgB,aAAaD,EAAM,KAAKC,CAAI,EAGhD,EAAOA,EAAK,aAAeA,aAAgB,WAAaA,EAAK,KAAO,MAGtE,OAAOD,CACT,EAMMG,GAAuB,EAAiB,EAAsB,IAAmB,CACrF,IAAMC,EAAMN,EAAgB,IAAIO,CAAE,mBAAK,IAAI,IAMvCD,EAAI,IAAIE,CAAG,GACR,GAAuCD,EAAG,UAA1C,EAGPD,EAAI,IAAIE,EAAKC,CAAK,EAClBT,EAAgB,IAAIO,EAAID,CAAG,CAC7B,EAUaI,GAAc,EAAsB,IAAmB,CAClE,IAAMH,EAAKI,EAAoB,SAAS,CAAC,CAAC,QAE1CN,EAAiBE,EAAIC,EAAKC,CAAK,EAE/BG,MAAgB,CACd,IAAMN,EAAMN,EAAgB,IAAIO,CAAE,EAE7BD,IAELA,EAAI,OAAOE,CAAG,EAEVF,EAAI,OAAS,GAAGN,EAAgB,OAAOO,CAAE,EAC/C,CAAC,CACH,EAEMM,EAAqB,OAAO,kBAAkB,EAG9CC,kBAAgB,IAAI,QAEpBC,GAAkB,EAAsB,IAAwD,CACpG,IAAMb,EAAQD,EAAmBe,CAAO,EAExC,IAAK,IAAMb,KAAQD,EAAO,CACxB,IAAMI,EAAMN,EAAgB,IAAIG,CAAI,EAEpC,GAAIG,GAAK,IAAIE,CAAG,EAAG,OAAOF,EAAI,IAAIE,CAAG,CACvC,CAEA,OAAOK,CACT,EAGMI,GAAa,EAAqB,IAAwD,CAC9F,IAAIC,EAAQJ,EAAc,IAAIK,CAAG,EAE5BD,IACH,kBAAQ,IAAI,IACZJ,EAAc,IAAIK,EAAKD,CAAK,GAG9B,IAAME,EAAWZ,EAIjB,OAFKU,EAAM,IAAIE,CAAQ,GAAGF,EAAM,IAAIE,EAAUL,EAAYI,EAAI,QAASX,CAAG,CAAC,EAEpEU,EAAM,IAAIE,CAAQ,CAC3B,EAIA,SAAgBC,EAAU,EAAsB,GAAGC,EAA2B,CAC5E,IAAMC,EAAQN,EAAON,EAAoB,QAAQ,EAAGH,CAAG,EAIvD,OAFIe,IAAUV,EAA2BS,EAAK,OAAS,EAAIA,EAAK,GAAK,IAAA,GAE9DC,CACT,CAEA,IAAaC,EAAmB,GAA4B,CAC1D,IAAML,EAAMR,EAAoB,cAAc,EACxCY,EAAQN,EAAOE,EAAKX,CAAG,EAE7B,GAAIe,IAAUV,EAAoB,OAAOU,EAEzC,MAAM,IAAIE,EAAYC,EAAW,mBAAmB,OAAOlB,CAAG,EAAGW,EAAI,QAAQ,SAAS,CAAC,CACzF,EAGA,SAAgBQ,EAAiB,EAAsC,CACrE,OAAO,OAAOC,CAAW,CAC3B,C"}
|
package/dist/define.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"define.cjs","names":[],"sources":["../src/define.ts"],"sourcesContent":["import { BaseElement } from './base-element';\nimport type { ComponentDefinition } from './component-types';\nimport { ORE_ERRORS, OreApiError } from './errors';\nimport { normalizePropDefinition, type PropInputDefs, type PropsDef, validatePropDefs } from './props';\nimport { toKebab } from './utils/dom';\n\n/**\n * Create and register a web component.\n *\n * The `setup` function runs for each connection and returns an `HTMLResult`.\n * Disconnecting disposes its state; reconnecting rebuilds it. All reactive\n * behaviour within a connection is expressed through directives inside the\n * template: not by re-evaluating setup itself.\n *\n * Everything besides `props`: lifecycle hooks, host bindings, context, slots,\n * emit: is a free function imported from `@vielzeug/ore` and called directly\n * from inside `setup()` (or from a composable it calls):\n *\n * ```ts\n * import { define, html, prop, onMounted, useEmit, useSlots } from '@vielzeug/ore';\n *\n * define<{ count?: number }>('my-counter', {\n * props: { count: prop.number(0) },\n * setup(props) {\n * const emit = useEmit<{ increment: number }>();\n * const slots = useSlots<'header' | 'footer'>();\n *\n * onMounted(() => console.log('mounted'));\n *\n * return html`<button @click=${() => emit('increment', props.count.value + 1)}>${props.count}</button>`;\n * },\n * });\n * ```\n *\n * Returns the registered class, so callers keep a handle for `instanceof` or\n * subclassing without a follow-up `customElements.get(tag)` cast.\n */\nexport function define<Props extends Record<string, unknown> = Record<never, never>>(\n tag: string,\n definition: ComponentDefinition<Props>,\n): CustomElementConstructor {\n if (!tag) throw new OreApiError(ORE_ERRORS.defineRequiresTag);\n if (customElements.get(tag)) throw new OreApiError(ORE_ERRORS.defineDuplicate(tag));\n\n const { props: propDefs } = definition;\n\n const normalizedPropDefs: PropsDef<Props> | undefined = (() => {\n if (!propDefs) return undefined;\n\n const errors = validatePropDefs(propDefs as Record<string, unknown>);\n\n if (errors.length > 0) throw new OreApiError(ORE_ERRORS.validationFailed(tag, errors));\n\n const normalized: PropInputDefs = {};\n\n for (const [key, def] of Object.entries(propDefs)) {\n normalized[key] = normalizePropDefinition(def, key);\n }\n\n return normalized as PropsDef<Props>;\n })();\n\n const observedAttrs = normalizedPropDefs ? Object.keys(normalizedPropDefs).map(toKebab) : [];\n\n const ComponentClass = class extends BaseElement {\n static override _definition = definition as unknown as ComponentDefinition;\n static override _normalizedPropDefs = normalizedPropDefs as PropsDef<Record<never, never>> | undefined;\n static override formAssociated = definition.formAssociated ?? false;\n static override observedAttributes = observedAttrs;\n };\n\n // Prop validation and class construction above are side-effect-free; registration\n // is intentionally the last, and only global side effect of define().\n Object.defineProperty(ComponentClass, 'name', { value: tag });\n customElements.define(tag, ComponentClass);\n\n return ComponentClass;\n}\n"],"mappings":"sHAqCA,
|
|
1
|
+
{"version":3,"file":"define.cjs","names":["OreApiError","ORE_ERRORS","validatePropDefs","normalizePropDefinition","toKebab","BaseElement","require_errors","require_dom","require_props","require_base_element","define","tag","propDefs","definition","normalizedPropDefs","errors","normalized","key","def","observedAttrs","ComponentClass"],"sources":["../src/define.ts"],"sourcesContent":["import { BaseElement } from './base-element';\nimport type { ComponentDefinition } from './component-types';\nimport { ORE_ERRORS, OreApiError } from './errors';\nimport { normalizePropDefinition, type PropInputDefs, type PropsDef, validatePropDefs } from './props';\nimport { toKebab } from './utils/dom';\n\n/**\n * Create and register a web component.\n *\n * The `setup` function runs for each connection and returns an `HTMLResult`.\n * Disconnecting disposes its state; reconnecting rebuilds it. All reactive\n * behaviour within a connection is expressed through directives inside the\n * template: not by re-evaluating setup itself.\n *\n * Everything besides `props`: lifecycle hooks, host bindings, context, slots,\n * emit: is a free function imported from `@vielzeug/ore` and called directly\n * from inside `setup()` (or from a composable it calls):\n *\n * ```ts\n * import { define, html, prop, onMounted, useEmit, useSlots } from '@vielzeug/ore';\n *\n * define<{ count?: number }>('my-counter', {\n * props: { count: prop.number(0) },\n * setup(props) {\n * const emit = useEmit<{ increment: number }>();\n * const slots = useSlots<'header' | 'footer'>();\n *\n * onMounted(() => console.log('mounted'));\n *\n * return html`<button @click=${() => emit('increment', props.count.value + 1)}>${props.count}</button>`;\n * },\n * });\n * ```\n *\n * Returns the registered class, so callers keep a handle for `instanceof` or\n * subclassing without a follow-up `customElements.get(tag)` cast.\n */\nexport function define<Props extends Record<string, unknown> = Record<never, never>>(\n tag: string,\n definition: ComponentDefinition<Props>,\n): CustomElementConstructor {\n if (!tag) throw new OreApiError(ORE_ERRORS.defineRequiresTag);\n if (customElements.get(tag)) throw new OreApiError(ORE_ERRORS.defineDuplicate(tag));\n\n const { props: propDefs } = definition;\n\n const normalizedPropDefs: PropsDef<Props> | undefined = (() => {\n if (!propDefs) return undefined;\n\n const errors = validatePropDefs(propDefs as Record<string, unknown>);\n\n if (errors.length > 0) throw new OreApiError(ORE_ERRORS.validationFailed(tag, errors));\n\n const normalized: PropInputDefs = {};\n\n for (const [key, def] of Object.entries(propDefs)) {\n normalized[key] = normalizePropDefinition(def, key);\n }\n\n return normalized as PropsDef<Props>;\n })();\n\n const observedAttrs = normalizedPropDefs ? Object.keys(normalizedPropDefs).map(toKebab) : [];\n\n const ComponentClass = class extends BaseElement {\n static override _definition = definition as unknown as ComponentDefinition;\n static override _normalizedPropDefs = normalizedPropDefs as PropsDef<Record<never, never>> | undefined;\n static override formAssociated = definition.formAssociated ?? false;\n static override observedAttributes = observedAttrs;\n };\n\n // Prop validation and class construction above are side-effect-free; registration\n // is intentionally the last, and only global side effect of define().\n Object.defineProperty(ComponentClass, 'name', { value: tag });\n customElements.define(tag, ComponentClass);\n\n return ComponentClass;\n}\n"],"mappings":"A,sHAqCA,SAAgBU,EACd,EACA,EAC0B,CAC1B,GAAI,CAACC,EAAK,MAAM,IAAIX,EAAAA,YAAYC,EAAAA,WAAW,iBAAiB,EAC5D,GAAI,eAAe,IAAIU,CAAG,EAAG,MAAM,IAAIX,EAAAA,YAAYC,EAAAA,WAAW,gBAAgBU,CAAG,CAAC,EAElF,GAAM,CAAE,MAAOC,GAAaC,EAEtBC,OAAyD,CAC7D,GAAI,CAACF,EAAU,OAEf,IAAMG,EAASb,EAAAA,iBAAiBU,CAAmC,EAEnE,GAAIG,EAAO,OAAS,EAAG,MAAM,IAAIf,EAAAA,YAAYC,EAAAA,WAAW,iBAAiBU,EAAKI,CAAM,CAAC,EAErF,IAAMC,EAA4B,CAAC,EAEnC,IAAK,GAAM,CAACC,EAAKC,KAAQ,OAAO,QAAQN,CAAQ,EAC9C,EAAWK,GAAOd,EAAAA,wBAAwBe,EAAKD,CAAG,EAGpD,OAAOD,CACT,EAAA,CAAG,EAEGG,EAAgBL,EAAqB,OAAO,KAAKA,CAAkB,CAAC,CAAC,IAAIV,EAAAA,OAAO,EAAI,CAAC,EAErFgB,EAAiB,cAAcf,EAAAA,WAAY,CAC/C,OAAgB,YAAcQ,EAC9B,OAAgB,oBAAsBC,EACtC,OAAgB,eAAiBD,EAAW,gBAAkB,GAC9D,OAAgB,mBAAqBM,CACvC,EAOA,OAHA,OAAO,eAAeC,EAAgB,OAAQ,CAAE,MAAOT,CAAI,CAAC,EAC5D,eAAe,OAAOA,EAAKS,CAAc,EAElCA,CACT,C"}
|
package/dist/define.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"define.js","names":[],"sources":["../src/define.ts"],"sourcesContent":["import { BaseElement } from './base-element';\nimport type { ComponentDefinition } from './component-types';\nimport { ORE_ERRORS, OreApiError } from './errors';\nimport { normalizePropDefinition, type PropInputDefs, type PropsDef, validatePropDefs } from './props';\nimport { toKebab } from './utils/dom';\n\n/**\n * Create and register a web component.\n *\n * The `setup` function runs for each connection and returns an `HTMLResult`.\n * Disconnecting disposes its state; reconnecting rebuilds it. All reactive\n * behaviour within a connection is expressed through directives inside the\n * template: not by re-evaluating setup itself.\n *\n * Everything besides `props`: lifecycle hooks, host bindings, context, slots,\n * emit: is a free function imported from `@vielzeug/ore` and called directly\n * from inside `setup()` (or from a composable it calls):\n *\n * ```ts\n * import { define, html, prop, onMounted, useEmit, useSlots } from '@vielzeug/ore';\n *\n * define<{ count?: number }>('my-counter', {\n * props: { count: prop.number(0) },\n * setup(props) {\n * const emit = useEmit<{ increment: number }>();\n * const slots = useSlots<'header' | 'footer'>();\n *\n * onMounted(() => console.log('mounted'));\n *\n * return html`<button @click=${() => emit('increment', props.count.value + 1)}>${props.count}</button>`;\n * },\n * });\n * ```\n *\n * Returns the registered class, so callers keep a handle for `instanceof` or\n * subclassing without a follow-up `customElements.get(tag)` cast.\n */\nexport function define<Props extends Record<string, unknown> = Record<never, never>>(\n tag: string,\n definition: ComponentDefinition<Props>,\n): CustomElementConstructor {\n if (!tag) throw new OreApiError(ORE_ERRORS.defineRequiresTag);\n if (customElements.get(tag)) throw new OreApiError(ORE_ERRORS.defineDuplicate(tag));\n\n const { props: propDefs } = definition;\n\n const normalizedPropDefs: PropsDef<Props> | undefined = (() => {\n if (!propDefs) return undefined;\n\n const errors = validatePropDefs(propDefs as Record<string, unknown>);\n\n if (errors.length > 0) throw new OreApiError(ORE_ERRORS.validationFailed(tag, errors));\n\n const normalized: PropInputDefs = {};\n\n for (const [key, def] of Object.entries(propDefs)) {\n normalized[key] = normalizePropDefinition(def, key);\n }\n\n return normalized as PropsDef<Props>;\n })();\n\n const observedAttrs = normalizedPropDefs ? Object.keys(normalizedPropDefs).map(toKebab) : [];\n\n const ComponentClass = class extends BaseElement {\n static override _definition = definition as unknown as ComponentDefinition;\n static override _normalizedPropDefs = normalizedPropDefs as PropsDef<Record<never, never>> | undefined;\n static override formAssociated = definition.formAssociated ?? false;\n static override observedAttributes = observedAttrs;\n };\n\n // Prop validation and class construction above are side-effect-free; registration\n // is intentionally the last, and only global side effect of define().\n Object.defineProperty(ComponentClass, 'name', { value: tag });\n customElements.define(tag, ComponentClass);\n\n return ComponentClass;\n}\n"],"mappings":"8NAqCA,
|
|
1
|
+
{"version":3,"file":"define.js","names":["define","tag","OreApiError","ORE_ERRORS","propDefs","definition","normalizedPropDefs","errors","validatePropDefs","normalized","key","def","normalizePropDefinition","observedAttrs","toKebab","ComponentClass","BaseElement"],"sources":["../src/define.ts"],"sourcesContent":["import { BaseElement } from './base-element';\nimport type { ComponentDefinition } from './component-types';\nimport { ORE_ERRORS, OreApiError } from './errors';\nimport { normalizePropDefinition, type PropInputDefs, type PropsDef, validatePropDefs } from './props';\nimport { toKebab } from './utils/dom';\n\n/**\n * Create and register a web component.\n *\n * The `setup` function runs for each connection and returns an `HTMLResult`.\n * Disconnecting disposes its state; reconnecting rebuilds it. All reactive\n * behaviour within a connection is expressed through directives inside the\n * template: not by re-evaluating setup itself.\n *\n * Everything besides `props`: lifecycle hooks, host bindings, context, slots,\n * emit: is a free function imported from `@vielzeug/ore` and called directly\n * from inside `setup()` (or from a composable it calls):\n *\n * ```ts\n * import { define, html, prop, onMounted, useEmit, useSlots } from '@vielzeug/ore';\n *\n * define<{ count?: number }>('my-counter', {\n * props: { count: prop.number(0) },\n * setup(props) {\n * const emit = useEmit<{ increment: number }>();\n * const slots = useSlots<'header' | 'footer'>();\n *\n * onMounted(() => console.log('mounted'));\n *\n * return html`<button @click=${() => emit('increment', props.count.value + 1)}>${props.count}</button>`;\n * },\n * });\n * ```\n *\n * Returns the registered class, so callers keep a handle for `instanceof` or\n * subclassing without a follow-up `customElements.get(tag)` cast.\n */\nexport function define<Props extends Record<string, unknown> = Record<never, never>>(\n tag: string,\n definition: ComponentDefinition<Props>,\n): CustomElementConstructor {\n if (!tag) throw new OreApiError(ORE_ERRORS.defineRequiresTag);\n if (customElements.get(tag)) throw new OreApiError(ORE_ERRORS.defineDuplicate(tag));\n\n const { props: propDefs } = definition;\n\n const normalizedPropDefs: PropsDef<Props> | undefined = (() => {\n if (!propDefs) return undefined;\n\n const errors = validatePropDefs(propDefs as Record<string, unknown>);\n\n if (errors.length > 0) throw new OreApiError(ORE_ERRORS.validationFailed(tag, errors));\n\n const normalized: PropInputDefs = {};\n\n for (const [key, def] of Object.entries(propDefs)) {\n normalized[key] = normalizePropDefinition(def, key);\n }\n\n return normalized as PropsDef<Props>;\n })();\n\n const observedAttrs = normalizedPropDefs ? Object.keys(normalizedPropDefs).map(toKebab) : [];\n\n const ComponentClass = class extends BaseElement {\n static override _definition = definition as unknown as ComponentDefinition;\n static override _normalizedPropDefs = normalizedPropDefs as PropsDef<Record<never, never>> | undefined;\n static override formAssociated = definition.formAssociated ?? false;\n static override observedAttributes = observedAttrs;\n };\n\n // Prop validation and class construction above are side-effect-free; registration\n // is intentionally the last, and only global side effect of define().\n Object.defineProperty(ComponentClass, 'name', { value: tag });\n customElements.define(tag, ComponentClass);\n\n return ComponentClass;\n}\n"],"mappings":"A,8NAqCA,SAAgBA,EACd,EACA,EAC0B,CAC1B,GAAI,CAACC,EAAK,MAAM,IAAIC,EAAYC,EAAW,iBAAiB,EAC5D,GAAI,eAAe,IAAIF,CAAG,EAAG,MAAM,IAAIC,EAAYC,EAAW,gBAAgBF,CAAG,CAAC,EAElF,GAAM,CAAE,MAAOG,GAAaC,EAEtBC,OAAyD,CAC7D,GAAI,CAACF,EAAU,OAEf,IAAMG,EAASC,EAAiBJ,CAAmC,EAEnE,GAAIG,EAAO,OAAS,EAAG,MAAM,IAAIL,EAAYC,EAAW,iBAAiBF,EAAKM,CAAM,CAAC,EAErF,IAAME,EAA4B,CAAC,EAEnC,IAAK,GAAM,CAACC,EAAKC,KAAQ,OAAO,QAAQP,CAAQ,EAC9C,EAAWM,GAAOE,EAAwBD,EAAKD,CAAG,EAGpD,OAAOD,CACT,EAAA,CAAG,EAEGI,EAAgBP,EAAqB,OAAO,KAAKA,CAAkB,CAAC,CAAC,IAAIQ,CAAO,EAAI,CAAC,EAErFC,EAAiB,cAAcC,CAAY,CAC/C,OAAgB,YAAcX,EAC9B,OAAgB,oBAAsBC,EACtC,OAAgB,eAAiBD,EAAW,gBAAkB,GAC9D,OAAgB,mBAAqBQ,CACvC,EAOA,OAHA,OAAO,eAAeE,EAAgB,OAAQ,CAAE,MAAOd,CAAI,CAAC,EAC5D,eAAe,OAAOA,EAAKc,CAAc,EAElCA,CACT,C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"classMap.cjs","names":[],"sources":["../../src/directives/classMap.ts"],"sourcesContent":["import { computed, type Readable } from '@vielzeug/ripple';\n\nimport { type MaybeReactive, resolveClassTokens } from '../utils/dom';\n\n/**\n * Produces a reactive string of class names from an object map.\n *\n * Each key is a class name. Its value may be:\n * - a static `boolean`\n * - a `Signal<boolean>`\n * - a getter `() => boolean`\n *\n * Returns a `Reactive<string>` that can be used directly in a template\n * class attribute:\n *\n * @example\n * ```ts\n * html`<div class=\"${classMap({ active: isActive, hidden: () => !isVisible.value })}\"></div>`\n * ```\n */\nexport const classMap = (map: Record<string, MaybeReactive<boolean>>): Readable<string> => {\n return computed(() => [...resolveClassTokens(map)].join(' '));\n};\n"],"mappings":"sEAoBA,
|
|
1
|
+
{"version":3,"file":"classMap.cjs","names":["computed","resolveClassTokens","require_dom","_vielzeug_ripple","classMap","map"],"sources":["../../src/directives/classMap.ts"],"sourcesContent":["import { computed, type Readable } from '@vielzeug/ripple';\n\nimport { type MaybeReactive, resolveClassTokens } from '../utils/dom';\n\n/**\n * Produces a reactive string of class names from an object map.\n *\n * Each key is a class name. Its value may be:\n * - a static `boolean`\n * - a `Signal<boolean>`\n * - a getter `() => boolean`\n *\n * Returns a `Reactive<string>` that can be used directly in a template\n * class attribute:\n *\n * @example\n * ```ts\n * html`<div class=\"${classMap({ active: isActive, hidden: () => !isVisible.value })}\"></div>`\n * ```\n */\nexport const classMap = (map: Record<string, MaybeReactive<boolean>>): Readable<string> => {\n return computed(() => [...resolveClassTokens(map)].join(' '));\n};\n"],"mappings":"A,sEAoBA,IAAaI,EAAY,IACvB,EAAOJ,EAAAA,SAAAA,KAAe,CAAC,GAAGC,EAAAA,mBAAmBI,CAAG,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"classMap.js","names":[],"sources":["../../src/directives/classMap.ts"],"sourcesContent":["import { computed, type Readable } from '@vielzeug/ripple';\n\nimport { type MaybeReactive, resolveClassTokens } from '../utils/dom';\n\n/**\n * Produces a reactive string of class names from an object map.\n *\n * Each key is a class name. Its value may be:\n * - a static `boolean`\n * - a `Signal<boolean>`\n * - a getter `() => boolean`\n *\n * Returns a `Reactive<string>` that can be used directly in a template\n * class attribute:\n *\n * @example\n * ```ts\n * html`<div class=\"${classMap({ active: isActive, hidden: () => !isVisible.value })}\"></div>`\n * ```\n */\nexport const classMap = (map: Record<string, MaybeReactive<boolean>>): Readable<string> => {\n return computed(() => [...resolveClassTokens(map)].join(' '));\n};\n"],"mappings":"iGAoBA,
|
|
1
|
+
{"version":3,"file":"classMap.js","names":["classMap","computed","resolveClassTokens","map"],"sources":["../../src/directives/classMap.ts"],"sourcesContent":["import { computed, type Readable } from '@vielzeug/ripple';\n\nimport { type MaybeReactive, resolveClassTokens } from '../utils/dom';\n\n/**\n * Produces a reactive string of class names from an object map.\n *\n * Each key is a class name. Its value may be:\n * - a static `boolean`\n * - a `Signal<boolean>`\n * - a getter `() => boolean`\n *\n * Returns a `Reactive<string>` that can be used directly in a template\n * class attribute:\n *\n * @example\n * ```ts\n * html`<div class=\"${classMap({ active: isActive, hidden: () => !isVisible.value })}\"></div>`\n * ```\n */\nexport const classMap = (map: Record<string, MaybeReactive<boolean>>): Readable<string> => {\n return computed(() => [...resolveClassTokens(map)].join(' '));\n};\n"],"mappings":"A,iGAoBA,IAAaA,EAAY,GAChBC,MAAe,CAAC,GAAGC,EAAmBC,CAAG,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"each.cjs","names":[],"sources":["../../src/directives/each.ts"],"sourcesContent":["import {\n batch,\n computed,\n createScope,\n type Readable,\n effect as rawEffect,\n type Scope,\n type Signal,\n signal,\n untrack,\n} from '@vielzeug/ripple';\n\nimport { invariant, ORE_ERRORS, OreApiError, OreLifecycleError, reportRuntimeError } from '../errors';\nimport { createDirectiveResult, type DirectiveResult, type HTMLResult } from '../template/result';\nimport { type MaybeReactive, removeNodes, runAll } from '../utils/dom';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\ntype MaybeReactiveArray<T> = MaybeReactive<T[]>;\ntype ItemKey = number | string;\n\ntype ItemEntry<T> = {\n cleanups: (() => void)[];\n data: Signal<T>;\n index: Signal<number>;\n nodes: Node[];\n scope: Scope;\n};\n\n// ─── Item lifecycle ───────────────────────────────────────────────────────────\n\nconst createItem = <T>(\n item: T,\n index: number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n parent: ParentNode,\n insertBefore: Node,\n): ItemEntry<T> => {\n const dataSignal: Signal<T> = signal(item);\n const indexSignal: Signal<number> = signal(index);\n const scope = createScope();\n const cleanups: (() => void)[] = [];\n let nodes: Node[] = [];\n\n scope.run(() => {\n const result = render(dataSignal, indexSignal);\n\n nodes = result.mount(parent, insertBefore, (fn) => cleanups.push(fn));\n });\n\n return { cleanups, data: dataSignal, index: indexSignal, nodes, scope };\n};\n\nconst removeItem = <T>(entry: ItemEntry<T>): void => {\n entry.scope.dispose();\n runAll(entry.cleanups);\n removeNodes(entry.nodes);\n};\n\n// ─── Reconciler ───────────────────────────────────────────────────────────────\n\n/**\n * Reconciles the live item map against the next array. The pass is transactional:\n * new entries are created first, existing entries are updated in-place via signal\n * writes, and stale entries are destroyed only after the whole pass has succeeded.\n * If creation throws (user render code), the caller rolls back the entries created\n * during this pass and the previous list state survives: the last good render stays\n * on screen. Returns the ordered list of entries matching nextList.\n */\nconst reconcileItems = <T>(\n itemsMap: Map<ItemKey, ItemEntry<T>>,\n next: T[],\n nextKeys: ItemKey[],\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n parent: ParentNode,\n endMarker: Node,\n createdThisPass: ItemEntry<T>[],\n): ItemEntry<T>[] => {\n // Phase 1: create every missing entry. This is the only step that can throw\n // (user render code), and it records what it created so a failure is undoable.\n for (let i = 0; i < next.length; i++) {\n const key = nextKeys[i];\n\n if (itemsMap.has(key)) continue;\n\n const entry = untrack(() => createItem(next[i], i, render, parent, endMarker));\n\n createdThisPass.push(entry);\n itemsMap.set(key, entry);\n }\n\n // Phase 2: ordered list + in-place updates for reused entries (signal writes).\n const nextOrdered: ItemEntry<T>[] = [];\n\n batch(() => {\n for (let i = 0; i < next.length; i++) {\n const entry = itemsMap.get(nextKeys[i]);\n\n invariant(entry, 'each() reconcile phase 2 lost an entry created in phase 1');\n\n entry.data.value = next[i];\n entry.index.value = i;\n nextOrdered.push(entry);\n }\n });\n\n // DOM ordering: right-to-left pass: move any item not already adjacent to cursor.\n // An item is in place when its last node directly precedes the cursor; items may\n // span several nodes (whitespace text around an element), so compare the last one.\n // Moving an already-placed item would disconnect and reconnect its custom elements.\n // O(n) DOM operations in the worst case; optimal for the typical small list sizes\n // encountered in UI components (tabs, options, menu items).\n let cursor: Node = endMarker;\n\n for (let j = nextOrdered.length - 1; j >= 0; j--) {\n const entry = nextOrdered[j];\n const firstNode = entry.nodes[0];\n const lastNode = entry.nodes[entry.nodes.length - 1];\n\n if (firstNode && lastNode !== cursor.previousSibling) {\n for (const node of entry.nodes) parent.insertBefore(node, cursor);\n }\n\n cursor = firstNode ?? cursor;\n }\n\n // Phase 3: commit: destroy stale entries only now that the pass has succeeded.\n const nextKeySet = new Set(nextKeys);\n\n for (const [key, entry] of itemsMap) {\n if (!nextKeySet.has(key)) {\n removeItem(entry);\n itemsMap.delete(key);\n }\n }\n\n return nextOrdered;\n};\n\n/** Undo a failed reconcile pass: destroy entries created during it, restoring the last good list. */\nconst rollbackCreated = <T>(itemsMap: Map<ItemKey, ItemEntry<T>>, created: ItemEntry<T>[]): void => {\n for (const entry of created) {\n removeItem(entry);\n\n for (const [key, candidate] of itemsMap) {\n if (candidate === entry) {\n itemsMap.delete(key);\n break;\n }\n }\n }\n};\n\nconst collectKeys = <T>(next: T[], keyFn: (item: T, index: number) => ItemKey): ItemKey[] => {\n const keys: ItemKey[] = [];\n const seen = new Set<ItemKey>();\n\n for (let i = 0; i < next.length; i++) {\n const key = keyFn(next[i], i);\n\n if (seen.has(key)) throw new OreApiError(ORE_ERRORS.eachDuplicateKey(String(key), i));\n\n seen.add(key);\n keys.push(key);\n }\n\n return keys;\n};\n\nconst reportEachError = (error: unknown, anchor: Comment): void => {\n const cause = error instanceof Error ? error : new Error(String(error));\n\n reportRuntimeError(\n new OreLifecycleError(`each() failed to reconcile a list update: ${cause.message}`, {\n cause,\n component: 'each()',\n phase: 'each-reconcile',\n }),\n anchor,\n );\n};\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Renders a keyed list of items as a `DirectiveResult`.\n *\n * Each item is rendered by the provided render function.\n * Items are reused by key when the list changes; only stale items are destroyed.\n *\n * The render function receives a `Readable<T>` signal and a `Readable<number>` index\n * signal. Use `item.value` to read the current item inside reactive expressions:\n *\n * ```ts\n * html`${each(items, (item) => item.id, (item) => html`<li>${() => item.value.name}</li>`)}`\n * ```\n *\n * **Plain array:** when a plain `T[]` is passed (not a signal or getter), it is\n * treated as a one-time static render. Mutations to the original array are not\n * tracked. Pass a `Signal<T[]>` or `() => T[]` for reactive lists.\n *\n * **Optional fallback:** the fourth argument renders when the list is empty.\n *\n * **Key choice:** pass a stable item identifier (e.g. `item.id`), never the\n * array index: an index-based key reassigns to a different item whenever the\n * list is reordered or an item is inserted/removed before it, causing full\n * item teardown/recreation instead of the in-place update `each()` is built\n * for.\n *\n * **Duplicate keys:** a reconciliation failure (e.g. duplicate keys, see `eachDuplicateKey`)\n * does not throw past this function: an uncaught exception inside the reactive effect that\n * drives `each()` would risk corrupting unrelated effects scheduled in the same update batch.\n * Instead the last valid list is preserved and the failure is reported via the `ore:error` DOM event\n * (see `OreLifecycleError`, phase `'each-reconcile'`) plus a dev-only console log: listen for\n * `ore:error` on `document`/`window` to observe this in every build, including production.\n */\nexport function each<T>(\n list: MaybeReactiveArray<T>,\n keyFn: (item: T, index: number) => string | number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n fallback?: () => HTMLResult,\n): DirectiveResult {\n const listSignal = Array.isArray(list)\n ? signal(list as T[])\n : typeof list === 'function'\n ? computed(list as () => T[])\n : list;\n\n return createDirectiveResult((anchor, registerCleanup) => {\n const parent = anchor.parentNode;\n\n invariant(parent, 'each() anchor comment has no parent node');\n\n const endMarker = document.createComment('each/end');\n\n parent.insertBefore(endMarker, anchor.nextSibling);\n\n let itemsMap = new Map<ItemKey, ItemEntry<T>>();\n let itemsOrdered: ItemEntry<T>[] = [];\n let fallbackNodes: Node[] | null = null;\n let fallbackCleanups: (() => void)[] = [];\n\n const mountFallback = (): void => {\n if (!fallback) return;\n\n const result = fallback();\n\n fallbackNodes = result.mount(parent, endMarker, (fn) => fallbackCleanups.push(fn));\n };\n\n const clearFallback = (): void => {\n if (fallbackNodes) {\n runAll(fallbackCleanups);\n removeNodes(fallbackNodes);\n fallbackNodes = null;\n fallbackCleanups = [];\n }\n };\n\n const sub = rawEffect(() => {\n const nextList = listSignal.value ?? [];\n\n if (nextList.length === 0) {\n for (const entry of untrack(() => itemsOrdered)) removeItem(entry);\n itemsMap = new Map();\n itemsOrdered = [];\n\n if (!fallbackNodes) untrack(mountFallback);\n\n return;\n }\n\n let nextKeys: ItemKey[];\n\n try {\n nextKeys = untrack(() => collectKeys(nextList, keyFn));\n } catch (error) {\n reportEachError(error, anchor);\n return;\n }\n\n clearFallback();\n\n const createdThisPass: ItemEntry<T>[] = [];\n\n try {\n itemsOrdered = untrack(() =>\n reconcileItems(itemsMap, nextList, nextKeys, render, parent, endMarker, createdThisPass),\n );\n } catch (error) {\n // Undo this pass instead of tearing the list down: entries created before the\n // throw are removed, everything that was on screen stays exactly as it was.\n reportEachError(error, anchor);\n untrack(() => rollbackCreated(itemsMap, createdThisPass));\n }\n });\n\n registerCleanup(() => sub.dispose());\n registerCleanup(() => {\n clearFallback();\n for (const entry of itemsOrdered) removeItem(entry);\n endMarker.remove();\n });\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"each.cjs","names":["signal","createScope","untrack","OreApiError","ORE_ERRORS","OreLifecycleError","computed","createDirectiveResult","rawEffect","require_errors","require_dom","require_result","_vielzeug_ripple","createItem","dataSignal","item","indexSignal","index","scope","cleanups","nodes","render","parent","insertBefore","fn","removeItem","entry","reconcileItems","i","next","key","nextKeys","itemsMap","endMarker","createdThisPass","nextOrdered","cursor","j","firstNode","lastNode","node","nextKeySet","rollbackCreated","created","candidate","collectKeys","keys","seen","keyFn","reportEachError","cause","error","anchor","each","listSignal","list","itemsOrdered","fallbackNodes","fallbackCleanups","mountFallback","fallback","clearFallback","sub","nextList","registerCleanup"],"sources":["../../src/directives/each.ts"],"sourcesContent":["import {\n batch,\n computed,\n createScope,\n type Readable,\n effect as rawEffect,\n type Scope,\n type Signal,\n signal,\n untrack,\n} from '@vielzeug/ripple';\n\nimport { invariant, ORE_ERRORS, OreApiError, OreLifecycleError, reportRuntimeError } from '../errors';\nimport { createDirectiveResult, type DirectiveResult, type HTMLResult } from '../template/result';\nimport { type MaybeReactive, removeNodes, runAll } from '../utils/dom';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\ntype MaybeReactiveArray<T> = MaybeReactive<T[]>;\ntype ItemKey = number | string;\n\ntype ItemEntry<T> = {\n cleanups: (() => void)[];\n data: Signal<T>;\n index: Signal<number>;\n nodes: Node[];\n scope: Scope;\n};\n\n// ─── Item lifecycle ───────────────────────────────────────────────────────────\n\nconst createItem = <T>(\n item: T,\n index: number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n parent: ParentNode,\n insertBefore: Node,\n): ItemEntry<T> => {\n const dataSignal: Signal<T> = signal(item);\n const indexSignal: Signal<number> = signal(index);\n const scope = createScope();\n const cleanups: (() => void)[] = [];\n let nodes: Node[] = [];\n\n scope.run(() => {\n const result = render(dataSignal, indexSignal);\n\n nodes = result.mount(parent, insertBefore, (fn) => cleanups.push(fn));\n });\n\n return { cleanups, data: dataSignal, index: indexSignal, nodes, scope };\n};\n\nconst removeItem = <T>(entry: ItemEntry<T>): void => {\n entry.scope.dispose();\n runAll(entry.cleanups);\n removeNodes(entry.nodes);\n};\n\n// ─── Reconciler ───────────────────────────────────────────────────────────────\n\n/**\n * Reconciles the live item map against the next array. The pass is transactional:\n * new entries are created first, existing entries are updated in-place via signal\n * writes, and stale entries are destroyed only after the whole pass has succeeded.\n * If creation throws (user render code), the caller rolls back the entries created\n * during this pass and the previous list state survives: the last good render stays\n * on screen. Returns the ordered list of entries matching nextList.\n */\nconst reconcileItems = <T>(\n itemsMap: Map<ItemKey, ItemEntry<T>>,\n next: T[],\n nextKeys: ItemKey[],\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n parent: ParentNode,\n endMarker: Node,\n createdThisPass: ItemEntry<T>[],\n): ItemEntry<T>[] => {\n // Phase 1: create every missing entry. This is the only step that can throw\n // (user render code), and it records what it created so a failure is undoable.\n for (let i = 0; i < next.length; i++) {\n const key = nextKeys[i];\n\n if (itemsMap.has(key)) continue;\n\n const entry = untrack(() => createItem(next[i], i, render, parent, endMarker));\n\n createdThisPass.push(entry);\n itemsMap.set(key, entry);\n }\n\n // Phase 2: ordered list + in-place updates for reused entries (signal writes).\n const nextOrdered: ItemEntry<T>[] = [];\n\n batch(() => {\n for (let i = 0; i < next.length; i++) {\n const entry = itemsMap.get(nextKeys[i]);\n\n invariant(entry, 'each() reconcile phase 2 lost an entry created in phase 1');\n\n entry.data.value = next[i];\n entry.index.value = i;\n nextOrdered.push(entry);\n }\n });\n\n // DOM ordering: right-to-left pass: move any item not already adjacent to cursor.\n // An item is in place when its last node directly precedes the cursor; items may\n // span several nodes (whitespace text around an element), so compare the last one.\n // Moving an already-placed item would disconnect and reconnect its custom elements.\n // O(n) DOM operations in the worst case; optimal for the typical small list sizes\n // encountered in UI components (tabs, options, menu items).\n let cursor: Node = endMarker;\n\n for (let j = nextOrdered.length - 1; j >= 0; j--) {\n const entry = nextOrdered[j];\n const firstNode = entry.nodes[0];\n const lastNode = entry.nodes[entry.nodes.length - 1];\n\n if (firstNode && lastNode !== cursor.previousSibling) {\n for (const node of entry.nodes) parent.insertBefore(node, cursor);\n }\n\n cursor = firstNode ?? cursor;\n }\n\n // Phase 3: commit: destroy stale entries only now that the pass has succeeded.\n const nextKeySet = new Set(nextKeys);\n\n for (const [key, entry] of itemsMap) {\n if (!nextKeySet.has(key)) {\n removeItem(entry);\n itemsMap.delete(key);\n }\n }\n\n return nextOrdered;\n};\n\n/** Undo a failed reconcile pass: destroy entries created during it, restoring the last good list. */\nconst rollbackCreated = <T>(itemsMap: Map<ItemKey, ItemEntry<T>>, created: ItemEntry<T>[]): void => {\n for (const entry of created) {\n removeItem(entry);\n\n for (const [key, candidate] of itemsMap) {\n if (candidate === entry) {\n itemsMap.delete(key);\n break;\n }\n }\n }\n};\n\nconst collectKeys = <T>(next: T[], keyFn: (item: T, index: number) => ItemKey): ItemKey[] => {\n const keys: ItemKey[] = [];\n const seen = new Set<ItemKey>();\n\n for (let i = 0; i < next.length; i++) {\n const key = keyFn(next[i], i);\n\n if (seen.has(key)) throw new OreApiError(ORE_ERRORS.eachDuplicateKey(String(key), i));\n\n seen.add(key);\n keys.push(key);\n }\n\n return keys;\n};\n\nconst reportEachError = (error: unknown, anchor: Comment): void => {\n const cause = error instanceof Error ? error : new Error(String(error));\n\n reportRuntimeError(\n new OreLifecycleError(`each() failed to reconcile a list update: ${cause.message}`, {\n cause,\n component: 'each()',\n phase: 'each-reconcile',\n }),\n anchor,\n );\n};\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Renders a keyed list of items as a `DirectiveResult`.\n *\n * Each item is rendered by the provided render function.\n * Items are reused by key when the list changes; only stale items are destroyed.\n *\n * The render function receives a `Readable<T>` signal and a `Readable<number>` index\n * signal. Use `item.value` to read the current item inside reactive expressions:\n *\n * ```ts\n * html`${each(items, (item) => item.id, (item) => html`<li>${() => item.value.name}</li>`)}`\n * ```\n *\n * **Plain array:** when a plain `T[]` is passed (not a signal or getter), it is\n * treated as a one-time static render. Mutations to the original array are not\n * tracked. Pass a `Signal<T[]>` or `() => T[]` for reactive lists.\n *\n * **Optional fallback:** the fourth argument renders when the list is empty.\n *\n * **Key choice:** pass a stable item identifier (e.g. `item.id`), never the\n * array index: an index-based key reassigns to a different item whenever the\n * list is reordered or an item is inserted/removed before it, causing full\n * item teardown/recreation instead of the in-place update `each()` is built\n * for.\n *\n * **Duplicate keys:** a reconciliation failure (e.g. duplicate keys, see `eachDuplicateKey`)\n * does not throw past this function: an uncaught exception inside the reactive effect that\n * drives `each()` would risk corrupting unrelated effects scheduled in the same update batch.\n * Instead the last valid list is preserved and the failure is reported via the `ore:error` DOM event\n * (see `OreLifecycleError`, phase `'each-reconcile'`) plus a dev-only console log: listen for\n * `ore:error` on `document`/`window` to observe this in every build, including production.\n */\nexport function each<T>(\n list: MaybeReactiveArray<T>,\n keyFn: (item: T, index: number) => string | number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n fallback?: () => HTMLResult,\n): DirectiveResult {\n const listSignal = Array.isArray(list)\n ? signal(list as T[])\n : typeof list === 'function'\n ? computed(list as () => T[])\n : list;\n\n return createDirectiveResult((anchor, registerCleanup) => {\n const parent = anchor.parentNode;\n\n invariant(parent, 'each() anchor comment has no parent node');\n\n const endMarker = document.createComment('each/end');\n\n parent.insertBefore(endMarker, anchor.nextSibling);\n\n let itemsMap = new Map<ItemKey, ItemEntry<T>>();\n let itemsOrdered: ItemEntry<T>[] = [];\n let fallbackNodes: Node[] | null = null;\n let fallbackCleanups: (() => void)[] = [];\n\n const mountFallback = (): void => {\n if (!fallback) return;\n\n const result = fallback();\n\n fallbackNodes = result.mount(parent, endMarker, (fn) => fallbackCleanups.push(fn));\n };\n\n const clearFallback = (): void => {\n if (fallbackNodes) {\n runAll(fallbackCleanups);\n removeNodes(fallbackNodes);\n fallbackNodes = null;\n fallbackCleanups = [];\n }\n };\n\n const sub = rawEffect(() => {\n const nextList = listSignal.value ?? [];\n\n if (nextList.length === 0) {\n for (const entry of untrack(() => itemsOrdered)) removeItem(entry);\n itemsMap = new Map();\n itemsOrdered = [];\n\n if (!fallbackNodes) untrack(mountFallback);\n\n return;\n }\n\n let nextKeys: ItemKey[];\n\n try {\n nextKeys = untrack(() => collectKeys(nextList, keyFn));\n } catch (error) {\n reportEachError(error, anchor);\n return;\n }\n\n clearFallback();\n\n const createdThisPass: ItemEntry<T>[] = [];\n\n try {\n itemsOrdered = untrack(() =>\n reconcileItems(itemsMap, nextList, nextKeys, render, parent, endMarker, createdThisPass),\n );\n } catch (error) {\n // Undo this pass instead of tearing the list down: entries created before the\n // throw are removed, everything that was on screen stays exactly as it was.\n reportEachError(error, anchor);\n untrack(() => rollbackCreated(itemsMap, createdThisPass));\n }\n });\n\n registerCleanup(() => sub.dispose());\n registerCleanup(() => {\n clearFallback();\n for (const entry of itemsOrdered) removeItem(entry);\n endMarker.remove();\n });\n });\n}\n"],"mappings":"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"}
|
package/dist/directives/each.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{ORE_ERRORS as e,OreApiError as t,OreLifecycleError as n,invariant as r,reportRuntimeError as i}from"../errors.js";import{removeNodes as a,runAll as o}from"../utils/dom.js";import{createDirectiveResult as s}from"../template/result.js";import{batch as c,computed as l,createScope as u,effect as d,signal as f,untrack as p}from"@vielzeug/ripple";var m=(e,t,n,r,i)=>{let a=f(e),o=f(t),s=u(),c=[],l=[];return s.run(()=>{l=n(a,o).mount(r,i,e=>c.push(e))}),{cleanups:c,data:a,index:o,nodes:l,scope:s}},h=e=>{e.scope.dispose(),o(e.cleanups),a(e.nodes)},g=(e,t,n,i,a,o,s)=>{for(let r=0;r<t.length;r++){let c=n[r];if(e.has(c))continue;let l=p(()=>m(t[r],r,i,a,o));s.push(l),e.set(c,l)}let l=[];c(()=>{for(let i=0;i<t.length;i++){let a=e.get(n[i]);r(a,`each() reconcile phase 2 lost an entry created in phase 1`),a.data.value=t[i],a.index.value=i,l.push(a)}});let u=o;for(let e=l.length-1;e>=0;e--){let t=l[e],n=t.nodes[0],r=t.nodes[t.nodes.length-1];if(n&&r!==u.previousSibling)for(let e of t.nodes)a.insertBefore(e,u);u=n??u}let d=new Set(n);for(let[t,n]of e)d.has(t)||(h(n),e.delete(t));return l},_=(e,t)=>{for(let n of t){h(n);for(let[t,r]of e)if(r===n){e.delete(t);break}}},v=(n,r)=>{let i=[],a
|
|
1
|
+
import{ORE_ERRORS as e,OreApiError as t,OreLifecycleError as n,invariant as r,reportRuntimeError as i}from"../errors.js";import{removeNodes as a,runAll as o}from"../utils/dom.js";import{createDirectiveResult as s}from"../template/result.js";import{batch as c,computed as l,createScope as u,effect as d,signal as f,untrack as p}from"@vielzeug/ripple";var m=(e,t,n,r,i)=>{let a=f(e),o=f(t),s=u(),c=[],l=[];return s.run(()=>{l=n(a,o).mount(r,i,e=>c.push(e))}),{cleanups:c,data:a,index:o,nodes:l,scope:s}},h=e=>{e.scope.dispose(),o(e.cleanups),a(e.nodes)},g=(e,t,n,i,a,o,s)=>{for(let r=0;r<t.length;r++){let c=n[r];if(e.has(c))continue;let l=p(()=>m(t[r],r,i,a,o));s.push(l),e.set(c,l)}let l=[];c(()=>{for(let i=0;i<t.length;i++){let a=e.get(n[i]);r(a,`each() reconcile phase 2 lost an entry created in phase 1`),a.data.value=t[i],a.index.value=i,l.push(a)}});let u=o;for(let e=l.length-1;e>=0;e--){let t=l[e],n=t.nodes[0],r=t.nodes[t.nodes.length-1];if(n&&r!==u.previousSibling)for(let e of t.nodes)a.insertBefore(e,u);u=n??u}let d=new Set(n);for(let[t,n]of e)d.has(t)||(h(n),e.delete(t));return l},_=(e,t)=>{for(let n of t){h(n);for(let[t,r]of e)if(r===n){e.delete(t);break}}},v=(n,r)=>{let i=[],a=/* @__PURE__ */ new Set;for(let o=0;o<n.length;o++){let s=r(n[o],o);if(a.has(s))throw new t(e.eachDuplicateKey(String(s),o));a.add(s),i.push(s)}return i},y=(e,t)=>{let r=e instanceof Error?e:Error(String(e));i(new n(`each() failed to reconcile a list update: ${r.message}`,{cause:r,component:`each()`,phase:`each-reconcile`}),t)};function b(e,t,n,i){let c=Array.isArray(e)?f(e):typeof e==`function`?l(e):e;return s((e,s)=>{let l=e.parentNode;r(l,`each() anchor comment has no parent node`);let u=document.createComment(`each/end`);l.insertBefore(u,e.nextSibling);let f=/* @__PURE__ */ new Map,m=[],b=null,x=[],S=()=>{i&&(b=i().mount(l,u,e=>x.push(e)))},C=()=>{b&&(o(x),a(b),b=null,x=[])},w=d(()=>{let r=c.value??[];if(r.length===0){for(let e of p(()=>m))h(e);f=/* @__PURE__ */ new Map,m=[],b||p(S);return}let i;try{i=p(()=>v(r,t))}catch(t){y(t,e);return}C();let a=[];try{m=p(()=>g(f,r,i,n,l,u,a))}catch(t){y(t,e),p(()=>_(f,a))}});s(()=>w.dispose()),s(()=>{C();for(let e of m)h(e);u.remove()})})}export{b as each};
|
|
2
2
|
//# sourceMappingURL=each.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"each.js","names":[],"sources":["../../src/directives/each.ts"],"sourcesContent":["import {\n batch,\n computed,\n createScope,\n type Readable,\n effect as rawEffect,\n type Scope,\n type Signal,\n signal,\n untrack,\n} from '@vielzeug/ripple';\n\nimport { invariant, ORE_ERRORS, OreApiError, OreLifecycleError, reportRuntimeError } from '../errors';\nimport { createDirectiveResult, type DirectiveResult, type HTMLResult } from '../template/result';\nimport { type MaybeReactive, removeNodes, runAll } from '../utils/dom';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\ntype MaybeReactiveArray<T> = MaybeReactive<T[]>;\ntype ItemKey = number | string;\n\ntype ItemEntry<T> = {\n cleanups: (() => void)[];\n data: Signal<T>;\n index: Signal<number>;\n nodes: Node[];\n scope: Scope;\n};\n\n// ─── Item lifecycle ───────────────────────────────────────────────────────────\n\nconst createItem = <T>(\n item: T,\n index: number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n parent: ParentNode,\n insertBefore: Node,\n): ItemEntry<T> => {\n const dataSignal: Signal<T> = signal(item);\n const indexSignal: Signal<number> = signal(index);\n const scope = createScope();\n const cleanups: (() => void)[] = [];\n let nodes: Node[] = [];\n\n scope.run(() => {\n const result = render(dataSignal, indexSignal);\n\n nodes = result.mount(parent, insertBefore, (fn) => cleanups.push(fn));\n });\n\n return { cleanups, data: dataSignal, index: indexSignal, nodes, scope };\n};\n\nconst removeItem = <T>(entry: ItemEntry<T>): void => {\n entry.scope.dispose();\n runAll(entry.cleanups);\n removeNodes(entry.nodes);\n};\n\n// ─── Reconciler ───────────────────────────────────────────────────────────────\n\n/**\n * Reconciles the live item map against the next array. The pass is transactional:\n * new entries are created first, existing entries are updated in-place via signal\n * writes, and stale entries are destroyed only after the whole pass has succeeded.\n * If creation throws (user render code), the caller rolls back the entries created\n * during this pass and the previous list state survives: the last good render stays\n * on screen. Returns the ordered list of entries matching nextList.\n */\nconst reconcileItems = <T>(\n itemsMap: Map<ItemKey, ItemEntry<T>>,\n next: T[],\n nextKeys: ItemKey[],\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n parent: ParentNode,\n endMarker: Node,\n createdThisPass: ItemEntry<T>[],\n): ItemEntry<T>[] => {\n // Phase 1: create every missing entry. This is the only step that can throw\n // (user render code), and it records what it created so a failure is undoable.\n for (let i = 0; i < next.length; i++) {\n const key = nextKeys[i];\n\n if (itemsMap.has(key)) continue;\n\n const entry = untrack(() => createItem(next[i], i, render, parent, endMarker));\n\n createdThisPass.push(entry);\n itemsMap.set(key, entry);\n }\n\n // Phase 2: ordered list + in-place updates for reused entries (signal writes).\n const nextOrdered: ItemEntry<T>[] = [];\n\n batch(() => {\n for (let i = 0; i < next.length; i++) {\n const entry = itemsMap.get(nextKeys[i]);\n\n invariant(entry, 'each() reconcile phase 2 lost an entry created in phase 1');\n\n entry.data.value = next[i];\n entry.index.value = i;\n nextOrdered.push(entry);\n }\n });\n\n // DOM ordering: right-to-left pass: move any item not already adjacent to cursor.\n // An item is in place when its last node directly precedes the cursor; items may\n // span several nodes (whitespace text around an element), so compare the last one.\n // Moving an already-placed item would disconnect and reconnect its custom elements.\n // O(n) DOM operations in the worst case; optimal for the typical small list sizes\n // encountered in UI components (tabs, options, menu items).\n let cursor: Node = endMarker;\n\n for (let j = nextOrdered.length - 1; j >= 0; j--) {\n const entry = nextOrdered[j];\n const firstNode = entry.nodes[0];\n const lastNode = entry.nodes[entry.nodes.length - 1];\n\n if (firstNode && lastNode !== cursor.previousSibling) {\n for (const node of entry.nodes) parent.insertBefore(node, cursor);\n }\n\n cursor = firstNode ?? cursor;\n }\n\n // Phase 3: commit: destroy stale entries only now that the pass has succeeded.\n const nextKeySet = new Set(nextKeys);\n\n for (const [key, entry] of itemsMap) {\n if (!nextKeySet.has(key)) {\n removeItem(entry);\n itemsMap.delete(key);\n }\n }\n\n return nextOrdered;\n};\n\n/** Undo a failed reconcile pass: destroy entries created during it, restoring the last good list. */\nconst rollbackCreated = <T>(itemsMap: Map<ItemKey, ItemEntry<T>>, created: ItemEntry<T>[]): void => {\n for (const entry of created) {\n removeItem(entry);\n\n for (const [key, candidate] of itemsMap) {\n if (candidate === entry) {\n itemsMap.delete(key);\n break;\n }\n }\n }\n};\n\nconst collectKeys = <T>(next: T[], keyFn: (item: T, index: number) => ItemKey): ItemKey[] => {\n const keys: ItemKey[] = [];\n const seen = new Set<ItemKey>();\n\n for (let i = 0; i < next.length; i++) {\n const key = keyFn(next[i], i);\n\n if (seen.has(key)) throw new OreApiError(ORE_ERRORS.eachDuplicateKey(String(key), i));\n\n seen.add(key);\n keys.push(key);\n }\n\n return keys;\n};\n\nconst reportEachError = (error: unknown, anchor: Comment): void => {\n const cause = error instanceof Error ? error : new Error(String(error));\n\n reportRuntimeError(\n new OreLifecycleError(`each() failed to reconcile a list update: ${cause.message}`, {\n cause,\n component: 'each()',\n phase: 'each-reconcile',\n }),\n anchor,\n );\n};\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Renders a keyed list of items as a `DirectiveResult`.\n *\n * Each item is rendered by the provided render function.\n * Items are reused by key when the list changes; only stale items are destroyed.\n *\n * The render function receives a `Readable<T>` signal and a `Readable<number>` index\n * signal. Use `item.value` to read the current item inside reactive expressions:\n *\n * ```ts\n * html`${each(items, (item) => item.id, (item) => html`<li>${() => item.value.name}</li>`)}`\n * ```\n *\n * **Plain array:** when a plain `T[]` is passed (not a signal or getter), it is\n * treated as a one-time static render. Mutations to the original array are not\n * tracked. Pass a `Signal<T[]>` or `() => T[]` for reactive lists.\n *\n * **Optional fallback:** the fourth argument renders when the list is empty.\n *\n * **Key choice:** pass a stable item identifier (e.g. `item.id`), never the\n * array index: an index-based key reassigns to a different item whenever the\n * list is reordered or an item is inserted/removed before it, causing full\n * item teardown/recreation instead of the in-place update `each()` is built\n * for.\n *\n * **Duplicate keys:** a reconciliation failure (e.g. duplicate keys, see `eachDuplicateKey`)\n * does not throw past this function: an uncaught exception inside the reactive effect that\n * drives `each()` would risk corrupting unrelated effects scheduled in the same update batch.\n * Instead the last valid list is preserved and the failure is reported via the `ore:error` DOM event\n * (see `OreLifecycleError`, phase `'each-reconcile'`) plus a dev-only console log: listen for\n * `ore:error` on `document`/`window` to observe this in every build, including production.\n */\nexport function each<T>(\n list: MaybeReactiveArray<T>,\n keyFn: (item: T, index: number) => string | number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n fallback?: () => HTMLResult,\n): DirectiveResult {\n const listSignal = Array.isArray(list)\n ? signal(list as T[])\n : typeof list === 'function'\n ? computed(list as () => T[])\n : list;\n\n return createDirectiveResult((anchor, registerCleanup) => {\n const parent = anchor.parentNode;\n\n invariant(parent, 'each() anchor comment has no parent node');\n\n const endMarker = document.createComment('each/end');\n\n parent.insertBefore(endMarker, anchor.nextSibling);\n\n let itemsMap = new Map<ItemKey, ItemEntry<T>>();\n let itemsOrdered: ItemEntry<T>[] = [];\n let fallbackNodes: Node[] | null = null;\n let fallbackCleanups: (() => void)[] = [];\n\n const mountFallback = (): void => {\n if (!fallback) return;\n\n const result = fallback();\n\n fallbackNodes = result.mount(parent, endMarker, (fn) => fallbackCleanups.push(fn));\n };\n\n const clearFallback = (): void => {\n if (fallbackNodes) {\n runAll(fallbackCleanups);\n removeNodes(fallbackNodes);\n fallbackNodes = null;\n fallbackCleanups = [];\n }\n };\n\n const sub = rawEffect(() => {\n const nextList = listSignal.value ?? [];\n\n if (nextList.length === 0) {\n for (const entry of untrack(() => itemsOrdered)) removeItem(entry);\n itemsMap = new Map();\n itemsOrdered = [];\n\n if (!fallbackNodes) untrack(mountFallback);\n\n return;\n }\n\n let nextKeys: ItemKey[];\n\n try {\n nextKeys = untrack(() => collectKeys(nextList, keyFn));\n } catch (error) {\n reportEachError(error, anchor);\n return;\n }\n\n clearFallback();\n\n const createdThisPass: ItemEntry<T>[] = [];\n\n try {\n itemsOrdered = untrack(() =>\n reconcileItems(itemsMap, nextList, nextKeys, render, parent, endMarker, createdThisPass),\n );\n } catch (error) {\n // Undo this pass instead of tearing the list down: entries created before the\n // throw are removed, everything that was on screen stays exactly as it was.\n reportEachError(error, anchor);\n untrack(() => rollbackCreated(itemsMap, createdThisPass));\n }\n });\n\n registerCleanup(() => sub.dispose());\n registerCleanup(() => {\n clearFallback();\n for (const entry of itemsOrdered) removeItem(entry);\n endMarker.remove();\n });\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"each.js","names":["rawEffect","createItem","dataSignal","signal","item","indexSignal","index","scope","createScope","cleanups","nodes","render","parent","insertBefore","fn","removeItem","entry","runAll","removeNodes","reconcileItems","i","next","key","nextKeys","itemsMap","untrack","endMarker","createdThisPass","nextOrdered","batch","invariant","cursor","j","firstNode","lastNode","node","nextKeySet","rollbackCreated","created","candidate","collectKeys","keys","seen","keyFn","OreApiError","ORE_ERRORS","reportEachError","cause","error","reportRuntimeError","OreLifecycleError","anchor","each","listSignal","list","computed","createDirectiveResult","itemsOrdered","fallbackNodes","fallbackCleanups","mountFallback","fallback","clearFallback","sub","effect","nextList","registerCleanup"],"sources":["../../src/directives/each.ts"],"sourcesContent":["import {\n batch,\n computed,\n createScope,\n type Readable,\n effect as rawEffect,\n type Scope,\n type Signal,\n signal,\n untrack,\n} from '@vielzeug/ripple';\n\nimport { invariant, ORE_ERRORS, OreApiError, OreLifecycleError, reportRuntimeError } from '../errors';\nimport { createDirectiveResult, type DirectiveResult, type HTMLResult } from '../template/result';\nimport { type MaybeReactive, removeNodes, runAll } from '../utils/dom';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\ntype MaybeReactiveArray<T> = MaybeReactive<T[]>;\ntype ItemKey = number | string;\n\ntype ItemEntry<T> = {\n cleanups: (() => void)[];\n data: Signal<T>;\n index: Signal<number>;\n nodes: Node[];\n scope: Scope;\n};\n\n// ─── Item lifecycle ───────────────────────────────────────────────────────────\n\nconst createItem = <T>(\n item: T,\n index: number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n parent: ParentNode,\n insertBefore: Node,\n): ItemEntry<T> => {\n const dataSignal: Signal<T> = signal(item);\n const indexSignal: Signal<number> = signal(index);\n const scope = createScope();\n const cleanups: (() => void)[] = [];\n let nodes: Node[] = [];\n\n scope.run(() => {\n const result = render(dataSignal, indexSignal);\n\n nodes = result.mount(parent, insertBefore, (fn) => cleanups.push(fn));\n });\n\n return { cleanups, data: dataSignal, index: indexSignal, nodes, scope };\n};\n\nconst removeItem = <T>(entry: ItemEntry<T>): void => {\n entry.scope.dispose();\n runAll(entry.cleanups);\n removeNodes(entry.nodes);\n};\n\n// ─── Reconciler ───────────────────────────────────────────────────────────────\n\n/**\n * Reconciles the live item map against the next array. The pass is transactional:\n * new entries are created first, existing entries are updated in-place via signal\n * writes, and stale entries are destroyed only after the whole pass has succeeded.\n * If creation throws (user render code), the caller rolls back the entries created\n * during this pass and the previous list state survives: the last good render stays\n * on screen. Returns the ordered list of entries matching nextList.\n */\nconst reconcileItems = <T>(\n itemsMap: Map<ItemKey, ItemEntry<T>>,\n next: T[],\n nextKeys: ItemKey[],\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n parent: ParentNode,\n endMarker: Node,\n createdThisPass: ItemEntry<T>[],\n): ItemEntry<T>[] => {\n // Phase 1: create every missing entry. This is the only step that can throw\n // (user render code), and it records what it created so a failure is undoable.\n for (let i = 0; i < next.length; i++) {\n const key = nextKeys[i];\n\n if (itemsMap.has(key)) continue;\n\n const entry = untrack(() => createItem(next[i], i, render, parent, endMarker));\n\n createdThisPass.push(entry);\n itemsMap.set(key, entry);\n }\n\n // Phase 2: ordered list + in-place updates for reused entries (signal writes).\n const nextOrdered: ItemEntry<T>[] = [];\n\n batch(() => {\n for (let i = 0; i < next.length; i++) {\n const entry = itemsMap.get(nextKeys[i]);\n\n invariant(entry, 'each() reconcile phase 2 lost an entry created in phase 1');\n\n entry.data.value = next[i];\n entry.index.value = i;\n nextOrdered.push(entry);\n }\n });\n\n // DOM ordering: right-to-left pass: move any item not already adjacent to cursor.\n // An item is in place when its last node directly precedes the cursor; items may\n // span several nodes (whitespace text around an element), so compare the last one.\n // Moving an already-placed item would disconnect and reconnect its custom elements.\n // O(n) DOM operations in the worst case; optimal for the typical small list sizes\n // encountered in UI components (tabs, options, menu items).\n let cursor: Node = endMarker;\n\n for (let j = nextOrdered.length - 1; j >= 0; j--) {\n const entry = nextOrdered[j];\n const firstNode = entry.nodes[0];\n const lastNode = entry.nodes[entry.nodes.length - 1];\n\n if (firstNode && lastNode !== cursor.previousSibling) {\n for (const node of entry.nodes) parent.insertBefore(node, cursor);\n }\n\n cursor = firstNode ?? cursor;\n }\n\n // Phase 3: commit: destroy stale entries only now that the pass has succeeded.\n const nextKeySet = new Set(nextKeys);\n\n for (const [key, entry] of itemsMap) {\n if (!nextKeySet.has(key)) {\n removeItem(entry);\n itemsMap.delete(key);\n }\n }\n\n return nextOrdered;\n};\n\n/** Undo a failed reconcile pass: destroy entries created during it, restoring the last good list. */\nconst rollbackCreated = <T>(itemsMap: Map<ItemKey, ItemEntry<T>>, created: ItemEntry<T>[]): void => {\n for (const entry of created) {\n removeItem(entry);\n\n for (const [key, candidate] of itemsMap) {\n if (candidate === entry) {\n itemsMap.delete(key);\n break;\n }\n }\n }\n};\n\nconst collectKeys = <T>(next: T[], keyFn: (item: T, index: number) => ItemKey): ItemKey[] => {\n const keys: ItemKey[] = [];\n const seen = new Set<ItemKey>();\n\n for (let i = 0; i < next.length; i++) {\n const key = keyFn(next[i], i);\n\n if (seen.has(key)) throw new OreApiError(ORE_ERRORS.eachDuplicateKey(String(key), i));\n\n seen.add(key);\n keys.push(key);\n }\n\n return keys;\n};\n\nconst reportEachError = (error: unknown, anchor: Comment): void => {\n const cause = error instanceof Error ? error : new Error(String(error));\n\n reportRuntimeError(\n new OreLifecycleError(`each() failed to reconcile a list update: ${cause.message}`, {\n cause,\n component: 'each()',\n phase: 'each-reconcile',\n }),\n anchor,\n );\n};\n\n// ─── Public API ───────────────────────────────────────────────────────────────\n\n/**\n * Renders a keyed list of items as a `DirectiveResult`.\n *\n * Each item is rendered by the provided render function.\n * Items are reused by key when the list changes; only stale items are destroyed.\n *\n * The render function receives a `Readable<T>` signal and a `Readable<number>` index\n * signal. Use `item.value` to read the current item inside reactive expressions:\n *\n * ```ts\n * html`${each(items, (item) => item.id, (item) => html`<li>${() => item.value.name}</li>`)}`\n * ```\n *\n * **Plain array:** when a plain `T[]` is passed (not a signal or getter), it is\n * treated as a one-time static render. Mutations to the original array are not\n * tracked. Pass a `Signal<T[]>` or `() => T[]` for reactive lists.\n *\n * **Optional fallback:** the fourth argument renders when the list is empty.\n *\n * **Key choice:** pass a stable item identifier (e.g. `item.id`), never the\n * array index: an index-based key reassigns to a different item whenever the\n * list is reordered or an item is inserted/removed before it, causing full\n * item teardown/recreation instead of the in-place update `each()` is built\n * for.\n *\n * **Duplicate keys:** a reconciliation failure (e.g. duplicate keys, see `eachDuplicateKey`)\n * does not throw past this function: an uncaught exception inside the reactive effect that\n * drives `each()` would risk corrupting unrelated effects scheduled in the same update batch.\n * Instead the last valid list is preserved and the failure is reported via the `ore:error` DOM event\n * (see `OreLifecycleError`, phase `'each-reconcile'`) plus a dev-only console log: listen for\n * `ore:error` on `document`/`window` to observe this in every build, including production.\n */\nexport function each<T>(\n list: MaybeReactiveArray<T>,\n keyFn: (item: T, index: number) => string | number,\n render: (item: Readable<T>, index: Readable<number>) => HTMLResult,\n fallback?: () => HTMLResult,\n): DirectiveResult {\n const listSignal = Array.isArray(list)\n ? signal(list as T[])\n : typeof list === 'function'\n ? computed(list as () => T[])\n : list;\n\n return createDirectiveResult((anchor, registerCleanup) => {\n const parent = anchor.parentNode;\n\n invariant(parent, 'each() anchor comment has no parent node');\n\n const endMarker = document.createComment('each/end');\n\n parent.insertBefore(endMarker, anchor.nextSibling);\n\n let itemsMap = new Map<ItemKey, ItemEntry<T>>();\n let itemsOrdered: ItemEntry<T>[] = [];\n let fallbackNodes: Node[] | null = null;\n let fallbackCleanups: (() => void)[] = [];\n\n const mountFallback = (): void => {\n if (!fallback) return;\n\n const result = fallback();\n\n fallbackNodes = result.mount(parent, endMarker, (fn) => fallbackCleanups.push(fn));\n };\n\n const clearFallback = (): void => {\n if (fallbackNodes) {\n runAll(fallbackCleanups);\n removeNodes(fallbackNodes);\n fallbackNodes = null;\n fallbackCleanups = [];\n }\n };\n\n const sub = rawEffect(() => {\n const nextList = listSignal.value ?? [];\n\n if (nextList.length === 0) {\n for (const entry of untrack(() => itemsOrdered)) removeItem(entry);\n itemsMap = new Map();\n itemsOrdered = [];\n\n if (!fallbackNodes) untrack(mountFallback);\n\n return;\n }\n\n let nextKeys: ItemKey[];\n\n try {\n nextKeys = untrack(() => collectKeys(nextList, keyFn));\n } catch (error) {\n reportEachError(error, anchor);\n return;\n }\n\n clearFallback();\n\n const createdThisPass: ItemEntry<T>[] = [];\n\n try {\n itemsOrdered = untrack(() =>\n reconcileItems(itemsMap, nextList, nextKeys, render, parent, endMarker, createdThisPass),\n );\n } catch (error) {\n // Undo this pass instead of tearing the list down: entries created before the\n // throw are removed, everything that was on screen stays exactly as it was.\n reportEachError(error, anchor);\n untrack(() => rollbackCreated(itemsMap, createdThisPass));\n }\n });\n\n registerCleanup(() => sub.dispose());\n registerCleanup(() => {\n clearFallback();\n for (const entry of itemsOrdered) removeItem(entry);\n endMarker.remove();\n });\n });\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live.cjs","names":[],"sources":["../../src/directives/live.ts"],"sourcesContent":["import type { Readable } from '@vielzeug/ripple';\n\nimport { makeBrand } from '../utils/brand';\n\n/**\n * A per-binding marker produced by `live()`. Wraps the source signal: the\n * \"live\" flag belongs to this one binding site, never to the signal itself,\n * so other bindings of the same signal are unaffected.\n *\n * @example\n * html`<input value=\"${live(model)}\" />`\n */\nexport type LiveBinding<T> = { readonly source: Readable<T> };\n\nconst liveBrand = makeBrand<LiveBinding<unknown>>('ore:live');\n\n/**\n * Marks one attribute binding as \"live\" so stale app-state writes never clobber\n * in-progress user input at that binding site.\n *\n * For form controls: if the current DOM value diverges from the last write made\n * by this binding, subsequent app-state writes are silently dropped until the\n * DOM value matches the incoming value or no prior write has been recorded.\n */\nexport const live = <T>(source: Readable<T>): LiveBinding<T> => liveBrand.stamp({ source }) as LiveBinding<T>;\n\nexport const isLiveBinding = liveBrand.is;\n"],"mappings":"AAcA,
|
|
1
|
+
{"version":3,"file":"live.cjs","names":["makeBrand","liveBrand","live","isLiveBinding"],"sources":["../../src/directives/live.ts"],"sourcesContent":["import type { Readable } from '@vielzeug/ripple';\n\nimport { makeBrand } from '../utils/brand';\n\n/**\n * A per-binding marker produced by `live()`. Wraps the source signal: the\n * \"live\" flag belongs to this one binding site, never to the signal itself,\n * so other bindings of the same signal are unaffected.\n *\n * @example\n * html`<input value=\"${live(model)}\" />`\n */\nexport type LiveBinding<T> = { readonly source: Readable<T> };\n\nconst liveBrand = makeBrand<LiveBinding<unknown>>('ore:live');\n\n/**\n * Marks one attribute binding as \"live\" so stale app-state writes never clobber\n * in-progress user input at that binding site.\n *\n * For form controls: if the current DOM value diverges from the last write made\n * by this binding, subsequent app-state writes are silently dropped until the\n * DOM value matches the incoming value or no prior write has been recorded.\n */\nexport const live = <T>(source: Readable<T>): LiveBinding<T> => liveBrand.stamp({ source }) as LiveBinding<T>;\n\nexport const isLiveBinding = liveBrand.is;\n"],"mappings":"AAcA,IAAMC,E,4BAAYD,CAAAA,CAAAA,UAAgC,UAAU,EAU/CE,EAAW,GAAwCD,EAAU,MAAM,CAAE,QAAO,CAAC,EAE7EE,EAAgBF,EAAU,G"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live.js","names":[],"sources":["../../src/directives/live.ts"],"sourcesContent":["import type { Readable } from '@vielzeug/ripple';\n\nimport { makeBrand } from '../utils/brand';\n\n/**\n * A per-binding marker produced by `live()`. Wraps the source signal: the\n * \"live\" flag belongs to this one binding site, never to the signal itself,\n * so other bindings of the same signal are unaffected.\n *\n * @example\n * html`<input value=\"${live(model)}\" />`\n */\nexport type LiveBinding<T> = { readonly source: Readable<T> };\n\nconst liveBrand = makeBrand<LiveBinding<unknown>>('ore:live');\n\n/**\n * Marks one attribute binding as \"live\" so stale app-state writes never clobber\n * in-progress user input at that binding site.\n *\n * For form controls: if the current DOM value diverges from the last write made\n * by this binding, subsequent app-state writes are silently dropped until the\n * DOM value matches the incoming value or no prior write has been recorded.\n */\nexport const live = <T>(source: Readable<T>): LiveBinding<T> => liveBrand.stamp({ source }) as LiveBinding<T>;\n\nexport const isLiveBinding = liveBrand.is;\n"],"mappings":"8CAcA,
|
|
1
|
+
{"version":3,"file":"live.js","names":["liveBrand","makeBrand","live","isLiveBinding"],"sources":["../../src/directives/live.ts"],"sourcesContent":["import type { Readable } from '@vielzeug/ripple';\n\nimport { makeBrand } from '../utils/brand';\n\n/**\n * A per-binding marker produced by `live()`. Wraps the source signal: the\n * \"live\" flag belongs to this one binding site, never to the signal itself,\n * so other bindings of the same signal are unaffected.\n *\n * @example\n * html`<input value=\"${live(model)}\" />`\n */\nexport type LiveBinding<T> = { readonly source: Readable<T> };\n\nconst liveBrand = makeBrand<LiveBinding<unknown>>('ore:live');\n\n/**\n * Marks one attribute binding as \"live\" so stale app-state writes never clobber\n * in-progress user input at that binding site.\n *\n * For form controls: if the current DOM value diverges from the last write made\n * by this binding, subsequent app-state writes are silently dropped until the\n * DOM value matches the incoming value or no prior write has been recorded.\n */\nexport const live = <T>(source: Readable<T>): LiveBinding<T> => liveBrand.stamp({ source }) as LiveBinding<T>;\n\nexport const isLiveBinding = liveBrand.is;\n"],"mappings":"A,8CAcA,IAAMA,EAAYC,EAAgC,UAAU,EAU/CC,EAAW,GAAwCF,EAAU,MAAM,CAAE,QAAO,CAAC,EAE7EG,EAAgBH,EAAU,G"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.cjs","names":[],"sources":["../../src/directives/model.ts"],"sourcesContent":["import type { Signal } from '@vielzeug/ripple';\nimport { makeBrand } from '../utils/brand';\n\n/**\n * Options for `model()`. Every form control reads and writes differently; the\n * defaults cover native inputs, and `get`/`event` override them for anything else.\n */\nexport type ModelOptions<T = unknown> = {\n /**\n * Event that triggers the write-back from the control to the signal.\n * Defaults to `'change'` for checkboxes, radios, and `<select>`, and\n * `'input'` for everything else.\n */\n event?: string;\n /**\n * Reads the current value out of the element on each write-back event.\n * Defaults to `el.checked` for checkboxes/radios and `el.value` otherwise\n * (always a string for native text/number inputs: pass a `get` like\n * `(el) => Number(el.value)` to keep a numeric signal numeric).\n */\n get?: (el: HTMLElement) => T;\n};\n\n/**\n * Per-binding marker produced by `model()`. Like `live()`, the two-way behavior\n * belongs to this one binding site, never to the signal itself.\n *\n * @internal Consumed by the template binding layer (see `template/instantiator.ts`).\n */\nexport type ModelSource<T = unknown> = {\n readonly event?: string;\n readonly get?: (el: HTMLElement) => T;\n readonly source: Signal<T>;\n};\n\nconst modelBrand = makeBrand<ModelSource>('ore:model');\n\nexport const isModelSource = modelBrand.is;\n\n/** @internal Default value reader for `model()` (see template/instantiator.ts). */\nexport const modelDefaultGet = (el: HTMLElement): unknown => {\n if (el instanceof HTMLInputElement && (el.type === 'checkbox' || el.type === 'radio')) return el.checked;\n\n return (el as HTMLInputElement).value;\n};\n\n/** @internal Default write-back event for `model()` (see template/instantiator.ts). */\nexport const modelDefaultEvent = (el: HTMLElement): string =>\n el instanceof HTMLInputElement && (el.type === 'checkbox' || el.type === 'radio')\n ? 'change'\n : el instanceof HTMLSelectElement\n ? 'change'\n : 'input';\n\n/**\n * Two-way binding for a form control: `value=\"${model(signal)}\"`.\n *\n * Writes the signal to the control's `value`/`checked` property on every change,\n * and writes user input back into the signal on the control's change event\n * (`'change'` for checkboxes/radios/selects, `'input'` otherwise). This is the\n * complete controlled-input loop: the `live()` + manual `@input` handler pair it\n * replaces was easy to get half-wired.\n *\n * The signal is written with whatever `get` reads back (a string by default for\n * text inputs), so declare the signal with the matching type or pass `get`.\n *\n * @example\n * ```ts\n * const text = signal('');\n * const agree = signal(false);\n *\n * html`\n * <input value=\"${model(text)}\" />\n * <input type=\"checkbox\" checked=\"${model(agree)}\" />\n * <input type=\"number\" value=\"${model(count, { get: (el) => Number(el.value) })}\" />\n * `\n * ```\n */\nexport const model = <T>(source: Signal<T>, options?: ModelOptions<T>): ModelSource<T> =>\n modelBrand.stamp({ event: options?.event, get: options?.get, source }) as ModelSource<T>;\n"],"mappings":"AAmCA,
|
|
1
|
+
{"version":3,"file":"model.cjs","names":["makeBrand","modelBrand","isModelSource","modelDefaultGet","el","modelDefaultEvent","model","options"],"sources":["../../src/directives/model.ts"],"sourcesContent":["import type { Signal } from '@vielzeug/ripple';\nimport { makeBrand } from '../utils/brand';\n\n/**\n * Options for `model()`. Every form control reads and writes differently; the\n * defaults cover native inputs, and `get`/`event` override them for anything else.\n */\nexport type ModelOptions<T = unknown> = {\n /**\n * Event that triggers the write-back from the control to the signal.\n * Defaults to `'change'` for checkboxes, radios, and `<select>`, and\n * `'input'` for everything else.\n */\n event?: string;\n /**\n * Reads the current value out of the element on each write-back event.\n * Defaults to `el.checked` for checkboxes/radios and `el.value` otherwise\n * (always a string for native text/number inputs: pass a `get` like\n * `(el) => Number(el.value)` to keep a numeric signal numeric).\n */\n get?: (el: HTMLElement) => T;\n};\n\n/**\n * Per-binding marker produced by `model()`. Like `live()`, the two-way behavior\n * belongs to this one binding site, never to the signal itself.\n *\n * @internal Consumed by the template binding layer (see `template/instantiator.ts`).\n */\nexport type ModelSource<T = unknown> = {\n readonly event?: string;\n readonly get?: (el: HTMLElement) => T;\n readonly source: Signal<T>;\n};\n\nconst modelBrand = makeBrand<ModelSource>('ore:model');\n\nexport const isModelSource = modelBrand.is;\n\n/** @internal Default value reader for `model()` (see template/instantiator.ts). */\nexport const modelDefaultGet = (el: HTMLElement): unknown => {\n if (el instanceof HTMLInputElement && (el.type === 'checkbox' || el.type === 'radio')) return el.checked;\n\n return (el as HTMLInputElement).value;\n};\n\n/** @internal Default write-back event for `model()` (see template/instantiator.ts). */\nexport const modelDefaultEvent = (el: HTMLElement): string =>\n el instanceof HTMLInputElement && (el.type === 'checkbox' || el.type === 'radio')\n ? 'change'\n : el instanceof HTMLSelectElement\n ? 'change'\n : 'input';\n\n/**\n * Two-way binding for a form control: `value=\"${model(signal)}\"`.\n *\n * Writes the signal to the control's `value`/`checked` property on every change,\n * and writes user input back into the signal on the control's change event\n * (`'change'` for checkboxes/radios/selects, `'input'` otherwise). This is the\n * complete controlled-input loop: the `live()` + manual `@input` handler pair it\n * replaces was easy to get half-wired.\n *\n * The signal is written with whatever `get` reads back (a string by default for\n * text inputs), so declare the signal with the matching type or pass `get`.\n *\n * @example\n * ```ts\n * const text = signal('');\n * const agree = signal(false);\n *\n * html`\n * <input value=\"${model(text)}\" />\n * <input type=\"checkbox\" checked=\"${model(agree)}\" />\n * <input type=\"number\" value=\"${model(count, { get: (el) => Number(el.value) })}\" />\n * `\n * ```\n */\nexport const model = <T>(source: Signal<T>, options?: ModelOptions<T>): ModelSource<T> =>\n modelBrand.stamp({ event: options?.event, get: options?.get, source }) as ModelSource<T>;\n"],"mappings":"AAmCA,IAAMC,E,4BAAaD,CAAAA,CAAAA,UAAuB,WAAW,EAExCE,EAAgBD,EAAW,GAG3BE,EAAmB,GAC1BC,aAAc,mBAAqBA,EAAG,OAAS,YAAcA,EAAG,OAAS,SAAiBA,EAAG,QAEzFA,EAAwB,MAIrBC,EAAqB,GAChCD,aAAc,mBAAqBA,EAAG,OAAS,YAAcA,EAAG,OAAS,UAErEA,aAAc,kBADd,SAGE,QA0BKE,GAAY,EAAmB,IAC1CL,EAAW,MAAM,CAAE,MAAOM,GAAS,MAAO,IAAKA,GAAS,IAAK,QAAO,CAAC,E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model.js","names":[],"sources":["../../src/directives/model.ts"],"sourcesContent":["import type { Signal } from '@vielzeug/ripple';\nimport { makeBrand } from '../utils/brand';\n\n/**\n * Options for `model()`. Every form control reads and writes differently; the\n * defaults cover native inputs, and `get`/`event` override them for anything else.\n */\nexport type ModelOptions<T = unknown> = {\n /**\n * Event that triggers the write-back from the control to the signal.\n * Defaults to `'change'` for checkboxes, radios, and `<select>`, and\n * `'input'` for everything else.\n */\n event?: string;\n /**\n * Reads the current value out of the element on each write-back event.\n * Defaults to `el.checked` for checkboxes/radios and `el.value` otherwise\n * (always a string for native text/number inputs: pass a `get` like\n * `(el) => Number(el.value)` to keep a numeric signal numeric).\n */\n get?: (el: HTMLElement) => T;\n};\n\n/**\n * Per-binding marker produced by `model()`. Like `live()`, the two-way behavior\n * belongs to this one binding site, never to the signal itself.\n *\n * @internal Consumed by the template binding layer (see `template/instantiator.ts`).\n */\nexport type ModelSource<T = unknown> = {\n readonly event?: string;\n readonly get?: (el: HTMLElement) => T;\n readonly source: Signal<T>;\n};\n\nconst modelBrand = makeBrand<ModelSource>('ore:model');\n\nexport const isModelSource = modelBrand.is;\n\n/** @internal Default value reader for `model()` (see template/instantiator.ts). */\nexport const modelDefaultGet = (el: HTMLElement): unknown => {\n if (el instanceof HTMLInputElement && (el.type === 'checkbox' || el.type === 'radio')) return el.checked;\n\n return (el as HTMLInputElement).value;\n};\n\n/** @internal Default write-back event for `model()` (see template/instantiator.ts). */\nexport const modelDefaultEvent = (el: HTMLElement): string =>\n el instanceof HTMLInputElement && (el.type === 'checkbox' || el.type === 'radio')\n ? 'change'\n : el instanceof HTMLSelectElement\n ? 'change'\n : 'input';\n\n/**\n * Two-way binding for a form control: `value=\"${model(signal)}\"`.\n *\n * Writes the signal to the control's `value`/`checked` property on every change,\n * and writes user input back into the signal on the control's change event\n * (`'change'` for checkboxes/radios/selects, `'input'` otherwise). This is the\n * complete controlled-input loop: the `live()` + manual `@input` handler pair it\n * replaces was easy to get half-wired.\n *\n * The signal is written with whatever `get` reads back (a string by default for\n * text inputs), so declare the signal with the matching type or pass `get`.\n *\n * @example\n * ```ts\n * const text = signal('');\n * const agree = signal(false);\n *\n * html`\n * <input value=\"${model(text)}\" />\n * <input type=\"checkbox\" checked=\"${model(agree)}\" />\n * <input type=\"number\" value=\"${model(count, { get: (el) => Number(el.value) })}\" />\n * `\n * ```\n */\nexport const model = <T>(source: Signal<T>, options?: ModelOptions<T>): ModelSource<T> =>\n modelBrand.stamp({ event: options?.event, get: options?.get, source }) as ModelSource<T>;\n"],"mappings":"8CAmCA,
|
|
1
|
+
{"version":3,"file":"model.js","names":["modelBrand","makeBrand","isModelSource","modelDefaultGet","el","modelDefaultEvent","model","options"],"sources":["../../src/directives/model.ts"],"sourcesContent":["import type { Signal } from '@vielzeug/ripple';\nimport { makeBrand } from '../utils/brand';\n\n/**\n * Options for `model()`. Every form control reads and writes differently; the\n * defaults cover native inputs, and `get`/`event` override them for anything else.\n */\nexport type ModelOptions<T = unknown> = {\n /**\n * Event that triggers the write-back from the control to the signal.\n * Defaults to `'change'` for checkboxes, radios, and `<select>`, and\n * `'input'` for everything else.\n */\n event?: string;\n /**\n * Reads the current value out of the element on each write-back event.\n * Defaults to `el.checked` for checkboxes/radios and `el.value` otherwise\n * (always a string for native text/number inputs: pass a `get` like\n * `(el) => Number(el.value)` to keep a numeric signal numeric).\n */\n get?: (el: HTMLElement) => T;\n};\n\n/**\n * Per-binding marker produced by `model()`. Like `live()`, the two-way behavior\n * belongs to this one binding site, never to the signal itself.\n *\n * @internal Consumed by the template binding layer (see `template/instantiator.ts`).\n */\nexport type ModelSource<T = unknown> = {\n readonly event?: string;\n readonly get?: (el: HTMLElement) => T;\n readonly source: Signal<T>;\n};\n\nconst modelBrand = makeBrand<ModelSource>('ore:model');\n\nexport const isModelSource = modelBrand.is;\n\n/** @internal Default value reader for `model()` (see template/instantiator.ts). */\nexport const modelDefaultGet = (el: HTMLElement): unknown => {\n if (el instanceof HTMLInputElement && (el.type === 'checkbox' || el.type === 'radio')) return el.checked;\n\n return (el as HTMLInputElement).value;\n};\n\n/** @internal Default write-back event for `model()` (see template/instantiator.ts). */\nexport const modelDefaultEvent = (el: HTMLElement): string =>\n el instanceof HTMLInputElement && (el.type === 'checkbox' || el.type === 'radio')\n ? 'change'\n : el instanceof HTMLSelectElement\n ? 'change'\n : 'input';\n\n/**\n * Two-way binding for a form control: `value=\"${model(signal)}\"`.\n *\n * Writes the signal to the control's `value`/`checked` property on every change,\n * and writes user input back into the signal on the control's change event\n * (`'change'` for checkboxes/radios/selects, `'input'` otherwise). This is the\n * complete controlled-input loop: the `live()` + manual `@input` handler pair it\n * replaces was easy to get half-wired.\n *\n * The signal is written with whatever `get` reads back (a string by default for\n * text inputs), so declare the signal with the matching type or pass `get`.\n *\n * @example\n * ```ts\n * const text = signal('');\n * const agree = signal(false);\n *\n * html`\n * <input value=\"${model(text)}\" />\n * <input type=\"checkbox\" checked=\"${model(agree)}\" />\n * <input type=\"number\" value=\"${model(count, { get: (el) => Number(el.value) })}\" />\n * `\n * ```\n */\nexport const model = <T>(source: Signal<T>, options?: ModelOptions<T>): ModelSource<T> =>\n modelBrand.stamp({ event: options?.event, get: options?.get, source }) as ModelSource<T>;\n"],"mappings":"A,8CAmCA,IAAMA,EAAaC,EAAuB,WAAW,EAExCC,EAAgBF,EAAW,GAG3BG,EAAmB,GAC1BC,aAAc,mBAAqBA,EAAG,OAAS,YAAcA,EAAG,OAAS,SAAiBA,EAAG,QAEzFA,EAAwB,MAIrBC,EAAqB,GAChCD,aAAc,mBAAqBA,EAAG,OAAS,YAAcA,EAAG,OAAS,UAErEA,aAAc,kBADd,SAGE,QA0BKE,GAAY,EAAmB,IAC1CN,EAAW,MAAM,CAAE,MAAOO,GAAS,MAAO,IAAKA,GAAS,IAAK,QAAO,CAAC,E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"styleMap.cjs","names":[],"sources":["../../src/directives/styleMap.ts"],"sourcesContent":["import { computed, type Readable } from '@vielzeug/ripple';\n\nimport { cssDeclarationName, cssDeclarationValue } from '../utils/dom';\n\ntype StyleInput =\n | string\n | number\n | null\n | undefined\n | false\n | (() => string | number | null | undefined | false)\n | Readable<string | number | null | undefined | false>;\n\n/**\n * Builds a reactive inline style string from a style object.\n *\n * Takes a record of CSS property names (camelCase) with values that can be:\n * - Static strings/numbers\n * - Functions that return strings/numbers\n * - Signals for reactive updates\n *\n * @example\n * ```ts\n * const color = signal('red');\n * const size = signal(16);\n *\n * html`<div style=${styleMap({\n * backgroundColor: color,\n * width: () => `${size.value}px`,\n * padding: '10px'\n * })}></div>`\n * ```\n */\nexport const styleMap = (record: Record<string, StyleInput>): Readable<string> => {\n return computed(() => {\n const declarations: string[] = [];\n\n for (const [name, input] of Object.entries(record)) {\n const value = cssDeclarationValue(input);\n\n if (!value) continue;\n\n const safeName = cssDeclarationName(name);\n\n if (!safeName) continue;\n\n declarations.push(`${safeName}:${value}`);\n }\n\n return declarations.join(';');\n });\n};\n"],"mappings":"sEAiCA,
|
|
1
|
+
{"version":3,"file":"styleMap.cjs","names":["computed","cssDeclarationValue","cssDeclarationName","require_dom","_vielzeug_ripple","styleMap","declarations","name","input","record","value","safeName"],"sources":["../../src/directives/styleMap.ts"],"sourcesContent":["import { computed, type Readable } from '@vielzeug/ripple';\n\nimport { cssDeclarationName, cssDeclarationValue } from '../utils/dom';\n\ntype StyleInput =\n | string\n | number\n | null\n | undefined\n | false\n | (() => string | number | null | undefined | false)\n | Readable<string | number | null | undefined | false>;\n\n/**\n * Builds a reactive inline style string from a style object.\n *\n * Takes a record of CSS property names (camelCase) with values that can be:\n * - Static strings/numbers\n * - Functions that return strings/numbers\n * - Signals for reactive updates\n *\n * @example\n * ```ts\n * const color = signal('red');\n * const size = signal(16);\n *\n * html`<div style=${styleMap({\n * backgroundColor: color,\n * width: () => `${size.value}px`,\n * padding: '10px'\n * })}></div>`\n * ```\n */\nexport const styleMap = (record: Record<string, StyleInput>): Readable<string> => {\n return computed(() => {\n const declarations: string[] = [];\n\n for (const [name, input] of Object.entries(record)) {\n const value = cssDeclarationValue(input);\n\n if (!value) continue;\n\n const safeName = cssDeclarationName(name);\n\n if (!safeName) continue;\n\n declarations.push(`${safeName}:${value}`);\n }\n\n return declarations.join(';');\n });\n};\n"],"mappings":"A,sEAiCA,IAAaK,EAAY,IACvB,EAAOL,EAAAA,SAAAA,KAAe,CACpB,IAAMM,EAAyB,CAAC,EAEhC,IAAK,GAAM,CAACC,EAAMC,KAAU,OAAO,QAAQC,CAAM,EAAG,CAClD,IAAMC,EAAQT,EAAAA,oBAAoBO,CAAK,EAEvC,GAAI,CAACE,EAAO,SAEZ,IAAMC,EAAWT,EAAAA,mBAAmBK,CAAI,EAEnCI,GAELL,EAAa,KAAK,GAAGK,EAAS,GAAGD,GAAO,CAC1C,CAEA,OAAOJ,EAAa,KAAK,GAAG,CAC9B,CAAC,E"}
|