@elurjs/core 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +157 -0
- package/dist/lib/async.cjs +16 -0
- package/dist/lib/async.js +123 -0
- package/dist/lib/component.cjs +2 -0
- package/dist/lib/component.js +66 -0
- package/dist/lib/context.cjs +2 -0
- package/dist/lib/context.js +52 -0
- package/dist/lib/devtools.cjs +59 -0
- package/dist/lib/devtools.js +419 -0
- package/dist/lib/elur/async.cjs +16 -0
- package/dist/lib/elur/async.cjs.map +1 -0
- package/dist/lib/elur/async.d.cts +32 -0
- package/dist/lib/elur/async.d.ts +32 -0
- package/dist/lib/elur/async.js +123 -0
- package/dist/lib/elur/async.js.map +1 -0
- package/dist/lib/elur/component.cjs +2 -0
- package/dist/lib/elur/component.cjs.map +1 -0
- package/dist/lib/elur/component.d.cts +17 -0
- package/dist/lib/elur/component.d.ts +17 -0
- package/dist/lib/elur/component.js +66 -0
- package/dist/lib/elur/component.js.map +1 -0
- package/dist/lib/elur/context.cjs +2 -0
- package/dist/lib/elur/context.cjs.map +1 -0
- package/dist/lib/elur/context.d.cts +29 -0
- package/dist/lib/elur/context.d.ts +29 -0
- package/dist/lib/elur/context.js +52 -0
- package/dist/lib/elur/context.js.map +1 -0
- package/dist/lib/elur/devtools.cjs +59 -0
- package/dist/lib/elur/devtools.cjs.map +1 -0
- package/dist/lib/elur/devtools.d.cts +24 -0
- package/dist/lib/elur/devtools.d.ts +24 -0
- package/dist/lib/elur/devtools.js +419 -0
- package/dist/lib/elur/devtools.js.map +1 -0
- package/dist/lib/elur/form.cjs +2 -0
- package/dist/lib/elur/form.cjs.map +1 -0
- package/dist/lib/elur/form.d.cts +283 -0
- package/dist/lib/elur/form.d.ts +283 -0
- package/dist/lib/elur/form.js +315 -0
- package/dist/lib/elur/form.js.map +1 -0
- package/dist/lib/elur/hydrate/index.cjs +2 -0
- package/dist/lib/elur/hydrate/index.cjs.map +1 -0
- package/dist/lib/elur/hydrate/index.d.cts +13 -0
- package/dist/lib/elur/hydrate/index.d.ts +13 -0
- package/dist/lib/elur/hydrate/index.js +410 -0
- package/dist/lib/elur/hydrate/index.js.map +1 -0
- package/dist/lib/elur/index.d.cts +19 -0
- package/dist/lib/elur/index.d.ts +19 -0
- package/dist/lib/elur/lifecycle.cjs +2 -0
- package/dist/lib/elur/lifecycle.cjs.map +1 -0
- package/dist/lib/elur/lifecycle.d.cts +45 -0
- package/dist/lib/elur/lifecycle.d.ts +45 -0
- package/dist/lib/elur/lifecycle.js +39 -0
- package/dist/lib/elur/lifecycle.js.map +1 -0
- package/dist/lib/elur/plugins.cjs +2 -0
- package/dist/lib/elur/plugins.cjs.map +1 -0
- package/dist/lib/elur/plugins.d.cts +48 -0
- package/dist/lib/elur/plugins.d.ts +48 -0
- package/dist/lib/elur/plugins.js +59 -0
- package/dist/lib/elur/plugins.js.map +1 -0
- package/dist/lib/elur/reactivity.cjs +2 -0
- package/dist/lib/elur/reactivity.cjs.map +1 -0
- package/dist/lib/elur/reactivity.d.cts +60 -0
- package/dist/lib/elur/reactivity.d.ts +60 -0
- package/dist/lib/elur/reactivity.js +188 -0
- package/dist/lib/elur/reactivity.js.map +1 -0
- package/dist/lib/elur/router.cjs +12 -0
- package/dist/lib/elur/router.cjs.map +1 -0
- package/dist/lib/elur/router.d.cts +137 -0
- package/dist/lib/elur/router.d.ts +137 -0
- package/dist/lib/elur/router.js +513 -0
- package/dist/lib/elur/router.js.map +1 -0
- package/dist/lib/elur/server/index.cjs +2 -0
- package/dist/lib/elur/server/index.cjs.map +1 -0
- package/dist/lib/elur/server/index.d.cts +44 -0
- package/dist/lib/elur/server/index.d.ts +44 -0
- package/dist/lib/elur/server/index.js +307 -0
- package/dist/lib/elur/server/index.js.map +1 -0
- package/dist/lib/elur/store.cjs +2 -0
- package/dist/lib/elur/store.cjs.map +1 -0
- package/dist/lib/elur/store.d.cts +137 -0
- package/dist/lib/elur/store.d.ts +137 -0
- package/dist/lib/elur/store.js +143 -0
- package/dist/lib/elur/store.js.map +1 -0
- package/dist/lib/elur/template/bindings.cjs +2 -0
- package/dist/lib/elur/template/bindings.cjs.map +1 -0
- package/dist/lib/elur/template/bindings.d.cts +40 -0
- package/dist/lib/elur/template/bindings.d.ts +40 -0
- package/dist/lib/elur/template/bindings.js +214 -0
- package/dist/lib/elur/template/bindings.js.map +1 -0
- package/dist/lib/elur/template/dom-write.cjs +2 -0
- package/dist/lib/elur/template/dom-write.cjs.map +1 -0
- package/dist/lib/elur/template/dom-write.d.cts +5 -0
- package/dist/lib/elur/template/dom-write.d.ts +5 -0
- package/dist/lib/elur/template/dom-write.js +16 -0
- package/dist/lib/elur/template/dom-write.js.map +1 -0
- package/dist/lib/elur/template/error-boundary.cjs +2 -0
- package/dist/lib/elur/template/error-boundary.cjs.map +1 -0
- package/dist/lib/elur/template/error-boundary.d.cts +7 -0
- package/dist/lib/elur/template/error-boundary.d.ts +7 -0
- package/dist/lib/elur/template/error-boundary.js +90 -0
- package/dist/lib/elur/template/error-boundary.js.map +1 -0
- package/dist/lib/elur/template/html.cjs +2 -0
- package/dist/lib/elur/template/html.cjs.map +1 -0
- package/dist/lib/elur/template/html.d.cts +8 -0
- package/dist/lib/elur/template/html.d.ts +8 -0
- package/dist/lib/elur/template/html.js +104 -0
- package/dist/lib/elur/template/html.js.map +1 -0
- package/dist/lib/elur/template/index.cjs +1 -0
- package/dist/lib/elur/template/index.d.cts +15 -0
- package/dist/lib/elur/template/index.d.ts +15 -0
- package/dist/lib/elur/template/index.js +13 -0
- package/dist/lib/elur/template/keyed-diff.cjs +2 -0
- package/dist/lib/elur/template/keyed-diff.cjs.map +1 -0
- package/dist/lib/elur/template/keyed-diff.d.cts +32 -0
- package/dist/lib/elur/template/keyed-diff.d.ts +32 -0
- package/dist/lib/elur/template/keyed-diff.js +100 -0
- package/dist/lib/elur/template/keyed-diff.js.map +1 -0
- package/dist/lib/elur/template/keyed.cjs +2 -0
- package/dist/lib/elur/template/keyed.cjs.map +1 -0
- package/dist/lib/elur/template/keyed.d.cts +27 -0
- package/dist/lib/elur/template/keyed.d.ts +27 -0
- package/dist/lib/elur/template/keyed.js +52 -0
- package/dist/lib/elur/template/keyed.js.map +1 -0
- package/dist/lib/elur/template/mount-helpers.cjs +2 -0
- package/dist/lib/elur/template/mount-helpers.cjs.map +1 -0
- package/dist/lib/elur/template/mount-helpers.d.cts +29 -0
- package/dist/lib/elur/template/mount-helpers.d.ts +29 -0
- package/dist/lib/elur/template/mount-helpers.js +132 -0
- package/dist/lib/elur/template/mount-helpers.js.map +1 -0
- package/dist/lib/elur/template/node-binding.cjs +2 -0
- package/dist/lib/elur/template/node-binding.cjs.map +1 -0
- package/dist/lib/elur/template/node-binding.d.cts +6 -0
- package/dist/lib/elur/template/node-binding.d.ts +6 -0
- package/dist/lib/elur/template/node-binding.js +95 -0
- package/dist/lib/elur/template/node-binding.js.map +1 -0
- package/dist/lib/elur/template/portal.cjs +2 -0
- package/dist/lib/elur/template/portal.cjs.map +1 -0
- package/dist/lib/elur/template/portal.d.cts +19 -0
- package/dist/lib/elur/template/portal.d.ts +19 -0
- package/dist/lib/elur/template/portal.js +49 -0
- package/dist/lib/elur/template/portal.js.map +1 -0
- package/dist/lib/elur/template/raw.cjs +2 -0
- package/dist/lib/elur/template/raw.cjs.map +1 -0
- package/dist/lib/elur/template/raw.d.cts +9 -0
- package/dist/lib/elur/template/raw.d.ts +9 -0
- package/dist/lib/elur/template/raw.js +29 -0
- package/dist/lib/elur/template/raw.js.map +1 -0
- package/dist/lib/elur/template/sanitize.cjs +2 -0
- package/dist/lib/elur/template/sanitize.cjs.map +1 -0
- package/dist/lib/elur/template/sanitize.d.cts +16 -0
- package/dist/lib/elur/template/sanitize.d.ts +16 -0
- package/dist/lib/elur/template/sanitize.js +29 -0
- package/dist/lib/elur/template/sanitize.js.map +1 -0
- package/dist/lib/elur/template/transitions.cjs +2 -0
- package/dist/lib/elur/template/transitions.cjs.map +1 -0
- package/dist/lib/elur/template/transitions.d.cts +43 -0
- package/dist/lib/elur/template/transitions.d.ts +43 -0
- package/dist/lib/elur/template/transitions.js +96 -0
- package/dist/lib/elur/template/transitions.js.map +1 -0
- package/dist/lib/elur/template/types.cjs +2 -0
- package/dist/lib/elur/template/types.cjs.map +1 -0
- package/dist/lib/elur/template/types.d.cts +114 -0
- package/dist/lib/elur/template/types.d.ts +114 -0
- package/dist/lib/elur/template/types.js +23 -0
- package/dist/lib/elur/template/types.js.map +1 -0
- package/dist/lib/elur.cjs +1 -0
- package/dist/lib/elur.js +18 -0
- package/dist/lib/form.cjs +2 -0
- package/dist/lib/form.js +315 -0
- package/dist/lib/hydrate.cjs +2 -0
- package/dist/lib/hydrate.js +410 -0
- package/dist/lib/index.cjs +1 -0
- package/dist/lib/index.d.cts +2 -0
- package/dist/lib/index.d.ts +2 -0
- package/dist/lib/index.js +18 -0
- package/dist/lib/lifecycle.cjs +2 -0
- package/dist/lib/lifecycle.js +39 -0
- package/dist/lib/plugins.cjs +2 -0
- package/dist/lib/plugins.js +59 -0
- package/dist/lib/router.cjs +12 -0
- package/dist/lib/router.js +513 -0
- package/dist/lib/server.cjs +2 -0
- package/dist/lib/server.js +307 -0
- package/dist/lib/signals.cjs +2 -0
- package/dist/lib/signals.js +188 -0
- package/dist/lib/store.cjs +2 -0
- package/dist/lib/store.js +143 -0
- package/dist/lib/template.cjs +1 -0
- package/dist/lib/template.js +13 -0
- package/package.json +216 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signals.js","names":[],"sources":["../../src/elur/reactivity.ts"],"sourcesContent":["// --- Dependency tracking ---\n\ninterface _EffectCtx {\n effect: (() => void) | null;\n deps: Set<Signal<any>> | null;\n}\n\ninterface _ReactivityGlobalState {\n ctxPool: _EffectCtx[];\n ctxStack: _EffectCtx[];\n activeEffect: (() => void) | null;\n activeDeps: Set<Signal<any>> | null;\n activeErrorHandler: ((err: unknown) => void) | null;\n errorHandlerStack: (((err: unknown) => void) | null)[];\n batchLevel: number;\n pendingEffectsSet: Set<() => void>;\n pendingEffectsArr: (() => void)[];\n effectDepth: number;\n notifyBuf: ((() => void) | null)[];\n notifyBase: number;\n signalDebugHooks: _SignalDebugHooks | null;\n}\n\nfunction _createReactivityState(): _ReactivityGlobalState {\n return {\n ctxPool: [],\n ctxStack: [],\n activeEffect: null,\n activeDeps: null,\n activeErrorHandler: null,\n errorHandlerStack: [],\n batchLevel: 0,\n pendingEffectsSet: new Set<() => void>(),\n pendingEffectsArr: [],\n effectDepth: 0,\n notifyBuf: [],\n notifyBase: 0,\n signalDebugHooks: null,\n };\n}\n\nconst _reactivityStateKey = Symbol.for(\"elur/reactivity-state\");\nconst _globalObj = globalThis as Record<PropertyKey, unknown>;\nconst _state = (() => {\n const existing = _globalObj[_reactivityStateKey] as _ReactivityGlobalState | undefined;\n if (existing) return existing;\n const created = _createReactivityState();\n _globalObj[_reactivityStateKey] = created;\n return created;\n})();\n\n// --- Error boundary support ---\n\n/**\n * @internal — Register an error boundary handler. All `effect()` calls made\n * synchronously while this handler is active will capture it. When those\n * effects re-run and throw, the captured handler is invoked.\n */\nexport function _pushErrorHandler(h: (err: unknown) => void): void {\n _state.errorHandlerStack.push(_state.activeErrorHandler);\n _state.activeErrorHandler = h;\n}\n\n/** @internal — Restore the previous error boundary handler. */\nexport function _popErrorHandler(): void {\n _state.activeErrorHandler = _state.errorHandlerStack.pop() ?? null;\n}\n\n// --- Batching ---\n\n/**\n * Guards against synchronous effect re-entrancy.\n *\n * Effects re-run synchronously when a signal they depend on changes. If an\n * effect writes to a signal it also reads, this creates a synchronous loop.\n * The guard below caps re-execution at MAX_EFFECT_DEPTH (100) iterations and\n * throws an error to prevent stack overflow.\n *\n * Throwing is deliberate: when the guard fires, the reactive graph is in an\n * inconsistent state — the effect partially executed, dependencies may be\n * stale, and continuing could produce incorrect UI. Throwing surfaces the\n * bug immediately instead of silently degrading.\n *\n * To avoid re-entrancy:\n * 1. Use `computed()` for derived state — computed signals cache their result\n * and only recompute when dependencies change. They never write to other\n * signals, so they can't loop.\n * 2. Use `watch()` for side effects — watch callbacks receive the new value;\n * if you need to update other signals, do it inside `batch()` to coalesce\n * notifications.\n * 3. Never write to a signal you read in the same effect — this is the direct\n * cause of re-entrancy. If you need to transform a value, use `computed()`\n * instead.\n * 4. Use `batch()` to group writes — batched writes flush once at the end,\n * preventing intermediate re-runs.\n */\n\nconst MAX_EFFECT_DEPTH = 100;\nconst _NOTIFY_SHRINK_TRIGGER = 64;\nconst _NOTIFY_LOW_USAGE = 16;\nconst _NOTIFY_SHRINK_TO = 32;\n\nexport interface _SignalDebugHooks {\n onCreate?: (signal: Signal<any>, initialValue: unknown) => void;\n onWrite?: (signal: Signal<any>, value: unknown) => void;\n}\n\nexport function _setSignalDebugHooks(hooks: _SignalDebugHooks | null): void {\n _state.signalDebugHooks = hooks;\n}\n\n/** @internal — notify buffer capacity, exposed for tests. */\nexport function _getNotifyBufSize(): number {\n return _state.notifyBuf.length;\n}\n\n// --- Signal ---\n\nexport class Signal<T> {\n private _value: T;\n private _subs = new Set<() => void>();\n\n constructor(initialValue: T) {\n this._value = initialValue;\n _state.signalDebugHooks?.onCreate?.(this, initialValue);\n }\n\n /** Read the current value. Subscribes the active effect if one exists. */\n get value(): T {\n if (_state.activeEffect) {\n this._subs.add(_state.activeEffect);\n _state.activeDeps?.add(this);\n }\n return this._value;\n }\n\n /** Write a new value. Notifies subscribers when the value changes. */\n set value(newValue: T) {\n if (Object.is(this._value, newValue)) return;\n this._value = newValue;\n _state.signalDebugHooks?.onWrite?.(this, newValue);\n this._notify();\n }\n\n /** Mutate the value via a updater function. */\n update(fn: (current: T) => T): void {\n this.value = fn(this._value);\n }\n\n /** Read without subscribing the active effect. */\n peek(): T {\n return this._value;\n }\n\n /** @internal */\n _removeSub(sub: () => void): void {\n this._subs.delete(sub);\n }\n\n private _notify(): void {\n if (_state.batchLevel > 0) {\n for (const s of this._subs) {\n if (!_state.pendingEffectsSet.has(s)) {\n _state.pendingEffectsSet.add(s);\n _state.pendingEffectsArr.push(s);\n }\n }\n return;\n }\n const base = _state.notifyBase;\n let len = 0;\n for (const s of this._subs) _state.notifyBuf[base + len++] = s;\n _state.notifyBase = base + len;\n try {\n for (let i = 0; i < len; i++) {\n const fn = _state.notifyBuf[base + i];\n _state.notifyBuf[base + i] = null!;\n fn?.();\n }\n } finally {\n _state.notifyBase = base;\n // Shrink oversized notify buffer after a low-usage top-level flush.\n if (\n base === 0 &&\n _state.notifyBuf.length > _NOTIFY_SHRINK_TRIGGER &&\n len < _NOTIFY_LOW_USAGE\n ) {\n _state.notifyBuf.length = _NOTIFY_SHRINK_TO;\n }\n }\n }\n\n dispose(): void {\n this._subs.clear();\n }\n}\n\n// --- Factories ---\n\nexport function signal<T>(initialValue: T): Signal<T> {\n return new Signal(initialValue);\n}\n\n/**\n * Runs `fn` and re-runs it whenever any signal read inside changes.\n * Returns a dispose function to tear down the effect.\n * If `fn` returns a function, it is called as cleanup before each re-run\n * and on disposal.\n */\nexport function effect(fn: () => void | (() => void)): () => void {\n let disposed = false;\n let cleanup: (() => void) | void;\n\n // Opt: Double buffering para evitar crear `new Set()` en cada ejecución\n let deps = new Set<Signal<any>>();\n let newDeps = new Set<Signal<any>>();\n\n const capturedErrorHandler = _state.activeErrorHandler;\n\n const execute = () => {\n if (disposed) return;\n if (typeof cleanup === \"function\") cleanup();\n\n // Intercambiamos los buffers. 'deps' ahora tiene los viejos, 'newDeps' está limpio para recolectar.\n const temp = deps;\n deps = newDeps;\n newDeps = temp;\n newDeps.clear();\n\n const ctx = _state.ctxPool.length > 0 ? _state.ctxPool.pop()! : { effect: null, deps: null };\n ctx.effect = _state.activeEffect;\n ctx.deps = _state.activeDeps;\n _state.ctxStack.push(ctx);\n _state.activeEffect = execute;\n _state.activeDeps = newDeps;\n\n _state.effectDepth++;\n if (_state.effectDepth > MAX_EFFECT_DEPTH) {\n _state.effectDepth = 0;\n // Restaurar desde el stack unificado\n const restored = _state.ctxStack.pop()!;\n _state.activeEffect = restored.effect;\n _state.activeDeps = restored.deps;\n restored.effect = null;\n restored.deps = null;\n _state.ctxPool.push(restored);\n throw new Error(\n \"[elur] Maximum effect re-execution depth exceeded (possible infinite loop).\"\n );\n }\n\n try {\n cleanup = fn();\n } catch (err) {\n if (capturedErrorHandler) {\n capturedErrorHandler(err);\n } else {\n throw err;\n }\n } finally {\n _state.effectDepth--;\n const restored = _state.ctxStack.pop()!;\n _state.activeEffect = restored.effect;\n _state.activeDeps = restored.deps;\n restored.effect = null; // limpiar referencias para GC\n restored.deps = null;\n _state.ctxPool.push(restored); // devolver al pool para reutilizar\n\n\n // Cleanup phase: Desuscribirse de señales que estaban en 'deps' pero NO en 'newDeps'\n for (const oldDep of deps) {\n if (!newDeps.has(oldDep)) {\n oldDep._removeSub(execute);\n }\n }\n }\n };\n\n execute();\n\n return () => {\n disposed = true;\n if (typeof cleanup === \"function\") cleanup();\n // Al desechar, usamos newDeps porque es el que quedó activo después del último execute\n for (const dep of newDeps) {\n dep._removeSub(execute);\n }\n newDeps.clear();\n deps.clear();\n };\n}\n\n/** Derived signal that recalculates when its dependencies change. */\nconst _computedSentinel = Symbol(\"elur-computed-uninitialized\");\n\nexport function computed<T>(\n fn: () => T,\n equals: (a: T, b: T) => boolean = Object.is,\n): Signal<T> & { dispose(): void } {\n const s = new Signal<T | typeof _computedSentinel>(_computedSentinel);\n let disposeEffect: (() => void) | null = null;\n let initialized = false;\n let disposed = false;\n\n const ensureInitialized = () => {\n if (initialized || disposed) return;\n initialized = true;\n // Important: initialize untracked so first computed evaluation does not\n // accidentally subscribe the currently running external effect.\n untrack(() => {\n disposeEffect = effect(() => {\n const next = fn();\n const prev = s.peek();\n // The first computed value must always be written; afterwards use\n // the supplied equality comparator (defaults to Object.is).\n if (prev === _computedSentinel || !equals(next, prev)) {\n s.value = next;\n }\n });\n });\n };\n\n const signalProto = Object.getPrototypeOf(s) as Signal<T>;\n const valueDescriptor = Object.getOwnPropertyDescriptor(signalProto, \"value\");\n if (!valueDescriptor?.get || !valueDescriptor?.set) {\n throw new Error(\"[elur] Internal error: Signal.value descriptor not found.\");\n }\n\n Object.defineProperty(s, \"value\", {\n get() {\n ensureInitialized();\n return valueDescriptor.get!.call(this) as T;\n },\n set(v: T) {\n valueDescriptor.set!.call(this, v);\n },\n configurable: true,\n });\n\n const originalDispose = s.dispose;\n\n s.dispose = () => {\n disposed = true;\n disposeEffect?.();\n disposeEffect = null;\n originalDispose.call(s); // Opt: Evitar el uso lento de .bind()\n };\n return s as Signal<T> & { dispose(): void };\n}\n\n/** Groups multiple signal writes so effects flush once at the end. */\nexport function batch(fn: () => void): void {\n _state.batchLevel++;\n try {\n fn();\n } finally {\n _state.batchLevel--;\n if (_state.batchLevel === 0 && _state.pendingEffectsArr.length > 0) {\n const len = _state.pendingEffectsArr.length;\n for (let i = 0; i < len; i++) _state.pendingEffectsArr[i]();\n _state.pendingEffectsArr.length = 0; // reset O(1) sin GC — key del cambio\n _state.pendingEffectsSet.clear();\n }\n }\n}\n\n/** Executes `fn` without subscribing to any signals read inside it. */\nexport function untrack<T>(fn: () => T): T {\n const prevEffect = _state.activeEffect;\n const prevDeps = _state.activeDeps;\n _state.activeEffect = null;\n _state.activeDeps = null;\n try {\n return fn();\n } finally {\n _state.activeEffect = prevEffect;\n _state.activeDeps = prevDeps;\n }\n}\n\n// --- watch ---\n\nexport interface WatchOptions {\n /** Fire the callback immediately with the current value. Default: `false`. */\n immediate?: boolean;\n /** Automatically dispose after the first callback invocation. Default: `false`. */\n once?: boolean;\n}\n\n/**\n * Watches a reactive source and calls `callback(newValue, oldValue)` on each change.\n * Accepts a Signal or a getter function. Returns a dispose function.\n */\nexport function watch<T>(\n source: Signal<T> | (() => T),\n callback: (newValue: T, oldValue: T | undefined) => void,\n options: WatchOptions = {}\n): () => void {\n const { immediate = false, once = false } = options;\n\n const getter: () => T =\n source instanceof Signal ? () => source.value : source;\n\n let oldValue: T | undefined;\n let isFirst = true;\n let disposed = false;\n\n const dispose = effect(() => {\n const newValue = getter();\n\n if (isFirst) {\n isFirst = false;\n if (immediate && !disposed) {\n const snap = newValue;\n untrack(() => callback(snap, undefined));\n if (once) { disposed = true; Promise.resolve().then(dispose); }\n }\n oldValue = newValue;\n return;\n }\n\n if (!disposed) {\n const snap = newValue;\n const prev = oldValue;\n oldValue = newValue;\n untrack(() => callback(snap, prev));\n if (once) { disposed = true; Promise.resolve().then(dispose); }\n }\n });\n\n return () => {\n disposed = true;\n dispose();\n };\n}\n\n// --- nextTick ---\n\n/** Returns a promise that resolves on the next microtask. Accepts an optional callback. */\nexport function nextTick(fn?: () => void): Promise<void> {\n return fn ? Promise.resolve().then(fn) : Promise.resolve();\n}"],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./signals.cjs`),t=require(`./lifecycle.cjs`),n=require(`./context.cjs`),r=require(`./elur/template/html.cjs`);var i=n.createInjectionKey(`elur:router`),a=null,o=null,s=[],c=`__elur_scroll`,l=`__elur_pos`;function u(){if(!a)throw Error(`[elur] No active router. Call createRouter() first, or instantiate an outlet that auto-bootstraps one (e.g. IonRouterOutlet).`);return a}function d(){return a!==null}function f(){return{left:window.scrollX??window.pageXOffset??0,top:window.scrollY??window.pageYOffset??0}}function ee(e){if(!e||typeof e!=`object`)return null;let t=e[c];if(!t||typeof t!=`object`)return null;let n=t.left,r=t.top;return typeof n!=`number`||typeof r!=`number`?null:{left:n,top:r}}function p(e){if(!e||typeof e!=`object`)return null;let t=e[l];return typeof t==`number`?t:null}function m(e,t,n){let r=e&&typeof e==`object`?{...e}:{};return r[c]={left:t.left,top:t.top},r[l]=n,r}function h(e){let t={};return new URLSearchParams(e).forEach((e,n)=>{t[n]=e}),t}function g(e){let t=new URLSearchParams;for(let[n,r]of Object.entries(e))r!=null&&r!==!1&&t.set(n,String(r));let n=t.toString();return n?`?`+n:``}function _(e){return e===`*`?[{kind:`wildcard`}]:e.split(`/`).filter(Boolean).map(e=>e===`*`?{kind:`wildcard`}:e.startsWith(`:`)?{kind:`param`,name:e.slice(1)}:{kind:`literal`,value:e})}function v(e,t){return t===`*`?e===``?`*`:e+`/*`:(e+(t.startsWith(`/`)?t:`/`+t)).replace(/\/+/g,`/`)||`/`}function y(e,t=``,n=[]){let r=[];for(let i of e){let e=v(t,i.path),a=[...n,i.component],o=_(e);r.push({fullPath:e,segments:o,chain:a,name:i.name,meta:i.meta,beforeEnter:i.beforeEnter,record:i}),i.children?.length&&r.push(...y(i.children,e,a))}return r}function b(e,t){let n=e.split(`/`).filter(Boolean),r=t.segments;if(r.length===1&&r[0].kind===`wildcard`)return{};let i=r.length>0&&r[r.length-1].kind===`wildcard`,a=i?r.slice(0,-1):r;if(i){if(n.length<a.length)return null}else if(n.length!==a.length)return null;let o={};for(let e=0;e<a.length;e++){let t=a[e];if(t.kind===`literal`){if(n[e]!==t.value)return null}else if(t.kind===`param`)try{o[t.name]=decodeURIComponent(n[e]??``)}catch{o[t.name]=n[e]??``}}return o}function x(e){return e.segments.reduce((e,t)=>t.kind===`literal`?e+2:t.kind===`param`?e+1:e,0)}function S(e,t){let n,r={},i=-1;for(let a of t){let t=b(e,a);if(t===null)continue;let o=x(a);o>i&&(n=a,r=t,i=o)}return n?{route:n,params:r}:void 0}function C(e){let t=e.trim();return!t||t===`/`?``:(t.startsWith(`/`)||(t=`/`+t),t.endsWith(`/`)&&(t=t.slice(0,-1)),t)}function w(){if(typeof document>`u`)return``;let e=document.querySelector(`base`);if(!e)return``;let t=e.getAttribute(`href`)||``;try{return C(new URL(t,window.location.origin).pathname)}catch{return C(t)}}function te(e){return e===!1?{allow:!1}:e===!0||e==null?{allow:!0}:typeof e==`string`?{allow:!1,redirect:e}:typeof e==`object`&&`redirect`in e&&typeof e.redirect==`string`?{allow:!1,redirect:e.redirect}:{allow:!0}}function T(t,n){let r=n?.base==null?w():C(n.base),i=n?.mode??`history`,s=i===`hash`,c=n?.scrollBehavior,l=new Map,u=!1;function d(e){return e?e.startsWith(`/`)?e:`/`+e:`/`}function _(e){let t=d(e||`/`);if(r&&t.startsWith(r)){let e=t.slice(r.length);return e===``?`/`:d(e)}return t}function v(e){let t=d(e);return r?(r+t).replace(/\/+/g,`/`)||`/`:t}function b(){let e=window.location.hash||``;if(e.startsWith(`#`)&&(e=e.slice(1)),!e)return{pathname:`/`,search:``};e.startsWith(`/`)||(e=`/`+e);let t=e.indexOf(`?`),n=t===-1?e:e.slice(0,t),r=t===-1?``:e.slice(t);return{pathname:_(n),search:r}}function x(){return s?b():{pathname:_(window.location.pathname||`/`),search:window.location.search||``}}function T(e,t){let n=v(e)+g(t);return s?`#`+n:n}function E(e,t){return d(e)+g(t)}let D=x(),O=D.pathname,k=h(D.search),A=y(t),j=new Map;for(let e of A)e.name&&(j.has(e.name)&&console.warn(`[Elur Router] Duplicate route name: "${e.name}"`),j.set(e.name,e));let M=S(O,A),N=e.signal(O),P=e.signal(M?.params??{}),F=e.signal(k),I=p(history.state)??0,L=e.signal({action:`initial`,direction:`none`}),R=e.signal(I>0);s?l.set(E(O,k),f()):history.replaceState(m(history.state,f(),I),``);function z(e){window.scrollTo(e.left,e.top)}function B(e,t,n){if(c){let r=c(e,t,n);if(!r)return;z(r);return}z(n??{left:0,top:0})}function V(e,t){let n=f();if(s){l.set(E(e,t),n);return}history.replaceState(m(history.state,n,I),``)}let H=[],U=[],W=0;function G(e,t,n,r,i){let a=[...H];n&&a.push(n);let o=++W;if(a.length===0){r();return}let s=0;function c(n){if(o!==W)return;let l=te(n);if(!l.allow){if(l.redirect&&l.redirect!==e){Q(l.redirect);return}if(l.redirect===e){r();return}i?.();return}if(s>=a.length){r();return}let u=a[s++](e,t);if(u instanceof Promise){u.then(c);return}c(u)}c(void 0)}let K=!1;function q(e,t){let n=e.indexOf(`?`),r=d((n===-1?e:e.slice(0,n))||`/`),i=n===-1?{}:h(e.slice(n)),a=t?{...i,...t}:i,o={};for(let[e,t]of Object.entries(a))t!=null&&t!==!1&&(o[e]=String(t));return{pathname:r,stringQuery:o}}function ne(e){let t=j.get(e.name);if(!t)throw Error(`[Elur Router] No route with name "${e.name}"`);return`/`+t.segments.map(t=>{if(t.kind===`literal`)return t.value;if(t.kind===`wildcard`)return``;let n=e.params?.[t.name];if(n==null)throw Error(`[Elur Router] Missing param "${t.name}" for route "${e.name}"`);return encodeURIComponent(String(n))}).filter(Boolean).join(`/`)}function J(e,t){return typeof e==`string`?q(e,t?.query):q(ne(e),{...e.query??{},...t?.query??{}})}o&&=(o(),null);let Y=(e,t,n,r,i)=>{let a=N.value,o={...F.value},s=S(e,A),c=`none`;r!=null&&(r<I?c=`back`:r>I&&(c=`forward`)),G(e,a,s?.route.beforeEnter,()=>{r!=null&&(I=r);let i=Z;Z=void 0,L.value={action:`pop`,direction:c,animation:i},P.value=s?.params??{},F.value=t,N.value=e,R.value=I>0,B(e,a,n);for(let t of U)try{t(e,a)}catch{}},()=>i(a,o))};if(s){let e=()=>{if(u){u=!1;return}let e=x(),t=h(e.search),n=l.get(E(e.pathname,t))??null;Y(e.pathname,t,n,null,(e,t)=>{u=!0,window.location.hash=T(e,t).slice(1),queueMicrotask(()=>{u=!1})})};window.addEventListener(`hashchange`,e),o=()=>window.removeEventListener(`hashchange`,e)}else{let e=e=>{let t=x(),n=h(t.search),r=ee(e.state??history.state),i=p(e.state??history.state);Y(t.pathname,n,r,i,(e,t)=>{history.pushState(m({},f(),I),``,T(e,t))})};window.addEventListener(`popstate`,e),o=()=>window.removeEventListener(`popstate`,e)}function X(e,t,n,r,i,a,o){o||(V(n,r),I+=1),L.value=a,P.value=i?.params??{},F.value=t,N.value=e,R.value=I>0;let c=T(e,t);if(s)l.set(E(e,t),{left:0,top:0}),o?history.replaceState(history.state,``,c):(u=!0,window.location.hash=c.slice(1),queueMicrotask(()=>{u=!1}));else{let e=m({},{left:0,top:0},I);o?history.replaceState(e,``,c):history.pushState(e,``,c)}B(e,n,null);for(let t of U)try{t(e,n)}catch{}}let Z;function Q(e,t){K=!0;let{pathname:n,stringQuery:r}=J(e,t),i=N.value,a={...F.value},o=S(n,A),s={action:`push`,direction:t?.direction??`forward`,animation:t?.animation};G(n,i,o?.route.beforeEnter,()=>X(n,r,i,a,o,s,!1))}function re(e,t){K=!0;let{pathname:n,stringQuery:r}=J(e,t),i=N.value,a={...F.value},o=S(n,A),s={action:`replace`,direction:t?.direction??`root`,animation:t?.animation};G(n,i,o?.route.beforeEnter,()=>X(n,r,i,a,o,s,!0))}function ie(e){e!==void 0&&(Z=e),history.back()}function ae(e){e!==void 0&&(Z=e),history.forward()}function oe(e){history.go(e)}function se(e,t=!0){let n=N.value;return t?n===e:n===e||n.startsWith(e.endsWith(`/`)?e:e+`/`)}function ce(e){let t=S(e,A);return t?{matched:!0,params:t.params,route:t.route.record}:{matched:!1,params:{},route:void 0}}function le(e){return H.push(e),()=>{let t=H.indexOf(e);t!==-1&&H.splice(t,1)}}function ue(e){return U.push(e),()=>{let t=U.indexOf(e);t!==-1&&U.splice(t,1)}}let $={current:N,params:P,query:F,intent:L,canGoBack:R,base:r||`/`,navigate:Q,replace:re,back:ie,forward:ae,go:oe,isActive:se,resolve:ce,beforeEach:le,afterEach:ue,routes:t,_flat:A,_guards:H,_base:r,_mode:i};return a&&console.warn(`[elur] A router already exists. The previous router is being replaced. Only one router instance should be active at a time.`),a=$,queueMicrotask(()=>{K||G(O,``,S(O,A)?.route.beforeEnter,()=>{},()=>{let e=T(`/`,{});s?(l.set(E(`/`,{}),{left:0,top:0}),history.replaceState(history.state,``,e)):history.replaceState(m({},{left:0,top:0},I),``,e);let t=S(`/`,A);L.value={action:`replace`,direction:`root`},N.value=`/`,P.value=t?.params??{},F.value={},R.value=I>0,B(`/`,O,null)})}),$}function E(){return n.inject(i)||u()}function D(){o&&=(o(),null),a=null,s.length=0}var O=class extends t.ElurComponent{_depth;_router;constructor(e=0,t){super(),this._depth=e,this._router=t}render(){let e=this._depth,t=this._router;return r.html`<div class="router-view">${()=>{let n=t??E(),i=S(n.current.value,n._flat);if(!i)return r.html`
|
|
2
|
+
<div style="color:#f87171;padding:16px 0">
|
|
3
|
+
404 — Route not found: <strong>${n.current.value}</strong>
|
|
4
|
+
</div>
|
|
5
|
+
`;if(e>=i.route.chain.length)return r.html`
|
|
6
|
+
<span></span>
|
|
7
|
+
`;let a=i.route.chain[e];return a?a():r.html`
|
|
8
|
+
<span></span>
|
|
9
|
+
`}}</div>`}},k=class extends t.ElurComponent{_to;_label;_router;constructor(e,t,n){super(),this._to=e,this._label=t,this._router=n}render(){let e=this._to,t=this._label,n=this._router??E(),i=e.startsWith(`/`)?e:`/`+e,a=(n._base?n._base+i:i).replace(/\/+/g,`/`);return r.html`
|
|
10
|
+
<a href=${n._mode===`hash`?`#`+a:a} style=${()=>n.current.value===e?`color:#38bdf8;font-weight:700;text-decoration:none;cursor:pointer;padding:4px 10px;border-radius:4px;background:#0c2a3a`:`color:#a3a3a3;text-decoration:none;cursor:pointer;padding:4px 10px;border-radius:4px`} @click=${t=>{t.preventDefault(),n.navigate(e)}}>${t}</a>
|
|
11
|
+
`}};function A(e){let t=s.indexOf(e);t>=0&&s.splice(t,1),s.push(e)}function j(e){let t=s.indexOf(e);t>=0&&s.splice(t,1)}function M(){let e=s.length?s[s.length-1]:a;if(!e)return null;let t=e,n=t.current.value,r=S(n,t._flat),i=r?.route.beforeEnter,o=t._guards.map((e,t)=>e.name||`beforeEach#${t+1}`);return i&&o.push(i.name||`beforeEnter`),{mode:t._mode,base:t._base||`/`,currentPath:n,params:{...t.params.value},query:{...t.query.value},matchedPath:r?.route.fullPath??null,activeGuards:{globalCount:t._guards.length,hasRouteGuard:!!i,names:o}}}exports.Link=k,exports.RouterKey=i,exports.RouterView=O,exports._debugGetRouterInternal=M,exports._debugRegisterRouter=A,exports._debugUnregisterRouter=j,exports._hasActiveRouter=d,exports._resetRouter=D,exports.createRouter=T,exports.elurRouter=E;
|
|
12
|
+
//# sourceMappingURL=router.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.cjs","names":[],"sources":["../../src/elur/router.ts"],"sourcesContent":["import { signal } from \"./reactivity.js\";\nimport type { Signal } from \"./reactivity.js\";\nimport { ElurComponent } from \"./lifecycle.js\";\nimport type { ElurTemplate } from \"./template/index.js\";\nimport { html } from \"./template/index.js\";\nimport { createInjectionKey, inject } from \"./context.js\";\n\n// =============================================================================\n// Public types\n// =============================================================================\n\n/**\n * Value returned (or resolved) by a navigation guard.\n *\n * - `true` / `void` / `undefined` — allow.\n * - `false` — cancel (no redirect).\n * - `string` — redirect to that path.\n * - `{ redirect: string }` — redirect, object form.\n *\n * The object form exists so guards written for the outlet API can be reused\n * verbatim by the core. See `elur-ionic`'s `GuardResult`.\n */\nexport type NavigationGuardResult =\n | void\n | undefined\n | boolean\n | string\n | { redirect: string };\n\n/** Guard function invoked before navigation commits. */\nexport type NavigationGuard = (\n to: string,\n from: string,\n) => NavigationGuardResult | Promise<NavigationGuardResult>;\n\nexport interface RouteRecord {\n /** Optional unique name to enable named navigation. */\n name?: string;\n /** Route path segment. Supports literals, params (`:id`), and wildcards (`*`). */\n path: string;\n /**\n * Factory returning the view for this route level.\n *\n * OPTIONAL — when the core router is auto-bootstrapped by an outlet\n * (Ionic's IonRouterOutlet, future others), the outlet owns component\n * mounting and the core never invokes this. In that case, omit it.\n */\n component?: () => ElurTemplate | ElurComponent;\n /** Optional arbitrary metadata for guards, layouts, and auth checks. */\n meta?: Record<string, unknown>;\n /** Child routes. Paths are joined with the parent. */\n children?: RouteRecord[];\n /** Route-level guard. Runs only when entering this specific route. */\n beforeEnter?: NavigationGuard;\n}\n\n/** Callback for `afterEach` hooks — receives the committed `to` and `from` paths. */\nexport type AfterEachHook = (to: string, from: string) => void;\n\n/** Named route target for programmatic navigation. */\nexport interface NamedRouteLocation {\n name: string;\n params?: Record<string, string | number>;\n query?: Record<string, string | number | boolean | null | undefined>;\n}\n\n/** Navigation input accepted by `navigate` / `replace`. */\nexport type RouteLocation = string | NamedRouteLocation;\n\n/** Serializable scroll position used by the router for history restoration. */\nexport interface ScrollPosition {\n left: number;\n top: number;\n}\n\nexport type ScrollBehavior = (\n to: string,\n from: string,\n savedPosition: ScrollPosition | null,\n) => ScrollPosition | false | void;\n\nexport type RouterMode = \"history\" | \"hash\";\n\nexport interface ResolvedRoute {\n matched: boolean;\n params: Record<string, string>;\n route: RouteRecord | undefined;\n}\n\n// -----------------------------------------------------------------------------\n// Navigation intent\n// -----------------------------------------------------------------------------\n\nexport type NavigationAction = \"push\" | \"replace\" | \"pop\" | \"initial\";\nexport type NavigationDirection = \"forward\" | \"back\" | \"root\" | \"none\";\n\nexport interface NavigationIntent {\n action: NavigationAction;\n direction: NavigationDirection;\n animation?: unknown;\n}\n\nexport interface NavigateOptions {\n query?: Record<string, string | number | boolean | null | undefined>;\n direction?: NavigationDirection;\n animation?: unknown;\n}\n\nexport interface RouterOptions {\n base?: string;\n mode?: RouterMode;\n scrollBehavior?: ScrollBehavior;\n}\n\nexport interface Router {\n readonly current: Signal<string>;\n readonly params: Signal<Record<string, string>>;\n readonly query: Signal<Record<string, string>>;\n readonly base: string;\n readonly intent: Signal<NavigationIntent>;\n readonly canGoBack: Signal<boolean>;\n navigate(location: RouteLocation, options?: NavigateOptions): void;\n replace(location: RouteLocation, options?: NavigateOptions): void;\n back(animation?: unknown): void;\n forward(animation?: unknown): void;\n go(delta: number): void;\n isActive(path: string, exact?: boolean): boolean;\n resolve(path: string): ResolvedRoute;\n readonly routes: RouteRecord[];\n beforeEach(guard: NavigationGuard): () => void;\n afterEach(hook: AfterEachHook): () => void;\n}\n\nexport const RouterKey = createInjectionKey<Router>(\"elur:router\");\n\n// =============================================================================\n// Internals\n// =============================================================================\n\ntype Segment =\n | { kind: \"literal\"; value: string }\n | { kind: \"param\"; name: string }\n | { kind: \"wildcard\" };\n\ninterface FlatRoute {\n fullPath: string;\n segments: Segment[];\n chain: Array<(() => ElurTemplate | ElurComponent) | undefined>;\n name?: string;\n meta?: Record<string, unknown>;\n beforeEnter?: NavigationGuard;\n record: RouteRecord;\n}\n\ninterface RouterInternal extends Router {\n _flat: FlatRoute[];\n _guards: NavigationGuard[];\n _base: string;\n _mode: RouterMode;\n}\n\nlet _currentRouter: RouterInternal | null = null;\nlet _currentPopstateCleanup: (() => void) | null = null;\n\n// Routers injected via mount({ router }) are not the global singleton. DevTools\n// tracks them separately so it can inspect the router actually active in the UI.\nconst _mountedRouters: Router[] = [];\n\nconst SCROLL_STATE_KEY = \"__elur_scroll\";\nconst POSITION_STATE_KEY = \"__elur_pos\";\n\nfunction getRouter(): RouterInternal {\n if (!_currentRouter) {\n throw new Error(\n \"[elur] No active router. Call createRouter() first, \" +\n \"or instantiate an outlet that auto-bootstraps one (e.g. IonRouterOutlet).\"\n );\n }\n return _currentRouter;\n}\n\n/**\n * @internal Whether a router is currently active. Used by outlets that\n * want to auto-bootstrap if the user didn't call `createRouter()` themselves.\n */\nexport function _hasActiveRouter(): boolean {\n return _currentRouter !== null;\n}\n\n// =============================================================================\n// History state helpers\n// =============================================================================\n\nfunction getCurrentScrollPosition(): ScrollPosition {\n return {\n left: window.scrollX ?? window.pageXOffset ?? 0,\n top: window.scrollY ?? window.pageYOffset ?? 0,\n };\n}\n\nfunction readScrollPositionFromState(state: unknown): ScrollPosition | null {\n if (!state || typeof state !== \"object\") return null;\n const raw = (state as Record<string, unknown>)[SCROLL_STATE_KEY];\n if (!raw || typeof raw !== \"object\") return null;\n const left = (raw as Record<string, unknown>).left;\n const top = (raw as Record<string, unknown>).top;\n if (typeof left !== \"number\" || typeof top !== \"number\") return null;\n return { left, top };\n}\n\nfunction readPositionFromState(state: unknown): number | null {\n if (!state || typeof state !== \"object\") return null;\n const raw = (state as Record<string, unknown>)[POSITION_STATE_KEY];\n return typeof raw === \"number\" ? raw : null;\n}\n\nfunction buildHistoryState(\n prev: unknown,\n scroll: ScrollPosition,\n position: number,\n): Record<string, unknown> {\n const base = prev && typeof prev === \"object\"\n ? { ...(prev as Record<string, unknown>) }\n : {};\n base[SCROLL_STATE_KEY] = { left: scroll.left, top: scroll.top };\n base[POSITION_STATE_KEY] = position;\n return base;\n}\n\n// =============================================================================\n// Query / path helpers\n// =============================================================================\n\nfunction parseQuery(search: string): Record<string, string> {\n const result: Record<string, string> = {};\n new URLSearchParams(search).forEach((v, k) => { result[k] = v; });\n return result;\n}\n\nfunction buildQueryString(\n q: Record<string, string | number | boolean | null | undefined>,\n): string {\n const p = new URLSearchParams();\n for (const [k, v] of Object.entries(q)) {\n if (v != null && v !== false) p.set(k, String(v));\n }\n const s = p.toString();\n return s ? \"?\" + s : \"\";\n}\n\nfunction parseSegments(fullPath: string): Segment[] {\n if (fullPath === \"*\") return [{ kind: \"wildcard\" }];\n return fullPath\n .split(\"/\")\n .filter(Boolean)\n .map((part): Segment => {\n if (part === \"*\") return { kind: \"wildcard\" };\n if (part.startsWith(\":\")) return { kind: \"param\", name: part.slice(1) };\n return { kind: \"literal\", value: part };\n });\n}\n\nfunction joinPaths(parent: string, child: string): string {\n if (child === \"*\") return parent === \"\" ? \"*\" : parent + \"/*\";\n const segment = child.startsWith(\"/\") ? child : \"/\" + child;\n return (parent + segment).replace(/\\/+/g, \"/\") || \"/\";\n}\n\nfunction flattenRoutes(\n routes: RouteRecord[],\n parentPath = \"\",\n parentChain: Array<(() => ElurTemplate | ElurComponent) | undefined> = [],\n): FlatRoute[] {\n const result: FlatRoute[] = [];\n for (const route of routes) {\n const fullPath = joinPaths(parentPath, route.path);\n const chain = [...parentChain, route.component];\n const segments = parseSegments(fullPath);\n result.push({\n fullPath,\n segments,\n chain,\n name: route.name,\n meta: route.meta,\n beforeEnter: route.beforeEnter,\n record: route,\n });\n if (route.children?.length) {\n result.push(...flattenRoutes(route.children, fullPath, chain));\n }\n }\n return result;\n}\n\nfunction tryMatch(path: string, route: FlatRoute): Record<string, string> | null {\n const parts = path.split(\"/\").filter(Boolean);\n const segs = route.segments;\n if (segs.length === 1 && segs[0].kind === \"wildcard\") return {};\n const lastIsWild = segs.length > 0 && segs[segs.length - 1].kind === \"wildcard\";\n const fixedSegs = lastIsWild ? segs.slice(0, -1) : segs;\n if (lastIsWild) {\n if (parts.length < fixedSegs.length) return null;\n } else {\n if (parts.length !== fixedSegs.length) return null;\n }\n const params: Record<string, string> = {};\n for (let i = 0; i < fixedSegs.length; i++) {\n const seg = fixedSegs[i];\n if (seg.kind === \"literal\") {\n if (parts[i] !== seg.value) return null;\n } else if (seg.kind === \"param\") {\n try {\n params[seg.name] = decodeURIComponent(parts[i] ?? \"\");\n } catch {\n params[seg.name] = parts[i] ?? \"\";\n }\n }\n }\n return params;\n}\n\nfunction specificity(route: FlatRoute): number {\n return route.segments.reduce((acc, seg) => {\n if (seg.kind === \"literal\") return acc + 2;\n if (seg.kind === \"param\") return acc + 1;\n return acc;\n }, 0);\n}\n\nfunction matchFlat(\n path: string,\n flat: FlatRoute[],\n): { route: FlatRoute; params: Record<string, string> } | undefined {\n let best: FlatRoute | undefined;\n let bestParams: Record<string, string> = {};\n let bestScore = -1;\n for (const route of flat) {\n const params = tryMatch(path, route);\n if (params === null) continue;\n const score = specificity(route);\n if (score > bestScore) {\n best = route;\n bestParams = params;\n bestScore = score;\n }\n }\n return best ? { route: best, params: bestParams } : undefined;\n}\n\n// =============================================================================\n// Base path helpers\n// =============================================================================\n\nfunction normalizeBase(raw: string): string {\n let b = raw.trim();\n if (!b || b === \"/\") return \"\";\n if (!b.startsWith(\"/\")) b = \"/\" + b;\n if (b.endsWith(\"/\")) b = b.slice(0, -1);\n return b;\n}\n\nfunction detectBase(): string {\n if (typeof document === \"undefined\") return \"\";\n const baseEl = document.querySelector(\"base\");\n if (!baseEl) return \"\";\n const href = baseEl.getAttribute(\"href\") || \"\";\n try {\n const url = new URL(href, window.location.origin);\n return normalizeBase(url.pathname);\n } catch {\n return normalizeBase(href);\n }\n}\n\n// =============================================================================\n// Guard result normalization\n// =============================================================================\n\n/**\n * Normalize any guard result into the internal flow's {allow, redirect} shape.\n * Accepts: `true`, `false`, `void`/`undefined`, `string`, `{ redirect: string }`.\n */\nfunction normalizeGuardResult(\n r: NavigationGuardResult,\n): { allow: boolean; redirect?: string } {\n if (r === false) return { allow: false };\n if (r === true || r === undefined || r === null) return { allow: true };\n if (typeof r === \"string\") return { allow: false, redirect: r };\n if (typeof r === \"object\" && \"redirect\" in r && typeof r.redirect === \"string\") {\n return { allow: false, redirect: r.redirect };\n }\n // Unknown — be permissive rather than break navigation.\n return { allow: true };\n}\n\n// =============================================================================\n// createRouter\n// =============================================================================\n\nexport function createRouter(routes: RouteRecord[], options?: RouterOptions): Router {\n const _base = options?.base != null ? normalizeBase(options.base) : detectBase();\n const _mode: RouterMode = options?.mode ?? \"history\";\n const _isHashMode = _mode === \"hash\";\n const _scrollBehavior = options?.scrollBehavior;\n const _hashScrollPositions = new Map<string, ScrollPosition>();\n let _ignoreNextHashChange = false;\n\n function normalizeAppPath(raw: string): string {\n if (!raw) return \"/\";\n return raw.startsWith(\"/\") ? raw : \"/\" + raw;\n }\n\n function stripBase(rawPath: string): string {\n const path = normalizeAppPath(rawPath || \"/\");\n if (_base && path.startsWith(_base)) {\n const stripped = path.slice(_base.length);\n return stripped === \"\" ? \"/\" : normalizeAppPath(stripped);\n }\n return path;\n }\n\n function withBase(appPath: string): string {\n const p = normalizeAppPath(appPath);\n if (!_base) return p;\n return (_base + p).replace(/\\/+/g, \"/\") || \"/\";\n }\n\n function readHashLocation(): { pathname: string; search: string } {\n let raw = window.location.hash || \"\";\n if (raw.startsWith(\"#\")) raw = raw.slice(1);\n if (!raw) return { pathname: \"/\", search: \"\" };\n if (!raw.startsWith(\"/\")) raw = \"/\" + raw;\n const qIdx = raw.indexOf(\"?\");\n const pathname = qIdx === -1 ? raw : raw.slice(0, qIdx);\n const search = qIdx === -1 ? \"\" : raw.slice(qIdx);\n return { pathname: stripBase(pathname), search };\n }\n\n function readLocation(): { pathname: string; search: string } {\n if (_isHashMode) return readHashLocation();\n return {\n pathname: stripBase(window.location.pathname || \"/\"),\n search: window.location.search || \"\",\n };\n }\n\n function buildUrl(pathname: string, stringQuery: Record<string, string>): string {\n const fullPath = withBase(pathname) + buildQueryString(stringQuery);\n return _isHashMode ? \"#\" + fullPath : fullPath;\n }\n\n function routeKey(pathname: string, stringQuery: Record<string, string>): string {\n return normalizeAppPath(pathname) + buildQueryString(stringQuery);\n }\n\n // -------------------------------------------------------------------------\n // Initial state\n // -------------------------------------------------------------------------\n\n const initialLoc = readLocation();\n const initialPath = initialLoc.pathname;\n const initialQuery = parseQuery(initialLoc.search);\n const flat = flattenRoutes(routes);\n\n const _nameIndex = new Map<string, FlatRoute>();\n for (const route of flat) {\n if (!route.name) continue;\n if (_nameIndex.has(route.name)) {\n console.warn(`[Elur Router] Duplicate route name: \"${route.name}\"`);\n }\n _nameIndex.set(route.name, route);\n }\n const initialMatch = matchFlat(initialPath, flat);\n\n const current = signal(initialPath);\n const params = signal<Record<string, string>>(initialMatch?.params ?? {});\n const query = signal<Record<string, string>>(initialQuery);\n\n let _currentPosition = readPositionFromState(history.state) ?? 0;\n\n const intent = signal<NavigationIntent>({\n action: \"initial\",\n direction: \"none\",\n });\n\n const canGoBack = signal<boolean>(_currentPosition > 0);\n\n if (_isHashMode) {\n _hashScrollPositions.set(routeKey(initialPath, initialQuery), getCurrentScrollPosition());\n } else {\n history.replaceState(\n buildHistoryState(history.state, getCurrentScrollPosition(), _currentPosition),\n \"\",\n );\n }\n\n // -------------------------------------------------------------------------\n // Scroll\n // -------------------------------------------------------------------------\n\n function _scrollTo(pos: ScrollPosition): void {\n window.scrollTo(pos.left, pos.top);\n }\n\n function _applyScroll(to: string, from: string, savedPosition: ScrollPosition | null): void {\n if (_scrollBehavior) {\n const result = _scrollBehavior(to, from, savedPosition);\n if (!result) return;\n _scrollTo(result);\n return;\n }\n _scrollTo(savedPosition ?? { left: 0, top: 0 });\n }\n\n function _saveCurrentEntryScroll(pathname: string, stringQuery: Record<string, string>): void {\n const pos = getCurrentScrollPosition();\n if (_isHashMode) {\n _hashScrollPositions.set(routeKey(pathname, stringQuery), pos);\n return;\n }\n history.replaceState(\n buildHistoryState(history.state, pos, _currentPosition),\n \"\",\n );\n }\n\n // -------------------------------------------------------------------------\n // Guards\n // -------------------------------------------------------------------------\n\n const _guards: NavigationGuard[] = [];\n const _afterHooks: AfterEachHook[] = [];\n let _navGeneration = 0;\n\n function _runGuards(\n to: string,\n from: string,\n routeGuard: NavigationGuard | undefined,\n onCommit: () => void,\n onCancel?: () => void,\n ): void {\n const guards: NavigationGuard[] = [..._guards];\n if (routeGuard) guards.push(routeGuard);\n\n const gen = ++_navGeneration;\n\n if (guards.length === 0) { onCommit(); return; }\n\n let idx = 0;\n function runNext(prev: NavigationGuardResult): void {\n if (gen !== _navGeneration) return;\n\n const norm = normalizeGuardResult(prev);\n if (!norm.allow) {\n if (norm.redirect && norm.redirect !== to) {\n navigate(norm.redirect);\n return;\n }\n if (norm.redirect === to) {\n // Guarding TO the same path — treat as allow to avoid loops\n onCommit();\n return;\n }\n onCancel?.();\n return;\n }\n if (idx >= guards.length) { onCommit(); return; }\n const result = guards[idx++](to, from);\n if (result instanceof Promise) { result.then(runNext); return; }\n runNext(result);\n }\n runNext(undefined);\n }\n\n // -------------------------------------------------------------------------\n // Path / location resolution\n // -------------------------------------------------------------------------\n\n let _hasNavigated = false;\n\n function _parsePath(\n path: string,\n queryObj?: Record<string, string | number | boolean | null | undefined>,\n ): { pathname: string; stringQuery: Record<string, string> } {\n const qIdx = path.indexOf(\"?\");\n const rawPath = qIdx === -1 ? path : path.slice(0, qIdx);\n const pathname = normalizeAppPath(rawPath || \"/\");\n const inlineQ = qIdx === -1 ? {} : parseQuery(path.slice(qIdx));\n const finalQuery = queryObj ? { ...inlineQ, ...queryObj } : inlineQ;\n const stringQuery: Record<string, string> = {};\n for (const [k, v] of Object.entries(finalQuery)) {\n if (v != null && v !== false) stringQuery[k] = String(v);\n }\n return { pathname, stringQuery };\n }\n\n function _resolveNamedPath(location: NamedRouteLocation): string {\n const found = _nameIndex.get(location.name);\n if (!found) {\n throw new Error(`[Elur Router] No route with name \"${location.name}\"`);\n }\n const parts = found.segments.map((seg) => {\n if (seg.kind === \"literal\") return seg.value;\n if (seg.kind === \"wildcard\") return \"\";\n const value = location.params?.[seg.name];\n if (value == null) {\n throw new Error(\n `[Elur Router] Missing param \"${seg.name}\" for route \"${location.name}\"`,\n );\n }\n return encodeURIComponent(String(value));\n });\n return \"/\" + parts.filter(Boolean).join(\"/\");\n }\n\n function _resolveLocation(\n location: RouteLocation,\n options?: NavigateOptions,\n ): { pathname: string; stringQuery: Record<string, string> } {\n if (typeof location === \"string\") {\n return _parsePath(location, options?.query);\n }\n const pathname = _resolveNamedPath(location);\n const mergedQuery = { ...(location.query ?? {}), ...(options?.query ?? {}) };\n return _parsePath(pathname, mergedQuery);\n }\n\n // -------------------------------------------------------------------------\n // Popstate / hashchange listener\n // -------------------------------------------------------------------------\n\n if (_currentPopstateCleanup) {\n _currentPopstateCleanup();\n _currentPopstateCleanup = null;\n }\n\n const handleBrowserNav = (\n p: string,\n newQuery: Record<string, string>,\n savedPos: ScrollPosition | null,\n nextPosition: number | null,\n onCancelRestore: (from: string, fromQuery: Record<string, string>) => void,\n ) => {\n const from = current.value;\n const fromQuery = { ...query.value };\n const m = matchFlat(p, flat);\n\n let direction: NavigationDirection = \"none\";\n if (nextPosition != null) {\n if (nextPosition < _currentPosition) direction = \"back\";\n else if (nextPosition > _currentPosition) direction = \"forward\";\n }\n\n _runGuards(\n p,\n from,\n m?.route.beforeEnter,\n () => {\n if (nextPosition != null) _currentPosition = nextPosition;\n const animation = _pendingPopAnimation;\n _pendingPopAnimation = undefined;\n intent.value = { action: \"pop\", direction, animation };\n params.value = m?.params ?? {};\n query.value = newQuery;\n current.value = p;\n canGoBack.value = _currentPosition > 0;\n _applyScroll(p, from, savedPos);\n for (const hook of _afterHooks) {\n try { hook(p, from); } catch { /* ignore */ }\n }\n },\n () => onCancelRestore(from, fromQuery),\n );\n };\n\n if (_isHashMode) {\n const onHashChange = () => {\n if (_ignoreNextHashChange) {\n _ignoreNextHashChange = false;\n return;\n }\n const loc = readLocation();\n const nextQuery = parseQuery(loc.search);\n const savedPos = _hashScrollPositions.get(routeKey(loc.pathname, nextQuery)) ?? null;\n handleBrowserNav(\n loc.pathname,\n nextQuery,\n savedPos,\n null,\n (from, fromQuery) => {\n _ignoreNextHashChange = true;\n window.location.hash = buildUrl(from, fromQuery).slice(1);\n queueMicrotask(() => { _ignoreNextHashChange = false; });\n },\n );\n };\n window.addEventListener(\"hashchange\", onHashChange);\n _currentPopstateCleanup = () => window.removeEventListener(\"hashchange\", onHashChange);\n } else {\n const onPopstate = (ev: PopStateEvent) => {\n const loc = readLocation();\n const nextQuery = parseQuery(loc.search);\n const savedPos = readScrollPositionFromState(ev.state ?? history.state);\n const nextPos = readPositionFromState(ev.state ?? history.state);\n handleBrowserNav(\n loc.pathname,\n nextQuery,\n savedPos,\n nextPos,\n (from, fromQuery) => {\n history.pushState(\n buildHistoryState({}, getCurrentScrollPosition(), _currentPosition),\n \"\",\n buildUrl(from, fromQuery),\n );\n },\n );\n };\n window.addEventListener(\"popstate\", onPopstate);\n _currentPopstateCleanup = () => window.removeEventListener(\"popstate\", onPopstate);\n }\n\n // -------------------------------------------------------------------------\n // Internal commit (programmatic navigation)\n // -------------------------------------------------------------------------\n\n function _commit(\n pathname: string,\n stringQuery: Record<string, string>,\n from: string,\n fromQuery: Record<string, string>,\n m: ReturnType<typeof matchFlat>,\n nextIntent: NavigationIntent,\n useReplace: boolean,\n ): void {\n if (!useReplace) {\n _saveCurrentEntryScroll(from, fromQuery);\n _currentPosition += 1;\n }\n\n intent.value = nextIntent;\n params.value = m?.params ?? {};\n query.value = stringQuery;\n current.value = pathname;\n canGoBack.value = _currentPosition > 0;\n\n const url = buildUrl(pathname, stringQuery);\n\n if (_isHashMode) {\n _hashScrollPositions.set(routeKey(pathname, stringQuery), { left: 0, top: 0 });\n if (useReplace) {\n history.replaceState(history.state, \"\", url);\n } else {\n _ignoreNextHashChange = true;\n window.location.hash = url.slice(1);\n queueMicrotask(() => { _ignoreNextHashChange = false; });\n }\n } else {\n const nextState = buildHistoryState({}, { left: 0, top: 0 }, _currentPosition);\n if (useReplace) {\n history.replaceState(nextState, \"\", url);\n } else {\n history.pushState(nextState, \"\", url);\n }\n }\n\n _applyScroll(pathname, from, null);\n for (const hook of _afterHooks) {\n try { hook(pathname, from); } catch { /* ignore */ }\n }\n }\n\n // -------------------------------------------------------------------------\n // Public navigation API\n // -------------------------------------------------------------------------\n\n let _pendingPopAnimation: unknown = undefined;\n\n function navigate(location: RouteLocation, options?: NavigateOptions): void {\n _hasNavigated = true;\n const { pathname, stringQuery } = _resolveLocation(location, options);\n const from = current.value;\n const fromQuery = { ...query.value };\n const m = matchFlat(pathname, flat);\n\n const nextIntent: NavigationIntent = {\n action: \"push\",\n direction: options?.direction ?? \"forward\",\n animation: options?.animation,\n };\n\n _runGuards(\n pathname,\n from,\n m?.route.beforeEnter,\n () => _commit(pathname, stringQuery, from, fromQuery, m, nextIntent, false),\n );\n }\n\n function replace(location: RouteLocation, options?: NavigateOptions): void {\n _hasNavigated = true;\n const { pathname, stringQuery } = _resolveLocation(location, options);\n const from = current.value;\n const fromQuery = { ...query.value };\n const m = matchFlat(pathname, flat);\n\n const nextIntent: NavigationIntent = {\n action: \"replace\",\n direction: options?.direction ?? \"root\",\n animation: options?.animation,\n };\n\n _runGuards(\n pathname,\n from,\n m?.route.beforeEnter,\n () => _commit(pathname, stringQuery, from, fromQuery, m, nextIntent, true),\n );\n }\n\n function back(animation?: unknown): void {\n if (animation !== undefined) _pendingPopAnimation = animation;\n history.back();\n }\n\n function forward(animation?: unknown): void {\n if (animation !== undefined) _pendingPopAnimation = animation;\n history.forward();\n }\n\n function go(delta: number): void { history.go(delta); }\n\n function isActive(path: string, exact = true): boolean {\n const cur = current.value;\n if (exact) return cur === path;\n return cur === path || cur.startsWith(path.endsWith(\"/\") ? path : path + \"/\");\n }\n\n function resolve(path: string): ResolvedRoute {\n const m = matchFlat(path, flat);\n if (!m) return { matched: false, params: {}, route: undefined };\n return { matched: true, params: m.params, route: m.route.record };\n }\n\n function beforeEach(guard: NavigationGuard): () => void {\n _guards.push(guard);\n return () => {\n const idx = _guards.indexOf(guard);\n if (idx !== -1) _guards.splice(idx, 1);\n };\n }\n\n function afterEach(hook: AfterEachHook): () => void {\n _afterHooks.push(hook);\n return () => {\n const idx = _afterHooks.indexOf(hook);\n if (idx !== -1) _afterHooks.splice(idx, 1);\n };\n }\n\n const router: RouterInternal = {\n current, params, query, intent, canGoBack,\n base: _base || \"/\",\n navigate, replace, back, forward, go,\n isActive, resolve,\n beforeEach, afterEach, routes,\n _flat: flat, _guards, _base, _mode,\n };\n\n if (_currentRouter) {\n console.warn(\n \"[elur] A router already exists. The previous router is being replaced. \" +\n \"Only one router instance should be active at a time.\",\n );\n }\n _currentRouter = router;\n\n queueMicrotask(() => {\n if (_hasNavigated) return;\n\n const m = matchFlat(initialPath, flat);\n _runGuards(\n initialPath,\n \"\",\n m?.route.beforeEnter,\n () => { /* allowed */ },\n () => {\n const fallback = \"/\";\n const url = buildUrl(fallback, {});\n if (_isHashMode) {\n _hashScrollPositions.set(routeKey(fallback, {}), { left: 0, top: 0 });\n history.replaceState(history.state, \"\", url);\n } else {\n history.replaceState(\n buildHistoryState({}, { left: 0, top: 0 }, _currentPosition),\n \"\",\n url,\n );\n }\n const fm = matchFlat(fallback, flat);\n intent.value = { action: \"replace\", direction: \"root\" };\n current.value = fallback;\n params.value = fm?.params ?? {};\n query.value = {};\n canGoBack.value = _currentPosition > 0;\n _applyScroll(fallback, initialPath, null);\n },\n );\n });\n\n return router;\n}\n\nexport function elurRouter(): Router {\n const injected = inject(RouterKey);\n if (injected) return injected;\n return getRouter();\n}\n\n/** @internal */\nexport function _resetRouter(): void {\n if (_currentPopstateCleanup) {\n _currentPopstateCleanup();\n _currentPopstateCleanup = null;\n }\n _currentRouter = null;\n _mountedRouters.length = 0;\n}\n\nexport class RouterView extends ElurComponent {\n private _depth: number;\n private _router?: RouterInternal;\n\n constructor(depth = 0, router?: Router) {\n super();\n this._depth = depth;\n this._router = router as RouterInternal | undefined;\n }\n\n render(): ElurTemplate {\n const depth = this._depth;\n const explicitRouter = this._router;\n return html`<div class=\"router-view\">${() => {\n const router = explicitRouter ?? elurRouter() as RouterInternal;\n const matched = matchFlat(router.current.value, router._flat);\n if (!matched) {\n return html`\n <div style=\"color:#f87171;padding:16px 0\">\n 404 — Route not found: <strong>${router.current.value}</strong>\n </div>\n `;\n }\n if (depth >= matched.route.chain.length) {\n return html`\n <span></span>\n `;\n }\n const factory = matched.route.chain[depth];\n // chain entries can be undefined when the route was registered without\n // a `component` (typical when an outlet auto-bootstraps the router).\n // In that case there's nothing to render at this depth.\n if (!factory) return html`\n <span></span>\n `;\n return factory();\n }}</div>`;\n }\n}\n\nexport class Link extends ElurComponent {\n private _to: string;\n private _label: string;\n private _router?: RouterInternal;\n\n constructor(to: string, label: string, router?: Router) {\n super();\n this._to = to;\n this._label = label;\n this._router = router as RouterInternal | undefined;\n }\n\n render(): ElurTemplate {\n const to = this._to;\n const label = this._label;\n const router = this._router ?? elurRouter() as RouterInternal;\n const appPath = to.startsWith(\"/\") ? to : \"/\" + to;\n const fullPath = (router._base ? (router._base + appPath) : appPath).replace(/\\/+/g, \"/\");\n const href = router._mode === \"hash\" ? \"#\" + fullPath : fullPath;\n return html`\n <a href=${href} style=${() => router.current.value === to\n ? \"color:#38bdf8;font-weight:700;text-decoration:none;cursor:pointer;padding:4px 10px;border-radius:4px;background:#0c2a3a\"\n : \"color:#a3a3a3;text-decoration:none;cursor:pointer;padding:4px 10px;border-radius:4px\"} @click=${(e: Event) => { e.preventDefault(); router.navigate(to); }}>${label}</a>\n `;\n }\n}\n\nexport interface _RouterDebugInternal {\n mode: RouterMode;\n base: string;\n currentPath: string;\n params: Record<string, string>;\n query: Record<string, string>;\n matchedPath: string | null;\n activeGuards: { globalCount: number; hasRouteGuard: boolean; names: string[] };\n}\n\n/** @internal Register a router that was injected via mount({ router }). */\nexport function _debugRegisterRouter(router: Router): void {\n const idx = _mountedRouters.indexOf(router);\n if (idx >= 0) _mountedRouters.splice(idx, 1);\n _mountedRouters.push(router);\n}\n\n/** @internal Unregister a router when its mount point is unmounted. */\nexport function _debugUnregisterRouter(router: Router): void {\n const idx = _mountedRouters.indexOf(router);\n if (idx >= 0) _mountedRouters.splice(idx, 1);\n}\n\nexport function _debugGetRouterInternal(): _RouterDebugInternal | null {\n // Prefer the most recently mounted injected router over the global singleton\n // so devtools inspect the router actually active in the current UI.\n const router = _mountedRouters.length\n ? _mountedRouters[_mountedRouters.length - 1]\n : _currentRouter;\n if (!router) return null;\n const internal = router as RouterInternal;\n const currentPath = internal.current.value;\n const matched = matchFlat(currentPath, internal._flat);\n const routeGuard = matched?.route.beforeEnter;\n const names = internal._guards.map((g, idx) => g.name || `beforeEach#${idx + 1}`);\n if (routeGuard) names.push(routeGuard.name || \"beforeEnter\");\n return {\n mode: internal._mode,\n base: internal._base || \"/\",\n currentPath,\n params: { ...internal.params.value },\n query: { ...internal.query.value },\n matchedPath: matched?.route.fullPath ?? null,\n activeGuards: {\n globalCount: internal._guards.length,\n hasRouteGuard: Boolean(routeGuard),\n names,\n },\n };\n}"],"mappings":"kMAqIA,IAAa,EAAY,EAAA,mBAA2B,cAAc,CA4B9D,EAAwC,KACxC,EAA+C,KAI7C,EAA4B,EAAE,CAE9B,EAAmB,gBACnB,EAAqB,aAE3B,SAAS,GAA4B,CACjC,GAAI,CAAC,EACD,MAAU,MACN,gIAEH,CAEL,OAAO,EAOX,SAAgB,GAA4B,CACxC,OAAO,IAAmB,KAO9B,SAAS,GAA2C,CAChD,MAAO,CACH,KAAM,OAAO,SAAW,OAAO,aAAe,EAC9C,IAAK,OAAO,SAAW,OAAO,aAAe,EAChD,CAGL,SAAS,GAA4B,EAAuC,CACxE,GAAI,CAAC,GAAS,OAAO,GAAU,SAAU,OAAO,KAChD,IAAM,EAAO,EAAkC,GAC/C,GAAI,CAAC,GAAO,OAAO,GAAQ,SAAU,OAAO,KAC5C,IAAM,EAAQ,EAAgC,KACxC,EAAO,EAAgC,IAE7C,OADI,OAAO,GAAS,UAAY,OAAO,GAAQ,SAAiB,KACzD,CAAE,OAAM,MAAK,CAGxB,SAAS,EAAsB,EAA+B,CAC1D,GAAI,CAAC,GAAS,OAAO,GAAU,SAAU,OAAO,KAChD,IAAM,EAAO,EAAkC,GAC/C,OAAO,OAAO,GAAQ,SAAW,EAAM,KAG3C,SAAS,EACL,EACA,EACA,EACuB,CACvB,IAAM,EAAO,GAAQ,OAAO,GAAS,SAC/B,CAAE,GAAI,EAAkC,CACxC,EAAE,CAGR,MAFA,GAAK,GAAoB,CAAE,KAAM,EAAO,KAAM,IAAK,EAAO,IAAK,CAC/D,EAAK,GAAsB,EACpB,EAOX,SAAS,EAAW,EAAwC,CACxD,IAAM,EAAiC,EAAE,CAEzC,OADA,IAAI,gBAAgB,EAAO,CAAC,SAAS,EAAG,IAAM,CAAE,EAAO,GAAK,GAAK,CAC1D,EAGX,SAAS,EACL,EACM,CACN,IAAM,EAAI,IAAI,gBACd,IAAK,GAAM,CAAC,EAAG,KAAM,OAAO,QAAQ,EAAE,CAC9B,GAAK,MAAQ,IAAM,IAAO,EAAE,IAAI,EAAG,OAAO,EAAE,CAAC,CAErD,IAAM,EAAI,EAAE,UAAU,CACtB,OAAO,EAAI,IAAM,EAAI,GAGzB,SAAS,EAAc,EAA6B,CAEhD,OADI,IAAa,IAAY,CAAC,CAAE,KAAM,WAAY,CAAC,CAC5C,EACF,MAAM,IAAI,CACV,OAAO,QAAQ,CACf,IAAK,GACE,IAAS,IAAY,CAAE,KAAM,WAAY,CACzC,EAAK,WAAW,IAAI,CAAS,CAAE,KAAM,QAAS,KAAM,EAAK,MAAM,EAAE,CAAE,CAChE,CAAE,KAAM,UAAW,MAAO,EAAM,CACzC,CAGV,SAAS,EAAU,EAAgB,EAAuB,CAGtD,OAFI,IAAU,IAAY,IAAW,GAAK,IAAM,EAAS,MAEjD,GADQ,EAAM,WAAW,IAAI,CAAG,EAAQ,IAAM,IAC5B,QAAQ,OAAQ,IAAI,EAAI,IAGtD,SAAS,EACL,EACA,EAAa,GACb,EAAuE,EAAE,CAC9D,CACX,IAAM,EAAsB,EAAE,CAC9B,IAAK,IAAM,KAAS,EAAQ,CACxB,IAAM,EAAW,EAAU,EAAY,EAAM,KAAK,CAC5C,EAAQ,CAAC,GAAG,EAAa,EAAM,UAAU,CACzC,EAAW,EAAc,EAAS,CACxC,EAAO,KAAK,CACR,WACA,WACA,QACA,KAAM,EAAM,KACZ,KAAM,EAAM,KACZ,YAAa,EAAM,YACnB,OAAQ,EACX,CAAC,CACE,EAAM,UAAU,QAChB,EAAO,KAAK,GAAG,EAAc,EAAM,SAAU,EAAU,EAAM,CAAC,CAGtE,OAAO,EAGX,SAAS,EAAS,EAAc,EAAiD,CAC7E,IAAM,EAAQ,EAAK,MAAM,IAAI,CAAC,OAAO,QAAQ,CACvC,EAAO,EAAM,SACnB,GAAI,EAAK,SAAW,GAAK,EAAK,GAAG,OAAS,WAAY,MAAO,EAAE,CAC/D,IAAM,EAAa,EAAK,OAAS,GAAK,EAAK,EAAK,OAAS,GAAG,OAAS,WAC/D,EAAY,EAAa,EAAK,MAAM,EAAG,GAAG,CAAG,EACnD,GAAI,MACI,EAAM,OAAS,EAAU,OAAQ,OAAO,aAExC,EAAM,SAAW,EAAU,OAAQ,OAAO,KAElD,IAAM,EAAiC,EAAE,CACzC,IAAK,IAAI,EAAI,EAAG,EAAI,EAAU,OAAQ,IAAK,CACvC,IAAM,EAAM,EAAU,GACtB,GAAI,EAAI,OAAS,cACT,EAAM,KAAO,EAAI,MAAO,OAAO,aAC5B,EAAI,OAAS,QACpB,GAAI,CACA,EAAO,EAAI,MAAQ,mBAAmB,EAAM,IAAM,GAAG,MACjD,CACJ,EAAO,EAAI,MAAQ,EAAM,IAAM,IAI3C,OAAO,EAGX,SAAS,EAAY,EAA0B,CAC3C,OAAO,EAAM,SAAS,QAAQ,EAAK,IAC3B,EAAI,OAAS,UAAkB,EAAM,EACrC,EAAI,OAAS,QAAgB,EAAM,EAChC,EACR,EAAE,CAGT,SAAS,EACL,EACA,EACgE,CAChE,IAAI,EACA,EAAqC,EAAE,CACvC,EAAY,GAChB,IAAK,IAAM,KAAS,EAAM,CACtB,IAAM,EAAS,EAAS,EAAM,EAAM,CACpC,GAAI,IAAW,KAAM,SACrB,IAAM,EAAQ,EAAY,EAAM,CAC5B,EAAQ,IACR,EAAO,EACP,EAAa,EACb,EAAY,GAGpB,OAAO,EAAO,CAAE,MAAO,EAAM,OAAQ,EAAY,CAAG,IAAA,GAOxD,SAAS,EAAc,EAAqB,CACxC,IAAI,EAAI,EAAI,MAAM,CAIlB,MAHI,CAAC,GAAK,IAAM,IAAY,IACvB,EAAE,WAAW,IAAI,GAAE,EAAI,IAAM,GAC9B,EAAE,SAAS,IAAI,GAAE,EAAI,EAAE,MAAM,EAAG,GAAG,EAChC,GAGX,SAAS,GAAqB,CAC1B,GAAI,OAAO,SAAa,IAAa,MAAO,GAC5C,IAAM,EAAS,SAAS,cAAc,OAAO,CAC7C,GAAI,CAAC,EAAQ,MAAO,GACpB,IAAM,EAAO,EAAO,aAAa,OAAO,EAAI,GAC5C,GAAI,CAEA,OAAO,EADK,IAAI,IAAI,EAAM,OAAO,SAAS,OAAO,CACxB,SAAS,MAC9B,CACJ,OAAO,EAAc,EAAK,EAYlC,SAAS,GACL,EACqC,CAQrC,OAPI,IAAM,GAAc,CAAE,MAAO,GAAO,CACpC,IAAM,IAAQ,GAAyB,KAAa,CAAE,MAAO,GAAM,CACnE,OAAO,GAAM,SAAiB,CAAE,MAAO,GAAO,SAAU,EAAG,CAC3D,OAAO,GAAM,UAAY,aAAc,GAAK,OAAO,EAAE,UAAa,SAC3D,CAAE,MAAO,GAAO,SAAU,EAAE,SAAU,CAG1C,CAAE,MAAO,GAAM,CAO1B,SAAgB,EAAa,EAAuB,EAAiC,CACjF,IAAM,EAAQ,GAAS,MAAQ,KAAqC,GAAY,CAA1C,EAAc,EAAQ,KAAK,CAC3D,EAAoB,GAAS,MAAQ,UACrC,EAAc,IAAU,OACxB,EAAkB,GAAS,eAC3B,EAAuB,IAAI,IAC7B,EAAwB,GAE5B,SAAS,EAAiB,EAAqB,CAE3C,OADK,EACE,EAAI,WAAW,IAAI,CAAG,EAAM,IAAM,EADxB,IAIrB,SAAS,EAAU,EAAyB,CACxC,IAAM,EAAO,EAAiB,GAAW,IAAI,CAC7C,GAAI,GAAS,EAAK,WAAW,EAAM,CAAE,CACjC,IAAM,EAAW,EAAK,MAAM,EAAM,OAAO,CACzC,OAAO,IAAa,GAAK,IAAM,EAAiB,EAAS,CAE7D,OAAO,EAGX,SAAS,EAAS,EAAyB,CACvC,IAAM,EAAI,EAAiB,EAAQ,CAEnC,OADK,GACG,EAAQ,GAAG,QAAQ,OAAQ,IAAI,EAAI,IADxB,EAIvB,SAAS,GAAyD,CAC9D,IAAI,EAAM,OAAO,SAAS,MAAQ,GAElC,GADI,EAAI,WAAW,IAAI,GAAE,EAAM,EAAI,MAAM,EAAE,EACvC,CAAC,EAAK,MAAO,CAAE,SAAU,IAAK,OAAQ,GAAI,CACzC,EAAI,WAAW,IAAI,GAAE,EAAM,IAAM,GACtC,IAAM,EAAO,EAAI,QAAQ,IAAI,CACvB,EAAW,IAAS,GAAK,EAAM,EAAI,MAAM,EAAG,EAAK,CACjD,EAAS,IAAS,GAAK,GAAK,EAAI,MAAM,EAAK,CACjD,MAAO,CAAE,SAAU,EAAU,EAAS,CAAE,SAAQ,CAGpD,SAAS,GAAqD,CAE1D,OADI,EAAoB,GAAkB,CACnC,CACH,SAAU,EAAU,OAAO,SAAS,UAAY,IAAI,CACpD,OAAQ,OAAO,SAAS,QAAU,GACrC,CAGL,SAAS,EAAS,EAAkB,EAA6C,CAC7E,IAAM,EAAW,EAAS,EAAS,CAAG,EAAiB,EAAY,CACnE,OAAO,EAAc,IAAM,EAAW,EAG1C,SAAS,EAAS,EAAkB,EAA6C,CAC7E,OAAO,EAAiB,EAAS,CAAG,EAAiB,EAAY,CAOrE,IAAM,EAAa,GAAc,CAC3B,EAAc,EAAW,SACzB,EAAe,EAAW,EAAW,OAAO,CAC5C,EAAO,EAAc,EAAO,CAE5B,EAAa,IAAI,IACvB,IAAK,IAAM,KAAS,EACX,EAAM,OACP,EAAW,IAAI,EAAM,KAAK,EAC1B,QAAQ,KAAK,wCAAwC,EAAM,KAAK,GAAG,CAEvE,EAAW,IAAI,EAAM,KAAM,EAAM,EAErC,IAAM,EAAe,EAAU,EAAa,EAAK,CAE3C,EAAU,EAAA,OAAO,EAAY,CAC7B,EAAS,EAAA,OAA+B,GAAc,QAAU,EAAE,CAAC,CACnE,EAAQ,EAAA,OAA+B,EAAa,CAEtD,EAAmB,EAAsB,QAAQ,MAAM,EAAI,EAEzD,EAAS,EAAA,OAAyB,CACpC,OAAQ,UACR,UAAW,OACd,CAAC,CAEI,EAAY,EAAA,OAAgB,EAAmB,EAAE,CAEnD,EACA,EAAqB,IAAI,EAAS,EAAa,EAAa,CAAE,GAA0B,CAAC,CAEzF,QAAQ,aACJ,EAAkB,QAAQ,MAAO,GAA0B,CAAE,EAAiB,CAC9E,GACH,CAOL,SAAS,EAAU,EAA2B,CAC1C,OAAO,SAAS,EAAI,KAAM,EAAI,IAAI,CAGtC,SAAS,EAAa,EAAY,EAAc,EAA4C,CACxF,GAAI,EAAiB,CACjB,IAAM,EAAS,EAAgB,EAAI,EAAM,EAAc,CACvD,GAAI,CAAC,EAAQ,OACb,EAAU,EAAO,CACjB,OAEJ,EAAU,GAAiB,CAAE,KAAM,EAAG,IAAK,EAAG,CAAC,CAGnD,SAAS,EAAwB,EAAkB,EAA2C,CAC1F,IAAM,EAAM,GAA0B,CACtC,GAAI,EAAa,CACb,EAAqB,IAAI,EAAS,EAAU,EAAY,CAAE,EAAI,CAC9D,OAEJ,QAAQ,aACJ,EAAkB,QAAQ,MAAO,EAAK,EAAiB,CACvD,GACH,CAOL,IAAM,EAA6B,EAAE,CAC/B,EAA+B,EAAE,CACnC,EAAiB,EAErB,SAAS,EACL,EACA,EACA,EACA,EACA,EACI,CACJ,IAAM,EAA4B,CAAC,GAAG,EAAQ,CAC1C,GAAY,EAAO,KAAK,EAAW,CAEvC,IAAM,EAAM,EAAE,EAEd,GAAI,EAAO,SAAW,EAAG,CAAE,GAAU,CAAE,OAEvC,IAAI,EAAM,EACV,SAAS,EAAQ,EAAmC,CAChD,GAAI,IAAQ,EAAgB,OAE5B,IAAM,EAAO,GAAqB,EAAK,CACvC,GAAI,CAAC,EAAK,MAAO,CACb,GAAI,EAAK,UAAY,EAAK,WAAa,EAAI,CACvC,EAAS,EAAK,SAAS,CACvB,OAEJ,GAAI,EAAK,WAAa,EAAI,CAEtB,GAAU,CACV,OAEJ,KAAY,CACZ,OAEJ,GAAI,GAAO,EAAO,OAAQ,CAAE,GAAU,CAAE,OACxC,IAAM,EAAS,EAAO,KAAO,EAAI,EAAK,CACtC,GAAI,aAAkB,QAAS,CAAE,EAAO,KAAK,EAAQ,CAAE,OACvD,EAAQ,EAAO,CAEnB,EAAQ,IAAA,GAAU,CAOtB,IAAI,EAAgB,GAEpB,SAAS,EACL,EACA,EACyD,CACzD,IAAM,EAAO,EAAK,QAAQ,IAAI,CAExB,EAAW,GADD,IAAS,GAAK,EAAO,EAAK,MAAM,EAAG,EAAK,GACX,IAAI,CAC3C,EAAU,IAAS,GAAK,EAAE,CAAG,EAAW,EAAK,MAAM,EAAK,CAAC,CACzD,EAAa,EAAW,CAAE,GAAG,EAAS,GAAG,EAAU,CAAG,EACtD,EAAsC,EAAE,CAC9C,IAAK,GAAM,CAAC,EAAG,KAAM,OAAO,QAAQ,EAAW,CACvC,GAAK,MAAQ,IAAM,KAAO,EAAY,GAAK,OAAO,EAAE,EAE5D,MAAO,CAAE,WAAU,cAAa,CAGpC,SAAS,GAAkB,EAAsC,CAC7D,IAAM,EAAQ,EAAW,IAAI,EAAS,KAAK,CAC3C,GAAI,CAAC,EACD,MAAU,MAAM,qCAAqC,EAAS,KAAK,GAAG,CAa1E,MAAO,IAXO,EAAM,SAAS,IAAK,GAAQ,CACtC,GAAI,EAAI,OAAS,UAAW,OAAO,EAAI,MACvC,GAAI,EAAI,OAAS,WAAY,MAAO,GACpC,IAAM,EAAQ,EAAS,SAAS,EAAI,MACpC,GAAI,GAAS,KACT,MAAU,MACN,gCAAgC,EAAI,KAAK,eAAe,EAAS,KAAK,GACzE,CAEL,OAAO,mBAAmB,OAAO,EAAM,CAAC,EAC1C,CACiB,OAAO,QAAQ,CAAC,KAAK,IAAI,CAGhD,SAAS,EACL,EACA,EACyD,CAMzD,OALI,OAAO,GAAa,SACb,EAAW,EAAU,GAAS,MAAM,CAIxC,EAFU,GAAkB,EAAS,CACxB,CAAE,GAAI,EAAS,OAAS,EAAE,CAAG,GAAI,GAAS,OAAS,EAAE,CAAG,CACpC,CAO5C,AAEI,KADA,GAAyB,CACC,MAG9B,IAAM,GACF,EACA,EACA,EACA,EACA,IACC,CACD,IAAM,EAAO,EAAQ,MACf,EAAY,CAAE,GAAG,EAAM,MAAO,CAC9B,EAAI,EAAU,EAAG,EAAK,CAExB,EAAiC,OACjC,GAAgB,OACZ,EAAe,EAAkB,EAAY,OACxC,EAAe,IAAkB,EAAY,YAG1D,EACI,EACA,EACA,GAAG,MAAM,gBACH,CACE,GAAgB,OAAM,EAAmB,GAC7C,IAAM,EAAY,EAClB,EAAuB,IAAA,GACvB,EAAO,MAAQ,CAAE,OAAQ,MAAO,YAAW,YAAW,CACtD,EAAO,MAAQ,GAAG,QAAU,EAAE,CAC9B,EAAM,MAAQ,EACd,EAAQ,MAAQ,EAChB,EAAU,MAAQ,EAAmB,EACrC,EAAa,EAAG,EAAM,EAAS,CAC/B,IAAK,IAAM,KAAQ,EACf,GAAI,CAAE,EAAK,EAAG,EAAK,MAAU,QAG/B,EAAgB,EAAM,EAAU,CACzC,EAGL,GAAI,EAAa,CACb,IAAM,MAAqB,CACvB,GAAI,EAAuB,CACvB,EAAwB,GACxB,OAEJ,IAAM,EAAM,GAAc,CACpB,EAAY,EAAW,EAAI,OAAO,CAClC,EAAW,EAAqB,IAAI,EAAS,EAAI,SAAU,EAAU,CAAC,EAAI,KAChF,EACI,EAAI,SACJ,EACA,EACA,MACC,EAAM,IAAc,CACjB,EAAwB,GACxB,OAAO,SAAS,KAAO,EAAS,EAAM,EAAU,CAAC,MAAM,EAAE,CACzD,mBAAqB,CAAE,EAAwB,IAAS,EAE/D,EAEL,OAAO,iBAAiB,aAAc,EAAa,CACnD,MAAgC,OAAO,oBAAoB,aAAc,EAAa,KACnF,CACH,IAAM,EAAc,GAAsB,CACtC,IAAM,EAAM,GAAc,CACpB,EAAY,EAAW,EAAI,OAAO,CAClC,EAAW,GAA4B,EAAG,OAAS,QAAQ,MAAM,CACjE,EAAU,EAAsB,EAAG,OAAS,QAAQ,MAAM,CAChE,EACI,EAAI,SACJ,EACA,EACA,GACC,EAAM,IAAc,CACjB,QAAQ,UACJ,EAAkB,EAAE,CAAE,GAA0B,CAAE,EAAiB,CACnE,GACA,EAAS,EAAM,EAAU,CAC5B,EAER,EAEL,OAAO,iBAAiB,WAAY,EAAW,CAC/C,MAAgC,OAAO,oBAAoB,WAAY,EAAW,CAOtF,SAAS,EACL,EACA,EACA,EACA,EACA,EACA,EACA,EACI,CACC,IACD,EAAwB,EAAM,EAAU,CACxC,GAAoB,GAGxB,EAAO,MAAQ,EACf,EAAO,MAAQ,GAAG,QAAU,EAAE,CAC9B,EAAM,MAAQ,EACd,EAAQ,MAAQ,EAChB,EAAU,MAAQ,EAAmB,EAErC,IAAM,EAAM,EAAS,EAAU,EAAY,CAE3C,GAAI,EACA,EAAqB,IAAI,EAAS,EAAU,EAAY,CAAE,CAAE,KAAM,EAAG,IAAK,EAAG,CAAC,CAC1E,EACA,QAAQ,aAAa,QAAQ,MAAO,GAAI,EAAI,EAE5C,EAAwB,GACxB,OAAO,SAAS,KAAO,EAAI,MAAM,EAAE,CACnC,mBAAqB,CAAE,EAAwB,IAAS,MAEzD,CACH,IAAM,EAAY,EAAkB,EAAE,CAAE,CAAE,KAAM,EAAG,IAAK,EAAG,CAAE,EAAiB,CAC1E,EACA,QAAQ,aAAa,EAAW,GAAI,EAAI,CAExC,QAAQ,UAAU,EAAW,GAAI,EAAI,CAI7C,EAAa,EAAU,EAAM,KAAK,CAClC,IAAK,IAAM,KAAQ,EACf,GAAI,CAAE,EAAK,EAAU,EAAK,MAAU,GAQ5C,IAAI,EAEJ,SAAS,EAAS,EAAyB,EAAiC,CACxE,EAAgB,GAChB,GAAM,CAAE,WAAU,eAAgB,EAAiB,EAAU,EAAQ,CAC/D,EAAO,EAAQ,MACf,EAAY,CAAE,GAAG,EAAM,MAAO,CAC9B,EAAI,EAAU,EAAU,EAAK,CAE7B,EAA+B,CACjC,OAAQ,OACR,UAAW,GAAS,WAAa,UACjC,UAAW,GAAS,UACvB,CAED,EACI,EACA,EACA,GAAG,MAAM,gBACH,EAAQ,EAAU,EAAa,EAAM,EAAW,EAAG,EAAY,GAAM,CAC9E,CAGL,SAAS,GAAQ,EAAyB,EAAiC,CACvE,EAAgB,GAChB,GAAM,CAAE,WAAU,eAAgB,EAAiB,EAAU,EAAQ,CAC/D,EAAO,EAAQ,MACf,EAAY,CAAE,GAAG,EAAM,MAAO,CAC9B,EAAI,EAAU,EAAU,EAAK,CAE7B,EAA+B,CACjC,OAAQ,UACR,UAAW,GAAS,WAAa,OACjC,UAAW,GAAS,UACvB,CAED,EACI,EACA,EACA,GAAG,MAAM,gBACH,EAAQ,EAAU,EAAa,EAAM,EAAW,EAAG,EAAY,GAAK,CAC7E,CAGL,SAAS,GAAK,EAA2B,CACjC,IAAc,IAAA,KAAW,EAAuB,GACpD,QAAQ,MAAM,CAGlB,SAAS,GAAQ,EAA2B,CACpC,IAAc,IAAA,KAAW,EAAuB,GACpD,QAAQ,SAAS,CAGrB,SAAS,GAAG,EAAqB,CAAE,QAAQ,GAAG,EAAM,CAEpD,SAAS,GAAS,EAAc,EAAQ,GAAe,CACnD,IAAM,EAAM,EAAQ,MAEpB,OADI,EAAc,IAAQ,EACnB,IAAQ,GAAQ,EAAI,WAAW,EAAK,SAAS,IAAI,CAAG,EAAO,EAAO,IAAI,CAGjF,SAAS,GAAQ,EAA6B,CAC1C,IAAM,EAAI,EAAU,EAAM,EAAK,CAE/B,OADK,EACE,CAAE,QAAS,GAAM,OAAQ,EAAE,OAAQ,MAAO,EAAE,MAAM,OAAQ,CADlD,CAAE,QAAS,GAAO,OAAQ,EAAE,CAAE,MAAO,IAAA,GAAW,CAInE,SAAS,GAAW,EAAoC,CAEpD,OADA,EAAQ,KAAK,EAAM,KACN,CACT,IAAM,EAAM,EAAQ,QAAQ,EAAM,CAC9B,IAAQ,IAAI,EAAQ,OAAO,EAAK,EAAE,EAI9C,SAAS,GAAU,EAAiC,CAEhD,OADA,EAAY,KAAK,EAAK,KACT,CACT,IAAM,EAAM,EAAY,QAAQ,EAAK,CACjC,IAAQ,IAAI,EAAY,OAAO,EAAK,EAAE,EAIlD,IAAM,EAAyB,CAC3B,UAAS,SAAQ,QAAO,SAAQ,YAChC,KAAM,GAAS,IACf,WAAU,WAAS,QAAM,WAAS,MAClC,YAAU,WACV,cAAY,aAAW,SACvB,MAAO,EAAM,UAAS,QAAO,QAChC,CA2CD,OAzCI,GACA,QAAQ,KACJ,8HAEH,CAEL,EAAiB,EAEjB,mBAAqB,CACb,GAGJ,EACI,EACA,GAHM,EAAU,EAAa,EAAK,EAI/B,MAAM,gBACH,OACA,CACF,IACM,EAAM,EAAS,IAAU,EAAE,CAAC,CAC9B,GACA,EAAqB,IAAI,EAAS,IAAU,EAAE,CAAC,CAAE,CAAE,KAAM,EAAG,IAAK,EAAG,CAAC,CACrE,QAAQ,aAAa,QAAQ,MAAO,GAAI,EAAI,EAE5C,QAAQ,aACJ,EAAkB,EAAE,CAAE,CAAE,KAAM,EAAG,IAAK,EAAG,CAAE,EAAiB,CAC5D,GACA,EACH,CAEL,IAAM,EAAK,EAAU,IAAU,EAAK,CACpC,EAAO,MAAQ,CAAE,OAAQ,UAAW,UAAW,OAAQ,CACvD,EAAQ,MAAQ,IAChB,EAAO,MAAQ,GAAI,QAAU,EAAE,CAC/B,EAAM,MAAQ,EAAE,CAChB,EAAU,MAAQ,EAAmB,EACrC,EAAa,IAAU,EAAa,KAAK,EAEhD,EACH,CAEK,EAGX,SAAgB,GAAqB,CAGjC,OAFiB,EAAA,OAAO,EAAU,EAE3B,GAAW,CAItB,SAAgB,GAAqB,CACjC,AAEI,KADA,GAAyB,CACC,MAE9B,EAAiB,KACjB,EAAgB,OAAS,EAG7B,IAAa,EAAb,cAAgC,EAAA,aAAc,CAC1C,OACA,QAEA,YAAY,EAAQ,EAAG,EAAiB,CACpC,OAAO,CACP,KAAK,OAAS,EACd,KAAK,QAAU,EAGnB,QAAuB,CACnB,IAAM,EAAQ,KAAK,OACb,EAAiB,KAAK,QAC5B,MAAO,GAAA,IAAI,gCAAkC,CACzC,IAAM,EAAS,GAAkB,GAAY,CACvC,EAAU,EAAU,EAAO,QAAQ,MAAO,EAAO,MAAM,CAC7D,GAAI,CAAC,EACD,MAAO,GAAA,IAAI;;yDAE8B,EAAO,QAAQ,MAAM;;kBAIlE,GAAI,GAAS,EAAQ,MAAM,MAAM,OAC7B,MAAO,GAAA,IAAI;;kBAIf,IAAM,EAAU,EAAQ,MAAM,MAAM,GAOpC,OAHK,EAGE,GAAS,CAHK,EAAA,IAAI;;eAI3B,UAIG,EAAb,cAA0B,EAAA,aAAc,CACpC,IACA,OACA,QAEA,YAAY,EAAY,EAAe,EAAiB,CACpD,OAAO,CACP,KAAK,IAAM,EACX,KAAK,OAAS,EACd,KAAK,QAAU,EAGnB,QAAuB,CACnB,IAAM,EAAK,KAAK,IACV,EAAQ,KAAK,OACb,EAAS,KAAK,SAAW,GAAY,CACrC,EAAU,EAAG,WAAW,IAAI,CAAG,EAAK,IAAM,EAC1C,GAAY,EAAO,MAAS,EAAO,MAAQ,EAAW,GAAS,QAAQ,OAAQ,IAAI,CAEzF,MAAO,GAAA,IAAI;0BADE,EAAO,QAAU,OAAS,IAAM,EAAW,EAEjC,iBAAmB,EAAO,QAAQ,QAAU,EACzD,0HACA,uFAAuF,cAAe,GAAa,CAAE,EAAE,gBAAgB,CAAE,EAAO,SAAS,EAAG,EAAI,GAAG,EAAM;YAgB3L,SAAgB,EAAqB,EAAsB,CACvD,IAAM,EAAM,EAAgB,QAAQ,EAAO,CACvC,GAAO,GAAG,EAAgB,OAAO,EAAK,EAAE,CAC5C,EAAgB,KAAK,EAAO,CAIhC,SAAgB,EAAuB,EAAsB,CACzD,IAAM,EAAM,EAAgB,QAAQ,EAAO,CACvC,GAAO,GAAG,EAAgB,OAAO,EAAK,EAAE,CAGhD,SAAgB,GAAuD,CAGnE,IAAM,EAAS,EAAgB,OACzB,EAAgB,EAAgB,OAAS,GACzC,EACN,GAAI,CAAC,EAAQ,OAAO,KACpB,IAAM,EAAW,EACX,EAAc,EAAS,QAAQ,MAC/B,EAAU,EAAU,EAAa,EAAS,MAAM,CAChD,EAAa,GAAS,MAAM,YAC5B,EAAQ,EAAS,QAAQ,KAAK,EAAG,IAAQ,EAAE,MAAQ,cAAc,EAAM,IAAI,CAEjF,OADI,GAAY,EAAM,KAAK,EAAW,MAAQ,cAAc,CACrD,CACH,KAAM,EAAS,MACf,KAAM,EAAS,OAAS,IACxB,cACA,OAAQ,CAAE,GAAG,EAAS,OAAO,MAAO,CACpC,MAAO,CAAE,GAAG,EAAS,MAAM,MAAO,CAClC,YAAa,GAAS,MAAM,UAAY,KACxC,aAAc,CACV,YAAa,EAAS,QAAQ,OAC9B,cAAe,EAAQ,EACvB,QACH,CACJ"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { Signal } from "./reactivity.js";
|
|
2
|
+
import { ElurComponent } from "./lifecycle.js";
|
|
3
|
+
import type { ElurTemplate } from "./template/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* Value returned (or resolved) by a navigation guard.
|
|
6
|
+
*
|
|
7
|
+
* - `true` / `void` / `undefined` — allow.
|
|
8
|
+
* - `false` — cancel (no redirect).
|
|
9
|
+
* - `string` — redirect to that path.
|
|
10
|
+
* - `{ redirect: string }` — redirect, object form.
|
|
11
|
+
*
|
|
12
|
+
* The object form exists so guards written for the outlet API can be reused
|
|
13
|
+
* verbatim by the core. See `elur-ionic`'s `GuardResult`.
|
|
14
|
+
*/
|
|
15
|
+
export type NavigationGuardResult = void | undefined | boolean | string | {
|
|
16
|
+
redirect: string;
|
|
17
|
+
};
|
|
18
|
+
/** Guard function invoked before navigation commits. */
|
|
19
|
+
export type NavigationGuard = (to: string, from: string) => NavigationGuardResult | Promise<NavigationGuardResult>;
|
|
20
|
+
export interface RouteRecord {
|
|
21
|
+
/** Optional unique name to enable named navigation. */
|
|
22
|
+
name?: string;
|
|
23
|
+
/** Route path segment. Supports literals, params (`:id`), and wildcards (`*`). */
|
|
24
|
+
path: string;
|
|
25
|
+
/**
|
|
26
|
+
* Factory returning the view for this route level.
|
|
27
|
+
*
|
|
28
|
+
* OPTIONAL — when the core router is auto-bootstrapped by an outlet
|
|
29
|
+
* (Ionic's IonRouterOutlet, future others), the outlet owns component
|
|
30
|
+
* mounting and the core never invokes this. In that case, omit it.
|
|
31
|
+
*/
|
|
32
|
+
component?: () => ElurTemplate | ElurComponent;
|
|
33
|
+
/** Optional arbitrary metadata for guards, layouts, and auth checks. */
|
|
34
|
+
meta?: Record<string, unknown>;
|
|
35
|
+
/** Child routes. Paths are joined with the parent. */
|
|
36
|
+
children?: RouteRecord[];
|
|
37
|
+
/** Route-level guard. Runs only when entering this specific route. */
|
|
38
|
+
beforeEnter?: NavigationGuard;
|
|
39
|
+
}
|
|
40
|
+
/** Callback for `afterEach` hooks — receives the committed `to` and `from` paths. */
|
|
41
|
+
export type AfterEachHook = (to: string, from: string) => void;
|
|
42
|
+
/** Named route target for programmatic navigation. */
|
|
43
|
+
export interface NamedRouteLocation {
|
|
44
|
+
name: string;
|
|
45
|
+
params?: Record<string, string | number>;
|
|
46
|
+
query?: Record<string, string | number | boolean | null | undefined>;
|
|
47
|
+
}
|
|
48
|
+
/** Navigation input accepted by `navigate` / `replace`. */
|
|
49
|
+
export type RouteLocation = string | NamedRouteLocation;
|
|
50
|
+
/** Serializable scroll position used by the router for history restoration. */
|
|
51
|
+
export interface ScrollPosition {
|
|
52
|
+
left: number;
|
|
53
|
+
top: number;
|
|
54
|
+
}
|
|
55
|
+
export type ScrollBehavior = (to: string, from: string, savedPosition: ScrollPosition | null) => ScrollPosition | false | void;
|
|
56
|
+
export type RouterMode = "history" | "hash";
|
|
57
|
+
export interface ResolvedRoute {
|
|
58
|
+
matched: boolean;
|
|
59
|
+
params: Record<string, string>;
|
|
60
|
+
route: RouteRecord | undefined;
|
|
61
|
+
}
|
|
62
|
+
export type NavigationAction = "push" | "replace" | "pop" | "initial";
|
|
63
|
+
export type NavigationDirection = "forward" | "back" | "root" | "none";
|
|
64
|
+
export interface NavigationIntent {
|
|
65
|
+
action: NavigationAction;
|
|
66
|
+
direction: NavigationDirection;
|
|
67
|
+
animation?: unknown;
|
|
68
|
+
}
|
|
69
|
+
export interface NavigateOptions {
|
|
70
|
+
query?: Record<string, string | number | boolean | null | undefined>;
|
|
71
|
+
direction?: NavigationDirection;
|
|
72
|
+
animation?: unknown;
|
|
73
|
+
}
|
|
74
|
+
export interface RouterOptions {
|
|
75
|
+
base?: string;
|
|
76
|
+
mode?: RouterMode;
|
|
77
|
+
scrollBehavior?: ScrollBehavior;
|
|
78
|
+
}
|
|
79
|
+
export interface Router {
|
|
80
|
+
readonly current: Signal<string>;
|
|
81
|
+
readonly params: Signal<Record<string, string>>;
|
|
82
|
+
readonly query: Signal<Record<string, string>>;
|
|
83
|
+
readonly base: string;
|
|
84
|
+
readonly intent: Signal<NavigationIntent>;
|
|
85
|
+
readonly canGoBack: Signal<boolean>;
|
|
86
|
+
navigate(location: RouteLocation, options?: NavigateOptions): void;
|
|
87
|
+
replace(location: RouteLocation, options?: NavigateOptions): void;
|
|
88
|
+
back(animation?: unknown): void;
|
|
89
|
+
forward(animation?: unknown): void;
|
|
90
|
+
go(delta: number): void;
|
|
91
|
+
isActive(path: string, exact?: boolean): boolean;
|
|
92
|
+
resolve(path: string): ResolvedRoute;
|
|
93
|
+
readonly routes: RouteRecord[];
|
|
94
|
+
beforeEach(guard: NavigationGuard): () => void;
|
|
95
|
+
afterEach(hook: AfterEachHook): () => void;
|
|
96
|
+
}
|
|
97
|
+
export declare const RouterKey: import("./context.js").InjectionKey<Router>;
|
|
98
|
+
/**
|
|
99
|
+
* @internal Whether a router is currently active. Used by outlets that
|
|
100
|
+
* want to auto-bootstrap if the user didn't call `createRouter()` themselves.
|
|
101
|
+
*/
|
|
102
|
+
export declare function _hasActiveRouter(): boolean;
|
|
103
|
+
export declare function createRouter(routes: RouteRecord[], options?: RouterOptions): Router;
|
|
104
|
+
export declare function elurRouter(): Router;
|
|
105
|
+
/** @internal */
|
|
106
|
+
export declare function _resetRouter(): void;
|
|
107
|
+
export declare class RouterView extends ElurComponent {
|
|
108
|
+
private _depth;
|
|
109
|
+
private _router?;
|
|
110
|
+
constructor(depth?: number, router?: Router);
|
|
111
|
+
render(): ElurTemplate;
|
|
112
|
+
}
|
|
113
|
+
export declare class Link extends ElurComponent {
|
|
114
|
+
private _to;
|
|
115
|
+
private _label;
|
|
116
|
+
private _router?;
|
|
117
|
+
constructor(to: string, label: string, router?: Router);
|
|
118
|
+
render(): ElurTemplate;
|
|
119
|
+
}
|
|
120
|
+
export interface _RouterDebugInternal {
|
|
121
|
+
mode: RouterMode;
|
|
122
|
+
base: string;
|
|
123
|
+
currentPath: string;
|
|
124
|
+
params: Record<string, string>;
|
|
125
|
+
query: Record<string, string>;
|
|
126
|
+
matchedPath: string | null;
|
|
127
|
+
activeGuards: {
|
|
128
|
+
globalCount: number;
|
|
129
|
+
hasRouteGuard: boolean;
|
|
130
|
+
names: string[];
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/** @internal Register a router that was injected via mount({ router }). */
|
|
134
|
+
export declare function _debugRegisterRouter(router: Router): void;
|
|
135
|
+
/** @internal Unregister a router when its mount point is unmounted. */
|
|
136
|
+
export declare function _debugUnregisterRouter(router: Router): void;
|
|
137
|
+
export declare function _debugGetRouterInternal(): _RouterDebugInternal | null;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { Signal } from "./reactivity.js";
|
|
2
|
+
import { ElurComponent } from "./lifecycle.js";
|
|
3
|
+
import type { ElurTemplate } from "./template/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* Value returned (or resolved) by a navigation guard.
|
|
6
|
+
*
|
|
7
|
+
* - `true` / `void` / `undefined` — allow.
|
|
8
|
+
* - `false` — cancel (no redirect).
|
|
9
|
+
* - `string` — redirect to that path.
|
|
10
|
+
* - `{ redirect: string }` — redirect, object form.
|
|
11
|
+
*
|
|
12
|
+
* The object form exists so guards written for the outlet API can be reused
|
|
13
|
+
* verbatim by the core. See `elur-ionic`'s `GuardResult`.
|
|
14
|
+
*/
|
|
15
|
+
export type NavigationGuardResult = void | undefined | boolean | string | {
|
|
16
|
+
redirect: string;
|
|
17
|
+
};
|
|
18
|
+
/** Guard function invoked before navigation commits. */
|
|
19
|
+
export type NavigationGuard = (to: string, from: string) => NavigationGuardResult | Promise<NavigationGuardResult>;
|
|
20
|
+
export interface RouteRecord {
|
|
21
|
+
/** Optional unique name to enable named navigation. */
|
|
22
|
+
name?: string;
|
|
23
|
+
/** Route path segment. Supports literals, params (`:id`), and wildcards (`*`). */
|
|
24
|
+
path: string;
|
|
25
|
+
/**
|
|
26
|
+
* Factory returning the view for this route level.
|
|
27
|
+
*
|
|
28
|
+
* OPTIONAL — when the core router is auto-bootstrapped by an outlet
|
|
29
|
+
* (Ionic's IonRouterOutlet, future others), the outlet owns component
|
|
30
|
+
* mounting and the core never invokes this. In that case, omit it.
|
|
31
|
+
*/
|
|
32
|
+
component?: () => ElurTemplate | ElurComponent;
|
|
33
|
+
/** Optional arbitrary metadata for guards, layouts, and auth checks. */
|
|
34
|
+
meta?: Record<string, unknown>;
|
|
35
|
+
/** Child routes. Paths are joined with the parent. */
|
|
36
|
+
children?: RouteRecord[];
|
|
37
|
+
/** Route-level guard. Runs only when entering this specific route. */
|
|
38
|
+
beforeEnter?: NavigationGuard;
|
|
39
|
+
}
|
|
40
|
+
/** Callback for `afterEach` hooks — receives the committed `to` and `from` paths. */
|
|
41
|
+
export type AfterEachHook = (to: string, from: string) => void;
|
|
42
|
+
/** Named route target for programmatic navigation. */
|
|
43
|
+
export interface NamedRouteLocation {
|
|
44
|
+
name: string;
|
|
45
|
+
params?: Record<string, string | number>;
|
|
46
|
+
query?: Record<string, string | number | boolean | null | undefined>;
|
|
47
|
+
}
|
|
48
|
+
/** Navigation input accepted by `navigate` / `replace`. */
|
|
49
|
+
export type RouteLocation = string | NamedRouteLocation;
|
|
50
|
+
/** Serializable scroll position used by the router for history restoration. */
|
|
51
|
+
export interface ScrollPosition {
|
|
52
|
+
left: number;
|
|
53
|
+
top: number;
|
|
54
|
+
}
|
|
55
|
+
export type ScrollBehavior = (to: string, from: string, savedPosition: ScrollPosition | null) => ScrollPosition | false | void;
|
|
56
|
+
export type RouterMode = "history" | "hash";
|
|
57
|
+
export interface ResolvedRoute {
|
|
58
|
+
matched: boolean;
|
|
59
|
+
params: Record<string, string>;
|
|
60
|
+
route: RouteRecord | undefined;
|
|
61
|
+
}
|
|
62
|
+
export type NavigationAction = "push" | "replace" | "pop" | "initial";
|
|
63
|
+
export type NavigationDirection = "forward" | "back" | "root" | "none";
|
|
64
|
+
export interface NavigationIntent {
|
|
65
|
+
action: NavigationAction;
|
|
66
|
+
direction: NavigationDirection;
|
|
67
|
+
animation?: unknown;
|
|
68
|
+
}
|
|
69
|
+
export interface NavigateOptions {
|
|
70
|
+
query?: Record<string, string | number | boolean | null | undefined>;
|
|
71
|
+
direction?: NavigationDirection;
|
|
72
|
+
animation?: unknown;
|
|
73
|
+
}
|
|
74
|
+
export interface RouterOptions {
|
|
75
|
+
base?: string;
|
|
76
|
+
mode?: RouterMode;
|
|
77
|
+
scrollBehavior?: ScrollBehavior;
|
|
78
|
+
}
|
|
79
|
+
export interface Router {
|
|
80
|
+
readonly current: Signal<string>;
|
|
81
|
+
readonly params: Signal<Record<string, string>>;
|
|
82
|
+
readonly query: Signal<Record<string, string>>;
|
|
83
|
+
readonly base: string;
|
|
84
|
+
readonly intent: Signal<NavigationIntent>;
|
|
85
|
+
readonly canGoBack: Signal<boolean>;
|
|
86
|
+
navigate(location: RouteLocation, options?: NavigateOptions): void;
|
|
87
|
+
replace(location: RouteLocation, options?: NavigateOptions): void;
|
|
88
|
+
back(animation?: unknown): void;
|
|
89
|
+
forward(animation?: unknown): void;
|
|
90
|
+
go(delta: number): void;
|
|
91
|
+
isActive(path: string, exact?: boolean): boolean;
|
|
92
|
+
resolve(path: string): ResolvedRoute;
|
|
93
|
+
readonly routes: RouteRecord[];
|
|
94
|
+
beforeEach(guard: NavigationGuard): () => void;
|
|
95
|
+
afterEach(hook: AfterEachHook): () => void;
|
|
96
|
+
}
|
|
97
|
+
export declare const RouterKey: import("./context.js").InjectionKey<Router>;
|
|
98
|
+
/**
|
|
99
|
+
* @internal Whether a router is currently active. Used by outlets that
|
|
100
|
+
* want to auto-bootstrap if the user didn't call `createRouter()` themselves.
|
|
101
|
+
*/
|
|
102
|
+
export declare function _hasActiveRouter(): boolean;
|
|
103
|
+
export declare function createRouter(routes: RouteRecord[], options?: RouterOptions): Router;
|
|
104
|
+
export declare function elurRouter(): Router;
|
|
105
|
+
/** @internal */
|
|
106
|
+
export declare function _resetRouter(): void;
|
|
107
|
+
export declare class RouterView extends ElurComponent {
|
|
108
|
+
private _depth;
|
|
109
|
+
private _router?;
|
|
110
|
+
constructor(depth?: number, router?: Router);
|
|
111
|
+
render(): ElurTemplate;
|
|
112
|
+
}
|
|
113
|
+
export declare class Link extends ElurComponent {
|
|
114
|
+
private _to;
|
|
115
|
+
private _label;
|
|
116
|
+
private _router?;
|
|
117
|
+
constructor(to: string, label: string, router?: Router);
|
|
118
|
+
render(): ElurTemplate;
|
|
119
|
+
}
|
|
120
|
+
export interface _RouterDebugInternal {
|
|
121
|
+
mode: RouterMode;
|
|
122
|
+
base: string;
|
|
123
|
+
currentPath: string;
|
|
124
|
+
params: Record<string, string>;
|
|
125
|
+
query: Record<string, string>;
|
|
126
|
+
matchedPath: string | null;
|
|
127
|
+
activeGuards: {
|
|
128
|
+
globalCount: number;
|
|
129
|
+
hasRouteGuard: boolean;
|
|
130
|
+
names: string[];
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/** @internal Register a router that was injected via mount({ router }). */
|
|
134
|
+
export declare function _debugRegisterRouter(router: Router): void;
|
|
135
|
+
/** @internal Unregister a router when its mount point is unmounted. */
|
|
136
|
+
export declare function _debugUnregisterRouter(router: Router): void;
|
|
137
|
+
export declare function _debugGetRouterInternal(): _RouterDebugInternal | null;
|