@relicloops/cathode 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/inject.js ADDED
@@ -0,0 +1,27 @@
1
+ import { render } from './runtime.js';
2
+ function renderToFragment(node) {
3
+ const temp = document.createElement('div');
4
+ render(node, temp);
5
+ const fragment = document.createDocumentFragment();
6
+ while (temp.firstChild) {
7
+ fragment.appendChild(temp.firstChild);
8
+ }
9
+ return fragment;
10
+ }
11
+ export async function inject(node, parent, options) {
12
+ const mode = options?.mode ?? 'replace';
13
+ if (mode === 'replace') {
14
+ render(node, parent);
15
+ return;
16
+ }
17
+ const fragment = renderToFragment(node);
18
+ if (mode === 'append') {
19
+ parent.appendChild(fragment);
20
+ return;
21
+ }
22
+ if (mode === 'prepend') {
23
+ parent.insertBefore(fragment, parent.firstChild);
24
+ return;
25
+ }
26
+ render(node, parent);
27
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Returns event props that trigger lazy loading on hover
3
+ *
4
+ * @example
5
+ * const Dashboard = lazyOnDemand(() => import('./Dashboard.js'));
6
+ * const hoverProps = lazyOnHover(Dashboard);
7
+ * <a href="/dashboard" {...hoverProps}>Dashboard</a>
8
+ */
9
+ export function lazyOnHover(component) {
10
+ return {
11
+ onMouseEnter: () => {
12
+ // console.trace( '[lazyOnHover] Mouse enter, status:', component.__status );
13
+ if (component.__status === 'pending') {
14
+ component.awake();
15
+ }
16
+ }
17
+ };
18
+ }
19
+ /**
20
+ * Triggers lazy loading after a delay
21
+ *
22
+ * @param component - The lazy component to load
23
+ * @param delayMs - Delay in milliseconds before loading
24
+ * @returns Cleanup function to cancel the timeout
25
+ *
26
+ * @example
27
+ * const Analytics = lazyOnDemand(() => import('./Analytics.js'));
28
+ * lazyAfterDelay(Analytics, 5000); // Load after 5 seconds
29
+ */
30
+ export function lazyAfterDelay(component, delayMs) {
31
+ // console.trace( '[lazyAfterDelay] Setting up delay:', delayMs, 'ms, status:', component.__status );
32
+ if (component.__status !== 'pending') {
33
+ return () => { };
34
+ }
35
+ const timeoutId = window.setTimeout(() => {
36
+ // console.trace( '[lazyAfterDelay] Delay elapsed, calling awake()' );
37
+ component.awake();
38
+ }, delayMs);
39
+ return () => clearTimeout(timeoutId);
40
+ }
41
+ /**
42
+ * Triggers lazy loading when the browser is idle
43
+ *
44
+ * @param component - The lazy component to load
45
+ * @param timeout - Maximum time to wait before loading (default: 5000ms)
46
+ * @returns Cleanup function to cancel the idle callback
47
+ *
48
+ * @example
49
+ * const ChatWidget = lazyOnDemand(() => import('./ChatWidget.js'));
50
+ * lazyWhenIdle(ChatWidget); // Load when browser idle
51
+ */
52
+ export function lazyWhenIdle(component, timeout = 5000) {
53
+ // console.trace( '[lazyWhenIdle] Setting up idle callback, timeout:', timeout, 'ms, status:', component.__status );
54
+ if (typeof requestIdleCallback === 'undefined') {
55
+ // console.trace( '[lazyWhenIdle] requestIdleCallback not available, using delay fallback' );
56
+ return lazyAfterDelay(component, timeout);
57
+ }
58
+ if (component.__status !== 'pending') {
59
+ return () => { };
60
+ }
61
+ const requestId = requestIdleCallback(() => {
62
+ // console.trace( '[lazyWhenIdle] Idle callback triggered, calling awake()' );
63
+ component.awake();
64
+ }, { timeout });
65
+ return () => cancelIdleCallback(requestId);
66
+ }
67
+ /**
68
+ * Component wrapper that triggers lazy loading when scrolled into view
69
+ *
70
+ * @param props.component - The lazy component to load
71
+ * @param props.componentProps - Props to pass to the component when loaded
72
+ * @param props.fallback - Content to show before component is visible
73
+ * @param props.rootMargin - Margin around root for intersection detection (e.g., "200px")
74
+ * @param props.threshold - Visibility threshold (0-1) that triggers loading
75
+ *
76
+ * @example
77
+ * const Footer = lazyOnDemand(() => import('./Footer.js'));
78
+ * <LazyOnVisible
79
+ * component={Footer}
80
+ * fallback={<div style="height: 200px">Scroll to load footer</div>}
81
+ * rootMargin="200px"
82
+ * />
83
+ */
84
+ export function LazyOnVisible(props) {
85
+ return {
86
+ type: '__lazy_onvisible__',
87
+ props: {
88
+ component: props.component,
89
+ componentProps: props.componentProps || {},
90
+ fallback: props.fallback || null,
91
+ observerOptions: {
92
+ rootMargin: props.rootMargin,
93
+ threshold: props.threshold
94
+ }
95
+ },
96
+ children: []
97
+ };
98
+ }
package/lib/lazy.js ADDED
@@ -0,0 +1,96 @@
1
+ import { h } from './runtime.js';
2
+ const autoLoadCache = new Map();
3
+ const onDemandCache = new Map();
4
+ function createLazyComponent(loader, autoLoad) {
5
+ const cache = autoLoad ? autoLoadCache : onDemandCache;
6
+ let warnedDeprecatedLoad = false;
7
+ if (cache.has(loader)) {
8
+ return cache.get(loader);
9
+ }
10
+ const LazyWrapper = ((props) => {
11
+ // console.trace( '[LazyWrapper] Called, autoLoad:', autoLoad, 'status:', LazyWrapper.__status );
12
+ if (LazyWrapper.__status === 'resolved' && LazyWrapper.__component) {
13
+ // console.trace( '[LazyWrapper] Returning resolved component' );
14
+ return h(LazyWrapper.__component, props);
15
+ }
16
+ if (LazyWrapper.__status === 'rejected') {
17
+ // console.trace( '[LazyWrapper] Returning error' );
18
+ return {
19
+ type: '__lazy_error__',
20
+ props: { error: LazyWrapper.__error },
21
+ children: []
22
+ };
23
+ }
24
+ if (!LazyWrapper.__promise && LazyWrapper.__autoLoad) {
25
+ // console.trace( '[LazyWrapper] Auto-loading' );
26
+ LazyWrapper.awake();
27
+ }
28
+ const vnode = {
29
+ type: LazyWrapper.__autoLoad ? '__lazy_pending__' : '__lazy_ondemand_pending__',
30
+ props: { wrapper: LazyWrapper, componentProps: props },
31
+ children: []
32
+ };
33
+ // console.trace( '[LazyWrapper] Returning VNode with type:', vnode.type );
34
+ return vnode;
35
+ });
36
+ LazyWrapper.__lazy = true;
37
+ LazyWrapper.__status = 'pending';
38
+ LazyWrapper.__component = null;
39
+ LazyWrapper.__error = null;
40
+ LazyWrapper.__promise = null;
41
+ LazyWrapper.__autoLoad = autoLoad;
42
+ LazyWrapper.awake = () => {
43
+ // console.trace( '[LazyWrapper.awake] Called, autoLoad:', autoLoad, 'has promise:', !!LazyWrapper.__promise );
44
+ if (LazyWrapper.__promise) {
45
+ // console.trace( '[LazyWrapper.awake] Promise already exists, returning it' );
46
+ return LazyWrapper.__promise;
47
+ }
48
+ // SSR safety
49
+ if (typeof document === 'undefined') {
50
+ return Promise.resolve();
51
+ }
52
+ // console.trace( '[LazyWrapper.awake] Starting loader()' );
53
+ LazyWrapper.__promise = loader()
54
+ .then(mod => {
55
+ let component = null;
56
+ if (mod && typeof mod === 'object' && 'default' in mod && mod.default) {
57
+ component = mod.default;
58
+ }
59
+ else if (typeof mod === 'function') {
60
+ component = mod;
61
+ }
62
+ else if (mod && typeof mod === 'object') {
63
+ const candidates = Object.values(mod).filter((value) => typeof value === 'function');
64
+ if (candidates.length === 1) {
65
+ component = candidates[0];
66
+ }
67
+ }
68
+ if (!component) {
69
+ const keys = mod && typeof mod === 'object' ? Object.keys(mod) : [];
70
+ throw new Error(`Cathode lazy loader: missing component export. Provide a default export or a single named export. Found: [${keys.join(', ')}]`);
71
+ }
72
+ LazyWrapper.__component = component;
73
+ LazyWrapper.__status = 'resolved';
74
+ })
75
+ .catch(err => {
76
+ LazyWrapper.__error = err;
77
+ LazyWrapper.__status = 'rejected';
78
+ });
79
+ return LazyWrapper.__promise;
80
+ };
81
+ LazyWrapper.__load = () => {
82
+ if (!warnedDeprecatedLoad && typeof console !== 'undefined') {
83
+ console.warn('[Cathode] Lazy component __load() is deprecated. Use awake() instead.');
84
+ warnedDeprecatedLoad = true;
85
+ }
86
+ return LazyWrapper.awake();
87
+ };
88
+ cache.set(loader, LazyWrapper);
89
+ return LazyWrapper;
90
+ }
91
+ export function lazy(loader) {
92
+ return createLazyComponent(loader, true);
93
+ }
94
+ export function lazyOnDemand(loader) {
95
+ return createLazyComponent(loader, false);
96
+ }
package/lib/mount.js ADDED
@@ -0,0 +1,17 @@
1
+ import { render } from './runtime.js';
2
+ const mountedParents = new WeakSet();
3
+ export async function mount(node, parent) {
4
+ render(node, parent);
5
+ mountedParents.add(parent);
6
+ return {
7
+ unmount: async () => unmount(parent),
8
+ cleanup: async () => cleanup(parent),
9
+ };
10
+ }
11
+ export async function unmount(parent) {
12
+ parent.innerHTML = '';
13
+ mountedParents.delete(parent);
14
+ }
15
+ export async function cleanup(parent) {
16
+ await unmount(parent);
17
+ }
package/lib/runtime.js ADDED
@@ -0,0 +1,188 @@
1
+ import { findLazyPending } from './suspense.js';
2
+ export function h(type, props, ...children) {
3
+ return { type, props: props || {}, children };
4
+ }
5
+ export const Fragment = (props) => Array.isArray(props.children) ? props.children : [props.children];
6
+ export function render(node, parent) {
7
+ parent.innerHTML = '';
8
+ parent.appendChild(toDOM(node));
9
+ }
10
+ function toDOM(node) {
11
+ if (node === null || node === undefined) {
12
+ return document.createTextNode('');
13
+ }
14
+ if (typeof node === 'string' || typeof node === 'number') {
15
+ return document.createTextNode(String(node));
16
+ }
17
+ if (Array.isArray(node)) {
18
+ const frag = document.createDocumentFragment();
19
+ node.forEach(child => frag.appendChild(toDOM(child)));
20
+ return frag;
21
+ }
22
+ // Debug: log VNode type
23
+ if (node.type === '__lazy_ondemand_pending__') {
24
+ // console.trace( '[DEBUG] toDOM processing __lazy_ondemand_pending__' );
25
+ }
26
+ // Handle Suspense boundary
27
+ if (node.type === '__suspense__') {
28
+ const { fallback, children } = node.props;
29
+ const pending = findLazyPending(children);
30
+ if (pending.length === 0) {
31
+ return toDOM(children);
32
+ }
33
+ const container = document.createElement('div');
34
+ container.setAttribute('data-cathode-suspense', 'true');
35
+ container.appendChild(toDOM(fallback));
36
+ Promise.all(pending.map(lc => lc.awake())).then(() => {
37
+ const content = toDOM(children);
38
+ container.innerHTML = '';
39
+ container.appendChild(content);
40
+ });
41
+ return container;
42
+ }
43
+ // Handle lazy pending marker
44
+ if (node.type === '__lazy_pending__') {
45
+ const { wrapper, componentProps } = node.props;
46
+ const placeholder = document.createElement('div');
47
+ placeholder.setAttribute('data-cathode-lazy', 'pending');
48
+ wrapper.awake().then(() => {
49
+ if (wrapper.__status === 'resolved') {
50
+ const resolved = toDOM(h(wrapper.__component, componentProps));
51
+ placeholder.replaceWith(resolved);
52
+ }
53
+ });
54
+ return placeholder;
55
+ }
56
+ // Handle lazy ondemand pending marker
57
+ if (node.type === '__lazy_ondemand_pending__') {
58
+ const { wrapper, componentProps } = node.props;
59
+ const placeholder = document.createElement('div');
60
+ placeholder.setAttribute('data-cathode-lazy', 'ondemand-pending');
61
+ placeholder.style.display = 'none';
62
+ // Store reference for manual re-render after load
63
+ placeholder.__lazyComponent = wrapper;
64
+ placeholder.__componentProps = componentProps;
65
+ // Set up a mechanism to watch for when awake() is called
66
+ // console.trace( '[lazy ondemand] Created placeholder, status:', wrapper.__status, 'has promise:', !!wrapper.__promise );
67
+ let stopPolling = false;
68
+ let pollCount = 0;
69
+ const checkAndRender = () => {
70
+ pollCount++;
71
+ // Stop if placeholder is no longer in the document
72
+ if (stopPolling) {
73
+ // console.trace( '[lazy ondemand] Stopped polling, stopPolling:', stopPolling );
74
+ return;
75
+ }
76
+ // Wait for element to be connected before checking
77
+ if (!placeholder.isConnected) {
78
+ if (pollCount % 10 === 0) {
79
+ // console.trace( '[lazy ondemand] Waiting for connection...', pollCount );
80
+ }
81
+ setTimeout(checkAndRender, 100);
82
+ return;
83
+ }
84
+ if (wrapper.__promise) {
85
+ // console.trace( '[lazy ondemand] Found promise after', pollCount, 'polls, status:', wrapper.__status );
86
+ stopPolling = true; // Stop polling once we find the promise
87
+ wrapper.__promise.then(() => {
88
+ // console.trace( '[lazy ondemand] Component loaded:', wrapper.__component?.name, 'status:', wrapper.__status, 'connected:', placeholder.isConnected );
89
+ if (wrapper.__status === 'resolved' && placeholder.isConnected) {
90
+ const resolved = toDOM(h(wrapper.__component, componentProps));
91
+ // console.trace( '[lazy ondemand] Replacing placeholder with resolved component' );
92
+ placeholder.replaceWith(resolved);
93
+ }
94
+ });
95
+ }
96
+ else {
97
+ if (pollCount % 10 === 0) {
98
+ // console.trace( '[lazy ondemand] Still polling...', pollCount, 'status:', wrapper.__status );
99
+ }
100
+ // Check again after a short delay
101
+ setTimeout(checkAndRender, 100);
102
+ }
103
+ };
104
+ // Start checking after a small delay to allow DOM connection
105
+ setTimeout(checkAndRender, 0);
106
+ return placeholder;
107
+ }
108
+ // Handle lazy onvisible marker
109
+ if (node.type === '__lazy_onvisible__') {
110
+ const { component, componentProps, fallback, observerOptions } = node.props;
111
+ const container = document.createElement('div');
112
+ container.setAttribute('data-cathode-lazy', 'onvisible');
113
+ if (fallback) {
114
+ container.appendChild(toDOM(fallback));
115
+ }
116
+ if (typeof IntersectionObserver !== 'undefined') {
117
+ const observer = new IntersectionObserver((entries) => {
118
+ entries.forEach((entry) => {
119
+ if (entry.isIntersecting) {
120
+ component.awake().then(() => {
121
+ if (component.__status === 'resolved') {
122
+ const resolved = toDOM(h(component, componentProps));
123
+ container.innerHTML = '';
124
+ container.appendChild(resolved);
125
+ }
126
+ });
127
+ observer.disconnect();
128
+ }
129
+ });
130
+ }, observerOptions);
131
+ observer.observe(container);
132
+ }
133
+ else {
134
+ // SSR or old browser fallback
135
+ component.awake();
136
+ }
137
+ return container;
138
+ }
139
+ // Handle lazy error marker
140
+ if (node.type === '__lazy_error__') {
141
+ const errorDiv = document.createElement('div');
142
+ errorDiv.setAttribute('data-cathode-lazy', 'error');
143
+ errorDiv.textContent = `Error: ${node.props.error?.message || 'Unknown'}`;
144
+ return errorDiv;
145
+ }
146
+ // Handle error boundary
147
+ if (node.type === '__error_boundary__') {
148
+ const { fallback, children } = node.props;
149
+ try {
150
+ return toDOM(children);
151
+ }
152
+ catch (error) {
153
+ if (typeof fallback === 'function') {
154
+ return toDOM(fallback(error));
155
+ }
156
+ return toDOM(fallback);
157
+ }
158
+ }
159
+ if (typeof node.type === 'function') {
160
+ return toDOM(node.type({ ...node.props, children: node.children }));
161
+ }
162
+ const el = document.createElement(node.type);
163
+ const props = node.props || {};
164
+ Object.entries(props).forEach(([key, value]) => {
165
+ if (key === 'className') {
166
+ if (typeof value === 'string') {
167
+ el.setAttribute('class', value);
168
+ }
169
+ }
170
+ else if (key.startsWith('on') && typeof value === 'function') {
171
+ el[key.toLowerCase()] = value;
172
+ }
173
+ else if (value !== false && value !== null) {
174
+ el.setAttribute(key, String(value));
175
+ }
176
+ });
177
+ if (node.children) {
178
+ node.children.forEach((child) => {
179
+ el.appendChild(toDOM(child));
180
+ });
181
+ }
182
+ return el;
183
+ }
184
+ // Expose globals for JSX pragma usage when bundled.
185
+ if (typeof globalThis !== 'undefined') {
186
+ globalThis.h = h;
187
+ globalThis.Fragment = Fragment;
188
+ }
@@ -0,0 +1,40 @@
1
+ export function Suspense(props) {
2
+ return {
3
+ type: '__suspense__',
4
+ props: {
5
+ fallback: props.fallback,
6
+ children: props.children
7
+ },
8
+ children: Array.isArray(props.children) ? props.children : [props.children]
9
+ };
10
+ }
11
+ export function findLazyPending(node) {
12
+ if (!node || typeof node === 'string' || typeof node === 'number') {
13
+ return [];
14
+ }
15
+ if (Array.isArray(node)) {
16
+ return node.flatMap(n => findLazyPending(n));
17
+ }
18
+ const vnode = node;
19
+ // Check if type is a lazy component function (has __lazy marker)
20
+ if (typeof vnode.type === 'function' && vnode.type.__lazy) {
21
+ const lazyComp = vnode.type;
22
+ // Only return if still pending AND (autoLoad OR already manually loading)
23
+ if (lazyComp.__status === 'pending' && (lazyComp.__autoLoad || lazyComp.__promise)) {
24
+ return [lazyComp];
25
+ }
26
+ }
27
+ // Also check for already-rendered lazy pending marker
28
+ if (vnode.type === '__lazy_pending__') {
29
+ return [vnode.props.wrapper];
30
+ }
31
+ // Skip ondemand pending markers (not auto-loading)
32
+ if (vnode.type === '__lazy_ondemand_pending__') {
33
+ return [];
34
+ }
35
+ // Recurse into children
36
+ if (vnode.children) {
37
+ return vnode.children.flatMap(c => findLazyPending(c));
38
+ }
39
+ return [];
40
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@relicloops/cathode",
3
+ "version": "2.0.0",
4
+ "license": "Apache-2.0",
5
+ "description": "Lightweight JSX runtime.",
6
+ "type": "module",
7
+ "main": "index.js",
8
+ "types": "./types/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./types/index.d.ts",
12
+ "default": "./index.js"
13
+ },
14
+ "./jsx-runtime": {
15
+ "types": "./types/jsx-runtime.d.ts",
16
+ "default": "./jsx-runtime.js"
17
+ },
18
+ "./jsx-dev-runtime": {
19
+ "types": "./types/jsx-dev-runtime.d.ts",
20
+ "default": "./jsx-dev-runtime.js"
21
+ }
22
+ },
23
+ "files": [
24
+ "index.js",
25
+ "jsx-runtime.js",
26
+ "jsx-dev-runtime.js",
27
+ "lib/**/*.js",
28
+ "types/**/*.d.ts",
29
+ "README.md",
30
+ "LICENSE"
31
+ ],
32
+ "publishConfig": {
33
+ "access": "public"
34
+ }
35
+ }
@@ -0,0 +1,21 @@
1
+ export * from './lib/runtime.js';
2
+ export { inject } from './lib/inject.js';
3
+ export type { InjectMode, InjectOptions } from './lib/inject.js';
4
+ export { eject } from './lib/eject.js';
5
+ export { mount, unmount, cleanup } from './lib/mount.js';
6
+ export type { MountHandle } from './lib/mount.js';
7
+ export { css } from './lib/css.js';
8
+ export type { CSSOptions } from './lib/css.js';
9
+ export { lazy, lazyOnDemand } from './lib/lazy.js';
10
+ export type { ComponentType, LazyComponent } from './lib/lazy.js';
11
+ export { lazyOnHover, lazyAfterDelay, lazyWhenIdle, LazyOnVisible } from './lib/lazy-helpers.js';
12
+ export { Suspense } from './lib/suspense.js';
13
+ export type { SuspenseProps } from './lib/suspense.js';
14
+ export { ErrorBoundary } from './lib/error-boundary.js';
15
+ export type { ErrorBoundaryProps } from './lib/error-boundary.js';
16
+ declare global {
17
+ namespace JSX {
18
+ interface IntrinsicElements extends Record<string, any> {
19
+ }
20
+ }
21
+ }
@@ -0,0 +1 @@
1
+ export * from './jsx-runtime.js';
@@ -0,0 +1,17 @@
1
+ import type { VNode } from './lib/runtime.js';
2
+ import { Fragment } from './lib/runtime.js';
3
+ export { Fragment };
4
+ export type { VNode };
5
+ type JSXProps = Record<string, unknown> & {
6
+ children?: unknown;
7
+ };
8
+ export declare namespace JSX {
9
+ interface Element extends VNode {
10
+ }
11
+ interface IntrinsicElements extends Record<string, any> {
12
+ }
13
+ }
14
+ export type JSXIntrinsicElements = JSX.IntrinsicElements;
15
+ export declare function jsx(type: VNode['type'], props: JSXProps | null, key?: unknown): JSX.Element;
16
+ export declare const jsxs: typeof jsx;
17
+ export declare const jsxDEV: typeof jsx;
@@ -0,0 +1,4 @@
1
+ export interface CSSOptions {
2
+ inline?: boolean;
3
+ }
4
+ export declare function css(urlOrContent: string, options?: CSSOptions): void;
@@ -0,0 +1 @@
1
+ export declare function eject(parent: HTMLElement): Promise<void>;
@@ -0,0 +1,6 @@
1
+ import type { VNode } from './runtime.js';
2
+ export interface ErrorBoundaryProps {
3
+ fallback: VNode | string | ((error: Error) => VNode | string);
4
+ children?: any;
5
+ }
6
+ export declare function ErrorBoundary(props: ErrorBoundaryProps): VNode;
@@ -0,0 +1,5 @@
1
+ export type InjectMode = 'append' | 'prepend' | 'replace';
2
+ export type InjectOptions = {
3
+ mode?: InjectMode;
4
+ };
5
+ export declare function inject(node: any, parent: HTMLElement, options?: InjectOptions): Promise<void>;
@@ -0,0 +1,61 @@
1
+ import type { LazyComponent } from './lazy.js';
2
+ import type { VNode } from './runtime.js';
3
+ /**
4
+ * Returns event props that trigger lazy loading on hover
5
+ *
6
+ * @example
7
+ * const Dashboard = lazyOnDemand(() => import('./Dashboard.js'));
8
+ * const hoverProps = lazyOnHover(Dashboard);
9
+ * <a href="/dashboard" {...hoverProps}>Dashboard</a>
10
+ */
11
+ export declare function lazyOnHover<P = any>(component: LazyComponent<P>): {
12
+ onMouseEnter: () => void;
13
+ };
14
+ /**
15
+ * Triggers lazy loading after a delay
16
+ *
17
+ * @param component - The lazy component to load
18
+ * @param delayMs - Delay in milliseconds before loading
19
+ * @returns Cleanup function to cancel the timeout
20
+ *
21
+ * @example
22
+ * const Analytics = lazyOnDemand(() => import('./Analytics.js'));
23
+ * lazyAfterDelay(Analytics, 5000); // Load after 5 seconds
24
+ */
25
+ export declare function lazyAfterDelay<P = any>(component: LazyComponent<P>, delayMs: number): () => void;
26
+ /**
27
+ * Triggers lazy loading when the browser is idle
28
+ *
29
+ * @param component - The lazy component to load
30
+ * @param timeout - Maximum time to wait before loading (default: 5000ms)
31
+ * @returns Cleanup function to cancel the idle callback
32
+ *
33
+ * @example
34
+ * const ChatWidget = lazyOnDemand(() => import('./ChatWidget.js'));
35
+ * lazyWhenIdle(ChatWidget); // Load when browser idle
36
+ */
37
+ export declare function lazyWhenIdle<P = any>(component: LazyComponent<P>, timeout?: number): () => void;
38
+ /**
39
+ * Component wrapper that triggers lazy loading when scrolled into view
40
+ *
41
+ * @param props.component - The lazy component to load
42
+ * @param props.componentProps - Props to pass to the component when loaded
43
+ * @param props.fallback - Content to show before component is visible
44
+ * @param props.rootMargin - Margin around root for intersection detection (e.g., "200px")
45
+ * @param props.threshold - Visibility threshold (0-1) that triggers loading
46
+ *
47
+ * @example
48
+ * const Footer = lazyOnDemand(() => import('./Footer.js'));
49
+ * <LazyOnVisible
50
+ * component={Footer}
51
+ * fallback={<div style="height: 200px">Scroll to load footer</div>}
52
+ * rootMargin="200px"
53
+ * />
54
+ */
55
+ export declare function LazyOnVisible<P = any>(props: {
56
+ component: LazyComponent<P>;
57
+ componentProps?: P;
58
+ fallback?: VNode | string;
59
+ rootMargin?: string;
60
+ threshold?: number;
61
+ }): VNode;
@@ -0,0 +1,20 @@
1
+ import type { VNode } from './runtime.js';
2
+ export type ComponentType<P = any> = (props: P) => VNode | VNode[] | string | number | null | undefined;
3
+ type LazyLoader<P = any> = () => Promise<{
4
+ default: ComponentType<P>;
5
+ }>;
6
+ export interface LazyComponent<P = any> {
7
+ (props: P): VNode;
8
+ __lazy: true;
9
+ __status: 'pending' | 'resolved' | 'rejected';
10
+ __component: ComponentType<P> | null;
11
+ __error: Error | null;
12
+ __promise: Promise<void> | null;
13
+ /** @deprecated Use awake() instead. */
14
+ __load: () => Promise<void>;
15
+ awake: () => Promise<void>;
16
+ __autoLoad: boolean;
17
+ }
18
+ export declare function lazy<P = any>(loader: LazyLoader<P>): LazyComponent<P>;
19
+ export declare function lazyOnDemand<P = any>(loader: LazyLoader<P>): LazyComponent<P>;
20
+ export {};
@@ -0,0 +1,7 @@
1
+ export type MountHandle = {
2
+ unmount: () => Promise<void>;
3
+ cleanup: () => Promise<void>;
4
+ };
5
+ export declare function mount(node: any, parent: HTMLElement): Promise<MountHandle>;
6
+ export declare function unmount(parent: HTMLElement): Promise<void>;
7
+ export declare function cleanup(parent: HTMLElement): Promise<void>;
@@ -0,0 +1,10 @@
1
+ export type VNode = {
2
+ type: string | ((props: any) => VNode | VNode[] | string | number | null | undefined);
3
+ props: Record<string, any>;
4
+ children: Array<VNode | string | number | null | undefined>;
5
+ };
6
+ export declare function h(type: VNode['type'], props: Record<string, any> | null, ...children: Array<VNode | string | number | null | undefined>): VNode;
7
+ export declare const Fragment: (props: {
8
+ children?: any;
9
+ }) => any[];
10
+ export declare function render(node: any, parent: HTMLElement): void;