214 lines
5.7 KiB
JavaScript
214 lines
5.7 KiB
JavaScript
// src/core/state.js
|
|
|
|
const globalState = new Map();
|
|
|
|
/**
|
|
* Normale State-Erstellung (nicht persistent)
|
|
*/
|
|
export function createState(key, initialValue) {
|
|
if (!globalState.has(key)) globalState.set(key, initialValue);
|
|
|
|
return {
|
|
get() {
|
|
return globalState.get(key);
|
|
},
|
|
set(value) {
|
|
globalState.set(key, value);
|
|
dispatchEvent(new CustomEvent('statechange', {
|
|
detail: { key, value }
|
|
}));
|
|
},
|
|
subscribe(callback) {
|
|
const handler = (event) => {
|
|
if (event.detail.key === key) {
|
|
callback(event.detail.value);
|
|
}
|
|
};
|
|
addEventListener('statechange', handler);
|
|
return () => removeEventListener('statechange', handler);
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Persistent State via localStorage
|
|
*/
|
|
export function createPersistentState(key, defaultValue) {
|
|
const stored = localStorage.getItem(`state:${key}`);
|
|
const initial = stored !== null ? JSON.parse(stored) : defaultValue;
|
|
|
|
const state = createState(key, initial);
|
|
|
|
state.subscribe((val) => {
|
|
localStorage.setItem(`state:${key}`, JSON.stringify(val));
|
|
});
|
|
|
|
return state;
|
|
}
|
|
|
|
/**
|
|
* Zugriff auf alle States (intern)
|
|
*/
|
|
export function getRawState() {
|
|
return globalState;
|
|
}
|
|
|
|
/**
|
|
* Elementbindung: Text, Attribut, Klasse oder Custom-Callback
|
|
*/
|
|
export function bindStateToElement({ state, element, property = 'text', attributeName = null }) {
|
|
const apply = (value) => {
|
|
if (property === 'text') {
|
|
element.textContent = value;
|
|
} else if (property === 'attr' && attributeName) {
|
|
element.setAttribute(attributeName, value);
|
|
} else if (property === 'class') {
|
|
element.className = value;
|
|
} else if (typeof property === 'function') {
|
|
property(element, value);
|
|
}
|
|
};
|
|
|
|
apply(state.get());
|
|
return state.subscribe(apply);
|
|
}
|
|
|
|
/**
|
|
* Mehrere Elemente gleichzeitig binden
|
|
*/
|
|
export function bindStateToElements({ state, elements, ...rest }) {
|
|
return elements.map(el => bindStateToElement({ state, element: el, ...rest }));
|
|
}
|
|
|
|
/**
|
|
* Declarative Auto-Bindings via data-bind Attribute
|
|
*/
|
|
export function initStateBindings() {
|
|
document.querySelectorAll('[data-bind]').forEach(el => {
|
|
const key = el.getAttribute('data-bind');
|
|
const state = createState(key, '');
|
|
bindStateToElement({ state, element: el, property: 'text' });
|
|
});
|
|
|
|
document.querySelectorAll('[data-bind-attr]').forEach(el => {
|
|
const key = el.getAttribute('data-bind-attr');
|
|
const attr = el.getAttribute('data-bind-attr-name') || 'value';
|
|
const state = createState(key, '');
|
|
bindStateToElement({ state, element: el, property: 'attr', attributeName: attr });
|
|
});
|
|
|
|
document.querySelectorAll('[data-bind-class]').forEach(el => {
|
|
const key = el.getAttribute('data-bind-class');
|
|
const state = createState(key, '');
|
|
bindStateToElement({ state, element: el, property: 'class' });
|
|
});
|
|
|
|
document.querySelectorAll('[data-bind-input]').forEach(el => {
|
|
const key = el.getAttribute('data-bind-input');
|
|
const persistent = el.hasAttribute('data-persistent');
|
|
const state = persistent ? createPersistentState(key, '') : createState(key, '');
|
|
|
|
bindInputToState(el, state);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Zwei-Wege-Bindung für Formulareingaben
|
|
*/
|
|
export function bindInputToState(inputEl, state) {
|
|
// Initialwert setzen
|
|
if (inputEl.type === 'checkbox') {
|
|
inputEl.checked = !!state.get();
|
|
} else {
|
|
inputEl.value = state.get();
|
|
}
|
|
|
|
// DOM → State
|
|
inputEl.addEventListener('input', () => {
|
|
if (inputEl.type === 'checkbox') {
|
|
state.set(inputEl.checked);
|
|
} else {
|
|
state.set(inputEl.value);
|
|
}
|
|
});
|
|
|
|
// State → DOM
|
|
return state.subscribe((val) => {
|
|
if (inputEl.type === 'checkbox') {
|
|
inputEl.checked = !!val;
|
|
} else {
|
|
inputEl.value = val;
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* Berechneter Zustand auf Basis anderer States
|
|
*/
|
|
export function createComputedState(dependencies, computeFn) {
|
|
let value = computeFn(...dependencies.map(d => d.get()));
|
|
const key = `computed:${Math.random().toString(36).slice(2)}`;
|
|
const state = createState(key, value);
|
|
|
|
dependencies.forEach(dep => {
|
|
dep.subscribe(() => {
|
|
const newValue = computeFn(...dependencies.map(d => d.get()));
|
|
state.set(newValue);
|
|
});
|
|
});
|
|
|
|
return state;
|
|
}
|
|
|
|
/**
|
|
* Aktiviert Undo/Redo für einen State
|
|
*/
|
|
export function enableUndoRedoForState(state) {
|
|
const history = [];
|
|
let index = -1;
|
|
let lock = false;
|
|
|
|
const record = (val) => {
|
|
if (lock) return;
|
|
history.splice(index + 1); // zukünftige States verwerfen
|
|
history.push(val);
|
|
index++;
|
|
};
|
|
|
|
record(state.get());
|
|
|
|
const unsubscribe = state.subscribe((val) => {
|
|
if (!lock) record(val);
|
|
});
|
|
|
|
return {
|
|
undo() {
|
|
if (index > 0) {
|
|
lock = true;
|
|
index--;
|
|
state.set(history[index]);
|
|
lock = false;
|
|
}
|
|
},
|
|
redo() {
|
|
if (index < history.length - 1) {
|
|
lock = true;
|
|
index++;
|
|
state.set(history[index]);
|
|
lock = false;
|
|
}
|
|
},
|
|
destroy: unsubscribe
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Dev-Konsole zur Nachverfolgung von State-Änderungen
|
|
*/
|
|
export function enableStateLogging(state, label = 'state') {
|
|
state.subscribe((val) => {
|
|
console.debug(`[State Change: ${label}]`, val);
|
|
});
|
|
}
|