what-core 0.6.7 → 0.6.9

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/hooks.d.ts CHANGED
@@ -1,25 +1,26 @@
1
1
  // What Framework - Hooks Type Definitions
2
2
 
3
- import { Signal } from './index.js';
3
+ import { Signal, Computed, Updater, VNodeChild } from './index.js';
4
4
 
5
- export function useState<T>(initial: T | (() => T)): [Signal<T>, (value: T | ((prev: T) => T)) => void];
6
- export function useSignal<T>(initial: T): Signal<T>;
7
- export function useComputed<T>(fn: () => T): Signal<T>;
5
+ export function useState<T>(initial: T | (() => T)): [Signal<T>, (value: Updater<T>) => void];
6
+ export function useSignal<T>(initial: T | (() => T)): Signal<T>;
7
+ export function useComputed<T>(fn: () => T): Computed<T>;
8
8
  export function useEffect(fn: () => void | (() => void), deps?: any[]): void;
9
- export function useMemo<T>(fn: () => T, deps?: any[]): T;
9
+ export function useMemo<T>(fn: () => T, deps?: any[]): Computed<T>;
10
10
  export function useCallback<T extends (...args: any[]) => any>(fn: T, deps?: any[]): T;
11
- export function useRef<T>(initial?: T): { current: T };
11
+ export function useRef<T>(initial?: T): { current: T | undefined };
12
12
  export function useContext<T>(context: { _defaultValue: T }): T;
13
- export function createContext<T>(defaultValue?: T): { _defaultValue: T; Provider: (props: { value: T; children: any }) => any };
13
+ export function createContext<T>(defaultValue?: T): { _defaultValue: T; Provider: (props: { value: T; children?: VNodeChild }) => VNodeChild; Consumer: (props: { children: (value: T) => VNodeChild }) => VNodeChild };
14
14
  export function useReducer<S, A>(reducer: (state: S, action: A) => S, initialState: S, init?: (s: S) => S): [Signal<S>, (action: A) => void];
15
15
  export function onMount(fn: () => void | (() => void)): void;
16
16
  export function onCleanup(fn: () => void): void;
17
17
 
18
- export interface Resource<T> {
19
- (): T | undefined;
18
+ export function createResource<T = any, S = any>(
19
+ fetcher: (source?: S, ctx?: { signal: AbortSignal }) => Promise<T> | T,
20
+ options?: { initialValue?: T; source?: S },
21
+ ): [Signal<T | null>, {
20
22
  loading: Signal<boolean>;
21
- error: Signal<Error | null>;
22
- refetch: () => void;
23
- mutate: (value: T) => void;
24
- }
25
- export function createResource<T>(fetcher: () => Promise<T>, options?: { initialValue?: T }): Resource<T>;
23
+ error: Signal<any>;
24
+ refetch: (source?: S) => Promise<any>;
25
+ mutate: (value: Updater<T | null>) => void;
26
+ }];
package/index.d.ts CHANGED
@@ -130,11 +130,11 @@ export function classList(el: Element, classes: Record<string, boolean | (() =>
130
130
 
131
131
  // --- Hooks ---
132
132
 
133
- export function useState<T>(initial: T | (() => T)): [T, (value: Updater<T>) => void];
133
+ export function useState<T>(initial: T | (() => T)): [Signal<T>, (value: Updater<T>) => void];
134
134
  export function useSignal<T>(initial: T | (() => T)): Signal<T>;
135
135
  export function useComputed<T>(fn: () => T): Computed<T>;
136
136
  export function useEffect(fn: () => void | (() => void), deps?: unknown[]): void;
137
- export function useMemo<T>(fn: () => T, deps?: unknown[]): T;
137
+ export function useMemo<T>(fn: () => T, deps?: unknown[]): Computed<T>;
138
138
  export function useCallback<T extends (...args: any[]) => any>(fn: T, deps?: unknown[]): T;
139
139
  export function useRef<T>(initial: T): { current: T };
140
140
 
@@ -149,7 +149,7 @@ export function useReducer<S, A>(
149
149
  reducer: (state: S, action: A) => S,
150
150
  initialState: S,
151
151
  init?: (initial: S) => S,
152
- ): [S, (action: A) => void];
152
+ ): [Signal<S>, (action: A) => void];
153
153
  export function onMount(fn: () => void): void;
154
154
  export function onCleanup(fn: () => void): void;
155
155
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "what-core",
3
- "version": "0.6.7",
3
+ "version": "0.6.9",
4
4
  "description": "What Framework - The closest framework to vanilla JS",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -73,7 +73,7 @@
73
73
  "license": "MIT",
74
74
  "repository": {
75
75
  "type": "git",
76
- "url": "https://github.com/CelsianJs/what-framework"
76
+ "url": "git+https://github.com/CelsianJs/what-framework.git"
77
77
  },
78
78
  "bugs": {
79
79
  "url": "https://github.com/CelsianJs/what-framework/issues"
package/src/dom.js CHANGED
@@ -589,28 +589,8 @@ function applyProps(el, newProps, oldProps, isSvg) {
589
589
  }
590
590
 
591
591
  function setProp(el, key, value, isSvg) {
592
- // Sanitize URL attributes before property assignment or setAttribute can commit them.
593
- if (!isSafeUrlAttributeValue(key, value)) {
594
- if (typeof console !== 'undefined') {
595
- console.warn(`[what] Blocked unsafe URL in "${key}" attribute: ${value}`);
596
- }
597
- el.removeAttribute(getDomAttributeName(key));
598
- return;
599
- }
600
- // Reactive function props — wrap in effect for fine-grained updates
601
- if (typeof value === 'function' && !(key.startsWith('on') && key.length > 2) && key !== 'ref') {
602
- if (!el._propEffects) el._propEffects = {};
603
- if (el._propEffects[key]) {
604
- try { el._propEffects[key](); } catch (e) { /* already disposed */ }
605
- }
606
- el._propEffects[key] = effect(() => {
607
- const resolved = value();
608
- setProp(el, key, resolved, isSvg);
609
- });
610
- return;
611
- }
612
-
613
- // Event handlers
592
+ // Event handlers are not URL-bearing attributes; keep this fast path ahead of
593
+ // URL sanitization so hot event mount paths do not pay attr normalization costs.
614
594
  if (key.startsWith('on') && key.length > 2) {
615
595
  let eventName = key.slice(2);
616
596
  let useCapture = false;
@@ -636,6 +616,35 @@ function setProp(el, key, value, isSvg) {
636
616
  return;
637
617
  }
638
618
 
619
+ // Refs are object/function assignments, not DOM URL sinks. Keep them out of
620
+ // URL sanitization when setProp is reached directly by reactive updates.
621
+ if (key === 'ref') {
622
+ if (typeof value === 'function') value(el);
623
+ else if (value) value.current = el;
624
+ return;
625
+ }
626
+
627
+ // Sanitize URL attributes before property assignment or setAttribute can commit them.
628
+ if (!isSafeUrlAttributeValue(key, value)) {
629
+ if (typeof console !== 'undefined') {
630
+ console.warn(`[what] Blocked unsafe URL in "${key}" attribute: ${value}`);
631
+ }
632
+ el.removeAttribute(getDomAttributeName(key));
633
+ return;
634
+ }
635
+ // Reactive function props — wrap in effect for fine-grained updates
636
+ if (typeof value === 'function') {
637
+ if (!el._propEffects) el._propEffects = {};
638
+ if (el._propEffects[key]) {
639
+ try { el._propEffects[key](); } catch (e) { /* already disposed */ }
640
+ }
641
+ el._propEffects[key] = effect(() => {
642
+ const resolved = value();
643
+ setProp(el, key, resolved, isSvg);
644
+ });
645
+ return;
646
+ }
647
+
639
648
  // className / class
640
649
  if (key === 'className' || key === 'class') {
641
650
  if (isSvg) {
package/src/index.js CHANGED
@@ -15,12 +15,16 @@ export { h, Fragment, html } from './h.js';
15
15
  // DOM mounting & rendering (fine-grained, no VDOM reconciler)
16
16
  export { mount } from './dom.js';
17
17
 
18
- // Hooks — Solid-style API (signal-first)
19
- // React-style hooks (useState, useEffect, useMemo, useCallback, useRef)
20
- // are available via 'what-framework/react-compat' only.
18
+ // Hooks — run-once component hooks backed by signals.
19
+ // useState returns a signal accessor plus setter; useMemo returns a computed signal.
21
20
  export {
21
+ useState,
22
22
  useSignal,
23
23
  useComputed,
24
+ useEffect,
25
+ useMemo,
26
+ useCallback,
27
+ useRef,
24
28
  useContext,
25
29
  useReducer,
26
30
  createContext,
package/src/reactive.js CHANGED
@@ -323,36 +323,13 @@ function _runEffect(e) {
323
323
 
324
324
  // Stable effect fast path: deps don't change, skip cleanup/re-subscribe.
325
325
  if (e._stable) {
326
- if (e._cleanup) {
327
- try { e._cleanup(); } catch (err) {
328
- if (__DEV__) console.warn('[what] Error in effect cleanup:', err);
329
- }
330
- e._cleanup = null;
331
- }
332
- const prev = currentEffect;
333
- currentEffect = null; // Don't re-track deps (already subscribed)
334
- try {
335
- const result = e.fn();
336
- if (typeof result === 'function') e._cleanup = result;
337
- } catch (err) {
338
- if (__devtools?.onError) __devtools.onError(err, { type: 'effect', effect: e });
339
- if (__DEV__) console.warn('[what] Error in stable effect:', err);
340
- } finally {
341
- currentEffect = prev;
342
- }
343
- if (__DEV__ && __devtools?.onEffectRun) __devtools.onEffectRun(e);
326
+ runStableEffect(e);
344
327
  return;
345
328
  }
346
329
 
347
330
  cleanup(e);
348
331
  // Run effect cleanup from previous run
349
- if (e._cleanup) {
350
- try { e._cleanup(); } catch (err) {
351
- if (__devtools?.onError) __devtools.onError(err, { type: 'effect-cleanup', effect: e });
352
- if (__DEV__) console.warn('[what] Error in effect cleanup:', err);
353
- }
354
- e._cleanup = null;
355
- }
332
+ runEffectCleanup(e, 'effect cleanup');
356
333
  const prev = currentEffect;
357
334
  currentEffect = e;
358
335
  try {
@@ -376,14 +353,41 @@ function _disposeEffect(e) {
376
353
  if (__DEV__ && __devtools) __devtools.onEffectDispose(e);
377
354
  cleanup(e);
378
355
  // Run cleanup on dispose
379
- if (e._cleanup) {
380
- try { e._cleanup(); } catch (err) {
381
- if (__DEV__) console.warn('[what] Error in effect cleanup on dispose:', err);
382
- }
383
- e._cleanup = null;
356
+ runEffectCleanup(e, 'effect cleanup on dispose');
357
+ }
358
+
359
+ function reportEffectCleanupError(err, e, phase) {
360
+ if (__devtools?.onError) __devtools.onError(err, { type: 'effect-cleanup', effect: e, phase });
361
+ if (__DEV__) console.warn(`[what] Error in ${phase}:`, err);
362
+ }
363
+
364
+ function runEffectCleanup(e, phase) {
365
+ if (!e._cleanup) return;
366
+ const cleanupFn = e._cleanup;
367
+ e._cleanup = null;
368
+ try {
369
+ cleanupFn();
370
+ } catch (err) {
371
+ reportEffectCleanupError(err, e, phase);
384
372
  }
385
373
  }
386
374
 
375
+ function runStableEffect(e) {
376
+ const prev = currentEffect;
377
+ currentEffect = null; // Don't re-track deps (already subscribed)
378
+ try {
379
+ runEffectCleanup(e, 'stable effect cleanup');
380
+ const result = e.fn();
381
+ if (typeof result === 'function') e._cleanup = result;
382
+ } catch (err) {
383
+ if (__devtools?.onError) __devtools.onError(err, { type: 'effect', effect: e });
384
+ if (__DEV__) console.warn('[what] Error in stable effect:', err);
385
+ } finally {
386
+ currentEffect = prev;
387
+ }
388
+ if (__DEV__ && __devtools?.onEffectRun) __devtools.onEffectRun(e);
389
+ }
390
+
387
391
  function cleanup(e) {
388
392
  const deps = e.deps;
389
393
  for (let i = 0; i < deps.length; i++) deps[i].delete(e);
@@ -413,21 +417,7 @@ function notify(subs) {
413
417
  // _onNotify may call notify() recursively — tracked by notifyDepth.
414
418
  e._onNotify();
415
419
  } else if (batchDepth === 0 && e._stable) {
416
- // Inline execution for stable effects
417
- const prev = currentEffect;
418
- currentEffect = null;
419
- try {
420
- const result = e.fn();
421
- if (typeof result === 'function') {
422
- if (e._cleanup) try { e._cleanup(); } catch (err) {}
423
- e._cleanup = result;
424
- }
425
- } catch (err) {
426
- if (__devtools?.onError) __devtools.onError(err, { type: 'effect', effect: e });
427
- if (__DEV__) console.warn('[what] Error in stable effect:', err);
428
- } finally {
429
- currentEffect = prev;
430
- }
420
+ runStableEffect(e);
431
421
  } else if (!e._pending) {
432
422
  e._pending = true;
433
423
  const level = e._level;
@@ -450,20 +440,7 @@ function notify(subs) {
450
440
  if (e._onNotify) {
451
441
  e._onNotify();
452
442
  } else if (batchDepth === 0 && e._stable) {
453
- const prev = currentEffect;
454
- currentEffect = null;
455
- try {
456
- const result = e.fn();
457
- if (typeof result === 'function') {
458
- if (e._cleanup) try { e._cleanup(); } catch (err) {}
459
- e._cleanup = result;
460
- }
461
- } catch (err) {
462
- if (__devtools?.onError) __devtools.onError(err, { type: 'effect', effect: e });
463
- if (__DEV__) console.warn('[what] Error in stable effect:', err);
464
- } finally {
465
- currentEffect = prev;
466
- }
443
+ runStableEffect(e);
467
444
  } else if (!e._pending) {
468
445
  e._pending = true;
469
446
  const level = e._level;