@primer-io/primer-js 0.3.2 → 0.3.3
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 +111 -0
- package/dist/custom-elements.json +14 -5
- package/dist/primer-loader.d.ts +5 -4
- package/dist/primer-loader.js +2 -2
- package/dist/primer-react-wrappers.js +2 -2
- package/dist/web-types.json +1 -1
- package/package.json +2 -2
- package/src/controllers/reactive-state/README.md +210 -0
package/dist/web-types.json
CHANGED
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@primer-io/primer-js",
|
|
3
3
|
"description": "Primer Composable Checkout is a web component-based SDK for building secure, customizable, and PCI-compliant checkout experiences. Designed with a modular architecture, it integrates seamlessly with any JavaScript framework and supports multiple payment methods.",
|
|
4
4
|
"license": "BSD-3-Clause",
|
|
5
|
-
"version": "0.3.
|
|
5
|
+
"version": "0.3.3",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/primer-loader.js",
|
|
8
8
|
"types": "./dist/primer-loader.d.ts",
|
|
@@ -45,4 +45,4 @@
|
|
|
45
45
|
},
|
|
46
46
|
"customElements": "dist/custom-elements.json",
|
|
47
47
|
"web-types": "./dist/web-types.json"
|
|
48
|
-
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Reactive State Management Library
|
|
2
|
+
|
|
3
|
+
A TypeScript library for state management in reactive controllers that solves common issues:
|
|
4
|
+
|
|
5
|
+
- Enforces type safety for state, actions, and reducers
|
|
6
|
+
- Catches missing action handlers at compile time
|
|
7
|
+
- Enables splitting state into smaller contexts for optimal performance
|
|
8
|
+
- Provides a consistent pattern for state management across controllers
|
|
9
|
+
|
|
10
|
+
## Key Concepts
|
|
11
|
+
|
|
12
|
+
### Action Types
|
|
13
|
+
|
|
14
|
+
Actions are defined as discriminated unions with a `type` property:
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
type UserAction =
|
|
18
|
+
| { type: 'SET_NAME'; payload: string }
|
|
19
|
+
| { type: 'SET_EMAIL'; payload: string }
|
|
20
|
+
| { type: 'RESET' };
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Handlers Map
|
|
24
|
+
|
|
25
|
+
Action handlers are defined in a map where every action type must have a corresponding handler:
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
const handlers: ActionHandlerMap<UserState, UserAction, null> = {
|
|
29
|
+
SET_NAME: (state, action) => ({ ...state, name: action.payload }),
|
|
30
|
+
SET_EMAIL: (state, action) => ({ ...state, email: action.payload }),
|
|
31
|
+
RESET: () => initialUserState,
|
|
32
|
+
};
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
If you forget to handle an action, TypeScript will generate a compile-time error.
|
|
36
|
+
|
|
37
|
+
## Core Components
|
|
38
|
+
|
|
39
|
+
### `ReactiveStateController`
|
|
40
|
+
|
|
41
|
+
Base class for creating state controllers:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
class ReactiveStateController<
|
|
45
|
+
Host extends ReactiveControllerHost,
|
|
46
|
+
State,
|
|
47
|
+
Action extends { type: string },
|
|
48
|
+
Callbacks = ReducerCallbacks<State, Action> | null,
|
|
49
|
+
> {
|
|
50
|
+
constructor(
|
|
51
|
+
host: Host,
|
|
52
|
+
initialState: State,
|
|
53
|
+
reducer: TypedReducer<State, Action, Callbacks>,
|
|
54
|
+
initialCallbacks: Callbacks,
|
|
55
|
+
stateHandler?: (state: State) => void,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
// Public API for accessing state
|
|
59
|
+
public get currentState(): Readonly<State>;
|
|
60
|
+
|
|
61
|
+
// Dispatch actions to update state
|
|
62
|
+
protected dispatch(action: Action): void;
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### `CompositeStateController`
|
|
67
|
+
|
|
68
|
+
For managing multiple state slices to avoid excessive re-renders:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
class CompositeStateController<Host extends ReactiveControllerHost> {
|
|
72
|
+
// Add sub-controllers for different state slices
|
|
73
|
+
addController<State, Action, Callbacks>(
|
|
74
|
+
controller: ReactiveStateController<Host, State, Action, Callbacks>,
|
|
75
|
+
): void;
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### `createReducer`
|
|
80
|
+
|
|
81
|
+
Creates a type-safe reducer from handlers:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
function createReducer<State, Action extends { type: string }, Callbacks>(
|
|
85
|
+
handlers: ActionHandlerMap<State, Action, Callbacks>,
|
|
86
|
+
): TypedReducer<State, Action, Callbacks>;
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Simple Example
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
// 1. Define your state interface
|
|
93
|
+
interface CounterState {
|
|
94
|
+
count: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 2. Define possible actions
|
|
98
|
+
type CounterAction =
|
|
99
|
+
| { type: 'INCREMENT' }
|
|
100
|
+
| { type: 'DECREMENT' }
|
|
101
|
+
| { type: 'SET_COUNT'; payload: number };
|
|
102
|
+
|
|
103
|
+
// 3. Create initial state
|
|
104
|
+
const initialState: CounterState = { count: 0 };
|
|
105
|
+
|
|
106
|
+
// 4. Define action handlers
|
|
107
|
+
const handlers: ActionHandlerMap<CounterState, CounterAction, null> = {
|
|
108
|
+
INCREMENT: (state) => ({ ...state, count: state.count + 1 }),
|
|
109
|
+
DECREMENT: (state) => ({ ...state, count: state.count - 1 }),
|
|
110
|
+
SET_COUNT: (state, action) => ({ ...state, count: action.payload }),
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// 5. Create a reducer
|
|
114
|
+
const counterReducer = createReducer(handlers);
|
|
115
|
+
|
|
116
|
+
// 6. Create your controller
|
|
117
|
+
class CounterController extends ReactiveStateController<
|
|
118
|
+
ReactiveControllerHost,
|
|
119
|
+
CounterState,
|
|
120
|
+
CounterAction,
|
|
121
|
+
null
|
|
122
|
+
> {
|
|
123
|
+
constructor(host: ReactiveControllerHost) {
|
|
124
|
+
super(host, initialState, counterReducer, null);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Public API methods
|
|
128
|
+
increment() {
|
|
129
|
+
this.dispatch({ type: 'INCREMENT' });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
decrement() {
|
|
133
|
+
this.dispatch({ type: 'DECREMENT' });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
setCount(count: number) {
|
|
137
|
+
this.dispatch({ type: 'SET_COUNT', payload: count });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Public getter for the count
|
|
141
|
+
get count(): number {
|
|
142
|
+
return this.currentState.count;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Advanced Usage: State Splitting
|
|
148
|
+
|
|
149
|
+
For complex components, splitting state improves performance by preventing unnecessary re-renders:
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
// 1. Define separate state slices
|
|
153
|
+
interface CoreState {
|
|
154
|
+
/* core properties */
|
|
155
|
+
}
|
|
156
|
+
interface FormState {
|
|
157
|
+
/* form properties that change frequently */
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 2. Create separate controllers for each slice
|
|
161
|
+
class CoreController extends ReactiveStateController<
|
|
162
|
+
Host,
|
|
163
|
+
CoreState,
|
|
164
|
+
CoreAction,
|
|
165
|
+
null
|
|
166
|
+
> {
|
|
167
|
+
/* ... */
|
|
168
|
+
}
|
|
169
|
+
class FormController extends ReactiveStateController<
|
|
170
|
+
Host,
|
|
171
|
+
FormState,
|
|
172
|
+
FormAction,
|
|
173
|
+
null
|
|
174
|
+
> {
|
|
175
|
+
/* ... */
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// 3. Combine with CompositeStateController
|
|
179
|
+
class ComplexController extends CompositeStateController<Host> {
|
|
180
|
+
private coreController: CoreController;
|
|
181
|
+
private formController: FormController;
|
|
182
|
+
|
|
183
|
+
constructor(host: Host) {
|
|
184
|
+
super(host);
|
|
185
|
+
|
|
186
|
+
this.coreController = new CoreController(host /* ... */);
|
|
187
|
+
this.formController = new FormController(host /* ... */);
|
|
188
|
+
|
|
189
|
+
this.addController(this.coreController);
|
|
190
|
+
this.addController(this.formController);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Expose state slices through getters
|
|
194
|
+
get coreState(): Readonly<CoreState> {
|
|
195
|
+
return this.coreController.currentState;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
get formState(): Readonly<FormState> {
|
|
199
|
+
return this.formController.currentState;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Benefits
|
|
205
|
+
|
|
206
|
+
- **Type Safety**: Ensures all action types are handled correctly
|
|
207
|
+
- **Performance**: Granular state updates through context splitting
|
|
208
|
+
- **Maintainability**: Consistent patterns across all controllers
|
|
209
|
+
- **Debugging**: Predictable state updates through pure reducer functions
|
|
210
|
+
- **Testability**: Easy to test state transitions in isolation
|