streetui 1.7.0 → 1.9.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.
@@ -7,7 +7,7 @@
7
7
  * (`streetui --version`) — the consolidated test-suite pins all three to the
8
8
  * same coordinated release so they can never silently drift apart.
9
9
  */
10
- declare const VERSION = "1.7.0";
10
+ declare const VERSION = "1.9.0";
11
11
 
12
12
  /**
13
13
  * StreetUI reactive signals — framework-owned reactivity, no external libraries.
@@ -341,6 +341,195 @@ interface SerializedGraph {
341
341
  readonly root: SerializedNode;
342
342
  }
343
343
 
344
+ /**
345
+ * First-class StreetUI-native components (§3–§9).
346
+ *
347
+ * A component is a *reusable unit that owns its own local state and lifecycle*.
348
+ * It is NOT a virtual-DOM element, a second reactive system, or a second
349
+ * renderer. It compiles into the EXISTING pipeline: `container.component(...)`
350
+ * creates a reserved `'component'` GraphNode (already a `SemanticNodeType`,
351
+ * already mapped to `<div>`), runs the component's `setup` synchronously during
352
+ * the same build-time descent every other builder uses, and routes any cleanups
353
+ * the setup registers into the node's `NodeInstance` at mount (so they run,
354
+ * children-first, when the component leaves the graph).
355
+ *
356
+ * Design (derived from the existing DSL architecture, not copied from React/Vue):
357
+ *
358
+ * const UserCard = component<{ name: Signal<string> }>((props, ctx) => {
359
+ * // ── setup: runs ONCE per instance, at build time ──
360
+ * const open = signal(false);
361
+ * ctx.effect(() => { ... }); // auto-disposed on unmount
362
+ * ctx.onCleanup(() => { ... }); // explicit teardown hook
363
+ * // ── render: fills the component's own container scope ──
364
+ * return (content) => {
365
+ * content.text(props.name); // fine-grained: signal prop, no re-setup
366
+ * content.when(open, (c) => c.text('expanded'));
367
+ * ctx.renderChildren(content); // where slotted children go (§6)
368
+ * };
369
+ * }, { name: 'UserCard' });
370
+ *
371
+ * page.component('card-1', UserCard, { name }, (slot) => slot.text('child'));
372
+ *
373
+ * Props are ordinary typed values (§5 — typing is a TypeScript concern, no
374
+ * runtime schema). Passing a `Signal<T>` prop and binding it in the render body
375
+ * gives fine-grained updates (§13) WITHOUT re-running `setup`: only the bound
376
+ * node mutates when the signal changes. `setup` re-runs only when the component
377
+ * is genuinely rebuilt (removed + re-created by a keyed list / conditional),
378
+ * at which point the previous instance is disposed first — so cleanup stays
379
+ * correct.
380
+ */
381
+
382
+ /**
383
+ * The render half of a component: fills the component's own container scope.
384
+ * Returned by `setup` so that per-instance state created in `setup` is captured
385
+ * by closure and the render body can read it.
386
+ */
387
+ type ComponentRender = (content: ContainerDSL) => void;
388
+ /**
389
+ * Lifecycle + composition surface handed to a component's `setup`. Local
390
+ * reactive state is created with the EXISTING `signal`/`derived`/`effect`/
391
+ * `batch` primitives (§8) — `ctx` only adds ownership: anything registered here
392
+ * is torn down automatically when the component leaves the graph (§9).
393
+ */
394
+ interface ComponentContext {
395
+ /** This instance's stable identity key (the `key` passed at the call site). */
396
+ readonly key: string;
397
+ /**
398
+ * Register a teardown callback. Runs when the component unmounts (children
399
+ * first, then this — mirroring `NodeInstance.dispose`). Use for resources,
400
+ * `form.dispose`, subscriptions, timers, etc.
401
+ */
402
+ onCleanup(fn: () => void): void;
403
+ /**
404
+ * Run a reactive effect owned by this component. Wraps the framework's
405
+ * `effect()`; the returned unsubscribe is auto-tracked and disposed on
406
+ * unmount, so component effects never leak (the pain point the audit ranked
407
+ * #1). The effect may itself return a cleanup, exactly like `effect()`.
408
+ */
409
+ effect(fn: () => void | (() => void)): void;
410
+ /**
411
+ * Render the caller-supplied children into `content` at this point (§6 native
412
+ * child composition / slots). No-op when the call site passed no children.
413
+ * Call it wherever the component wants its slotted content to appear.
414
+ */
415
+ renderChildren(content: ContainerDSL): void;
416
+ }
417
+ /**
418
+ * A component's setup function: receives typed props and the lifecycle context,
419
+ * creates any local state, and returns the render function. Runs synchronously
420
+ * during the build-time descent (like `errorBoundary`'s callback), so it must be
421
+ * SSR-safe — on the server it runs at render time and its cleanups run when the
422
+ * SSR root is disposed.
423
+ */
424
+ type ComponentSetup<P> = (props: P, ctx: ComponentContext) => ComponentRender;
425
+ /**
426
+ * The opaque, reusable definition produced by `component(...)`. Carries the
427
+ * `setup` and an inspectable `name`; the brand lets the builder method (and
428
+ * DevTools) recognise a definition at runtime without a class.
429
+ */
430
+ interface ComponentDefinition<P> {
431
+ readonly __streetui_component: true;
432
+ readonly name: string;
433
+ readonly setup: ComponentSetup<P>;
434
+ }
435
+ /**
436
+ * Define a reusable component. Returns a `ComponentDefinition` you render with
437
+ * `container.component(key, def, props, children?)`. This is a pure factory — it
438
+ * builds no graph and runs no `setup`; instantiation happens per call site.
439
+ *
440
+ * @param setup Runs once per instance: create local state, return the render fn.
441
+ * @param options.name Human-readable name for DevTools/`data-streetui-component`.
442
+ */
443
+ declare function component<P = Record<string, never>>(setup: ComponentSetup<P>, options?: {
444
+ name?: string;
445
+ }): ComponentDefinition<P>;
446
+ /** Runtime guard: is `value` a component definition? */
447
+ declare function isComponentDefinition(value: unknown): value is ComponentDefinition<unknown>;
448
+
449
+ /**
450
+ * Transition configuration (§2, §4).
451
+ *
452
+ * A `TransitionConfig` is a pure, declarative description of a CSS class-based
453
+ * enter/leave transition — the engine choice for this milestone. It contains NO
454
+ * DOM references, NO timers and NO browser-only APIs, so it is safe to build on
455
+ * the server (where it is simply ignored — see the renderer's SSR guard) and to
456
+ * carry on a graph handler (`__transition__<nodeId>`) alongside the existing
457
+ * `__overlay__`/`__component__` descriptors.
458
+ *
459
+ * The class model follows the widely-understood enter/leave convention:
460
+ *
461
+ * enter: [enterActive (+ enter) whole phase] · enterFrom (start) → enterTo (end)
462
+ * leave: [leaveActive (+ leave) whole phase] · leaveFrom (start) → leaveTo (end)
463
+ *
464
+ * `name` is a shorthand that expands to `${name}-enter-from`,
465
+ * `${name}-enter-active`, `${name}-enter-to` and the leave equivalents; explicit
466
+ * class fields override the derived ones. Because the classes are just strings,
467
+ * SSR output is deterministic (no class is applied on the server at all — the
468
+ * controller is browser-only), satisfying §4/§21.
469
+ */
470
+ interface TransitionConfig {
471
+ /** Shorthand base: expands to `${name}-enter-from`, `${name}-enter-active`, … */
472
+ readonly name?: string;
473
+ /** Class(es) present for the whole enter phase (in addition to `enterActive`). */
474
+ readonly enter?: string;
475
+ /** Class(es) applied at the start of enter, removed on the next frame. */
476
+ readonly enterFrom?: string;
477
+ /** Class(es) present for the whole enter phase (where the CSS `transition` lives). */
478
+ readonly enterActive?: string;
479
+ /** Class(es) added on the next frame, removed when enter completes. */
480
+ readonly enterTo?: string;
481
+ /** Class(es) present for the whole leave phase (in addition to `leaveActive`). */
482
+ readonly leave?: string;
483
+ /** Class(es) applied at the start of leave, removed on the next frame. */
484
+ readonly leaveFrom?: string;
485
+ /** Class(es) present for the whole leave phase (where the CSS `transition` lives). */
486
+ readonly leaveActive?: string;
487
+ /** Class(es) added on the next frame, removed when leave completes. */
488
+ readonly leaveTo?: string;
489
+ /**
490
+ * Also animate the very first appearance (initial mount). Hydration never
491
+ * animates appear (the DOM is already present and correct); this only affects
492
+ * fresh browser mounts. Defaults to false.
493
+ */
494
+ readonly appear?: boolean;
495
+ /**
496
+ * Fallback completion timeout in milliseconds. A transition normally completes
497
+ * on the element's `transitionend`/`animationend`. This timeout is the safety
498
+ * net for (a) transitions that fire no such event and (b) test DOMs like
499
+ * happy-dom that dispatch no transition events at all — making tests
500
+ * deterministic without a real browser (§24). Defaults to 1000. Use a small
501
+ * value (or 0 → next macrotask) in tests.
502
+ */
503
+ readonly duration?: number;
504
+ }
505
+ /**
506
+ * The resolved, ready-to-apply form of a {@link TransitionConfig}: each phase's
507
+ * classes are pre-split into arrays so the controller applies/removes them with
508
+ * no per-run string parsing. Produced once by {@link resolveTransition} at wire
509
+ * time (browser only).
510
+ */
511
+ interface ResolvedTransition {
512
+ /** enter classes present for the whole phase (base `enter` + `enterActive`). */
513
+ readonly enterActive: readonly string[];
514
+ /** enter start classes (removed next frame). */
515
+ readonly enterFrom: readonly string[];
516
+ /** enter end classes (added next frame). */
517
+ readonly enterTo: readonly string[];
518
+ readonly leaveActive: readonly string[];
519
+ readonly leaveFrom: readonly string[];
520
+ readonly leaveTo: readonly string[];
521
+ readonly appear: boolean;
522
+ readonly duration: number;
523
+ }
524
+ /**
525
+ * Resolve a {@link TransitionConfig} into applied class arrays. The `name`
526
+ * shorthand supplies defaults; any explicit field overrides the derived class
527
+ * for that phase-slot (still merged with `enter`/`leave` base classes).
528
+ */
529
+ declare function resolveTransition(config: TransitionConfig): ResolvedTransition;
530
+ /** Runtime brand check for a transition descriptor value. */
531
+ declare function isTransitionConfig(value: unknown): value is TransitionConfig;
532
+
344
533
  /**
345
534
  * StreetUI DSL type system.
346
535
  * All builder callbacks and option shapes live here.
@@ -387,6 +576,14 @@ interface A11yOptions {
387
576
  readonly ariaRequired?: boolean;
388
577
  /** aria-modal — mark a dialog as modal (content outside is inert to AT). */
389
578
  readonly ariaModal?: boolean;
579
+ /** aria-owns — id(s) of elements owned by this one when the DOM can't express it. */
580
+ readonly ariaOwns?: string;
581
+ /** aria-activedescendant — id of the active option in a composite widget (menu/listbox/combobox). */
582
+ readonly ariaActiveDescendant?: string;
583
+ /** aria-haspopup — the element opens a popup ('menu' | 'listbox' | 'dialog' | 'grid' | 'tree' | true). */
584
+ readonly ariaHasPopup?: boolean | 'menu' | 'listbox' | 'tree' | 'grid' | 'dialog';
585
+ /** aria-selected — selection state within a composite widget. */
586
+ readonly ariaSelected?: boolean;
390
587
  }
391
588
  interface TextOptions extends A11yOptions {
392
589
  readonly class?: string;
@@ -450,6 +647,15 @@ interface ContainerOptions extends A11yOptions {
450
647
  readonly class?: string;
451
648
  readonly id?: string;
452
649
  readonly key?: string;
650
+ /**
651
+ * Enter/leave transition for this element (§2). CSS class-based and
652
+ * browser-only: on the server it is ignored (deterministic SSR output). The
653
+ * enter animation runs when the element is added by a reactive `when`/`listOf`
654
+ * change (or on initial mount when `appear` is set); the leave animation runs
655
+ * before the element is removed and disposed — the reconciler defers teardown
656
+ * until the transition completes.
657
+ */
658
+ readonly transition?: TransitionConfig;
453
659
  }
454
660
  interface SectionOptions extends ContainerOptions {
455
661
  }
@@ -457,6 +663,21 @@ interface FormOptions extends ContainerOptions {
457
663
  readonly onSubmit?: (e: Event) => void;
458
664
  }
459
665
  interface ListOptions extends ContainerOptions {
666
+ /**
667
+ * Enter/leave transition applied to each list item (§7). Preserves keyed
668
+ * identity: reordering reuses items (no leave/enter), append/prepend enter,
669
+ * remove leaves before disposal, and a removed key that reappears mid-leave is
670
+ * reclaimed (leave→enter). `transition` (inherited) applies to the list
671
+ * container itself; `itemTransition` applies to its rows.
672
+ */
673
+ readonly itemTransition?: TransitionConfig;
674
+ }
675
+ /** Options for `when()` (§2 conditional transitions). */
676
+ interface WhenOptions {
677
+ /** Transition applied to the active branch as it mounts/unmounts. */
678
+ readonly transition?: TransitionConfig;
679
+ /** Also animate the branch present on the initial mount (appear). */
680
+ readonly appear?: boolean;
460
681
  }
461
682
  /** Options for a plain portal (mount children into `document.body`). */
462
683
  interface PortalOptions extends ContainerOptions {
@@ -529,7 +750,7 @@ interface ContainerDSL extends ContentDSL {
529
750
  * renders while the condition is false. Compiles into the same reactive
530
751
  * reconciliation machinery as `listOf` — there is no separate render path.
531
752
  */
532
- when(condition: Bindable<boolean>, builder: ContainerBuilder, elseBuilder?: ContainerBuilder): void;
753
+ when(condition: Bindable<boolean>, builder: ContainerBuilder, elseBuilder?: ContainerBuilder, options?: WhenOptions): void;
533
754
  /**
534
755
  * Render `builder`, but swap to `options.fallback` when the boundary enters an
535
756
  * error state. A boundary enters that state when (a) any observed `source`
@@ -577,6 +798,18 @@ interface ContainerDSL extends ContentDSL {
577
798
  * `aria-live="polite"`. Non-modal and never steals focus; no Escape handling.
578
799
  */
579
800
  toast(key: string, options: OverlayOptions, builder: ContainerBuilder): void;
801
+ /**
802
+ * Instantiate a reusable `component()` at this position (§3–§9). Creates a
803
+ * `'component'` node (rendered as a `<div>` wrapper), runs the definition's
804
+ * `setup(props, ctx)` synchronously to obtain its render function, and fills
805
+ * the component's own container scope with it. Any `ctx.effect`/`ctx.onCleanup`
806
+ * registered by the setup is torn down automatically when the component leaves
807
+ * the graph. `props` are strongly typed by the definition's generic; pass
808
+ * `Signal` props for fine-grained updates that do NOT re-run `setup` (§13).
809
+ * The optional `children` builder is rendered wherever the component calls
810
+ * `ctx.renderChildren` (§6 native child composition).
811
+ */
812
+ component<P>(key: string, def: ComponentDefinition<P>, props: P, children?: ContainerBuilder): void;
580
813
  }
581
814
  interface SectionDSL extends ContainerDSL {
582
815
  }
@@ -636,7 +869,7 @@ declare class ContainerBuilderBase extends ContentBuilderBase implements Contain
636
869
  list(key: string, builder: ListBuilder, options?: ListOptions): void;
637
870
  listOf<T>(key: string, items: Signal<T[]> | ReadonlySignal<T[]>, renderItem: (item: T, index: number, content: ContentDSL) => void, options?: ListOptions): void;
638
871
  form(key: string, builder: FormBuilder, options?: FormOptions): void;
639
- when(condition: Bindable<boolean>, builder: ContainerBuilder, elseBuilder?: ContainerBuilder): void;
872
+ when(condition: Bindable<boolean>, builder: ContainerBuilder, elseBuilder?: ContainerBuilder, options?: WhenOptions): void;
640
873
  errorBoundary(id: string, builder: ContainerBuilder, options: ErrorBoundaryOptions): void;
641
874
  portal(key: string, builder: ContainerBuilder, options?: PortalOptions): void;
642
875
  /**
@@ -653,6 +886,24 @@ declare class ContainerBuilderBase extends ContentBuilderBase implements Contain
653
886
  tooltip(key: string, options: OverlayOptions, builder: ContainerBuilder): void;
654
887
  dropdown(key: string, options: OverlayOptions, builder: ContainerBuilder): void;
655
888
  toast(key: string, options: OverlayOptions, builder: ContainerBuilder): void;
889
+ /**
890
+ * Instantiate a reusable component (§3–§9). Creates a `'component'` node
891
+ * (rendered as a `<div>` wrapper — preserves the one-node/one-element
892
+ * positional-hydration invariant), then runs `def.setup(props, ctx)`
893
+ * synchronously to obtain the render function and fills the component's own
894
+ * container scope with it — structurally identical to `container`/
895
+ * `errorBoundary`. Cleanups the setup registers via `ctx.effect`/
896
+ * `ctx.onCleanup` are collected into a closure and exposed to the renderer
897
+ * through a `__component__<id>` handler (mirroring `__overlay__`); the mount/
898
+ * hydrate paths read it and route each into `NodeInstance.trackCleanup`, so
899
+ * teardown runs (children-first) when the component leaves the graph.
900
+ *
901
+ * `setup` runs once per instance here at build time. When this component sits
902
+ * inside a keyed list / conditional, a rebuild disposes the old instance
903
+ * (running its cleanups + pruning its `__component__` entry) and re-runs this
904
+ * method for the new node — so re-invocation is safe and leak-free.
905
+ */
906
+ component<P>(key: string, def: ComponentDefinition<P>, props: P, children?: ContainerBuilder): void;
656
907
  }
657
908
  declare class SectionBuilderImpl extends ContainerBuilderBase implements SectionDSL {
658
909
  }
@@ -745,4 +996,4 @@ declare function compile(app: StreetApp, options?: CompileOptions): CompiledAppl
745
996
  */
746
997
  declare function compileGraph(graph: ApplicationGraph, options?: CompileOptions): CompiledApplication;
747
998
 
748
- export { type ListDSL as $, type ApplicationId as A, BaseNode as B, type CompiledApplication as C, DiagnosticCollector as D, type ErrorBoundaryOptions as E, type ErrorFallbackBuilder as F, GraphNode as G, type ErrorSource as H, type EventDescriptor as I, type FormBuilder as J, FormBuilderImpl as K, type FormDSL as L, type FormOptions as M, type GraphNodeData as N, type HandlerFn as O, type PageDSL as P, type HeadingOptions as Q, type ReadonlySignal as R, StreetApp as S, type ImageOptions as T, type Unsubscribe as U, VERSION as V, type InputOptions as W, type InputOptionsBase as X, type LinkOptions as Y, type ListBuilder as Z, ListBuilderImpl as _, Signal as a, type ListOptions as a0, type ListPlanEntry as a1, type NodeId as a2, type NodeMetadata as a3, type OverlayOptions as a4, type PageBuilder as a5, PageBuilderImpl as a6, type PortalOptions as a7, type PropValue as a8, type Props as a9, reactiveListItemSignature as aA, resetIdCounter as aB, signal as aC, signalKind as aD, streetui as aE, type ReactiveConsumer as aa, type ReactiveSource as ab, type SectionBuilder as ac, SectionBuilderImpl as ad, type SectionDSL as ae, type SectionOptions as af, type SerializedGraph as ag, type SerializedNode as ah, type StateRef as ai, type StreetUI as aj, type TextOptions as ak, type TextValue as al, batch as am, compile as an, compileGraph as ao, createNodeId as ap, derived as aq, effect as ar, formatDiagnostic as as, generateApplicationId as at, generateNodeId as au, isBatching as av, nextId as aw, nodeIdPrefix as ax, observerCount as ay, reactiveListItemKey as az, type Subscriber as b, ApplicationGraph as c, type SemanticNodeType as d, type ContainerDSL as e, type SignalKind as f, type A11yOptions as g, AppBuilder as h, type AppDSL as i, type AppOptions as j, type ApplicationGraphOptions as k, type Bindable as l, type BindableText as m, type BoundInputOptions as n, type ButtonOptions as o, type CompileOptions as p, type ContainerBuilder as q, ContainerBuilderImpl as r, type ContainerOptions as s, type ContentDSL as t, type ControlledInputOptions as u, DerivedSignal as v, type Diagnostic as w, DiagnosticError as x, type DiagnosticLocation as y, type DiagnosticSeverity as z };
999
+ export { type InputOptions as $, type ApplicationId as A, BaseNode as B, type CompiledApplication as C, DiagnosticCollector as D, type Diagnostic as E, DiagnosticError as F, GraphNode as G, type DiagnosticLocation as H, type DiagnosticSeverity as I, type ErrorBoundaryOptions as J, type ErrorFallbackBuilder as K, type ErrorSource as L, type EventDescriptor as M, type FormBuilder as N, FormBuilderImpl as O, type PageDSL as P, type FormDSL as Q, type ReadonlySignal as R, StreetApp as S, type TransitionConfig as T, type Unsubscribe as U, VERSION as V, type FormOptions as W, type GraphNodeData as X, type HandlerFn as Y, type HeadingOptions as Z, type ImageOptions as _, type ComponentDefinition as a, type InputOptionsBase as a0, type LinkOptions as a1, type ListBuilder as a2, ListBuilderImpl as a3, type ListDSL as a4, type ListOptions as a5, type ListPlanEntry as a6, type NodeId as a7, type NodeMetadata as a8, type OverlayOptions as a9, formatDiagnostic as aA, generateApplicationId as aB, generateNodeId as aC, isBatching as aD, isComponentDefinition as aE, isTransitionConfig as aF, nextId as aG, nodeIdPrefix as aH, observerCount as aI, reactiveListItemKey as aJ, reactiveListItemSignature as aK, resetIdCounter as aL, resolveTransition as aM, signal as aN, signalKind as aO, streetui as aP, type PageBuilder as aa, PageBuilderImpl as ab, type PortalOptions as ac, type PropValue as ad, type Props as ae, type ReactiveConsumer as af, type ReactiveSource as ag, type ResolvedTransition as ah, type SectionBuilder as ai, SectionBuilderImpl as aj, type SectionDSL as ak, type SectionOptions as al, type SerializedGraph as am, type SerializedNode as an, type StateRef as ao, type StreetUI as ap, type TextOptions as aq, type TextValue as ar, type WhenOptions as as, batch as at, compile as au, compileGraph as av, component as aw, createNodeId as ax, derived as ay, effect as az, type ContainerBuilder as b, Signal as c, type Subscriber as d, ApplicationGraph as e, type SemanticNodeType as f, type ContainerDSL as g, type SignalKind as h, type A11yOptions as i, AppBuilder as j, type AppDSL as k, type AppOptions as l, type ApplicationGraphOptions as m, type Bindable as n, type BindableText as o, type BoundInputOptions as p, type ButtonOptions as q, type CompileOptions as r, type ComponentContext as s, type ComponentRender as t, type ComponentSetup as u, ContainerBuilderImpl as v, type ContainerOptions as w, type ContentDSL as x, type ControlledInputOptions as y, DerivedSignal as z };