@craft-ng/core 0.0.1 → 0.0.2
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/fesm2022/craft-ng-core.mjs +102 -1
- package/fesm2022/craft-ng-core.mjs.map +1 -1
- package/package.json +2 -2
- package/types/craft-ng-core.d.ts +280 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@craft-ng/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "Reactive State Management for Angular - Type-safe, signal-based utilities for composable and reusable state management",
|
|
5
5
|
"author": "Romain Geffrault",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,4 +38,4 @@
|
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"tslib": "^2.3.0"
|
|
40
40
|
}
|
|
41
|
-
}
|
|
41
|
+
}
|
package/types/craft-ng-core.d.ts
CHANGED
|
@@ -6074,6 +6074,74 @@ type ReadonlySource$<T> = {
|
|
|
6074
6074
|
subscribe: (callback: (value: T) => void) => ReturnType<EventEmitter<T>['subscribe']>;
|
|
6075
6075
|
value: Signal<T | undefined>;
|
|
6076
6076
|
};
|
|
6077
|
+
/**
|
|
6078
|
+
* Creates an event emitter with automatic cleanup and signal-based value tracking.
|
|
6079
|
+
*
|
|
6080
|
+
* `source$` provides a lightweight event streaming solution that combines event emission,
|
|
6081
|
+
* automatic subscription cleanup via `DestroyRef`, and signal-based reactive value tracking.
|
|
6082
|
+
*
|
|
6083
|
+
* @template T - The type of values emitted by the source
|
|
6084
|
+
*
|
|
6085
|
+
* @returns {Source$<T>} An object with the following methods and properties:
|
|
6086
|
+
* - `emit(value: T)` - Emits a value to all subscribers and updates the internal signal
|
|
6087
|
+
* - `subscribe(callback: (value: T) => void)` - Subscribes to emissions with automatic cleanup
|
|
6088
|
+
* - `value: Signal<T | undefined>` - A read-only signal containing the last emitted value
|
|
6089
|
+
* - `asReadonly()` - Returns a read-only version (only `subscribe` and `value`)
|
|
6090
|
+
* - `preserveLastValue()` - Returns a variant that immediately emits the last value to new subscribers
|
|
6091
|
+
*
|
|
6092
|
+
* @example
|
|
6093
|
+
* Basic usage with state coordination
|
|
6094
|
+
* ```typescript
|
|
6095
|
+
* import { source$, state, on$ } from '@craft-ng/core';
|
|
6096
|
+
*
|
|
6097
|
+
* @Component({
|
|
6098
|
+
* selector: 'app-counter',
|
|
6099
|
+
* template: `
|
|
6100
|
+
* <p>Count: {{ counter() }}</p>
|
|
6101
|
+
* <button (click)="counter.increment()">+1</button>
|
|
6102
|
+
* <button (click)="reset$.emit()">Reset</button>
|
|
6103
|
+
* `,
|
|
6104
|
+
* })
|
|
6105
|
+
* export class CounterComponent {
|
|
6106
|
+
* reset$ = source$<void>();
|
|
6107
|
+
*
|
|
6108
|
+
* counter = state(0, ({ set, update }) => ({
|
|
6109
|
+
* increment: () => update((v) => v + 1),
|
|
6110
|
+
* reset: on$(this.reset$, () => set(0)),
|
|
6111
|
+
* }));
|
|
6112
|
+
* }
|
|
6113
|
+
* ```
|
|
6114
|
+
*
|
|
6115
|
+
*
|
|
6116
|
+
* @example
|
|
6117
|
+
* Late subscriber with preserved last value
|
|
6118
|
+
* ```typescript
|
|
6119
|
+
* const notifications$ = source$<string>().preserveLastValue();
|
|
6120
|
+
*
|
|
6121
|
+
* notifications$.emit('Server started');
|
|
6122
|
+
* notifications$.emit('Database connected');
|
|
6123
|
+
*
|
|
6124
|
+
* // Late subscriber receives the last value immediately
|
|
6125
|
+
* notifications$.subscribe((msg) => {
|
|
6126
|
+
* console.log(msg); // 'Database connected'
|
|
6127
|
+
* });
|
|
6128
|
+
* ```
|
|
6129
|
+
*
|
|
6130
|
+
* @example
|
|
6131
|
+
* Read-only access pattern
|
|
6132
|
+
* ```typescript
|
|
6133
|
+
* class DataService {
|
|
6134
|
+
* private dataUpdated$ = source$<Data>();
|
|
6135
|
+
* readonly dataUpdated = this.dataUpdated$.asReadonly();
|
|
6136
|
+
*
|
|
6137
|
+
* updateData(data: Data) {
|
|
6138
|
+
* this.dataUpdated$.emit(data);
|
|
6139
|
+
* }
|
|
6140
|
+
* }
|
|
6141
|
+
* ```
|
|
6142
|
+
*
|
|
6143
|
+
* @see {@link https://ng-craft.dev/utils/source$ | source$ documentation}
|
|
6144
|
+
*/
|
|
6077
6145
|
declare function source$<T>(): Source$<T>;
|
|
6078
6146
|
|
|
6079
6147
|
type InferSourceType<S> = S extends SignalSource<infer T> ? T : S extends Source$<infer U> ? U : S extends _Subscribable<infer V> ? V : never;
|
|
@@ -8164,5 +8232,215 @@ declare function reactiveWritableSignal<State>(initialValue: State, reactionBuil
|
|
|
8164
8232
|
current: NoInfer<State>;
|
|
8165
8233
|
}) => NoInfer<State>, options?: SyncOptions) => ReactionEntry<State, Params>) => Record<string, ReactionEntry<State, any>>): WritableSignal<State>;
|
|
8166
8234
|
|
|
8167
|
-
|
|
8168
|
-
|
|
8235
|
+
type FromEventToSource$<T> = ReadonlySource$<T> & {
|
|
8236
|
+
dispose: () => void;
|
|
8237
|
+
};
|
|
8238
|
+
/**
|
|
8239
|
+
* Converts DOM events to a ReadonlySource$ stream with automatic cleanup on component destruction.
|
|
8240
|
+
*
|
|
8241
|
+
* This function bridges DOM events with ng-craft's source$ reactive system by:
|
|
8242
|
+
* - Converting native DOM events to source emissions
|
|
8243
|
+
* - Automatically removing event listeners on component destruction
|
|
8244
|
+
* - Supporting optional event payload transformation
|
|
8245
|
+
* - Providing manual disposal capability
|
|
8246
|
+
* - Returning a readonly source with `subscribe` and `value` properties
|
|
8247
|
+
*
|
|
8248
|
+
* @remarks
|
|
8249
|
+
* **Automatic Cleanup:**
|
|
8250
|
+
* - Event listeners are automatically removed when the injection context is destroyed
|
|
8251
|
+
* - Uses Angular's `DestroyRef` for lifecycle management
|
|
8252
|
+
* - Manual cleanup available via `dispose()` method
|
|
8253
|
+
* - Prevents memory leaks from dangling event listeners
|
|
8254
|
+
*
|
|
8255
|
+
* **Use Cases:**
|
|
8256
|
+
* - **User interactions**: Click, input, scroll, keyboard events
|
|
8257
|
+
* - **Window events**: Resize, scroll, focus, online/offline
|
|
8258
|
+
* - **Document events**: Visibility changes, custom events
|
|
8259
|
+
* - **Media events**: Video/audio play, pause, ended
|
|
8260
|
+
* - **Form events**: Submit, change, input
|
|
8261
|
+
* - **Custom events**: Application-specific DOM events
|
|
8262
|
+
*
|
|
8263
|
+
* **Event Transformation:**
|
|
8264
|
+
* - Without `computedValue`: Emits the raw event object
|
|
8265
|
+
* - With `computedValue`: Transforms event before emission
|
|
8266
|
+
* - Useful for extracting specific event properties
|
|
8267
|
+
* - Reduces payload size and improves type safety
|
|
8268
|
+
*
|
|
8269
|
+
* **Integration with Stores:**
|
|
8270
|
+
* - Use the readonly source in queries/mutations via `on$()`
|
|
8271
|
+
* - Trigger async operations on DOM events
|
|
8272
|
+
* - Coordinate multiple components via event sources
|
|
8273
|
+
*
|
|
8274
|
+
* **Injection Context:**
|
|
8275
|
+
* - Must be called within Angular injection context
|
|
8276
|
+
* - Typically called in component constructor or class fields
|
|
8277
|
+
* - Uses `assertInInjectionContext()` for safety
|
|
8278
|
+
*
|
|
8279
|
+
* @template T - The type of the event object
|
|
8280
|
+
* @template ComputedValue - The type after optional transformation
|
|
8281
|
+
*
|
|
8282
|
+
* @param target - The DOM element or event target to listen to.
|
|
8283
|
+
* Can be any EventTarget (HTMLElement, Window, Document, etc.)
|
|
8284
|
+
*
|
|
8285
|
+
* @param eventName - The name of the event to listen for.
|
|
8286
|
+
* Standard DOM event names: 'click', 'input', 'scroll', etc.
|
|
8287
|
+
*
|
|
8288
|
+
* @param options - Optional configuration:
|
|
8289
|
+
* - `event`: Event listener options (capture, passive, once, etc.)
|
|
8290
|
+
* - `computedValue`: Function to transform event before emission
|
|
8291
|
+
*
|
|
8292
|
+
* @returns A readonly source that emits on DOM events with:
|
|
8293
|
+
* - `subscribe()`: Subscribe to event emissions
|
|
8294
|
+
* - `value`: Signal containing the last emitted value
|
|
8295
|
+
* - `dispose()`: Method to manually remove event listener
|
|
8296
|
+
* - Automatic cleanup on component destruction
|
|
8297
|
+
*
|
|
8298
|
+
* @example
|
|
8299
|
+
* Basic click event source
|
|
8300
|
+
* ```ts
|
|
8301
|
+
* @Component({
|
|
8302
|
+
* selector: 'app-clicker',
|
|
8303
|
+
* template: '<button #btn>Click me</button>',
|
|
8304
|
+
* })
|
|
8305
|
+
* export class ClickerComponent {
|
|
8306
|
+
* @ViewChild('btn', { read: ElementRef }) button!: ElementRef<HTMLButtonElement>;
|
|
8307
|
+
*
|
|
8308
|
+
* click$ = fromEventToSource$<MouseEvent>(
|
|
8309
|
+
* this.button.nativeElement,
|
|
8310
|
+
* 'click'
|
|
8311
|
+
* );
|
|
8312
|
+
*
|
|
8313
|
+
* counter = state(0, ({ update }) => ({
|
|
8314
|
+
* increment: on$(this.click$, () => update((v) => v + 1)),
|
|
8315
|
+
* }));
|
|
8316
|
+
* }
|
|
8317
|
+
* ```
|
|
8318
|
+
*
|
|
8319
|
+
* @example
|
|
8320
|
+
* Window scroll event with transformation
|
|
8321
|
+
* ```ts
|
|
8322
|
+
* @Component({
|
|
8323
|
+
* selector: 'app-infinite-scroll',
|
|
8324
|
+
* template: '...',
|
|
8325
|
+
* })
|
|
8326
|
+
* export class InfiniteScrollComponent {
|
|
8327
|
+
* scroll$ = fromEventToSource$(window, 'scroll', {
|
|
8328
|
+
* computedValue: () => ({
|
|
8329
|
+
* scrollY: window.scrollY,
|
|
8330
|
+
* scrollHeight: document.documentElement.scrollHeight,
|
|
8331
|
+
* clientHeight: window.innerHeight,
|
|
8332
|
+
* }),
|
|
8333
|
+
* event: { passive: true },
|
|
8334
|
+
* });
|
|
8335
|
+
*
|
|
8336
|
+
* // Access scroll position via signal
|
|
8337
|
+
* scrollPosition = this.scroll$.value;
|
|
8338
|
+
*
|
|
8339
|
+
* // Subscribe to scroll events
|
|
8340
|
+
* ngOnInit() {
|
|
8341
|
+
* this.scroll$.subscribe((data) => {
|
|
8342
|
+
* const nearBottom = data.scrollY + data.clientHeight >= data.scrollHeight - 100;
|
|
8343
|
+
* if (nearBottom) {
|
|
8344
|
+
* // Load more data
|
|
8345
|
+
* }
|
|
8346
|
+
* });
|
|
8347
|
+
* }
|
|
8348
|
+
* }
|
|
8349
|
+
* ```
|
|
8350
|
+
*
|
|
8351
|
+
* @example
|
|
8352
|
+
* Form input event for real-time search
|
|
8353
|
+
* ```ts
|
|
8354
|
+
* @Component({
|
|
8355
|
+
* selector: 'app-search',
|
|
8356
|
+
* template: '<input #searchInput type="text" placeholder="Search..." />',
|
|
8357
|
+
* })
|
|
8358
|
+
* export class SearchComponent {
|
|
8359
|
+
* @ViewChild('searchInput', { read: ElementRef }) input!: ElementRef<HTMLInputElement>;
|
|
8360
|
+
*
|
|
8361
|
+
* input$ = fromEventToSource$(this.input.nativeElement, 'input', {
|
|
8362
|
+
* computedValue: (event: Event) => {
|
|
8363
|
+
* const target = event.target as HTMLInputElement;
|
|
8364
|
+
* return target.value;
|
|
8365
|
+
* },
|
|
8366
|
+
* });
|
|
8367
|
+
*
|
|
8368
|
+
* // Current input value as signal
|
|
8369
|
+
* searchTerm = this.input$.value;
|
|
8370
|
+
*
|
|
8371
|
+
* // React to input changes
|
|
8372
|
+
* searchResults = state([], ({ set }) => ({
|
|
8373
|
+
* search: on$(this.input$, async (term) => {
|
|
8374
|
+
* if (term.length < 3) {
|
|
8375
|
+
* set([]);
|
|
8376
|
+
* return;
|
|
8377
|
+
* }
|
|
8378
|
+
* const results = await fetchResults(term);
|
|
8379
|
+
* set(results);
|
|
8380
|
+
* }),
|
|
8381
|
+
* }));
|
|
8382
|
+
* }
|
|
8383
|
+
* ```
|
|
8384
|
+
*
|
|
8385
|
+
* @example
|
|
8386
|
+
* Window resize event
|
|
8387
|
+
* ```ts
|
|
8388
|
+
* @Component({
|
|
8389
|
+
* selector: 'app-responsive',
|
|
8390
|
+
* template: '...',
|
|
8391
|
+
* })
|
|
8392
|
+
* export class ResponsiveComponent {
|
|
8393
|
+
* resize$ = fromEventToSource$(window, 'resize', {
|
|
8394
|
+
* computedValue: () => ({
|
|
8395
|
+
* width: window.innerWidth,
|
|
8396
|
+
* height: window.innerHeight,
|
|
8397
|
+
* }),
|
|
8398
|
+
* });
|
|
8399
|
+
*
|
|
8400
|
+
* dimensions = this.resize$.value;
|
|
8401
|
+
* }
|
|
8402
|
+
* ```
|
|
8403
|
+
*
|
|
8404
|
+
* @example
|
|
8405
|
+
* Manual disposal for dynamic elements
|
|
8406
|
+
* ```ts
|
|
8407
|
+
* @Component({
|
|
8408
|
+
* selector: 'app-dynamic',
|
|
8409
|
+
* template: '...',
|
|
8410
|
+
* })
|
|
8411
|
+
* export class DynamicComponent {
|
|
8412
|
+
* private click$?: FromEventToSource$<MouseEvent>;
|
|
8413
|
+
*
|
|
8414
|
+
* attachListener(element: HTMLElement) {
|
|
8415
|
+
* // Remove previous listener if exists
|
|
8416
|
+
* this.click$?.dispose();
|
|
8417
|
+
*
|
|
8418
|
+
* // Attach to new element
|
|
8419
|
+
* this.click$ = fromEventToSource$<MouseEvent>(element, 'click');
|
|
8420
|
+
* }
|
|
8421
|
+
*
|
|
8422
|
+
* detachListener() {
|
|
8423
|
+
* // Manually remove listener before component destruction
|
|
8424
|
+
* this.click$?.dispose();
|
|
8425
|
+
* this.click$ = undefined;
|
|
8426
|
+
* }
|
|
8427
|
+
* }
|
|
8428
|
+
* ```
|
|
8429
|
+
*
|
|
8430
|
+
* @see {@link https://ng-craft.dev/utils/from-event-to-source$ | fromEventToSource$ documentation}
|
|
8431
|
+
*/
|
|
8432
|
+
declare function fromEventToSource$<T>(target: EventTarget, eventName: string, options?: {
|
|
8433
|
+
event?: boolean | AddEventListenerOptions;
|
|
8434
|
+
computedValue?: never;
|
|
8435
|
+
}): FromEventToSource$<T>;
|
|
8436
|
+
declare function fromEventToSource$<T, ComputedValue>(target: EventTarget, eventName: string, options?: {
|
|
8437
|
+
event?: boolean | AddEventListenerOptions;
|
|
8438
|
+
computedValue: (event: T) => ComputedValue;
|
|
8439
|
+
}): FromEventToSource$<ComputedValue>;
|
|
8440
|
+
|
|
8441
|
+
declare function on$<State, SourceType>(_source: {
|
|
8442
|
+
subscribe: EventEmitter<SourceType>['subscribe'];
|
|
8443
|
+
}, callback: (source: SourceType) => State): SourceBranded;
|
|
8444
|
+
|
|
8445
|
+
export { EXTERNALLY_PROVIDED, EmptyContext, GlobalPersisterHandlerService, STORE_CONFIG_TOKEN, SourceBrand, SourceBranded, addMany, addOne, afterRecomputation, asyncProcess, capitalize, computedIds, computedSource, computedTotal, contract, craft, craftAsyncProcesses, craftComputedStates, craftFactoryEntries, craftInject, craftInputs, craftMutations, craftQuery, craftQueryParam, craftQueryParams, craftSetAllQueriesParamsStandalone, craftSources, craftState, createMethodHandlers, fromEventToSource$, insertLocalStoragePersister, insertPaginationPlaceholderData, insertReactOnMutation, isSource, linkedSource, localStoragePersister, map, mapOne, mutation, on$, partialContext, query, queryParam, reactiveWritableSignal, removeAll, removeMany, removeOne, resourceById, serializeQueryParams, serializedQueryParamsObjectToString, setAll, setMany, setOne, signalSource, source$, sourceFromEvent, stackedSource, state, toSource, toggleMany, toggleOne, updateMany, updateOne, upsertMany, upsertOne };
|
|
8446
|
+
export type { AsyncProcessOutput, AsyncProcessRef, CloudProxy, CloudProxySource, ContextConstraints, ContextInput, CraftFactory, CraftFactoryEntries, CraftFactoryUtility, DeferredExtract, EntitiesUtilBrand, EqualParams, ExcludeCommonKeys, ExtractSignalPropsAndMethods, FilterMethodsBoundToSources, FilterPrivateFields, FilterSource, FlatRecord, FromEventToSource$, HasChild$1 as HasChild, HasKeys, IdSelector, Identifier, InferInjectedType, IsAny, IsEmptyObject, IsNever, IsUnknown, MakeOptionalPropertiesRequired$1 as MakeOptionalPropertiesRequired, MergeObject$1 as MergeObject, MergeObjects$1 as MergeObjects, MergeTwoContexts, MutationOutput, MutationRef, Not, OmitStrict, PartialContext, Prettify, QueryOutput, QueryParamConfig, QueryParamNavigationOptions$1 as QueryParamNavigationOptions, QueryParamOutput, QueryParamsToState, QueryRef, ReadonlySource, ReadonlySource$, RemoveIndexSignature, ReplaceStoreConfigToken, ResourceByIdHandler, ResourceByIdLikeMutationRef, ResourceByIdLikeQueryRef, ResourceByIdRef, ResourceLikeMutationRef, ResourceLikeQueryRef, SignalSource, Source$, SourceFromEvent, SourceSetterMethods, SourceSubscribe, SpecificCraftQueryParamOutputs, SpecificCraftQueryParamsOutputs, StackSource, StateOutput, StoreConfigConstraints, StoreConfigToken, ToConnectableMethodFromInject, ToConnectableSourceFromInject, UnionToIntersection$1 as UnionToIntersection, UnionToTuple$1 as UnionToTuple, Update };
|