eleva 1.0.1 → 1.1.0
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 +21 -10
- package/dist/{eleva-plugins.cjs.js → eleva-plugins.cjs} +1002 -292
- package/dist/eleva-plugins.cjs.map +1 -0
- package/dist/eleva-plugins.d.cts +1352 -0
- package/dist/eleva-plugins.d.cts.map +1 -0
- package/dist/eleva-plugins.d.ts +1352 -0
- package/dist/eleva-plugins.d.ts.map +1 -0
- package/dist/{eleva-plugins.esm.js → eleva-plugins.js} +1002 -292
- package/dist/eleva-plugins.js.map +1 -0
- package/dist/eleva-plugins.umd.js +1001 -291
- package/dist/eleva-plugins.umd.js.map +1 -1
- package/dist/eleva-plugins.umd.min.js +1 -1
- package/dist/eleva-plugins.umd.min.js.map +1 -1
- package/dist/{eleva.cjs.js → eleva.cjs} +421 -191
- package/dist/eleva.cjs.map +1 -0
- package/dist/eleva.d.cts +1329 -0
- package/dist/eleva.d.cts.map +1 -0
- package/dist/eleva.d.ts +473 -226
- package/dist/eleva.d.ts.map +1 -0
- package/dist/{eleva.esm.js → eleva.js} +422 -192
- package/dist/eleva.js.map +1 -0
- package/dist/eleva.umd.js +420 -190
- package/dist/eleva.umd.js.map +1 -1
- package/dist/eleva.umd.min.js +1 -1
- package/dist/eleva.umd.min.js.map +1 -1
- package/dist/plugins/attr.cjs +279 -0
- package/dist/plugins/attr.cjs.map +1 -0
- package/dist/plugins/attr.d.cts +101 -0
- package/dist/plugins/attr.d.cts.map +1 -0
- package/dist/plugins/attr.d.ts +101 -0
- package/dist/plugins/attr.d.ts.map +1 -0
- package/dist/plugins/attr.js +276 -0
- package/dist/plugins/attr.js.map +1 -0
- package/dist/plugins/attr.umd.js +111 -22
- package/dist/plugins/attr.umd.js.map +1 -1
- package/dist/plugins/attr.umd.min.js +1 -1
- package/dist/plugins/attr.umd.min.js.map +1 -1
- package/dist/plugins/router.cjs +1873 -0
- package/dist/plugins/router.cjs.map +1 -0
- package/dist/plugins/router.d.cts +1296 -0
- package/dist/plugins/router.d.cts.map +1 -0
- package/dist/plugins/router.d.ts +1296 -0
- package/dist/plugins/router.d.ts.map +1 -0
- package/dist/plugins/router.js +1870 -0
- package/dist/plugins/router.js.map +1 -0
- package/dist/plugins/router.umd.js +482 -186
- package/dist/plugins/router.umd.js.map +1 -1
- package/dist/plugins/router.umd.min.js +1 -1
- package/dist/plugins/router.umd.min.js.map +1 -1
- package/dist/plugins/store.cjs +920 -0
- package/dist/plugins/store.cjs.map +1 -0
- package/dist/plugins/store.d.cts +266 -0
- package/dist/plugins/store.d.cts.map +1 -0
- package/dist/plugins/store.d.ts +266 -0
- package/dist/plugins/store.d.ts.map +1 -0
- package/dist/plugins/store.js +917 -0
- package/dist/plugins/store.js.map +1 -0
- package/dist/plugins/store.umd.js +410 -85
- package/dist/plugins/store.umd.js.map +1 -1
- package/dist/plugins/store.umd.min.js +1 -1
- package/dist/plugins/store.umd.min.js.map +1 -1
- package/package.json +112 -68
- package/src/core/Eleva.js +195 -115
- package/src/index.cjs +10 -0
- package/src/index.js +11 -0
- package/src/modules/Emitter.js +68 -20
- package/src/modules/Renderer.js +82 -20
- package/src/modules/Signal.js +43 -15
- package/src/modules/TemplateEngine.js +50 -9
- package/src/plugins/Attr.js +121 -19
- package/src/plugins/Router.js +526 -181
- package/src/plugins/Store.js +448 -69
- package/src/plugins/index.js +1 -0
- package/types/core/Eleva.d.ts +263 -169
- package/types/core/Eleva.d.ts.map +1 -1
- package/types/index.d.cts +3 -0
- package/types/index.d.cts.map +1 -0
- package/types/index.d.ts +5 -0
- package/types/index.d.ts.map +1 -1
- package/types/modules/Emitter.d.ts +73 -30
- package/types/modules/Emitter.d.ts.map +1 -1
- package/types/modules/Renderer.d.ts +48 -18
- package/types/modules/Renderer.d.ts.map +1 -1
- package/types/modules/Signal.d.ts +44 -16
- package/types/modules/Signal.d.ts.map +1 -1
- package/types/modules/TemplateEngine.d.ts +46 -11
- package/types/modules/TemplateEngine.d.ts.map +1 -1
- package/types/plugins/Attr.d.ts +83 -16
- package/types/plugins/Attr.d.ts.map +1 -1
- package/types/plugins/Router.d.ts +498 -207
- package/types/plugins/Router.d.ts.map +1 -1
- package/types/plugins/Store.d.ts +211 -37
- package/types/plugins/Store.d.ts.map +1 -1
- package/dist/eleva-plugins.cjs.js.map +0 -1
- package/dist/eleva-plugins.esm.js.map +0 -1
- package/dist/eleva.cjs.js.map +0 -1
- package/dist/eleva.esm.js.map +0 -1
|
@@ -0,0 +1,920 @@
|
|
|
1
|
+
/*! Eleva Store Plugin v1.1.0 | MIT License | https://elevajs.com */
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @module eleva/plugins/store
|
|
6
|
+
* @fileoverview Reactive state management plugin with namespaced modules,
|
|
7
|
+
* persistence, and subscription system.
|
|
8
|
+
*/ // ============================================================================
|
|
9
|
+
// TYPE DEFINITIONS
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// -----------------------------------------------------------------------------
|
|
12
|
+
// External Type Imports
|
|
13
|
+
// -----------------------------------------------------------------------------
|
|
14
|
+
/**
|
|
15
|
+
* Type imports from the Eleva core library.
|
|
16
|
+
* @typedef {import('eleva').Eleva} Eleva
|
|
17
|
+
* @typedef {import('eleva').ComponentDefinition} ComponentDefinition
|
|
18
|
+
* @typedef {import('eleva').ComponentContext} ComponentContext
|
|
19
|
+
* @typedef {import('eleva').SetupResult} SetupResult
|
|
20
|
+
* @typedef {import('eleva').ComponentProps} ComponentProps
|
|
21
|
+
* @typedef {import('eleva').ChildrenMap} ChildrenMap
|
|
22
|
+
* @typedef {import('eleva').MountResult} MountResult
|
|
23
|
+
*/ /**
|
|
24
|
+
* Generic type import.
|
|
25
|
+
* @template T
|
|
26
|
+
* @typedef {import('eleva').Signal<T>} Signal
|
|
27
|
+
*/ // -----------------------------------------------------------------------------
|
|
28
|
+
// Store Type Definitions
|
|
29
|
+
// -----------------------------------------------------------------------------
|
|
30
|
+
/**
|
|
31
|
+
* Mutation record emitted to subscribers.
|
|
32
|
+
* @typedef {Object} StoreMutation
|
|
33
|
+
* @property {string} type
|
|
34
|
+
* The action name that was dispatched.
|
|
35
|
+
* @property {unknown} payload
|
|
36
|
+
* The payload passed to the action.
|
|
37
|
+
* @property {number} timestamp
|
|
38
|
+
* Unix timestamp of when the mutation occurred.
|
|
39
|
+
* @description Record passed to subscribers when state changes via dispatch.
|
|
40
|
+
* @example
|
|
41
|
+
* store.subscribe((mutation, state) => {
|
|
42
|
+
* console.log(`Action: ${mutation.type}`);
|
|
43
|
+
* console.log(`Payload: ${mutation.payload}`);
|
|
44
|
+
* console.log(`Time: ${new Date(mutation.timestamp)}`);
|
|
45
|
+
* });
|
|
46
|
+
*/ /**
|
|
47
|
+
* Store configuration options.
|
|
48
|
+
* @typedef {Object} StoreOptions
|
|
49
|
+
* @property {Record<string, unknown>} [state]
|
|
50
|
+
* Initial state object.
|
|
51
|
+
* @property {Record<string, ActionFunction>} [actions]
|
|
52
|
+
* Action functions for state mutations.
|
|
53
|
+
* @property {Record<string, StoreModule>} [namespaces]
|
|
54
|
+
* Namespaced modules for organizing store.
|
|
55
|
+
* @property {StorePersistenceOptions} [persistence]
|
|
56
|
+
* Persistence configuration.
|
|
57
|
+
* @property {boolean} [devTools]
|
|
58
|
+
* Enable development tools integration.
|
|
59
|
+
* @property {StoreErrorHandler} [onError]
|
|
60
|
+
* Error handler function.
|
|
61
|
+
* @description Configuration options passed to StorePlugin.install().
|
|
62
|
+
* @example
|
|
63
|
+
* app.use(StorePlugin, {
|
|
64
|
+
* state: { count: 0, user: null },
|
|
65
|
+
* actions: {
|
|
66
|
+
* increment: (state) => state.count.value++,
|
|
67
|
+
* setUser: (state, user) => state.user.value = user
|
|
68
|
+
* },
|
|
69
|
+
* persistence: { enabled: true, key: 'my-app' }
|
|
70
|
+
* });
|
|
71
|
+
*/ /**
|
|
72
|
+
* Namespaced store module definition.
|
|
73
|
+
* @typedef {Object} StoreModule
|
|
74
|
+
* @property {Record<string, unknown>} state
|
|
75
|
+
* Module state.
|
|
76
|
+
* @property {Record<string, ActionFunction>} [actions]
|
|
77
|
+
* Module actions.
|
|
78
|
+
* @description Defines a namespaced module for organizing related state and actions.
|
|
79
|
+
* @example
|
|
80
|
+
* // Define a module
|
|
81
|
+
* const authModule = {
|
|
82
|
+
* state: { user: null, token: null },
|
|
83
|
+
* actions: {
|
|
84
|
+
* login: (state, { user, token }) => {
|
|
85
|
+
* state.auth.user.value = user;
|
|
86
|
+
* state.auth.token.value = token;
|
|
87
|
+
* }
|
|
88
|
+
* }
|
|
89
|
+
* };
|
|
90
|
+
*
|
|
91
|
+
* // Register dynamically
|
|
92
|
+
* store.registerModule('auth', authModule);
|
|
93
|
+
*/ /**
|
|
94
|
+
* Store persistence configuration.
|
|
95
|
+
* @typedef {Object} StorePersistenceOptions
|
|
96
|
+
* @property {boolean} [enabled]
|
|
97
|
+
* Enable state persistence.
|
|
98
|
+
* @property {string} [key]
|
|
99
|
+
* Storage key (default: "eleva-store").
|
|
100
|
+
* @property {'localStorage' | 'sessionStorage'} [storage]
|
|
101
|
+
* Storage type.
|
|
102
|
+
* @property {string[]} [include]
|
|
103
|
+
* Dot-path prefixes to persist (e.g., "auth.user").
|
|
104
|
+
* @property {string[]} [exclude]
|
|
105
|
+
* Dot-path prefixes to exclude.
|
|
106
|
+
* @description Configuration for persisting store state to localStorage or sessionStorage.
|
|
107
|
+
* @example
|
|
108
|
+
* // Persist only specific state paths
|
|
109
|
+
* persistence: {
|
|
110
|
+
* enabled: true,
|
|
111
|
+
* key: 'my-app-store',
|
|
112
|
+
* storage: 'localStorage',
|
|
113
|
+
* include: ['user', 'settings.theme']
|
|
114
|
+
* }
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* // Exclude sensitive data
|
|
118
|
+
* persistence: {
|
|
119
|
+
* enabled: true,
|
|
120
|
+
* exclude: ['auth.token', 'temp']
|
|
121
|
+
* }
|
|
122
|
+
*/ /**
|
|
123
|
+
* Store error handler callback.
|
|
124
|
+
* @typedef {(error: Error, context: string) => void} StoreErrorHandler
|
|
125
|
+
* @description Custom error handler for store operations.
|
|
126
|
+
* @example
|
|
127
|
+
* app.use(StorePlugin, {
|
|
128
|
+
* onError: (error, context) => {
|
|
129
|
+
* console.error(`Store error in ${context}:`, error);
|
|
130
|
+
* // Send to error tracking service
|
|
131
|
+
* errorTracker.capture(error, { context });
|
|
132
|
+
* }
|
|
133
|
+
* });
|
|
134
|
+
*/ /**
|
|
135
|
+
* Reactive state tree containing signals and nested namespaces.
|
|
136
|
+
* @typedef {Record<string, Signal<unknown> | Record<string, unknown>>} StoreState
|
|
137
|
+
* @description Represents the store's reactive state structure with support for nested modules.
|
|
138
|
+
*/ /**
|
|
139
|
+
* Action function signature for store actions.
|
|
140
|
+
* @typedef {(state: StoreState, payload?: unknown) => unknown} ActionFunction
|
|
141
|
+
* @description Function that receives state and optional payload, returns action result.
|
|
142
|
+
*/ /**
|
|
143
|
+
* Dispatch function signature for triggering actions.
|
|
144
|
+
* @typedef {(actionName: string, payload?: unknown) => Promise<unknown>} DispatchFunction
|
|
145
|
+
* @description Dispatches an action by name with optional payload, returns action result.
|
|
146
|
+
*/ /**
|
|
147
|
+
* Subscribe callback signature for mutation listeners.
|
|
148
|
+
* @typedef {(mutation: StoreMutation, state: StoreState) => void} SubscribeCallback
|
|
149
|
+
* @description Called after each successful action dispatch with mutation details and current state.
|
|
150
|
+
*/ /**
|
|
151
|
+
* Store API exposed to components via ctx.store.
|
|
152
|
+
* @typedef {Object} StoreApi
|
|
153
|
+
* @property {StoreState} state
|
|
154
|
+
* Reactive state signals (supports nested modules).
|
|
155
|
+
* @property {DispatchFunction} dispatch
|
|
156
|
+
* Dispatch an action by name with optional payload.
|
|
157
|
+
* @property {(callback: SubscribeCallback) => () => void} subscribe
|
|
158
|
+
* Subscribe to state mutations. Returns unsubscribe function.
|
|
159
|
+
* @property {() => Record<string, unknown>} getState
|
|
160
|
+
* Get a snapshot of current state values.
|
|
161
|
+
* @property {(namespace: string, module: StoreModule) => void} registerModule
|
|
162
|
+
* Register a namespaced module dynamically.
|
|
163
|
+
* @property {(namespace: string) => void} unregisterModule
|
|
164
|
+
* Unregister a namespaced module.
|
|
165
|
+
* @property {(key: string, initialValue: unknown) => Signal<unknown>} createState
|
|
166
|
+
* Create a new state signal dynamically.
|
|
167
|
+
* @property {(name: string, actionFn: ActionFunction) => void} createAction
|
|
168
|
+
* Register a new action dynamically.
|
|
169
|
+
* @property {new <T>(value: T) => Signal<T>} signal
|
|
170
|
+
* Signal class constructor for manual state creation.
|
|
171
|
+
* @description The store API injected into component setup as `ctx.store`.
|
|
172
|
+
* @example
|
|
173
|
+
* app.component('Counter', {
|
|
174
|
+
* setup({ store }) {
|
|
175
|
+
* // Access reactive state
|
|
176
|
+
* const count = store.state.count;
|
|
177
|
+
*
|
|
178
|
+
* // Dispatch actions
|
|
179
|
+
* const increment = () => store.dispatch('increment');
|
|
180
|
+
*
|
|
181
|
+
* // Subscribe to changes
|
|
182
|
+
* const unsub = store.subscribe((mutation) => {
|
|
183
|
+
* console.log('State changed:', mutation.type);
|
|
184
|
+
* });
|
|
185
|
+
*
|
|
186
|
+
* return { count, increment, onUnmount: () => unsub() };
|
|
187
|
+
* },
|
|
188
|
+
* template: (ctx) => `<button @click="increment">${ctx.count.value}</button>`
|
|
189
|
+
* });
|
|
190
|
+
* @see StoreMutation - Mutation record structure.
|
|
191
|
+
* @see StoreModule - Module definition for namespaces.
|
|
192
|
+
*/ /**
|
|
193
|
+
* @class 🏪 StorePlugin
|
|
194
|
+
* @classdesc A powerful reactive state management plugin for Eleva that enables sharing
|
|
195
|
+
* reactive data across the entire application. The Store plugin provides a centralized,
|
|
196
|
+
* reactive data store that can be accessed from any component's setup function.
|
|
197
|
+
*
|
|
198
|
+
* Core Features:
|
|
199
|
+
* - Centralized reactive state management using Eleva's signal system
|
|
200
|
+
* - Global state accessibility through component setup functions
|
|
201
|
+
* - Namespace support for organizing store modules
|
|
202
|
+
* - Built-in persistence with localStorage/sessionStorage support
|
|
203
|
+
* - Action-based state mutations with validation
|
|
204
|
+
* - Subscription system for reactive updates
|
|
205
|
+
* - DevTools integration for debugging
|
|
206
|
+
* - Plugin architecture for extensibility
|
|
207
|
+
*
|
|
208
|
+
* @example
|
|
209
|
+
* // Install the plugin
|
|
210
|
+
* const app = new Eleva("myApp");
|
|
211
|
+
* app.use(StorePlugin, {
|
|
212
|
+
* state: {
|
|
213
|
+
* user: { name: "John", email: "john@example.com" },
|
|
214
|
+
* counter: 0,
|
|
215
|
+
* todos: []
|
|
216
|
+
* },
|
|
217
|
+
* actions: {
|
|
218
|
+
* increment: (state) => state.counter.value++,
|
|
219
|
+
* addTodo: (state, todo) => state.todos.value = [...state.todos.value, todo],
|
|
220
|
+
* setUser: (state, user) => state.user.value = user
|
|
221
|
+
* },
|
|
222
|
+
* persistence: {
|
|
223
|
+
* enabled: true,
|
|
224
|
+
* key: "myApp-store",
|
|
225
|
+
* storage: "localStorage"
|
|
226
|
+
* }
|
|
227
|
+
* });
|
|
228
|
+
*
|
|
229
|
+
* // Use store in components
|
|
230
|
+
* app.component("Counter", {
|
|
231
|
+
* setup({ store }) {
|
|
232
|
+
* return {
|
|
233
|
+
* count: store.state.counter,
|
|
234
|
+
* increment: () => store.dispatch("increment"),
|
|
235
|
+
* user: store.state.user
|
|
236
|
+
* };
|
|
237
|
+
* },
|
|
238
|
+
* template: (ctx) => `
|
|
239
|
+
* <div>
|
|
240
|
+
* <p>Hello ${ctx.user.value.name}!</p>
|
|
241
|
+
* <p>Count: ${ctx.count.value}</p>
|
|
242
|
+
* <button @click="increment">+</button>
|
|
243
|
+
* </div>
|
|
244
|
+
* `
|
|
245
|
+
* });
|
|
246
|
+
*/ const StorePlugin = {
|
|
247
|
+
/**
|
|
248
|
+
* Unique identifier for the plugin
|
|
249
|
+
* @type {string}
|
|
250
|
+
*/ name: "store",
|
|
251
|
+
/**
|
|
252
|
+
* Plugin version
|
|
253
|
+
* @type {string}
|
|
254
|
+
*/ version: "1.1.0",
|
|
255
|
+
/**
|
|
256
|
+
* Plugin description
|
|
257
|
+
* @type {string}
|
|
258
|
+
*/ description: "Reactive state management for sharing data across the entire Eleva application",
|
|
259
|
+
/**
|
|
260
|
+
* Installs the plugin into the Eleva instance.
|
|
261
|
+
*
|
|
262
|
+
* @public
|
|
263
|
+
* @param {Eleva} eleva - The Eleva instance.
|
|
264
|
+
* @param {StoreOptions} options - Plugin configuration options.
|
|
265
|
+
* @param {Record<string, unknown>} [options.state={}] - Initial state object.
|
|
266
|
+
* @param {Record<string, ActionFunction>} [options.actions={}] - Action functions for state mutations.
|
|
267
|
+
* @param {Record<string, StoreModule>} [options.namespaces={}] - Namespaced modules for organizing store.
|
|
268
|
+
* @param {StorePersistenceOptions} [options.persistence] - Persistence configuration.
|
|
269
|
+
* @param {boolean} [options.persistence.enabled=false] - Enable state persistence.
|
|
270
|
+
* @param {string} [options.persistence.key="eleva-store"] - Storage key.
|
|
271
|
+
* @param {'localStorage' | 'sessionStorage'} [options.persistence.storage="localStorage"] - Storage type.
|
|
272
|
+
* @param {string[]} [options.persistence.include] - Dot-path prefixes to persist (e.g., "auth.user")
|
|
273
|
+
* @param {string[]} [options.persistence.exclude] - Dot-path prefixes to exclude (applies when include is empty).
|
|
274
|
+
* @param {boolean} [options.devTools=false] - Enable development tools integration.
|
|
275
|
+
* @param {(error: Error, context: string) => void} [options.onError=null] - Error handler function.
|
|
276
|
+
* @returns {void}
|
|
277
|
+
* @description
|
|
278
|
+
* Installs the store and injects `store` into component setup context by wrapping
|
|
279
|
+
* `eleva.mount` and `eleva._mountComponents`. Also exposes `eleva.store` and
|
|
280
|
+
* helper methods (`eleva.dispatch`, `eleva.getState`, `eleva.subscribe`, `eleva.createAction`).
|
|
281
|
+
* Uninstall restores the originals.
|
|
282
|
+
*
|
|
283
|
+
* @example
|
|
284
|
+
* // Basic installation
|
|
285
|
+
* app.use(StorePlugin, {
|
|
286
|
+
* state: { count: 0, user: null },
|
|
287
|
+
* actions: {
|
|
288
|
+
* increment: (state) => state.count.value++,
|
|
289
|
+
* setUser: (state, user) => state.user.value = user
|
|
290
|
+
* }
|
|
291
|
+
* });
|
|
292
|
+
*
|
|
293
|
+
* // Advanced installation with persistence and namespaces
|
|
294
|
+
* app.use(StorePlugin, {
|
|
295
|
+
* state: { theme: "light" },
|
|
296
|
+
* namespaces: {
|
|
297
|
+
* auth: {
|
|
298
|
+
* state: { user: null, token: null },
|
|
299
|
+
* actions: {
|
|
300
|
+
* login: (state, { user, token }) => {
|
|
301
|
+
* state.auth.user.value = user;
|
|
302
|
+
* state.auth.token.value = token;
|
|
303
|
+
* },
|
|
304
|
+
* logout: (state) => {
|
|
305
|
+
* state.auth.user.value = null;
|
|
306
|
+
* state.auth.token.value = null;
|
|
307
|
+
* }
|
|
308
|
+
* }
|
|
309
|
+
* }
|
|
310
|
+
* },
|
|
311
|
+
* persistence: {
|
|
312
|
+
* enabled: true,
|
|
313
|
+
* include: ["theme", "auth.user"]
|
|
314
|
+
* }
|
|
315
|
+
* });
|
|
316
|
+
*/ install (eleva, options = {}) {
|
|
317
|
+
const { state = {}, actions = {}, namespaces = {}, persistence = {}, devTools = false, onError = null } = options;
|
|
318
|
+
/**
|
|
319
|
+
* @class Store
|
|
320
|
+
* @classdesc Store instance that manages all state and provides the API.
|
|
321
|
+
* @private
|
|
322
|
+
*/ class Store {
|
|
323
|
+
/**
|
|
324
|
+
* Initializes the root state and actions.
|
|
325
|
+
* Creates reactive signals for each state property and copies actions.
|
|
326
|
+
*
|
|
327
|
+
* @private
|
|
328
|
+
* @param {Record<string, unknown>} initialState - The initial state key-value pairs.
|
|
329
|
+
* @param {Record<string, ActionFunction>} initialActions - The action functions to register.
|
|
330
|
+
* @returns {void}
|
|
331
|
+
*/ _initializeState(initialState, initialActions) {
|
|
332
|
+
// Create reactive signals for each state property
|
|
333
|
+
Object.entries(initialState).forEach(([key, value])=>{
|
|
334
|
+
this.state[key] = new eleva.signal(value);
|
|
335
|
+
});
|
|
336
|
+
// Set up actions
|
|
337
|
+
this.actions = {
|
|
338
|
+
...initialActions
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Initializes namespaced modules.
|
|
343
|
+
* Creates namespace objects and populates them with state signals and actions.
|
|
344
|
+
*
|
|
345
|
+
* @private
|
|
346
|
+
* @param {Record<string, StoreModule>} namespaces - Map of namespace names to module definitions.
|
|
347
|
+
* @returns {void}
|
|
348
|
+
*/ _initializeNamespaces(namespaces) {
|
|
349
|
+
Object.entries(namespaces).forEach(([namespace, module])=>{
|
|
350
|
+
const { state: moduleState = {}, actions: moduleActions = {} } = module;
|
|
351
|
+
// Create namespace object if it doesn't exist
|
|
352
|
+
if (!this.state[namespace]) {
|
|
353
|
+
this.state[namespace] = {};
|
|
354
|
+
}
|
|
355
|
+
if (!this.actions[namespace]) {
|
|
356
|
+
this.actions[namespace] = {};
|
|
357
|
+
}
|
|
358
|
+
// Initialize namespaced state
|
|
359
|
+
Object.entries(moduleState).forEach(([key, value])=>{
|
|
360
|
+
this.state[namespace][key] = new eleva.signal(value);
|
|
361
|
+
});
|
|
362
|
+
// Set up namespaced actions
|
|
363
|
+
this.actions[namespace] = {
|
|
364
|
+
...moduleActions
|
|
365
|
+
};
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Loads persisted state from storage.
|
|
370
|
+
* Reads from localStorage/sessionStorage and applies values to state signals.
|
|
371
|
+
* Does nothing if persistence is disabled or running in SSR environment.
|
|
372
|
+
*
|
|
373
|
+
* @private
|
|
374
|
+
* @returns {void}
|
|
375
|
+
*/ _loadPersistedState() {
|
|
376
|
+
if (!this.persistence.enabled || typeof window === "undefined") {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
try {
|
|
380
|
+
const storage = window[this.persistence.storage];
|
|
381
|
+
const persistedData = storage.getItem(this.persistence.key);
|
|
382
|
+
if (persistedData) {
|
|
383
|
+
const data = JSON.parse(persistedData);
|
|
384
|
+
this._applyPersistedData(data);
|
|
385
|
+
}
|
|
386
|
+
} catch (error) {
|
|
387
|
+
if (this.onError) {
|
|
388
|
+
this.onError(error, "Failed to load persisted state");
|
|
389
|
+
} else {
|
|
390
|
+
console.warn("[StorePlugin] Failed to load persisted state:", error);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Applies persisted data to the current state.
|
|
396
|
+
* Recursively updates signal values for paths that should be persisted.
|
|
397
|
+
*
|
|
398
|
+
* @private
|
|
399
|
+
* @param {Record<string, unknown>} data - The persisted data object to apply.
|
|
400
|
+
* @param {Record<string, unknown>} [currentState=this.state] - The current state object to update.
|
|
401
|
+
* @param {string} [path=""] - The current dot-notation path (for include/exclude filtering).
|
|
402
|
+
* @returns {void}
|
|
403
|
+
*/ _applyPersistedData(data, currentState = this.state, path = "") {
|
|
404
|
+
Object.entries(data).forEach(([key, value])=>{
|
|
405
|
+
const fullPath = path ? `${path}.${key}` : key;
|
|
406
|
+
if (this._shouldPersist(fullPath)) {
|
|
407
|
+
if (currentState[key] && typeof currentState[key] === "object" && "value" in currentState[key]) {
|
|
408
|
+
// This is a signal, update its value
|
|
409
|
+
currentState[key].value = value;
|
|
410
|
+
} else if (typeof value === "object" && value !== null && currentState[key]) {
|
|
411
|
+
// This is a nested object, recurse
|
|
412
|
+
this._applyPersistedData(value, currentState[key], fullPath);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Determines if a state path should be persisted.
|
|
419
|
+
* Checks against include/exclude filters configured in persistence options.
|
|
420
|
+
*
|
|
421
|
+
* @private
|
|
422
|
+
* @param {string} path - The dot-notation path to check (e.g., "auth.user").
|
|
423
|
+
* @returns {boolean} True if the path should be persisted, false otherwise.
|
|
424
|
+
*/ _shouldPersist(path) {
|
|
425
|
+
const { include, exclude } = this.persistence;
|
|
426
|
+
if (include && include.length > 0) {
|
|
427
|
+
return include.some((includePath)=>path.startsWith(includePath));
|
|
428
|
+
}
|
|
429
|
+
if (exclude && exclude.length > 0) {
|
|
430
|
+
return !exclude.some((excludePath)=>path.startsWith(excludePath));
|
|
431
|
+
}
|
|
432
|
+
return true;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Saves current state to storage.
|
|
436
|
+
* Extracts persistable data and writes to localStorage/sessionStorage.
|
|
437
|
+
* Does nothing if persistence is disabled or running in SSR environment.
|
|
438
|
+
*
|
|
439
|
+
* @private
|
|
440
|
+
* @returns {void}
|
|
441
|
+
*/ _saveState() {
|
|
442
|
+
if (!this.persistence.enabled || typeof window === "undefined") {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
try {
|
|
446
|
+
const storage = window[this.persistence.storage];
|
|
447
|
+
const dataToSave = this._extractPersistedData();
|
|
448
|
+
storage.setItem(this.persistence.key, JSON.stringify(dataToSave));
|
|
449
|
+
} catch (error) {
|
|
450
|
+
if (this.onError) {
|
|
451
|
+
this.onError(error, "Failed to save state");
|
|
452
|
+
} else {
|
|
453
|
+
console.warn("[StorePlugin] Failed to save state:", error);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Extracts data that should be persisted.
|
|
459
|
+
* Recursively extracts signal values for paths that pass persistence filters.
|
|
460
|
+
*
|
|
461
|
+
* @private
|
|
462
|
+
* @param {Record<string, unknown>} [currentState=this.state] - The state object to extract from.
|
|
463
|
+
* @param {string} [path=""] - The current dot-notation path (for include/exclude filtering).
|
|
464
|
+
* @returns {Record<string, unknown>} The extracted data object with raw values (not signals).
|
|
465
|
+
*/ _extractPersistedData(currentState = this.state, path = "") {
|
|
466
|
+
const result = {};
|
|
467
|
+
Object.entries(currentState).forEach(([key, value])=>{
|
|
468
|
+
const fullPath = path ? `${path}.${key}` : key;
|
|
469
|
+
if (this._shouldPersist(fullPath)) {
|
|
470
|
+
if (value && typeof value === "object" && "value" in value) {
|
|
471
|
+
// This is a signal, extract its value
|
|
472
|
+
result[key] = value.value;
|
|
473
|
+
} else if (typeof value === "object" && value !== null) {
|
|
474
|
+
// This is a nested object, recurse
|
|
475
|
+
const nestedData = this._extractPersistedData(value, fullPath);
|
|
476
|
+
if (Object.keys(nestedData).length > 0) {
|
|
477
|
+
result[key] = nestedData;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
return result;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Sets up development tools integration.
|
|
486
|
+
* Registers the store with Eleva DevTools if available and enabled.
|
|
487
|
+
* Does nothing if devTools is disabled, running in SSR, or DevTools not installed.
|
|
488
|
+
*
|
|
489
|
+
* @private
|
|
490
|
+
* @returns {void}
|
|
491
|
+
*/ _setupDevTools() {
|
|
492
|
+
if (!this.devTools || typeof window === "undefined" || !window.__ELEVA_DEVTOOLS__) {
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
window.__ELEVA_DEVTOOLS__.registerStore(this);
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Dispatches an action to mutate the state.
|
|
499
|
+
*
|
|
500
|
+
* Execution flow:
|
|
501
|
+
* 1. Retrieves the action function (supports namespaced actions like "auth.login")
|
|
502
|
+
* 2. Records mutation for devtools/history (keeps last 100 mutations)
|
|
503
|
+
* 3. Executes action with await (actions can be sync or async)
|
|
504
|
+
* 4. Saves state if persistence is enabled
|
|
505
|
+
* 5. Notifies all subscribers with (mutation, state)
|
|
506
|
+
* 6. Notifies devtools if enabled
|
|
507
|
+
*
|
|
508
|
+
* @note Always returns a Promise regardless of whether the action is sync or async.
|
|
509
|
+
* Subscriber callbacks that throw are caught and passed to onError handler.
|
|
510
|
+
*
|
|
511
|
+
* @async
|
|
512
|
+
* @param {string} actionName - The name of the action to dispatch (supports dot notation for namespaces).
|
|
513
|
+
* @param {unknown} payload - The payload to pass to the action.
|
|
514
|
+
* @returns {Promise<unknown>} The result of the action (undefined if action returns nothing).
|
|
515
|
+
* @throws {Error} If action is not found or action function throws.
|
|
516
|
+
* @see subscribe - Listen for mutations triggered by dispatch.
|
|
517
|
+
* @see getState - Get current state values.
|
|
518
|
+
*/ async dispatch(actionName, payload) {
|
|
519
|
+
try {
|
|
520
|
+
const action = this._getAction(actionName);
|
|
521
|
+
if (!action) {
|
|
522
|
+
const error = new Error(`Action "${actionName}" not found`);
|
|
523
|
+
if (this.onError) {
|
|
524
|
+
this.onError(error, actionName);
|
|
525
|
+
}
|
|
526
|
+
throw error;
|
|
527
|
+
}
|
|
528
|
+
const mutation = {
|
|
529
|
+
type: actionName,
|
|
530
|
+
payload,
|
|
531
|
+
timestamp: Date.now()
|
|
532
|
+
};
|
|
533
|
+
// Record mutation for devtools
|
|
534
|
+
this.mutations.push(mutation);
|
|
535
|
+
if (this.mutations.length > 100) {
|
|
536
|
+
this.mutations.shift(); // Keep only last 100 mutations
|
|
537
|
+
}
|
|
538
|
+
// Execute the action
|
|
539
|
+
const result = await action.call(null, this.state, payload);
|
|
540
|
+
// Save state if persistence is enabled
|
|
541
|
+
this._saveState();
|
|
542
|
+
// Notify subscribers
|
|
543
|
+
this.subscribers.forEach((callback)=>{
|
|
544
|
+
try {
|
|
545
|
+
callback(mutation, this.state);
|
|
546
|
+
} catch (error) {
|
|
547
|
+
if (this.onError) {
|
|
548
|
+
this.onError(error, "Subscriber callback failed");
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
// Notify devtools
|
|
553
|
+
if (this.devTools && typeof window !== "undefined" && window.__ELEVA_DEVTOOLS__) {
|
|
554
|
+
window.__ELEVA_DEVTOOLS__.notifyMutation(mutation, this.state);
|
|
555
|
+
}
|
|
556
|
+
return result;
|
|
557
|
+
} catch (error) {
|
|
558
|
+
if (this.onError) {
|
|
559
|
+
this.onError(error, `Action dispatch failed: ${actionName}`);
|
|
560
|
+
}
|
|
561
|
+
throw error;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Gets an action by name (supports namespaced actions).
|
|
566
|
+
* Traverses the actions object using dot-notation path segments.
|
|
567
|
+
*
|
|
568
|
+
* @private
|
|
569
|
+
* @param {string} actionName - The action name, supports dot notation for namespaces (e.g., "auth.login").
|
|
570
|
+
* @returns {ActionFunction | null} The action function if found and is a function, null otherwise.
|
|
571
|
+
*/ _getAction(actionName) {
|
|
572
|
+
const parts = actionName.split(".");
|
|
573
|
+
let current = this.actions;
|
|
574
|
+
for (const part of parts){
|
|
575
|
+
if (current[part] === undefined) {
|
|
576
|
+
return null;
|
|
577
|
+
}
|
|
578
|
+
current = current[part];
|
|
579
|
+
}
|
|
580
|
+
return typeof current === "function" ? current : null;
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Subscribes to store mutations.
|
|
584
|
+
* Callback is invoked after every successful action dispatch.
|
|
585
|
+
*
|
|
586
|
+
* @param {SubscribeCallback} callback
|
|
587
|
+
* Called after each mutation with:
|
|
588
|
+
* - mutation.type: The action name that was dispatched
|
|
589
|
+
* - mutation.payload: The payload passed to the action
|
|
590
|
+
* - mutation.timestamp: When the mutation occurred (Date.now())
|
|
591
|
+
* - state: The current state object (contains Signals)
|
|
592
|
+
* @returns {() => void} Unsubscribe function. Call to stop receiving notifications.
|
|
593
|
+
* @throws {Error} If callback is not a function.
|
|
594
|
+
* @see dispatch - Triggers mutations that notify subscribers.
|
|
595
|
+
*/ subscribe(callback) {
|
|
596
|
+
if (typeof callback !== "function") {
|
|
597
|
+
throw new Error("Subscribe callback must be a function");
|
|
598
|
+
}
|
|
599
|
+
this.subscribers.add(callback);
|
|
600
|
+
// Return unsubscribe function
|
|
601
|
+
return ()=>{
|
|
602
|
+
this.subscribers.delete(callback);
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Gets current state values (not signals).
|
|
607
|
+
*
|
|
608
|
+
* @note When persistence include/exclude filters are configured,
|
|
609
|
+
* this returns only the filtered subset of state.
|
|
610
|
+
* @returns {Record<string, unknown>} The current state values (filtered by persistence config if set).
|
|
611
|
+
* @see replaceState - Set state values.
|
|
612
|
+
* @see subscribe - Listen for state changes.
|
|
613
|
+
*/ getState() {
|
|
614
|
+
return this._extractPersistedData();
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Replaces state values (useful for testing or state hydration).
|
|
618
|
+
*
|
|
619
|
+
* @note When persistence include/exclude filters are configured,
|
|
620
|
+
* this only updates the filtered subset of state.
|
|
621
|
+
* @param {Record<string, unknown>} newState - The new state object.
|
|
622
|
+
* @returns {void}
|
|
623
|
+
* @see getState - Get current state values.
|
|
624
|
+
*/ replaceState(newState) {
|
|
625
|
+
this._applyPersistedData(newState);
|
|
626
|
+
this._saveState();
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Clears persisted state from storage.
|
|
630
|
+
* Does nothing if persistence is disabled or running in SSR.
|
|
631
|
+
* @returns {void}
|
|
632
|
+
*/ clearPersistedState() {
|
|
633
|
+
if (!this.persistence.enabled || typeof window === "undefined") {
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
try {
|
|
637
|
+
const storage = window[this.persistence.storage];
|
|
638
|
+
storage.removeItem(this.persistence.key);
|
|
639
|
+
} catch (error) {
|
|
640
|
+
if (this.onError) {
|
|
641
|
+
this.onError(error, "Failed to clear persisted state");
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Registers a new namespaced module at runtime.
|
|
647
|
+
* Logs a warning if the namespace already exists.
|
|
648
|
+
* Module state is nested under `state[namespace]` and actions under `actions[namespace]`.
|
|
649
|
+
* @param {string} namespace - The namespace for the module.
|
|
650
|
+
* @param {StoreModule} module - The module definition.
|
|
651
|
+
* @param {Record<string, unknown>} module.state - The module's initial state.
|
|
652
|
+
* @param {Record<string, ActionFunction>} module.actions - The module's actions.
|
|
653
|
+
* @returns {void}
|
|
654
|
+
*/ registerModule(namespace, module) {
|
|
655
|
+
if (this.state[namespace] || this.actions[namespace]) {
|
|
656
|
+
console.warn(`[StorePlugin] Module "${namespace}" already exists`);
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
// Initialize the module
|
|
660
|
+
this.state[namespace] = {};
|
|
661
|
+
this.actions[namespace] = {};
|
|
662
|
+
const namespaces = {
|
|
663
|
+
[namespace]: module
|
|
664
|
+
};
|
|
665
|
+
this._initializeNamespaces(namespaces);
|
|
666
|
+
this._saveState();
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Unregisters a namespaced module.
|
|
670
|
+
* Logs a warning if the namespace doesn't exist.
|
|
671
|
+
* Removes both state and actions under the namespace.
|
|
672
|
+
* @param {string} namespace - The namespace to unregister.
|
|
673
|
+
* @returns {void}
|
|
674
|
+
*/ unregisterModule(namespace) {
|
|
675
|
+
if (!this.state[namespace] && !this.actions[namespace]) {
|
|
676
|
+
console.warn(`[StorePlugin] Module "${namespace}" does not exist`);
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
delete this.state[namespace];
|
|
680
|
+
delete this.actions[namespace];
|
|
681
|
+
this._saveState();
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* Creates a new reactive state property at runtime.
|
|
685
|
+
*
|
|
686
|
+
* @param {string} key - The state key.
|
|
687
|
+
* @param {*} initialValue - The initial value.
|
|
688
|
+
* @returns {Signal} The created signal, or existing signal if key exists.
|
|
689
|
+
*/ createState(key, initialValue) {
|
|
690
|
+
if (this.state[key]) {
|
|
691
|
+
return this.state[key]; // Return existing state
|
|
692
|
+
}
|
|
693
|
+
this.state[key] = new eleva.signal(initialValue);
|
|
694
|
+
this._saveState();
|
|
695
|
+
return this.state[key];
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Creates a new action at runtime.
|
|
699
|
+
* Overwrites existing action if name already exists.
|
|
700
|
+
* Supports dot-notation for namespaced actions (e.g., "auth.login").
|
|
701
|
+
* @param {string} name - The action name (supports dot notation for namespaces).
|
|
702
|
+
* @param {ActionFunction} actionFn - The action function (receives state and payload).
|
|
703
|
+
* @returns {void}
|
|
704
|
+
* @throws {Error} If actionFn is not a function.
|
|
705
|
+
* @example
|
|
706
|
+
* // Root-level action
|
|
707
|
+
* store.createAction("increment", (state) => state.count.value++);
|
|
708
|
+
*
|
|
709
|
+
* // Namespaced action
|
|
710
|
+
* store.createAction("auth.login", async (state, credentials) => {
|
|
711
|
+
* // ... login logic
|
|
712
|
+
* });
|
|
713
|
+
*/ createAction(name, actionFn) {
|
|
714
|
+
if (typeof actionFn !== "function") {
|
|
715
|
+
throw new Error("Action must be a function");
|
|
716
|
+
}
|
|
717
|
+
// Fast path: no dot means simple action (avoids array allocation)
|
|
718
|
+
if (name.indexOf(".") === -1) {
|
|
719
|
+
this.actions[name] = actionFn;
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
// Namespaced action, traverse/create nested structure
|
|
723
|
+
const parts = name.split(".");
|
|
724
|
+
const lastIndex = parts.length - 1;
|
|
725
|
+
let current = this.actions;
|
|
726
|
+
for(let i = 0; i < lastIndex; i++){
|
|
727
|
+
current = current[parts[i]] || (current[parts[i]] = {});
|
|
728
|
+
}
|
|
729
|
+
current[parts[lastIndex]] = actionFn;
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Creates a new Store instance.
|
|
733
|
+
* Initializes state signals, actions, persistence, and devtools integration.
|
|
734
|
+
*
|
|
735
|
+
* @constructor
|
|
736
|
+
*/ constructor(){
|
|
737
|
+
/** @type {Record<string, Signal | Record<string, unknown>>} */ this.state = {};
|
|
738
|
+
/** @type {Record<string, ActionFunction | Record<string, ActionFunction>>} */ this.actions = {};
|
|
739
|
+
/** @type {Set<SubscribeCallback>} */ this.subscribers = new Set();
|
|
740
|
+
/** @type {StoreMutation[]} */ this.mutations = [];
|
|
741
|
+
/** @type {{enabled: boolean, key: string, storage: string, include: string[]|null, exclude: string[]|null}} */ this.persistence = {
|
|
742
|
+
enabled: false,
|
|
743
|
+
key: "eleva-store",
|
|
744
|
+
storage: "localStorage",
|
|
745
|
+
include: null,
|
|
746
|
+
exclude: null,
|
|
747
|
+
...persistence
|
|
748
|
+
};
|
|
749
|
+
/** @type {boolean} */ this.devTools = devTools;
|
|
750
|
+
/** @type {((error: Error, context: string) => void)|null} */ this.onError = onError;
|
|
751
|
+
this._initializeState(state, actions);
|
|
752
|
+
this._initializeNamespaces(namespaces);
|
|
753
|
+
this._loadPersistedState();
|
|
754
|
+
this._setupDevTools();
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
// Create the store instance
|
|
758
|
+
const store = new Store();
|
|
759
|
+
// Store the original mount method to override it
|
|
760
|
+
const originalMount = eleva.mount;
|
|
761
|
+
/**
|
|
762
|
+
* Overridden mount method that injects store context into components.
|
|
763
|
+
* Wraps the original mount to add `ctx.store` to the component's setup context.
|
|
764
|
+
*
|
|
765
|
+
* @param {HTMLElement} container - The DOM element where the component will be mounted.
|
|
766
|
+
* @param {string | ComponentDefinition} compName - Component name or definition.
|
|
767
|
+
* @param {ComponentProps} [props={}] - Optional properties to pass to the component.
|
|
768
|
+
* @returns {Promise<MountResult>} The mount result.
|
|
769
|
+
*/ eleva.mount = async (container, compName, props = {})=>{
|
|
770
|
+
// Get the component definition
|
|
771
|
+
const componentDef = typeof compName === "string" ? eleva._components.get(compName) || compName : compName;
|
|
772
|
+
if (!componentDef) {
|
|
773
|
+
return await originalMount.call(eleva, container, compName, props);
|
|
774
|
+
}
|
|
775
|
+
// Create a wrapped component that injects store into setup
|
|
776
|
+
const wrappedComponent = {
|
|
777
|
+
...componentDef,
|
|
778
|
+
async setup (ctx) {
|
|
779
|
+
/** @type {StoreApi} */ ctx.store = {
|
|
780
|
+
// Core store functionality
|
|
781
|
+
state: store.state,
|
|
782
|
+
dispatch: store.dispatch.bind(store),
|
|
783
|
+
subscribe: store.subscribe.bind(store),
|
|
784
|
+
getState: store.getState.bind(store),
|
|
785
|
+
// Module management
|
|
786
|
+
registerModule: store.registerModule.bind(store),
|
|
787
|
+
unregisterModule: store.unregisterModule.bind(store),
|
|
788
|
+
// Utilities for dynamic state/action creation
|
|
789
|
+
createState: store.createState.bind(store),
|
|
790
|
+
createAction: store.createAction.bind(store),
|
|
791
|
+
// Access to signal constructor for manual state creation
|
|
792
|
+
signal: eleva.signal
|
|
793
|
+
};
|
|
794
|
+
// Call original setup if it exists
|
|
795
|
+
const originalSetup = componentDef.setup;
|
|
796
|
+
const result = originalSetup ? await originalSetup(ctx) : {};
|
|
797
|
+
return result;
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
// Call original mount with wrapped component
|
|
801
|
+
return await originalMount.call(eleva, container, wrappedComponent, props);
|
|
802
|
+
};
|
|
803
|
+
// Override _mountComponents to ensure child components also get store context
|
|
804
|
+
const originalMountComponents = eleva._mountComponents;
|
|
805
|
+
/**
|
|
806
|
+
* Overridden _mountComponents method that injects store context into child components.
|
|
807
|
+
* Wraps each child component's setup function to add `ctx.store` before mounting.
|
|
808
|
+
*
|
|
809
|
+
* @param {HTMLElement} container - The parent container element.
|
|
810
|
+
* @param {ChildrenMap} children - Map of selectors to component definitions.
|
|
811
|
+
* @param {MountResult[]} childInstances - Array to store mounted instances.
|
|
812
|
+
* @param {ComponentContext & SetupResult} context - Parent component context.
|
|
813
|
+
* @returns {Promise<void>}
|
|
814
|
+
*/ eleva._mountComponents = async (container, children, childInstances, context)=>{
|
|
815
|
+
// Create wrapped children with store injection
|
|
816
|
+
const wrappedChildren = {};
|
|
817
|
+
for (const [selector, childComponent] of Object.entries(children)){
|
|
818
|
+
const componentDef = typeof childComponent === "string" ? eleva._components.get(childComponent) || childComponent : childComponent;
|
|
819
|
+
if (componentDef && typeof componentDef === "object") {
|
|
820
|
+
wrappedChildren[selector] = {
|
|
821
|
+
...componentDef,
|
|
822
|
+
async setup (ctx) {
|
|
823
|
+
/** @type {StoreApi} */ ctx.store = {
|
|
824
|
+
// Core store functionality
|
|
825
|
+
state: store.state,
|
|
826
|
+
dispatch: store.dispatch.bind(store),
|
|
827
|
+
subscribe: store.subscribe.bind(store),
|
|
828
|
+
getState: store.getState.bind(store),
|
|
829
|
+
// Module management
|
|
830
|
+
registerModule: store.registerModule.bind(store),
|
|
831
|
+
unregisterModule: store.unregisterModule.bind(store),
|
|
832
|
+
// Utilities for dynamic state/action creation
|
|
833
|
+
createState: store.createState.bind(store),
|
|
834
|
+
createAction: store.createAction.bind(store),
|
|
835
|
+
// Access to signal constructor for manual state creation
|
|
836
|
+
signal: eleva.signal
|
|
837
|
+
};
|
|
838
|
+
// Call original setup if it exists
|
|
839
|
+
const originalSetup = componentDef.setup;
|
|
840
|
+
const result = originalSetup ? await originalSetup(ctx) : {};
|
|
841
|
+
return result;
|
|
842
|
+
}
|
|
843
|
+
};
|
|
844
|
+
} else {
|
|
845
|
+
wrappedChildren[selector] = childComponent;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
// Call original _mountComponents with wrapped children
|
|
849
|
+
return await originalMountComponents.call(eleva, container, wrappedChildren, childInstances, context);
|
|
850
|
+
};
|
|
851
|
+
// Expose store instance and utilities on the Eleva instance
|
|
852
|
+
/** @type {StoreApi} */ eleva.store = store;
|
|
853
|
+
/**
|
|
854
|
+
* Expose utility methods on the Eleva instance.
|
|
855
|
+
* These are top-level helpers (e.g., `eleva.dispatch`) in addition to `eleva.store`.
|
|
856
|
+
*/ /** @type {(name: string, actionFn: ActionFunction) => void} */ eleva.createAction = (name, actionFn)=>{
|
|
857
|
+
store.createAction(name, actionFn);
|
|
858
|
+
};
|
|
859
|
+
/** @type {DispatchFunction} */ eleva.dispatch = (actionName, payload)=>{
|
|
860
|
+
return store.dispatch(actionName, payload);
|
|
861
|
+
};
|
|
862
|
+
/** @type {() => Record<string, unknown>} */ eleva.getState = ()=>{
|
|
863
|
+
return store.getState();
|
|
864
|
+
};
|
|
865
|
+
/** @type {(callback: SubscribeCallback) => () => void} */ eleva.subscribe = (callback)=>{
|
|
866
|
+
return store.subscribe(callback);
|
|
867
|
+
};
|
|
868
|
+
// Store original methods for cleanup
|
|
869
|
+
eleva._originalMount = originalMount;
|
|
870
|
+
eleva._originalMountComponents = originalMountComponents;
|
|
871
|
+
},
|
|
872
|
+
/**
|
|
873
|
+
* Uninstalls the plugin from the Eleva instance.
|
|
874
|
+
*
|
|
875
|
+
* @public
|
|
876
|
+
* @param {Eleva} eleva - The Eleva instance.
|
|
877
|
+
* @returns {void}
|
|
878
|
+
* @description
|
|
879
|
+
* Restores the original Eleva methods and removes all plugin-specific
|
|
880
|
+
* functionality. This method should be called when the plugin is no
|
|
881
|
+
* longer needed.
|
|
882
|
+
* Also removes `eleva.store` and top-level helpers (`eleva.dispatch`,
|
|
883
|
+
* `eleva.getState`, `eleva.subscribe`, `eleva.createAction`).
|
|
884
|
+
*
|
|
885
|
+
* @example
|
|
886
|
+
* // Uninstall the plugin
|
|
887
|
+
* StorePlugin.uninstall(app);
|
|
888
|
+
*/ uninstall (eleva) {
|
|
889
|
+
// Restore original mount method
|
|
890
|
+
if (eleva._originalMount) {
|
|
891
|
+
eleva.mount = eleva._originalMount;
|
|
892
|
+
delete eleva._originalMount;
|
|
893
|
+
}
|
|
894
|
+
// Restore original _mountComponents method
|
|
895
|
+
if (eleva._originalMountComponents) {
|
|
896
|
+
eleva._mountComponents = eleva._originalMountComponents;
|
|
897
|
+
delete eleva._originalMountComponents;
|
|
898
|
+
}
|
|
899
|
+
// Remove store instance and utility methods
|
|
900
|
+
if (eleva.store) {
|
|
901
|
+
delete eleva.store;
|
|
902
|
+
}
|
|
903
|
+
if (eleva.createAction) {
|
|
904
|
+
delete eleva.createAction;
|
|
905
|
+
}
|
|
906
|
+
if (eleva.dispatch) {
|
|
907
|
+
delete eleva.dispatch;
|
|
908
|
+
}
|
|
909
|
+
if (eleva.getState) {
|
|
910
|
+
delete eleva.getState;
|
|
911
|
+
}
|
|
912
|
+
if (eleva.subscribe) {
|
|
913
|
+
delete eleva.subscribe;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
};
|
|
917
|
+
|
|
918
|
+
exports.Store = StorePlugin;
|
|
919
|
+
exports.StorePlugin = StorePlugin;
|
|
920
|
+
//# sourceMappingURL=store.cjs.map
|