/** * Le type `TProxyState` est utilisé lorsque l'état contient un objet ou un tableau, * permettant d'accéder à l'objet ou au tableau directement via un proxy. * Le proxy gère l'accès aux propriétés de l'objet ou du tableau, ainsi qu'à celles du state. * Si la clé n'est pas présente dans l'objet ou le tableau, le proxy vérifie si elle existe dans le state. * Si la clé n'est ni dans l'objet, ni dans le state, le proxy renvoie `undefined`. */ export type TProxyState = Partial>>; /** * Le type `TState` est une combinaison de l'interface `IState` et un type générique `T`. * Il est utilisé pour définir la structure complète d'un état. * ## Example * ```typescript * const userState: TState<{ name: string; age: number }> = { * id: 'user123', * value: { name: 'John', age: 30 }, * mutator: [userState, (newValue) => newValue], * subscribe: (ref, callback) => 'subscription123', * unsubscribe: (id) => {}, * delete: () => {}, * }; * ``` */ export type TState = IState & T; /** * Le type `State` représente un état, qui peut être soit un `TState` complet, soit un `TProxyState` partiel. * Il permet de gérer des états complets ou partiels de manière uniforme. */ export type State = TState | TProxyState; /** * Le type `StateMutator` est un tableau qui représente un mutateur d'état. * Il contient deux éléments : * - Le premier élément est de type `State` et renvoie la valeur actuelle de l'état. * - Le deuxième élément est une fonction qui prend une nouvelle valeur de type `T` en paramètre * et renvoie la nouvelle valeur de l'état, tout en notifiant les abonnés du changement. */ export type StateMutator = [ State ,(value:T) => T | Promise | void | Promise ]; /** * Le type `MutationListernerId` est une chaîne de caractères formatée de manière unique * pour représenter l'identifiant d'un gestionnaire de mutations. * ## Example * ```typescript * const subscriptionId: MutationListernerId = 'xxxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx'; * ``` */ export type MutationListernerId = `${string}-${string}-${string}-${string}-${string}`; /** * L'interface `IState` définit la structure d'un objet d'état utilisé dans la bibliothèque de gestion d'états. * Elle comporte les propriétés et méthodes suivantes : */ export interface IState{ /** * Un identifiant unique pour l'objet d'état. * Il est utilisé pour distinguer et accéder à des objets d'état spécifiques au sein d'une collection d'états. */ id:string; /** * Représente la valeur actuelle de l'état. * Le type `T` est un paramètre de type générique qui peut être de n'importe quel type spécifié. */ value:T; /** * Obtient le mutateur de l'état. * Un mutateur est une fonction qui permet de modifier l'état tout en notifiant les abonnés du changement. * Le mutateur renvoie un tuple avec deux éléments : * - Le premier élément du tuple renvoie la valeur actuelle de l'état, permettant un accès direct à la valeur. * Si la valeur contenue dans l'état est un objet ou un tableau, un proxy est utilisé pour gérer l'accès aux propriétés. * Le proxy permet d'accéder à l'objet ou au tableau tout en maintenant un lien vers l'état lui-même. * Si la valeur est une primitive, elle est directement renvoyée en tant que valeur de l'état. * - Le deuxième élément du tuple renvoie une fonction de mise à jour de l'état. Cette fonction prend une nouvelle valeur * en paramètre et met à jour la valeur de l'état, tout en notifiant les abonnés du changement. * @returns Un tuple contenant la valeur actuelle de l'état et la fonction de mise à jour. * ## Example * ```typescript * const [countState,setCountState] = useState(42); * const mutator: StateMutator = countState.mutator; * const currentValue = mutator[0]; * const updateFunction = mutator[1]; * ``` */ mutator:StateMutator; /** * S'abonner aux changements d'état. * @param ref - Une référence à un élément associé à l'abonnement. * @param callback - Une fonction de rappel appelée lorsque l'état change. * @returns Un identifiant unique pour le gestionnaire de mutations. * ## Example * ```typescript * const [countState,setCountState] = useState(42); * const subscriptionId = countState.subscribe(document.body, (value) => { * console.log('State changed:', value); * }); * ``` */ subscribe:( ref:HTMLElement , callback:( value:T ) => T | Promise | void | Promise ) => MutationListernerId | null; /** * Se désabonner des changements d'état. * @param id - Un identifiant unique pour le gestionnaire de mutations à supprimer. * ## Example * ```typescript * const [countState,setCountState] = useState(42); * const subscriptionId = countState.subscribe(document.body, (value) => { * console.log('State changed:', value); * }); * // Plus tard, pour se désabonner : * countState.unsubscribe(subscriptionId); * ``` */ unsubscribe:( id:MutationListernerId ) => void; /** * Supprimer l'objet d'état. * Supprime l'objet d'état de la collection d'états. * * Exemple : * ```typescript * let [state , setState] = useState('Hello World'); * ... // autres opérations * state.delete(); * ``` */ delete:() => void; } /** The `State` class is a TypeScript class that allows for managing and subscribing to state changes. */ export class _State implements IState{ #_id = crypto.randomUUID(); get id(){ return this.#_id }; /** * `mutation_callback_stack` est une propriété privée de la classe `State` qui stocke les écouteurs de mutation. * Chaque entrée dans la carte associe un identifiant unique (de type `string`) à un objet contenant `target` et `callback`. * - `target`: Une référence à l'élément HTML qui s'abonne aux changements d'état. * - `callback`: La fonction de rappel appelée lorsqu'un changement d'état se produit. */ mutation_callback_stack:Map void;` is a parameter in the `subscribe` method of the `IState` interface and `State` class. It represents a callback function that will be called when the state changes. */ callback:(value:T) => void; }> = new Map(); /** * `#_value:T;` est une propriété privée de la classe `State`. Elle représente la valeur actuelle de l'état. * Le type `T` est un paramètre générique qui peut être de n'importe quel type spécifié lors de l'utilisation de la classe `State`. */ #_value:T; /** * Obtient la valeur de l'état. */ get value(){return this.#_value;} /** * Renvoie la valeur de l'état. */ valueOf():T { return this.value; } /** * Renvoie la valeur de l'état. */ [Symbol.toPrimitive]():T { return this.valueOf(); } constructor(value){ this.#_value = value; } /** * `#_update` est une méthode privée de la classe `State`. Elle permet de mettre à jour la valeur de l'état * et de renvoyer la valeur mise à jour. */ #_update = (value) => { this.#_value = value; return this.#_value; } /** * La méthode privée `#_mutationCallback` est responsable de parcourir la pile d'écouteurs de mutation * et d'appeler les fonctions de rappel associées à chaque écouteur de mutation. */ mutationCallback = (value:T) => { Array.from( [...this.mutation_callback_stack.keys()] , async (key) => { const option = this.mutation_callback_stack.get(key); if((option as any).target.ownerDocument && (option as any).target.ownerDocument.contains((option as any).target))(option as any).callback(value); else this.mutation_callback_stack.delete(key); } ) } get mutator():StateMutator{ return [ (typeof this.value == 'object' || Array.isArray(this.value) ? new Proxy( this as State , { get : ( target:State , key ) => { if( target.valueOf() && target.valueOf()[key] )return target.valueOf()[key]; else if( target[key] )return target[key]; else return undefined; } } ) as TProxyState : this as IState as TState), (value) => { this.mutationCallback(value); return this.#_update(value); } ] } subscribe(referenceElement:HTMLElement , callback:(value:T) => void):MutationListernerId|null { if(typeof callback != 'function')return null; const mutationListerId = crypto.randomUUID() as MutationListernerId; this.mutation_callback_stack.set(mutationListerId , { get target(){return referenceElement}, callback }); return mutationListerId; } unsubscribe(mutationListerId:string) { return ( this.mutation_callback_stack.has(mutationListerId) ? this.mutation_callback_stack.delete(mutationListerId) : null ) } delete(){ console.log('delete state' , this); return States.delete( this.id ) } } class States{ /** * `#_states` est une structure de données interne de type `Map` qui stocke les états. * Chaque état est associé à un identifiant unique, soit sous forme de nombre, soit sous forme de chaîne. * Ces identifiants permettent d'accéder facilement à un état spécifique. */ static #_states:Map> = new Map(); /** * La méthode statique `list` renvoie un tableau d'objets représentant les états actuellement enregistrés. * Chaque objet contient une propriété `value`, qui correspond à la valeur de l'état. * Cette méthode facilite l'inspection de l'ensemble des états gérés. * ## Exemple : * ```typescript * const allStates = States.list; * console.log(allStates); * ``` */ static get list(){ console.log({states : States.#_states}) return Array.from([...States.#_states.values()] as _State[] , (state , iterator) => { console.log(state.value) return { value : state.value } }); } /** * La méthode statique `set` permet d'ajouter un nouvel état à la collection d'états gérée par `States`. * Elle prend en paramètre une valeur initiale pour cet état. En retour, elle fournit un mutateur associé * à ce nouvel état, permettant ainsi la modification de l'état et la notification des abonnés aux changements. * * Exemple : * ```typescript * const stateMutator = States.set('Initial Value'); * console.log(stateMutator); * ``` */ static set(value){ let state = new _State(value); States.#_states.set( state.id , state ); // let iterator = this.#_states.size - 1; return (this.#_states as any).get(state.id).mutator; } /** * La méthode statique `get` permet de récupérer un état spécifique en fonction de son identifiant. * Cela facilite l'accès à un état particulier pour effectuer des opérations sur celui-ci. * Elle renvoie l'état associé à l'identifiant spécifié, ou `undefined` si aucun état correspondant n'est trouvé. * ## Exemple : * ```typescript * const specificState = States.get('your_state_id'); * console.log(specificState?.value); * ``` */ static get( stateId:string ){return States.#_states.get( stateId ) } /** * La méthode statique `delete` permet de supprimer un état spécifique en fonction de son identifiant. * Si un état doit être retiré de la collection, cette méthode offre une manière de le faire. * Elle renvoie `true` si l'état est supprimé avec succès, sinon `false`. * ## Exemple : * ```typescript * const deleted = States.delete('your_state_id'); * console.log('State deleted:', deleted); * ``` */ static delete( stateId:IState['id'] ){ console.log( this.#_states.has( stateId ) ) return ( this.#_states.has( stateId ) ? this.#_states.delete( stateId ) : false ) } } /** * La fonction `listStates` affiche la liste des états dans la console sous forme de tableau. */ export const listStates = () => { console.table(States.list); } /** * La fonction `getState` renvoie un état en utilisant son identifiant. * @param stateId - L'identifiant de l'état à récupérer. * @returns L'état correspondant à l'identifiant spécifié. * ## Exemple : * ```typescript * const retrievedState = getState(counterState[0].id); * console.log(retrievedState?.value); * ``` */ export const getState = ( stateId:string ) => { return States.get( stateId ); } /** /** * La fonction `useState` renvoie un mutateur d'état pour un argument donné. * @param {T} arg - Le paramètre `arg` est de type `T`, ce qui signifie qu'il peut être de n'importe quel type spécifié lors * de l'appel de la fonction `useState`. Il représente la valeur initiale de l'état. * @returns Un objet StateMutator. * ## Exemple : * ```typescript * let [state , setState] = useState('Hello World'); * ``` */ export function useState(arg:T):[ TState ,(value:T) => T]{ return States.set(arg); }