@almadar/ui 5.62.0 → 5.63.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.
Files changed (35) hide show
  1. package/dist/avl/index.cjs +490 -147
  2. package/dist/avl/index.js +490 -147
  3. package/dist/components/core/atoms/Box.d.ts +2 -0
  4. package/dist/components/core/molecules/CalendarGrid.d.ts +9 -1
  5. package/dist/components/core/molecules/PositionedCanvas.d.ts +14 -1
  6. package/dist/components/core/molecules/ReplyTree.d.ts +13 -1
  7. package/dist/components/game/molecules/three/index.cjs +64 -0
  8. package/dist/components/game/molecules/three/index.js +64 -0
  9. package/dist/components/game/organisms/hooks/useCamera.d.ts +16 -0
  10. package/dist/components/index.cjs +490 -148
  11. package/dist/components/index.js +490 -148
  12. package/dist/components/marketing/organisms/CaseStudyOrganism.d.ts +10 -1
  13. package/dist/components/marketing/organisms/FeatureGridOrganism.d.ts +10 -1
  14. package/dist/components/marketing/organisms/HeroOrganism.d.ts +24 -1
  15. package/dist/components/marketing/organisms/PricingOrganism.d.ts +14 -1
  16. package/dist/components/marketing/organisms/ShowcaseOrganism.d.ts +14 -1
  17. package/dist/components/marketing/organisms/StatsOrganism.d.ts +6 -1
  18. package/dist/components/marketing/organisms/StepFlowOrganism.d.ts +9 -1
  19. package/dist/components/marketing/organisms/TeamOrganism.d.ts +10 -1
  20. package/dist/docs/index.cjs +64 -0
  21. package/dist/docs/index.d.cts +2 -0
  22. package/dist/docs/index.js +64 -0
  23. package/dist/hooks/index.cjs +148 -0
  24. package/dist/hooks/index.d.ts +2 -0
  25. package/dist/hooks/index.js +147 -1
  26. package/dist/hooks/useCanvasGestures.d.ts +44 -0
  27. package/dist/hooks/useTapReveal.d.ts +32 -0
  28. package/dist/marketing/index.cjs +59 -0
  29. package/dist/marketing/index.d.cts +2 -0
  30. package/dist/marketing/index.js +59 -0
  31. package/dist/providers/index.cjs +490 -147
  32. package/dist/providers/index.js +490 -147
  33. package/dist/runtime/index.cjs +490 -147
  34. package/dist/runtime/index.js +490 -147
  35. package/package.json +2 -2
@@ -12,8 +12,17 @@
12
12
  import React from 'react';
13
13
  import type { EntityWith } from '@almadar/core';
14
14
  import type { DisplayStateProps } from '../../core/organisms/types';
15
+ /** The per-case-study entity fields this organism reads (FieldValue-compatible). */
16
+ export interface CaseStudyRow {
17
+ title: string;
18
+ description?: string;
19
+ category?: string;
20
+ categoryColor?: string;
21
+ href?: string;
22
+ linkLabel?: string;
23
+ }
15
24
  export interface CaseStudyOrganismProps extends DisplayStateProps {
16
- entity?: EntityWith<'title'> | readonly EntityWith<'title'>[];
25
+ entity?: EntityWith<CaseStudyRow> | readonly EntityWith<CaseStudyRow>[];
17
26
  heading?: string;
18
27
  subtitle?: string;
19
28
  }
@@ -12,8 +12,17 @@
12
12
  import React from 'react';
13
13
  import type { EntityWith } from '@almadar/core';
14
14
  import type { DisplayStateProps } from '../../core/organisms/types';
15
+ /** The per-feature entity fields this organism reads (FieldValue-compatible; the
16
+ * `icon`/`href` are string names/urls, the molecule widens `icon` to `IconInput`). */
17
+ export interface FeatureRow {
18
+ title: string;
19
+ icon?: string;
20
+ description?: string;
21
+ href?: string;
22
+ linkLabel?: string;
23
+ }
15
24
  export interface FeatureGridOrganismProps extends DisplayStateProps {
16
- entity?: EntityWith<'title'> | readonly EntityWith<'title'>[];
25
+ entity?: EntityWith<FeatureRow> | readonly EntityWith<FeatureRow>[];
17
26
  columns?: 2 | 3 | 4 | 6;
18
27
  heading?: string;
19
28
  subtitle?: string;
@@ -12,8 +12,31 @@
12
12
  import React from 'react';
13
13
  import type { EntityWith } from '@almadar/core';
14
14
  import type { DisplayStateProps } from '../../core/organisms/types';
15
+ /** A hero CTA: a plain `{ label, href }` object (FieldValue-compatible — a `type`,
16
+ * not `interface`, so it's assignable to the entity-row `FieldValue` index sig). */
17
+ export type HeroAction = {
18
+ label?: string;
19
+ href?: string;
20
+ };
21
+ /** The hero entity fields this organism reads (FieldValue-compatible; `image` is a plain
22
+ * `{ src, alt }` object, the action fields plain `{ label, href }` objects). */
23
+ export interface HeroRow {
24
+ title: string;
25
+ tag?: string;
26
+ titleAccent?: string;
27
+ subtitle?: string;
28
+ primaryAction?: HeroAction;
29
+ secondaryAction?: HeroAction;
30
+ installCommand?: string;
31
+ image?: {
32
+ src?: string;
33
+ alt?: string;
34
+ };
35
+ imagePosition?: 'below' | 'right' | 'background';
36
+ background?: 'dark' | 'gradient' | 'subtle';
37
+ }
15
38
  export interface HeroOrganismProps extends DisplayStateProps {
16
- entity?: EntityWith<'title'>;
39
+ entity?: EntityWith<HeroRow>;
17
40
  children?: React.ReactNode;
18
41
  }
19
42
  export declare const HeroOrganism: React.FC<HeroOrganismProps>;
@@ -12,8 +12,21 @@
12
12
  import React from 'react';
13
13
  import type { EntityWith } from '@almadar/core';
14
14
  import type { DisplayStateProps } from '../../core/organisms/types';
15
+ /** The per-plan entity fields this organism reads. The organism reads FLAT
16
+ * `actionLabel`/`actionHref` (the molecule's PricingCardProps nests them under
17
+ * `action`), so this is its own shape, not PricingCardProps. */
18
+ export interface PricingPlanRow {
19
+ name: string;
20
+ price: string;
21
+ description?: string;
22
+ features?: string[];
23
+ actionLabel?: string;
24
+ actionHref?: string;
25
+ highlighted?: boolean;
26
+ badge?: string;
27
+ }
15
28
  export interface PricingOrganismProps extends DisplayStateProps {
16
- entity?: EntityWith<'name' | 'price'> | readonly EntityWith<'name' | 'price'>[];
29
+ entity?: EntityWith<PricingPlanRow> | readonly EntityWith<PricingPlanRow>[];
17
30
  heading?: string;
18
31
  subtitle?: string;
19
32
  }
@@ -12,8 +12,21 @@
12
12
  import React from 'react';
13
13
  import type { EntityWith } from '@almadar/core';
14
14
  import type { DisplayStateProps } from '../../core/organisms/types';
15
+ /** The per-showcase entity fields this organism reads (FieldValue-compatible; `image`
16
+ * is a plain `{ src, alt }` object, `accentColor` a string). */
17
+ export interface ShowcaseRow {
18
+ title: string;
19
+ description?: string;
20
+ image?: {
21
+ src?: string;
22
+ alt?: string;
23
+ };
24
+ href?: string;
25
+ badge?: string;
26
+ accentColor?: string;
27
+ }
15
28
  export interface ShowcaseOrganismProps extends DisplayStateProps {
16
- entity?: EntityWith<'title'> | readonly EntityWith<'title'>[];
29
+ entity?: EntityWith<ShowcaseRow> | readonly EntityWith<ShowcaseRow>[];
17
30
  columns?: 2 | 3 | 4;
18
31
  heading?: string;
19
32
  subtitle?: string;
@@ -11,8 +11,13 @@
11
11
  import React from 'react';
12
12
  import type { EntityWith } from '@almadar/core';
13
13
  import type { DisplayStateProps } from '../../core/organisms/types';
14
+ /** The per-stat entity fields this organism reads (both required). */
15
+ export interface StatRow {
16
+ value: string;
17
+ label: string;
18
+ }
14
19
  export interface StatsOrganismProps extends DisplayStateProps {
15
- entity?: EntityWith<'value' | 'label'> | readonly EntityWith<'value' | 'label'>[];
20
+ entity?: EntityWith<StatRow> | readonly EntityWith<StatRow>[];
16
21
  columns?: 2 | 3 | 4 | 6;
17
22
  }
18
23
  export declare const StatsOrganism: React.FC<StatsOrganismProps>;
@@ -11,8 +11,16 @@
11
11
  import React from 'react';
12
12
  import type { EntityWith } from '@almadar/core';
13
13
  import type { DisplayStateProps } from '../../core/organisms/types';
14
+ /** The per-step entity fields this organism reads (FieldValue-compatible; `icon` is a
15
+ * string name, the molecule widens it to `IconInput`). */
16
+ export interface StepRow {
17
+ title: string;
18
+ number?: number;
19
+ description?: string;
20
+ icon?: string;
21
+ }
14
22
  export interface StepFlowOrganismProps extends DisplayStateProps {
15
- entity?: EntityWith<'title'> | readonly EntityWith<'title'>[];
23
+ entity?: EntityWith<StepRow> | readonly EntityWith<StepRow>[];
16
24
  orientation?: 'horizontal' | 'vertical';
17
25
  showConnectors?: boolean;
18
26
  heading?: string;
@@ -11,8 +11,17 @@
11
11
  import React from 'react';
12
12
  import type { EntityWith } from '@almadar/core';
13
13
  import type { DisplayStateProps } from '../../core/organisms/types';
14
+ /** The per-member entity fields this organism reads (FieldValue-compatible;
15
+ * `avatar` is a string url/initials). */
16
+ export interface TeamMemberRow {
17
+ name: string;
18
+ nameAr?: string;
19
+ role?: string;
20
+ bio?: string;
21
+ avatar?: string;
22
+ }
14
23
  export interface TeamOrganismProps extends DisplayStateProps {
15
- entity?: EntityWith<'name'> | readonly EntityWith<'name'>[];
24
+ entity?: EntityWith<TeamMemberRow> | readonly EntityWith<TeamMemberRow>[];
16
25
  heading?: string;
17
26
  subtitle?: string;
18
27
  }
@@ -2702,12 +2702,60 @@ var init_useEventBus = __esm({
2702
2702
  };
2703
2703
  }
2704
2704
  });
2705
+ function useTapReveal(options = {}) {
2706
+ const { onReveal, onDismiss, refs, enabled = true } = options;
2707
+ const [revealed, setRevealed] = React8.useState(false);
2708
+ const onRevealRef = React8.useRef(onReveal);
2709
+ const onDismissRef = React8.useRef(onDismiss);
2710
+ onRevealRef.current = onReveal;
2711
+ onDismissRef.current = onDismiss;
2712
+ const reveal = React8.useCallback(() => {
2713
+ setRevealed((wasRevealed) => {
2714
+ if (!wasRevealed) onRevealRef.current?.();
2715
+ return true;
2716
+ });
2717
+ }, []);
2718
+ const dismiss = React8.useCallback(() => {
2719
+ setRevealed((wasRevealed) => {
2720
+ if (wasRevealed) onDismissRef.current?.();
2721
+ return false;
2722
+ });
2723
+ }, []);
2724
+ const onPointerDown = React8.useCallback(
2725
+ (e) => {
2726
+ if (!enabled || e.pointerType === "mouse") return;
2727
+ if (revealed) dismiss();
2728
+ else reveal();
2729
+ },
2730
+ [enabled, revealed, reveal, dismiss]
2731
+ );
2732
+ React8.useEffect(() => {
2733
+ if (!revealed || typeof document === "undefined") return;
2734
+ const onDocDown = (ev) => {
2735
+ const target = ev.target;
2736
+ if (!(target instanceof Node)) return;
2737
+ const inside = (refs ?? []).some((r2) => r2.current?.contains(target));
2738
+ if (!inside) dismiss();
2739
+ };
2740
+ const id = setTimeout(() => document.addEventListener("pointerdown", onDocDown), 0);
2741
+ return () => {
2742
+ clearTimeout(id);
2743
+ document.removeEventListener("pointerdown", onDocDown);
2744
+ };
2745
+ }, [revealed, refs, dismiss]);
2746
+ return { revealed, triggerProps: { onPointerDown }, reveal, dismiss };
2747
+ }
2748
+ var init_useTapReveal = __esm({
2749
+ "hooks/useTapReveal.ts"() {
2750
+ }
2751
+ });
2705
2752
  var paddingStyles, paddingXStyles, paddingYStyles, marginStyles, marginXStyles, marginYStyles, bgStyles, roundedStyles, shadowStyles, displayStyles, overflowStyles, positionStyles; exports.Box = void 0;
2706
2753
  var init_Box = __esm({
2707
2754
  "components/core/atoms/Box.tsx"() {
2708
2755
  "use client";
2709
2756
  init_cn();
2710
2757
  init_useEventBus();
2758
+ init_useTapReveal();
2711
2759
  paddingStyles = {
2712
2760
  none: "p-0",
2713
2761
  xs: "p-1",
@@ -2833,10 +2881,12 @@ var init_Box = __esm({
2833
2881
  action,
2834
2882
  actionPayload,
2835
2883
  hoverEvent,
2884
+ tapReveal = true,
2836
2885
  maxWidth,
2837
2886
  onClick,
2838
2887
  onMouseEnter,
2839
2888
  onMouseLeave,
2889
+ onPointerDown,
2840
2890
  ...rest
2841
2891
  }, ref) => {
2842
2892
  const eventBus = useEventBus();
@@ -2859,6 +2909,19 @@ var init_Box = __esm({
2859
2909
  }
2860
2910
  onMouseLeave?.(e);
2861
2911
  }, [hoverEvent, eventBus, onMouseLeave]);
2912
+ const { triggerProps } = useTapReveal({
2913
+ enabled: tapReveal && !!hoverEvent,
2914
+ onReveal: React8.useCallback(() => {
2915
+ if (hoverEvent) eventBus.emit(`UI:${hoverEvent}`, { hovered: true });
2916
+ }, [hoverEvent, eventBus]),
2917
+ onDismiss: React8.useCallback(() => {
2918
+ if (hoverEvent) eventBus.emit(`UI:${hoverEvent}`, { hovered: false });
2919
+ }, [hoverEvent, eventBus])
2920
+ });
2921
+ const handlePointerDown = React8.useCallback((e) => {
2922
+ if (hoverEvent && tapReveal) triggerProps.onPointerDown(e);
2923
+ onPointerDown?.(e);
2924
+ }, [hoverEvent, tapReveal, triggerProps, onPointerDown]);
2862
2925
  const isClickable = action || onClick;
2863
2926
  return React8__default.default.createElement(
2864
2927
  Component,
@@ -2886,6 +2949,7 @@ var init_Box = __esm({
2886
2949
  onClick: isClickable ? handleClick : void 0,
2887
2950
  onMouseEnter: hoverEvent || onMouseEnter ? handleMouseEnter : void 0,
2888
2951
  onMouseLeave: hoverEvent || onMouseLeave ? handleMouseLeave : void 0,
2952
+ onPointerDown: hoverEvent && tapReveal || onPointerDown ? handlePointerDown : void 0,
2889
2953
  style: maxWidth ? { maxWidth, ...rest.style } : rest.style,
2890
2954
  ...rest
2891
2955
  },
@@ -56,6 +56,8 @@ interface BoxProps extends React.HTMLAttributes<HTMLDivElement> {
56
56
  actionPayload?: EventPayload;
57
57
  /** Declarative hover event — emits UI:{hoverEvent} with { hovered: true/false } on mouseEnter/mouseLeave */
58
58
  hoverEvent?: EventKey;
59
+ /** When true (default), a touch/pen tap also fires `hoverEvent` (toggling hovered) so hover-only reveals work on touch. */
60
+ tapReveal?: boolean;
59
61
  /** Maximum width (CSS value, e.g., "550px", "80rem") */
60
62
  maxWidth?: string;
61
63
  /** Children elements */
@@ -2660,12 +2660,60 @@ var init_useEventBus = __esm({
2660
2660
  };
2661
2661
  }
2662
2662
  });
2663
+ function useTapReveal(options = {}) {
2664
+ const { onReveal, onDismiss, refs, enabled = true } = options;
2665
+ const [revealed, setRevealed] = useState(false);
2666
+ const onRevealRef = useRef(onReveal);
2667
+ const onDismissRef = useRef(onDismiss);
2668
+ onRevealRef.current = onReveal;
2669
+ onDismissRef.current = onDismiss;
2670
+ const reveal = useCallback(() => {
2671
+ setRevealed((wasRevealed) => {
2672
+ if (!wasRevealed) onRevealRef.current?.();
2673
+ return true;
2674
+ });
2675
+ }, []);
2676
+ const dismiss = useCallback(() => {
2677
+ setRevealed((wasRevealed) => {
2678
+ if (wasRevealed) onDismissRef.current?.();
2679
+ return false;
2680
+ });
2681
+ }, []);
2682
+ const onPointerDown = useCallback(
2683
+ (e) => {
2684
+ if (!enabled || e.pointerType === "mouse") return;
2685
+ if (revealed) dismiss();
2686
+ else reveal();
2687
+ },
2688
+ [enabled, revealed, reveal, dismiss]
2689
+ );
2690
+ useEffect(() => {
2691
+ if (!revealed || typeof document === "undefined") return;
2692
+ const onDocDown = (ev) => {
2693
+ const target = ev.target;
2694
+ if (!(target instanceof Node)) return;
2695
+ const inside = (refs ?? []).some((r2) => r2.current?.contains(target));
2696
+ if (!inside) dismiss();
2697
+ };
2698
+ const id = setTimeout(() => document.addEventListener("pointerdown", onDocDown), 0);
2699
+ return () => {
2700
+ clearTimeout(id);
2701
+ document.removeEventListener("pointerdown", onDocDown);
2702
+ };
2703
+ }, [revealed, refs, dismiss]);
2704
+ return { revealed, triggerProps: { onPointerDown }, reveal, dismiss };
2705
+ }
2706
+ var init_useTapReveal = __esm({
2707
+ "hooks/useTapReveal.ts"() {
2708
+ }
2709
+ });
2663
2710
  var paddingStyles, paddingXStyles, paddingYStyles, marginStyles, marginXStyles, marginYStyles, bgStyles, roundedStyles, shadowStyles, displayStyles, overflowStyles, positionStyles, Box;
2664
2711
  var init_Box = __esm({
2665
2712
  "components/core/atoms/Box.tsx"() {
2666
2713
  "use client";
2667
2714
  init_cn();
2668
2715
  init_useEventBus();
2716
+ init_useTapReveal();
2669
2717
  paddingStyles = {
2670
2718
  none: "p-0",
2671
2719
  xs: "p-1",
@@ -2791,10 +2839,12 @@ var init_Box = __esm({
2791
2839
  action,
2792
2840
  actionPayload,
2793
2841
  hoverEvent,
2842
+ tapReveal = true,
2794
2843
  maxWidth,
2795
2844
  onClick,
2796
2845
  onMouseEnter,
2797
2846
  onMouseLeave,
2847
+ onPointerDown,
2798
2848
  ...rest
2799
2849
  }, ref) => {
2800
2850
  const eventBus = useEventBus();
@@ -2817,6 +2867,19 @@ var init_Box = __esm({
2817
2867
  }
2818
2868
  onMouseLeave?.(e);
2819
2869
  }, [hoverEvent, eventBus, onMouseLeave]);
2870
+ const { triggerProps } = useTapReveal({
2871
+ enabled: tapReveal && !!hoverEvent,
2872
+ onReveal: useCallback(() => {
2873
+ if (hoverEvent) eventBus.emit(`UI:${hoverEvent}`, { hovered: true });
2874
+ }, [hoverEvent, eventBus]),
2875
+ onDismiss: useCallback(() => {
2876
+ if (hoverEvent) eventBus.emit(`UI:${hoverEvent}`, { hovered: false });
2877
+ }, [hoverEvent, eventBus])
2878
+ });
2879
+ const handlePointerDown = useCallback((e) => {
2880
+ if (hoverEvent && tapReveal) triggerProps.onPointerDown(e);
2881
+ onPointerDown?.(e);
2882
+ }, [hoverEvent, tapReveal, triggerProps, onPointerDown]);
2820
2883
  const isClickable = action || onClick;
2821
2884
  return React8.createElement(
2822
2885
  Component,
@@ -2844,6 +2907,7 @@ var init_Box = __esm({
2844
2907
  onClick: isClickable ? handleClick : void 0,
2845
2908
  onMouseEnter: hoverEvent || onMouseEnter ? handleMouseEnter : void 0,
2846
2909
  onMouseLeave: hoverEvent || onMouseLeave ? handleMouseLeave : void 0,
2910
+ onPointerDown: hoverEvent && tapReveal || onPointerDown ? handlePointerDown : void 0,
2847
2911
  style: maxWidth ? { maxWidth, ...rest.style } : rest.style,
2848
2912
  ...rest
2849
2913
  },
@@ -2516,6 +2516,152 @@ function usePinchZoom(options = {}) {
2516
2516
  resetZoom
2517
2517
  };
2518
2518
  }
2519
+ function localPoint(canvas, clientX, clientY) {
2520
+ if (!canvas) return { x: clientX, y: clientY };
2521
+ const rect = canvas.getBoundingClientRect();
2522
+ return { x: clientX - rect.left, y: clientY - rect.top };
2523
+ }
2524
+ function useCanvasGestures(options) {
2525
+ const {
2526
+ canvasRef,
2527
+ enabled = true,
2528
+ wheelStep = 1.1,
2529
+ onPointerDown,
2530
+ onPointerMove,
2531
+ onPointerUp,
2532
+ onZoom,
2533
+ onPanDelta,
2534
+ onMultiTouchStart
2535
+ } = options;
2536
+ const pointers = react.useRef(/* @__PURE__ */ new Map());
2537
+ const pinch = react.useRef(null);
2538
+ const computePinch = react.useCallback(() => {
2539
+ const pts = [...pointers.current.values()];
2540
+ const dx = pts[1].x - pts[0].x;
2541
+ const dy = pts[1].y - pts[0].y;
2542
+ return {
2543
+ distance: Math.hypot(dx, dy) || 1,
2544
+ centroid: { x: (pts[0].x + pts[1].x) / 2, y: (pts[0].y + pts[1].y) / 2 }
2545
+ };
2546
+ }, []);
2547
+ const handlePointerDown = react.useCallback(
2548
+ (e) => {
2549
+ if (!enabled) return;
2550
+ e.currentTarget.setPointerCapture(e.pointerId);
2551
+ pointers.current.set(e.pointerId, localPoint(canvasRef.current, e.clientX, e.clientY));
2552
+ if (pointers.current.size === 2) {
2553
+ onMultiTouchStart?.();
2554
+ pinch.current = computePinch();
2555
+ } else if (pointers.current.size === 1) {
2556
+ onPointerDown?.(e);
2557
+ }
2558
+ },
2559
+ [enabled, canvasRef, onMultiTouchStart, computePinch, onPointerDown]
2560
+ );
2561
+ const handlePointerMove = react.useCallback(
2562
+ (e) => {
2563
+ if (!enabled) return;
2564
+ if (pointers.current.has(e.pointerId)) {
2565
+ pointers.current.set(e.pointerId, localPoint(canvasRef.current, e.clientX, e.clientY));
2566
+ }
2567
+ if (pointers.current.size >= 2 && pinch.current) {
2568
+ const next = computePinch();
2569
+ const prev = pinch.current;
2570
+ if (next.distance !== prev.distance) {
2571
+ onZoom?.(next.distance / prev.distance, next.centroid.x, next.centroid.y);
2572
+ }
2573
+ onPanDelta?.(next.centroid.x - prev.centroid.x, next.centroid.y - prev.centroid.y);
2574
+ pinch.current = next;
2575
+ return;
2576
+ }
2577
+ onPointerMove?.(e);
2578
+ },
2579
+ [enabled, canvasRef, computePinch, onZoom, onPanDelta, onPointerMove]
2580
+ );
2581
+ const endPointer = react.useCallback(
2582
+ (e, fireUp) => {
2583
+ const wasMulti = pointers.current.size >= 2;
2584
+ pointers.current.delete(e.pointerId);
2585
+ if (pointers.current.size < 2) pinch.current = null;
2586
+ if (wasMulti && pointers.current.size === 1) return;
2587
+ if (pointers.current.size === 0 && fireUp) onPointerUp?.(e);
2588
+ },
2589
+ [onPointerUp]
2590
+ );
2591
+ const handlePointerUp = react.useCallback(
2592
+ (e) => {
2593
+ if (!enabled) return;
2594
+ endPointer(e, true);
2595
+ },
2596
+ [enabled, endPointer]
2597
+ );
2598
+ const handlePointerCancel = react.useCallback(
2599
+ (e) => {
2600
+ if (!enabled) return;
2601
+ endPointer(e, false);
2602
+ },
2603
+ [enabled, endPointer]
2604
+ );
2605
+ const handleWheel = react.useCallback(
2606
+ (e) => {
2607
+ if (!enabled) return;
2608
+ e.preventDefault();
2609
+ const { x, y } = localPoint(canvasRef.current, e.clientX, e.clientY);
2610
+ onZoom?.(e.deltaY < 0 ? wheelStep : 1 / wheelStep, x, y);
2611
+ },
2612
+ [enabled, canvasRef, wheelStep, onZoom]
2613
+ );
2614
+ return {
2615
+ onPointerDown: handlePointerDown,
2616
+ onPointerMove: handlePointerMove,
2617
+ onPointerUp: handlePointerUp,
2618
+ onPointerCancel: handlePointerCancel,
2619
+ onWheel: handleWheel
2620
+ };
2621
+ }
2622
+ function useTapReveal(options = {}) {
2623
+ const { onReveal, onDismiss, refs, enabled = true } = options;
2624
+ const [revealed, setRevealed] = react.useState(false);
2625
+ const onRevealRef = react.useRef(onReveal);
2626
+ const onDismissRef = react.useRef(onDismiss);
2627
+ onRevealRef.current = onReveal;
2628
+ onDismissRef.current = onDismiss;
2629
+ const reveal = react.useCallback(() => {
2630
+ setRevealed((wasRevealed) => {
2631
+ if (!wasRevealed) onRevealRef.current?.();
2632
+ return true;
2633
+ });
2634
+ }, []);
2635
+ const dismiss = react.useCallback(() => {
2636
+ setRevealed((wasRevealed) => {
2637
+ if (wasRevealed) onDismissRef.current?.();
2638
+ return false;
2639
+ });
2640
+ }, []);
2641
+ const onPointerDown = react.useCallback(
2642
+ (e) => {
2643
+ if (!enabled || e.pointerType === "mouse") return;
2644
+ if (revealed) dismiss();
2645
+ else reveal();
2646
+ },
2647
+ [enabled, revealed, reveal, dismiss]
2648
+ );
2649
+ react.useEffect(() => {
2650
+ if (!revealed || typeof document === "undefined") return;
2651
+ const onDocDown = (ev) => {
2652
+ const target = ev.target;
2653
+ if (!(target instanceof Node)) return;
2654
+ const inside = (refs ?? []).some((r) => r.current?.contains(target));
2655
+ if (!inside) dismiss();
2656
+ };
2657
+ const id = setTimeout(() => document.addEventListener("pointerdown", onDocDown), 0);
2658
+ return () => {
2659
+ clearTimeout(id);
2660
+ document.removeEventListener("pointerdown", onDocDown);
2661
+ };
2662
+ }, [revealed, refs, dismiss]);
2663
+ return { revealed, triggerProps: { onPointerDown }, reveal, dismiss };
2664
+ }
2519
2665
  var ALMADAR_DND_MIME = "application/x-almadar-dnd";
2520
2666
  function useDraggable({ payload, disabled = false }) {
2521
2667
  const [isDragging, setIsDragging] = react.useState(false);
@@ -2767,6 +2913,7 @@ exports.updateEntity = updateEntity;
2767
2913
  exports.updateSingleton = updateSingleton;
2768
2914
  exports.useAgentChat = useAgentChat;
2769
2915
  exports.useAuthContext = useAuthContext;
2916
+ exports.useCanvasGestures = useCanvasGestures;
2770
2917
  exports.useCompile = useCompile;
2771
2918
  exports.useConnectGitHub = useConnectGitHub;
2772
2919
  exports.useDeepAgentGeneration = useDeepAgentGeneration;
@@ -2800,6 +2947,7 @@ exports.useQuerySingleton = useQuerySingleton;
2800
2947
  exports.useRenderInterpolation = useRenderInterpolation;
2801
2948
  exports.useSingletonEntity = useSingletonEntity;
2802
2949
  exports.useSwipeGesture = useSwipeGesture;
2950
+ exports.useTapReveal = useTapReveal;
2803
2951
  exports.useTraitListens = useTraitListens;
2804
2952
  exports.useTranslate = useTranslate;
2805
2953
  exports.useUIEvents = useUIEvents;
@@ -24,6 +24,8 @@ export { useDragReorder, type DragReorderResult, } from './useDragReorder';
24
24
  export { useInfiniteScroll, type InfiniteScrollOptions, type InfiniteScrollResult, } from './useInfiniteScroll';
25
25
  export { usePullToRefresh, type PullToRefreshOptions, type PullToRefreshResult, } from './usePullToRefresh';
26
26
  export { usePinchZoom, type PinchZoomOptions, type PinchZoomResult, } from './usePinchZoom';
27
+ export { useCanvasGestures, type UseCanvasGesturesOptions, type CanvasGestureCallbacks, type CanvasGestureHandlers, } from './useCanvasGestures';
28
+ export { useTapReveal, type TapRevealOptions, type TapRevealResult, } from './useTapReveal';
27
29
  export { useDraggable, ALMADAR_DND_MIME, type DraggablePayload, type UseDraggableOptions, type UseDraggableResult, } from './useDraggable';
28
30
  export { useDropZone, type UseDropZoneOptions, type UseDropZoneResult, } from './useDropZone';
29
31
  export { useRenderInterpolation, type Positioned, type RenderInterpolationOptions, type RenderInterpolationHandle, } from './useRenderInterpolation';