{"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 & lifecycle events ──────────────────────────────────────\n// Internal to BaseElement — the only state machine and event dispatcher 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\nconst LIFECYCLE_EVENTS = {\n  CONNECT: 'ore:connect',\n  DISCONNECT: 'ore:disconnect',\n} as const;\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    this.dispatchEvent(new CustomEvent(LIFECYCLE_EVENTS.CONNECT, { bubbles: false, composed: false }));\n  }\n\n  attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n    if (oldValue === newValue) return;\n\n    const propMeta = getPropMeta(this, name);\n\n    if (!propMeta) return;\n\n    const parsed = propMeta.parse(newValue);\n\n    if (\n      !Object.is(\n        untrack(() => propMeta.signal.value),\n        parsed,\n      )\n    )\n      propMeta.signal.value = parsed as never;\n  }\n\n  disconnectedCallback(): void {\n    this._component.generation++;\n    this._component.phase = ComponentPhase.UNMOUNTED;\n    this.dispatchEvent(new CustomEvent(LIFECYCLE_EVENTS.DISCONNECT, { bubbles: false, composed: false }));\n    this._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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"}