@zenithcore/core
Version:
Core functionality for ZenithKernel framework
894 lines (770 loc) • 23.5 kB
text/typescript
import {
useState as reactiveUseState,
useRef as reactiveUseRef,
useEffect,
useMemo,
useCallback,
initializeComponent,
withComponent,
getComponentInstance,
cleanupComponent,
bindStateToAttribute,
bindStateToText,
bindStateToClass,
bindStateToStyle,
type ComponentInstance
} from '../../core/reactive-state';
// Import signal system for enhanced reactivity
import {
Signal,
signal,
computed,
effect,
isSignal,
resolve,
type MaybeSignal
} from '../../core/signals';
// Import SignalManager for coordinated reactivity
import {getSignalManager, SignalManager} from '../../core/SignalManager';
import { H } from 'vitest/dist/chunks/environment.d.Dmw5ulng';
export {
reactiveUseState as useState,
reactiveUseRef as useRef,
useEffect,
useMemo,
useCallback,
// Signal exports
signal,
computed,
effect,
isSignal,
resolve
};
// Enhanced reactive attribute types
type ReactiveValue<T> = T | Signal<T> | (() => T);
type ReactiveClass = ReactiveValue<string | string[] | Record<string, boolean>>;
type ReactiveStyle = ReactiveValue<string | Record<string, string | number>>;
type ReactiveAttribute<T> = ReactiveValue<T>;
interface Attributes {
[key: string]: any;
children?: Children;
// Enhanced reactive attributes
signal?: boolean; // Enable signal-based reactivity
signalId?: string; // Signal namespace ID
hydraId?: string; // Hydra component ID for context
// Reactive attribute prefixes
$class?: ReactiveClass;
$style?: ReactiveStyle;
$textContent?: ReactiveValue<string | number>;
$innerHTML?: ReactiveValue<string>;
// Generic reactive attributes with $ prefix
[K: `$${string}`]: ReactiveAttribute<any>;
}
/**
* Create a signal-based reactive component with SignalManager integration
*/
function createSignalReactiveComponent(
Component: (props: any) => HTMLElement | DocumentFragment,
props: any
): HTMLElement | DocumentFragment {
const {ecsEntity, ecsManager, signalId, hydraId, signal: enableSignals, ...componentProps} = props;
const signalManager = getSignalManager();
// Create wrapper element
const wrapper = document.createElement('div');
wrapper.setAttribute('data-signal-component', 'true');
if (signalId) wrapper.setAttribute('data-signal-id', signalId);
if (hydraId) wrapper.setAttribute('data-hydra-id', hydraId);
// Create or get Hydra context for signal management
let hydraContext;
if (hydraId) {
try {
hydraContext = signalManager.createHydraContext(hydraId);
} catch {
// Context already exists, get signals
hydraContext = {signals: signalManager.getHydraSignals(hydraId)};
}
}
// Initialize legacy component instance for backward compatibility
const componentInstance = initializeComponent(wrapper, ecsEntity, ecsManager);
// Render component with signal context
const result = withComponent(componentInstance, () => {
return Component(componentProps);
});
// Handle result and apply signal reactivity
let targetElement: HTMLElement;
if (result instanceof DocumentFragment) {
wrapper.appendChild(result);
targetElement = wrapper;
} else if (result instanceof HTMLElement) {
targetElement = result;
targetElement.setAttribute('data-signal-component', 'true');
if (signalId) targetElement.setAttribute('data-signal-id', signalId);
if (hydraId) targetElement.setAttribute('data-hydra-id', hydraId);
} else {
targetElement = wrapper;
}
// Setup cleanup for signals when component unmounts
const cleanup = () => {
if (hydraId) {
signalManager.cleanupHydraContext(hydraId);
}
cleanupComponent(targetElement);
};
// Store cleanup function
(targetElement as any).__signalCleanup = cleanup;
return targetElement;
}
/**
* Enhanced reactive binding using signals
*/
function createSignalBinding<T>(
element: HTMLElement,
property: string,
value: ReactiveValue<T>,
transform?: (val: T) => string
): () => void {
const signalManager = getSignalManager();
const bindingId = `${element.tagName.toLowerCase()}-${property}-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`;
if (isSignal(value)) {
// Direct signal binding
signalManager.bindToDOM(bindingId, element, property, value as Signal<T>, transform);
return () => signalManager.removeDOMBinding(bindingId);
} else if (typeof value === 'function') {
// Create computed signal from function
const computedSig = computed(value as () => T);
signalManager.bindToDOM(bindingId, element, property, computedSig, transform);
return () => {
signalManager.removeDOMBinding(bindingId);
computedSig.dispose();
};
} else {
// Static value - set immediately
const stringValue = transform ? transform(value as T) : String(value);
if (property === 'textContent' || property === 'innerHTML') {
(element as any)[property] = stringValue;
} else {
element.setAttribute(property, stringValue);
}
return () => {
}; // No cleanup needed for static values
}
}
/**
* Enhanced class binding with signal support
*/
function createSignalClassBinding(
element: HTMLElement,
value: ReactiveClass
): () => void {
const signalManager = getSignalManager();
const bindingId = `${element.tagName.toLowerCase()}-class-${Date.now()}-${Math.random().toString(36).substr(2, 5)}`;
if (isSignal(value)) {
signalManager.bindClassList(bindingId, element, value as Signal<any>);
return () => signalManager.removeDOMBinding(bindingId);
} else if (typeof value === 'function') {
const computedSig = computed(value as () => any);
signalManager.bindClassList(bindingId, element, computedSig);
return () => {
signalManager.removeDOMBinding(bindingId);
computedSig.dispose();
};
} else {
// Static class value
if (typeof value === 'string') {
element.className = value;
} else if (Array.isArray(value)) {
element.className = value.filter(Boolean).join(' ');
} else if (value && typeof value === 'object') {
const classes: string[] = [];
for (const [className, condition] of Object.entries(value)) {
if (condition) classes.push(className);
}
element.className = classes.join(' ');
}
return () => {
};
}
}
/**
* Enhanced style binding with signal support
*/
function createSignalStyleBinding(
element: HTMLElement,
value: ReactiveStyle
): () => void {
if (isSignal(value)) {
return effect(() => {
const styleValue = (value as Signal<any>).value;
applyStyles(element, styleValue);
}).dispose;
} else if (typeof value === 'function') {
return effect(() => {
const styleValue = (value as () => any)();
applyStyles(element, styleValue);
}).dispose;
} else {
applyStyles(element, value as any);
return () => {
};
}
}
function applyStyles(element: HTMLElement, styleValue: any): void {
if (typeof styleValue === 'string') {
element.setAttribute('style', styleValue);
} else if (styleValue && typeof styleValue === 'object') {
for (const [property, val] of Object.entries(styleValue)) {
if (val != null) {
const cssProperty = property.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`);
element.style.setProperty(cssProperty, String(val));
} else {
const cssProperty = property.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`);
element.style.removeProperty(cssProperty);
}
}
}
}
// ZenithKernel JSX Runtime - Core implementation
type Children =
| string
| number
| boolean
| null
| undefined
| HTMLElement
| DocumentFragment
| Children[];
// Type definitions for special Hydra components
interface HydraProps extends Attributes {
type: 'island' | 'system' | 'wasm';
id: string;
entry: string;
execType: 'local' | 'remote' | 'edge';
context?: Record<string, any>;
strategy?: 'immediate' | 'visible' | 'interaction' | 'idle' | 'manual';
trustLevel?: 'unverified' | 'local' | 'community' | 'verified';
zkProof?: string;
manifestUrl?: string;
props?: Record<string, any>;
}
interface MetaProps extends Attributes {
title?: string;
description?: string;
keywords?: string[];
author?: string;
viewport?: string;
layout?: string;
// OpenGraph properties
'og:title'?: string;
'og:description'?: string;
'og:image'?: string;
'og:type'?: string;
'og:url'?: string;
// Twitter Card properties
'twitter:card'?: string;
'twitter:title'?: string;
'twitter:description'?: string;
'twitter:image'?: string;
}
interface SafeScriptProps extends Attributes {
type?: 'on_load' | 'on_before_load' | 'lifecycle_id';
src?: string;
integrity?: string;
crossorigin?: 'anonymous' | 'use-credentials';
nonce?: string;
async?: boolean;
defer?: boolean;
}
interface CSSProps extends Attributes {
href?: string;
media?: string;
integrity?: string;
crossorigin?: 'anonymous' | 'use-credentials';
}
/**
* JSX Factory function - the core of the JSX runtime
*/
export function jsx(
type: string | Function,
props: Attributes | null,
...children: Children[]
): HTMLElement | DocumentFragment {
const allProps = props || {};
const allChildren = children.length === 1 ? children[0] : children;
// Handle special Hydra components
if (typeof type === 'string') {
switch (type) {
case 'Hydra':
return createHydraComponent(allProps as HydraProps, allChildren);
case 'meta':
return createMetaComponent(allProps as MetaProps);
case 'safeScript':
return createSafeScriptComponent(allProps as SafeScriptProps, allChildren);
case 'css':
return createCSSComponent(allProps as CSSProps, allChildren);
default:
return createStandardElement(type, allProps, allChildren) as HTMLElement | DocumentFragment;
}
}
// Handle functional components
if (typeof type === 'function') {
const componentProps = { ...allProps, children: allChildren };
// Check if component needs signal reactivity
if (componentProps.signal || componentProps.signalId || componentProps.hydraId) {
return createSignalReactiveComponent(type(),componentProps);
}
// Standard component with legacy reactive-state system
const { ...restProps } = componentProps;
const element = document.createElement('div');
const componentInstance = initializeComponent(element);
const result = withComponent(componentInstance, () => {
return type(restProps);
});
if (result instanceof DocumentFragment) {
element.appendChild(result);
return element;
}
return result as HTMLElement;
}
throw new Error(`Invalid JSX element type: ${type}`);
}
/**
* Create standard HTML elements with enhanced reactivity
*/
function createStandardElement(
tagName: string,
props: Attributes,
children: Children
): HTMLElement | void {
const element = document.createElement(tagName);
const cleanupFunctions: (() => void)[] = [];
// Apply attributes and setup reactive bindings
for (const [key, value] of Object.entries(props)) {
if (key === 'children') continue;
// Handle reactive attributes with $ prefix
if ((key.startsWith('$'))) {
const property = key.slice(1); // Remove $ prefix
if (property === 'class') {
const cleanup = createSignalClassBinding(element, value as ReactiveClass);
cleanupFunctions.push(cleanup);
} else if (property === 'style') {
const cleanup = createSignalStyleBinding(element, value as ReactiveStyle);
cleanupFunctions.push(cleanup);
} else {
const cleanup = createSignalBinding(element, property, value);
cleanupFunctions.push(cleanup);
}
continue;
}
// Handle legacy reactive attributes (for backward compatibility)
if (isSignal(value)) {
const cleanup = bindStateToAttribute(element, key, value as unknown as () => any);
cleanupFunctions.push(cleanup);
continue;
}
// Handle event listeners
if (key.startsWith('on') && typeof value === 'function') {
const eventName = key.slice(2).toLowerCase();
element.addEventListener(eventName, value);
cleanupFunctions.push(() => {
element.removeEventListener(eventName, value);
});
continue;
}
// Handle boolean attributes
if (typeof value === 'boolean') {
if (value) {
element.setAttribute(key, '');
}
continue;
}
// Handle standard attributes
if (value !== null && value !== undefined) {
element.setAttribute(key, String(value));
}
}
// Store cleanup functions
(element as any).__jsxCleanup = () => {
cleanupFunctions.forEach(fn => fn());
};
// Append children
appendChildren(element, children);
return element;
}
/**
* Create Hydra island component
*/
function createHydraComponent(props: HydraProps, children: Children): HTMLElement {
const {
type,
id,
entry,
execType,
context = {},
strategy = 'immediate',
trustLevel = 'local',
zkProof,
manifestUrl,
props: componentProps = {},
...attrs
} = props;
const element = document.createElement('div');
element.className = 'hydra-island';
element.setAttribute('data-hydra-id', id);
element.setAttribute('data-hydra-entry', entry);
element.setAttribute('data-hydra-exec-type', execType);
element.setAttribute('data-hydra-strategy', strategy);
element.setAttribute('data-hydra-trust-level', trustLevel);
if (zkProof) {
element.setAttribute('data-hydra-zk-proof', zkProof);
}
if (manifestUrl) {
element.setAttribute('data-hydra-manifest', manifestUrl);
}
// Store hydration context and props
(element as any).__hydraContext = context;
(element as any).__hydraProps = componentProps;
// Apply additional attributes
for (const [key, value] of Object.entries(attrs)) {
if (value !== null && value !== undefined) {
element.setAttribute(key, String(value));
}
}
// Add children as fallback content
appendChildren(element, children);
// Mark for hydration by the Hydra runtime
element.setAttribute('data-hydra-state', 'pending');
return element;
}
/**
* Create meta component for page metadata
*/
function createMetaComponent(props: MetaProps): DocumentFragment {
const fragment = document.createDocumentFragment();
// Create title element
if (props.title) {
const titleElement = document.createElement('title');
titleElement.textContent = props.title;
fragment.appendChild(titleElement);
}
// Create meta tags
const metaTags: Array<[string, string]> = [];
if (props.description) {
metaTags.push(['name', 'description'], ['content', props.description]);
}
if (props.keywords) {
metaTags.push(['name', 'keywords'], ['content', props.keywords.join(', ')]);
}
if (props.author) {
metaTags.push(['name', 'author'], ['content', props.author]);
}
if (props.viewport) {
metaTags.push(['name', 'viewport'], ['content', props.viewport]);
}
// OpenGraph tags
for (const [key, value] of Object.entries(props)) {
if (key.startsWith('og:') && value) {
metaTags.push(['property', key], ['content', String(value)]);
}
if (key.startsWith('twitter:') && value) {
metaTags.push(['name', key], ['content', String(value)]);
}
}
// Create meta elements in pairs
for (let i = 0; i < metaTags.length; i += 2) {
const metaElement = document.createElement('meta');
metaElement.setAttribute(metaTags[i][0], metaTags[i][1]);
metaElement.setAttribute(metaTags[i + 1][0], metaTags[i + 1][1]);
fragment.appendChild(metaElement);
}
// Layout meta tag
if (props.layout) {
const layoutMeta = document.createElement('meta');
layoutMeta.setAttribute('name', 'layout');
layoutMeta.setAttribute('content', props.layout);
fragment.appendChild(layoutMeta);
}
return fragment;
}
/**
* Create secure script component
*/
function createSafeScriptComponent(props: SafeScriptProps, children: Children): HTMLElement {
const {
type = 'on_load',
src,
integrity,
crossorigin,
nonce,
async: isAsync,
defer,
...attrs
} = props;
const script = document.createElement('script');
// Set script type based on loading strategy
script.setAttribute('data-script-type', type);
if (src) {
script.src = src;
// Security attributes
if (integrity) {
script.integrity = integrity;
}
if (crossorigin) {
script.crossOrigin = crossorigin;
}
if (nonce) {
script.nonce = nonce;
}
if (isAsync) {
script.async = true;
}
if (defer) {
script.defer = true;
}
}
// Add inline script content
if (children) {
const content = flattenChildren(children);
if (content.trim()) {
script.textContent = content;
}
}
// Apply additional attributes
for (const [key, value] of Object.entries(attrs)) {
if (value !== null && value !== undefined) {
script.setAttribute(key, String(value));
}
}
return script;
}
/**
* Create CSS component
*/
function createCSSComponent(props: CSSProps, children: Children): HTMLElement {
if (props.href) {
// External stylesheet
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = props.href;
if (props.media) {
link.media = props.media;
}
if (props.integrity) {
link.integrity = props.integrity;
}
if (props.crossorigin) {
link.crossOrigin = props.crossorigin;
}
return link;
} else {
// Inline styles
const style = document.createElement('style');
if (props.media) {
style.media = props.media;
}
if (children) {
const content = flattenChildren(children);
style.textContent = content;
}
return style;
}
}
/**
* Append children to an element
*/
function appendChildren(element: HTMLElement, children: Children): void {
if (children == null) return;
if (Array.isArray(children)) {
children.forEach(child => appendChild(element, child));
} else {
appendChild(element, children);
}
}
/**
* Append a single child to an element
*/
function appendChild(element: HTMLElement, child: Children): void {
if (child == null || typeof child === 'boolean') {
return;
}
if (typeof child === 'string' || typeof child === 'number') {
element.appendChild(document.createTextNode(String(child)));
return;
}
if (child instanceof HTMLElement || child instanceof DocumentFragment) {
element.appendChild(child);
return;
}
if (Array.isArray(child)) {
child.forEach(grandChild => appendChild(element, grandChild));
return;
}
// Handle signals as text content
if (isSignal(child)) {
const textNode = document.createTextNode('');
element.appendChild(textNode);
// Create reactive binding for text content
effect(() => {
textNode.textContent = String((child as Signal<any>).value);
});
return;
}
}
/**
* Flatten children array to text content
*/
function flattenChildren(children: Children): string {
if (children == null || typeof children === 'boolean') {
return '';
}
if (typeof children === 'string' || typeof children === 'number') {
return String(children);
}
if (Array.isArray(children)) {
return children.map(flattenChildren).join('');
}
return '';
}
/**
* JSX Fragment implementation
*/
export function Fragment(props: { children?: Children }): DocumentFragment {
const fragment = document.createDocumentFragment();
if (props.children) {
appendChildren(fragment as any, props.children);
}
return fragment;
}
// Compatibility exports
export { jsx as jsxs, jsx as jsxDEV };
// Type definitions for JSX
export namespace JSX {
export interface Element extends HTMLElement {}
export interface IntrinsicElements {
// Standard HTML elements with reactive support
[elemName: string]: Attributes;
// Special Hydra components
Hydra: HydraProps;
meta: MetaProps;
safeScript: SafeScriptProps;
css: CSSProps;
// Common HTML elements with enhanced typing
div: Attributes;
span: Attributes;
p: Attributes;
h1: Attributes;
h2: Attributes;
h3: Attributes;
h4: Attributes;
h5: Attributes;
h6: Attributes;
button: Attributes & { type?: 'button' | 'submit' | 'reset' };
input: Attributes & {
type?: string;
value?: ReactiveValue<string | number>;
checked?: ReactiveValue<boolean>;
placeholder?: string;
};
img: Attributes & {
src?: ReactiveValue<string>;
alt?: string;
loading?: 'lazy' | 'eager';
};
a: Attributes & {
href?: ReactiveValue<string>;
target?: '_blank' | '_self' | '_parent' | '_top';
rel?: string;
};
}
export interface ElementChildrenAttribute {
children: {};
}
}
/**
* Cleanup function for JSX elements
*/
export function cleanup(element: HTMLElement): void {
// Cleanup JSX reactive bindings
if ((element as any).__jsxCleanup) {
(element as any).__jsxCleanup();
}
// Cleanup signal-based reactive bindings
if ((element as any).__signalCleanup) {
(element as any).__signalCleanup();
}
// Cleanup legacy reactive-state bindings
cleanupComponent(element);
// Recursively cleanup children
const children = element.querySelectorAll('*');
children.forEach(child => {
if (child instanceof HTMLElement) {
cleanup(child);
}
});
}
/**
* Render JSX to string (for SSR)
*/
export function renderToString(element: HTMLElement | DocumentFragment): string {
if (element instanceof DocumentFragment) {
const div = document.createElement('div');
div.appendChild(element.cloneNode(true));
return div.innerHTML;
}
return element.outerHTML;
}
/**
* Create a reactive text node that updates when signal changes
*/
export function createReactiveText(value: ReactiveValue<string | number>): Text {
const textNode = document.createTextNode('');
if (isSignal(value)) {
effect(() => {
textNode.textContent = String((value as Signal<any>).value);
});
} else if (typeof value === 'function') {
effect(() => {
textNode.textContent = String((value as () => any)());
});
} else {
textNode.textContent = String(value);
}
return textNode;
}
/**
* Hydra-specific utility functions
*/
export const hydra = {
/**
* Create a Hydra island programmatically
*/
createIsland(
id: string,
entry: string,
options: Partial<HydraProps> = {}
): HTMLElement {
return jsx('Hydra', {
type: 'island',
id,
entry,
execType: 'local',
...options
}) as HTMLElement;
},
/**
* Create a meta tag set
*/
createMeta(props: MetaProps): DocumentFragment {
return createMetaComponent(props);
},
/**
* Create a secure script
*/
createScript(
content: string,
options: Partial<SafeScriptProps> = {}
): HTMLElement {
return jsx('safeScript', options, content) as HTMLElement;
}
};
// Default export for compatibility
export default jsx;