what-core 0.12.3 → 0.13.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/index.d.ts CHANGED
@@ -28,7 +28,13 @@ export function signal<T>(initial: T, debugName?: string): Signal<T>;
28
28
  export function computed<T>(fn: () => T): Computed<T>;
29
29
  export function effect(fn: () => void | (() => void), opts?: { stable?: boolean }): () => void;
30
30
  export function signalMemo<T>(fn: () => T): Computed<T>;
31
- export function batch<T>(fn: () => T): T;
31
+ /**
32
+ * Group signal writes so effects run once at the end. The callback's return
33
+ * value is DISCARDED: batch() returns undefined. It was declared as `<T>(fn: ()
34
+ * => T) => T`, so `const rows = batch(() => compute())` typechecked and handed
35
+ * back undefined at runtime.
36
+ */
37
+ export function batch(fn: () => unknown): void;
32
38
  export function untrack<T>(fn: () => T): T;
33
39
  export function flushSync(): void;
34
40
  export function createRoot<T>(fn: (dispose: () => void) => T): T;
@@ -46,10 +52,17 @@ export function onRootCleanup(fn: () => void): void;
46
52
  // --- Virtual DOM ---
47
53
 
48
54
  export type PrimitiveChild = string | number | boolean | null | undefined;
49
- export type VNodeChild = PrimitiveChild | VNode | (() => VNodeChild) | VNodeChild[];
55
+
56
+ // `VNode<any>`, not `VNode`. VNode is invariant in P (its `tag` holds a
57
+ // `Component<P>`, whose parameter position is contravariant under
58
+ // strictFunctionTypes), so `VNode` — which means `VNode<Record<string, any>>` —
59
+ // rejects every specifically-typed node. `h('div', {}, h('h1', { style: '' }))`
60
+ // did not compile for any TypeScript user before 0.12.5, and neither did
61
+ // passing that tree to mount().
62
+ export type VNodeChild = PrimitiveChild | VNode<any> | (() => VNodeChild) | VNodeChild[];
50
63
 
51
64
  /** A component may legitimately render nothing, so `null` is part of the contract. */
52
- export type Component<P = {}> = ((props: P & { children?: VNodeChild }) => VNode | null) & {
65
+ export type Component<P = {}> = ((props: P & { children?: VNodeChild }) => VNode<any> | null) & {
53
66
  /**
54
67
  * Opt out of realizing compiled children before the component runs.
55
68
  *
@@ -83,7 +96,7 @@ export function html(strings: TemplateStringsArray, ...values: any[]): VNode | V
83
96
 
84
97
  // --- DOM ---
85
98
 
86
- export function mount(vnode: VNodeChild, container: string | Element): () => void;
99
+ export function mount(vnode: VNodeChild, container: string | Element | DocumentFragment): () => void;
87
100
 
88
101
  /** Attach reactive bindings to server-rendered DOM instead of creating it. */
89
102
  export function hydrate(vnode: VNodeChild, container: Element): Node | null;
@@ -106,11 +119,33 @@ export function classList(el: Element, classes: Record<string, boolean | (() =>
106
119
 
107
120
  // --- Hooks ---
108
121
 
109
- export function useState<T>(initial: T | (() => T)): [T, (value: Updater<T>) => void];
122
+ /**
123
+ * Returns [signal, setter]. The first element is the SIGNAL ITSELF, not a
124
+ * snapshot value: components run once, so there is no re-render to hand a new
125
+ * `T` to. Read it by calling it (`count()`), or pass it straight into JSX where
126
+ * insert() binds it reactively.
127
+ *
128
+ * It was declared as `[T, setter]`, which made the correct code (`count()`) a
129
+ * type error and the wrong code (`count + 1`) type-check.
130
+ */
131
+ export function useState<T>(initial: T | (() => T)): [Signal<T>, (value: Updater<T>) => void];
110
132
  export function useSignal<T>(initial: T | (() => T)): Signal<T>;
111
133
  export function useComputed<T>(fn: () => T): Computed<T>;
112
134
  export function useEffect(fn: () => void | (() => void), deps?: unknown[]): void;
113
- export function useMemo<T>(fn: () => T, deps?: unknown[]): T;
135
+ /**
136
+ * Returns a COMPUTED ACCESSOR, not the value. Same reason as useState: the
137
+ * component body runs once, so nothing would ever hand back a refreshed `T`.
138
+ * Read it by calling it (`total()`), or pass the accessor straight into JSX
139
+ * where insert() tracks it.
140
+ *
141
+ * `deps` is accepted for React familiarity and ignored at runtime: computed()
142
+ * tracks whatever signals the callback actually reads.
143
+ *
144
+ * It was declared as `T`, which inverted both halves: `useMemo(...) * 2`
145
+ * type-checked and produced NaN, while the correct `useMemo(...)()` was the
146
+ * type error.
147
+ */
148
+ export function useMemo<T>(fn: () => T, deps?: unknown[]): Computed<T>;
114
149
  export function useCallback<T extends (...args: any[]) => any>(fn: T, deps?: unknown[]): T;
115
150
  export function useRef<T>(initial: T): { current: T };
116
151
 
@@ -121,11 +156,19 @@ export interface Context<T> {
121
156
 
122
157
  export function createContext<T>(defaultValue: T): Context<T>;
123
158
  export function useContext<T>(context: Context<T>): T;
159
+ /**
160
+ * Returns [signal, dispatch]. The first element is the SIGNAL ITSELF, exactly
161
+ * as useState returns it and for the same run-once reason. Read it with
162
+ * `state()`.
163
+ *
164
+ * It was declared as `[S, dispatch]`, so `state.items` type-checked (and read
165
+ * `undefined` off a function object) while `state()` did not.
166
+ */
124
167
  export function useReducer<S, A>(
125
168
  reducer: (state: S, action: A) => S,
126
169
  initialState: S,
127
170
  init?: (initial: S) => S,
128
- ): [S, (action: A) => void];
171
+ ): [Signal<S>, (action: A) => void];
129
172
  export function onMount(fn: () => void): void;
130
173
  export function onCleanup(fn: () => void): void;
131
174
 
@@ -190,11 +233,22 @@ export function Island(props: IslandProps): VNode;
190
233
 
191
234
  // --- State ---
192
235
 
193
- export type DerivedFn<T> = ((state: any) => T) & { _isDerived: true };
236
+ /** `_storeComputed` is the marker createStore() actually reads to tell a derived value from an action. */
237
+ export type DerivedFn<T> = ((state: any) => T) & { _storeComputed: true };
194
238
  export function derived<T>(fn: (state: any) => T): DerivedFn<T>;
239
+ /** @deprecated Use derived(). Warns once at runtime. */
195
240
  export function storeComputed<T>(fn: (state: any) => T): DerivedFn<T>;
196
241
  export type StoreDefinition = Record<string, any>;
197
- export type Store<T extends StoreDefinition> = T;
242
+ /**
243
+ * The store hook resolves every derived key to its VALUE. State and derived
244
+ * keys are both getters on the returned object, only actions stay callable.
245
+ * `Store<T> = T` left a derived key typed as the DerivedFn you wrote in the
246
+ * definition, so `store.total` looked callable and `store.total * 2` was the
247
+ * type error, which is backwards from the runtime.
248
+ */
249
+ export type Store<T extends StoreDefinition> = {
250
+ [K in keyof T]: T[K] extends DerivedFn<infer U> ? U : T[K];
251
+ };
198
252
  export function createStore<T extends StoreDefinition>(definition: T): () => Store<T>;
199
253
  export function atom<T>(initial: T): Signal<T>;
200
254
 
@@ -280,31 +334,153 @@ export function smoothScrollTo(
280
334
  ): Promise<void>;
281
335
 
282
336
  // --- Animation ---
337
+ //
338
+ // Every accessor below is a plain getter function, NOT a Signal: the objects
339
+ // these factories return expose reads through closures and offer writes as
340
+ // named methods (set/snap/setValue), so nothing here carries .set/.peek. The
341
+ // declarations used to promise Signal<number> and a single shared SpringValue
342
+ // shape for both spring() and tween(), which are two different objects.
343
+
344
+ export interface SpringConfig {
345
+ stiffness?: number;
346
+ damping?: number;
347
+ mass?: number;
348
+ precision?: number;
349
+ }
283
350
 
351
+ /** Physics-based value. `set()` retargets and animates; `snap()` jumps without animating. */
284
352
  export interface SpringValue {
285
353
  current(): number;
286
- set(value: number): void;
354
+ target(): number;
355
+ velocity(): number;
356
+ isAnimating(): boolean;
357
+ set(target: number): void;
287
358
  stop(): void;
288
- reset(): void;
359
+ snap(value: number): void;
360
+ subscribe(fn: (value: number) => void): () => void;
361
+ }
362
+
363
+ export interface TweenConfig {
364
+ duration?: number;
365
+ easing?: (t: number) => number;
366
+ onUpdate?: (value: number, progress: number) => void;
367
+ onComplete?: () => void;
368
+ }
369
+
370
+ /**
371
+ * Easing-based interpolation from `from` to `to`. Starts immediately on
372
+ * creation and runs to completion, so it exposes `cancel()` rather than the
373
+ * spring's retargeting `set()`/`stop()`.
374
+ */
375
+ export interface TweenValue {
376
+ progress(): number;
377
+ value(): number;
378
+ isAnimating(): boolean;
379
+ cancel(): void;
380
+ subscribe(fn: (value: number) => void): () => void;
289
381
  }
290
382
 
291
- export function spring(initialValue?: number, config?: Record<string, any>): SpringValue;
292
- export function tween(initialValue?: number, config?: Record<string, any>): SpringValue;
383
+ export function spring(initialValue: number, config?: SpringConfig): SpringValue;
384
+ /**
385
+ * Both endpoints are required and positional. This was declared as
386
+ * `tween(initialValue?, config?)` returning a SpringValue, so the documented
387
+ * call `tween(0, 100, { duration: 300 })` was a type error, while
388
+ * `tween(0).current()` type-checked and threw (`current` does not exist, and a
389
+ * missing `to` interpolates toward undefined).
390
+ */
391
+ export function tween(from: number, to: number, config?: TweenConfig): TweenValue;
293
392
  export const easings: Record<string, (t: number) => number>;
294
- export function useTransition(options?: Record<string, any>): {
295
- mounted: Signal<boolean>;
296
- styles: Computed<Record<string, any>>;
297
- show: () => void;
298
- hide: () => void;
393
+ /**
394
+ * Drives a 0..1 progress value over `duration`. This is NOT React's
395
+ * useTransition, and it never had the mounted/styles/show/hide members that
396
+ * were declared here, and all four were undefined at runtime.
397
+ */
398
+ export function useTransition(options?: { duration?: number; easing?: (t: number) => number }): {
399
+ isTransitioning: () => boolean;
400
+ progress: () => number;
401
+ start: (callback?: () => void) => Promise<void>;
299
402
  };
300
- export function useGesture(ref: { current?: Element | null } | Element, handlers?: Record<string, (payload: any) => void>): void;
301
- export function useAnimatedValue(initialValue?: number): {
302
- value: Signal<number>;
303
- animateTo: (target: number, config?: Record<string, any>) => Promise<void>;
403
+
404
+ /** Live gesture state. `startX`/`startY` are plain numbers rewritten in place at gesture start. */
405
+ export interface GestureState {
406
+ isDragging: Signal<boolean>;
407
+ startX: number;
408
+ startY: number;
409
+ currentX: Signal<number>;
410
+ currentY: Signal<number>;
411
+ deltaX: Signal<number>;
412
+ deltaY: Signal<number>;
413
+ velocity: Signal<GestureVelocity>;
414
+ }
415
+
416
+ export interface GestureVelocity {
417
+ x: number;
418
+ y: number;
419
+ }
420
+
421
+ /**
422
+ * `preventDefault` is why this cannot be a `Record<string, (payload) => void>`:
423
+ * it sits in the same object as the callbacks and is a boolean.
424
+ */
425
+ export interface GestureHandlers {
426
+ onDragStart?: (payload: { x: number; y: number }) => void;
427
+ onDrag?: (payload: {
428
+ x: number; y: number; deltaX: number; deltaY: number; velocity: GestureVelocity;
429
+ }) => void;
430
+ onDragEnd?: (payload: { deltaX: number; deltaY: number; velocity: GestureVelocity }) => void;
431
+ onPinch?: (payload: { scale: number; centerX: number; centerY: number }) => void;
432
+ onSwipe?: (payload: { direction: 'up' | 'down' | 'left' | 'right'; velocity: GestureVelocity }) => void;
433
+ onTap?: (payload: { x: number; y: number }) => void;
434
+ onLongPress?: (payload: { x: number; y: number }) => void;
435
+ /** Opt in to e.preventDefault() in the touch handlers (listeners become non-passive). */
436
+ preventDefault?: boolean;
437
+ }
438
+
439
+ /** Returns the gesture state. It was declared `void`, so the state was unreachable from TypeScript. */
440
+ export function useGesture(
441
+ ref: { current?: Element | null } | Element | (() => Element | null),
442
+ handlers?: GestureHandlers,
443
+ ): GestureState;
444
+
445
+ /** Handle for one running animation started by useAnimatedValue. */
446
+ export interface AnimationHandle {
304
447
  stop: () => void;
448
+ }
449
+
450
+ /**
451
+ * `animateTo` and `stop` were declared here and have never existed. Animations
452
+ * are started with spring()/timing(), each returning its own handle to stop.
453
+ */
454
+ export function useAnimatedValue(initialValue: number): {
455
+ value: () => number;
456
+ setValue: (value: number) => void;
457
+ spring: (toValue: number, config?: SpringConfig) => AnimationHandle;
458
+ timing: (toValue: number, config?: TweenConfig) => AnimationHandle;
459
+ interpolate: (inputRange: number[], outputRange: number[]) => () => number;
460
+ subscribe: (fn: (value: number) => void) => () => void;
305
461
  };
306
- export function createTransitionClasses(name: string): string;
307
- export function cssTransition(config: Record<string, any>): Record<string, any>;
462
+
463
+ export interface TransitionClasses {
464
+ enter: string;
465
+ enterActive: string;
466
+ enterDone: string;
467
+ exit: string;
468
+ exitActive: string;
469
+ exitDone: string;
470
+ }
471
+
472
+ /** Returns the six class NAMES for `name`, not a single string. */
473
+ export function createTransitionClasses(name: string): TransitionClasses;
474
+ /**
475
+ * Applies the enter/exit class sequence to an element and resolves when the
476
+ * transition is done. It was declared as a synchronous `(config) => object`.
477
+ */
478
+ export function cssTransition(
479
+ element: Element,
480
+ name: string,
481
+ type?: 'enter' | 'exit',
482
+ duration?: number,
483
+ ): Promise<void>;
308
484
 
309
485
  // --- Accessibility ---
310
486
 
@@ -353,11 +529,51 @@ export function useAriaChecked(initialChecked?: boolean): {
353
529
  checkboxProps: () => Record<string, any>;
354
530
  };
355
531
 
356
- export function useRovingTabIndex(itemCountOrSignal: number | (() => number)): {
532
+ export interface RovingTabIndexOptions {
533
+ /**
534
+ * Container role, emitted by containerProps(). The hook emits NO role by
535
+ * default: roving tabindex is the shared keyboard mechanic of toolbars,
536
+ * menus, trees, grids, tablists, radiogroups and listboxes, and a default
537
+ * spread last would silently overwrite the role the caller wrote beside it.
538
+ */
539
+ role?: string;
540
+ }
541
+
542
+ /**
543
+ * Props for one roving item. `ref` is the item's REGISTRATION (the hook holds
544
+ * the node through it and cannot move focus without it), so a `ref` passed via
545
+ * `overrides` is chained rather than replaced. `tabIndex` is an accessor, not a
546
+ * number: exactly one item is tabbable at a time and which one changes as focus
547
+ * roves, so a resolved value could not survive the spread.
548
+ */
549
+ export interface RovingItemProps {
550
+ ref: { current: Element | null };
551
+ tabIndex: () => number;
552
+ onKeyDown: (e: KeyboardEvent) => void;
553
+ onFocus: (e: Event) => void;
554
+ [prop: string]: any;
555
+ }
556
+
557
+ export function useRovingTabIndex(
558
+ itemCountOrSignal: number | (() => number),
559
+ options?: RovingTabIndexOptions,
560
+ ): {
561
+ /** Active index, clamped to the current count so the group is never untabbable. */
357
562
  focusIndex: () => number;
563
+ /**
564
+ * Set the active index. Out-of-range indexes are REFUSED, not clamped. Moves
565
+ * real focus only when the group already owns it, so syncing from application
566
+ * state cannot steal focus from elsewhere on the page.
567
+ */
358
568
  setFocusIndex: (index: number) => void;
359
- getItemProps: (index: number) => Record<string, any>;
360
- containerProps: () => Record<string, any>;
569
+ /**
570
+ * Explicitly move focus to an item (a menu focusing its first item on open).
571
+ * Returns the element it focused, or null when the index is out of range or
572
+ * the item is not in the DOM.
573
+ */
574
+ focusItem: (index: number) => Element | null;
575
+ getItemProps: (index: number, overrides?: Record<string, any>) => RovingItemProps;
576
+ containerProps: (overrides?: Record<string, any>) => Record<string, any>;
361
577
  };
362
578
 
363
579
  export function VisuallyHidden(props: { children?: VNodeChild; as?: string }): VNode;
@@ -413,7 +629,8 @@ export function Spinner(props?: Record<string, any>): VNode;
413
629
  // --- Data Fetching ---
414
630
 
415
631
  export function useFetch<T = any>(url: string, options?: Record<string, any>): {
416
- data: () => T;
632
+ /** null until the first response lands. It was declared as bare `T`, so strict-mode callers skipped the check they need. */
633
+ data: () => T | null;
417
634
  error: () => any;
418
635
  isLoading: () => boolean;
419
636
  refetch: () => Promise<void>;
@@ -429,36 +646,88 @@ export function useSWR<T = any>(key: string | null | false, fetcher: (key: strin
429
646
  revalidate: () => Promise<T | void>;
430
647
  };
431
648
 
649
+ /**
650
+ * 'idle' is a real state, not a placeholder: a query with `enabled: false` and
651
+ * nothing cached is idle, and reporting it as loading is what renders a spinner
652
+ * that never comes down.
653
+ */
654
+ export type QueryStatus = 'idle' | 'loading' | 'success' | 'error';
655
+ /** Whether a request is on the wire, independent of whether data exists. */
656
+ export type FetchStatus = 'idle' | 'fetching';
657
+
658
+ /**
659
+ * Any array key is joined into a single normalized string, so an array and its
660
+ * normalization address the same cache entry everywhere a key is accepted.
661
+ */
662
+ export type QueryKey = string | readonly unknown[];
663
+
432
664
  export function useQuery<T = any>(options: Record<string, any>): {
433
665
  data: () => T | null;
434
666
  error: () => any;
435
- status: () => string;
667
+ status: () => QueryStatus;
668
+ fetchStatus: () => FetchStatus;
436
669
  isLoading: () => boolean;
437
670
  isFetching: () => boolean;
438
671
  isError: () => boolean;
439
672
  isSuccess: () => boolean;
673
+ isIdle: () => boolean;
674
+ /** Whether the `enabled` gate is currently open. */
675
+ isEnabled: () => boolean;
440
676
  refetch: () => Promise<T | void>;
441
677
  };
442
678
 
679
+ /**
680
+ * Pages, plus the param each was fetched with. `pageParams[i]` names `pages[i]`.
681
+ */
682
+ export interface InfiniteData<T> {
683
+ pages: T[];
684
+ pageParams: unknown[];
685
+ }
686
+
687
+ /**
688
+ * `data()` hands back the page CONTAINER, not a flat array. It was declared as
689
+ * `T[]`, so `data().map(...)` type-checked and threw. The rows are in
690
+ * `data().pages`. A `select` option replaces the container wholesale, and that
691
+ * transformation is not expressible here.
692
+ */
443
693
  export function useInfiniteQuery<T = any>(options: Record<string, any>): {
444
- data: () => T[];
694
+ data: () => InfiniteData<T>;
445
695
  error: () => any;
446
- status: () => string;
696
+ status: () => QueryStatus;
447
697
  isLoading: () => boolean;
448
- isFetchingNextPage: () => boolean;
698
+ isError: () => boolean;
699
+ isSuccess: () => boolean;
700
+ isIdle: () => boolean;
701
+ isFetching: () => boolean;
702
+ isEnabled: () => boolean;
449
703
  hasNextPage: () => boolean;
704
+ hasPreviousPage: () => boolean;
705
+ isFetchingNextPage: () => boolean;
706
+ isFetchingPreviousPage: () => boolean;
450
707
  fetchNextPage: () => Promise<void>;
708
+ fetchPreviousPage: () => Promise<void>;
451
709
  refetch: () => Promise<void>;
452
710
  };
453
711
 
712
+ /**
713
+ * Synchronous. It was declared as returning Promise<void>, so `await
714
+ * invalidateQueries(...)` awaited undefined and read as "the refetches are
715
+ * done" when they had only been kicked off. The subscribers it wakes fetch on
716
+ * their own.
717
+ *
718
+ * An array key is a PREFIX unless `exact` is set: invalidateQueries(['todos'])
719
+ * also invalidates ['todos', 1]. `hard` clears the cached value immediately
720
+ * (loading state); the default keeps stale data on screen while it refetches.
721
+ */
454
722
  export function invalidateQueries(
455
- keyOrPredicate: string | ((key: string) => boolean),
456
- options?: { exact?: boolean },
457
- ): Promise<void>;
458
-
459
- export function prefetchQuery<T = any>(key: string, fetcher: (key: string) => Promise<T>): Promise<T>;
460
- export function setQueryData<T = any>(key: string, updater: T | ((prev: T | null) => T)): void;
461
- export function getQueryData<T = any>(key: string): T | null;
723
+ keyOrPredicate: QueryKey | ((key: string) => boolean),
724
+ options?: { exact?: boolean; hard?: boolean },
725
+ ): void;
726
+
727
+ export function prefetchQuery<T = any>(key: QueryKey, fetcher: (key: string) => Promise<T>): Promise<T>;
728
+ export function setQueryData<T = any>(key: QueryKey, updater: T | ((prev: T | null) => T)): void;
729
+ /** `undefined` for a key the cache has never held; `null` for one that was emptied. */
730
+ export function getQueryData<T = any>(key: QueryKey): T | null | undefined;
462
731
  export function clearCache(): void;
463
732
 
464
733
  // --- Forms ---
@@ -469,14 +738,39 @@ export interface FieldError {
469
738
  [key: string]: any;
470
739
  }
471
740
 
472
- export interface RegisterProps {
741
+ /**
742
+ * What register() actually hands back, which varies by control type. The event
743
+ * handlers are LOWERCASE (`oninput`, `onchange`), because that is the key the
744
+ * DOM binding and the merge in <Input>/<Radio> look for; `onInput` was declared
745
+ * here and register() has never defined it, so `register('email').onInput(e)`
746
+ * type-checked and threw at the first keystroke.
747
+ *
748
+ * `value` and `checked` are reactive rather than snapshots: `value` and the
749
+ * checkbox `checked` are getters, and the radio `checked` is a thunk, so that a
750
+ * spread (`{...register('email')}`) binds instead of freezing the value at
751
+ * mount.
752
+ *
753
+ * A closed type alias rather than an interface: the key set really is closed
754
+ * (this is the whole object register() builds), and closing it is what makes
755
+ * the phantom `onInput` an error again instead of an `any` off an index
756
+ * signature. It must stay a `type`, not an `interface`, because only a type
757
+ * alias gets the implicit index signature that `h('input', register(name))`
758
+ * needs to be assignable to Record<string, any>.
759
+ */
760
+ export type RegisterProps = {
473
761
  name: string;
474
- value: any;
475
- onInput: (e: any) => void;
476
762
  onBlur: () => void;
477
763
  onFocus: () => void;
478
764
  ref?: any;
479
- }
765
+ /** text/select/textarea, and the option's own value on a radio. */
766
+ value?: any;
767
+ /** checkbox/radio only. */
768
+ checked?: boolean | (() => boolean);
769
+ /** text/select/textarea only. */
770
+ oninput?: (e: any) => void;
771
+ /** checkbox/radio only. */
772
+ onchange?: (e: any) => void;
773
+ };
480
774
 
481
775
  export interface FormState {
482
776
  readonly values: Record<string, any>;
@@ -485,6 +779,8 @@ export interface FormState {
485
779
  readonly touched: Record<string, boolean>;
486
780
  isDirty: () => boolean;
487
781
  isValid: Computed<boolean>;
782
+ /** True while the resolver is running. Present since resolvers went async; never declared. */
783
+ isValidating: () => boolean;
488
784
  isSubmitting: () => boolean;
489
785
  isSubmitted: () => boolean;
490
786
  submitCount: () => number;
@@ -578,6 +874,15 @@ export interface ErrorCodeDefinition {
578
874
 
579
875
  export const ERROR_CODES: Record<string, ErrorCodeDefinition>;
580
876
 
877
+ /**
878
+ * Look up a catalogue entry by its `ERR_*` code.
879
+ *
880
+ * Errors thrown outside what-core carry only their code — the suggestion and
881
+ * the worked example live once, in the catalogue, so that the client bundle
882
+ * does not have to ship the prose. This is how they are recovered.
883
+ */
884
+ export function getErrorDefinition(code: string): ErrorCodeDefinition | undefined;
885
+
581
886
  export interface WhatErrorJSON {
582
887
  code: string;
583
888
  message: string;
@@ -676,3 +981,24 @@ export function getMountedComponents(): unknown[];
676
981
  export function registerSignal(sig: unknown): void;
677
982
  export function unregisterSignal(sig: unknown): void;
678
983
  export function getActiveSignals(): unknown[];
984
+
985
+ // --- Internal cross-package exports ---
986
+ // Underscore-prefixed names are not public API and carry no compatibility
987
+ // promise. They are declared here because sibling packages in this repo import
988
+ // them across the package boundary (what-server, what-text), and an
989
+ // undeclared cross-package import is exactly the rename that ships broken:
990
+ // `hygiene:types` skips `_`-prefixed names in its reverse direction, so
991
+ // nothing else would have caught it. Application code should not use these.
992
+
993
+ /** @internal Used by what-server. True for `aria-*` and `data-*` attribute names. */
994
+ export function _isAriaAttr(name: string): boolean;
995
+ /** @internal Used by what-server. Opens an SSR component scope. */
996
+ export function _beginComponentSSR(...args: unknown[]): unknown;
997
+ /** @internal Used by what-server. Closes the scope opened by `_beginComponentSSR`. */
998
+ export function _endComponentSSR(...args: unknown[]): unknown;
999
+ /** @internal Used by what-server. The keyed-array mapping `<For>` compiles down to. */
1000
+ export function _mapArrayToArray(...args: unknown[]): unknown;
1001
+ /** @internal Used by what-server/node. Installs the Node AsyncLocalStorage backend. */
1002
+ export function __installServerContextStorage(storage: unknown): void;
1003
+ /** @internal Used by what-text. Lets the text engine intercept text-node insertion. */
1004
+ export function _setTextInsertHook(hook: unknown): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "what-core",
3
- "version": "0.12.3",
3
+ "version": "0.13.0",
4
4
  "description": "What Framework - Signal-based UI framework built for AI agents",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/render.d.ts CHANGED
@@ -42,3 +42,10 @@ export function isHydrating(): boolean;
42
42
  // SVG counterpart to template(): elements are created in the SVG namespace, which
43
43
  // a plain innerHTML template cannot do.
44
44
  export function svgTemplate(html: string): () => Element;
45
+
46
+ /**
47
+ * @internal The component-call helper the compiler emits for `<Component />`.
48
+ * Re-exported by what-framework/render so compiled output can import it from
49
+ * the package a scaffolded app actually depends on.
50
+ */
51
+ export function _$createComponent(component: unknown, props?: unknown, ...args: unknown[]): unknown;