frontend-hamroun
Version:
A lightweight frontend JavaScript framework with React-like syntax
1,258 lines (1,241 loc) • 40.7 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
function jsx(type, props) {
console.log('JSX Transform:', { type, props });
const processedProps = { ...props };
// Handle children properly
if (arguments.length > 2) {
processedProps.children = Array.prototype.slice.call(arguments, 2);
}
return { type, props: processedProps };
}
const Fragment = ({ children }) => children;
async function createElement(vnode) {
console.log('Creating element from:', vnode);
// Handle primitives and null
if (vnode == null) {
return document.createTextNode('');
}
if (typeof vnode === 'boolean') {
return document.createTextNode('');
}
if (typeof vnode === 'number' || typeof vnode === 'string') {
return document.createTextNode(String(vnode));
}
// Handle arrays
if (Array.isArray(vnode)) {
const fragment = document.createDocumentFragment();
for (const child of vnode) {
const node = await createElement(child);
fragment.appendChild(node);
}
return fragment;
}
// Handle VNode
if ('type' in vnode && vnode.props !== undefined) {
const { type, props } = vnode;
// Handle function components
if (typeof type === 'function') {
try {
const result = await type(props || {});
const node = await createElement(result);
if (node instanceof Element) {
node.setAttribute('data-component-id', type.name || type.toString());
}
return node;
}
catch (error) {
console.error('Error rendering component:', error);
return document.createTextNode('');
}
}
// Create DOM element
const element = document.createElement(type);
// Handle props
for (const [key, value] of Object.entries(props || {})) {
if (key === 'children')
continue;
if (key.startsWith('on') && typeof value === 'function') {
const eventName = key.toLowerCase().slice(2);
// Remove existing event listener if any
const existingHandler = element.__events?.[eventName];
if (existingHandler) {
element.removeEventListener(eventName, existingHandler);
}
// Add new event listener
element.addEventListener(eventName, value);
if (!element.__events) {
element.__events = {};
}
element.__events[eventName] = value;
}
else if (key === 'style' && typeof value === 'object') {
Object.assign(element.style, value);
}
else if (key === 'className') {
element.setAttribute('class', String(value));
}
else if (key !== 'key' && key !== 'ref') {
element.setAttribute(key, String(value));
}
}
// Handle children
const children = props?.children;
if (children != null) {
const childArray = Array.isArray(children) ? children.flat() : [children];
for (const child of childArray) {
const childNode = await createElement(child);
element.appendChild(childNode);
}
}
return element;
}
// Handle other objects by converting to string
return document.createTextNode(String(vnode));
}
let isBatching = false;
const queue = [];
function batchUpdates(fn) {
if (isBatching) {
queue.push(fn);
return;
}
isBatching = true;
try {
fn();
while (queue.length > 0) {
const nextFn = queue.shift();
nextFn?.();
}
}
finally {
isBatching = false;
}
}
const contexts = new Map();
function createContext(defaultValue) {
const contextId = Symbol('context');
const Provider = ({ value, children }) => {
contexts.set(contextId, value);
return children;
};
const Consumer = ({ children }) => {
const value = contexts.get(contextId) ?? defaultValue;
return children(value);
};
const context = {
Provider,
Consumer,
displayName: 'Context'
};
return context;
}
function useContext(context) {
// In a real implementation, this would access the context value from the component tree
// For now, returning a default value to satisfy TypeScript
return {};
}
// Current render ID counter
let currentRender = 0;
let isSSR = false;
// State storage
const states = new Map();
const stateIndices = new Map();
const effects = new Map();
const memos = new Map();
const refs = new Map();
// Rendering callbacks
let globalRenderCallback = null;
let globalContainer = null;
let currentElement = null;
function setRenderCallback(callback, element, container) {
globalRenderCallback = callback;
globalContainer = container;
currentElement = element;
}
function prepareRender(component = null, isServerSideRender = false) {
currentRender++;
isSSR = isServerSideRender;
stateIndices.set(currentRender, 0);
// Initialize state array for this render if it doesn't exist
if (!states.has(currentRender)) {
states.set(currentRender, []);
}
return currentRender;
}
function finishRender() {
// Don't reset currentRender to 0 immediately to preserve state
// currentRender = 0;
}
function useState(initial) {
if (!currentRender) {
throw new Error("useState must be called within a render");
}
if (!states.has(currentRender)) {
states.set(currentRender, []);
}
const componentStates = states.get(currentRender);
const index = stateIndices.get(currentRender) || 0;
if (index >= componentStates.length) {
componentStates.push(initial);
}
const state = componentStates[index];
const setState = (newValue) => {
// For SSR, don't actually update state
if (isSSR) {
return;
}
const nextValue = typeof newValue === 'function'
? newValue(componentStates[index])
: newValue;
if (componentStates[index] === nextValue)
return;
componentStates[index] = nextValue;
if (isBatching) {
batchUpdates(() => rerender(currentRender));
}
else {
rerender(currentRender);
}
};
stateIndices.set(currentRender, index + 1);
return [state, setState];
}
function useEffect(callback, deps) {
if (!currentRender)
throw new Error("useEffect must be called within a render");
// Skip effects during SSR
if (isSSR) {
const effectIndex = stateIndices.get(currentRender) || 0;
stateIndices.set(currentRender, effectIndex + 1);
return;
}
const effectIndex = stateIndices.get(currentRender) || 0;
if (!effects.has(currentRender)) {
effects.set(currentRender, []);
}
const componentEffects = effects.get(currentRender);
const prevEffect = componentEffects[effectIndex];
if (!prevEffect || !deps || !prevEffect.deps || deps.some((dep, i) => dep !== prevEffect.deps[i])) {
if (prevEffect?.cleanup) {
prevEffect.cleanup();
}
// Schedule effect execution after render is complete
queueMicrotask(() => {
const cleanup = callback() || undefined;
componentEffects[effectIndex] = { cleanup, deps: deps || [] };
});
}
stateIndices.set(currentRender, effectIndex + 1);
}
function useMemo(factory, deps) {
if (!currentRender)
throw new Error("useMemo must be called within a render");
const memoIndex = stateIndices.get(currentRender) || 0;
if (!memos.has(currentRender)) {
memos.set(currentRender, []);
}
const componentMemos = memos.get(currentRender);
const prevMemo = componentMemos[memoIndex];
if (!prevMemo || (deps && deps.some((dep, i) => !Object.is(dep, prevMemo.deps[i])))) {
const value = factory();
componentMemos[memoIndex] = { value, deps: deps || [] };
stateIndices.set(currentRender, memoIndex + 1);
return value;
}
stateIndices.set(currentRender, memoIndex + 1);
return prevMemo.value;
}
function useRef(initial) {
if (!currentRender)
throw new Error("useRef must be called within a render");
const refIndex = stateIndices.get(currentRender) || 0;
if (!refs.has(currentRender)) {
refs.set(currentRender, []);
}
const componentRefs = refs.get(currentRender);
if (refIndex >= componentRefs.length) {
const ref = { current: initial };
componentRefs.push(ref);
stateIndices.set(currentRender, refIndex + 1);
return ref;
}
const ref = componentRefs[refIndex];
stateIndices.set(currentRender, refIndex + 1);
return ref;
}
async function rerender(rendererId) {
try {
// Skip rerender during SSR
if (isSSR) {
return;
}
// Clean up effects
const componentEffects = effects.get(rendererId);
if (componentEffects) {
componentEffects.forEach(effect => {
if (effect.cleanup)
effect.cleanup();
});
effects.set(rendererId, []);
}
// Trigger re-render
if (globalRenderCallback && globalContainer && currentElement) {
await globalRenderCallback(currentElement, globalContainer);
}
}
catch (error) {
console.error('Error during rerender:', error);
}
}
function useErrorBoundary() {
const [error, setError] = useState(null);
return [error, () => setError(null)];
}
// Render function for client-side rendering
async function render(element, container) {
try {
// Set up render callback for state updates
setRenderCallback(render, element, container);
// Prepare render context
const renderId = prepareRender(element);
// Create DOM nodes from virtual elements
const domNode = await createElement(element);
// Clear container and append new content
// Handle both Element and DocumentFragment cases
if (container instanceof Element) {
container.innerHTML = '';
}
else {
// For DocumentFragment, clear all children
while (container.firstChild) {
container.removeChild(container.firstChild);
}
}
container.appendChild(domNode);
// Finish render
finishRender();
console.log('Render completed successfully');
}
catch (error) {
console.error('Error during render:', error);
throw error;
}
}
// Hydrate function for client-side hydration of SSR content
async function hydrate(element, container) {
try {
console.log('Starting hydration...');
// For now, hydrate works the same as render
// In a more advanced implementation, this would preserve existing DOM
// and only attach event listeners and initialize state
await render(element, container);
console.log('Hydration completed successfully');
}
catch (error) {
console.error('Error during hydration:', error);
throw error;
}
}
async function renderToString(element) {
prepareRender(element); // Pass the element to establish render context
try {
const html = await renderNodeToString(element);
return html;
}
finally {
}
}
async function renderNodeToString(node) {
// Handle null, undefined, boolean
if (node == null || typeof node === 'boolean') {
return '';
}
// Handle primitives
if (typeof node === 'string' || typeof node === 'number') {
return escapeHtml(String(node));
}
// Handle arrays
if (Array.isArray(node)) {
const results = await Promise.all(node.map(child => renderNodeToString(child)));
return results.join('');
}
// Handle objects with type and props (React-like elements)
if (node && typeof node === 'object' && 'type' in node) {
const { type, props = {} } = node;
// Handle function components
if (typeof type === 'function') {
try {
// Set up a new render context for this component
const componentRenderId = prepareRender(type);
try {
const result = await type(props);
return await renderNodeToString(result);
}
finally {
finishRender();
}
}
catch (error) {
console.error('Error rendering component:', error);
return `<!-- Error rendering component: ${error.message} -->`;
}
}
// Handle DOM elements
if (typeof type === 'string') {
return await renderDOMElement(type, props);
}
}
// Fallback for other objects
if (typeof node === 'object') {
return escapeHtml(JSON.stringify(node));
}
return escapeHtml(String(node));
}
async function renderDOMElement(tagName, props) {
const { children, ...attrs } = props;
// Self-closing tags
const voidElements = new Set([
'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
'link', 'meta', 'param', 'source', 'track', 'wbr'
]);
// Build attributes string
const attributeString = Object.entries(attrs)
.filter(([key, value]) => {
// Filter out React-specific props and event handlers
if (key.startsWith('on') || key === 'key' || key === 'ref')
return false;
if (value == null || value === false)
return false;
return true;
})
.map(([key, value]) => {
// Handle className -> class
if (key === 'className')
key = 'class';
// Handle boolean attributes
if (value === true)
return key;
// Handle style objects
if (key === 'style' && typeof value === 'object' && value !== null) {
const styleString = Object.entries(value)
.map(([prop, val]) => `${kebabCase(prop)}:${val}`)
.join(';');
return `style="${escapeHtml(styleString)}"`;
}
return `${key}="${escapeHtml(String(value))}"`;
})
.join(' ');
const openTag = `<${tagName}${attributeString ? ' ' + attributeString : ''}>`;
// Self-closing elements
if (voidElements.has(tagName)) {
return openTag.slice(0, -1) + '/>';
}
// Elements with children
const closeTag = `</${tagName}>`;
if (children != null) {
const childrenString = await renderNodeToString(children);
return openTag + childrenString + closeTag;
}
return openTag + closeTag;
}
function escapeHtml(text) {
const htmlEscapes = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
'/': '/'
};
return text.replace(/[&<>"'/]/g, (match) => htmlEscapes[match]);
}
function kebabCase(str) {
return str.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
}
function arePropsEqual(oldProps, newProps) {
const oldKeys = Object.keys(oldProps).filter(k => k !== 'children');
const newKeys = Object.keys(newProps).filter(k => k !== 'children');
if (oldKeys.length !== newKeys.length)
return false;
return oldKeys.every(key => oldProps[key] === newProps[key]);
}
function diff(oldNode, newNode) {
if (oldNode == null || newNode == null)
return oldNode !== newNode;
if (typeof oldNode !== typeof newNode)
return true;
if (typeof newNode === 'string' || typeof newNode === 'number')
return oldNode !== newNode;
if (newNode.type !== oldNode.type)
return true;
return !arePropsEqual(oldNode.props, newNode.props);
}
function shouldComponentUpdate(oldProps, newProps) {
return !arePropsEqual(oldProps, newProps);
}
class Component {
constructor(props = {}) {
this.state = {};
this.element = null;
this._mounted = false;
this.props = props;
}
componentDidMount() {
// Hook for after component is mounted
}
async setState(newState) {
const prevState = { ...this.state };
this.state = { ...prevState, ...newState };
console.log(`${this.constructor.name} state updated:`, {
prev: prevState,
next: this.state
});
await Promise.resolve(); // Ensure state is updated before re-render
if (this._mounted) {
await this.update();
}
else {
await this.update();
}
}
_replayEvents(oldElement, newElement) {
const oldEvents = oldElement.__events || {};
Object.entries(oldEvents).forEach(([event, handler]) => {
newElement.addEventListener(event, handler);
});
newElement.__events = oldEvents;
}
_deepCloneWithEvents(node) {
const clone = node.cloneNode(false);
// Copy events from original element
const events = node.__events || {};
clone.__events = events;
Object.entries(events).forEach(([event, handler]) => {
clone.addEventListener(event, handler);
});
// Clone children
Array.from(node.childNodes).forEach(child => {
if (child instanceof HTMLElement) {
clone.appendChild(this._deepCloneWithEvents(child));
}
else {
clone.appendChild(child.cloneNode(true));
}
});
return clone;
}
async update() {
const vdom = this.render();
if (!vdom)
return document.createTextNode('');
const rendered = await createElement(vdom);
if (rendered instanceof HTMLElement) {
return this._updateElement(rendered);
}
const wrapper = document.createElement('div');
wrapper.appendChild(rendered);
return this._updateElement(wrapper);
}
async _updateElement(rendered) {
const newElement = this._deepCloneWithEvents(rendered);
newElement.__instance = this;
if (!this.element) {
this.element = newElement;
if (!this._mounted) {
this._mounted = true;
queueMicrotask(() => this.componentDidMount());
}
}
else if (this.element.parentNode) {
this.element.parentNode.replaceChild(newElement, this.element);
this.element = newElement;
}
return this.element;
}
render() {
throw new Error('Component must implement render() method');
}
}
/**
* Client-side router for single-page applications
*/
// Create context for router
const RouterContext = createContext({
location: {
pathname: '/',
search: '',
hash: ''
},
params: {},
navigate: () => { },
match: () => false
});
// Parse path pattern into regex
function parsePath(path) {
// Convert :param syntax to capture groups
const pattern = path
.replace(/\/+$/, '') // Remove trailing slashes
.replace(/^\/+/, '/') // Ensure leading slash
.replace(/\/:([^/]+)/g, '/([^/]+)');
// Get param names
const paramNames = [];
path.replace(/\/:([^/]+)/g, (_, paramName) => {
paramNames.push(paramName);
return '';
});
return { pattern, paramNames };
}
// Match a path against a pattern
function matchPath(pathname, route) {
const { path, exact = false } = route;
const { pattern, paramNames } = parsePath(path);
// Create regex with or without exact matching
const regex = new RegExp(`^${pattern}${exact ? '$' : ''}`);
const match = pathname.match(regex);
if (!match)
return null;
// Extract params
const params = {};
paramNames.forEach((name, index) => {
params[name] = match[index + 1];
});
return params;
}
// Router Provider component
function RouterProvider({ routes, children }) {
// Get initial location from window if available
const getInitialLocation = () => {
if (typeof window === 'undefined') {
return { pathname: '/', search: '', hash: '' };
}
return {
pathname: window.location.pathname,
search: window.location.search,
hash: window.location.hash
};
};
// Fix: Call the function to get the initial value instead of passing the function itself
const [location, setLocation] = useState(getInitialLocation());
const [params, setParams] = useState({});
// Update params when location changes
useEffect(() => {
for (const route of routes) {
const matchedParams = matchPath(location.pathname, route);
if (matchedParams) {
setParams(matchedParams);
break;
}
}
}, [location, routes]);
// Listen for popstate events
useEffect(() => {
if (typeof window === 'undefined')
return;
const handlePopState = () => {
setLocation({
pathname: window.location.pathname,
search: window.location.search,
hash: window.location.hash
});
};
window.addEventListener('popstate', handlePopState);
return () => window.removeEventListener('popstate', handlePopState);
}, []);
// Navigation function
const navigate = (to, options = {}) => {
if (typeof window === 'undefined')
return;
const { replace = false, state = null } = options;
if (replace) {
window.history.replaceState(state, '', to);
}
else {
window.history.pushState(state, '', to);
}
// Update location
setLocation({
pathname: window.location.pathname,
search: window.location.search,
hash: window.location.hash
});
};
// Match function to test if a path matches the current location
const match = (path) => {
const { pattern } = parsePath(path);
const regex = new RegExp(`^${pattern}`);
return regex.test(location.pathname);
};
// Router context value
const routerValue = {
location: {
pathname: location.pathname,
search: location.search,
hash: location.hash
},
params,
navigate,
match
};
return jsx(RouterContext.Provider, { value: routerValue, children });
}
// Route component
function Route({ path, component: Component, props = {} }) {
const context = useContext();
const params = context.params;
const locationPathname = context.location.pathname;
const routeToMatch = { path, component: Component };
const match = matchPath(locationPathname, routeToMatch);
if (!match)
return null;
return jsx(Component, { ...props, params });
}
// Switch component
function Switch({ children }) {
const context = useContext();
const locationPathname = context.location.pathname;
// Find the first matching route
const child = Array.isArray(children)
? children.find(child => {
if (!child || typeof child.type !== 'function' || !child.props.path)
return false;
const routeObj = {
path: child.props.path,
component: child.type,
exact: child.props.exact || false
};
return matchPath(locationPathname, routeObj);
})
: children;
return child || null;
}
// Link component
function Link({ to, replace = false, state = null, className = '', activeClassName = '', children, ...rest }) {
const context = useContext();
const navigate = context.navigate;
const match = context.match;
const handleClick = (e) => {
e.preventDefault();
navigate(to, { replace, state });
};
const isActive = match(to);
const classes = [
className,
isActive ? activeClassName : ''
].filter(Boolean).join(' ');
return jsx('a', {
href: to,
className: classes || undefined,
onClick: handleClick,
...rest,
children
});
}
// Redirect component
function Redirect({ to, replace = true }) {
const context = useContext();
const navigate = context.navigate;
useEffect(() => {
navigate(to, { replace });
}, [to]);
return null;
}
// Hooks
function useLocation() {
const context = useContext();
return context.location;
}
function useParams() {
const context = useContext();
return context.params;
}
function useNavigate() {
const context = useContext();
return context.navigate;
}
/**
* Form handling utilities for the framework
*/
function useForm(options) {
const { initialValues, validate, onSubmit } = options;
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
const [dirty, setDirty] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const [submitCount, setSubmitCount] = useState(0);
// Track form validity and dirty state
const isValid = Object.keys(errors).length === 0;
const isDirty = Object.values(dirty).some(Boolean);
// Validate form when values or validate function changes
useEffect(() => {
if (validate) {
const validationErrors = validate(values);
setErrors(validationErrors || {});
}
}, [values, validate]);
// Handle form input changes
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
const fieldValue = type === 'checkbox' ? checked : value;
setValues(prev => ({
...prev,
[name]: fieldValue
}));
setDirty(prev => ({
...prev,
[name]: true
}));
};
// Handle field blur
const handleBlur = (e) => {
const { name } = e.target;
setTouched(prev => ({
...prev,
[name]: true
}));
};
// Set field value programmatically
const setFieldValue = (field, value) => {
setValues(prev => ({
...prev,
[field]: value
}));
setDirty(prev => ({
...prev,
[field]: true
}));
};
// Set field error programmatically
const setFieldError = (field, error) => {
setErrors(prev => ({
...prev,
[field]: error
}));
};
// Update multiple values at once
const updateValues = (newValues) => {
setValues(prev => ({
...prev,
...newValues
}));
// Mark changed fields as dirty
const dirtyFields = {};
Object.keys(newValues).forEach(key => {
dirtyFields[key] = true;
});
setDirty(prev => ({
...prev,
...dirtyFields
}));
};
// Reset form to initial state
const resetForm = () => {
setValues(initialValues);
setErrors({});
setTouched({});
setDirty({});
setIsSubmitting(false);
};
// Handle form submission
const handleSubmit = async (e) => {
e.preventDefault();
// Mark all fields as touched
const allTouched = {};
Object.keys(values).forEach(key => {
allTouched[key] = true;
});
setTouched(allTouched);
// Validate before submission
let validationErrors = {};
if (validate) {
validationErrors = validate(values);
setErrors(validationErrors || {});
}
// Only proceed if valid
if (Object.keys(validationErrors).length === 0 && onSubmit) {
setIsSubmitting(true);
setSubmitCount(count => count + 1);
try {
await onSubmit(values, {
fields: Object.keys(values).reduce((acc, key) => {
acc[key] = {
value: values[key],
error: errors[key],
touched: touched[key] || false,
dirty: dirty[key] || false
};
return acc;
}, {}),
isValid,
isDirty,
isSubmitting: true,
submitCount: submitCount + 1
});
}
finally {
setIsSubmitting(false);
}
}
};
return {
values,
errors,
touched,
dirty,
isValid,
isDirty,
isSubmitting,
submitCount,
handleChange,
handleBlur,
handleSubmit,
setFieldValue,
setFieldError,
setValues: updateValues,
resetForm
};
}
function createEventBus() {
const events = new Map();
const onceHandlers = new WeakMap();
const on = (event, handler) => {
if (!events.has(event)) {
events.set(event, new Set());
}
events.get(event).add(handler);
// Return a function to remove this handler
return () => off(event, handler);
};
const once = (event, handler) => {
// Create a wrapper that will call the handler and remove itself
const onceWrapper = (...args) => {
off(event, onceWrapper);
handler(...args);
};
// Store the mapping between the original handler and the wrapper
onceHandlers.set(handler, onceWrapper);
// Register the wrapper
return on(event, onceWrapper);
};
const off = (event, handler) => {
// If no handler is provided, remove all handlers for the event
if (!handler) {
events.delete(event);
return;
}
// Check if it's a once handler wrapper
const wrappedHandler = onceHandlers.get(handler);
const handlerToRemove = wrappedHandler || handler;
if (events.has(event)) {
events.get(event).delete(handlerToRemove);
// Clean up empty event sets
if (events.get(event).size === 0) {
events.delete(event);
}
}
};
const emit = (event, ...args) => {
if (!events.has(event)) {
return;
}
// Create a copy of the handlers to avoid issues if handlers modify the set
const handlers = Array.from(events.get(event));
// Call each handler with the arguments
for (const handler of handlers) {
handler(...args);
}
};
const clear = (event) => {
if (event) {
events.delete(event);
}
else {
events.clear();
}
};
return { on, once, off, emit, clear };
}
// Create a global event bus instance
const globalEventBus = createEventBus();
// Hook to use the event bus in components
function useEvent(event, handler, options = {}) {
return options.once
? globalEventBus.once(event, handler)
: globalEventBus.on(event, handler);
}
/**
* Global state management solution similar to Redux
*/
function createStore(reducer, initialState, middlewares = []) {
let state = initialState;
let listeners = [];
// Apply middlewares
let dispatch = (action) => {
state = reducer(state, action);
listeners.forEach(listener => listener());
return action;
};
// Chain middlewares
if (middlewares.length > 0) {
const middlewareAPI = {
getState: () => state,
dispatch: (action) => dispatch(action),
subscribe: (listener) => subscribe(listener)
};
const chain = middlewares.map(middleware => middleware(middlewareAPI));
dispatch = chain.reduce((a, b) => (next) => a(b(next)))(dispatch);
}
// Subscribe to store changes
function subscribe(listener) {
listeners.push(listener);
return function unsubscribe() {
listeners = listeners.filter(l => l !== listener);
};
}
// Initialize store with default state
dispatch({ type: '@@INIT' });
return {
getState: () => state,
dispatch,
subscribe
};
}
const StoreContext = createContext({
store: {
getState: () => ({}),
dispatch: () => { },
subscribe: () => () => { }
},
state: {}
});
function StoreProvider({ store, children }) {
const [state, setState] = useState(store.getState());
useEffect(() => {
const unsubscribe = store.subscribe(() => {
setState(store.getState());
});
return unsubscribe;
}, [store]);
return jsx(StoreContext.Provider, {
value: { store, state },
children
});
}
function useSelector(selector) {
const context = useContext();
return selector(context.state);
}
function useDispatch() {
const context = useContext();
return context.store.dispatch;
}
function useStore() {
const context = useContext();
return context.store;
}
// Common middlewares
const logger = (store) => (next) => (action) => {
console.group(action.type);
console.log('Previous state:', store.getState());
console.log('Action:', action);
const result = next(action);
console.log('Next state:', store.getState());
console.groupEnd();
return result;
};
const thunk = (store) => (next) => (action) => {
if (typeof action === 'function') {
return action(store.dispatch, store.getState);
}
return next(action);
};
/**
* Lifecycle events for components and application
*/
exports.LifecycleEvents = void 0;
(function (LifecycleEvents) {
LifecycleEvents["APP_INIT"] = "app:init";
LifecycleEvents["APP_MOUNTED"] = "app:mounted";
LifecycleEvents["APP_UPDATED"] = "app:updated";
LifecycleEvents["APP_ERROR"] = "app:error";
LifecycleEvents["APP_DESTROYED"] = "app:destroyed";
LifecycleEvents["COMPONENT_CREATED"] = "component:created";
LifecycleEvents["COMPONENT_MOUNTED"] = "component:mounted";
LifecycleEvents["COMPONENT_UPDATED"] = "component:updated";
LifecycleEvents["COMPONENT_ERROR"] = "component:error";
LifecycleEvents["COMPONENT_UNMOUNTED"] = "component:unmounted";
LifecycleEvents["ROUTER_BEFORE_CHANGE"] = "router:before-change";
LifecycleEvents["ROUTER_AFTER_CHANGE"] = "router:after-change";
LifecycleEvents["ROUTER_ERROR"] = "router:error";
LifecycleEvents["STORE_INITIALIZED"] = "store:initialized";
LifecycleEvents["STORE_BEFORE_ACTION"] = "store:before-action";
LifecycleEvents["STORE_AFTER_ACTION"] = "store:after-action";
LifecycleEvents["STORE_ERROR"] = "store:error";
})(exports.LifecycleEvents || (exports.LifecycleEvents = {}));
// Event emitters
function emitAppInit(data) {
globalEventBus.emit(exports.LifecycleEvents.APP_INIT, data);
}
function emitAppMounted(rootElement) {
globalEventBus.emit(exports.LifecycleEvents.APP_MOUNTED, rootElement);
}
function emitAppUpdated() {
globalEventBus.emit(exports.LifecycleEvents.APP_UPDATED);
}
function emitAppError(error) {
globalEventBus.emit(exports.LifecycleEvents.APP_ERROR, error);
}
function emitAppDestroyed() {
globalEventBus.emit(exports.LifecycleEvents.APP_DESTROYED);
}
function emitComponentCreated(info) {
globalEventBus.emit(exports.LifecycleEvents.COMPONENT_CREATED, info);
}
function emitComponentMounted(info, element) {
globalEventBus.emit(exports.LifecycleEvents.COMPONENT_MOUNTED, info, element);
}
function emitComponentUpdated(info, prevProps, newProps) {
globalEventBus.emit(exports.LifecycleEvents.COMPONENT_UPDATED, info, prevProps, newProps);
}
function emitComponentError(info, error) {
globalEventBus.emit(exports.LifecycleEvents.COMPONENT_ERROR, info, error);
}
function emitComponentUnmounted(info) {
globalEventBus.emit(exports.LifecycleEvents.COMPONENT_UNMOUNTED, info);
}
// Event listeners
function onAppInit(handler) {
return globalEventBus.on(exports.LifecycleEvents.APP_INIT, handler);
}
function onAppMounted(handler) {
return globalEventBus.on(exports.LifecycleEvents.APP_MOUNTED, handler);
}
function onAppUpdated(handler) {
return globalEventBus.on(exports.LifecycleEvents.APP_UPDATED, handler);
}
function onAppError(handler) {
return globalEventBus.on(exports.LifecycleEvents.APP_ERROR, handler);
}
function onAppDestroyed(handler) {
return globalEventBus.on(exports.LifecycleEvents.APP_DESTROYED, handler);
}
function onComponentCreated(handler) {
return globalEventBus.on(exports.LifecycleEvents.COMPONENT_CREATED, handler);
}
function onComponentMounted(handler) {
return globalEventBus.on(exports.LifecycleEvents.COMPONENT_MOUNTED, handler);
}
function onComponentUpdated(handler) {
return globalEventBus.on(exports.LifecycleEvents.COMPONENT_UPDATED, handler);
}
function onComponentError(handler) {
return globalEventBus.on(exports.LifecycleEvents.COMPONENT_ERROR, handler);
}
function onComponentUnmounted(handler) {
return globalEventBus.on(exports.LifecycleEvents.COMPONENT_UNMOUNTED, handler);
}
// Core JSX and rendering functions
// Default export object for compatibility
const frontendHamroun = {
// JSX
jsx,
jsxs: jsx,
jsxDEV: jsx,
Fragment,
createElement,
// Rendering
render,
hydrate,
renderToString,
// Hooks
useState,
useEffect,
useMemo,
useRef,
useContext,
useErrorBoundary,
// Context
createContext,
// Utilities
batchUpdates,
diff,
shouldComponentUpdate,
// Component class
Component,
// Router
RouterProvider,
Route,
Switch,
Link,
Redirect,
useLocation,
useParams,
useNavigate,
// Forms
useForm,
// Event bus
createEventBus,
useEvent,
eventBus: globalEventBus
};
exports.Component = Component;
exports.Fragment = Fragment;
exports.Link = Link;
exports.Redirect = Redirect;
exports.Route = Route;
exports.RouterProvider = RouterProvider;
exports.StoreProvider = StoreProvider;
exports.Switch = Switch;
exports.batchUpdates = batchUpdates;
exports.createContext = createContext;
exports.createElement = createElement;
exports.createEventBus = createEventBus;
exports.createStore = createStore;
exports.default = frontendHamroun;
exports.diff = diff;
exports.emitAppDestroyed = emitAppDestroyed;
exports.emitAppError = emitAppError;
exports.emitAppInit = emitAppInit;
exports.emitAppMounted = emitAppMounted;
exports.emitAppUpdated = emitAppUpdated;
exports.emitComponentCreated = emitComponentCreated;
exports.emitComponentError = emitComponentError;
exports.emitComponentMounted = emitComponentMounted;
exports.emitComponentUnmounted = emitComponentUnmounted;
exports.emitComponentUpdated = emitComponentUpdated;
exports.eventBus = globalEventBus;
exports.hydrate = hydrate;
exports.jsx = jsx;
exports.jsxDEV = jsx;
exports.jsxs = jsx;
exports.logger = logger;
exports.onAppDestroyed = onAppDestroyed;
exports.onAppError = onAppError;
exports.onAppInit = onAppInit;
exports.onAppMounted = onAppMounted;
exports.onAppUpdated = onAppUpdated;
exports.onComponentCreated = onComponentCreated;
exports.onComponentError = onComponentError;
exports.onComponentMounted = onComponentMounted;
exports.onComponentUnmounted = onComponentUnmounted;
exports.onComponentUpdated = onComponentUpdated;
exports.render = render;
exports.renderToString = renderToString;
exports.shouldComponentUpdate = shouldComponentUpdate;
exports.thunk = thunk;
exports.useContext = useContext;
exports.useDispatch = useDispatch;
exports.useEffect = useEffect;
exports.useErrorBoundary = useErrorBoundary;
exports.useEvent = useEvent;
exports.useForm = useForm;
exports.useLocation = useLocation;
exports.useMemo = useMemo;
exports.useNavigate = useNavigate;
exports.useParams = useParams;
exports.useRef = useRef;
exports.useSelector = useSelector;
exports.useState = useState;
exports.useStore = useStore;
//# sourceMappingURL=index.cjs.map