Initial Commit

This commit is contained in:
2026-02-12 17:10:03 -06:00
commit 6992a06105
135 changed files with 18014 additions and 0 deletions
+132
View File
@@ -0,0 +1,132 @@
// 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;