@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@craft-ng/core",
3
- "version": "0.0.1",
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
+ }
@@ -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
- 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, insertLocalStoragePersister, insertPaginationPlaceholderData, insertReactOnMutation, isSource, linkedSource, localStoragePersister, map, mapOne, mutation, 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 };
8168
- export type { AsyncProcessOutput, AsyncProcessRef, CloudProxy, CloudProxySource, ContextConstraints, ContextInput, CraftFactory, CraftFactoryEntries, CraftFactoryUtility, DeferredExtract, EntitiesUtilBrand, EqualParams, ExcludeCommonKeys, ExtractSignalPropsAndMethods, FilterMethodsBoundToSources, FilterPrivateFields, FilterSource, FlatRecord, 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 };
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 };