@miurajs/miura-data-flow 0.0.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/README.md +565 -0
- package/index.ts +1 -0
- package/package.json +30 -0
- package/src/global-state.ts +158 -0
- package/src/middleware.ts +162 -0
- package/src/miura-data-flow.ts +114 -0
- package/src/providers/firebase-provider.ts +39 -0
- package/src/providers/graphql-provider.ts +39 -0
- package/src/providers/grpc-web-provider.ts +35 -0
- package/src/providers/index.ts +11 -0
- package/src/providers/indexed-db-provider.ts +48 -0
- package/src/providers/local-storage-provider.ts +36 -0
- package/src/providers/provider-manager.ts +21 -0
- package/src/providers/provider.ts +23 -0
- package/src/providers/rest-provider.ts +351 -0
- package/src/providers/s3-provider.ts +41 -0
- package/src/providers/supabase-provider.ts +45 -0
- package/src/providers/websockets-provider.ts +54 -0
- package/src/store.ts +237 -0
- package/stories/data-flow-demo.stories.ts +640 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/web-components';
|
|
2
|
+
import {
|
|
3
|
+
Store,
|
|
4
|
+
globalState,
|
|
5
|
+
createLoggerMiddleware,
|
|
6
|
+
createPersistenceMiddleware,
|
|
7
|
+
createCacheMiddleware,
|
|
8
|
+
createDevToolsMiddleware,
|
|
9
|
+
StoreState
|
|
10
|
+
} from '@miura/miura-data-flow';
|
|
11
|
+
import { html } from '@miura/miura-element';
|
|
12
|
+
import { MiuraElement } from '@miura/miura-element';
|
|
13
|
+
|
|
14
|
+
// Define the store state interface
|
|
15
|
+
interface DemoStoreState extends StoreState {
|
|
16
|
+
count: number;
|
|
17
|
+
user: any;
|
|
18
|
+
todos: Array<{ id: number; text: string; completed: boolean }>;
|
|
19
|
+
loading: boolean;
|
|
20
|
+
error: any;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const meta: Meta = {
|
|
24
|
+
title: 'miura-data-flow/Data Flow Demo',
|
|
25
|
+
parameters: {
|
|
26
|
+
docs: {
|
|
27
|
+
description: {
|
|
28
|
+
component: `
|
|
29
|
+
# miura Data Flow Demo
|
|
30
|
+
|
|
31
|
+
This story demonstrates the comprehensive state management features of miura Data Flow:
|
|
32
|
+
|
|
33
|
+
## 🏪 Store Management
|
|
34
|
+
- Reactive state management with subscriptions
|
|
35
|
+
- Action-based state updates
|
|
36
|
+
- Immutable state updates
|
|
37
|
+
- Type-safe state access
|
|
38
|
+
|
|
39
|
+
## 🌍 Global State
|
|
40
|
+
- Application-wide state management
|
|
41
|
+
- Component-specific subscriptions
|
|
42
|
+
- Cross-component communication
|
|
43
|
+
|
|
44
|
+
## 🔌 Middleware System
|
|
45
|
+
- Logger middleware for debugging
|
|
46
|
+
- Persistence middleware for localStorage
|
|
47
|
+
- Cache middleware for API responses
|
|
48
|
+
- DevTools middleware for Redux DevTools
|
|
49
|
+
|
|
50
|
+
## 📡 Reactive Updates
|
|
51
|
+
- Automatic UI updates on state changes
|
|
52
|
+
- Selective subscriptions to specific properties
|
|
53
|
+
- Performance-optimized change detection
|
|
54
|
+
|
|
55
|
+
## Usage Examples
|
|
56
|
+
|
|
57
|
+
\`\`\`typescript
|
|
58
|
+
import { Store, globalState, createLoggerMiddleware } from '@miura/miura-data-flow';
|
|
59
|
+
|
|
60
|
+
// Create a store
|
|
61
|
+
const store = new Store({ count: 0, user: null });
|
|
62
|
+
|
|
63
|
+
// Define actions
|
|
64
|
+
store.defineActions({
|
|
65
|
+
increment: (state) => ({ count: state.count + 1 }),
|
|
66
|
+
setUser: (state, user) => ({ user })
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Add middleware
|
|
70
|
+
store.use(createLoggerMiddleware());
|
|
71
|
+
|
|
72
|
+
// Subscribe to changes
|
|
73
|
+
const unsubscribe = store.subscribe((state, prevState) => {
|
|
74
|
+
console.log('State changed:', state);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Global state
|
|
78
|
+
globalState.set('theme', 'dark');
|
|
79
|
+
globalState.subscribeTo('my-component', 'theme', (theme) => {
|
|
80
|
+
console.log('Theme changed:', theme);
|
|
81
|
+
});
|
|
82
|
+
\`\`\`
|
|
83
|
+
`
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export default meta;
|
|
90
|
+
type Story = StoryObj;
|
|
91
|
+
|
|
92
|
+
// Data Flow demo component
|
|
93
|
+
class DataFlowDemo extends MiuraElement {
|
|
94
|
+
private store: Store<DemoStoreState>;
|
|
95
|
+
private globalState = globalState;
|
|
96
|
+
private storeEvents: any[] = [];
|
|
97
|
+
private globalEvents: any[] = [];
|
|
98
|
+
private unsubscribeStore: (() => void) | null = null;
|
|
99
|
+
private unsubscribeGlobal: (() => void) | null = null;
|
|
100
|
+
|
|
101
|
+
constructor() {
|
|
102
|
+
super();
|
|
103
|
+
this.initializeStore();
|
|
104
|
+
this.setupGlobalState();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
connectedCallback() {
|
|
108
|
+
super.connectedCallback();
|
|
109
|
+
this.setupEventListeners();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
disconnectedCallback() {
|
|
113
|
+
if (this.unsubscribeStore) {
|
|
114
|
+
this.unsubscribeStore();
|
|
115
|
+
}
|
|
116
|
+
if (this.unsubscribeGlobal) {
|
|
117
|
+
this.unsubscribeGlobal();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
private initializeStore() {
|
|
122
|
+
// Create store with initial state
|
|
123
|
+
this.store = new Store<DemoStoreState>({
|
|
124
|
+
count: 0,
|
|
125
|
+
user: null,
|
|
126
|
+
todos: [],
|
|
127
|
+
loading: false,
|
|
128
|
+
error: null
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Define actions — each receives (state, ...args) and returns Partial<T>
|
|
132
|
+
this.store.defineActions({
|
|
133
|
+
increment: (state) => ({ count: state.count + 1 }),
|
|
134
|
+
decrement: (state) => ({ count: state.count - 1 }),
|
|
135
|
+
setUser: (_state, ...args: unknown[]) => ({ user: args[0] }),
|
|
136
|
+
addTodo: (state, ...args: unknown[]) => ({
|
|
137
|
+
todos: [...state.todos, { id: Date.now(), text: args[0] as string, completed: false }]
|
|
138
|
+
}),
|
|
139
|
+
toggleTodo: (state, ...args: unknown[]) => ({
|
|
140
|
+
todos: state.todos.map(todo =>
|
|
141
|
+
todo.id === (args[0] as number) ? { ...todo, completed: !todo.completed } : todo
|
|
142
|
+
)
|
|
143
|
+
}),
|
|
144
|
+
removeTodo: (state, ...args: unknown[]) => ({
|
|
145
|
+
todos: state.todos.filter(todo => todo.id !== (args[0] as number))
|
|
146
|
+
}),
|
|
147
|
+
setLoading: (_state, ...args: unknown[]) => ({ loading: args[0] as boolean }),
|
|
148
|
+
setError: (_state, ...args: unknown[]) => ({ error: args[0] })
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Add middleware
|
|
152
|
+
this.store.use(createLoggerMiddleware());
|
|
153
|
+
this.store.use(createPersistenceMiddleware(['user', 'todos']));
|
|
154
|
+
this.store.use(createCacheMiddleware(30000)); // 30 seconds
|
|
155
|
+
this.store.use(createDevToolsMiddleware('DataFlowDemo'));
|
|
156
|
+
|
|
157
|
+
// Subscribe to store changes
|
|
158
|
+
this.unsubscribeStore = this.store.subscribe((state, prevState) => {
|
|
159
|
+
this.addStoreEvent('State Updated', { state, prevState });
|
|
160
|
+
this.requestUpdate();
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private setupGlobalState() {
|
|
165
|
+
// Subscribe to global state changes
|
|
166
|
+
this.unsubscribeGlobal = this.globalState.subscribe(
|
|
167
|
+
'data-flow-demo',
|
|
168
|
+
['theme', 'language', 'notifications'],
|
|
169
|
+
(state, prevState) => {
|
|
170
|
+
this.addGlobalEvent('Global State Updated', { state, prevState });
|
|
171
|
+
this.requestUpdate();
|
|
172
|
+
}
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
private setupEventListeners() {
|
|
177
|
+
this.shadowRoot!.addEventListener('click', (e) => {
|
|
178
|
+
const target = (e.composedPath()[0] || e.target) as HTMLElement;
|
|
179
|
+
|
|
180
|
+
if (target.matches('[data-action="increment"]')) {
|
|
181
|
+
this.store.dispatch('increment');
|
|
182
|
+
} else if (target.matches('[data-action="decrement"]')) {
|
|
183
|
+
this.store.dispatch('decrement');
|
|
184
|
+
} else if (target.matches('[data-action="set-user"]')) {
|
|
185
|
+
this.store.dispatch('setUser', { id: '1', name: 'John Doe', email: 'john@example.com' });
|
|
186
|
+
} else if (target.matches('[data-action="clear-user"]')) {
|
|
187
|
+
this.store.dispatch('setUser', null);
|
|
188
|
+
} else if (target.matches('[data-action="add-todo"]')) {
|
|
189
|
+
const input = this.shadowRoot!.querySelector('[name="todo-input"]') as HTMLInputElement;
|
|
190
|
+
if (input.value.trim()) {
|
|
191
|
+
this.store.dispatch('addTodo', input.value.trim());
|
|
192
|
+
input.value = '';
|
|
193
|
+
}
|
|
194
|
+
} else if (target.matches('[data-action="toggle-theme"]')) {
|
|
195
|
+
const currentTheme = this.globalState.get('theme');
|
|
196
|
+
this.globalState.set('theme', currentTheme === 'light' ? 'dark' : 'light');
|
|
197
|
+
} else if (target.matches('[data-action="set-language"]')) {
|
|
198
|
+
const select = this.shadowRoot!.querySelector('[name="language"]') as HTMLSelectElement;
|
|
199
|
+
this.globalState.set('language', select.value);
|
|
200
|
+
} else if (target.matches('[data-action="add-notification"]')) {
|
|
201
|
+
const notifications = this.globalState.get('notifications') || [];
|
|
202
|
+
const newNotification = {
|
|
203
|
+
id: Date.now().toString(),
|
|
204
|
+
message: `Notification ${notifications.length + 1}`,
|
|
205
|
+
type: ['info', 'success', 'warning', 'error'][Math.floor(Math.random() * 4)] as any
|
|
206
|
+
};
|
|
207
|
+
this.globalState.set('notifications', [...notifications, newNotification]);
|
|
208
|
+
} else if (target.matches('[data-action="clear-notifications"]')) {
|
|
209
|
+
this.globalState.set('notifications', []);
|
|
210
|
+
} else if (target.matches('[data-action="clear-store-events"]')) {
|
|
211
|
+
this.storeEvents = [];
|
|
212
|
+
this.requestUpdate();
|
|
213
|
+
} else if (target.matches('[data-action="clear-global-events"]')) {
|
|
214
|
+
this.globalEvents = [];
|
|
215
|
+
this.requestUpdate();
|
|
216
|
+
} else if (target.matches('[data-action="toggle-todo"]')) {
|
|
217
|
+
const id = parseInt(target.getAttribute('data-id') || '0');
|
|
218
|
+
this.store.dispatch('toggleTodo', id);
|
|
219
|
+
} else if (target.matches('[data-action="remove-todo"]')) {
|
|
220
|
+
const id = parseInt(target.getAttribute('data-id') || '0');
|
|
221
|
+
this.store.dispatch('removeTodo', id);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private addStoreEvent(type: string, details: any) {
|
|
227
|
+
this.storeEvents.unshift({
|
|
228
|
+
type,
|
|
229
|
+
details,
|
|
230
|
+
timestamp: new Date().toLocaleTimeString()
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Keep only last 10 events
|
|
234
|
+
if (this.storeEvents.length > 10) {
|
|
235
|
+
this.storeEvents = this.storeEvents.slice(0, 10);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private addGlobalEvent(type: string, details: any) {
|
|
240
|
+
this.globalEvents.unshift({
|
|
241
|
+
type,
|
|
242
|
+
details,
|
|
243
|
+
timestamp: new Date().toLocaleTimeString()
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Keep only last 10 events
|
|
247
|
+
if (this.globalEvents.length > 10) {
|
|
248
|
+
this.globalEvents = this.globalEvents.slice(0, 10);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
template() {
|
|
253
|
+
const storeState = this.store.getState();
|
|
254
|
+
const globalStateData = {
|
|
255
|
+
theme: this.globalState.get('theme'),
|
|
256
|
+
language: this.globalState.get('language'),
|
|
257
|
+
notifications: this.globalState.get('notifications') || []
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
return html`
|
|
261
|
+
<style>
|
|
262
|
+
:host {
|
|
263
|
+
display: block;
|
|
264
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
265
|
+
max-width: 1400px;
|
|
266
|
+
margin: 0 auto;
|
|
267
|
+
padding: 20px;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.container {
|
|
271
|
+
display: grid;
|
|
272
|
+
grid-template-columns: 1fr 1fr;
|
|
273
|
+
gap: 20px;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.section {
|
|
277
|
+
background: #f8f9fa;
|
|
278
|
+
border-radius: 8px;
|
|
279
|
+
padding: 20px;
|
|
280
|
+
border: 1px solid #e9ecef;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.section h2 {
|
|
284
|
+
margin-top: 0;
|
|
285
|
+
color: #495057;
|
|
286
|
+
border-bottom: 2px solid #007bff;
|
|
287
|
+
padding-bottom: 10px;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.form-group {
|
|
291
|
+
margin-bottom: 15px;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
label {
|
|
295
|
+
display: block;
|
|
296
|
+
margin-bottom: 5px;
|
|
297
|
+
font-weight: 500;
|
|
298
|
+
color: #495057;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
input, select, textarea {
|
|
302
|
+
width: 100%;
|
|
303
|
+
padding: 8px 12px;
|
|
304
|
+
border: 1px solid #ced4da;
|
|
305
|
+
border-radius: 4px;
|
|
306
|
+
font-size: 14px;
|
|
307
|
+
box-sizing: border-box;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
button {
|
|
311
|
+
background: #007bff;
|
|
312
|
+
color: white;
|
|
313
|
+
border: none;
|
|
314
|
+
padding: 8px 16px;
|
|
315
|
+
border-radius: 4px;
|
|
316
|
+
cursor: pointer;
|
|
317
|
+
font-size: 14px;
|
|
318
|
+
margin-right: 8px;
|
|
319
|
+
margin-bottom: 8px;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
button:hover {
|
|
323
|
+
background: #0056b3;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
button.secondary {
|
|
327
|
+
background: #6c757d;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
button.secondary:hover {
|
|
331
|
+
background: #545b62;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
button.success {
|
|
335
|
+
background: #28a745;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
button.success:hover {
|
|
339
|
+
background: #1e7e34;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
button.danger {
|
|
343
|
+
background: #dc3545;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
button.danger:hover {
|
|
347
|
+
background: #c82333;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.state-display {
|
|
351
|
+
background: #fff;
|
|
352
|
+
border: 1px solid #dee2e6;
|
|
353
|
+
border-radius: 4px;
|
|
354
|
+
padding: 15px;
|
|
355
|
+
margin-bottom: 15px;
|
|
356
|
+
font-family: 'Courier New', monospace;
|
|
357
|
+
font-size: 12px;
|
|
358
|
+
max-height: 200px;
|
|
359
|
+
overflow-y: auto;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.events {
|
|
363
|
+
max-height: 300px;
|
|
364
|
+
overflow-y: auto;
|
|
365
|
+
background: #fff;
|
|
366
|
+
border: 1px solid #dee2e6;
|
|
367
|
+
border-radius: 4px;
|
|
368
|
+
padding: 10px;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.event {
|
|
372
|
+
background: #f8f9fa;
|
|
373
|
+
border-left: 4px solid #007bff;
|
|
374
|
+
padding: 8px;
|
|
375
|
+
margin-bottom: 8px;
|
|
376
|
+
font-size: 12px;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.event.store {
|
|
380
|
+
border-left-color: #28a745;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.event.global {
|
|
384
|
+
border-left-color: #ffc107;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.event-time {
|
|
388
|
+
color: #6c757d;
|
|
389
|
+
font-size: 11px;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.todo-item {
|
|
393
|
+
display: flex;
|
|
394
|
+
align-items: center;
|
|
395
|
+
padding: 8px;
|
|
396
|
+
border: 1px solid #dee2e6;
|
|
397
|
+
border-radius: 4px;
|
|
398
|
+
margin-bottom: 8px;
|
|
399
|
+
background: #fff;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.todo-item.completed {
|
|
403
|
+
opacity: 0.6;
|
|
404
|
+
text-decoration: line-through;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.todo-text {
|
|
408
|
+
flex: 1;
|
|
409
|
+
margin: 0 10px;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.notification {
|
|
413
|
+
padding: 8px 12px;
|
|
414
|
+
border-radius: 4px;
|
|
415
|
+
margin-bottom: 8px;
|
|
416
|
+
font-size: 12px;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.notification.info {
|
|
420
|
+
background: #d1ecf1;
|
|
421
|
+
color: #0c5460;
|
|
422
|
+
border: 1px solid #bee5eb;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.notification.success {
|
|
426
|
+
background: #d4edda;
|
|
427
|
+
color: #155724;
|
|
428
|
+
border: 1px solid #c3e6cb;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.notification.warning {
|
|
432
|
+
background: #fff3cd;
|
|
433
|
+
color: #856404;
|
|
434
|
+
border: 1px solid #ffeaa7;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.notification.error {
|
|
438
|
+
background: #f8d7da;
|
|
439
|
+
color: #721c24;
|
|
440
|
+
border: 1px solid #f5c6cb;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
.stats {
|
|
444
|
+
display: grid;
|
|
445
|
+
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
|
446
|
+
gap: 10px;
|
|
447
|
+
margin-bottom: 15px;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.stat {
|
|
451
|
+
background: #fff;
|
|
452
|
+
padding: 10px;
|
|
453
|
+
border-radius: 4px;
|
|
454
|
+
text-align: center;
|
|
455
|
+
border: 1px solid #dee2e6;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
.stat-value {
|
|
459
|
+
font-size: 24px;
|
|
460
|
+
font-weight: bold;
|
|
461
|
+
color: #007bff;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.stat-label {
|
|
465
|
+
font-size: 12px;
|
|
466
|
+
color: #6c757d;
|
|
467
|
+
margin-top: 5px;
|
|
468
|
+
}
|
|
469
|
+
</style>
|
|
470
|
+
|
|
471
|
+
<div class="container">
|
|
472
|
+
<div class="section">
|
|
473
|
+
<h2>🏪 Store Management</h2>
|
|
474
|
+
|
|
475
|
+
<div class="stats">
|
|
476
|
+
<div class="stat">
|
|
477
|
+
<div class="stat-value">${storeState.count}</div>
|
|
478
|
+
<div class="stat-label">Count</div>
|
|
479
|
+
</div>
|
|
480
|
+
<div class="stat">
|
|
481
|
+
<div class="stat-value">${storeState.todos.length}</div>
|
|
482
|
+
<div class="stat-label">Todos</div>
|
|
483
|
+
</div>
|
|
484
|
+
<div class="stat">
|
|
485
|
+
<div class="stat-value">${storeState.todos.filter(t => t.completed).length}</div>
|
|
486
|
+
<div class="stat-label">Completed</div>
|
|
487
|
+
</div>
|
|
488
|
+
<div class="stat">
|
|
489
|
+
<div class="stat-value">${storeState.user ? 'Yes' : 'No'}</div>
|
|
490
|
+
<div class="stat-label">User</div>
|
|
491
|
+
</div>
|
|
492
|
+
</div>
|
|
493
|
+
|
|
494
|
+
<div class="form-group">
|
|
495
|
+
<button data-action="increment">Increment</button>
|
|
496
|
+
<button data-action="decrement">Decrement</button>
|
|
497
|
+
</div>
|
|
498
|
+
|
|
499
|
+
<div class="form-group">
|
|
500
|
+
<button data-action="set-user" class="success">Set User</button>
|
|
501
|
+
<button data-action="clear-user" class="danger">Clear User</button>
|
|
502
|
+
</div>
|
|
503
|
+
|
|
504
|
+
<div class="form-group">
|
|
505
|
+
<label>Add Todo:</label>
|
|
506
|
+
<div style="display: flex; gap: 8px;">
|
|
507
|
+
<input type="text" name="todo-input" placeholder="Enter todo...">
|
|
508
|
+
<button data-action="add-todo">Add</button>
|
|
509
|
+
</div>
|
|
510
|
+
</div>
|
|
511
|
+
|
|
512
|
+
<div class="state-display">
|
|
513
|
+
<strong>Store State:</strong><br>
|
|
514
|
+
<pre>${JSON.stringify(storeState, null, 2)}</pre>
|
|
515
|
+
</div>
|
|
516
|
+
</div>
|
|
517
|
+
|
|
518
|
+
<div class="section">
|
|
519
|
+
<h2>🌍 Global State</h2>
|
|
520
|
+
|
|
521
|
+
<div class="form-group">
|
|
522
|
+
<label>Theme:</label>
|
|
523
|
+
<button data-action="toggle-theme">
|
|
524
|
+
Toggle Theme (${globalStateData.theme})
|
|
525
|
+
</button>
|
|
526
|
+
</div>
|
|
527
|
+
|
|
528
|
+
<div class="form-group">
|
|
529
|
+
<label>Language:</label>
|
|
530
|
+
<select name="language">
|
|
531
|
+
<option value="en" ${globalStateData.language === 'en' ? 'selected' : ''}>English</option>
|
|
532
|
+
<option value="es" ${globalStateData.language === 'es' ? 'selected' : ''}>Spanish</option>
|
|
533
|
+
<option value="fr" ${globalStateData.language === 'fr' ? 'selected' : ''}>French</option>
|
|
534
|
+
<option value="de" ${globalStateData.language === 'de' ? 'selected' : ''}>German</option>
|
|
535
|
+
</select>
|
|
536
|
+
<button data-action="set-language">Set Language</button>
|
|
537
|
+
</div>
|
|
538
|
+
|
|
539
|
+
<div class="form-group">
|
|
540
|
+
<button data-action="add-notification" class="success">Add Notification</button>
|
|
541
|
+
<button data-action="clear-notifications" class="secondary">Clear All</button>
|
|
542
|
+
</div>
|
|
543
|
+
|
|
544
|
+
<div class="state-display">
|
|
545
|
+
<strong>Global State:</strong><br>
|
|
546
|
+
<pre>${JSON.stringify(globalStateData, null, 2)}</pre>
|
|
547
|
+
</div>
|
|
548
|
+
</div>
|
|
549
|
+
|
|
550
|
+
<div class="section">
|
|
551
|
+
<h2>📝 Todo List</h2>
|
|
552
|
+
${storeState.todos.length === 0 ?
|
|
553
|
+
html`<p style="color: #6c757d; text-align: center;">No todos yet. Add one above!</p>` :
|
|
554
|
+
storeState.todos.map(todo => html`
|
|
555
|
+
<div class="todo-item ${todo.completed ? 'completed' : ''}">
|
|
556
|
+
<button data-action="toggle-todo" data-id="${todo.id}" class="secondary">
|
|
557
|
+
${todo.completed ? '✓' : '○'}
|
|
558
|
+
</button>
|
|
559
|
+
<span class="todo-text">${todo.text}</span>
|
|
560
|
+
<button data-action="remove-todo" data-id="${todo.id}" class="danger">×</button>
|
|
561
|
+
</div>
|
|
562
|
+
`)
|
|
563
|
+
}
|
|
564
|
+
</div>
|
|
565
|
+
|
|
566
|
+
<div class="section">
|
|
567
|
+
<h2>🔔 Notifications</h2>
|
|
568
|
+
${globalStateData.notifications.length === 0 ?
|
|
569
|
+
html`<p style="color: #6c757d; text-align: center;">No notifications</p>` :
|
|
570
|
+
globalStateData.notifications.map(notification => html`
|
|
571
|
+
<div class="notification ${notification.type}">
|
|
572
|
+
${notification.message}
|
|
573
|
+
</div>
|
|
574
|
+
`)
|
|
575
|
+
}
|
|
576
|
+
</div>
|
|
577
|
+
|
|
578
|
+
<div class="section" style="grid-column: 1 / -1;">
|
|
579
|
+
<h2>📊 Store Events</h2>
|
|
580
|
+
<button data-action="clear-store-events" class="secondary">Clear Events</button>
|
|
581
|
+
<div class="events">
|
|
582
|
+
${this.storeEvents.length === 0 ?
|
|
583
|
+
html`<div style="color: #6c757d; text-align: center; padding: 20px;">No store events yet</div>` :
|
|
584
|
+
this.storeEvents.map(event => html`
|
|
585
|
+
<div class="event store">
|
|
586
|
+
<div class="event-time">${event.timestamp}</div>
|
|
587
|
+
<strong>${event.type}</strong><br>
|
|
588
|
+
<pre style="font-size: 11px; margin: 5px 0 0 0; white-space: pre-wrap;">${JSON.stringify(event.details, null, 2)}</pre>
|
|
589
|
+
</div>
|
|
590
|
+
`)
|
|
591
|
+
}
|
|
592
|
+
</div>
|
|
593
|
+
</div>
|
|
594
|
+
|
|
595
|
+
<div class="section" style="grid-column: 1 / -1;">
|
|
596
|
+
<h2>🌍 Global Events</h2>
|
|
597
|
+
<button data-action="clear-global-events" class="secondary">Clear Events</button>
|
|
598
|
+
<div class="events">
|
|
599
|
+
${this.globalEvents.length === 0 ?
|
|
600
|
+
html`<div style="color: #6c757d; text-align: center; padding: 20px;">No global events yet</div>` :
|
|
601
|
+
this.globalEvents.map(event => html`
|
|
602
|
+
<div class="event global">
|
|
603
|
+
<div class="event-time">${event.timestamp}</div>
|
|
604
|
+
<strong>${event.type}</strong><br>
|
|
605
|
+
<pre style="font-size: 11px; margin: 5px 0 0 0; white-space: pre-wrap;">${JSON.stringify(event.details, null, 2)}</pre>
|
|
606
|
+
</div>
|
|
607
|
+
`)
|
|
608
|
+
}
|
|
609
|
+
</div>
|
|
610
|
+
</div>
|
|
611
|
+
</div>
|
|
612
|
+
`;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
customElements.define('data-flow-demo', DataFlowDemo);
|
|
617
|
+
|
|
618
|
+
export const Default: Story = {
|
|
619
|
+
render: () => `
|
|
620
|
+
<data-flow-demo></data-flow-demo>
|
|
621
|
+
`
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
export const WithInitialData: Story = {
|
|
625
|
+
render: () => `
|
|
626
|
+
<data-flow-demo></data-flow-demo>
|
|
627
|
+
`,
|
|
628
|
+
play: async () => {
|
|
629
|
+
// Initialize with some data
|
|
630
|
+
const { globalState } = await import('@miura/miura-data-flow');
|
|
631
|
+
|
|
632
|
+
// Set initial global state
|
|
633
|
+
globalState.set('theme', 'dark');
|
|
634
|
+
globalState.set('language', 'es');
|
|
635
|
+
globalState.set('notifications', [
|
|
636
|
+
{ id: '1', message: 'Welcome to miura Data Flow!', type: 'success' },
|
|
637
|
+
{ id: '2', message: 'This is a demo notification', type: 'info' }
|
|
638
|
+
]);
|
|
639
|
+
}
|
|
640
|
+
};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "dist",
|
|
5
|
+
"rootDir": ".",
|
|
6
|
+
"composite": true,
|
|
7
|
+
"baseUrl": ".",
|
|
8
|
+
"paths": {
|
|
9
|
+
"@miura/miura-debugger": ["../miura-debugger/index.ts"]
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"include": ["index.ts", "src/**/*"],
|
|
13
|
+
"exclude": ["node_modules"],
|
|
14
|
+
"references": [
|
|
15
|
+
{ "path": "../miura-debugger" }
|
|
16
|
+
]
|
|
17
|
+
}
|