@cascivo/core 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -2,12 +2,48 @@
2
2
 
3
3
  # @cascivo/core
4
4
 
5
- > Micro-FSM and Preact Signals primitives powering every cascade component
5
+ > Micro-FSM and Preact Signals primitives powering every cascivo component
6
6
 
7
7
  [cascivo.com](https://cascivo.com) · [Docs](https://docs.cascivo.com) · [Storybook](https://storybook.cascivo.com) · [GitHub](https://github.com/urbanisierung/cascivo)
8
8
 
9
9
  Core runtime for cascade — micro-FSM engine, Preact Signals integration (`useSignal`, `useComputed`, `useSignalEffect`, `useSignals`), and base utilities shared across all components.
10
10
 
11
+ ## Interaction hooks
12
+
13
+ Signal-driven, SSR-safe hooks (no `useState`/`useEffect`). These cover the user-facing `@heroui/use-*` hooks cascivo previously lacked.
14
+
15
+ ### `useDisclosure`
16
+
17
+ Open/close state for overlays, controllable via an optional `isOpen`.
18
+
19
+ ```tsx
20
+ const { isOpen, open, close, toggle } = useDisclosure({ defaultOpen: false })
21
+ // isOpen is a ReadonlySignal<boolean>; pass isOpen + onOpenChange for controlled use.
22
+ ```
23
+
24
+ ### `useInfiniteScroll`
25
+
26
+ Calls `onLoadMore` when a sentinel element scrolls into view (`IntersectionObserver`), gated by `hasMore`.
27
+
28
+ ```tsx
29
+ const { sentinelRef } = useInfiniteScroll({ hasMore, onLoadMore: loadNextPage })
30
+ return (
31
+ <ul>
32
+ {rows}
33
+ <li ref={sentinelRef} />
34
+ </ul>
35
+ )
36
+ ```
37
+
38
+ ### `useDraggable`
39
+
40
+ Pointer-driven drag offset applied as a CSS `translate` — no animation library (replaces HeroUI's Framer-based drag).
41
+
42
+ ```tsx
43
+ const { handleRef, targetRef, offset } = useDraggable({ axis: 'both' })
44
+ // apply offset.value as translate(offset.x, offset.y) on the target element.
45
+ ```
46
+
11
47
  ## Install
12
48
 
13
49
  ```sh
package/dist/index.d.mts CHANGED
@@ -254,6 +254,153 @@ interface UseClipboardReturn {
254
254
  */
255
255
  declare function useClipboard(options?: UseClipboardOptions): UseClipboardReturn;
256
256
  //#endregion
257
+ //#region src/disclosure.d.ts
258
+ interface UseDisclosureOptions {
259
+ /** Initial open state for uncontrolled use. */
260
+ defaultOpen?: boolean;
261
+ /** Controlled open state. When provided, the parent owns the state. */
262
+ isOpen?: boolean;
263
+ /** Called whenever open/close/toggle is requested. */
264
+ onOpenChange?: (open: boolean) => void;
265
+ }
266
+ interface UseDisclosureReturn {
267
+ isOpen: ReadonlySignal<boolean>;
268
+ open: () => void;
269
+ close: () => void;
270
+ toggle: () => void;
271
+ }
272
+ /**
273
+ * Open/close state for overlays (modal, drawer, popover, …), replacing the
274
+ * per-component `useSignal(open)` boilerplate. Backed by `useControllableSignal`
275
+ * so it is controlled when `isOpen` is passed and uncontrolled otherwise.
276
+ * Replaces HeroUI's `@heroui/use-disclosure`. No `useState`/`useEffect`.
277
+ */
278
+ declare function useDisclosure(options?: UseDisclosureOptions): UseDisclosureReturn;
279
+ //#endregion
280
+ //#region src/infinite-scroll.d.ts
281
+ interface UseInfiniteScrollOptions {
282
+ /** Whether more items remain to load. */
283
+ hasMore: boolean;
284
+ /** Called when the sentinel scrolls into view and `hasMore` is true. */
285
+ onLoadMore: () => void;
286
+ /** Disable the observer without unmounting. Default true. */
287
+ isEnabled?: boolean;
288
+ /** IntersectionObserver rootMargin (e.g. '200px' to prefetch early). */
289
+ rootMargin?: string;
290
+ }
291
+ interface UseInfiniteScrollReturn {
292
+ /** Place on a trailing element at the end of the list. */
293
+ sentinelRef: RefObject<HTMLElement | null>;
294
+ /** Optional scroll container; defaults to the viewport. */
295
+ rootRef: RefObject<HTMLElement | null>;
296
+ }
297
+ /**
298
+ * Calls `onLoadMore` when a sentinel element scrolls into view, gated by
299
+ * `hasMore`. Wires an `IntersectionObserver` inside `useSignalEffect` and
300
+ * disconnects it on teardown. SSR/no-DOM safe. Replaces HeroUI's
301
+ * `@heroui/use-infinite-scroll`. No `useState`/`useEffect`.
302
+ */
303
+ declare function useInfiniteScroll(options: UseInfiniteScrollOptions): UseInfiniteScrollReturn;
304
+ //#endregion
305
+ //#region src/draggable.d.ts
306
+ interface DragOffset {
307
+ x: number;
308
+ y: number;
309
+ }
310
+ interface UseDraggableOptions {
311
+ isDisabled?: boolean;
312
+ onDragEnd?: (offset: DragOffset) => void;
313
+ /** Constrain dragging to an axis. Default 'both'. */
314
+ axis?: 'x' | 'y' | 'both';
315
+ }
316
+ interface UseDraggableReturn {
317
+ /** Attach to the drag handle (e.g. a modal header). */
318
+ handleRef: RefObject<HTMLElement | null>;
319
+ /** Attach to the moved element; apply `offset` as a CSS translate. */
320
+ targetRef: RefObject<HTMLElement | null>;
321
+ offset: ReadonlySignal<DragOffset>;
322
+ isDragging: ReadonlySignal<boolean>;
323
+ reset: () => void;
324
+ }
325
+ /**
326
+ * Pointer-driven drag offset for overlays. Returns an `{ x, y }` offset signal
327
+ * the caller applies as a CSS `translate` (no animation library). Attaches
328
+ * pointer listeners in `useSignalEffect` and removes them on teardown.
329
+ * SSR/no-DOM safe. Replaces HeroUI's `@heroui/use-draggable` (Framer-free).
330
+ * No `useState`/`useEffect`.
331
+ */
332
+ declare function useDraggable(options?: UseDraggableOptions): UseDraggableReturn;
333
+ //#endregion
334
+ //#region src/resize-observer.d.ts
335
+ interface ElementSize {
336
+ width: number;
337
+ height: number;
338
+ }
339
+ interface UseResizeObserverOptions {
340
+ /** Which box model to observe. Default 'content-box'. */
341
+ box?: ResizeObserverBoxOptions;
342
+ }
343
+ interface UseResizeObserverReturn<T extends Element> {
344
+ /** Attach to the element whose size you want to track. */
345
+ ref: RefObject<T | null>;
346
+ /** Latest content-box size, or null before the first observation. */
347
+ size: ReadonlySignal<ElementSize | null>;
348
+ /** Latest raw ResizeObserverEntry, or null before the first observation. */
349
+ entry: ReadonlySignal<ResizeObserverEntry | null>;
350
+ }
351
+ /**
352
+ * Observes an element's size with a `ResizeObserver`, exposing the latest size
353
+ * as a signal. The declarative counterpart to Web Awesome's `wa-resize-observer`.
354
+ * Wires the observer inside `useSignalEffect` and disconnects it on teardown.
355
+ * SSR/no-DOM safe. No `useState`/`useEffect`.
356
+ */
357
+ declare function useResizeObserver<T extends Element = Element>(options?: UseResizeObserverOptions): UseResizeObserverReturn<T>;
358
+ //#endregion
359
+ //#region src/mutation-observer.d.ts
360
+ interface UseMutationObserverReturn<T extends Node> {
361
+ /** Attach to the node whose mutations you want to watch. */
362
+ ref: RefObject<T | null>;
363
+ /** The records from the most recent mutation batch. */
364
+ records: ReadonlySignal<MutationRecord[]>;
365
+ }
366
+ /**
367
+ * Watches a node's DOM tree with a `MutationObserver`, exposing the latest batch
368
+ * of records as a signal. The declarative counterpart to Web Awesome's
369
+ * `wa-mutation-observer`. Wires the observer inside `useSignalEffect` and
370
+ * disconnects it on teardown. SSR/no-DOM safe. No `useState`/`useEffect`.
371
+ *
372
+ * `init` is read when the effect (re-)subscribes; pass a stable object or accept
373
+ * that changing it between renders won't re-subscribe (mount-time semantics).
374
+ */
375
+ declare function useMutationObserver<T extends Node = Element>(init?: MutationObserverInit): UseMutationObserverReturn<T>;
376
+ //#endregion
377
+ //#region src/intersection-observer.d.ts
378
+ interface UseIntersectionObserverOptions {
379
+ root?: Element | Document | null;
380
+ rootMargin?: string;
381
+ threshold?: number | number[];
382
+ }
383
+ interface UseIntersectionObserverReturn<T extends Element> {
384
+ /** Attach to the element whose visibility you want to track. */
385
+ ref: RefObject<T | null>;
386
+ /** Latest IntersectionObserverEntry, or null before the first observation. */
387
+ entry: ReadonlySignal<IntersectionObserverEntry | null>;
388
+ /** Whether the element is currently intersecting the root. */
389
+ isIntersecting: ReadonlySignal<boolean>;
390
+ }
391
+ /**
392
+ * Tracks an element's visibility with an `IntersectionObserver`, exposing the
393
+ * latest entry and an `isIntersecting` flag as signals. The declarative
394
+ * counterpart to Web Awesome's `wa-intersection-observer`.
395
+ *
396
+ * This is the **generic** observer — for "load more when a sentinel appears"
397
+ * use `useInfiniteScroll`, which adds `hasMore`/`onLoadMore` semantics on top.
398
+ *
399
+ * Wires the observer inside `useSignalEffect` and disconnects it on teardown.
400
+ * SSR/no-DOM safe. No `useState`/`useEffect`.
401
+ */
402
+ declare function useIntersectionObserver<T extends Element = Element>(options?: UseIntersectionObserverOptions): UseIntersectionObserverReturn<T>;
403
+ //#endregion
257
404
  //#region src/dismissable-layer.d.ts
258
405
  interface DismissableLayerProps {
259
406
  children: ReactNode;
@@ -368,5 +515,5 @@ declare function useAnchorPosition(options: UseAnchorPositionOptions): UseAnchor
368
515
  //#region src/index.d.ts
369
516
  declare const VERSION = "0.0.0";
370
517
  //#endregion
371
- export { type AccessibilityMeta, type AnchorAlign, type AnchorPlacement, type AnchorSide, type ComponentIntent, type ComponentMeta, DismissableLayer, type DismissableLayerProps, ErrorBoundary, type ErrorBoundaryProps, type ExampleMeta, FocusScope, type FocusScopeProps, type IntentAntiPattern, type IntentContent, type IntentFlexibility, type IntentRelated, type IntentRelationship, type Machine, type MachineConfig, Portal, type PortalProps, Presence, type PresenceProps, type PropMeta, type ReadonlySignal, type RovingItemProps, type RovingOrientation, type Signal, Slot, type SlotProps, type StateConfig, SuspenseBoundary, type SuspenseBoundaryProps, type UseAnchorPositionOptions, type UseAnchorPositionReturn, type UseClipboardOptions, type UseClipboardReturn, type UseControllableSignalOptions, type UseRovingFocusOptions, type UseRovingFocusReturn, VERSION, VisuallyHidden, type VisuallyHiddenProps, type WcagLevel, batch, cn, composeRefs, computePosition, computed, createMachine, effect, mergeProps, signal, useAnchorPosition, useClipboard, useComputed, useControllableSignal, useId, useMachine, useMediaQuery, useRovingFocus, useScrollLock, useSignal, useSignalEffect, useSignals };
518
+ export { type AccessibilityMeta, type AnchorAlign, type AnchorPlacement, type AnchorSide, type ComponentIntent, type ComponentMeta, DismissableLayer, type DismissableLayerProps, type DragOffset, type ElementSize, ErrorBoundary, type ErrorBoundaryProps, type ExampleMeta, FocusScope, type FocusScopeProps, type IntentAntiPattern, type IntentContent, type IntentFlexibility, type IntentRelated, type IntentRelationship, type Machine, type MachineConfig, Portal, type PortalProps, Presence, type PresenceProps, type PropMeta, type ReadonlySignal, type RovingItemProps, type RovingOrientation, type Signal, Slot, type SlotProps, type StateConfig, SuspenseBoundary, type SuspenseBoundaryProps, type UseAnchorPositionOptions, type UseAnchorPositionReturn, type UseClipboardOptions, type UseClipboardReturn, type UseControllableSignalOptions, type UseDisclosureOptions, type UseDisclosureReturn, type UseDraggableOptions, type UseDraggableReturn, type UseInfiniteScrollOptions, type UseInfiniteScrollReturn, type UseIntersectionObserverOptions, type UseIntersectionObserverReturn, type UseMutationObserverReturn, type UseResizeObserverOptions, type UseResizeObserverReturn, type UseRovingFocusOptions, type UseRovingFocusReturn, VERSION, VisuallyHidden, type VisuallyHiddenProps, type WcagLevel, batch, cn, composeRefs, computePosition, computed, createMachine, effect, mergeProps, signal, useAnchorPosition, useClipboard, useComputed, useControllableSignal, useDisclosure, useDraggable, useId, useInfiniteScroll, useIntersectionObserver, useMachine, useMediaQuery, useMutationObserver, useResizeObserver, useRovingFocus, useScrollLock, useSignal, useSignalEffect, useSignals };
372
519
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/machine.ts","../src/utils.ts","../src/error-boundary.tsx","../src/suspense-boundary.tsx","../src/portal.tsx","../src/visually-hidden.tsx","../src/focus-scope.tsx","../src/slot.tsx","../src/controllable.ts","../src/media-query.ts","../src/scroll-lock.ts","../src/use-id.ts","../src/clipboard.ts","../src/dismissable-layer.tsx","../src/roving-focus.ts","../src/presence.tsx","../src/anchor.tsx","../src/index.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/machine.ts","../src/utils.ts","../src/error-boundary.tsx","../src/suspense-boundary.tsx","../src/portal.tsx","../src/visually-hidden.tsx","../src/focus-scope.tsx","../src/slot.tsx","../src/controllable.ts","../src/media-query.ts","../src/scroll-lock.ts","../src/use-id.ts","../src/clipboard.ts","../src/disclosure.ts","../src/infinite-scroll.ts","../src/draggable.ts","../src/resize-observer.ts","../src/mutation-observer.ts","../src/intersection-observer.ts","../src/dismissable-layer.tsx","../src/roving-focus.ts","../src/presence.tsx","../src/anchor.tsx","../src/index.ts"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -93,8 +93,8 @@ function Portal({ children, container }) {
93
93
  //#endregion
94
94
  //#region src/visually-hidden.module.css
95
95
  var visually_hidden_module_default = {
96
- "focusable": "rYIFkW_focusable",
97
- "root": "rYIFkW_root"
96
+ "focusable": "_9NCpAq_focusable",
97
+ "root": "_9NCpAq_root"
98
98
  };
99
99
  //#endregion
100
100
  //#region src/visually-hidden.tsx
@@ -171,8 +171,8 @@ const Slot = forwardRef(function Slot(props, forwardedRef) {
171
171
  const merged = mergeProps(slotProps, childProps);
172
172
  merged["className"] = cn(slotClassName, childProps["className"]);
173
173
  merged["style"] = {
174
- ...slotStyle ?? {},
175
- ...childProps["style"] ?? {}
174
+ ...slotStyle,
175
+ ...childProps["style"]
176
176
  };
177
177
  merged["ref"] = composeRefs(forwardedRef, childProps["ref"] ?? child.ref);
178
178
  return cloneElement(child, merged);
@@ -295,6 +295,256 @@ function useClipboard(options = {}) {
295
295
  };
296
296
  }
297
297
  //#endregion
298
+ //#region src/disclosure.ts
299
+ /**
300
+ * Open/close state for overlays (modal, drawer, popover, …), replacing the
301
+ * per-component `useSignal(open)` boilerplate. Backed by `useControllableSignal`
302
+ * so it is controlled when `isOpen` is passed and uncontrolled otherwise.
303
+ * Replaces HeroUI's `@heroui/use-disclosure`. No `useState`/`useEffect`.
304
+ */
305
+ function useDisclosure(options = {}) {
306
+ const { defaultOpen = false, isOpen, onOpenChange } = options;
307
+ const [open, setOpen] = useControllableSignal({
308
+ value: isOpen,
309
+ defaultValue: defaultOpen,
310
+ onChange: onOpenChange
311
+ });
312
+ return {
313
+ isOpen: open,
314
+ open: () => setOpen(true),
315
+ close: () => setOpen(false),
316
+ toggle: () => setOpen(!open.value)
317
+ };
318
+ }
319
+ //#endregion
320
+ //#region src/infinite-scroll.ts
321
+ /**
322
+ * Calls `onLoadMore` when a sentinel element scrolls into view, gated by
323
+ * `hasMore`. Wires an `IntersectionObserver` inside `useSignalEffect` and
324
+ * disconnects it on teardown. SSR/no-DOM safe. Replaces HeroUI's
325
+ * `@heroui/use-infinite-scroll`. No `useState`/`useEffect`.
326
+ */
327
+ function useInfiniteScroll(options) {
328
+ const { rootMargin = "0px" } = options;
329
+ const sentinelRef = useRef(null);
330
+ const rootRef = useRef(null);
331
+ const enabled = useSignal(options.isEnabled ?? true);
332
+ enabled.value = options.isEnabled ?? true;
333
+ const hasMore = useSignal(options.hasMore);
334
+ hasMore.value = options.hasMore;
335
+ const onLoadMoreRef = useRef(options.onLoadMore);
336
+ onLoadMoreRef.current = options.onLoadMore;
337
+ useSignalEffect(() => {
338
+ const isEnabled = enabled.value;
339
+ const sentinel = sentinelRef.current;
340
+ if (!isEnabled || typeof IntersectionObserver === "undefined" || !sentinel) return;
341
+ const observer = new IntersectionObserver((entries) => {
342
+ if (entries[0]?.isIntersecting && hasMore.value) onLoadMoreRef.current();
343
+ }, {
344
+ root: rootRef.current ?? null,
345
+ rootMargin
346
+ });
347
+ observer.observe(sentinel);
348
+ return () => observer.disconnect();
349
+ });
350
+ return {
351
+ sentinelRef,
352
+ rootRef
353
+ };
354
+ }
355
+ //#endregion
356
+ //#region src/draggable.ts
357
+ /**
358
+ * Pointer-driven drag offset for overlays. Returns an `{ x, y }` offset signal
359
+ * the caller applies as a CSS `translate` (no animation library). Attaches
360
+ * pointer listeners in `useSignalEffect` and removes them on teardown.
361
+ * SSR/no-DOM safe. Replaces HeroUI's `@heroui/use-draggable` (Framer-free).
362
+ * No `useState`/`useEffect`.
363
+ */
364
+ function useDraggable(options = {}) {
365
+ const handleRef = useRef(null);
366
+ const targetRef = useRef(null);
367
+ const offset = useSignal({
368
+ x: 0,
369
+ y: 0
370
+ });
371
+ const isDragging = useSignal(false);
372
+ const disabled = useSignal(options.isDisabled ?? false);
373
+ disabled.value = options.isDisabled ?? false;
374
+ const axisRef = useRef(options.axis ?? "both");
375
+ axisRef.current = options.axis ?? "both";
376
+ const onDragEndRef = useRef(options.onDragEnd);
377
+ onDragEndRef.current = options.onDragEnd;
378
+ useSignalEffect(() => {
379
+ if (disabled.value || typeof window === "undefined") return;
380
+ let startX = 0;
381
+ let startY = 0;
382
+ let baseX = 0;
383
+ let baseY = 0;
384
+ let activeHandle = null;
385
+ const onMove = (event) => {
386
+ const axis = axisRef.current;
387
+ const dx = axis === "y" ? 0 : event.clientX - startX;
388
+ const dy = axis === "x" ? 0 : event.clientY - startY;
389
+ offset.value = {
390
+ x: baseX + dx,
391
+ y: baseY + dy
392
+ };
393
+ };
394
+ const onUp = (event) => {
395
+ isDragging.value = false;
396
+ activeHandle?.releasePointerCapture?.(event.pointerId);
397
+ activeHandle = null;
398
+ window.removeEventListener("pointermove", onMove);
399
+ window.removeEventListener("pointerup", onUp);
400
+ window.removeEventListener("pointercancel", onUp);
401
+ onDragEndRef.current?.(offset.value);
402
+ };
403
+ const onDown = (event) => {
404
+ const handle = handleRef.current;
405
+ const target = event.target;
406
+ if (!handle || !(target instanceof Node) || !handle.contains(target)) return;
407
+ activeHandle = handle;
408
+ startX = event.clientX;
409
+ startY = event.clientY;
410
+ baseX = offset.value.x;
411
+ baseY = offset.value.y;
412
+ isDragging.value = true;
413
+ handle.setPointerCapture?.(event.pointerId);
414
+ window.addEventListener("pointermove", onMove);
415
+ window.addEventListener("pointerup", onUp);
416
+ window.addEventListener("pointercancel", onUp);
417
+ };
418
+ window.addEventListener("pointerdown", onDown);
419
+ return () => {
420
+ window.removeEventListener("pointerdown", onDown);
421
+ window.removeEventListener("pointermove", onMove);
422
+ window.removeEventListener("pointerup", onUp);
423
+ window.removeEventListener("pointercancel", onUp);
424
+ };
425
+ });
426
+ return {
427
+ handleRef,
428
+ targetRef,
429
+ offset,
430
+ isDragging,
431
+ reset: () => {
432
+ offset.value = {
433
+ x: 0,
434
+ y: 0
435
+ };
436
+ }
437
+ };
438
+ }
439
+ //#endregion
440
+ //#region src/resize-observer.ts
441
+ /**
442
+ * Observes an element's size with a `ResizeObserver`, exposing the latest size
443
+ * as a signal. The declarative counterpart to Web Awesome's `wa-resize-observer`.
444
+ * Wires the observer inside `useSignalEffect` and disconnects it on teardown.
445
+ * SSR/no-DOM safe. No `useState`/`useEffect`.
446
+ */
447
+ function useResizeObserver(options = {}) {
448
+ const ref = useRef(null);
449
+ const size = useSignal(null);
450
+ const entry = useSignal(null);
451
+ const box = useSignal(options.box);
452
+ box.value = options.box;
453
+ useSignalEffect(() => {
454
+ const observeBox = box.value;
455
+ const el = ref.current;
456
+ if (!el || typeof ResizeObserver === "undefined") return;
457
+ const observer = new ResizeObserver((entries) => {
458
+ const next = entries[entries.length - 1];
459
+ if (!next) return;
460
+ entry.value = next;
461
+ size.value = {
462
+ width: next.contentRect.width,
463
+ height: next.contentRect.height
464
+ };
465
+ });
466
+ observer.observe(el, observeBox ? { box: observeBox } : void 0);
467
+ return () => observer.disconnect();
468
+ });
469
+ return {
470
+ ref,
471
+ size,
472
+ entry
473
+ };
474
+ }
475
+ //#endregion
476
+ //#region src/mutation-observer.ts
477
+ /**
478
+ * Watches a node's DOM tree with a `MutationObserver`, exposing the latest batch
479
+ * of records as a signal. The declarative counterpart to Web Awesome's
480
+ * `wa-mutation-observer`. Wires the observer inside `useSignalEffect` and
481
+ * disconnects it on teardown. SSR/no-DOM safe. No `useState`/`useEffect`.
482
+ *
483
+ * `init` is read when the effect (re-)subscribes; pass a stable object or accept
484
+ * that changing it between renders won't re-subscribe (mount-time semantics).
485
+ */
486
+ function useMutationObserver(init = { childList: true }) {
487
+ const ref = useRef(null);
488
+ const records = useSignal([]);
489
+ const initRef = useRef(init);
490
+ initRef.current = init;
491
+ useSignalEffect(() => {
492
+ const node = ref.current;
493
+ if (!node || typeof MutationObserver === "undefined") return;
494
+ const observer = new MutationObserver((mutations) => {
495
+ records.value = mutations;
496
+ });
497
+ observer.observe(node, initRef.current);
498
+ return () => observer.disconnect();
499
+ });
500
+ return {
501
+ ref,
502
+ records
503
+ };
504
+ }
505
+ //#endregion
506
+ //#region src/intersection-observer.ts
507
+ /**
508
+ * Tracks an element's visibility with an `IntersectionObserver`, exposing the
509
+ * latest entry and an `isIntersecting` flag as signals. The declarative
510
+ * counterpart to Web Awesome's `wa-intersection-observer`.
511
+ *
512
+ * This is the **generic** observer — for "load more when a sentinel appears"
513
+ * use `useInfiniteScroll`, which adds `hasMore`/`onLoadMore` semantics on top.
514
+ *
515
+ * Wires the observer inside `useSignalEffect` and disconnects it on teardown.
516
+ * SSR/no-DOM safe. No `useState`/`useEffect`.
517
+ */
518
+ function useIntersectionObserver(options = {}) {
519
+ const { rootMargin = "0px", threshold = 0 } = options;
520
+ const ref = useRef(null);
521
+ const entry = useSignal(null);
522
+ const isIntersecting = useSignal(false);
523
+ const rootRef = useRef(options.root ?? null);
524
+ rootRef.current = options.root ?? null;
525
+ useSignalEffect(() => {
526
+ const el = ref.current;
527
+ if (!el || typeof IntersectionObserver === "undefined") return;
528
+ const observer = new IntersectionObserver((entries) => {
529
+ const next = entries[entries.length - 1];
530
+ if (!next) return;
531
+ entry.value = next;
532
+ isIntersecting.value = next.isIntersecting;
533
+ }, {
534
+ root: rootRef.current,
535
+ rootMargin,
536
+ threshold
537
+ });
538
+ observer.observe(el);
539
+ return () => observer.disconnect();
540
+ });
541
+ return {
542
+ ref,
543
+ entry,
544
+ isIntersecting
545
+ };
546
+ }
547
+ //#endregion
298
548
  //#region src/dismissable-layer.tsx
299
549
  const layers = [];
300
550
  /**
@@ -556,6 +806,6 @@ function useAnchorPosition(options) {
556
806
  //#region src/index.ts
557
807
  const VERSION = "0.0.0";
558
808
  //#endregion
559
- export { DismissableLayer, ErrorBoundary, FocusScope, Portal, Presence, Slot, SuspenseBoundary, VERSION, VisuallyHidden, batch, cn, composeRefs, computePosition, computed, createMachine, effect, mergeProps, signal, useAnchorPosition, useClipboard, useComputed, useControllableSignal, useId, useMachine, useMediaQuery, useRovingFocus, useScrollLock, useSignal, useSignalEffect, useSignals };
809
+ export { DismissableLayer, ErrorBoundary, FocusScope, Portal, Presence, Slot, SuspenseBoundary, VERSION, VisuallyHidden, batch, cn, composeRefs, computePosition, computed, createMachine, effect, mergeProps, signal, useAnchorPosition, useClipboard, useComputed, useControllableSignal, useDisclosure, useDraggable, useId, useInfiniteScroll, useIntersectionObserver, useMachine, useMediaQuery, useMutationObserver, useResizeObserver, useRovingFocus, useScrollLock, useSignal, useSignalEffect, useSignals };
560
810
 
561
811
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["useSignal","styles","useReactId"],"sources":["../src/machine.ts","../src/utils.ts","../src/error-boundary.tsx","../src/suspense-boundary.tsx","../src/portal.tsx","../src/visually-hidden.module.css","../src/visually-hidden.tsx","../src/focus-scope.tsx","../src/slot.tsx","../src/controllable.ts","../src/media-query.ts","../src/scroll-lock.ts","../src/use-id.ts","../src/clipboard.ts","../src/dismissable-layer.tsx","../src/roving-focus.ts","../src/presence.tsx","../src/anchor.tsx","../src/index.ts"],"sourcesContent":["import { useSignal } from '@preact/signals-react'\nimport type { Machine, MachineConfig } from './types.ts'\n\nexport function createMachine<S extends string, I extends S = S>(\n config: MachineConfig<S, I>,\n): Machine<S> {\n return config as Machine<S>\n}\n\nexport function useMachine<S extends string>(machine: Machine<S>) {\n const state = useSignal<S>(machine.initial)\n const send = (event: string) => {\n const next = machine.states[state.value]?.on?.[event] as S | undefined\n if (next !== undefined) state.value = next\n }\n return [state, send] as const\n}\n","import type { Ref, RefCallback, MutableRefObject } from 'react'\n\nexport function cn(...classes: (string | undefined | null | false | 0)[]): string {\n return classes.filter(Boolean).join(' ')\n}\n\nexport function composeRefs<T>(...refs: (Ref<T> | undefined)[]): RefCallback<T> {\n return (value) => {\n refs.forEach((ref) => {\n if (!ref) return\n if (typeof ref === 'function') {\n ref(value)\n } else {\n ;(ref as MutableRefObject<T | null>).current = value\n }\n })\n }\n}\n\ntype EventHandler = (...args: unknown[]) => unknown\n\nexport function mergeProps<T extends Record<string, unknown>>(...propsList: Partial<T>[]): T {\n const result: Record<string, unknown> = {}\n for (const props of propsList) {\n for (const [key, val] of Object.entries(props)) {\n if (key.startsWith('on') && typeof val === 'function' && typeof result[key] === 'function') {\n const existing = result[key] as EventHandler\n result[key] = (...args: unknown[]) => {\n existing(...args)\n ;(val as EventHandler)(...args)\n }\n } else {\n result[key] = val\n }\n }\n }\n return result as T\n}\n","import { Component, type ErrorInfo, type ReactNode } from 'react'\n\nexport interface ErrorBoundaryProps {\n children: ReactNode\n fallback: ReactNode | ((error: Error, reset: () => void) => ReactNode)\n onError?: (error: Error, info: ErrorInfo) => void\n}\n\ninterface State {\n error: Error | null\n}\n\nexport class ErrorBoundary extends Component<ErrorBoundaryProps, State> {\n state: State = { error: null }\n\n static getDerivedStateFromError(error: Error): State {\n return { error }\n }\n\n componentDidCatch(error: Error, info: ErrorInfo) {\n this.props.onError?.(error, info)\n }\n\n reset = () => {\n this.setState({ error: null })\n }\n\n render() {\n const { error } = this.state\n if (error !== null) {\n const { fallback } = this.props\n return typeof fallback === 'function' ? fallback(error, this.reset) : fallback\n }\n return this.props.children\n }\n}\n","import { Suspense, type ReactNode } from 'react'\n\nexport interface SuspenseBoundaryProps {\n children: ReactNode\n fallback?: ReactNode\n}\n\nexport function SuspenseBoundary({ children, fallback }: SuspenseBoundaryProps) {\n return (\n <Suspense fallback={fallback ?? <span aria-busy=\"true\" aria-label=\"Loading\" />}>\n {children}\n </Suspense>\n )\n}\n","'use client'\nimport { useRef, type ReactNode } from 'react'\nimport { createPortal } from 'react-dom'\nimport { useSignal, useSignalEffect, useSignals } from './signals.ts'\n\nexport interface PortalProps {\n children: ReactNode\n container?: Element | DocumentFragment\n}\n\nexport function Portal({ children, container }: PortalProps) {\n useSignals()\n const mounted = useSignal(false)\n const containerRef = useRef<Element | DocumentFragment | null>(null)\n\n useSignalEffect(() => {\n if (typeof document === 'undefined') return\n containerRef.current = container ?? document.body\n mounted.value = true\n return () => {\n mounted.value = false\n }\n })\n\n if (!mounted.value || containerRef.current === null) return null\n return createPortal(children, containerRef.current)\n}\n","@layer cascade.component {\n .root {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n }\n .focusable:focus,\n .focusable:focus-within {\n position: static;\n width: auto;\n height: auto;\n padding: var(--cascivo-space-2) var(--cascivo-space-3);\n margin: 0;\n overflow: visible;\n clip: auto;\n white-space: normal;\n }\n}\n","import type { ReactNode, HTMLAttributes } from 'react'\nimport styles from './visually-hidden.module.css'\n\nexport interface VisuallyHiddenProps extends HTMLAttributes<HTMLSpanElement> {\n children: ReactNode\n showOnFocus?: boolean\n}\n\nexport function VisuallyHidden({\n children,\n showOnFocus,\n className,\n ...props\n}: VisuallyHiddenProps) {\n const classes = [styles.root, showOnFocus && styles.focusable, className]\n .filter(Boolean)\n .join(' ')\n return (\n <span className={classes} {...props}>\n {children}\n </span>\n )\n}\n","'use client'\nimport { useRef, type ReactNode } from 'react'\nimport { useSignalEffect, useSignals } from './signals.ts'\n\nconst FOCUSABLE =\n 'a[href],area[href],input:not([disabled]),select:not([disabled]),textarea:not([disabled]),button:not([disabled]),[tabindex]:not([tabindex=\"-1\"])'\n\nexport interface FocusScopeProps {\n children: ReactNode\n trapped?: boolean\n restoreFocus?: boolean\n autoFocus?: boolean\n}\n\nexport function FocusScope({ children, trapped, restoreFocus, autoFocus }: FocusScopeProps) {\n useSignals()\n const containerRef = useRef<HTMLDivElement>(null)\n const previousFocusRef = useRef<Element | null>(null)\n\n useSignalEffect(() => {\n const container = containerRef.current\n if (!container) return\n if (restoreFocus) previousFocusRef.current = document.activeElement\n if (autoFocus) {\n const first = container.querySelector<HTMLElement>(FOCUSABLE)\n first?.focus()\n }\n if (!trapped) return\n function handleKeyDown(e: KeyboardEvent) {\n if (e.key !== 'Tab') return\n const focusable = Array.from(container!.querySelectorAll<HTMLElement>(FOCUSABLE))\n if (focusable.length === 0) return\n const first = focusable[0]!\n const last = focusable[focusable.length - 1]!\n if (e.shiftKey) {\n if (document.activeElement === first) {\n e.preventDefault()\n last.focus()\n }\n } else {\n if (document.activeElement === last) {\n e.preventDefault()\n first.focus()\n }\n }\n }\n document.addEventListener('keydown', handleKeyDown)\n return () => {\n document.removeEventListener('keydown', handleKeyDown)\n if (restoreFocus && previousFocusRef.current instanceof HTMLElement) {\n previousFocusRef.current.focus()\n }\n }\n })\n\n return <div ref={containerRef}>{children}</div>\n}\n","'use client'\nimport {\n Children,\n cloneElement,\n forwardRef,\n isValidElement,\n type CSSProperties,\n type ReactElement,\n type ReactNode,\n type Ref,\n} from 'react'\nimport { cn, composeRefs, mergeProps } from './utils.ts'\n\nexport interface SlotProps {\n children?: ReactNode\n className?: string\n style?: CSSProperties\n [key: string]: unknown\n}\n\ntype ElementWithRef = ReactElement<Record<string, unknown>> & { ref?: Ref<unknown> }\n\n/**\n * Slot renders its single child element while merging the Slot's own props and ref onto it —\n * the Radix-style `asChild` composition primitive, built on cascivo's existing\n * `mergeProps`/`composeRefs`/`cn`.\n *\n * - Non-handler props: child wins (so the consumer's element controls its own identity).\n * - `on*` handlers: chained (Slot's runs, then the child's) via `mergeProps`.\n * - `className`: unioned via `cn`. `style`: shallow-merged (child wins on conflicts).\n * - `ref`: composed so both the Slot consumer's ref and the child's own ref receive the node.\n *\n * Fails fast if `children` is not exactly one valid React element.\n */\nexport const Slot = forwardRef<unknown, SlotProps>(function Slot(props, forwardedRef) {\n const { children, className: slotClassName, style: slotStyle, ...slotProps } = props\n\n if (!isValidElement(children) || Children.count(children) !== 1) {\n throw new Error(\n 'Slot expects exactly one valid React element child (asChild requires a single element).',\n )\n }\n\n const child = children as ElementWithRef\n const childProps = child.props\n const merged = mergeProps<Record<string, unknown>>(slotProps, childProps)\n\n merged['className'] = cn(\n slotClassName as string | undefined,\n childProps['className'] as string | undefined,\n )\n merged['style'] = {\n ...((slotStyle as CSSProperties | undefined) ?? {}),\n ...((childProps['style'] as CSSProperties | undefined) ?? {}),\n }\n // React 19 exposes ref as a regular prop; React 18 keeps it on the element. Prefer props.ref to\n // avoid touching the deprecated `element.ref` getter when it isn't needed.\n const childRef = (childProps['ref'] as Ref<unknown> | undefined) ?? child.ref\n merged['ref'] = composeRefs(forwardedRef, childRef)\n\n return cloneElement(child, merged)\n})\n","'use client'\nimport { useRef } from 'react'\nimport { useSignal, type Signal } from './signals.ts'\n\nexport interface UseControllableSignalOptions<T> {\n /** Controlled value. When provided, the component is controlled for its whole life. */\n value?: T | undefined\n /** Initial value for uncontrolled use. */\n defaultValue?: T | undefined\n /** Called on every write, in both controlled and uncontrolled modes. */\n onChange?: ((value: T) => void) | undefined\n}\n\n/**\n * Codifies the controlled/uncontrolled pattern documented by hand in CLAUDE.md\n * (`const x = useSignal(open); x.value = open`).\n *\n * Returns a `[signal, setValue]` pair:\n * - **Controlled** (`value` provided): the signal mirrors `value` on every render; `setValue`\n * does not mutate the signal locally — it only routes the request through `onChange` so the\n * parent owns the state (React semantics).\n * - **Uncontrolled** (`value` undefined): the signal owns the state seeded from `defaultValue`;\n * `setValue` updates it locally and also calls `onChange`.\n *\n * Whether a component is controlled is fixed for its life, exactly like React.\n */\nexport function useControllableSignal<T>(\n options: UseControllableSignalOptions<T>,\n): [Signal<T>, (next: T) => void] {\n const { value, defaultValue, onChange } = options\n const isControlled = value !== undefined\n\n const sig = useSignal<T>((isControlled ? value : defaultValue) as T)\n\n // Keep onChange current without an effect (the onCloseRef idiom from CLAUDE.md).\n const onChangeRef = useRef(onChange)\n onChangeRef.current = onChange\n\n // Controlled: mirror the prop into the signal each render (no-op if unchanged).\n if (isControlled) sig.value = value as T\n\n const setValue = (next: T): void => {\n if (!isControlled) sig.value = next\n onChangeRef.current?.(next)\n }\n\n return [sig, setValue]\n}\n","'use client'\nimport { useSignal, useSignalEffect, type ReadonlySignal } from './signals.ts'\n\nfunction initialMatch(query: string): boolean {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return false\n return window.matchMedia(query).matches\n}\n\n/**\n * `useMediaQuery(query)` → a `ReadonlySignal<boolean>` that tracks whether the media query matches.\n * SSR-safe (defaults to `false` when `window`/`matchMedia` is unavailable). Subscribes to changes\n * inside `useSignalEffect`; the listener is cleaned up on teardown.\n */\nexport function useMediaQuery(query: string): ReadonlySignal<boolean> {\n const matches = useSignal(initialMatch(query))\n\n useSignalEffect(() => {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return\n const mql = window.matchMedia(query)\n matches.value = mql.matches\n const handler = (event: MediaQueryListEvent): void => {\n matches.value = event.matches\n }\n mql.addEventListener('change', handler)\n return () => mql.removeEventListener('change', handler)\n })\n\n return matches\n}\n","'use client'\nimport { useSignalEffect, type Signal } from './signals.ts'\n\n/**\n * Locks `document.body` scroll while `locked` is true, compensating for the scrollbar width to\n * avoid a layout shift. Accepts a plain boolean or a `Signal<boolean>` (reactive). The previous\n * inline styles are restored on unlock/teardown. DOM work runs inside `useSignalEffect`.\n */\nexport function useScrollLock(locked: Signal<boolean> | boolean): void {\n useSignalEffect(() => {\n const isLocked = typeof locked === 'boolean' ? locked : locked.value\n if (!isLocked || typeof document === 'undefined') return\n\n const body = document.body\n const prevOverflow = body.style.overflow\n const prevPaddingInlineEnd = body.style.paddingInlineEnd\n\n const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth\n body.style.overflow = 'hidden'\n if (scrollbarWidth > 0) {\n const current = Number.parseInt(window.getComputedStyle(body).paddingInlineEnd, 10) || 0\n body.style.paddingInlineEnd = `${current + scrollbarWidth}px`\n }\n\n return () => {\n body.style.overflow = prevOverflow\n body.style.paddingInlineEnd = prevPaddingInlineEnd\n }\n })\n}\n","'use client'\nimport { useId as useReactId } from 'react'\n\n/**\n * SSR-safe stable id for aria wiring (Label/Field). Wraps React's `useId` and strips the\n * non-selector-safe colons React emits, prefixing for readability (default `cascivo`).\n */\nexport function useId(prefix = 'cascivo'): string {\n const id = useReactId()\n return `${prefix}-${id.replace(/:/g, '')}`\n}\n","'use client'\nimport { useRef } from 'react'\nimport { useSignal, useSignalEffect, type ReadonlySignal } from './signals.ts'\n\nexport interface UseClipboardOptions {\n /** How long `copied` stays true after a successful copy, in ms. */\n timeout?: number\n}\n\nexport interface UseClipboardReturn {\n copied: ReadonlySignal<boolean>\n copy: (text: string) => Promise<void>\n}\n\n/**\n * Copy-to-clipboard helper that de-dupes the inline logic in `copy-button`.\n * `copy(text)` writes via `navigator.clipboard.writeText`; `copied` flips true then resets after\n * `timeout`. The reset timer is cleared on unmount via `useSignalEffect` (no `useEffect`).\n */\nexport function useClipboard(options: UseClipboardOptions = {}): UseClipboardReturn {\n const { timeout = 2000 } = options\n const copied = useSignal(false)\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n\n useSignalEffect(() => {\n return () => {\n if (timerRef.current) clearTimeout(timerRef.current)\n }\n })\n\n const copy = async (text: string): Promise<void> => {\n if (typeof navigator === 'undefined' || !navigator.clipboard) return\n await navigator.clipboard.writeText(text)\n copied.value = true\n if (timerRef.current) clearTimeout(timerRef.current)\n timerRef.current = setTimeout(() => {\n copied.value = false\n }, timeout)\n }\n\n return { copied, copy }\n}\n","'use client'\nimport { useRef, type ReactNode } from 'react'\nimport { useSignalEffect, useSignals } from './signals.ts'\n\n// Module-level registry so nested layers (a dropdown inside a modal) dismiss top-first. Effects\n// mount child-first, so a stack would invert nesting; instead we resolve the top-most layer by DOM\n// depth — a layer responds only when no other mounted layer's root is nested inside its own root.\ninterface LayerEntry {\n getRoot: () => HTMLElement | null\n}\nconst layers: LayerEntry[] = []\n\nexport interface DismissableLayerProps {\n children: ReactNode\n /** Called when an outside pointer lands or Escape is pressed (when this is the top-most layer). */\n onDismiss?: () => void\n /** Disable the outside-pointer dismissal (Escape still works unless escapeDismisses is false). */\n disableOutsidePointer?: boolean\n /** Whether Escape dismisses the top-most layer. Default true. */\n escapeDismisses?: boolean\n}\n\n/**\n * Wraps its children and dismisses on an outside pointer press or Escape. One tested implementation\n * of the dismissal logic every overlay reinvents. Nested layers form a stack — only the top-most\n * responds to Escape and outside pointers. All listeners run inside `useSignalEffect`.\n */\nexport function DismissableLayer({\n children,\n onDismiss,\n disableOutsidePointer,\n escapeDismisses = true,\n}: DismissableLayerProps) {\n useSignals()\n const rootRef = useRef<HTMLDivElement>(null)\n const onDismissRef = useRef(onDismiss)\n onDismissRef.current = onDismiss\n\n useSignalEffect(() => {\n if (typeof document === 'undefined') return\n const entry: LayerEntry = { getRoot: () => rootRef.current }\n layers.push(entry)\n\n // Top-most = deepest in the DOM: no other mounted layer's root sits inside this one's root.\n const isTop = (): boolean => {\n const root = rootRef.current\n if (!root) return false\n for (const other of layers) {\n if (other === entry) continue\n const otherRoot = other.getRoot()\n if (otherRoot && root.contains(otherRoot)) return false\n }\n return true\n }\n\n const handlePointer = (event: PointerEvent): void => {\n if (disableOutsidePointer || !isTop()) return\n const root = rootRef.current\n if (!root) return\n if (event.target instanceof Node && !root.contains(event.target)) {\n onDismissRef.current?.()\n }\n }\n const handleKey = (event: KeyboardEvent): void => {\n if (!escapeDismisses || event.key !== 'Escape' || !isTop()) return\n onDismissRef.current?.()\n }\n\n document.addEventListener('pointerdown', handlePointer, true)\n document.addEventListener('keydown', handleKey)\n return () => {\n document.removeEventListener('pointerdown', handlePointer, true)\n document.removeEventListener('keydown', handleKey)\n const idx = layers.indexOf(entry)\n if (idx !== -1) layers.splice(idx, 1)\n }\n })\n\n return <div ref={rootRef}>{children}</div>\n}\n","'use client'\nimport { useRef } from 'react'\nimport { useSignal, type ReadonlySignal } from './signals.ts'\n\nexport type RovingOrientation = 'horizontal' | 'vertical' | 'both'\n\nexport interface UseRovingFocusOptions {\n orientation?: RovingOrientation\n /** Wrap focus from last→first and first→last. Default false. */\n loop?: boolean\n /** Initial active index. Default 0. */\n defaultIndex?: number\n}\n\nexport interface RovingItemProps {\n tabIndex: number\n onKeyDown: (event: React.KeyboardEvent<HTMLElement>) => void\n onFocus: () => void\n ref: (el: HTMLElement | null) => void\n}\n\nexport interface UseRovingFocusReturn {\n activeIndex: ReadonlySignal<number>\n getItemProps: (index: number) => RovingItemProps\n setActiveIndex: (index: number) => void\n}\n\n/**\n * Roving tabindex without context. The parent calls `useRovingFocus(...)` and spreads\n * `getItemProps(i)` onto each item. Only the active item is tabbable (`tabIndex={0}`); arrow keys\n * move focus (Home/End jump; `loop` wraps). State lives in a signal — the consuming component reads\n * it during render, so React apps must call `useSignals()` (per the project rule).\n */\nexport function useRovingFocus(options: UseRovingFocusOptions = {}): UseRovingFocusReturn {\n const { orientation = 'horizontal', loop = false, defaultIndex = 0 } = options\n const activeIndex = useSignal(defaultIndex)\n const itemsRef = useRef<(HTMLElement | null)[]>([])\n\n const focusIndex = (index: number): void => {\n activeIndex.value = index\n itemsRef.current[index]?.focus()\n }\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLElement>, index: number): void => {\n const count = itemsRef.current.length\n if (count === 0) return\n const horizontal = orientation !== 'vertical'\n const vertical = orientation !== 'horizontal'\n let next = index\n if ((horizontal && event.key === 'ArrowRight') || (vertical && event.key === 'ArrowDown')) {\n next = index + 1\n } else if ((horizontal && event.key === 'ArrowLeft') || (vertical && event.key === 'ArrowUp')) {\n next = index - 1\n } else if (event.key === 'Home') {\n next = 0\n } else if (event.key === 'End') {\n next = count - 1\n } else {\n return\n }\n event.preventDefault()\n if (next < 0) next = loop ? count - 1 : 0\n if (next >= count) next = loop ? 0 : count - 1\n focusIndex(next)\n }\n\n const getItemProps = (index: number): RovingItemProps => ({\n tabIndex: activeIndex.value === index ? 0 : -1,\n onKeyDown: (event) => handleKeyDown(event, index),\n onFocus: () => {\n activeIndex.value = index\n },\n ref: (el) => {\n itemsRef.current[index] = el\n },\n })\n\n return {\n activeIndex,\n getItemProps,\n setActiveIndex: (index: number) => {\n activeIndex.value = index\n },\n }\n}\n","'use client'\nimport { cloneElement, isValidElement, useRef, type ReactElement, type Ref } from 'react'\nimport { useSignal, useSignalEffect, useSignals, type Signal } from './signals.ts'\nimport { composeRefs } from './utils.ts'\n\nexport interface PresenceProps {\n /** Whether the content should be present. Accepts a boolean or a Signal. */\n present: boolean | Signal<boolean>\n /** A single element child; receives `data-state=\"open|closed\"` and a composed ref. */\n children: ReactElement\n}\n\ntype ElementWithRef = ReactElement<Record<string, unknown>> & { ref?: Ref<unknown> }\n\n/**\n * Defers unmount until an exit animation/transition finishes. While `present` is true the child is\n * mounted with `data-state=\"open\"`; when it flips to false the child gets `data-state=\"closed\"` and\n * stays mounted until its `animationend`/`transitionend` fires (or unmounts immediately if there is\n * no animation). CSS drives the visuals via `data-state`; this only manages mount timing. No\n * `useEffect` — the present prop is mirrored into a signal and watched with `useSignalEffect`.\n */\nexport function Presence({ present, children }: PresenceProps) {\n useSignals()\n const presentVal = typeof present === 'boolean' ? present : present.value\n\n // Mirror the (boolean or signal) present value into a local signal so the effect reacts to both\n // prop changes (re-render) and signal changes.\n const presentSig = useSignal(presentVal)\n presentSig.value = presentVal\n\n const isMounted = useSignal(presentVal)\n const state = useSignal<'open' | 'closed'>(presentVal ? 'open' : 'closed')\n const nodeRef = useRef<HTMLElement | null>(null)\n\n useSignalEffect(() => {\n if (presentSig.value) {\n isMounted.value = true\n state.value = 'open'\n return\n }\n state.value = 'closed'\n const node = nodeRef.current\n if (!node || typeof getComputedStyle === 'undefined') {\n isMounted.value = false\n return\n }\n const styles = getComputedStyle(node)\n const animates =\n (styles.animationName !== 'none' &&\n styles.animationName !== '' &&\n styles.animationDuration !== '0s') ||\n (styles.transitionDuration !== '0s' && styles.transitionDuration !== '')\n if (!animates) {\n isMounted.value = false\n return\n }\n const handleEnd = (event: Event): void => {\n if (event.target === node) isMounted.value = false\n }\n node.addEventListener('animationend', handleEnd)\n node.addEventListener('transitionend', handleEnd)\n return () => {\n node.removeEventListener('animationend', handleEnd)\n node.removeEventListener('transitionend', handleEnd)\n }\n })\n\n if (!isMounted.value) return null\n if (!isValidElement(children)) {\n throw new Error('Presence expects a single valid React element child')\n }\n const child = children as ElementWithRef\n const childRef = (child.props['ref'] as Ref<unknown> | undefined) ?? child.ref\n return cloneElement(child, {\n 'data-state': state.value,\n ref: composeRefs(childRef, (el: HTMLElement | null) => {\n nodeRef.current = el\n }),\n } as Record<string, unknown>)\n}\n","'use client'\nimport { type CSSProperties, type RefObject } from 'react'\nimport { useSignal, useSignalEffect, useSignals, type Signal } from './signals.ts'\nimport { useId } from './use-id.ts'\n\nexport type AnchorSide = 'top' | 'bottom' | 'left' | 'right'\nexport type AnchorAlign = 'start' | 'center' | 'end'\nexport type AnchorPlacement = AnchorSide | `${AnchorSide}-${'start' | 'end'}`\n\nexport interface UseAnchorPositionOptions {\n anchorRef: RefObject<HTMLElement | null>\n floatingRef: RefObject<HTMLElement | null>\n placement?: AnchorPlacement\n /** Gate positioning (e.g. only while open). Default true. */\n enabled?: boolean | Signal<boolean>\n}\n\nexport interface UseAnchorPositionReturn {\n /** Spread onto the anchor element (CSS-anchor path sets `anchor-name`). */\n anchorStyle: CSSProperties\n /** Spread onto the floating element. */\n floatingStyle: CSSProperties\n /** Whether the CSS anchor-positioning primary path is active. */\n cssAnchorSupported: boolean\n}\n\nfunction supportsCssAnchor(): boolean {\n return (\n typeof CSS !== 'undefined' &&\n typeof CSS.supports === 'function' &&\n CSS.supports('anchor-name: --x')\n )\n}\n\nfunction parsePlacement(placement: AnchorPlacement): { side: AnchorSide; align: AnchorAlign } {\n const [side, end] = placement.split('-') as [AnchorSide, 'start' | 'end' | undefined]\n return { side, align: end ?? 'center' }\n}\n\n/** JS fallback math — position `floating` relative to `anchor` for the given placement. */\nexport function computePosition(\n anchor: {\n top: number\n left: number\n right: number\n bottom: number\n width: number\n height: number\n },\n floating: { width: number; height: number },\n placement: AnchorPlacement,\n): { top: number; left: number } {\n const { side, align } = parsePlacement(placement)\n let top = 0\n let left = 0\n if (side === 'top' || side === 'bottom') {\n top = side === 'bottom' ? anchor.bottom : anchor.top - floating.height\n left =\n align === 'start'\n ? anchor.left\n : align === 'end'\n ? anchor.right - floating.width\n : anchor.left + (anchor.width - floating.width) / 2\n } else {\n left = side === 'right' ? anchor.right : anchor.left - floating.width\n top =\n align === 'start'\n ? anchor.top\n : align === 'end'\n ? anchor.bottom - floating.height\n : anchor.top + (anchor.height - floating.height) / 2\n }\n return { top, left }\n}\n\nfunction cssAnchorInsets(side: AnchorSide, align: AnchorAlign): CSSProperties {\n const style: CSSProperties = {\n position: 'fixed',\n insetBlockStart: 'auto',\n insetInlineStart: 'auto',\n }\n if (side === 'bottom') style.top = 'anchor(bottom)'\n if (side === 'top') style.bottom = 'anchor(top)'\n if (side === 'right') style.left = 'anchor(right)'\n if (side === 'left') style.right = 'anchor(left)'\n if (side === 'top' || side === 'bottom') {\n style.left = align === 'end' ? undefined : align === 'start' ? 'anchor(left)' : 'anchor(center)'\n if (align === 'end') style.right = 'anchor(right)'\n } else {\n style.top = align === 'end' ? undefined : align === 'start' ? 'anchor(top)' : 'anchor(center)'\n if (align === 'end') style.bottom = 'anchor(bottom)'\n }\n return style\n}\n\n/**\n * Positions a floating element relative to an anchor. Primary path: CSS anchor positioning\n * (Chrome-leading) — emits `anchor-name`/`position-anchor` + `anchor()` insets. Fallback (behind\n * `CSS.supports`): JS `getBoundingClientRect` placement recomputed on scroll/resize inside\n * `useSignalEffect`. Small by design — not a collision/flip engine (deferred).\n */\nexport function useAnchorPosition(options: UseAnchorPositionOptions): UseAnchorPositionReturn {\n const { anchorRef, floatingRef, placement = 'bottom', enabled = true } = options\n useSignals()\n const supported = supportsCssAnchor()\n const name = useId('anchor')\n const pos = useSignal<{ top: number; left: number } | null>(null)\n\n useSignalEffect(() => {\n const isEnabled = typeof enabled === 'boolean' ? enabled : enabled.value\n if (supported || !isEnabled || typeof window === 'undefined') return\n\n const update = (): void => {\n const a = anchorRef.current\n const f = floatingRef.current\n if (!a || !f) return\n pos.value = computePosition(a.getBoundingClientRect(), f.getBoundingClientRect(), placement)\n }\n update()\n window.addEventListener('scroll', update, true)\n window.addEventListener('resize', update)\n return () => {\n window.removeEventListener('scroll', update, true)\n window.removeEventListener('resize', update)\n }\n })\n\n if (supported) {\n const { side, align } = parsePlacement(placement)\n return {\n anchorStyle: { anchorName: `--${name}` } as CSSProperties,\n floatingStyle: {\n positionAnchor: `--${name}`,\n ...cssAnchorInsets(side, align),\n } as CSSProperties,\n cssAnchorSupported: true,\n }\n }\n\n const p = pos.value\n return {\n anchorStyle: {},\n floatingStyle: p\n ? { position: 'fixed', top: p.top, left: p.left }\n : { position: 'fixed', visibility: 'hidden' },\n cssAnchorSupported: false,\n }\n}\n","export { createMachine, useMachine } from './machine.ts'\nexport {\n signal,\n computed,\n effect,\n batch,\n useSignal,\n useComputed,\n useSignalEffect,\n useSignals,\n} from './signals.ts'\nexport type { Signal, ReadonlySignal } from './signals.ts'\nexport { cn, composeRefs, mergeProps } from './utils.ts'\nexport type {\n Machine,\n MachineConfig,\n StateConfig,\n ComponentMeta,\n PropMeta,\n AccessibilityMeta,\n WcagLevel,\n ExampleMeta,\n ComponentIntent,\n IntentAntiPattern,\n IntentRelated,\n IntentFlexibility,\n IntentContent,\n IntentRelationship,\n} from './types.ts'\nexport const VERSION = '0.0.0'\nexport { ErrorBoundary } from './error-boundary.tsx'\nexport type { ErrorBoundaryProps } from './error-boundary.tsx'\nexport { SuspenseBoundary } from './suspense-boundary.tsx'\nexport type { SuspenseBoundaryProps } from './suspense-boundary.tsx'\nexport { Portal } from './portal.tsx'\nexport type { PortalProps } from './portal.tsx'\nexport { VisuallyHidden } from './visually-hidden.tsx'\nexport type { VisuallyHiddenProps } from './visually-hidden.tsx'\nexport { FocusScope } from './focus-scope.tsx'\nexport type { FocusScopeProps } from './focus-scope.tsx'\nexport { Slot } from './slot.tsx'\nexport type { SlotProps } from './slot.tsx'\nexport { useControllableSignal } from './controllable.ts'\nexport type { UseControllableSignalOptions } from './controllable.ts'\nexport { useMediaQuery } from './media-query.ts'\nexport { useScrollLock } from './scroll-lock.ts'\nexport { useId } from './use-id.ts'\nexport { useClipboard } from './clipboard.ts'\nexport type { UseClipboardOptions, UseClipboardReturn } from './clipboard.ts'\nexport { DismissableLayer } from './dismissable-layer.tsx'\nexport type { DismissableLayerProps } from './dismissable-layer.tsx'\nexport { useRovingFocus } from './roving-focus.ts'\nexport type {\n UseRovingFocusOptions,\n UseRovingFocusReturn,\n RovingItemProps,\n RovingOrientation,\n} from './roving-focus.ts'\nexport { Presence } from './presence.tsx'\nexport type { PresenceProps } from './presence.tsx'\nexport { useAnchorPosition, computePosition } from './anchor.tsx'\nexport type {\n UseAnchorPositionOptions,\n UseAnchorPositionReturn,\n AnchorPlacement,\n AnchorSide,\n AnchorAlign,\n} from './anchor.tsx'\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["useSignal","styles","useReactId"],"sources":["../src/machine.ts","../src/utils.ts","../src/error-boundary.tsx","../src/suspense-boundary.tsx","../src/portal.tsx","../src/visually-hidden.tsx","../src/focus-scope.tsx","../src/slot.tsx","../src/controllable.ts","../src/media-query.ts","../src/scroll-lock.ts","../src/use-id.ts","../src/clipboard.ts","../src/disclosure.ts","../src/infinite-scroll.ts","../src/draggable.ts","../src/resize-observer.ts","../src/mutation-observer.ts","../src/intersection-observer.ts","../src/dismissable-layer.tsx","../src/roving-focus.ts","../src/presence.tsx","../src/anchor.tsx","../src/index.ts"],"sourcesContent":["import { useSignal } from '@preact/signals-react'\nimport type { Machine, MachineConfig } from './types.ts'\n\nexport function createMachine<S extends string, I extends S = S>(\n config: MachineConfig<S, I>,\n): Machine<S> {\n return config as Machine<S>\n}\n\nexport function useMachine<S extends string>(machine: Machine<S>) {\n const state = useSignal<S>(machine.initial)\n const send = (event: string) => {\n const next = machine.states[state.value]?.on?.[event] as S | undefined\n if (next !== undefined) state.value = next\n }\n return [state, send] as const\n}\n","import type { Ref, RefCallback, MutableRefObject } from 'react'\n\nexport function cn(...classes: (string | undefined | null | false | 0)[]): string {\n return classes.filter(Boolean).join(' ')\n}\n\nexport function composeRefs<T>(...refs: (Ref<T> | undefined)[]): RefCallback<T> {\n return (value) => {\n refs.forEach((ref) => {\n if (!ref) return\n if (typeof ref === 'function') {\n ref(value)\n } else {\n ;(ref as MutableRefObject<T | null>).current = value\n }\n })\n }\n}\n\ntype EventHandler = (...args: unknown[]) => unknown\n\nexport function mergeProps<T extends Record<string, unknown>>(...propsList: Partial<T>[]): T {\n const result: Record<string, unknown> = {}\n for (const props of propsList) {\n for (const [key, val] of Object.entries(props)) {\n if (key.startsWith('on') && typeof val === 'function' && typeof result[key] === 'function') {\n const existing = result[key] as EventHandler\n result[key] = (...args: unknown[]) => {\n existing(...args)\n ;(val as EventHandler)(...args)\n }\n } else {\n result[key] = val\n }\n }\n }\n return result as T\n}\n","import { Component, type ErrorInfo, type ReactNode } from 'react'\n\nexport interface ErrorBoundaryProps {\n children: ReactNode\n fallback: ReactNode | ((error: Error, reset: () => void) => ReactNode)\n onError?: (error: Error, info: ErrorInfo) => void\n}\n\ninterface State {\n error: Error | null\n}\n\nexport class ErrorBoundary extends Component<ErrorBoundaryProps, State> {\n state: State = { error: null }\n\n static getDerivedStateFromError(error: Error): State {\n return { error }\n }\n\n componentDidCatch(error: Error, info: ErrorInfo) {\n this.props.onError?.(error, info)\n }\n\n reset = () => {\n this.setState({ error: null })\n }\n\n render() {\n const { error } = this.state\n if (error !== null) {\n const { fallback } = this.props\n return typeof fallback === 'function' ? fallback(error, this.reset) : fallback\n }\n return this.props.children\n }\n}\n","import { Suspense, type ReactNode } from 'react'\n\nexport interface SuspenseBoundaryProps {\n children: ReactNode\n fallback?: ReactNode\n}\n\nexport function SuspenseBoundary({ children, fallback }: SuspenseBoundaryProps) {\n return (\n <Suspense fallback={fallback ?? <span aria-busy=\"true\" aria-label=\"Loading\" />}>\n {children}\n </Suspense>\n )\n}\n","'use client'\nimport { useRef, type ReactNode } from 'react'\nimport { createPortal } from 'react-dom'\nimport { useSignal, useSignalEffect, useSignals } from './signals.ts'\n\nexport interface PortalProps {\n children: ReactNode\n container?: Element | DocumentFragment\n}\n\nexport function Portal({ children, container }: PortalProps) {\n useSignals()\n const mounted = useSignal(false)\n const containerRef = useRef<Element | DocumentFragment | null>(null)\n\n useSignalEffect(() => {\n if (typeof document === 'undefined') return\n containerRef.current = container ?? document.body\n mounted.value = true\n return () => {\n mounted.value = false\n }\n })\n\n if (!mounted.value || containerRef.current === null) return null\n return createPortal(children, containerRef.current)\n}\n","import type { ReactNode, HTMLAttributes } from 'react'\nimport styles from './visually-hidden.module.css'\n\nexport interface VisuallyHiddenProps extends HTMLAttributes<HTMLSpanElement> {\n children: ReactNode\n showOnFocus?: boolean\n}\n\nexport function VisuallyHidden({\n children,\n showOnFocus,\n className,\n ...props\n}: VisuallyHiddenProps) {\n const classes = [styles.root, showOnFocus && styles.focusable, className]\n .filter(Boolean)\n .join(' ')\n return (\n <span className={classes} {...props}>\n {children}\n </span>\n )\n}\n","'use client'\nimport { useRef, type ReactNode } from 'react'\nimport { useSignalEffect, useSignals } from './signals.ts'\n\nconst FOCUSABLE =\n 'a[href],area[href],input:not([disabled]),select:not([disabled]),textarea:not([disabled]),button:not([disabled]),[tabindex]:not([tabindex=\"-1\"])'\n\nexport interface FocusScopeProps {\n children: ReactNode\n trapped?: boolean\n restoreFocus?: boolean\n autoFocus?: boolean\n}\n\nexport function FocusScope({ children, trapped, restoreFocus, autoFocus }: FocusScopeProps) {\n useSignals()\n const containerRef = useRef<HTMLDivElement>(null)\n const previousFocusRef = useRef<Element | null>(null)\n\n useSignalEffect(() => {\n const container = containerRef.current\n if (!container) return\n if (restoreFocus) previousFocusRef.current = document.activeElement\n if (autoFocus) {\n const first = container.querySelector<HTMLElement>(FOCUSABLE)\n first?.focus()\n }\n if (!trapped) return\n function handleKeyDown(e: KeyboardEvent) {\n if (e.key !== 'Tab') return\n const focusable = Array.from(container!.querySelectorAll<HTMLElement>(FOCUSABLE))\n if (focusable.length === 0) return\n const first = focusable[0]!\n const last = focusable[focusable.length - 1]!\n if (e.shiftKey) {\n if (document.activeElement === first) {\n e.preventDefault()\n last.focus()\n }\n } else {\n if (document.activeElement === last) {\n e.preventDefault()\n first.focus()\n }\n }\n }\n document.addEventListener('keydown', handleKeyDown)\n return () => {\n document.removeEventListener('keydown', handleKeyDown)\n if (restoreFocus && previousFocusRef.current instanceof HTMLElement) {\n previousFocusRef.current.focus()\n }\n }\n })\n\n return <div ref={containerRef}>{children}</div>\n}\n","'use client'\nimport {\n Children,\n cloneElement,\n forwardRef,\n isValidElement,\n type CSSProperties,\n type ReactElement,\n type ReactNode,\n type Ref,\n} from 'react'\nimport { cn, composeRefs, mergeProps } from './utils.ts'\n\nexport interface SlotProps {\n children?: ReactNode\n className?: string\n style?: CSSProperties\n [key: string]: unknown\n}\n\ntype ElementWithRef = ReactElement<Record<string, unknown>> & { ref?: Ref<unknown> }\n\n/**\n * Slot renders its single child element while merging the Slot's own props and ref onto it —\n * the Radix-style `asChild` composition primitive, built on cascivo's existing\n * `mergeProps`/`composeRefs`/`cn`.\n *\n * - Non-handler props: child wins (so the consumer's element controls its own identity).\n * - `on*` handlers: chained (Slot's runs, then the child's) via `mergeProps`.\n * - `className`: unioned via `cn`. `style`: shallow-merged (child wins on conflicts).\n * - `ref`: composed so both the Slot consumer's ref and the child's own ref receive the node.\n *\n * Fails fast if `children` is not exactly one valid React element.\n */\nexport const Slot = forwardRef<unknown, SlotProps>(function Slot(props, forwardedRef) {\n const { children, className: slotClassName, style: slotStyle, ...slotProps } = props\n\n if (!isValidElement(children) || Children.count(children) !== 1) {\n throw new Error(\n 'Slot expects exactly one valid React element child (asChild requires a single element).',\n )\n }\n\n const child = children as ElementWithRef\n const childProps = child.props\n const merged = mergeProps<Record<string, unknown>>(slotProps, childProps)\n\n merged['className'] = cn(\n slotClassName as string | undefined,\n childProps['className'] as string | undefined,\n )\n merged['style'] = {\n ...(slotStyle as CSSProperties | undefined),\n ...(childProps['style'] as CSSProperties | undefined),\n }\n // React 19 exposes ref as a regular prop; React 18 keeps it on the element. Prefer props.ref to\n // avoid touching the deprecated `element.ref` getter when it isn't needed.\n const childRef = (childProps['ref'] as Ref<unknown> | undefined) ?? child.ref\n merged['ref'] = composeRefs(forwardedRef, childRef)\n\n return cloneElement(child, merged)\n})\n","'use client'\nimport { useRef } from 'react'\nimport { useSignal, type Signal } from './signals.ts'\n\nexport interface UseControllableSignalOptions<T> {\n /** Controlled value. When provided, the component is controlled for its whole life. */\n value?: T | undefined\n /** Initial value for uncontrolled use. */\n defaultValue?: T | undefined\n /** Called on every write, in both controlled and uncontrolled modes. */\n onChange?: ((value: T) => void) | undefined\n}\n\n/**\n * Codifies the controlled/uncontrolled pattern documented by hand in CLAUDE.md\n * (`const x = useSignal(open); x.value = open`).\n *\n * Returns a `[signal, setValue]` pair:\n * - **Controlled** (`value` provided): the signal mirrors `value` on every render; `setValue`\n * does not mutate the signal locally — it only routes the request through `onChange` so the\n * parent owns the state (React semantics).\n * - **Uncontrolled** (`value` undefined): the signal owns the state seeded from `defaultValue`;\n * `setValue` updates it locally and also calls `onChange`.\n *\n * Whether a component is controlled is fixed for its life, exactly like React.\n */\nexport function useControllableSignal<T>(\n options: UseControllableSignalOptions<T>,\n): [Signal<T>, (next: T) => void] {\n const { value, defaultValue, onChange } = options\n const isControlled = value !== undefined\n\n const sig = useSignal<T>((isControlled ? value : defaultValue) as T)\n\n // Keep onChange current without an effect (the onCloseRef idiom from CLAUDE.md).\n const onChangeRef = useRef(onChange)\n onChangeRef.current = onChange\n\n // Controlled: mirror the prop into the signal each render (no-op if unchanged).\n if (isControlled) sig.value = value as T\n\n const setValue = (next: T): void => {\n if (!isControlled) sig.value = next\n onChangeRef.current?.(next)\n }\n\n return [sig, setValue]\n}\n","'use client'\nimport { useSignal, useSignalEffect, type ReadonlySignal } from './signals.ts'\n\nfunction initialMatch(query: string): boolean {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return false\n return window.matchMedia(query).matches\n}\n\n/**\n * `useMediaQuery(query)` → a `ReadonlySignal<boolean>` that tracks whether the media query matches.\n * SSR-safe (defaults to `false` when `window`/`matchMedia` is unavailable). Subscribes to changes\n * inside `useSignalEffect`; the listener is cleaned up on teardown.\n */\nexport function useMediaQuery(query: string): ReadonlySignal<boolean> {\n const matches = useSignal(initialMatch(query))\n\n useSignalEffect(() => {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return\n const mql = window.matchMedia(query)\n matches.value = mql.matches\n const handler = (event: MediaQueryListEvent): void => {\n matches.value = event.matches\n }\n mql.addEventListener('change', handler)\n return () => mql.removeEventListener('change', handler)\n })\n\n return matches\n}\n","'use client'\nimport { useSignalEffect, type Signal } from './signals.ts'\n\n/**\n * Locks `document.body` scroll while `locked` is true, compensating for the scrollbar width to\n * avoid a layout shift. Accepts a plain boolean or a `Signal<boolean>` (reactive). The previous\n * inline styles are restored on unlock/teardown. DOM work runs inside `useSignalEffect`.\n */\nexport function useScrollLock(locked: Signal<boolean> | boolean): void {\n useSignalEffect(() => {\n const isLocked = typeof locked === 'boolean' ? locked : locked.value\n if (!isLocked || typeof document === 'undefined') return\n\n const body = document.body\n const prevOverflow = body.style.overflow\n const prevPaddingInlineEnd = body.style.paddingInlineEnd\n\n const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth\n body.style.overflow = 'hidden'\n if (scrollbarWidth > 0) {\n const current = Number.parseInt(window.getComputedStyle(body).paddingInlineEnd, 10) || 0\n body.style.paddingInlineEnd = `${current + scrollbarWidth}px`\n }\n\n return () => {\n body.style.overflow = prevOverflow\n body.style.paddingInlineEnd = prevPaddingInlineEnd\n }\n })\n}\n","'use client'\nimport { useId as useReactId } from 'react'\n\n/**\n * SSR-safe stable id for aria wiring (Label/Field). Wraps React's `useId` and strips the\n * non-selector-safe colons React emits, prefixing for readability (default `cascivo`).\n */\nexport function useId(prefix = 'cascivo'): string {\n const id = useReactId()\n return `${prefix}-${id.replace(/:/g, '')}`\n}\n","'use client'\nimport { useRef } from 'react'\nimport { useSignal, useSignalEffect, type ReadonlySignal } from './signals.ts'\n\nexport interface UseClipboardOptions {\n /** How long `copied` stays true after a successful copy, in ms. */\n timeout?: number\n}\n\nexport interface UseClipboardReturn {\n copied: ReadonlySignal<boolean>\n copy: (text: string) => Promise<void>\n}\n\n/**\n * Copy-to-clipboard helper that de-dupes the inline logic in `copy-button`.\n * `copy(text)` writes via `navigator.clipboard.writeText`; `copied` flips true then resets after\n * `timeout`. The reset timer is cleared on unmount via `useSignalEffect` (no `useEffect`).\n */\nexport function useClipboard(options: UseClipboardOptions = {}): UseClipboardReturn {\n const { timeout = 2000 } = options\n const copied = useSignal(false)\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n\n useSignalEffect(() => {\n return () => {\n if (timerRef.current) clearTimeout(timerRef.current)\n }\n })\n\n const copy = async (text: string): Promise<void> => {\n if (typeof navigator === 'undefined' || !navigator.clipboard) return\n await navigator.clipboard.writeText(text)\n copied.value = true\n if (timerRef.current) clearTimeout(timerRef.current)\n timerRef.current = setTimeout(() => {\n copied.value = false\n }, timeout)\n }\n\n return { copied, copy }\n}\n","'use client'\nimport { useControllableSignal } from './controllable.ts'\nimport type { ReadonlySignal } from './signals.ts'\n\nexport interface UseDisclosureOptions {\n /** Initial open state for uncontrolled use. */\n defaultOpen?: boolean\n /** Controlled open state. When provided, the parent owns the state. */\n isOpen?: boolean\n /** Called whenever open/close/toggle is requested. */\n onOpenChange?: (open: boolean) => void\n}\n\nexport interface UseDisclosureReturn {\n isOpen: ReadonlySignal<boolean>\n open: () => void\n close: () => void\n toggle: () => void\n}\n\n/**\n * Open/close state for overlays (modal, drawer, popover, …), replacing the\n * per-component `useSignal(open)` boilerplate. Backed by `useControllableSignal`\n * so it is controlled when `isOpen` is passed and uncontrolled otherwise.\n * Replaces HeroUI's `@heroui/use-disclosure`. No `useState`/`useEffect`.\n */\nexport function useDisclosure(options: UseDisclosureOptions = {}): UseDisclosureReturn {\n const { defaultOpen = false, isOpen, onOpenChange } = options\n const [open, setOpen] = useControllableSignal<boolean>({\n value: isOpen,\n defaultValue: defaultOpen,\n onChange: onOpenChange,\n })\n\n return {\n isOpen: open,\n open: () => setOpen(true),\n close: () => setOpen(false),\n toggle: () => setOpen(!open.value),\n }\n}\n","'use client'\nimport { useRef, type RefObject } from 'react'\nimport { useSignal, useSignalEffect } from './signals.ts'\n\nexport interface UseInfiniteScrollOptions {\n /** Whether more items remain to load. */\n hasMore: boolean\n /** Called when the sentinel scrolls into view and `hasMore` is true. */\n onLoadMore: () => void\n /** Disable the observer without unmounting. Default true. */\n isEnabled?: boolean\n /** IntersectionObserver rootMargin (e.g. '200px' to prefetch early). */\n rootMargin?: string\n}\n\nexport interface UseInfiniteScrollReturn {\n /** Place on a trailing element at the end of the list. */\n sentinelRef: RefObject<HTMLElement | null>\n /** Optional scroll container; defaults to the viewport. */\n rootRef: RefObject<HTMLElement | null>\n}\n\n/**\n * Calls `onLoadMore` when a sentinel element scrolls into view, gated by\n * `hasMore`. Wires an `IntersectionObserver` inside `useSignalEffect` and\n * disconnects it on teardown. SSR/no-DOM safe. Replaces HeroUI's\n * `@heroui/use-infinite-scroll`. No `useState`/`useEffect`.\n */\nexport function useInfiniteScroll(options: UseInfiniteScrollOptions): UseInfiniteScrollReturn {\n const { rootMargin = '0px' } = options\n const sentinelRef = useRef<HTMLElement | null>(null)\n const rootRef = useRef<HTMLElement | null>(null)\n\n // Mirror props into signals so the effect re-attaches when `isEnabled` flips;\n // `hasMore`/`onLoadMore` stay current via signal/ref reads at intersection time.\n const enabled = useSignal(options.isEnabled ?? true)\n enabled.value = options.isEnabled ?? true\n const hasMore = useSignal(options.hasMore)\n hasMore.value = options.hasMore\n const onLoadMoreRef = useRef(options.onLoadMore)\n onLoadMoreRef.current = options.onLoadMore\n\n useSignalEffect(() => {\n const isEnabled = enabled.value\n const sentinel = sentinelRef.current\n if (!isEnabled || typeof IntersectionObserver === 'undefined' || !sentinel) return\n\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries[0]?.isIntersecting && hasMore.value) onLoadMoreRef.current()\n },\n { root: rootRef.current ?? null, rootMargin },\n )\n observer.observe(sentinel)\n return () => observer.disconnect()\n })\n\n return { sentinelRef, rootRef }\n}\n","'use client'\nimport { useRef, type RefObject } from 'react'\nimport { useSignal, useSignalEffect, type ReadonlySignal } from './signals.ts'\n\nexport interface DragOffset {\n x: number\n y: number\n}\n\nexport interface UseDraggableOptions {\n isDisabled?: boolean\n onDragEnd?: (offset: DragOffset) => void\n /** Constrain dragging to an axis. Default 'both'. */\n axis?: 'x' | 'y' | 'both'\n}\n\nexport interface UseDraggableReturn {\n /** Attach to the drag handle (e.g. a modal header). */\n handleRef: RefObject<HTMLElement | null>\n /** Attach to the moved element; apply `offset` as a CSS translate. */\n targetRef: RefObject<HTMLElement | null>\n offset: ReadonlySignal<DragOffset>\n isDragging: ReadonlySignal<boolean>\n reset: () => void\n}\n\n/**\n * Pointer-driven drag offset for overlays. Returns an `{ x, y }` offset signal\n * the caller applies as a CSS `translate` (no animation library). Attaches\n * pointer listeners in `useSignalEffect` and removes them on teardown.\n * SSR/no-DOM safe. Replaces HeroUI's `@heroui/use-draggable` (Framer-free).\n * No `useState`/`useEffect`.\n */\nexport function useDraggable(options: UseDraggableOptions = {}): UseDraggableReturn {\n const handleRef = useRef<HTMLElement | null>(null)\n const targetRef = useRef<HTMLElement | null>(null)\n const offset = useSignal<DragOffset>({ x: 0, y: 0 })\n const isDragging = useSignal(false)\n\n const disabled = useSignal(options.isDisabled ?? false)\n disabled.value = options.isDisabled ?? false\n const axisRef = useRef(options.axis ?? 'both')\n axisRef.current = options.axis ?? 'both'\n const onDragEndRef = useRef(options.onDragEnd)\n onDragEndRef.current = options.onDragEnd\n\n useSignalEffect(() => {\n const isDisabled = disabled.value\n if (isDisabled || typeof window === 'undefined') return\n\n let startX = 0\n let startY = 0\n let baseX = 0\n let baseY = 0\n let activeHandle: HTMLElement | null = null\n\n const onMove = (event: PointerEvent): void => {\n const axis = axisRef.current\n const dx = axis === 'y' ? 0 : event.clientX - startX\n const dy = axis === 'x' ? 0 : event.clientY - startY\n offset.value = { x: baseX + dx, y: baseY + dy }\n }\n const onUp = (event: PointerEvent): void => {\n isDragging.value = false\n activeHandle?.releasePointerCapture?.(event.pointerId)\n activeHandle = null\n window.removeEventListener('pointermove', onMove)\n window.removeEventListener('pointerup', onUp)\n window.removeEventListener('pointercancel', onUp)\n onDragEndRef.current?.(offset.value)\n }\n // Listen on the window and resolve the handle lazily, so the handle element\n // need not be attached when this effect first runs — it may live deep inside\n // a portal/overlay whose ref commits after this hook's owner.\n const onDown = (event: PointerEvent): void => {\n const handle = handleRef.current\n const target = event.target\n if (!handle || !(target instanceof Node) || !handle.contains(target)) return\n activeHandle = handle\n startX = event.clientX\n startY = event.clientY\n baseX = offset.value.x\n baseY = offset.value.y\n isDragging.value = true\n handle.setPointerCapture?.(event.pointerId)\n window.addEventListener('pointermove', onMove)\n window.addEventListener('pointerup', onUp)\n window.addEventListener('pointercancel', onUp)\n }\n\n window.addEventListener('pointerdown', onDown)\n return () => {\n window.removeEventListener('pointerdown', onDown)\n window.removeEventListener('pointermove', onMove)\n window.removeEventListener('pointerup', onUp)\n window.removeEventListener('pointercancel', onUp)\n }\n })\n\n return {\n handleRef,\n targetRef,\n offset,\n isDragging,\n reset: () => {\n offset.value = { x: 0, y: 0 }\n },\n }\n}\n","'use client'\nimport { useRef, type RefObject } from 'react'\nimport { useSignal, useSignalEffect, type ReadonlySignal } from './signals.ts'\n\nexport interface ElementSize {\n width: number\n height: number\n}\n\nexport interface UseResizeObserverOptions {\n /** Which box model to observe. Default 'content-box'. */\n box?: ResizeObserverBoxOptions\n}\n\nexport interface UseResizeObserverReturn<T extends Element> {\n /** Attach to the element whose size you want to track. */\n ref: RefObject<T | null>\n /** Latest content-box size, or null before the first observation. */\n size: ReadonlySignal<ElementSize | null>\n /** Latest raw ResizeObserverEntry, or null before the first observation. */\n entry: ReadonlySignal<ResizeObserverEntry | null>\n}\n\n/**\n * Observes an element's size with a `ResizeObserver`, exposing the latest size\n * as a signal. The declarative counterpart to Web Awesome's `wa-resize-observer`.\n * Wires the observer inside `useSignalEffect` and disconnects it on teardown.\n * SSR/no-DOM safe. No `useState`/`useEffect`.\n */\nexport function useResizeObserver<T extends Element = Element>(\n options: UseResizeObserverOptions = {},\n): UseResizeObserverReturn<T> {\n const ref = useRef<T | null>(null)\n const size = useSignal<ElementSize | null>(null)\n const entry = useSignal<ResizeObserverEntry | null>(null)\n\n const box = useSignal(options.box)\n box.value = options.box\n\n useSignalEffect(() => {\n const observeBox = box.value\n const el = ref.current\n if (!el || typeof ResizeObserver === 'undefined') return\n\n const observer = new ResizeObserver((entries) => {\n const next = entries[entries.length - 1]\n if (!next) return\n entry.value = next\n size.value = { width: next.contentRect.width, height: next.contentRect.height }\n })\n observer.observe(el, observeBox ? { box: observeBox } : undefined)\n return () => observer.disconnect()\n })\n\n return { ref, size, entry }\n}\n","'use client'\nimport { useRef, type RefObject } from 'react'\nimport { useSignal, useSignalEffect, type ReadonlySignal } from './signals.ts'\n\nexport interface UseMutationObserverReturn<T extends Node> {\n /** Attach to the node whose mutations you want to watch. */\n ref: RefObject<T | null>\n /** The records from the most recent mutation batch. */\n records: ReadonlySignal<MutationRecord[]>\n}\n\n/**\n * Watches a node's DOM tree with a `MutationObserver`, exposing the latest batch\n * of records as a signal. The declarative counterpart to Web Awesome's\n * `wa-mutation-observer`. Wires the observer inside `useSignalEffect` and\n * disconnects it on teardown. SSR/no-DOM safe. No `useState`/`useEffect`.\n *\n * `init` is read when the effect (re-)subscribes; pass a stable object or accept\n * that changing it between renders won't re-subscribe (mount-time semantics).\n */\nexport function useMutationObserver<T extends Node = Element>(\n init: MutationObserverInit = { childList: true },\n): UseMutationObserverReturn<T> {\n const ref = useRef<T | null>(null)\n const records = useSignal<MutationRecord[]>([])\n const initRef = useRef(init)\n initRef.current = init\n\n useSignalEffect(() => {\n const node = ref.current\n if (!node || typeof MutationObserver === 'undefined') return\n\n const observer = new MutationObserver((mutations) => {\n records.value = mutations\n })\n observer.observe(node, initRef.current)\n return () => observer.disconnect()\n })\n\n return { ref, records }\n}\n","'use client'\nimport { useRef, type RefObject } from 'react'\nimport { useSignal, useSignalEffect, type ReadonlySignal } from './signals.ts'\n\nexport interface UseIntersectionObserverOptions {\n root?: Element | Document | null\n rootMargin?: string\n threshold?: number | number[]\n}\n\nexport interface UseIntersectionObserverReturn<T extends Element> {\n /** Attach to the element whose visibility you want to track. */\n ref: RefObject<T | null>\n /** Latest IntersectionObserverEntry, or null before the first observation. */\n entry: ReadonlySignal<IntersectionObserverEntry | null>\n /** Whether the element is currently intersecting the root. */\n isIntersecting: ReadonlySignal<boolean>\n}\n\n/**\n * Tracks an element's visibility with an `IntersectionObserver`, exposing the\n * latest entry and an `isIntersecting` flag as signals. The declarative\n * counterpart to Web Awesome's `wa-intersection-observer`.\n *\n * This is the **generic** observer — for \"load more when a sentinel appears\"\n * use `useInfiniteScroll`, which adds `hasMore`/`onLoadMore` semantics on top.\n *\n * Wires the observer inside `useSignalEffect` and disconnects it on teardown.\n * SSR/no-DOM safe. No `useState`/`useEffect`.\n */\nexport function useIntersectionObserver<T extends Element = Element>(\n options: UseIntersectionObserverOptions = {},\n): UseIntersectionObserverReturn<T> {\n const { rootMargin = '0px', threshold = 0 } = options\n const ref = useRef<T | null>(null)\n const entry = useSignal<IntersectionObserverEntry | null>(null)\n const isIntersecting = useSignal(false)\n const rootRef = useRef(options.root ?? null)\n rootRef.current = options.root ?? null\n\n useSignalEffect(() => {\n const el = ref.current\n if (!el || typeof IntersectionObserver === 'undefined') return\n\n const observer = new IntersectionObserver(\n (entries) => {\n const next = entries[entries.length - 1]\n if (!next) return\n entry.value = next\n isIntersecting.value = next.isIntersecting\n },\n { root: rootRef.current, rootMargin, threshold },\n )\n observer.observe(el)\n return () => observer.disconnect()\n })\n\n return { ref, entry, isIntersecting }\n}\n","'use client'\nimport { useRef, type ReactNode } from 'react'\nimport { useSignalEffect, useSignals } from './signals.ts'\n\n// Module-level registry so nested layers (a dropdown inside a modal) dismiss top-first. Effects\n// mount child-first, so a stack would invert nesting; instead we resolve the top-most layer by DOM\n// depth — a layer responds only when no other mounted layer's root is nested inside its own root.\ninterface LayerEntry {\n getRoot: () => HTMLElement | null\n}\nconst layers: LayerEntry[] = []\n\nexport interface DismissableLayerProps {\n children: ReactNode\n /** Called when an outside pointer lands or Escape is pressed (when this is the top-most layer). */\n onDismiss?: () => void\n /** Disable the outside-pointer dismissal (Escape still works unless escapeDismisses is false). */\n disableOutsidePointer?: boolean\n /** Whether Escape dismisses the top-most layer. Default true. */\n escapeDismisses?: boolean\n}\n\n/**\n * Wraps its children and dismisses on an outside pointer press or Escape. One tested implementation\n * of the dismissal logic every overlay reinvents. Nested layers form a stack — only the top-most\n * responds to Escape and outside pointers. All listeners run inside `useSignalEffect`.\n */\nexport function DismissableLayer({\n children,\n onDismiss,\n disableOutsidePointer,\n escapeDismisses = true,\n}: DismissableLayerProps) {\n useSignals()\n const rootRef = useRef<HTMLDivElement>(null)\n const onDismissRef = useRef(onDismiss)\n onDismissRef.current = onDismiss\n\n useSignalEffect(() => {\n if (typeof document === 'undefined') return\n const entry: LayerEntry = { getRoot: () => rootRef.current }\n layers.push(entry)\n\n // Top-most = deepest in the DOM: no other mounted layer's root sits inside this one's root.\n const isTop = (): boolean => {\n const root = rootRef.current\n if (!root) return false\n for (const other of layers) {\n if (other === entry) continue\n const otherRoot = other.getRoot()\n if (otherRoot && root.contains(otherRoot)) return false\n }\n return true\n }\n\n const handlePointer = (event: PointerEvent): void => {\n if (disableOutsidePointer || !isTop()) return\n const root = rootRef.current\n if (!root) return\n if (event.target instanceof Node && !root.contains(event.target)) {\n onDismissRef.current?.()\n }\n }\n const handleKey = (event: KeyboardEvent): void => {\n if (!escapeDismisses || event.key !== 'Escape' || !isTop()) return\n onDismissRef.current?.()\n }\n\n document.addEventListener('pointerdown', handlePointer, true)\n document.addEventListener('keydown', handleKey)\n return () => {\n document.removeEventListener('pointerdown', handlePointer, true)\n document.removeEventListener('keydown', handleKey)\n const idx = layers.indexOf(entry)\n if (idx !== -1) layers.splice(idx, 1)\n }\n })\n\n return <div ref={rootRef}>{children}</div>\n}\n","'use client'\nimport { useRef } from 'react'\nimport { useSignal, type ReadonlySignal } from './signals.ts'\n\nexport type RovingOrientation = 'horizontal' | 'vertical' | 'both'\n\nexport interface UseRovingFocusOptions {\n orientation?: RovingOrientation\n /** Wrap focus from last→first and first→last. Default false. */\n loop?: boolean\n /** Initial active index. Default 0. */\n defaultIndex?: number\n}\n\nexport interface RovingItemProps {\n tabIndex: number\n onKeyDown: (event: React.KeyboardEvent<HTMLElement>) => void\n onFocus: () => void\n ref: (el: HTMLElement | null) => void\n}\n\nexport interface UseRovingFocusReturn {\n activeIndex: ReadonlySignal<number>\n getItemProps: (index: number) => RovingItemProps\n setActiveIndex: (index: number) => void\n}\n\n/**\n * Roving tabindex without context. The parent calls `useRovingFocus(...)` and spreads\n * `getItemProps(i)` onto each item. Only the active item is tabbable (`tabIndex={0}`); arrow keys\n * move focus (Home/End jump; `loop` wraps). State lives in a signal — the consuming component reads\n * it during render, so React apps must call `useSignals()` (per the project rule).\n */\nexport function useRovingFocus(options: UseRovingFocusOptions = {}): UseRovingFocusReturn {\n const { orientation = 'horizontal', loop = false, defaultIndex = 0 } = options\n const activeIndex = useSignal(defaultIndex)\n const itemsRef = useRef<(HTMLElement | null)[]>([])\n\n const focusIndex = (index: number): void => {\n activeIndex.value = index\n itemsRef.current[index]?.focus()\n }\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLElement>, index: number): void => {\n const count = itemsRef.current.length\n if (count === 0) return\n const horizontal = orientation !== 'vertical'\n const vertical = orientation !== 'horizontal'\n let next = index\n if ((horizontal && event.key === 'ArrowRight') || (vertical && event.key === 'ArrowDown')) {\n next = index + 1\n } else if ((horizontal && event.key === 'ArrowLeft') || (vertical && event.key === 'ArrowUp')) {\n next = index - 1\n } else if (event.key === 'Home') {\n next = 0\n } else if (event.key === 'End') {\n next = count - 1\n } else {\n return\n }\n event.preventDefault()\n if (next < 0) next = loop ? count - 1 : 0\n if (next >= count) next = loop ? 0 : count - 1\n focusIndex(next)\n }\n\n const getItemProps = (index: number): RovingItemProps => ({\n tabIndex: activeIndex.value === index ? 0 : -1,\n onKeyDown: (event) => handleKeyDown(event, index),\n onFocus: () => {\n activeIndex.value = index\n },\n ref: (el) => {\n itemsRef.current[index] = el\n },\n })\n\n return {\n activeIndex,\n getItemProps,\n setActiveIndex: (index: number) => {\n activeIndex.value = index\n },\n }\n}\n","'use client'\nimport { cloneElement, isValidElement, useRef, type ReactElement, type Ref } from 'react'\nimport { useSignal, useSignalEffect, useSignals, type Signal } from './signals.ts'\nimport { composeRefs } from './utils.ts'\n\nexport interface PresenceProps {\n /** Whether the content should be present. Accepts a boolean or a Signal. */\n present: boolean | Signal<boolean>\n /** A single element child; receives `data-state=\"open|closed\"` and a composed ref. */\n children: ReactElement\n}\n\ntype ElementWithRef = ReactElement<Record<string, unknown>> & { ref?: Ref<unknown> }\n\n/**\n * Defers unmount until an exit animation/transition finishes. While `present` is true the child is\n * mounted with `data-state=\"open\"`; when it flips to false the child gets `data-state=\"closed\"` and\n * stays mounted until its `animationend`/`transitionend` fires (or unmounts immediately if there is\n * no animation). CSS drives the visuals via `data-state`; this only manages mount timing. No\n * `useEffect` — the present prop is mirrored into a signal and watched with `useSignalEffect`.\n */\nexport function Presence({ present, children }: PresenceProps) {\n useSignals()\n const presentVal = typeof present === 'boolean' ? present : present.value\n\n // Mirror the (boolean or signal) present value into a local signal so the effect reacts to both\n // prop changes (re-render) and signal changes.\n const presentSig = useSignal(presentVal)\n presentSig.value = presentVal\n\n const isMounted = useSignal(presentVal)\n const state = useSignal<'open' | 'closed'>(presentVal ? 'open' : 'closed')\n const nodeRef = useRef<HTMLElement | null>(null)\n\n useSignalEffect(() => {\n if (presentSig.value) {\n isMounted.value = true\n state.value = 'open'\n return\n }\n state.value = 'closed'\n const node = nodeRef.current\n if (!node || typeof getComputedStyle === 'undefined') {\n isMounted.value = false\n return\n }\n const styles = getComputedStyle(node)\n const animates =\n (styles.animationName !== 'none' &&\n styles.animationName !== '' &&\n styles.animationDuration !== '0s') ||\n (styles.transitionDuration !== '0s' && styles.transitionDuration !== '')\n if (!animates) {\n isMounted.value = false\n return\n }\n const handleEnd = (event: Event): void => {\n if (event.target === node) isMounted.value = false\n }\n node.addEventListener('animationend', handleEnd)\n node.addEventListener('transitionend', handleEnd)\n return () => {\n node.removeEventListener('animationend', handleEnd)\n node.removeEventListener('transitionend', handleEnd)\n }\n })\n\n if (!isMounted.value) return null\n if (!isValidElement(children)) {\n throw new Error('Presence expects a single valid React element child')\n }\n const child = children as ElementWithRef\n const childRef = (child.props['ref'] as Ref<unknown> | undefined) ?? child.ref\n return cloneElement(child, {\n 'data-state': state.value,\n ref: composeRefs(childRef, (el: HTMLElement | null) => {\n nodeRef.current = el\n }),\n } as Record<string, unknown>)\n}\n","'use client'\nimport { type CSSProperties, type RefObject } from 'react'\nimport { useSignal, useSignalEffect, useSignals, type Signal } from './signals.ts'\nimport { useId } from './use-id.ts'\n\nexport type AnchorSide = 'top' | 'bottom' | 'left' | 'right'\nexport type AnchorAlign = 'start' | 'center' | 'end'\nexport type AnchorPlacement = AnchorSide | `${AnchorSide}-${'start' | 'end'}`\n\nexport interface UseAnchorPositionOptions {\n anchorRef: RefObject<HTMLElement | null>\n floatingRef: RefObject<HTMLElement | null>\n placement?: AnchorPlacement\n /** Gate positioning (e.g. only while open). Default true. */\n enabled?: boolean | Signal<boolean>\n}\n\nexport interface UseAnchorPositionReturn {\n /** Spread onto the anchor element (CSS-anchor path sets `anchor-name`). */\n anchorStyle: CSSProperties\n /** Spread onto the floating element. */\n floatingStyle: CSSProperties\n /** Whether the CSS anchor-positioning primary path is active. */\n cssAnchorSupported: boolean\n}\n\nfunction supportsCssAnchor(): boolean {\n return (\n typeof CSS !== 'undefined' &&\n typeof CSS.supports === 'function' &&\n CSS.supports('anchor-name: --x')\n )\n}\n\nfunction parsePlacement(placement: AnchorPlacement): { side: AnchorSide; align: AnchorAlign } {\n const [side, end] = placement.split('-') as [AnchorSide, 'start' | 'end' | undefined]\n return { side, align: end ?? 'center' }\n}\n\n/** JS fallback math — position `floating` relative to `anchor` for the given placement. */\nexport function computePosition(\n anchor: {\n top: number\n left: number\n right: number\n bottom: number\n width: number\n height: number\n },\n floating: { width: number; height: number },\n placement: AnchorPlacement,\n): { top: number; left: number } {\n const { side, align } = parsePlacement(placement)\n let top = 0\n let left = 0\n if (side === 'top' || side === 'bottom') {\n top = side === 'bottom' ? anchor.bottom : anchor.top - floating.height\n left =\n align === 'start'\n ? anchor.left\n : align === 'end'\n ? anchor.right - floating.width\n : anchor.left + (anchor.width - floating.width) / 2\n } else {\n left = side === 'right' ? anchor.right : anchor.left - floating.width\n top =\n align === 'start'\n ? anchor.top\n : align === 'end'\n ? anchor.bottom - floating.height\n : anchor.top + (anchor.height - floating.height) / 2\n }\n return { top, left }\n}\n\nfunction cssAnchorInsets(side: AnchorSide, align: AnchorAlign): CSSProperties {\n const style: CSSProperties = {\n position: 'fixed',\n insetBlockStart: 'auto',\n insetInlineStart: 'auto',\n }\n if (side === 'bottom') style.top = 'anchor(bottom)'\n if (side === 'top') style.bottom = 'anchor(top)'\n if (side === 'right') style.left = 'anchor(right)'\n if (side === 'left') style.right = 'anchor(left)'\n if (side === 'top' || side === 'bottom') {\n style.left = align === 'end' ? undefined : align === 'start' ? 'anchor(left)' : 'anchor(center)'\n if (align === 'end') style.right = 'anchor(right)'\n } else {\n style.top = align === 'end' ? undefined : align === 'start' ? 'anchor(top)' : 'anchor(center)'\n if (align === 'end') style.bottom = 'anchor(bottom)'\n }\n return style\n}\n\n/**\n * Positions a floating element relative to an anchor. Primary path: CSS anchor positioning\n * (Chrome-leading) — emits `anchor-name`/`position-anchor` + `anchor()` insets. Fallback (behind\n * `CSS.supports`): JS `getBoundingClientRect` placement recomputed on scroll/resize inside\n * `useSignalEffect`. Small by design — not a collision/flip engine (deferred).\n */\nexport function useAnchorPosition(options: UseAnchorPositionOptions): UseAnchorPositionReturn {\n const { anchorRef, floatingRef, placement = 'bottom', enabled = true } = options\n useSignals()\n const supported = supportsCssAnchor()\n const name = useId('anchor')\n const pos = useSignal<{ top: number; left: number } | null>(null)\n\n useSignalEffect(() => {\n const isEnabled = typeof enabled === 'boolean' ? enabled : enabled.value\n if (supported || !isEnabled || typeof window === 'undefined') return\n\n const update = (): void => {\n const a = anchorRef.current\n const f = floatingRef.current\n if (!a || !f) return\n pos.value = computePosition(a.getBoundingClientRect(), f.getBoundingClientRect(), placement)\n }\n update()\n window.addEventListener('scroll', update, true)\n window.addEventListener('resize', update)\n return () => {\n window.removeEventListener('scroll', update, true)\n window.removeEventListener('resize', update)\n }\n })\n\n if (supported) {\n const { side, align } = parsePlacement(placement)\n return {\n anchorStyle: { anchorName: `--${name}` } as CSSProperties,\n floatingStyle: {\n positionAnchor: `--${name}`,\n ...cssAnchorInsets(side, align),\n } as CSSProperties,\n cssAnchorSupported: true,\n }\n }\n\n const p = pos.value\n return {\n anchorStyle: {},\n floatingStyle: p\n ? { position: 'fixed', top: p.top, left: p.left }\n : { position: 'fixed', visibility: 'hidden' },\n cssAnchorSupported: false,\n }\n}\n","export { createMachine, useMachine } from './machine.ts'\nexport {\n signal,\n computed,\n effect,\n batch,\n useSignal,\n useComputed,\n useSignalEffect,\n useSignals,\n} from './signals.ts'\nexport type { Signal, ReadonlySignal } from './signals.ts'\nexport { cn, composeRefs, mergeProps } from './utils.ts'\nexport type {\n Machine,\n MachineConfig,\n StateConfig,\n ComponentMeta,\n PropMeta,\n AccessibilityMeta,\n WcagLevel,\n ExampleMeta,\n ComponentIntent,\n IntentAntiPattern,\n IntentRelated,\n IntentFlexibility,\n IntentContent,\n IntentRelationship,\n} from './types.ts'\nexport const VERSION = '0.0.0'\nexport { ErrorBoundary } from './error-boundary.tsx'\nexport type { ErrorBoundaryProps } from './error-boundary.tsx'\nexport { SuspenseBoundary } from './suspense-boundary.tsx'\nexport type { SuspenseBoundaryProps } from './suspense-boundary.tsx'\nexport { Portal } from './portal.tsx'\nexport type { PortalProps } from './portal.tsx'\nexport { VisuallyHidden } from './visually-hidden.tsx'\nexport type { VisuallyHiddenProps } from './visually-hidden.tsx'\nexport { FocusScope } from './focus-scope.tsx'\nexport type { FocusScopeProps } from './focus-scope.tsx'\nexport { Slot } from './slot.tsx'\nexport type { SlotProps } from './slot.tsx'\nexport { useControllableSignal } from './controllable.ts'\nexport type { UseControllableSignalOptions } from './controllable.ts'\nexport { useMediaQuery } from './media-query.ts'\nexport { useScrollLock } from './scroll-lock.ts'\nexport { useId } from './use-id.ts'\nexport { useClipboard } from './clipboard.ts'\nexport type { UseClipboardOptions, UseClipboardReturn } from './clipboard.ts'\nexport { useDisclosure } from './disclosure.ts'\nexport type { UseDisclosureOptions, UseDisclosureReturn } from './disclosure.ts'\nexport { useInfiniteScroll } from './infinite-scroll.ts'\nexport type { UseInfiniteScrollOptions, UseInfiniteScrollReturn } from './infinite-scroll.ts'\nexport { useDraggable } from './draggable.ts'\nexport type { DragOffset, UseDraggableOptions, UseDraggableReturn } from './draggable.ts'\nexport { useResizeObserver } from './resize-observer.ts'\nexport type {\n ElementSize,\n UseResizeObserverOptions,\n UseResizeObserverReturn,\n} from './resize-observer.ts'\nexport { useMutationObserver } from './mutation-observer.ts'\nexport type { UseMutationObserverReturn } from './mutation-observer.ts'\nexport { useIntersectionObserver } from './intersection-observer.ts'\nexport type {\n UseIntersectionObserverOptions,\n UseIntersectionObserverReturn,\n} from './intersection-observer.ts'\nexport { DismissableLayer } from './dismissable-layer.tsx'\nexport type { DismissableLayerProps } from './dismissable-layer.tsx'\nexport { useRovingFocus } from './roving-focus.ts'\nexport type {\n UseRovingFocusOptions,\n UseRovingFocusReturn,\n RovingItemProps,\n RovingOrientation,\n} from './roving-focus.ts'\nexport { Presence } from './presence.tsx'\nexport type { PresenceProps } from './presence.tsx'\nexport { useAnchorPosition, computePosition } from './anchor.tsx'\nexport type {\n UseAnchorPositionOptions,\n UseAnchorPositionReturn,\n AnchorPlacement,\n AnchorSide,\n AnchorAlign,\n} from './anchor.tsx'\n"],"mappings":"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"}
package/dist/style.css CHANGED
@@ -1,5 +1,5 @@
1
- @layer cascade.component {
2
- .rYIFkW_root {
1
+ @layer cascivo.component {
2
+ ._9NCpAq_root {
3
3
  clip: rect(0, 0, 0, 0);
4
4
  white-space: nowrap;
5
5
  border: 0;
@@ -11,7 +11,7 @@
11
11
  overflow: hidden;
12
12
  }
13
13
 
14
- .rYIFkW_focusable:focus, .rYIFkW_focusable:focus-within {
14
+ ._9NCpAq_focusable:focus, ._9NCpAq_focusable:focus-within {
15
15
  width: auto;
16
16
  height: auto;
17
17
  padding: var(--cascivo-space-2) var(--cascivo-space-3);
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@cascivo/core",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "private": false,
5
- "description": "Micro-FSM and Preact Signals primitives powering every cascade component",
5
+ "description": "Micro-FSM and Preact Signals primitives powering every cascivo component",
6
6
  "keywords": [
7
7
  "cascivo",
8
8
  "css",
@@ -48,7 +48,7 @@
48
48
  "react": "^19.2.7",
49
49
  "react-dom": "^19.2.7",
50
50
  "typescript": "^5",
51
- "vite-plus": "^0.1.24"
51
+ "vite-plus": "^0.2.1"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "@preact/signals-react": ">=2.0.0",
package/readme.body.md CHANGED
@@ -1 +1,37 @@
1
1
  Core runtime for cascade — micro-FSM engine, Preact Signals integration (`useSignal`, `useComputed`, `useSignalEffect`, `useSignals`), and base utilities shared across all components.
2
+
3
+ ## Interaction hooks
4
+
5
+ Signal-driven, SSR-safe hooks (no `useState`/`useEffect`). These cover the user-facing `@heroui/use-*` hooks cascivo previously lacked.
6
+
7
+ ### `useDisclosure`
8
+
9
+ Open/close state for overlays, controllable via an optional `isOpen`.
10
+
11
+ ```tsx
12
+ const { isOpen, open, close, toggle } = useDisclosure({ defaultOpen: false })
13
+ // isOpen is a ReadonlySignal<boolean>; pass isOpen + onOpenChange for controlled use.
14
+ ```
15
+
16
+ ### `useInfiniteScroll`
17
+
18
+ Calls `onLoadMore` when a sentinel element scrolls into view (`IntersectionObserver`), gated by `hasMore`.
19
+
20
+ ```tsx
21
+ const { sentinelRef } = useInfiniteScroll({ hasMore, onLoadMore: loadNextPage })
22
+ return (
23
+ <ul>
24
+ {rows}
25
+ <li ref={sentinelRef} />
26
+ </ul>
27
+ )
28
+ ```
29
+
30
+ ### `useDraggable`
31
+
32
+ Pointer-driven drag offset applied as a CSS `translate` — no animation library (replaces HeroUI's Framer-based drag).
33
+
34
+ ```tsx
35
+ const { handleRef, targetRef, offset } = useDraggable({ axis: 'both' })
36
+ // apply offset.value as translate(offset.x, offset.y) on the target element.
37
+ ```