@lankajs/angular 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +0 -2
- package/dist/index.js.map +1 -1
- package/dist/testing.js +2 -39239
- package/dist/testing.js.map +1 -1
- package/package.json +4 -3
- package/skills/lanka-angular/SKILL.md +1 -1
- package/skills/lanka-angular/reference.md +1 -1
- package/dist/chunk-5WRI5ZAA.js +0 -31
- package/dist/chunk-5WRI5ZAA.js.map +0 -1
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/to-lanka-observable/toLankaObservable.ts","../src/to-lanka-signals/toLankaSignals.ts","../src/use-lanka-vm/useLankaVM.ts"],"sourcesContent":["import { createLankaViewSubscription } from \"lanka/extend\";\nimport type { ILankaReadableVM } from \"lanka/viewmodel\";\n\n/** What a subscriber hands in, and what it gets back. */\nexport interface ILankaObserver<TValue> {\n\tnext?: (value: TValue) => void;\n\terror?: (failure: unknown) => void;\n\tcomplete?: () => void;\n}\n\n/** What unsubscribing looks like, in RxJS's own vocabulary. */\nexport interface ILankaUnsubscribable {\n\tunsubscribe: () => void;\n}\n\n/**\n * A ViewModel as something the `async` pipe and an RxJS chain accept.\n *\n * `Subscribable` is the whole contract: one method, and `AsyncPipe` takes it as\n * readily as an `Observable`.\n */\nexport interface ILankaObservableVM<TState extends object> {\n\tsubscribe: (\n\t\tobserver: ILankaObserver<TState> | ((value: TState) => void),\n\t) => ILankaUnsubscribable;\n}\n\n/**\n * Reads a ViewModel as a stream, for the half of Angular that speaks RxJS.\n *\n * ```ts\n * @Component({ template: `@if (todos$ | async; as todos) { … }` })\n * export class TodoScreen {\n * \tprotected readonly todos$ = toLankaObservable(todosVM);\n * }\n * ```\n *\n * ```ts\n * // or in a chain, where signals cannot go\n * toLankaObservable(todosVM).subscribe(({ rows }) => this.log(rows.length));\n * ```\n *\n * ## Why this exists beside `useLankaVM` and `toLankaSignals`\n *\n * Angular is signals-first now and those two answer signals, which is the right\n * default. It is also a framework with fifteen years of `Observable` in it: the\n * `async` pipe, `HttpClient`, the router's events, every `switchMap` a codebase\n * already has. A consumer with a stream in hand reaches for `combineLatest`, and\n * a signal is not something they can pass to it.\n *\n * ## No `rxjs` import, deliberately\n *\n * `AsyncPipe` accepts `Subscribable<T>`, which is an INTERFACE — one method — so\n * this satisfies it structurally and adds no dependency. The parity canon's\n * order for an idiom is the framework's own library first, then what it already\n * requires, then a few lines written here, and only then somebody else's\n * package. This is the third rung, and it keeps `@lankajs/angular` importing\n * nothing but `@angular/core`.\n *\n * A consumer who wants the operators pipes it: `from(toLankaObservable(vm))`\n * takes a subscribable, and `toObservable` from `@angular/core/rxjs-interop`\n * takes the signal `useLankaVM` answers.\n *\n * ## It emits the CURRENT state first\n *\n * Like a `BehaviorSubject` and like every store an Angular consumer has met: a\n * subscriber gets the state it subscribed to before anything changes, because a\n * template rendering `| async` would otherwise show nothing until the first\n * write.\n *\n * ## No injection context needed\n *\n * Unlike `useLankaVM` and `toLankaSignals`, which take a `DestroyRef` because a\n * signal has no other way to learn its reader has gone. A stream's subscriber\n * holds its own unsubscribe, which is RxJS's answer to the same question — so\n * this works in a service, a resolver, an interceptor and a plain function.\n */\nexport const toLankaObservable = <TState extends object>(\n\tviewModel: ILankaReadableVM<TState>,\n): ILankaObservableVM<TState> => ({\n\tsubscribe: (observer) => {\n\t\tconst next =\n\t\t\ttypeof observer === \"function\" ? observer : (observer.next ?? (() => undefined));\n\n\t\tconst view = createLankaViewSubscription(viewModel, () => next(view.read()));\n\n\t\tnext(view.read());\n\n\t\treturn { unsubscribe: view.stop };\n\t},\n});\n","import { DestroyRef, assertInInjectionContext, computed, inject, signal } from \"@angular/core\";\nimport { createLankaViewSubscription } from \"lanka/extend\";\nimport type { Signal } from \"@angular/core\";\nimport type { ILankaReadableVM } from \"lanka/viewmodel\";\n\n/**\n * A ViewModel split the way an Angular service exposes state: a signal per\n * value, and the actions as themselves.\n *\n * An action is one object for the life of the store, so wrapping it in a signal\n * would make every call site write `load()()`. A value changes, so it is a\n * signal; a function does not, so it is a function.\n */\nexport type TLankaSignals<TState extends object> = {\n\t[TKey in keyof TState]: TState[TKey] extends (...args: never[]) => unknown\n\t\t? TState[TKey]\n\t\t: Signal<TState[TKey]>;\n};\n\n/**\n * Reads a ViewModel as the signals an Angular component expects.\n *\n * ```ts\n * @Component({ template: `@if (todos.isLoading()) { … } @for (row of todos.rows(); track row) { … }` })\n * export class TodoScreen {\n * \tprotected readonly todos = toLankaSignals(todosVM);\n * }\n * ```\n *\n * ## Why this exists beside `useLankaVM`\n *\n * `useLankaVM` answers ONE `Signal` over the whole state, which is the shape\n * every other binding on the shelf parallels: `state().rows`. It is also not how\n * Angular holds state. An Angular service exposes a signal per field —\n * `readonly rows = signal([])` — and a template reads `rows()`, never\n * `state().rows`. A consumer with that habit reaches for `todos.rows()` and finds\n * a call on a plain object.\n *\n * ## One subscription, and each signal is a `computed` over it\n *\n * There is one `subscribe` on the ViewModel and one version signal behind every\n * field, so a change wakes Angular once and each `computed` decides for itself\n * whether its own value moved. That is Angular's own deduplication — a `computed`\n * whose result is unchanged notifies nobody — arriving for free, and it is why\n * this is not a second subscription per field.\n *\n * ## The keys are read ONCE, at the call\n *\n * A ViewModel declares its state up front, so the field list is fixed at the\n * moment this is called. A key added to the state later has no signal here, and\n * that is the price of the shape: Angular's own services name their fields too.\n * `useLankaVM` is the answer for a state whose shape is genuinely dynamic.\n *\n * ## It needs an injection context, for the reason `useLankaVM` does\n *\n * `DestroyRef` is the only way to learn the caller has gone, and a subscription\n * that cannot learn that is a leak with no owner.\n */\nexport const toLankaSignals = <TState extends object>(\n\tviewModel: ILankaReadableVM<TState>,\n): TLankaSignals<TState> => {\n\tassertInInjectionContext(toLankaSignals);\n\n\tconst version = signal(0);\n\tconst view = createLankaViewSubscription(viewModel, () => {\n\t\tversion.update((seen) => seen + 1);\n\t});\n\n\tinject(DestroyRef).onDestroy(view.stop);\n\n\tconst signals = {} as Record<string, unknown>;\n\n\tfor (const [key, value] of Object.entries(viewModel.getState())) {\n\t\tsignals[key] =\n\t\t\ttypeof value === \"function\"\n\t\t\t\t? value\n\t\t\t\t: computed(() => {\n\t\t\t\t\t\t// Read for the DEPENDENCY, discard the number. The value itself\n\t\t\t\t\t\t// comes from a tracked read, so the key is recorded and this reader\n\t\t\t\t\t\t// is woken only for the keys it has signals for.\n\t\t\t\t\t\tversion();\n\n\t\t\t\t\t\treturn (view.read() as Record<string, unknown>)[key];\n\t\t\t\t\t});\n\t}\n\n\treturn signals as TLankaSignals<TState>;\n};\n","import { DestroyRef, assertInInjectionContext, inject, signal } from \"@angular/core\";\nimport { createLankaAccessTracker } from \"lanka/extend\";\nimport type { Signal } from \"@angular/core\";\nimport type { ILankaReadableVM } from \"lanka/viewmodel\";\n\n/**\n * Reads a ViewModel from Angular.\n *\n * ```ts\n * @Component({ template: `<li *ngFor=\"let row of state().rows\">{{ row }}</li>` })\n * export class TodoScreen {\n * \tprotected readonly state = useLankaVM(todoVM);\n * }\n * ```\n *\n * Without a selector the signal carries a value that RECORDS which keys were\n * read, and changes only when one of THOSE moves. With a selector the selector\n * decides and tracking is bypassed.\n *\n * Zoneless needs no extra step: a signal is what zoneless change detection\n * reads, so this is the shape Angular is moving towards rather than a bridge to\n * it.\n *\n * ## Why an injection context is REQUIRED, not preferred\n *\n * `@lankajs/vue` and `@lankajs/solid` publish a `stop()` for a call made outside\n * their framework's scope, because both can still work without one. Angular\n * cannot: `DestroyRef` is the only way to learn that the caller has gone, and a\n * subscription with no way to learn that is a leak with no owner.\n *\n * So this refuses at the call rather than leaking quietly, and the message names\n * the fix. Where the other two degrade, this one stops — and a refusal a\n * developer reads once beats a leak found in production.\n *\n * ## What this function does NOT contain\n *\n * The recording, the comparison and the blind-spot warning are\n * `createLankaAccessTracker` in core. Every binding on this shelf calls it,\n * which is what makes \"a screen updates for the keys it read\" a fact about lanka\n * rather than a fact about Angular.\n */\nexport function useLankaVM<TState extends object>(\n\tviewModel: ILankaReadableVM<TState>,\n): Signal<TState>;\n\nexport function useLankaVM<TState extends object, TSelected>(\n\tviewModel: ILankaReadableVM<TState>,\n\tselector: (state: TState) => TSelected,\n): Signal<TSelected>;\n\nexport function useLankaVM<TState extends object, TSelected>(\n\tviewModel: ILankaReadableVM<TState>,\n\tselector?: (state: TState) => TSelected,\n): Signal<TState | TSelected> {\n\tassertInInjectionContext(useLankaVM);\n\n\tconst tracker = createLankaAccessTracker(viewModel);\n\tconst read = (): TState | TSelected =>\n\t\tselector ? selector(viewModel.getState()) : tracker.read();\n\n\t// `equal: () => false` because a tracked read hands back the SAME proxy while\n\t// the state object is unchanged, and a signal compares by identity — so\n\t// setting it would be a no-op exactly when the tracker did its job. What\n\t// decides whether anything happens is `shouldNotify` below, which is the\n\t// framework's answer rather than the signal's.\n\tconst state = signal<TState | TSelected>(read(), { equal: () => false });\n\n\tconst stop = viewModel.subscribe((next, prev) => {\n\t\tif (selector) {\n\t\t\tconst picked = read();\n\n\t\t\t// Only when the SELECTION moved. The signal is `equal: () => false`, so\n\t\t\t// setting it always wakes — which is right for a tracked read and wrong\n\t\t\t// for a selected one, and is what the suite's selector scenes refuse.\n\t\t\tif (Object.is(picked, state())) return;\n\n\t\t\tstate.set(picked);\n\n\t\t\treturn;\n\t\t}\n\n\t\tif (!tracker.shouldNotify(next, prev)) {\n\t\t\t// No update will follow. If the changed key is linked to this component\n\t\t\t// through a getter it read, the screen froze — and in development core\n\t\t\t// says so by name.\n\t\t\ttracker.reportSkipped(next, prev);\n\t\t\treturn;\n\t\t}\n\n\t\tstate.set(read());\n\t});\n\tinject(DestroyRef).onDestroy(stop);\n\n\treturn state.asReadonly();\n}\n"],"mappings":";;;AAAA,SAAS,mCAAmC;AA6ErC,IAAM,oBAAoB,CAChC,eACiC;AAAA,EACjC,WAAW,CAAC,aAAa;AACxB,UAAM,OACL,OAAO,aAAa,aAAa,WAAY,SAAS,SAAS,MAAM;AAEtE,UAAM,OAAO,4BAA4B,WAAW,MAAM,KAAK,KAAK,KAAK,CAAC,CAAC;AAE3E,SAAK,KAAK,KAAK,CAAC;AAEhB,WAAO,EAAE,aAAa,KAAK,KAAK;AAAA,EACjC;AACD;;;AC1FA,SAAS,YAAY,0BAA0B,UAAU,QAAQ,cAAc;AAC/E,SAAS,+BAAAA,oCAAmC;AAyDrC,IAAM,iBAAiB,CAC7B,cAC2B;AAC3B,2BAAyB,cAAc;AAEvC,QAAM,UAAU,OAAO,CAAC;AACxB,QAAM,OAAOA,6BAA4B,WAAW,MAAM;AACzD,YAAQ,OAAO,CAAC,SAAS,OAAO,CAAC;AAAA,EAClC,CAAC;AAED,SAAO,UAAU,EAAE,UAAU,KAAK,IAAI;AAEtC,QAAM,UAAU,CAAC;AAEjB,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,UAAU,SAAS,CAAC,GAAG;AAChE,YAAQ,GAAG,IACV,OAAO,UAAU,aACd,QACA,SAAS,MAAM;AAIf,cAAQ;AAER,aAAQ,KAAK,KAAK,EAA8B,GAAG;AAAA,IACpD,CAAC;AAAA,EACL;AAEA,SAAO;AACR;;;ACvFA,SAAS,cAAAC,aAAY,4BAAAC,2BAA0B,UAAAC,SAAQ,UAAAC,eAAc;AACrE,SAAS,gCAAgC;AAiDlC,SAAS,WACf,WACA,UAC6B;AAC7B,EAAAF,0BAAyB,UAAU;AAEnC,QAAM,UAAU,yBAAyB,SAAS;AAClD,QAAM,OAAO,MACZ,WAAW,SAAS,UAAU,SAAS,CAAC,IAAI,QAAQ,KAAK;AAO1D,QAAM,QAAQE,QAA2B,KAAK,GAAG,EAAE,OAAO,MAAM,MAAM,CAAC;AAEvE,QAAM,OAAO,UAAU,UAAU,CAAC,MAAM,SAAS;AAChD,QAAI,UAAU;AACb,YAAM,SAAS,KAAK;AAKpB,UAAI,OAAO,GAAG,QAAQ,MAAM,CAAC,EAAG;AAEhC,YAAM,IAAI,MAAM;AAEhB;AAAA,IACD;AAEA,QAAI,CAAC,QAAQ,aAAa,MAAM,IAAI,GAAG;AAItC,cAAQ,cAAc,MAAM,IAAI;AAChC;AAAA,IACD;AAEA,UAAM,IAAI,KAAK,CAAC;AAAA,EACjB,CAAC;AACD,EAAAD,QAAOF,WAAU,EAAE,UAAU,IAAI;AAEjC,SAAO,MAAM,WAAW;AACzB;","names":["createLankaViewSubscription","DestroyRef","assertInInjectionContext","inject","signal"]}
|
|
1
|
+
{"version":3,"sources":["../src/to-lanka-observable/toLankaObservable.ts","../src/to-lanka-signals/toLankaSignals.ts","../src/use-lanka-vm/useLankaVM.ts"],"sourcesContent":["import { createLankaViewSubscription } from \"lanka/extend\";\nimport type { ILankaReadableVM } from \"lanka/viewmodel\";\n\n/** What a subscriber hands in, and what it gets back. */\nexport interface ILankaObserver<TValue> {\n\tnext?: (value: TValue) => void;\n\terror?: (failure: unknown) => void;\n\tcomplete?: () => void;\n}\n\n/** What unsubscribing looks like, in RxJS's own vocabulary. */\nexport interface ILankaUnsubscribable {\n\tunsubscribe: () => void;\n}\n\n/**\n * A ViewModel as something the `async` pipe and an RxJS chain accept.\n *\n * `Subscribable` is the whole contract: one method, and `AsyncPipe` takes it as\n * readily as an `Observable`.\n */\nexport interface ILankaObservableVM<TState extends object> {\n\tsubscribe: (\n\t\tobserver: ILankaObserver<TState> | ((value: TState) => void),\n\t) => ILankaUnsubscribable;\n}\n\n/**\n * Reads a ViewModel as a stream, for the half of Angular that speaks RxJS.\n *\n * ```ts\n * @Component({ template: `@if (todos$ | async; as todos) { … }` })\n * export class TodoScreen {\n * \tprotected readonly todos$ = toLankaObservable(todosVM);\n * }\n * ```\n *\n * ```ts\n * // or in a chain, where signals cannot go\n * toLankaObservable(todosVM).subscribe(({ rows }) => this.log(rows.length));\n * ```\n *\n * ## Why this exists beside `useLankaVM` and `toLankaSignals`\n *\n * Angular is signals-first now and those two answer signals, which is the right\n * default. It is also a framework with fifteen years of `Observable` in it: the\n * `async` pipe, `HttpClient`, the router's events, every `switchMap` a codebase\n * already has. A consumer with a stream in hand reaches for `combineLatest`, and\n * a signal is not something they can pass to it.\n *\n * ## No `rxjs` import, deliberately\n *\n * `AsyncPipe` accepts `Subscribable<T>`, which is an INTERFACE — one method — so\n * this satisfies it structurally and adds no dependency. The parity canon's\n * order for an idiom is the framework's own library first, then what it already\n * requires, then a few lines written here, and only then somebody else's\n * package. This is the third rung, and it keeps `@lankajs/angular` importing\n * nothing but `@angular/core`.\n *\n * A consumer who wants the operators pipes it: `from(toLankaObservable(vm))`\n * takes a subscribable, and `toObservable` from `@angular/core/rxjs-interop`\n * takes the signal `useLankaVM` answers.\n *\n * ## It emits the CURRENT state first\n *\n * Like a `BehaviorSubject` and like every store an Angular consumer has met: a\n * subscriber gets the state it subscribed to before anything changes, because a\n * template rendering `| async` would otherwise show nothing until the first\n * write.\n *\n * ## No injection context needed\n *\n * Unlike `useLankaVM` and `toLankaSignals`, which take a `DestroyRef` because a\n * signal has no other way to learn its reader has gone. A stream's subscriber\n * holds its own unsubscribe, which is RxJS's answer to the same question — so\n * this works in a service, a resolver, an interceptor and a plain function.\n */\nexport const toLankaObservable = <TState extends object>(\n\tviewModel: ILankaReadableVM<TState>,\n): ILankaObservableVM<TState> => ({\n\tsubscribe: (observer) => {\n\t\tconst next =\n\t\t\ttypeof observer === \"function\" ? observer : (observer.next ?? (() => undefined));\n\n\t\tconst view = createLankaViewSubscription(viewModel, () => next(view.read()));\n\n\t\tnext(view.read());\n\n\t\treturn { unsubscribe: view.stop };\n\t},\n});\n","import { DestroyRef, assertInInjectionContext, computed, inject, signal } from \"@angular/core\";\nimport { createLankaViewSubscription } from \"lanka/extend\";\nimport type { Signal } from \"@angular/core\";\nimport type { ILankaReadableVM } from \"lanka/viewmodel\";\n\n/**\n * A ViewModel split the way an Angular service exposes state: a signal per\n * value, and the actions as themselves.\n *\n * An action is one object for the life of the store, so wrapping it in a signal\n * would make every call site write `load()()`. A value changes, so it is a\n * signal; a function does not, so it is a function.\n */\nexport type TLankaSignals<TState extends object> = {\n\t[TKey in keyof TState]: TState[TKey] extends (...args: never[]) => unknown\n\t\t? TState[TKey]\n\t\t: Signal<TState[TKey]>;\n};\n\n/**\n * Reads a ViewModel as the signals an Angular component expects.\n *\n * ```ts\n * @Component({ template: `@if (todos.isLoading()) { … } @for (row of todos.rows(); track row) { … }` })\n * export class TodoScreen {\n * \tprotected readonly todos = toLankaSignals(todosVM);\n * }\n * ```\n *\n * ## Why this exists beside `useLankaVM`\n *\n * `useLankaVM` answers ONE `Signal` over the whole state, which is the shape\n * every other binding on the shelf parallels: `state().rows`. It is also not how\n * Angular holds state. An Angular service exposes a signal per field —\n * `readonly rows = signal([])` — and a template reads `rows()`, never\n * `state().rows`. A consumer with that habit reaches for `todos.rows()` and finds\n * a call on a plain object.\n *\n * ## One subscription, and each signal is a `computed` over it\n *\n * There is one `subscribe` on the ViewModel and one version signal behind every\n * field, so a change wakes Angular once and each `computed` decides for itself\n * whether its own value moved. That is Angular's own deduplication — a `computed`\n * whose result is unchanged notifies nobody — arriving for free, and it is why\n * this is not a second subscription per field.\n *\n * ## The keys are read ONCE, at the call\n *\n * A ViewModel declares its state up front, so the field list is fixed at the\n * moment this is called. A key added to the state later has no signal here, and\n * that is the price of the shape: Angular's own services name their fields too.\n * `useLankaVM` is the answer for a state whose shape is genuinely dynamic.\n *\n * ## It needs an injection context, for the reason `useLankaVM` does\n *\n * `DestroyRef` is the only way to learn the caller has gone, and a subscription\n * that cannot learn that is a leak with no owner.\n */\nexport const toLankaSignals = <TState extends object>(\n\tviewModel: ILankaReadableVM<TState>,\n): TLankaSignals<TState> => {\n\tassertInInjectionContext(toLankaSignals);\n\n\tconst version = signal(0);\n\tconst view = createLankaViewSubscription(viewModel, () => {\n\t\tversion.update((seen) => seen + 1);\n\t});\n\n\tinject(DestroyRef).onDestroy(view.stop);\n\n\tconst signals = {} as Record<string, unknown>;\n\n\tfor (const [key, value] of Object.entries(viewModel.getState())) {\n\t\tsignals[key] =\n\t\t\ttypeof value === \"function\"\n\t\t\t\t? value\n\t\t\t\t: computed(() => {\n\t\t\t\t\t\t// Read for the DEPENDENCY, discard the number. The value itself\n\t\t\t\t\t\t// comes from a tracked read, so the key is recorded and this reader\n\t\t\t\t\t\t// is woken only for the keys it has signals for.\n\t\t\t\t\t\tversion();\n\n\t\t\t\t\t\treturn (view.read() as Record<string, unknown>)[key];\n\t\t\t\t\t});\n\t}\n\n\treturn signals as TLankaSignals<TState>;\n};\n","import { DestroyRef, assertInInjectionContext, inject, signal } from \"@angular/core\";\nimport { createLankaAccessTracker } from \"lanka/extend\";\nimport type { Signal } from \"@angular/core\";\nimport type { ILankaReadableVM } from \"lanka/viewmodel\";\n\n/**\n * Reads a ViewModel from Angular.\n *\n * ```ts\n * @Component({ template: `<li *ngFor=\"let row of state().rows\">{{ row }}</li>` })\n * export class TodoScreen {\n * \tprotected readonly state = useLankaVM(todoVM);\n * }\n * ```\n *\n * Without a selector the signal carries a value that RECORDS which keys were\n * read, and changes only when one of THOSE moves. With a selector the selector\n * decides and tracking is bypassed.\n *\n * Zoneless needs no extra step: a signal is what zoneless change detection\n * reads, so this is the shape Angular is moving towards rather than a bridge to\n * it.\n *\n * ## Why an injection context is REQUIRED, not preferred\n *\n * `@lankajs/vue` and `@lankajs/solid` publish a `stop()` for a call made outside\n * their framework's scope, because both can still work without one. Angular\n * cannot: `DestroyRef` is the only way to learn that the caller has gone, and a\n * subscription with no way to learn that is a leak with no owner.\n *\n * So this refuses at the call rather than leaking quietly, and the message names\n * the fix. Where the other two degrade, this one stops — and a refusal a\n * developer reads once beats a leak found in production.\n *\n * ## What this function does NOT contain\n *\n * The recording, the comparison and the blind-spot warning are\n * `createLankaAccessTracker` in core. Every binding on this shelf calls it,\n * which is what makes \"a screen updates for the keys it read\" a fact about lanka\n * rather than a fact about Angular.\n */\nexport function useLankaVM<TState extends object>(\n\tviewModel: ILankaReadableVM<TState>,\n): Signal<TState>;\n\nexport function useLankaVM<TState extends object, TSelected>(\n\tviewModel: ILankaReadableVM<TState>,\n\tselector: (state: TState) => TSelected,\n): Signal<TSelected>;\n\nexport function useLankaVM<TState extends object, TSelected>(\n\tviewModel: ILankaReadableVM<TState>,\n\tselector?: (state: TState) => TSelected,\n): Signal<TState | TSelected> {\n\tassertInInjectionContext(useLankaVM);\n\n\tconst tracker = createLankaAccessTracker(viewModel);\n\tconst read = (): TState | TSelected =>\n\t\tselector ? selector(viewModel.getState()) : tracker.read();\n\n\t// `equal: () => false` because a tracked read hands back the SAME proxy while\n\t// the state object is unchanged, and a signal compares by identity — so\n\t// setting it would be a no-op exactly when the tracker did its job. What\n\t// decides whether anything happens is `shouldNotify` below, which is the\n\t// framework's answer rather than the signal's.\n\tconst state = signal<TState | TSelected>(read(), { equal: () => false });\n\n\tconst stop = viewModel.subscribe((next, prev) => {\n\t\tif (selector) {\n\t\t\tconst picked = read();\n\n\t\t\t// Only when the SELECTION moved. The signal is `equal: () => false`, so\n\t\t\t// setting it always wakes — which is right for a tracked read and wrong\n\t\t\t// for a selected one, and is what the suite's selector scenes refuse.\n\t\t\tif (Object.is(picked, state())) return;\n\n\t\t\tstate.set(picked);\n\n\t\t\treturn;\n\t\t}\n\n\t\tif (!tracker.shouldNotify(next, prev)) {\n\t\t\t// No update will follow. If the changed key is linked to this component\n\t\t\t// through a getter it read, the screen froze — and in development core\n\t\t\t// says so by name.\n\t\t\ttracker.reportSkipped(next, prev);\n\t\t\treturn;\n\t\t}\n\n\t\tstate.set(read());\n\t});\n\tinject(DestroyRef).onDestroy(stop);\n\n\treturn state.asReadonly();\n}\n"],"mappings":";AAAA,SAAS,mCAAmC;AA6ErC,IAAM,oBAAoB,CAChC,eACiC;AAAA,EACjC,WAAW,CAAC,aAAa;AACxB,UAAM,OACL,OAAO,aAAa,aAAa,WAAY,SAAS,SAAS,MAAM;AAEtE,UAAM,OAAO,4BAA4B,WAAW,MAAM,KAAK,KAAK,KAAK,CAAC,CAAC;AAE3E,SAAK,KAAK,KAAK,CAAC;AAEhB,WAAO,EAAE,aAAa,KAAK,KAAK;AAAA,EACjC;AACD;;;AC1FA,SAAS,YAAY,0BAA0B,UAAU,QAAQ,cAAc;AAC/E,SAAS,+BAAAA,oCAAmC;AAyDrC,IAAM,iBAAiB,CAC7B,cAC2B;AAC3B,2BAAyB,cAAc;AAEvC,QAAM,UAAU,OAAO,CAAC;AACxB,QAAM,OAAOA,6BAA4B,WAAW,MAAM;AACzD,YAAQ,OAAO,CAAC,SAAS,OAAO,CAAC;AAAA,EAClC,CAAC;AAED,SAAO,UAAU,EAAE,UAAU,KAAK,IAAI;AAEtC,QAAM,UAAU,CAAC;AAEjB,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,UAAU,SAAS,CAAC,GAAG;AAChE,YAAQ,GAAG,IACV,OAAO,UAAU,aACd,QACA,SAAS,MAAM;AAIf,cAAQ;AAER,aAAQ,KAAK,KAAK,EAA8B,GAAG;AAAA,IACpD,CAAC;AAAA,EACL;AAEA,SAAO;AACR;;;ACvFA,SAAS,cAAAC,aAAY,4BAAAC,2BAA0B,UAAAC,SAAQ,UAAAC,eAAc;AACrE,SAAS,gCAAgC;AAiDlC,SAAS,WACf,WACA,UAC6B;AAC7B,EAAAF,0BAAyB,UAAU;AAEnC,QAAM,UAAU,yBAAyB,SAAS;AAClD,QAAM,OAAO,MACZ,WAAW,SAAS,UAAU,SAAS,CAAC,IAAI,QAAQ,KAAK;AAO1D,QAAM,QAAQE,QAA2B,KAAK,GAAG,EAAE,OAAO,MAAM,MAAM,CAAC;AAEvE,QAAM,OAAO,UAAU,UAAU,CAAC,MAAM,SAAS;AAChD,QAAI,UAAU;AACb,YAAM,SAAS,KAAK;AAKpB,UAAI,OAAO,GAAG,QAAQ,MAAM,CAAC,EAAG;AAEhC,YAAM,IAAI,MAAM;AAEhB;AAAA,IACD;AAEA,QAAI,CAAC,QAAQ,aAAa,MAAM,IAAI,GAAG;AAItC,cAAQ,cAAc,MAAM,IAAI;AAChC;AAAA,IACD;AAEA,UAAM,IAAI,KAAK,CAAC;AAAA,EACjB,CAAC;AACD,EAAAD,QAAOF,WAAU,EAAE,UAAU,IAAI;AAEjC,SAAO,MAAM,WAAW;AACzB;","names":["createLankaViewSubscription","DestroyRef","assertInInjectionContext","inject","signal"]}
|