133 lines
4.0 KiB
JavaScript
133 lines
4.0 KiB
JavaScript
// saladui/core/state-machine.js
|
|
/**
|
|
* StateMachine class for SaladUI framework
|
|
* Handles state transitions, event processing, and state-specific behavior
|
|
*/
|
|
class StateMachine {
|
|
/**
|
|
* Create a state machine
|
|
*
|
|
* @param {Object} stateConfig - Configuration object defining states and transitions
|
|
* @param {string} initialState - The initial state to start in
|
|
* @param {Object} options - Optional configuration options. Currently supports:
|
|
* - onStateChanged: A callback function to be called when the state changes
|
|
*/
|
|
constructor(stateConfig, initialState, options) {
|
|
this.stateConfig = stateConfig;
|
|
this.state = initialState || "idle";
|
|
this.previousState = null;
|
|
this.options = options || {};
|
|
}
|
|
|
|
/**
|
|
* Trigger a transition based on an event
|
|
*
|
|
* @param {string} event - The event triggering the transition
|
|
* @param {Object} params - Parameters to pass to the handlers
|
|
* @returns {boolean} Whether the transition was successful
|
|
*/
|
|
transition(event, params = {}) {
|
|
const currentStateConfig = this.stateConfig[this.state];
|
|
if (!currentStateConfig) return false;
|
|
|
|
const transition = currentStateConfig.transitions?.[event];
|
|
if (!transition) return false;
|
|
|
|
const nextState = this.determineNextState(transition, params);
|
|
if (!nextState) return false;
|
|
|
|
const prevState = this.state;
|
|
|
|
this.executeTransition(prevState, nextState, params);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Determine the next state based on the transition definition
|
|
*
|
|
* @param {string|Function|Object} transition - Transition definition
|
|
* @param {Object} params - Parameters to help determine the next state
|
|
* @returns {string|null} The next state or null if not determinable
|
|
*/
|
|
determineNextState(transition, params) {
|
|
if (typeof transition === "string") {
|
|
return transition;
|
|
} else if (typeof transition === "function") {
|
|
return transition(params);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Execute a transition between states, with optional animation
|
|
*
|
|
* @param {string} prevState - The state we're coming from
|
|
* @param {string} nextState - The state we're going to
|
|
* @param {Object} params - Parameters to pass to handlers
|
|
*/
|
|
executeTransition(prevState, nextState, params = {}) {
|
|
// Execute exit handlers
|
|
this.executeStateHandler(prevState, "exit", params);
|
|
|
|
// Update state
|
|
this.previousState = prevState;
|
|
this.state = nextState;
|
|
|
|
let callbackResult;
|
|
// Execute state change hook
|
|
if (typeof this.options.onStateChanged === "function") {
|
|
callbackResult = this.options.onStateChanged(
|
|
prevState,
|
|
nextState,
|
|
params,
|
|
);
|
|
}
|
|
|
|
if (callbackResult && typeof callbackResult.then === "function") {
|
|
// If it returns a promise, wait for completion before executing enter handler
|
|
callbackResult
|
|
.then(() => {
|
|
this.executeStateHandler(nextState, "enter", params);
|
|
})
|
|
.catch((error) => {
|
|
console.error("Animation promise rejected:", error);
|
|
// Still execute enter handler even if animation fails
|
|
this.executeStateHandler(nextState, "enter", params);
|
|
});
|
|
} else {
|
|
// If it doesn't return a promise, execute enter handler immediately
|
|
this.executeStateHandler(nextState, "enter", params);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Execute a state handler (enter or exit)
|
|
*
|
|
* @param {string} stateName - The state whose handler to execute
|
|
* @param {string} handlerType - 'enter' or 'exit'
|
|
* @param {Object} params - Parameters to pass to the handler
|
|
*/
|
|
executeStateHandler(stateName, handlerType, params) {
|
|
const stateConfig = this.stateConfig[stateName];
|
|
if (!stateConfig) return;
|
|
|
|
const handler = stateConfig[handlerType];
|
|
|
|
if (typeof handler === "function") {
|
|
handler(params);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the state has changed since last transition
|
|
*
|
|
* @returns {boolean} Whether the state has changed
|
|
*/
|
|
hasStateChanged() {
|
|
return this.state !== this.previousState;
|
|
}
|
|
}
|
|
|
|
export default StateMachine;
|