Skip to content

Commit

Permalink
Fix(StatePath): Change to non-recursive typing, revert array element …
Browse files Browse the repository at this point in the history
…selection (#2)
  • Loading branch information
stefanholzapfel authored Oct 10, 2024
1 parent 7e7d3aa commit 2d4c251
Show file tree
Hide file tree
Showing 8 changed files with 979 additions and 103 deletions.
18 changes: 17 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,20 @@ setSate(
- Fix(typing): Revert to recursive type with additional condition

## [4.0.4]
- Fix(typing): Make state object "Required" in StatePath
- Fix(typing): Make state object "Required" in StatePath

## [5.0.0]
🚨🚨: I strongly recommend not using v4 and upgrade to this version
- Fix(StatePath): Change to non-recursive typing, revert array element selection

### 🚨 BREAKING CHANGE:
Since recursive typing proved to be super slow for large states and impossible to properly use intellisense,
I had to revert to multiple function overloads.
As a consequence the StatePath array element selection had to be changed / reverted:
```
['library', 'books', 1] or ['library', 'books', book => book.title === 'Harry Potter']
```
Needs to be reverted to that:
```
['library', { array: 'books', get: 1}] or ['library', { array: 'books', get: book => book.title === 'Harry Potter'}]
```
25 changes: 17 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,24 +244,33 @@ Same as using directly, just use the method ```this.setState()``` on your elemen
Since it is cumbersome to subscribe / mutate array elements, we have array operators.

<h2> Subscribing array elements </h2>
To subscribe to a specific element in an array, you can provide a predicate function or a numeric index. For the function, the subscription will use the
first element where it returns true. The syntax for the predicate function is:
To subscribe to a specific element in an array, you can provide a predicate function or a numeric index as part of the
```ArrayElementSelector``` interface:

```
type ArrayElementSelector<ArrayName, ElementType> = { array: ArrayName, get: IndexOrPredicateFunction<ElementType> };
```

The syntax for the predicate function is:

```
type PredicateFunction<ArrayType> = (array: ArrayType, index?: number) => boolean;
```

Example with predicate function:
For the function, the subscription will use the first element where it returns true.

Example with predicate function (takes the fist book with author named "Me" or the element at index 10 from the data
aray - whatever comes first):
```
this.subscribeState(['books', 'data', predicate: (book, index) => book.author === 'Me' || index === 10 ], value => {
this.subscribeState(['books', { array: 'data', get: (book, index) => book.author === 'Me' || index === 10 } ], value => {
value.previous // the value before change
value.current // the new value
})
```

Example with numeric index:
Example with numeric index (takes the element at index 10 of the "data" array):
```
this.subscribeState(['books', 'data', 10], value => {
this.subscribeState(['books', { array: 'data', get: 10 }], value => {
value.previous // the value before change
value.current // the new value
})
Expand All @@ -276,9 +285,9 @@ In your state changes you can also use this syntax to update, push or pull (remo
```
{
_arrayOperation:
{ op: 'update', at: PredicateFunction<State[number]> | number, val: StateChange<State[number]> | ((element: State[number]) => StateChange<State[number]>) } |
{ op: 'update', at: IndexOrPredicateFunction<State[number]> | number, val: StateChange<State[number]> | ((element: State[number]) => StateChange<State[number]>) } |
{ op: 'push', at?: number, val: State[number] } |
{ op: 'pull', at?: PredicateFunction<State[number]> | number }
{ op: 'pull', at?: IndexOrPredicateFunction<State[number]> | number }
}
```

Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@stefanholzapfel/lit-state",
"version": "4.0.4",
"version": "5.0.0",
"description": "A reactive state management for Lit",
"publishConfig": {
"access": "public"
Expand All @@ -16,11 +16,11 @@
"author": "Stefan Holzapfel",
"license": "MIT",
"dependencies": {
"lit": "^3.2.0",
"lit": "^3.2.1",
"ts-essentials": "^10.0.2"
},
"devDependencies": {
"typescript": "^5.6.2"
"typescript": "^5.6.3"
},
"type": "module",
"types": "dist/index.d.ts",
Expand Down
38 changes: 15 additions & 23 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,21 @@ export interface Change<P> {
export type StateReducerMode = 'merge' | 'replace';

export type PredicateFunction<ArrayType> = (array: ArrayType, index?: number) => boolean;
export type IndexOrPredicateFunction<Type> = number | PredicateFunction<Type>;
export type ArrayElementSelector<ArrayName, ElementType> = { array: ArrayName, get: IndexOrPredicateFunction<ElementType> };

export interface GetStateOptions {
getDeepCopy?: boolean;
}

export interface SubscribeStateOptions {
export interface SubscribeStateOptions extends GetStateOptions {
getInitialValue?: boolean;
// Set true to trigger changes when a sub-property of a subscribed property changes
pushNestedChanges?: boolean;
getDeepCopy?: boolean;
}

export interface SubscribeStateFromElementOptions extends SubscribeStateOptions {
autoUnsubscribe?: boolean;
}

export interface SetStateOptions<State> {
Expand All @@ -41,39 +50,22 @@ export interface SetStateOptions<State> {
entryPath?: StatePath<State>;
}

export interface SubscribeStateFromElementOptions extends SubscribeStateOptions {
autoUnsubscribe?: boolean;
}

export type StateChange<State> =
State extends Array<any> ?
{
_arrayOperation:
{ op: 'update', at: PredicateFunction<State[number]> | number, val: StateChange<State[number]> | ((element: State[number]) => StateChange<State[number]>) } |
{ op: 'update', at: IndexOrPredicateFunction<State[number]>, val: StateChange<State[number]> | ((element: State[number]) => StateChange<State[number]>) } |
{ op: 'push', at?: number, val: State[number] } |
{ op: 'pull', at?: PredicateFunction<State[number]> | number }
{ op: 'pull', at?: IndexOrPredicateFunction<State[number]> }
} |
State :
{
[P in keyof State]?:
State[P] | StateChange<State[P]>
} & { _reducerMode?: StateReducerMode };

export type IndexOrPredicateFunction<Type> = number | PredicateFunction<Type>;
export type StatePathKey = IndexOrPredicateFunction<any> | string;

export type StatePath<Obj, Path extends (string | IndexOrPredicateFunction<any>)[] = []> =
object extends Required<Obj>
? Path
: Obj extends object
? (Path |
// Check if object is array
(Obj extends readonly any[] ?
// ...when array only allow index or PredicateFunction
StatePath<Obj[number], [...Path, IndexOrPredicateFunction<Obj[number]>]>
// ...when object generate type of all possible keys
: { [Key in string & keyof Obj]: StatePath<Obj[Key], [...Path, Key]> }[string & keyof Obj]))
: Path;
// TODO: Properly type that so StatePath correlates with the generic inputs of the subscription & get overloads
export type StatePath<State> = readonly (string | ArrayElementSelector<string, any>)[];

export * from './litElementStateful';
export * from './litElementState.service';
Expand Down
Loading

0 comments on commit 2d4c251

Please sign in to comment.