@tmjeee/w-lib 0.0.1 → 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/CHANGELOG.md +13 -10
- package/README.md +181 -122
- package/dist/index.d.ts +195 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +436 -11
- package/dist/index.js.map +1 -1
- package/package.json +14 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,18 +5,21 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [0.0.1] - 2026-05-27
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
## [0.1.1] - 2026-06-01
|
|
10
|
+
- added `template-selection` , a convenient Angular template marker
|
|
11
|
+
- separate each utilities into it's own example file
|
|
12
|
+
|
|
13
|
+
## [0.1.0] - 2026-05-29
|
|
14
|
+
- added initial version of
|
|
15
|
+
- event emitter
|
|
16
|
+
- state management
|
|
17
|
+
- finite state machine
|
|
18
|
+
- loading states
|
|
19
|
+
|
|
20
|
+
## [0.0.1] - 2026-05-27
|
|
11
21
|
- Initial library skeleton for npm publishing
|
|
12
|
-
-
|
|
13
|
-
- Full ESM + CJS dual package support via tsdown
|
|
22
|
+
- Full ESM package support via tsdown
|
|
14
23
|
- TypeScript strict configuration
|
|
15
24
|
- Vitest test suite with coverage
|
|
16
|
-
- `examples/basic.ts` runnable usage example
|
|
17
|
-
- Complete publish-ready `package.json` (exports map, files, sideEffects, publishConfig)
|
|
18
25
|
- MIT license and minimal conventional changelog
|
|
19
|
-
|
|
20
|
-
### Notes
|
|
21
|
-
- This is the bootstrap release of the w-lib skeleton.
|
|
22
|
-
- Ready for `npm publish --access public`.
|
package/README.md
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@tmjeee/w-lib)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
A minimal, modern TypeScript library skeleton ready for publishing to npmjs.com.
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
## Development
|
|
8
|
+
See [here](./README_DEV.md) for more info.
|
|
9
|
+
|
|
10
|
+
A minimal, Angular and TypeScript library.
|
|
9
11
|
|
|
10
12
|
## Installation
|
|
11
13
|
|
|
@@ -17,164 +19,221 @@ npm install @tmjeee/w-lib
|
|
|
17
19
|
|
|
18
20
|
This package is **ESM-only**.
|
|
19
21
|
|
|
20
|
-
```ts
|
|
21
|
-
import { helloWorld } from '@tmjeee/w-lib';
|
|
22
|
-
|
|
23
|
-
console.log(helloWorld()); // "Hello, world!"
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
> **Note**: CommonJS (`require()`) is not supported.
|
|
27
|
-
> If you are in a CommonJS environment, use dynamic import instead:
|
|
28
|
-
> ```js
|
|
29
|
-
> const { helloWorld } = await import('@tmjeee/w-lib');
|
|
30
|
-
> ```
|
|
31
|
-
|
|
32
|
-
For more information, see the [Node.js documentation on publishing ESM packages](https://nodejs.org/en/learn/modules/publishing-a-package).
|
|
33
22
|
|
|
34
|
-
|
|
23
|
+
### loading-state (Angular)
|
|
24
|
+
```typescript
|
|
25
|
+
import {createLoadingState} from '@tmjeee/w-lib';
|
|
35
26
|
|
|
36
|
-
|
|
27
|
+
const loadingState = createLoadingState();
|
|
28
|
+
loadingState.withLoading('assets', async ()=>{
|
|
29
|
+
// xhr calls, long running calls
|
|
30
|
+
// loadingState.is('asset'); will be true while this function is running else false
|
|
31
|
+
});
|
|
37
32
|
|
|
38
|
-
|
|
33
|
+
const isLoading = loadingState.is('assets'); // signal<boolean> - true when loading else false
|
|
39
34
|
|
|
40
|
-
|
|
41
|
-
helloWorld(); // "Hello, world!"
|
|
35
|
+
loadingState.set('assets', false); // explicitly mark 'asset' as false
|
|
42
36
|
```
|
|
43
37
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
38
|
+
### Finite state machine (Generic)
|
|
39
|
+
```typescript
|
|
40
|
+
import {FsmState, Fsm} from '@tmjeee/w-lib';
|
|
41
|
+
|
|
42
|
+
class State1 extends FsmState {
|
|
43
|
+
enter(prevState: FsmState | null, ...params: any) {
|
|
44
|
+
console.log(`[State1] enter`);
|
|
45
|
+
}
|
|
46
|
+
exit(nextState: FsmState | null, ...params: any) {
|
|
47
|
+
console.log(`[State1] exit`);
|
|
48
|
+
}
|
|
49
|
+
update(...params: any) {
|
|
50
|
+
console.log(`[State1] update`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class State2 extends FsmState {
|
|
56
|
+
enter(prevState: FsmState | null, ...params: any) {
|
|
57
|
+
console.log(`[State2] enter`);
|
|
58
|
+
}
|
|
59
|
+
exit(nextState: FsmState | null, ...params: any) {
|
|
60
|
+
console.log(`[State2] exit`);
|
|
61
|
+
}
|
|
62
|
+
update(...params: any) {
|
|
63
|
+
console.log(`[State2] update`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const fsm = new Fsm();
|
|
68
|
+
fsm.register(
|
|
69
|
+
new State1(`state1`),
|
|
70
|
+
new State2(`state2`),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
fsm.set('state1', {}); // state1.enter(...)
|
|
74
|
+
fsm.update(); // state1.update({})
|
|
75
|
+
fsm.set('state2'); // state1.exit(...) then state2.enter(...)
|
|
76
|
+
fsm.update(); // state2.update({});
|
|
77
|
+
fsm.get(); // return current state -> state2
|
|
67
78
|
```
|
|
68
79
|
|
|
69
|
-
### Project Structure
|
|
70
80
|
|
|
81
|
+
### State Management (Generic)
|
|
82
|
+
```typescript
|
|
83
|
+
const store = createStateStore<State>({
|
|
84
|
+
name: 'jim',
|
|
85
|
+
age: 12,
|
|
86
|
+
address: {
|
|
87
|
+
address1: '1 Kent Street',
|
|
88
|
+
address2: 'Sydney',
|
|
89
|
+
postcode: '2000',
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
const address1 = store.get('address.address1');
|
|
95
|
+
store.on('change', (change) => {
|
|
96
|
+
const state = change.state;
|
|
97
|
+
const previousState = change.previousState;
|
|
98
|
+
const value = change.value;
|
|
99
|
+
const previousValue = change.previousValue;
|
|
100
|
+
|
|
101
|
+
console.log(`change`, state, previousState, value, previousValue);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// all the following will trigger 'change' event
|
|
105
|
+
store.update('address', (address) => ({address1: 'new address1', address2: 'new address2', postcode: 'new postcode'}));
|
|
106
|
+
store.update('name', (name) => `name-${new Date()}`);
|
|
107
|
+
store.set('name', 'name1');
|
|
108
|
+
store.patch({age: 2, name: 'test'});
|
|
109
|
+
store.batch((ctx) => {
|
|
110
|
+
ctx.set('age', 1);
|
|
111
|
+
ctx.update('age', (age)=>age + 1);
|
|
112
|
+
ctx.patch({age: 2, name: 'test'});
|
|
113
|
+
});
|
|
71
114
|
```
|
|
72
|
-
src/index.ts # Source (helloWorld)
|
|
73
|
-
test/index.test.ts # Vitest tests
|
|
74
|
-
examples/basic.ts # Runnable usage demo
|
|
75
|
-
dist/ # Build output (generated)
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
## Publishing
|
|
79
115
|
|
|
80
|
-
This package is configured for immediate publishing.
|
|
81
116
|
|
|
82
|
-
###
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
117
|
+
### Event Emitter (Generic)
|
|
118
|
+
```typescript
|
|
119
|
+
const emitter1 = new JsEventEmitter();
|
|
120
|
+
const emitter2 = new JsEventEmitter();
|
|
121
|
+
emitter1.on('test',()=>{
|
|
122
|
+
console.log(`[Emitter1] Receive test event`);
|
|
123
|
+
});
|
|
124
|
+
emitter2.once('test', ()=>{
|
|
125
|
+
console.log(`[Emitter2] Receive test event - once only`);
|
|
126
|
+
});
|
|
127
|
+
emitter1.emit('test', {test: 'test1'});
|
|
128
|
+
emitter1.emit('test', {test: 'test2'});
|
|
129
|
+
emitter2.emit('test', {test: 'test1'});
|
|
130
|
+
emitter2.emit('test', {test: 'test2'});
|
|
131
|
+
emitter1.off('test'); // turn off listening
|
|
132
|
+
emitter1.emit('test', {test: 'test3'});
|
|
93
133
|
```
|
|
94
134
|
|
|
95
|
-
### Manual / step-by-step (with safety checks)
|
|
96
|
-
|
|
97
|
-
```bash
|
|
98
|
-
# 1. Make sure everything is clean
|
|
99
|
-
npm run typecheck && npm test && npm run build
|
|
100
|
-
# (npm test now runs once and exits automatically)
|
|
101
135
|
|
|
102
|
-
# 2. Dry-run the publish (strongly recommended)
|
|
103
|
-
npm publish --dry-run --access public
|
|
104
136
|
|
|
105
|
-
|
|
106
|
-
npm version patch
|
|
137
|
+
### TemplateSelection Directive (Angular)
|
|
107
138
|
|
|
108
|
-
|
|
109
|
-
npm publish --access public
|
|
110
|
-
```
|
|
139
|
+
A lightweight directive that marks a template (or element) with a name. This allows you to define multiple named templates and then query + render them manually at runtime.
|
|
111
140
|
|
|
112
|
-
|
|
141
|
+
It is especially useful when you want to let a component support multiple "slots" or conditional template rendering without hardcoding them.
|
|
113
142
|
|
|
114
|
-
|
|
143
|
+
#### Import
|
|
115
144
|
|
|
116
|
-
|
|
145
|
+
```ts
|
|
146
|
+
import { TemplateSelection } from '@tmjeee/w-lib';
|
|
147
|
+
```
|
|
117
148
|
|
|
118
|
-
|
|
149
|
+
#### Supported Syntax
|
|
119
150
|
|
|
120
|
-
|
|
151
|
+
You can use it in two ways:
|
|
121
152
|
|
|
122
|
-
|
|
123
|
-
|
|
153
|
+
```html
|
|
154
|
+
<!-- Recommended: on ng-template -->
|
|
155
|
+
<ng-template templateSelection="header">Header content</ng-template>
|
|
156
|
+
<ng-template templateSelection="footer">Footer content</ng-template>
|
|
124
157
|
|
|
125
|
-
|
|
158
|
+
<!-- Also supported: structural directive syntax -->
|
|
159
|
+
<div *templateSelection="'sidebar'">Sidebar content</div>
|
|
160
|
+
```
|
|
126
161
|
|
|
127
|
-
|
|
128
|
-
- Go to https://www.npmjs.com/settings/~tokens
|
|
129
|
-
- Generate a new **Automation** token (or **Granular** with publish permission for `@tmjeee/w-lib`)
|
|
130
|
-
2. Add the token as a repository secret:
|
|
131
|
-
- In your GitHub repo → **Settings → Secrets and variables → Actions**
|
|
132
|
-
- Create a new secret named `NPM_TOKEN` with the value from step 1
|
|
133
|
-
3. (Optional but recommended) Enable npm **Trusted Publishing** (OIDC) for passwordless publishing:
|
|
134
|
-
- See the comments in `publish.yml` and https://docs.npmjs.com/trusted-publishers
|
|
162
|
+
You can also bind the name dynamically:
|
|
135
163
|
|
|
136
|
-
|
|
164
|
+
```html
|
|
165
|
+
<ng-template [templateSelection]="templateName">...</ng-template>
|
|
166
|
+
```
|
|
137
167
|
|
|
138
|
-
|
|
139
|
-
# 1. Update CHANGELOG.md
|
|
140
|
-
# 2. Commit everything
|
|
141
|
-
git add .
|
|
142
|
-
git commit -m "chore: prepare v0.0.2"
|
|
168
|
+
#### Querying Templates
|
|
143
169
|
|
|
144
|
-
|
|
145
|
-
npm version patch
|
|
146
|
-
git push && git push --tags
|
|
170
|
+
Use Angular's `viewChildren` or `contentChildren` to collect the templates:
|
|
147
171
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
172
|
+
```ts
|
|
173
|
+
@Component({...})
|
|
174
|
+
export class MyComponent {
|
|
175
|
+
// Use viewChildren when templates are defined inside this component
|
|
176
|
+
templates = viewChildren(TemplateSelection);
|
|
177
|
+
|
|
178
|
+
// Use contentChildren when accepting templates via content projection
|
|
179
|
+
// projectedTemplates = contentChildren(TemplateSelection);
|
|
180
|
+
}
|
|
181
|
+
```
|
|
151
182
|
|
|
152
|
-
|
|
183
|
+
#### Full Example
|
|
153
184
|
|
|
154
|
-
|
|
185
|
+
```ts
|
|
186
|
+
import { Component, viewChildren, ViewContainerRef, inject, afterNextRender } from '@angular/core';
|
|
187
|
+
import { TemplateSelection } from '@tmjeee/w-lib';
|
|
188
|
+
|
|
189
|
+
@Component({
|
|
190
|
+
selector: 'my-component',
|
|
191
|
+
standalone: true,
|
|
192
|
+
imports: [TemplateSelection],
|
|
193
|
+
template: `
|
|
194
|
+
<ng-template templateSelection="header">
|
|
195
|
+
<h1>My Header</h1>
|
|
196
|
+
</ng-template>
|
|
197
|
+
|
|
198
|
+
<div *templateSelection="'content'">
|
|
199
|
+
Main content here
|
|
200
|
+
</div>
|
|
201
|
+
`
|
|
202
|
+
})
|
|
203
|
+
export class MyComponent {
|
|
204
|
+
private vcr = inject(ViewContainerRef);
|
|
205
|
+
templates = viewChildren(TemplateSelection);
|
|
206
|
+
|
|
207
|
+
constructor() {
|
|
208
|
+
afterNextRender(() => {
|
|
209
|
+
const headerTpl = this.templates()
|
|
210
|
+
.find(t => t.name() === 'header')
|
|
211
|
+
?.templateRef;
|
|
212
|
+
|
|
213
|
+
if (headerTpl) {
|
|
214
|
+
this.vcr.createEmbeddedView(headerTpl);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
getTemplate(name: string) {
|
|
220
|
+
return this.templates().find(t => t.name() === name)?.templateRef;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
155
223
|
```
|
|
156
224
|
|
|
157
|
-
|
|
225
|
+
You can then use the retrieved `TemplateRef` with `*ngTemplateOutlet` or `ViewContainerRef.createEmbeddedView()`.
|
|
158
226
|
|
|
159
|
-
|
|
160
|
-
- **Testing**: [Vitest](https://vitest.dev/)
|
|
161
|
-
- **TypeScript**: Strict mode, ES2022 target, bundler resolution
|
|
227
|
+
#### Notes
|
|
162
228
|
|
|
163
|
-
|
|
229
|
+
- The directive is **standalone**.
|
|
230
|
+
- Use `viewChildren(TemplateSelection)` for templates defined inside the component.
|
|
231
|
+
- Use `contentChildren(TemplateSelection)` when the templates are provided by the parent via content projection.
|
|
164
232
|
|
|
165
|
-
For consistency with other `@tmjeee` projects you can add:
|
|
166
233
|
|
|
167
|
-
|
|
168
|
-
npm install -D oxlint oxfmt
|
|
169
|
-
```
|
|
234
|
+
## Disclaimer
|
|
170
235
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
```json
|
|
174
|
-
"lint": "oxlint",
|
|
175
|
-
"format": "oxfmt --check",
|
|
176
|
-
"format:fix": "oxfmt --write"
|
|
177
|
-
```
|
|
236
|
+
Disclaimer: Contains codes from [jyoung4242](https://github.com/jyoung4242/Game-Dev-Library).
|
|
178
237
|
|
|
179
238
|
## License
|
|
180
239
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,204 @@
|
|
|
1
|
-
|
|
1
|
+
import * as _angular_core0 from "@angular/core";
|
|
2
|
+
import { Signal, TemplateRef } from "@angular/core";
|
|
3
|
+
import { Subject } from "rxjs";
|
|
4
|
+
|
|
5
|
+
//#region src/loading-state.d.ts
|
|
2
6
|
/**
|
|
3
|
-
*
|
|
7
|
+
* A lightweight utility for managing per-action loading states using signals.
|
|
4
8
|
*
|
|
5
|
-
*
|
|
9
|
+
* Philosophy:
|
|
10
|
+
* - Most errors should propagate to GlobalErrorHandler.
|
|
11
|
+
* - This utility helps manage UI loading states cleanly without forcing try/catch everywhere.
|
|
12
|
+
* - Use it when you need to disable buttons / show spinners during async actions.
|
|
13
|
+
*
|
|
14
|
+
* Recommended usage:
|
|
15
|
+
*
|
|
16
|
+
* const loading = createLoadingState<'creatingBoard' | 'deletingBoard'>();
|
|
17
|
+
*
|
|
18
|
+
* async createBoard() {
|
|
19
|
+
* const title = await openDialog();
|
|
20
|
+
* if (!title) return;
|
|
21
|
+
*
|
|
22
|
+
* await loading.withLoading('creatingBoard', async () => {
|
|
23
|
+
* const board = await this.boardService.createBoard(...);
|
|
24
|
+
* this.router.navigate(...);
|
|
25
|
+
* });
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* In template:
|
|
29
|
+
* <button [disabled]="loading.is('creatingBoard')()">
|
|
30
|
+
* {{ loading.is('creatingBoard')() ? 'Creating...' : 'Create Board' }}
|
|
31
|
+
* </button>
|
|
32
|
+
*/
|
|
33
|
+
declare function createLoadingState<T extends string = string>(): {
|
|
34
|
+
is: (key: T) => Signal<boolean>;
|
|
35
|
+
set: (key: T, value: boolean) => void;
|
|
36
|
+
withLoading: <R>(key: T, fn: () => Promise<R>) => Promise<R>;
|
|
37
|
+
states: Signal<Partial<Record<T, boolean>>>;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Type helper for action loading keys.
|
|
41
|
+
* Example: type UserAction = 'addingToWorkspace' | 'removingFromWorkspace';
|
|
42
|
+
*/
|
|
43
|
+
type LoadingKey = string;
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/event-emitter.d.ts
|
|
46
|
+
interface EventEmitterHandler<T> {
|
|
47
|
+
(change: T): void;
|
|
48
|
+
}
|
|
49
|
+
interface EventEmitterSubscription {
|
|
50
|
+
unsubscribe: () => void;
|
|
51
|
+
}
|
|
52
|
+
interface EventEmitter<T> {
|
|
53
|
+
emit(event: string, payload: T): void;
|
|
54
|
+
on(event: string, handler: EventEmitterHandler<T>): void;
|
|
55
|
+
off(event: string, handler?: EventEmitterHandler<T>): void;
|
|
56
|
+
once(event: string, handler: EventEmitterHandler<T>): void;
|
|
57
|
+
}
|
|
58
|
+
declare class RxEventEmitter<T> implements EventEmitter<T> {
|
|
59
|
+
_listeners: Record<string, Subject<T>>;
|
|
60
|
+
emit(event: string, payload: T): void;
|
|
61
|
+
on(event: string, handler: EventEmitterHandler<T>): void;
|
|
62
|
+
off(event: string, handler?: EventEmitterHandler<T>): void;
|
|
63
|
+
once(event: string, handler: EventEmitterHandler<T>): void;
|
|
64
|
+
}
|
|
65
|
+
declare class JsEventEmitter<T> implements EventEmitter<T> {
|
|
66
|
+
_listeners: Record<string, EventEmitterHandler<T>[]>;
|
|
67
|
+
emit(event: string, payload: T): void;
|
|
68
|
+
on(event: string, handler: EventEmitterHandler<T>): void;
|
|
69
|
+
off(event: string, handler?: EventEmitterHandler<T>): void;
|
|
70
|
+
once(event: string, handler: EventEmitterHandler<T>): void;
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/state-management.d.ts
|
|
74
|
+
type Primitive = string | number | boolean | null | undefined;
|
|
75
|
+
type PathsOf<T, Prefix extends string = ""> = T extends Primitive ? never : T extends Array<infer _> ? never : { [K in keyof T & string]: Prefix extends "" ? K | (T[K] extends Primitive ? never : PathsOf<T[K], K>) : `${Prefix}.${K}` | (T[K] extends Primitive ? never : PathsOf<T[K], `${Prefix}.${K}`>) }[keyof T & string];
|
|
76
|
+
type Path<T> = PathsOf<T>;
|
|
77
|
+
type PathValue<T, P extends string> = P extends keyof T ? T[P] : P extends `${infer K}.${infer Rest}` ? K extends keyof T ? Rest extends Path<T[K]> ? PathValue<T[K], Rest> : never : never : never;
|
|
78
|
+
interface ChangePayload<State, V = unknown> {
|
|
79
|
+
path: string;
|
|
80
|
+
previousValue: V;
|
|
81
|
+
value: V;
|
|
82
|
+
previousState: Readonly<State>;
|
|
83
|
+
state: Readonly<State>;
|
|
84
|
+
}
|
|
85
|
+
type ChangeEventMap<State> = {
|
|
86
|
+
change: ChangePayload<State>;
|
|
87
|
+
[key: `change:${string}`]: ChangePayload<State>;
|
|
88
|
+
};
|
|
89
|
+
interface StateStore<State extends object> {
|
|
90
|
+
get(): Readonly<State>;
|
|
91
|
+
get<K extends Path<State>>(path: K): Readonly<PathValue<State, K>>;
|
|
92
|
+
select<T>(selector: (state: Readonly<State>) => T): Readonly<T>;
|
|
93
|
+
set<K extends Path<State>>(path: K, value: PathValue<State, K>): void;
|
|
94
|
+
update<K extends Path<State>>(path: K, fn: (current: PathValue<State, K>) => PathValue<State, K>): void;
|
|
95
|
+
patch(partial: DeepPartial<State>): void;
|
|
96
|
+
batch(fn: (store: BatchContext<State>) => void): void;
|
|
97
|
+
on<E extends keyof ChangeEventMap<State>>(event: E, handler: (payload: ChangeEventMap<State>[E]) => void): void;
|
|
98
|
+
off<E extends keyof ChangeEventMap<State>>(event: E, handler: (payload: ChangeEventMap<State>[E]) => void): void;
|
|
99
|
+
subscribe<K extends Path<State>>(path: K, handler: (payload: ChangePayload<State, PathValue<State, K>>) => void): () => void;
|
|
100
|
+
serialize(): string;
|
|
101
|
+
reset(): void;
|
|
102
|
+
}
|
|
103
|
+
interface BatchContext<State extends object> {
|
|
104
|
+
set<K extends Path<State>>(path: K, value: PathValue<State, K>): void;
|
|
105
|
+
update<K extends Path<State>>(path: K, fn: (current: PathValue<State, K>) => PathValue<State, K>): void;
|
|
106
|
+
patch(partial: DeepPartial<State>): void;
|
|
107
|
+
}
|
|
108
|
+
type DeepPartial<T> = T extends Primitive ? T : { [K in keyof T]?: DeepPartial<T[K]> };
|
|
109
|
+
declare function createStateStore<State extends object>(initialState: State, eventEmitter?: EventEmitter<ChangePayload<State>>): StateStore<State>;
|
|
110
|
+
//#endregion
|
|
111
|
+
//#region src/finite-state-machine.d.ts
|
|
112
|
+
type FsmResult<T = void> = {
|
|
113
|
+
success: boolean;
|
|
114
|
+
message?: string;
|
|
115
|
+
value?: T;
|
|
116
|
+
};
|
|
117
|
+
declare class Fsm {
|
|
118
|
+
states: Map<string, FsmState>;
|
|
119
|
+
current: FsmState | null;
|
|
120
|
+
currentParams: any[];
|
|
121
|
+
currentTime: number;
|
|
122
|
+
constructor();
|
|
123
|
+
/** Register new states, returns array of registered ExState */
|
|
124
|
+
register(...types: (string | FsmState)[]): FsmResult<FsmState[]>;
|
|
125
|
+
/** Change current state */
|
|
126
|
+
set(state: string | FsmState, ...params: any[]): FsmResult<FsmState> | Promise<FsmResult<FsmState>>;
|
|
127
|
+
/** Check if a state is registered */
|
|
128
|
+
has(state: string | FsmState): boolean;
|
|
129
|
+
/** Get current state */
|
|
130
|
+
get(): FsmResult<FsmState>;
|
|
131
|
+
/** Reset FSM */
|
|
132
|
+
reset(): FsmResult<void>;
|
|
133
|
+
/** Update current state */
|
|
134
|
+
update(): FsmResult<void> | Promise<FsmResult<void>>;
|
|
135
|
+
}
|
|
136
|
+
declare class FsmState {
|
|
137
|
+
name: string;
|
|
138
|
+
constructor(name: string);
|
|
139
|
+
enter(_previous: FsmState | null, ..._params: any): void | Promise<void>;
|
|
140
|
+
exit(_next: FsmState | null, ..._params: any): void | Promise<void>;
|
|
141
|
+
update(..._params: any): void | Promise<void>;
|
|
142
|
+
}
|
|
143
|
+
//#endregion
|
|
144
|
+
//#region src/template-selection.d.ts
|
|
145
|
+
/**
|
|
146
|
+
* A directive that marks a template (or element) with a name so it can be
|
|
147
|
+
* queried and rendered manually.
|
|
148
|
+
*
|
|
149
|
+
* This is useful in two main scenarios:
|
|
150
|
+
* - Defining multiple named templates inside a component and choosing which one to render at runtime.
|
|
151
|
+
* - Accepting named templates from a parent component via content projection.
|
|
152
|
+
*
|
|
153
|
+
* ## Supported Syntaxes
|
|
154
|
+
*
|
|
155
|
+
* ```html
|
|
156
|
+
* <!-- On ng-template (recommended for pure templates) -->
|
|
157
|
+
* <ng-template templateSelection="header">...</ng-template>
|
|
158
|
+
* <ng-template templateSelection="footer">...</ng-template>
|
|
159
|
+
*
|
|
160
|
+
* <!-- Using structural directive syntax (also supported) -->
|
|
161
|
+
* <div *templateSelection="'sidebar'">Sidebar content</div>
|
|
162
|
+
* ```
|
|
163
|
+
*
|
|
164
|
+
* ## Consumption Example
|
|
165
|
+
*
|
|
166
|
+
* You can query the templates using either `viewChildren()` or `contentChildren()`,
|
|
167
|
+
* depending on your use case:
|
|
6
168
|
*
|
|
7
|
-
* @example
|
|
8
169
|
* ```ts
|
|
9
|
-
*
|
|
170
|
+
* @Component({...})
|
|
171
|
+
* export class MyComponent {
|
|
172
|
+
* // Use viewChildren() if the templates are defined inside this component's own template
|
|
173
|
+
* internalTemplates = viewChildren(TemplateSelection);
|
|
174
|
+
*
|
|
175
|
+
* // Use contentChildren() if you want to accept named templates from the parent via content projection
|
|
176
|
+
* projectedTemplates = contentChildren(TemplateSelection);
|
|
177
|
+
*
|
|
178
|
+
* getTemplate(name: string): TemplateRef<any> | undefined {
|
|
179
|
+
* return this.internalTemplates()
|
|
180
|
+
* .find(t => t.name() === name)
|
|
181
|
+
* ?.templateRef;
|
|
182
|
+
* }
|
|
183
|
+
* }
|
|
184
|
+
* ```
|
|
10
185
|
*
|
|
11
|
-
*
|
|
186
|
+
* Then in the template:
|
|
187
|
+
* ```html
|
|
188
|
+
* <ng-container *ngTemplateOutlet="getTemplate('header')"></ng-container>
|
|
12
189
|
* ```
|
|
13
190
|
*/
|
|
14
|
-
declare
|
|
191
|
+
declare class TemplateSelection {
|
|
192
|
+
/**
|
|
193
|
+
* The name used to identify this template.
|
|
194
|
+
* Can be bound as `templateSelection="myName"` or `[templateSelection]="someVariable"`.
|
|
195
|
+
*/
|
|
196
|
+
name: _angular_core0.InputSignal<string>;
|
|
197
|
+
/**
|
|
198
|
+
* The underlying TemplateRef that can be used with *ngTemplateOutlet or ViewContainerRef.
|
|
199
|
+
*/
|
|
200
|
+
readonly templateRef: TemplateRef<any>;
|
|
201
|
+
}
|
|
15
202
|
//#endregion
|
|
16
|
-
export {
|
|
203
|
+
export { BatchContext, ChangePayload, EventEmitter, EventEmitterHandler, EventEmitterSubscription, Fsm, FsmResult, FsmState, JsEventEmitter, LoadingKey, Path, PathValue, RxEventEmitter, StateStore, TemplateSelection, createLoadingState, createStateStore };
|
|
17
204
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/loading-state.ts","../src/event-emitter.ts","../src/state-management.ts","../src/finite-state-machine.ts","../src/template-selection.ts"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,20 +1,445 @@
|
|
|
1
|
-
|
|
1
|
+
import { Directive, TemplateRef, computed, inject, input, signal } from "@angular/core";
|
|
2
|
+
import { Subject } from "rxjs";
|
|
3
|
+
import { take } from "rxjs/operators";
|
|
4
|
+
|
|
5
|
+
//#region src/loading-state.ts
|
|
2
6
|
/**
|
|
3
|
-
*
|
|
7
|
+
* A lightweight utility for managing per-action loading states using signals.
|
|
8
|
+
*
|
|
9
|
+
* Philosophy:
|
|
10
|
+
* - Most errors should propagate to GlobalErrorHandler.
|
|
11
|
+
* - This utility helps manage UI loading states cleanly without forcing try/catch everywhere.
|
|
12
|
+
* - Use it when you need to disable buttons / show spinners during async actions.
|
|
13
|
+
*
|
|
14
|
+
* Recommended usage:
|
|
4
15
|
*
|
|
5
|
-
*
|
|
16
|
+
* const loading = createLoadingState<'creatingBoard' | 'deletingBoard'>();
|
|
6
17
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
18
|
+
* async createBoard() {
|
|
19
|
+
* const title = await openDialog();
|
|
20
|
+
* if (!title) return;
|
|
10
21
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
22
|
+
* await loading.withLoading('creatingBoard', async () => {
|
|
23
|
+
* const board = await this.boardService.createBoard(...);
|
|
24
|
+
* this.router.navigate(...);
|
|
25
|
+
* });
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* In template:
|
|
29
|
+
* <button [disabled]="loading.is('creatingBoard')()">
|
|
30
|
+
* {{ loading.is('creatingBoard')() ? 'Creating...' : 'Create Board' }}
|
|
31
|
+
* </button>
|
|
13
32
|
*/
|
|
14
|
-
function
|
|
15
|
-
|
|
33
|
+
function createLoadingState() {
|
|
34
|
+
const loadingMap = signal({});
|
|
35
|
+
const signalCache = /* @__PURE__ */ new Map();
|
|
36
|
+
/**
|
|
37
|
+
* Returns a reactive signal indicating if a specific action is loading.
|
|
38
|
+
* The returned signal is stable (same reference) for the same key.
|
|
39
|
+
*/
|
|
40
|
+
const is = (key) => {
|
|
41
|
+
if (!signalCache.has(key)) signalCache.set(key, computed(() => loadingMap()[key] ?? false));
|
|
42
|
+
return signalCache.get(key);
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Manually set the loading state for a key.
|
|
46
|
+
*/
|
|
47
|
+
const set = (key, value) => {
|
|
48
|
+
loadingMap.update((map) => ({
|
|
49
|
+
...map,
|
|
50
|
+
[key]: value
|
|
51
|
+
}));
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Runs an async function while automatically managing the loading state.
|
|
55
|
+
* Errors are allowed to propagate (as per project convention).
|
|
56
|
+
*/
|
|
57
|
+
const withLoading = async (key, fn) => {
|
|
58
|
+
set(key, true);
|
|
59
|
+
try {
|
|
60
|
+
return await fn();
|
|
61
|
+
} finally {
|
|
62
|
+
set(key, false);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
return {
|
|
66
|
+
is,
|
|
67
|
+
set,
|
|
68
|
+
withLoading,
|
|
69
|
+
states: loadingMap.asReadonly()
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/event-emitter.ts
|
|
75
|
+
var RxEventEmitter = class {
|
|
76
|
+
_listeners = {};
|
|
77
|
+
emit(event, payload) {
|
|
78
|
+
const handlers = this._listeners[event];
|
|
79
|
+
if (handlers) handlers.next(payload);
|
|
80
|
+
}
|
|
81
|
+
on(event, handler) {
|
|
82
|
+
if (!this._listeners[event]) this._listeners[event] = new Subject();
|
|
83
|
+
this._listeners[event].subscribe(handler);
|
|
84
|
+
}
|
|
85
|
+
off(event, handler) {
|
|
86
|
+
if (this._listeners[event]) {
|
|
87
|
+
this._listeners[event].complete();
|
|
88
|
+
delete this._listeners[event];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
once(event, handler) {
|
|
92
|
+
if (!this._listeners[event]) this._listeners[event] = new Subject();
|
|
93
|
+
this._listeners[event].pipe(take(1)).subscribe(handler);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
var JsEventEmitter = class {
|
|
97
|
+
_listeners = {};
|
|
98
|
+
emit(event, payload) {
|
|
99
|
+
const handlers = this._listeners[event];
|
|
100
|
+
if (handlers && handlers.length) handlers.forEach((h) => {
|
|
101
|
+
h(payload);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
on(event, handler) {
|
|
105
|
+
if (!this._listeners[event]) this._listeners[event] = [];
|
|
106
|
+
if (!this._listeners[event].find((h) => h == handler)) this._listeners[event].push(handler);
|
|
107
|
+
}
|
|
108
|
+
off(event, handler) {
|
|
109
|
+
if (!this._listeners[event]) this._listeners[event] = [];
|
|
110
|
+
if (handler) {
|
|
111
|
+
const idx = this._listeners[event].findIndex((h) => h == handler);
|
|
112
|
+
if (idx >= 0) this._listeners[event].splice(idx, 1);
|
|
113
|
+
} else delete this._listeners[event];
|
|
114
|
+
}
|
|
115
|
+
once(event, handler) {
|
|
116
|
+
if (!this._listeners[event]) this._listeners[event] = [];
|
|
117
|
+
const onceOnlyHandler = (evt) => {
|
|
118
|
+
try {
|
|
119
|
+
handler(evt);
|
|
120
|
+
} finally {
|
|
121
|
+
this.off(event, onceOnlyHandler);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
this._listeners[event].push(onceOnlyHandler);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/state-management.ts
|
|
130
|
+
function parsePath(path) {
|
|
131
|
+
return path.split(".");
|
|
132
|
+
}
|
|
133
|
+
function deepFreeze(obj) {
|
|
134
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
135
|
+
Object.getOwnPropertyNames(obj).forEach((name) => {
|
|
136
|
+
const val = obj[name];
|
|
137
|
+
if (val && typeof val === "object") deepFreeze(val);
|
|
138
|
+
});
|
|
139
|
+
return Object.freeze(obj);
|
|
16
140
|
}
|
|
141
|
+
function deepClone(obj) {
|
|
142
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
143
|
+
if (Array.isArray(obj)) return obj.map(deepClone);
|
|
144
|
+
const result = {};
|
|
145
|
+
for (const key of Object.keys(obj)) result[key] = deepClone(obj[key]);
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
function getAtPath(obj, segments) {
|
|
149
|
+
let current = obj;
|
|
150
|
+
for (const seg of segments) {
|
|
151
|
+
if (current === null || typeof current !== "object") throw new Error(`Invalid path segment "${seg}": not an object`);
|
|
152
|
+
current = current[seg];
|
|
153
|
+
}
|
|
154
|
+
return current;
|
|
155
|
+
}
|
|
156
|
+
function setAtPath(obj, segments, value) {
|
|
157
|
+
if (segments.length === 0) return value;
|
|
158
|
+
const [head, ...tail] = segments;
|
|
159
|
+
if (typeof obj !== "object" || obj === null) throw new Error(`Cannot set path on non-object at segment "${head}"`);
|
|
160
|
+
const record = obj;
|
|
161
|
+
const updated = { ...record };
|
|
162
|
+
if (tail.length === 0) updated[head] = value;
|
|
163
|
+
else {
|
|
164
|
+
const child = record[head];
|
|
165
|
+
if (child === null || typeof child !== "object") throw new Error(`Path segment "${head}" is not an object`);
|
|
166
|
+
updated[head] = setAtPath(child, tail, value);
|
|
167
|
+
}
|
|
168
|
+
return updated;
|
|
169
|
+
}
|
|
170
|
+
function shallowEqual(a, b) {
|
|
171
|
+
if (a === b) return true;
|
|
172
|
+
if (a === null || b === null) return false;
|
|
173
|
+
if (typeof a !== "object" || typeof b !== "object") return false;
|
|
174
|
+
const keysA = Object.keys(a);
|
|
175
|
+
const keysB = Object.keys(b);
|
|
176
|
+
if (keysA.length !== keysB.length) return false;
|
|
177
|
+
for (const k of keysA) if (a[k] !== b[k]) return false;
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
function deepMerge(base, partial) {
|
|
181
|
+
const result = { ...base };
|
|
182
|
+
for (const key of Object.keys(partial)) {
|
|
183
|
+
const pVal = partial[key];
|
|
184
|
+
const bVal = base[key];
|
|
185
|
+
if (pVal !== void 0 && pVal !== null && typeof pVal === "object" && !Array.isArray(pVal) && bVal !== null && typeof bVal === "object") result[key] = deepMerge(bVal, pVal);
|
|
186
|
+
else if (pVal !== void 0) result[key] = pVal;
|
|
187
|
+
}
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
function createStateStore(initialState, eventEmitter = new JsEventEmitter()) {
|
|
191
|
+
const _initial = deepFreeze(deepClone(initialState));
|
|
192
|
+
let _state = _initial;
|
|
193
|
+
const _emitter = eventEmitter;
|
|
194
|
+
function _emit(path, previousValue, value, previousState) {
|
|
195
|
+
const payload = {
|
|
196
|
+
path,
|
|
197
|
+
previousValue,
|
|
198
|
+
value,
|
|
199
|
+
previousState,
|
|
200
|
+
state: _state
|
|
201
|
+
};
|
|
202
|
+
_emitter.emit("change", payload);
|
|
203
|
+
_emitter.emit(`change:${path}`, payload);
|
|
204
|
+
}
|
|
205
|
+
function _applySet(currentState, segments, value) {
|
|
206
|
+
return deepFreeze(setAtPath(deepClone(currentState), segments, value));
|
|
207
|
+
}
|
|
208
|
+
function get(path) {
|
|
209
|
+
if (path === void 0) return _state;
|
|
210
|
+
const segments = parsePath(path);
|
|
211
|
+
return deepFreeze(deepClone(getAtPath(_state, segments)));
|
|
212
|
+
}
|
|
213
|
+
function select(selector) {
|
|
214
|
+
const result = selector(_state);
|
|
215
|
+
if (result !== null && typeof result === "object") return deepFreeze(deepClone(result));
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
function set(path, value) {
|
|
219
|
+
const segments = parsePath(path);
|
|
220
|
+
const previousValue = getAtPath(_state, segments);
|
|
221
|
+
if (shallowEqual(previousValue, value)) return;
|
|
222
|
+
const previousState = _state;
|
|
223
|
+
_state = _applySet(_state, segments, value);
|
|
224
|
+
_emit(path, previousValue, value, previousState);
|
|
225
|
+
}
|
|
226
|
+
function update(path, fn) {
|
|
227
|
+
const segments = parsePath(path);
|
|
228
|
+
set(path, fn(deepClone(getAtPath(_state, segments))));
|
|
229
|
+
}
|
|
230
|
+
function patch(partial) {
|
|
231
|
+
const nextState = deepFreeze(deepMerge(_state, partial));
|
|
232
|
+
const changedPaths = [];
|
|
233
|
+
collectChangedPaths("", _state, nextState, changedPaths);
|
|
234
|
+
if (changedPaths.length === 0) return;
|
|
235
|
+
const previousState = _state;
|
|
236
|
+
_state = nextState;
|
|
237
|
+
for (const entry of changedPaths) _emit(entry.path, entry.prev, entry.next, previousState);
|
|
238
|
+
}
|
|
239
|
+
function collectChangedPaths(prefix, prev, next, out) {
|
|
240
|
+
if (prev === next) return;
|
|
241
|
+
if (prev !== null && next !== null && typeof prev === "object" && typeof next === "object" && !Array.isArray(prev) && !Array.isArray(next)) {
|
|
242
|
+
const allKeys = new Set([...Object.keys(prev), ...Object.keys(next)]);
|
|
243
|
+
for (const k of allKeys) {
|
|
244
|
+
const pv = prev[k];
|
|
245
|
+
const nv = next[k];
|
|
246
|
+
collectChangedPaths(prefix ? `${prefix}.${k}` : k, pv, nv, out);
|
|
247
|
+
}
|
|
248
|
+
} else if (!shallowEqual(prev, next)) out.push({
|
|
249
|
+
path: prefix,
|
|
250
|
+
prev,
|
|
251
|
+
next
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
function batch(fn) {
|
|
255
|
+
let pendingState = _state;
|
|
256
|
+
const changes = [];
|
|
257
|
+
const ctx = {
|
|
258
|
+
set(path, value) {
|
|
259
|
+
const segments = parsePath(path);
|
|
260
|
+
const previousValue = getAtPath(pendingState, segments);
|
|
261
|
+
if (shallowEqual(previousValue, value)) return;
|
|
262
|
+
const prevState = pendingState;
|
|
263
|
+
pendingState = deepFreeze(setAtPath(deepClone(pendingState), segments, value));
|
|
264
|
+
changes.push({
|
|
265
|
+
path,
|
|
266
|
+
prev: previousValue,
|
|
267
|
+
next: value,
|
|
268
|
+
prevState
|
|
269
|
+
});
|
|
270
|
+
},
|
|
271
|
+
update(path, fn2) {
|
|
272
|
+
const segments = parsePath(path);
|
|
273
|
+
const next = fn2(deepClone(getAtPath(pendingState, segments)));
|
|
274
|
+
ctx.set(path, next);
|
|
275
|
+
},
|
|
276
|
+
patch(partial) {
|
|
277
|
+
const merged = deepFreeze(deepMerge(pendingState, partial));
|
|
278
|
+
const changedPaths = [];
|
|
279
|
+
collectChangedPaths("", pendingState, merged, changedPaths);
|
|
280
|
+
if (changedPaths.length === 0) return;
|
|
281
|
+
const prevState = pendingState;
|
|
282
|
+
pendingState = merged;
|
|
283
|
+
for (const entry of changedPaths) changes.push({
|
|
284
|
+
path: entry.path,
|
|
285
|
+
prev: entry.prev,
|
|
286
|
+
next: entry.next,
|
|
287
|
+
prevState
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
fn(ctx);
|
|
292
|
+
if (pendingState === _state) return;
|
|
293
|
+
_state = pendingState;
|
|
294
|
+
for (const change of changes) _emit(change.path, change.prev, change.next, change.prevState);
|
|
295
|
+
}
|
|
296
|
+
function on(event, handler) {
|
|
297
|
+
_emitter.on(event, handler);
|
|
298
|
+
}
|
|
299
|
+
function off(event, handler) {
|
|
300
|
+
_emitter.off(event, handler);
|
|
301
|
+
}
|
|
302
|
+
function subscribe(path, handler) {
|
|
303
|
+
const eventName = `change:${path}`;
|
|
304
|
+
const wrapped = (payload) => handler(payload);
|
|
305
|
+
_emitter.on(eventName, wrapped);
|
|
306
|
+
return () => _emitter.off(eventName, wrapped);
|
|
307
|
+
}
|
|
308
|
+
function serialize() {
|
|
309
|
+
return JSON.stringify(_state);
|
|
310
|
+
}
|
|
311
|
+
function reset() {
|
|
312
|
+
const previousState = _state;
|
|
313
|
+
_state = _initial;
|
|
314
|
+
const changes = [];
|
|
315
|
+
collectChangedPaths("", previousState, _state, changes);
|
|
316
|
+
for (const change of changes) _emit(change.path, change.prev, change.next, previousState);
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
get,
|
|
320
|
+
select,
|
|
321
|
+
set,
|
|
322
|
+
update,
|
|
323
|
+
patch,
|
|
324
|
+
batch,
|
|
325
|
+
on,
|
|
326
|
+
off,
|
|
327
|
+
subscribe,
|
|
328
|
+
serialize,
|
|
329
|
+
reset
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
//#endregion
|
|
334
|
+
//#region src/finite-state-machine.ts
|
|
335
|
+
var Fsm = class {
|
|
336
|
+
states = /* @__PURE__ */ new Map();
|
|
337
|
+
current = null;
|
|
338
|
+
currentParams = [];
|
|
339
|
+
currentTime = 0;
|
|
340
|
+
constructor() {}
|
|
341
|
+
/** Register new states, returns array of registered ExState */
|
|
342
|
+
register(...types) {
|
|
343
|
+
const generatedStates = [];
|
|
344
|
+
for (const state of types) {
|
|
345
|
+
const newState = typeof state === "string" ? new FsmState(state) : state;
|
|
346
|
+
if (this.states.has(newState.name)) console.warn(`State "${newState.name}" is already registered and will be overwritten.`);
|
|
347
|
+
this.states.set(newState.name, newState);
|
|
348
|
+
generatedStates.push(newState);
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
success: true,
|
|
352
|
+
value: generatedStates
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
/** Change current state */
|
|
356
|
+
set(state, ...params) {
|
|
357
|
+
const name = typeof state === "string" ? state : state.name;
|
|
358
|
+
const next = this.states.get(name);
|
|
359
|
+
if (!next) return {
|
|
360
|
+
success: false,
|
|
361
|
+
message: `State "${name}" not found`
|
|
362
|
+
};
|
|
363
|
+
const finalize = () => {
|
|
364
|
+
this.current = next;
|
|
365
|
+
this.currentParams = params;
|
|
366
|
+
this.currentTime = Date.now();
|
|
367
|
+
return {
|
|
368
|
+
success: true,
|
|
369
|
+
value: next
|
|
370
|
+
};
|
|
371
|
+
};
|
|
372
|
+
if (!this.current) {
|
|
373
|
+
const entering = next.enter(this.current, ...params);
|
|
374
|
+
return entering instanceof Promise ? entering.then(finalize) : finalize();
|
|
375
|
+
}
|
|
376
|
+
const leaving = this.current.exit(next, ...params);
|
|
377
|
+
if (leaving instanceof Promise) return leaving.then(() => {
|
|
378
|
+
const entering = next.enter(this.current, ...params);
|
|
379
|
+
return entering instanceof Promise ? entering.then(finalize) : finalize();
|
|
380
|
+
});
|
|
381
|
+
const entering = next.enter(this.current, ...params);
|
|
382
|
+
return entering instanceof Promise ? entering.then(finalize) : finalize();
|
|
383
|
+
}
|
|
384
|
+
/** Check if a state is registered */
|
|
385
|
+
has(state) {
|
|
386
|
+
const name = typeof state === "string" ? state : state.name;
|
|
387
|
+
return this.states.has(name);
|
|
388
|
+
}
|
|
389
|
+
/** Get current state */
|
|
390
|
+
get() {
|
|
391
|
+
if (!this.current) return {
|
|
392
|
+
success: false,
|
|
393
|
+
message: "No state set"
|
|
394
|
+
};
|
|
395
|
+
return {
|
|
396
|
+
success: true,
|
|
397
|
+
value: this.current
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
/** Reset FSM */
|
|
401
|
+
reset() {
|
|
402
|
+
this.current = null;
|
|
403
|
+
this.currentParams = [];
|
|
404
|
+
this.currentTime = 0;
|
|
405
|
+
return { success: true };
|
|
406
|
+
}
|
|
407
|
+
/** Update current state */
|
|
408
|
+
update() {
|
|
409
|
+
if (!this.current) return {
|
|
410
|
+
success: false,
|
|
411
|
+
message: "No current state"
|
|
412
|
+
};
|
|
413
|
+
const result = this.current.update(...this.currentParams);
|
|
414
|
+
return result instanceof Promise ? result.then(() => ({ success: true })) : { success: true };
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
var FsmState = class {
|
|
418
|
+
constructor(name) {
|
|
419
|
+
this.name = name;
|
|
420
|
+
}
|
|
421
|
+
enter(_previous, ..._params) {}
|
|
422
|
+
exit(_next, ..._params) {}
|
|
423
|
+
update(..._params) {}
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
//#endregion
|
|
427
|
+
//#region src/template-selection.ts
|
|
428
|
+
var TemplateSelection = @Directive({
|
|
429
|
+
selector: "[templateSelection]",
|
|
430
|
+
standalone: true
|
|
431
|
+
}) class {
|
|
432
|
+
/**
|
|
433
|
+
* The name used to identify this template.
|
|
434
|
+
* Can be bound as `templateSelection="myName"` or `[templateSelection]="someVariable"`.
|
|
435
|
+
*/
|
|
436
|
+
name = input.required({ alias: "templateSelection" });
|
|
437
|
+
/**
|
|
438
|
+
* The underlying TemplateRef that can be used with *ngTemplateOutlet or ViewContainerRef.
|
|
439
|
+
*/
|
|
440
|
+
templateRef = inject(TemplateRef);
|
|
441
|
+
};
|
|
17
442
|
|
|
18
443
|
//#endregion
|
|
19
|
-
export {
|
|
444
|
+
export { Fsm, FsmState, JsEventEmitter, RxEventEmitter, TemplateSelection, createLoadingState, createStateStore };
|
|
20
445
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * Classic hello world sample for the w-lib skeleton.\n *\n * @returns The canonical greeting string\n *\n * @example\n * ```ts\n * import { helloWorld } from '@tmjeee/w-lib';\n *\n * console.log(helloWorld()); // \"Hello, world!\"\n * ```\n */\nexport function helloWorld(): string {\n return 'Hello, world!';\n}\n"],"mappings":";;;;;;;;;;;;;AAYA,SAAgB,aAAqB;AACnC,QAAO"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/loading-state.ts","../src/event-emitter.ts","../src/state-management.ts","../src/finite-state-machine.ts","../src/template-selection.ts"],"sourcesContent":["import { computed, signal, Signal } from '@angular/core';\n\n/**\n * A lightweight utility for managing per-action loading states using signals.\n *\n * Philosophy:\n * - Most errors should propagate to GlobalErrorHandler.\n * - This utility helps manage UI loading states cleanly without forcing try/catch everywhere.\n * - Use it when you need to disable buttons / show spinners during async actions.\n *\n * Recommended usage:\n *\n * const loading = createLoadingState<'creatingBoard' | 'deletingBoard'>();\n *\n * async createBoard() {\n * const title = await openDialog();\n * if (!title) return;\n *\n * await loading.withLoading('creatingBoard', async () => {\n * const board = await this.boardService.createBoard(...);\n * this.router.navigate(...);\n * });\n * }\n *\n * In template:\n * <button [disabled]=\"loading.is('creatingBoard')()\">\n * {{ loading.is('creatingBoard')() ? 'Creating...' : 'Create Board' }}\n * </button>\n */\nexport function createLoadingState<T extends string = string>() {\n const loadingMap = signal<Partial<Record<T, boolean>>>({});\n const signalCache = new Map<T, Signal<boolean>>();\n\n /**\n * Returns a reactive signal indicating if a specific action is loading.\n * The returned signal is stable (same reference) for the same key.\n */\n const is = (key: T): Signal<boolean> => {\n if (!signalCache.has(key)) {\n signalCache.set(\n key,\n computed(() => loadingMap()[key] ?? false)\n );\n }\n return signalCache.get(key)!;\n };\n\n /**\n * Manually set the loading state for a key.\n */\n const set = (key: T, value: boolean): void => {\n loadingMap.update((map) => ({ ...map, [key]: value }));\n };\n\n /**\n * Runs an async function while automatically managing the loading state.\n * Errors are allowed to propagate (as per project convention).\n */\n const withLoading = async <R>(key: T, fn: () => Promise<R>): Promise<R> => {\n set(key, true);\n try {\n return await fn();\n } finally {\n set(key, false);\n }\n };\n\n /**\n * Raw access to the loading map (rarely needed).\n */\n const states = loadingMap.asReadonly();\n\n return {\n is,\n set,\n withLoading,\n states,\n };\n}\n\n/**\n * Type helper for action loading keys.\n * Example: type UserAction = 'addingToWorkspace' | 'removingFromWorkspace';\n */\nexport type LoadingKey = string;\n","\nexport interface EventEmitterHandler<T> {\n (change:T):void;\n}\n\nexport interface EventEmitterSubscription {\n unsubscribe:()=>void;\n}\n\n\nexport interface EventEmitter<T> {\n emit(event: string, payload: T): void;\n on(event: string, handler: EventEmitterHandler<T>): void;\n off(event: string, handler?: EventEmitterHandler<T>): void;\n once(event: string, handler: EventEmitterHandler<T>): void;\n}\n\n// =============================================================\n// ============== RxJs =========================================\n// =============================================================\n\nimport {Subject} from 'rxjs';\nimport {take} from 'rxjs/operators';\n\nexport class RxEventEmitter<T> implements EventEmitter<T> {\n\n _listeners: Record<string, Subject<T>> = {};\n\n emit(event: string, payload: T): void {\n const handlers = this._listeners[event];\n if (handlers) {\n handlers.next(payload);\n }\n }\n on(event: string, handler: EventEmitterHandler<T>): void {\n if (!this._listeners[event]) {\n this._listeners[event] = new Subject();\n }\n this._listeners[event].subscribe(handler)\n }\n off(event: string, handler?: EventEmitterHandler<T>): void {\n const s = this._listeners[event];\n if (s) {\n this._listeners[event].complete();\n delete this._listeners[event];\n }\n }\n once(event: string, handler: EventEmitterHandler<T>): void {\n if (!this._listeners[event]) {\n this._listeners[event] = new Subject();\n }\n this._listeners[event].pipe(\n take(1)\n ).subscribe(handler);\n }\n}\n\n\n// =============================================================\n// ============== Js ===========================================\n// =============================================================\n\nexport class JsEventEmitter<T> implements EventEmitter<T> {\n\n _listeners: Record<string, EventEmitterHandler<T>[]> = {};\n\n emit(event: string, payload: T): void {\n const handlers = this._listeners[event];\n if (handlers && handlers.length) {\n handlers.forEach(h => {\n h(payload);\n })\n }\n }\n on(event: string, handler: EventEmitterHandler<T>): void {\n if (!this._listeners[event]) {\n this._listeners[event] = [];\n }\n const exists = this._listeners[event].find(h => h == handler);\n if (!exists) {\n this._listeners[event].push(handler);\n }\n }\n off(event: string, handler?: EventEmitterHandler<T>): void {\n if (!this._listeners[event]) {\n this._listeners[event] = [];\n }\n if (handler) {\n const idx = this._listeners[event].findIndex(h => h == handler);\n if (idx >= 0) {\n this._listeners[event].splice(idx, 1);\n }\n } else {\n delete this._listeners[event];\n }\n }\n once(event: string, handler:EventEmitterHandler<T>): void {\n if (!this._listeners[event]) {\n this._listeners[event] = [];\n }\n const onceOnlyHandler = (evt: T) => {\n try {\n handler(evt);\n } finally {\n this.off(event, onceOnlyHandler);\n }\n };\n this._listeners[event].push(onceOnlyHandler);\n }\n}","// ─── Path Typing ────────────────────────────────────────────────────────────\nimport {EventEmitter, JsEventEmitter} from './event-emitter';\n\ntype Primitive = string | number | boolean | null | undefined;\n\ntype PathsOf<T, Prefix extends string = \"\"> = T extends Primitive\n ? never\n : T extends Array<infer _>\n ? never\n : {\n [K in keyof T & string]: Prefix extends \"\"\n ? K | (T[K] extends Primitive ? never : PathsOf<T[K], K>)\n : `${Prefix}.${K}` | (T[K] extends Primitive ? never : PathsOf<T[K], `${Prefix}.${K}`>);\n }[keyof T & string];\n\nexport type Path<T> = PathsOf<T>;\n\nexport type PathValue<T, P extends string> = P extends keyof T\n ? T[P]\n : P extends `${infer K}.${infer Rest}`\n ? K extends keyof T\n ? Rest extends Path<T[K]>\n ? PathValue<T[K], Rest>\n : never\n : never\n : never;\n\n// ─── Event Types ─────────────────────────────────────────────────────────────\n\nexport interface ChangePayload<State, V = unknown> {\n path: string;\n previousValue: V;\n value: V;\n previousState: Readonly<State>;\n state: Readonly<State>;\n}\n\ntype ChangeEventMap<State> = {\n change: ChangePayload<State>;\n [key: `change:${string}`]: ChangePayload<State>;\n};\n\n// ─── StateStore Interface ────────────────────────────────────────────────────\n\nexport interface StateStore<State extends object> {\n get(): Readonly<State>;\n get<K extends Path<State>>(path: K): Readonly<PathValue<State, K>>;\n select<T>(selector: (state: Readonly<State>) => T): Readonly<T>;\n\n set<K extends Path<State>>(path: K, value: PathValue<State, K>): void;\n update<K extends Path<State>>(path: K, fn: (current: PathValue<State, K>) => PathValue<State, K>): void;\n patch(partial: DeepPartial<State>): void;\n batch(fn: (store: BatchContext<State>) => void): void;\n\n on<E extends keyof ChangeEventMap<State>>(event: E, handler: (payload: ChangeEventMap<State>[E]) => void): void;\n off<E extends keyof ChangeEventMap<State>>(event: E, handler: (payload: ChangeEventMap<State>[E]) => void): void;\n\n subscribe<K extends Path<State>>(path: K, handler: (payload: ChangePayload<State, PathValue<State, K>>) => void): () => void;\n\n serialize(): string;\n reset(): void;\n}\n\nexport interface BatchContext<State extends object> {\n set<K extends Path<State>>(path: K, value: PathValue<State, K>): void;\n update<K extends Path<State>>(path: K, fn: (current: PathValue<State, K>) => PathValue<State, K>): void;\n patch(partial: DeepPartial<State>): void;\n}\n\ntype DeepPartial<T> = T extends Primitive ? T : { [K in keyof T]?: DeepPartial<T[K]> };\n\n// ─── Utility Helpers ─────────────────────────────────────────────────────────\n\nfunction parsePath(path: string): string[] {\n return path.split(\".\");\n}\n\nfunction deepFreeze<T>(obj: T): Readonly<T> {\n if (obj === null || typeof obj !== \"object\") return obj;\n Object.getOwnPropertyNames(obj).forEach(name => {\n const val = (obj as Record<string, unknown>)[name];\n if (val && typeof val === \"object\") deepFreeze(val);\n });\n return Object.freeze(obj);\n}\n\nfunction deepClone<T>(obj: T): T {\n if (obj === null || typeof obj !== \"object\") return obj;\n if (Array.isArray(obj)) return (obj as unknown[]).map(deepClone) as unknown as T;\n const result = {} as Record<string, unknown>;\n for (const key of Object.keys(obj as object)) {\n result[key] = deepClone((obj as Record<string, unknown>)[key]);\n }\n return result as T;\n}\n\nfunction getAtPath<T>(obj: T, segments: string[]): unknown {\n let current: unknown = obj;\n for (const seg of segments) {\n if (current === null || typeof current !== \"object\") {\n throw new Error(`Invalid path segment \"${seg}\": not an object`);\n }\n current = (current as Record<string, unknown>)[seg];\n }\n return current;\n}\n\nfunction setAtPath<T extends object>(obj: T, segments: string[], value: unknown): T {\n if (segments.length === 0) return value as T;\n const [head, ...tail] = segments;\n if (typeof obj !== \"object\" || obj === null) {\n throw new Error(`Cannot set path on non-object at segment \"${head}\"`);\n }\n const record = obj as Record<string, unknown>;\n const updated = { ...record };\n if (tail.length === 0) {\n updated[head] = value;\n } else {\n const child = record[head];\n if (child === null || typeof child !== \"object\") {\n throw new Error(`Path segment \"${head}\" is not an object`);\n }\n updated[head] = setAtPath(child as object, tail, value);\n }\n return updated as T;\n}\n\nfunction shallowEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null) return false;\n if (typeof a !== \"object\" || typeof b !== \"object\") return false;\n const keysA = Object.keys(a as object);\n const keysB = Object.keys(b as object);\n if (keysA.length !== keysB.length) return false;\n for (const k of keysA) {\n if ((a as Record<string, unknown>)[k] !== (b as Record<string, unknown>)[k]) return false;\n }\n return true;\n}\n\nfunction deepMerge<T extends object>(base: T, partial: DeepPartial<T>): T {\n const result = { ...base } as Record<string, unknown>;\n for (const key of Object.keys(partial as object)) {\n const pVal = (partial as Record<string, unknown>)[key];\n const bVal = (base as Record<string, unknown>)[key];\n if (\n pVal !== undefined &&\n pVal !== null &&\n typeof pVal === \"object\" &&\n !Array.isArray(pVal) &&\n bVal !== null &&\n typeof bVal === \"object\"\n ) {\n result[key] = deepMerge(bVal as object, pVal as DeepPartial<object>);\n } else if (pVal !== undefined) {\n result[key] = pVal;\n }\n }\n return result as T;\n}\n\n// ─── Factory ─────────────────────────────────────────────────────────────────\n\nexport function createStateStore<State extends object>(\n initialState: State, \n eventEmitter: EventEmitter<ChangePayload<State>> = new JsEventEmitter<ChangePayload<State>>()\n): StateStore<State> {\n\n const _initial: Readonly<State> = deepFreeze(deepClone(initialState));\n let _state: Readonly<State> = _initial;\n\n const _emitter = eventEmitter;\n\n function _emit(path: string, previousValue: unknown, value: unknown, previousState: Readonly<State>): void {\n const payload: ChangePayload<State> = {\n path,\n previousValue,\n value,\n previousState,\n state: _state,\n };\n _emitter.emit(\"change\", payload);\n _emitter.emit(`change:${path}` as `change:${string}`, payload);\n }\n\n function _applySet(currentState: Readonly<State>, segments: string[], value: unknown): Readonly<State> {\n return deepFreeze(setAtPath(deepClone(currentState), segments, value));\n }\n\n // Overloaded get\n function get(): Readonly<State>;\n function get<K extends Path<State>>(path: K): Readonly<PathValue<State, K>>;\n function get<K extends Path<State>>(path?: K): Readonly<State> | Readonly<PathValue<State, K>> {\n if (path === undefined) return _state;\n const segments = parsePath(path);\n const val = getAtPath(_state, segments);\n return deepFreeze(deepClone(val)) as Readonly<PathValue<State, K>>;\n }\n\n function select<T>(selector: (state: Readonly<State>) => T): Readonly<T> {\n const result = selector(_state);\n if (result !== null && typeof result === \"object\") {\n return deepFreeze(deepClone(result)) as Readonly<T>;\n }\n return result as Readonly<T>;\n }\n\n function set<K extends Path<State>>(path: K, value: PathValue<State, K>): void {\n const segments = parsePath(path);\n const previousValue = getAtPath(_state, segments);\n if (shallowEqual(previousValue, value)) return;\n const previousState = _state;\n _state = _applySet(_state, segments, value);\n _emit(path, previousValue, value, previousState);\n }\n\n function update<K extends Path<State>>(path: K, fn: (current: PathValue<State, K>) => PathValue<State, K>): void {\n const segments = parsePath(path);\n const current = getAtPath(_state, segments) as PathValue<State, K>;\n const next = fn(deepClone(current));\n set(path, next);\n }\n\n function patch(partial: DeepPartial<State>): void {\n const merged = deepMerge(_state as State, partial);\n const nextState = deepFreeze(merged);\n const changedPaths: Array<{ path: string; prev: unknown; next: unknown }> = [];\n collectChangedPaths(\"\", _state, nextState, changedPaths);\n if (changedPaths.length === 0) return;\n const previousState = _state;\n _state = nextState;\n for (const entry of changedPaths) {\n _emit(entry.path, entry.prev, entry.next, previousState);\n }\n }\n\n function collectChangedPaths(\n prefix: string,\n prev: unknown,\n next: unknown,\n out: Array<{ path: string; prev: unknown; next: unknown }>,\n ): void {\n if (prev === next) return;\n if (\n prev !== null &&\n next !== null &&\n typeof prev === \"object\" &&\n typeof next === \"object\" &&\n !Array.isArray(prev) &&\n !Array.isArray(next)\n ) {\n const allKeys = new Set([...Object.keys(prev as object), ...Object.keys(next as object)]);\n for (const k of allKeys) {\n const pv = (prev as Record<string, unknown>)[k];\n const nv = (next as Record<string, unknown>)[k];\n const childPath = prefix ? `${prefix}.${k}` : k;\n collectChangedPaths(childPath, pv, nv, out);\n }\n } else if (!shallowEqual(prev, next)) {\n out.push({ path: prefix, prev, next });\n }\n }\n\n function batch(fn: (ctx: BatchContext<State>) => void): void {\n let pendingState = _state;\n const changes: Array<{ path: string; prev: unknown; next: unknown; prevState: Readonly<State> }> = [];\n\n const ctx: BatchContext<State> = {\n set<K extends Path<State>>(path: K, value: PathValue<State, K>): void {\n const segments = parsePath(path);\n const previousValue = getAtPath(pendingState, segments);\n if (shallowEqual(previousValue, value)) return;\n const prevState = pendingState;\n pendingState = deepFreeze(setAtPath(deepClone(pendingState), segments, value));\n changes.push({ path, prev: previousValue, next: value, prevState });\n },\n update<K extends Path<State>>(path: K, fn2: (current: PathValue<State, K>) => PathValue<State, K>): void {\n const segments = parsePath(path);\n const current = getAtPath(pendingState, segments) as PathValue<State, K>;\n const next = fn2(deepClone(current));\n ctx.set(path, next);\n },\n patch(partial: DeepPartial<State>): void {\n const merged = deepFreeze(deepMerge(pendingState as State, partial));\n const changedPaths: Array<{ path: string; prev: unknown; next: unknown }> = [];\n collectChangedPaths(\"\", pendingState, merged, changedPaths);\n if (changedPaths.length === 0) return;\n const prevState = pendingState;\n pendingState = merged;\n for (const entry of changedPaths) {\n changes.push({ path: entry.path, prev: entry.prev, next: entry.next, prevState });\n }\n },\n };\n\n fn(ctx);\n\n if (pendingState === _state) return;\n _state = pendingState;\n\n for (const change of changes) {\n _emit(change.path, change.prev, change.next, change.prevState);\n }\n }\n\n function on<E extends keyof ChangeEventMap<State>>(event: E, handler: (payload: ChangeEventMap<State>[E]) => void): void {\n _emitter.on(event, handler as (payload: ChangeEventMap<State>[E]) => void);\n }\n\n function off<E extends keyof ChangeEventMap<State>>(event: E, handler: (payload: ChangeEventMap<State>[E]) => void): void {\n _emitter.off(event, handler as (payload: ChangeEventMap<State>[E]) => void);\n }\n\n function subscribe<K extends Path<State>>(\n path: K,\n handler: (payload: ChangePayload<State, PathValue<State, K>>) => void,\n ): () => void {\n const eventName = `change:${path}` as `change:${string}`;\n const wrapped = (payload: ChangePayload<State>) => handler(payload as unknown as ChangePayload<State, PathValue<State, K>>);\n _emitter.on(eventName, wrapped);\n return () => _emitter.off(eventName, wrapped);\n }\n\n function serialize(): string {\n return JSON.stringify(_state);\n }\n\n function reset(): void {\n const previousState = _state;\n _state = _initial;\n const changes: Array<{ path: string; prev: unknown; next: unknown }> = [];\n collectChangedPaths(\"\", previousState, _state, changes);\n for (const change of changes) {\n _emit(change.path, change.prev, change.next, previousState);\n }\n }\n\n return { get, select, set, update, patch, batch, on, off, subscribe, serialize, reset };\n}","export type FsmResult<T = void> = {\n success: boolean;\n message?: string;\n value?: T;\n};\n\nexport class Fsm {\n public states = new Map<string, FsmState>();\n public current: FsmState | null = null;\n public currentParams: any[] = [];\n public currentTime: number = 0;\n\n constructor() {}\n\n /** Register new states, returns array of registered ExState */\n register(...types: (string | FsmState)[]): FsmResult<FsmState[]> {\n const generatedStates: FsmState[] = [];\n\n for (const state of types) {\n const newState = typeof state === \"string\" ? new FsmState(state) : state;\n\n if (this.states.has(newState.name)) {\n console.warn(`State \"${newState.name}\" is already registered and will be overwritten.`);\n }\n\n this.states.set(newState.name, newState);\n generatedStates.push(newState);\n }\n\n return { success: true, value: generatedStates };\n }\n\n /** Change current state */\n set(state: string | FsmState, ...params: any[]): FsmResult<FsmState> | Promise<FsmResult<FsmState>> {\n const name = typeof state === \"string\" ? state : state.name;\n const next = this.states.get(name);\n\n if (!next) return { success: false, message: `State \"${name}\" not found` };\n\n const finalize = () => {\n this.current = next;\n this.currentParams = params;\n this.currentTime = Date.now();\n return { success: true, value: next };\n };\n\n // No current state, only enter next\n if (!this.current) {\n const entering = next.enter(this.current, ...params);\n return entering instanceof Promise ? entering.then(finalize) : finalize();\n }\n\n // Handle current exit\n const leaving = this.current.exit(next, ...params);\n if (leaving instanceof Promise) {\n return leaving.then(() => {\n const entering = next.enter(this.current, ...params);\n return entering instanceof Promise ? entering.then(finalize) : finalize();\n });\n }\n\n const entering = next.enter(this.current, ...params);\n return entering instanceof Promise ? entering.then(finalize) : finalize();\n }\n\n /** Check if a state is registered */\n has(state: string | FsmState): boolean {\n const name = typeof state === \"string\" ? state : state.name;\n return this.states.has(name);\n }\n\n /** Get current state */\n get(): FsmResult<FsmState> {\n if (!this.current) return { success: false, message: \"No state set\" };\n return { success: true, value: this.current };\n }\n\n /** Reset FSM */\n reset(): FsmResult<void> {\n this.current = null;\n this.currentParams = [];\n this.currentTime = 0;\n return { success: true };\n }\n\n /** Update current state */\n update(): FsmResult<void> | Promise<FsmResult<void>> {\n if (!this.current) return { success: false, message: \"No current state\" };\n\n const result = this.current.update(...this.currentParams);\n return result instanceof Promise ? result.then(() => ({ success: true })) : { success: true };\n }\n}\n\nexport class FsmState {\n constructor(public name: string) {}\n\n enter(_previous: FsmState | null, ..._params: any): void | Promise<void> {}\n exit(_next: FsmState | null, ..._params: any): void | Promise<void> {}\n update(..._params: any): void | Promise<void> {}\n}","import { Directive, TemplateRef, input, inject } from '@angular/core';\n\n/**\n * A directive that marks a template (or element) with a name so it can be\n * queried and rendered manually.\n *\n * This is useful in two main scenarios:\n * - Defining multiple named templates inside a component and choosing which one to render at runtime.\n * - Accepting named templates from a parent component via content projection.\n *\n * ## Supported Syntaxes\n *\n * ```html\n * <!-- On ng-template (recommended for pure templates) -->\n * <ng-template templateSelection=\"header\">...</ng-template>\n * <ng-template templateSelection=\"footer\">...</ng-template>\n *\n * <!-- Using structural directive syntax (also supported) -->\n * <div *templateSelection=\"'sidebar'\">Sidebar content</div>\n * ```\n *\n * ## Consumption Example\n *\n * You can query the templates using either `viewChildren()` or `contentChildren()`,\n * depending on your use case:\n *\n * ```ts\n * @Component({...})\n * export class MyComponent {\n * // Use viewChildren() if the templates are defined inside this component's own template\n * internalTemplates = viewChildren(TemplateSelection);\n *\n * // Use contentChildren() if you want to accept named templates from the parent via content projection\n * projectedTemplates = contentChildren(TemplateSelection);\n *\n * getTemplate(name: string): TemplateRef<any> | undefined {\n * return this.internalTemplates()\n * .find(t => t.name() === name)\n * ?.templateRef;\n * }\n * }\n * ```\n *\n * Then in the template:\n * ```html\n * <ng-container *ngTemplateOutlet=\"getTemplate('header')\"></ng-container>\n * ```\n */\n@Directive({\n selector: '[templateSelection]',\n standalone: true,\n})\nexport class TemplateSelection {\n /**\n * The name used to identify this template.\n * Can be bound as `templateSelection=\"myName\"` or `[templateSelection]=\"someVariable\"`.\n */\n name = input.required<string>({ alias: 'templateSelection' });\n\n /**\n * The underlying TemplateRef that can be used with *ngTemplateOutlet or ViewContainerRef.\n */\n readonly templateRef = inject(TemplateRef);\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tmjeee/w-lib",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "A minimal, modern TypeScript library skeleton ready for publishing to npmjs.com. Includes helloWorld sample.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -52,8 +52,21 @@
|
|
|
52
52
|
"publish:npm": "npm run build && npm run test && npm publish --access public",
|
|
53
53
|
"publish:npm:dry": "npm run build && npm run test && npm publish --access public --dry-run"
|
|
54
54
|
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@angular/animations": "^21.2.13",
|
|
57
|
+
"@angular/cdk": "^21.2.11",
|
|
58
|
+
"@angular/common": "^21.2.0",
|
|
59
|
+
"@angular/compiler": "^21.2.0",
|
|
60
|
+
"@angular/core": "^21.2.0",
|
|
61
|
+
"@angular/forms": "^21.2.0",
|
|
62
|
+
"@angular/material": "^21.2.11",
|
|
63
|
+
"@angular/platform-browser": "^21.2.0",
|
|
64
|
+
"@angular/router": "^21.2.0",
|
|
65
|
+
"rxjs": "^7.8.2"
|
|
66
|
+
},
|
|
55
67
|
"devDependencies": {
|
|
56
68
|
"@types/node": "^22.15.0",
|
|
69
|
+
"jsdom": "^29.1.1",
|
|
57
70
|
"tsdown": "^0.20.3",
|
|
58
71
|
"typescript": "^5.9.3",
|
|
59
72
|
"vitest": "^4.0.8"
|