h2o-library 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/dist/charts/components/index.d.ts +1 -0
- package/dist/charts/components/src/button/button.d.ts +3 -0
- package/dist/charts/components/src/chip/chip.d.ts +4 -1
- package/dist/charts/components/src/content-switch/content-switch.d.ts +3 -0
- package/dist/charts/components/src/input/input.d.ts +5 -0
- package/dist/charts/components/src/onboarding/onboarding.d.ts +100 -0
- package/dist/charts/components/src/radio-group/radio-group.d.ts +3 -0
- package/dist/charts/hooks/index.d.ts +1 -0
- package/dist/charts/hooks/src/use-onboarding.d.ts +66 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/src/button/button.d.ts +3 -0
- package/dist/components/src/chip/chip.d.ts +4 -1
- package/dist/components/src/content-switch/content-switch.d.ts +3 -0
- package/dist/components/src/input/input.d.ts +5 -0
- package/dist/components/src/onboarding/onboarding.d.ts +100 -0
- package/dist/components/src/radio-group/radio-group.d.ts +3 -0
- package/dist/esm/components/index.d.ts +1 -0
- package/dist/esm/components/src/button/button.d.ts +3 -0
- package/dist/esm/components/src/button/button.js +1 -1
- package/dist/esm/components/src/chip/chip.d.ts +4 -1
- package/dist/esm/components/src/chip/chip.js +1 -1
- package/dist/esm/components/src/content-switch/content-switch.d.ts +3 -0
- package/dist/esm/components/src/content-switch/content-switch.js +1 -1
- package/dist/esm/components/src/input/input.d.ts +5 -0
- package/dist/esm/components/src/input/input.js +1 -1
- package/dist/esm/components/src/modal/modal.js +1 -1
- package/dist/esm/components/src/onboarding/onboarding.css +178 -0
- package/dist/esm/components/src/onboarding/onboarding.d.ts +100 -0
- package/dist/esm/components/src/onboarding/onboarding.js +2 -0
- package/dist/esm/components/src/radio-group/radio-group.d.ts +3 -0
- package/dist/esm/components/src/radio-group/radio-group.js +1 -1
- package/dist/esm/hooks/index.d.ts +1 -0
- package/dist/esm/hooks/src/use-onboarding.d.ts +66 -0
- package/dist/esm/hooks/src/use-onboarding.js +2 -0
- package/dist/esm/index.js +1 -1
- package/dist/hooks/components/index.d.ts +1 -0
- package/dist/hooks/components/src/button/button.d.ts +3 -0
- package/dist/hooks/components/src/chip/chip.d.ts +4 -1
- package/dist/hooks/components/src/content-switch/content-switch.d.ts +3 -0
- package/dist/hooks/components/src/input/input.d.ts +5 -0
- package/dist/hooks/components/src/onboarding/onboarding.d.ts +100 -0
- package/dist/hooks/components/src/radio-group/radio-group.d.ts +3 -0
- package/dist/hooks/hooks/index.d.ts +1 -0
- package/dist/hooks/hooks/src/use-onboarding.d.ts +66 -0
- package/dist/hooks/index.cjs.js +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/src/use-onboarding.d.ts +66 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.css +1 -1
- package/dist/types/components/index.d.ts +1 -0
- package/dist/types/components/src/button/button.d.ts +3 -0
- package/dist/types/components/src/chip/chip.d.ts +4 -1
- package/dist/types/components/src/content-switch/content-switch.d.ts +3 -0
- package/dist/types/components/src/input/input.d.ts +5 -0
- package/dist/types/components/src/onboarding/onboarding.d.ts +100 -0
- package/dist/types/components/src/radio-group/radio-group.d.ts +3 -0
- package/dist/types/hooks/index.d.ts +1 -0
- package/dist/types/hooks/src/use-onboarding.d.ts +66 -0
- package/dist/utils/components/index.d.ts +1 -0
- package/dist/utils/components/src/button/button.d.ts +3 -0
- package/dist/utils/components/src/chip/chip.d.ts +4 -1
- package/dist/utils/components/src/content-switch/content-switch.d.ts +3 -0
- package/dist/utils/components/src/input/input.d.ts +5 -0
- package/dist/utils/components/src/onboarding/onboarding.d.ts +100 -0
- package/dist/utils/components/src/radio-group/radio-group.d.ts +3 -0
- package/dist/utils/hooks/index.d.ts +1 -0
- package/dist/utils/hooks/src/use-onboarding.d.ts +66 -0
- package/package.json +6 -1
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import "./onboarding.css";
|
|
2
|
+
import type React from "react";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import { type OnboardingStorage, type UseOnboardingReturn } from "../../../hooks/src/use-onboarding";
|
|
5
|
+
/** Where a step popup sits relative to its target. `"center"` ignores the target and centers on screen. */
|
|
6
|
+
export type OnboardingPlacement = "top" | "bottom" | "left" | "right" | "auto" | "center";
|
|
7
|
+
/**
|
|
8
|
+
* How a step finds its anchor element:
|
|
9
|
+
* - `string` — a CSS selector (e.g. `"#sidebar"`, `".profile"`, `"[data-tour=save]"`). A bare word falls back to an element id.
|
|
10
|
+
* - `HTMLElement` — the element directly.
|
|
11
|
+
* - `() => HTMLElement | null` — resolved lazily when the step shows.
|
|
12
|
+
*/
|
|
13
|
+
export type OnboardingTarget = string | HTMLElement | (() => HTMLElement | null);
|
|
14
|
+
/**
|
|
15
|
+
* A single step in an onboarding flow.
|
|
16
|
+
*
|
|
17
|
+
* @property {OnboardingTarget} [target] - Element to anchor + spotlight. Omit for a centered step (welcome/finish screens).
|
|
18
|
+
* @property {ReactNode} [title] - Heading shown at the top of the popup.
|
|
19
|
+
* @property {ReactNode} content - Body message (required).
|
|
20
|
+
* @property {OnboardingPlacement} [placement] - Popup side. Default: the flow's `placement` (which defaults to `"auto"`).
|
|
21
|
+
* @property {number} [spotlightPadding] - Extra px around the target in the spotlight cutout.
|
|
22
|
+
* @property {number} [spotlightRadius] - Spotlight corner radius in px.
|
|
23
|
+
* @property {boolean} [disableInteraction] - Block clicks on the spotlighted element. Default: the flow's `disableInteraction`.
|
|
24
|
+
*/
|
|
25
|
+
export interface OnboardingStep {
|
|
26
|
+
target?: OnboardingTarget;
|
|
27
|
+
title?: ReactNode;
|
|
28
|
+
content: ReactNode;
|
|
29
|
+
placement?: OnboardingPlacement;
|
|
30
|
+
spotlightPadding?: number;
|
|
31
|
+
spotlightRadius?: number;
|
|
32
|
+
disableInteraction?: boolean;
|
|
33
|
+
showBack?: boolean;
|
|
34
|
+
showSkip?: boolean;
|
|
35
|
+
nextLabel?: string;
|
|
36
|
+
backLabel?: string;
|
|
37
|
+
finishLabel?: string;
|
|
38
|
+
}
|
|
39
|
+
/** Context passed to `renderStep` / `renderFooter` for fully custom popup UI. */
|
|
40
|
+
export interface OnboardingRenderContext extends UseOnboardingReturn {
|
|
41
|
+
step: OnboardingStep;
|
|
42
|
+
}
|
|
43
|
+
/** Imperative handle exposed via `ref` on `Onboarding`. */
|
|
44
|
+
export type OnboardingHandle = UseOnboardingReturn;
|
|
45
|
+
/**
|
|
46
|
+
* Props for `Onboarding`. Most are flow-wide defaults that individual steps can override.
|
|
47
|
+
*
|
|
48
|
+
* @property {string} id - Stable flow id; persistence key.
|
|
49
|
+
* @property {OnboardingStep[]} steps - Ordered steps.
|
|
50
|
+
* @property {boolean} [run] - Controlled active state. Omit to auto-manage (see `autoStart`).
|
|
51
|
+
*/
|
|
52
|
+
export interface OnboardingProps {
|
|
53
|
+
id: string;
|
|
54
|
+
steps: OnboardingStep[];
|
|
55
|
+
run?: boolean;
|
|
56
|
+
autoStart?: boolean;
|
|
57
|
+
persist?: boolean;
|
|
58
|
+
storage?: OnboardingStorage;
|
|
59
|
+
startDelay?: number;
|
|
60
|
+
placement?: OnboardingPlacement;
|
|
61
|
+
spotlightPadding?: number;
|
|
62
|
+
spotlightRadius?: number;
|
|
63
|
+
disableInteraction?: boolean;
|
|
64
|
+
closeOnOverlayClick?: boolean;
|
|
65
|
+
closeOnEsc?: boolean;
|
|
66
|
+
keyboardNavigation?: boolean;
|
|
67
|
+
scrollIntoView?: boolean;
|
|
68
|
+
scrollBehavior?: ScrollBehavior;
|
|
69
|
+
offset?: number;
|
|
70
|
+
zIndex?: number;
|
|
71
|
+
showProgress?: boolean;
|
|
72
|
+
showSkip?: boolean;
|
|
73
|
+
showBack?: boolean;
|
|
74
|
+
showClose?: boolean;
|
|
75
|
+
nextLabel?: string;
|
|
76
|
+
backLabel?: string;
|
|
77
|
+
skipLabel?: string;
|
|
78
|
+
finishLabel?: string;
|
|
79
|
+
className?: string;
|
|
80
|
+
overlayClassName?: string;
|
|
81
|
+
onStart?: () => void;
|
|
82
|
+
onStepChange?: (index: number, previous: number, step: OnboardingStep) => void;
|
|
83
|
+
onSkip?: (index: number) => void;
|
|
84
|
+
onFinish?: () => void;
|
|
85
|
+
onClose?: () => void;
|
|
86
|
+
renderStep?: (ctx: OnboardingRenderContext) => ReactNode;
|
|
87
|
+
renderFooter?: (ctx: OnboardingRenderContext) => ReactNode;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Modular onboarding / product-tour overlay. Drive a flow by `id` with a `steps`
|
|
91
|
+
* array — each step spotlights an element (by selector/id) and shows a popup with
|
|
92
|
+
* Back / Skip / Next controls. Persists completion so a flow shows once.
|
|
93
|
+
*
|
|
94
|
+
* Headless control lives in {@link useOnboarding}; this component is the batteries-included UI.
|
|
95
|
+
*
|
|
96
|
+
* @param {OnboardingProps} props
|
|
97
|
+
* @param {React.Ref<OnboardingHandle>} ref - Imperative controls (`start`, `next`, `skip`, `reset`, …).
|
|
98
|
+
* @returns {JSX.Element | null} A portal with the spotlight overlay + popup while active.
|
|
99
|
+
*/
|
|
100
|
+
export declare const Onboarding: React.ForwardRefExoticComponent<OnboardingProps & React.RefAttributes<UseOnboardingReturn>>;
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import "./radio-group.css";
|
|
2
|
+
import { Icon } from "../icon/icon";
|
|
2
3
|
import type { IconType, TooltipPosition } from "../../../types";
|
|
3
4
|
/**
|
|
4
5
|
* One entry in a `RadioGroup` `options` array.
|
|
5
6
|
* @property {string} label - Option title.
|
|
6
7
|
* @property {string} value - Emitted to `onChange` when selected.
|
|
7
8
|
* @property {import("../../../types").IconType} [icon] - Optional leading icon.
|
|
9
|
+
* @property {Omit<React.ComponentProps<typeof Icon>, "icon">} [iconProps] - Extra props forwarded to the built-in `Icon` (and its `<svg>`).
|
|
8
10
|
* @property {"active"|"inactive"} [dot] - Optional status dot style when no icon.
|
|
9
11
|
*/
|
|
10
12
|
export interface RadioOption {
|
|
11
13
|
label: string;
|
|
12
14
|
value: string;
|
|
13
15
|
icon?: IconType;
|
|
16
|
+
iconProps?: Omit<React.ComponentProps<typeof Icon>, "icon">;
|
|
14
17
|
dot?: "active" | "inactive";
|
|
15
18
|
disabled?: boolean;
|
|
16
19
|
tooltip?: string;
|
|
@@ -12,6 +12,7 @@ export * from "./src/use-key-press";
|
|
|
12
12
|
export * from "./src/use-long-press";
|
|
13
13
|
export * from "./src/use-media-query";
|
|
14
14
|
export * from "./src/use-mouse";
|
|
15
|
+
export * from "./src/use-onboarding";
|
|
15
16
|
export * from "./src/use-timeout";
|
|
16
17
|
export { useToast, ToastContext, type ToastContextType } from "./src/use-toast";
|
|
17
18
|
export * from "./src/use-window-scroll";
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluggable persistence adapter for onboarding flows. Swap to back completion
|
|
3
|
+
* with cookies, a backend, or anything else by implementing these two methods.
|
|
4
|
+
*
|
|
5
|
+
* @property {(id: string) => boolean} isCompleted - Whether the flow `id` was already finished/skipped.
|
|
6
|
+
* @property {(id: string, completed: boolean) => void} setCompleted - Persist (or clear) completion for `id`.
|
|
7
|
+
*/
|
|
8
|
+
export interface OnboardingStorage {
|
|
9
|
+
isCompleted: (id: string) => boolean;
|
|
10
|
+
setCompleted: (id: string, completed: boolean) => void;
|
|
11
|
+
}
|
|
12
|
+
export declare const localStorageOnboarding: OnboardingStorage;
|
|
13
|
+
export declare const memoryOnboarding: OnboardingStorage;
|
|
14
|
+
/**
|
|
15
|
+
* Options for {@link useOnboarding}.
|
|
16
|
+
*
|
|
17
|
+
* @property {string} id - Stable flow identifier; used as the persistence key.
|
|
18
|
+
* @property {number} total - Number of steps in the flow.
|
|
19
|
+
* @property {boolean} [run] - Controlled active state. Omit to let the hook manage start/stop internally.
|
|
20
|
+
* @property {boolean} [autoStart] - In uncontrolled mode, start on mount when the flow is not yet completed. Default `true`.
|
|
21
|
+
* @property {boolean} [persist] - Persist completion so a finished/skipped flow does not auto-restart. Default `true`.
|
|
22
|
+
* @property {OnboardingStorage} [storage] - Persistence adapter. Default `localStorageOnboarding`.
|
|
23
|
+
* @property {number} [startDelay] - Delay (ms) before auto-start, e.g. to let the page settle. Default `0`.
|
|
24
|
+
*/
|
|
25
|
+
export interface UseOnboardingOptions {
|
|
26
|
+
id: string;
|
|
27
|
+
total: number;
|
|
28
|
+
run?: boolean;
|
|
29
|
+
autoStart?: boolean;
|
|
30
|
+
persist?: boolean;
|
|
31
|
+
storage?: OnboardingStorage;
|
|
32
|
+
startDelay?: number;
|
|
33
|
+
onStart?: () => void;
|
|
34
|
+
onStepChange?: (index: number, previous: number) => void;
|
|
35
|
+
onSkip?: (index: number) => void;
|
|
36
|
+
onFinish?: () => void;
|
|
37
|
+
onStop?: () => void;
|
|
38
|
+
}
|
|
39
|
+
/** Imperative control surface + state returned by {@link useOnboarding}. */
|
|
40
|
+
export interface UseOnboardingReturn {
|
|
41
|
+
active: boolean;
|
|
42
|
+
index: number;
|
|
43
|
+
total: number;
|
|
44
|
+
isFirst: boolean;
|
|
45
|
+
isLast: boolean;
|
|
46
|
+
completed: boolean;
|
|
47
|
+
start: () => void;
|
|
48
|
+
stop: () => void;
|
|
49
|
+
next: () => void;
|
|
50
|
+
prev: () => void;
|
|
51
|
+
goTo: (index: number) => void;
|
|
52
|
+
skip: () => void;
|
|
53
|
+
finish: () => void;
|
|
54
|
+
reset: () => void;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Headless onboarding/tour state machine: drives a flow by `id`, tracks the
|
|
58
|
+
* current step, persists completion, and supports controlled or uncontrolled use.
|
|
59
|
+
*
|
|
60
|
+
* Pair with the `Onboarding` component for ready-made coachmarks, or render your
|
|
61
|
+
* own UI from the returned state for a fully custom flow.
|
|
62
|
+
*
|
|
63
|
+
* @param {UseOnboardingOptions} options
|
|
64
|
+
* @returns {UseOnboardingReturn} State (`active`, `index`, `isFirst`, …) and controls (`next`, `prev`, `skip`, `finish`, `reset`, …).
|
|
65
|
+
*/
|
|
66
|
+
export declare function useOnboarding({ id, total, run, autoStart, persist, storage, startDelay, onStart, onStepChange, onSkip, onFinish, onStop, }: UseOnboardingOptions): UseOnboardingReturn;
|
package/dist/hooks/index.cjs.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react");function t(t,n,r,o){const u=e.useRef(n);e.useEffect((()=>{u.current=n}),[n]),e.useEffect((()=>{const e=function(e){return void 0===e?"undefined"!=typeof window?window:null:null===e?null:null!=(t=e)&&"object"==typeof t&&"current"in t?e.current:e;var t}(r);if(null==e)return;const n=e=>u.current(e);return e.addEventListener(t,n,o),()=>{e.removeEventListener(t,n,o)}}),[t,r,o])}const n=["a[href]","button:not([disabled])","input:not([disabled])","textarea:not([disabled])","select:not([disabled])",'[tabindex]:not([tabindex="-1"])'].join(", ");const r=["mousemove","mousedown","keydown","touchstart","scroll","wheel"];function o(e,t){return"string"==typeof t?e===t:t.includes(e)}const u=400,s=8;const c=e.createContext(void 0),
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("react");function t(t,n,r,o){const u=e.useRef(n);e.useEffect((()=>{u.current=n}),[n]),e.useEffect((()=>{const e=function(e){return void 0===e?"undefined"!=typeof window?window:null:null===e?null:null!=(t=e)&&"object"==typeof t&&"current"in t?e.current:e;var t}(r);if(null==e)return;const n=e=>u.current(e);return e.addEventListener(t,n,o),()=>{e.removeEventListener(t,n,o)}}),[t,r,o])}const n=["a[href]","button:not([disabled])","input:not([disabled])","textarea:not([disabled])","select:not([disabled])",'[tabindex]:not([tabindex="-1"])'].join(", ");const r=["mousemove","mousedown","keydown","touchstart","scroll","wheel"];function o(e,t){return"string"==typeof t?e===t:t.includes(e)}const u=400,s=8;const c="h2o-onboarding:",i={isCompleted:e=>{try{return"undefined"!=typeof window&&"1"===window.localStorage.getItem(c+e)}catch(e){return!1}},setCompleted:(e,t)=>{try{if("undefined"==typeof window)return;t?window.localStorage.setItem(c+e,"1"):window.localStorage.removeItem(c+e)}catch(e){}}};const l=e.createContext(void 0),a=()=>"undefined"==typeof window?{x:0,y:0}:{x:window.scrollX,y:window.scrollY};const d=()=>"undefined"==typeof window?{width:0,height:0}:{width:window.innerWidth,height:window.innerHeight};exports.ToastContext=l,exports.localStorageOnboarding=i,exports.memoryOnboarding={isCompleted:()=>!1,setCompleted:()=>{}},exports.useClickOutside=function(t,n,r="mousedown"){e.useEffect((()=>{const e=e=>{const r=e.target,o=t.current;o&&!o.contains(r)&&n(e)};return document.addEventListener(r,e),document.addEventListener("touchstart",e),()=>{document.removeEventListener(r,e),document.removeEventListener("touchstart",e)}}),[t,n,r])},exports.useCopyToClipboard=function(t=2e3){const[n,r]=e.useState({copied:!1,error:null}),o=e.useRef(null);e.useEffect((()=>()=>{null!==o.current&&clearTimeout(o.current)}),[]);const u=e.useCallback((async e=>{var n;if(null!==o.current&&(clearTimeout(o.current),o.current=null),"undefined"==typeof navigator||!(null===(n=navigator.clipboard)||void 0===n?void 0:n.writeText)){const e=new Error("Clipboard API is not available");return r({copied:!1,error:e}),!1}try{return await navigator.clipboard.writeText(e),r({copied:!0,error:null}),t>0&&(o.current=setTimeout((()=>{r((e=>Object.assign(Object.assign({},e),{copied:!1}))),o.current=null}),t)),!0}catch(e){const t=e instanceof Error?e:new Error(String(e));return r({copied:!1,error:t}),!1}}),[t]);return Object.assign(Object.assign({},n),{copy:u})},exports.useDebounce=function(t,n){const[r,o]=e.useState(t);return e.useEffect((()=>{const e=window.setTimeout((()=>o(t)),n);return()=>window.clearTimeout(e)}),[t,n]),r},exports.useDropdownPosition=function(t,n,r="auto",o=320){const[u,s]=e.useState("bottom");return e.useEffect((()=>{if("auto"!==r)return void s(r);if(!n)return;const e=()=>{if(!t.current)return;const e=t.current.getBoundingClientRect(),n=window.innerHeight-e.bottom,r=e.top;s(n>=o||n>=r?"bottom":"top")};return document.addEventListener("scroll",e,!0),e(),()=>{document.removeEventListener("scroll",e,!0)}}),[n,r,t,o]),u},exports.useEventListener=t,exports.useEventListner=t,exports.useFocusTrap=function(t,r){e.useEffect((()=>{if(!r||!t.current)return;const e=t.current,o=()=>Array.from(e.querySelectorAll(n)),u=o();u.length>0&&u[0].focus();const s=t=>{if("Tab"!==t.key)return;const n=o();if(0===n.length)return;const r=n[0],u=n[n.length-1],s=document.activeElement;t.shiftKey?s!==r&&e.contains(s)||(t.preventDefault(),u.focus()):s!==u&&e.contains(s)||(t.preventDefault(),r.focus())};return document.addEventListener("keydown",s),()=>document.removeEventListener("keydown",s)}),[t,r])},exports.useHover=function(){const[t,n]=e.useState(!1),[r,o]=e.useState(null);return e.useEffect((()=>{if(!r)return;const e=()=>n(!0),t=()=>n(!1);return r.addEventListener("mouseenter",e),r.addEventListener("mouseleave",t),()=>{r.removeEventListener("mouseenter",e),r.removeEventListener("mouseleave",t)}}),[r]),[t,o]},exports.useIdle=function(t,n={}){var o;const u=null!==(o=n.events)&&void 0!==o?o:r,s=u.join(),[c,i]=e.useState(!1),l=e.useRef(null),a=e.useCallback((()=>{null!==l.current&&clearTimeout(l.current),i(!1),l.current=setTimeout((()=>i(!0)),t)}),[t]);return e.useEffect((()=>{if("undefined"==typeof window)return;const e=u;a();for(const t of e)window.addEventListener(t,a,{passive:!0});return()=>{for(const t of e)window.removeEventListener(t,a);null!==l.current&&clearTimeout(l.current)}}),[t,s,a]),c},exports.useInterval=function(t,n){const r=e.useRef(t);e.useEffect((()=>{r.current=t}),[t]),e.useEffect((()=>{if(null===n)return;const e=window.setInterval((()=>r.current()),n);return()=>window.clearInterval(e)}),[n])},exports.useKeyPress=function(t,n){const[r,u]=e.useState(!1),s=e.useRef(n);e.useEffect((()=>{s.current=n}),[n]);const c="string"==typeof t?t:t.join("\0"),i=e.useRef(t);if(i.current=t,e.useEffect((()=>{const e=e=>{o(e.key,i.current)&&(s.current?s.current(e):u(!0))},t=e=>{o(e.key,i.current)&&(s.current||u(!1))};return window.addEventListener("keydown",e),window.addEventListener("keyup",t),()=>{window.removeEventListener("keydown",e),window.removeEventListener("keyup",t)}}),[c]),void 0===n)return r},exports.useLongPress=function(t,n={}){const{delay:r=u,moveThreshold:o=s,onClick:c}=n,i=e.useRef(null),l=e.useRef(null),a=e.useRef(!1),d=e.useCallback((()=>{null!==i.current&&(clearTimeout(i.current),i.current=null)}),[]),f=e.useCallback((e=>{0===e.button&&(a.current=!1,l.current={x:e.clientX,y:e.clientY,id:e.pointerId},d(),i.current=setTimeout((()=>{a.current=!0,t(e),i.current=null}),r))}),[d,r,t]),v=e.useCallback((e=>{if(null===l.current||e.pointerId!==l.current.id)return;const t=e.clientX-l.current.x,n=e.clientY-l.current.y;t*t+n*n>o*o&&(l.current=null,d())}),[d,o]),p=e.useCallback((e=>{null!==l.current&&e.pointerId===l.current.id&&(!a.current&&c&&c(e),l.current=null),d()}),[d,c]);return{onPointerDown:f,onPointerMove:v,onPointerUp:p,onPointerCancel:p,onPointerLeave:p}},exports.useMediaQuery=t=>{const[n,r]=e.useState(!1);return e.useEffect((()=>{if("undefined"==typeof window)return;const e=window.matchMedia(t);e.matches!==n&&r(e.matches);const o=()=>{r(e.matches)};return e.addEventListener("change",o),()=>e.removeEventListener("change",o)}),[n,t]),n},exports.useMouse=function(t=!0){const[n,r]=e.useState({x:0,y:0,hasMoved:!1});return e.useEffect((()=>{if(!t)return;if("undefined"==typeof window)return;const e=e=>{r({x:e.clientX,y:e.clientY,hasMoved:!0})};return window.addEventListener("pointermove",e,{passive:!0}),()=>{window.removeEventListener("pointermove",e)}}),[t]),n},exports.useOnboarding=function({id:t,total:n,run:r,autoStart:o=!0,persist:u=!0,storage:s=i,startDelay:c=0,onStart:l,onStepChange:a,onSkip:d,onFinish:f,onStop:v}){const p=void 0!==r,[w,m]=e.useState(!1),[E,h]=e.useState(0),[b,g]=e.useState((()=>!!u&&s.isCompleted(t))),y=p?r:w,S=e.useRef(0),x=e.useCallback((e=>{S.current=e,h(e)}),[]),C=e.useRef({onStart:l,onStepChange:a,onSkip:d,onFinish:f,onStop:v,storage:s,persist:u,id:t});C.current={onStart:l,onStepChange:a,onSkip:d,onFinish:f,onStop:v,storage:s,persist:u,id:t};const k=e.useCallback((e=>{const{persist:t,storage:n,id:r}=C.current;t&&n.setCompleted(r,e),g(e)}),[]),L=e.useCallback((()=>{var e,t;x(0),p||m(!0),null===(t=(e=C.current).onStart)||void 0===t||t.call(e)}),[p,x]),T=e.useCallback((()=>{var e,t;p||m(!1),null===(t=(e=C.current).onStop)||void 0===t||t.call(e)}),[p]),R=e.useCallback((e=>{var t,r;const o=Math.max(0,Math.min(e,n-1)),u=S.current;o!==u&&(x(o),null===(r=(t=C.current).onStepChange)||void 0===r||r.call(t,o,u))}),[n,x]),I=e.useCallback((()=>{var e,t,n,r;k(!0),p||m(!1),null===(t=(e=C.current).onFinish)||void 0===t||t.call(e),null===(r=(n=C.current).onStop)||void 0===r||r.call(n)}),[p,k]),P=e.useCallback((()=>{var e,t;const r=S.current;r>=n-1?I():(x(r+1),null===(t=(e=C.current).onStepChange)||void 0===t||t.call(e,r+1,r))}),[n,I,x]),j=e.useCallback((()=>{var e,t;const n=S.current;n<=0||(x(n-1),null===(t=(e=C.current).onStepChange)||void 0===t||t.call(e,n-1,n))}),[x]),M=e.useCallback((()=>{var e,t,n,r;k(!0),p||m(!1),null===(t=(e=C.current).onSkip)||void 0===t||t.call(e,S.current),null===(r=(n=C.current).onStop)||void 0===r||r.call(n)}),[p,k]),O=e.useCallback((()=>{k(!1)}),[k]),D=e.useRef(!1);e.useEffect((()=>{if(!p&&o&&!D.current&&!(u&&s.isCompleted(t)||n<=0)){if(D.current=!0,c>0){const e=setTimeout(L,c);return()=>clearTimeout(e)}L()}}),[p,o,u,t,n,c]);const F=e.useRef(y);return e.useEffect((()=>{y&&!F.current&&x(0),F.current=y}),[y,x]),{active:y,index:E,total:n,isFirst:E<=0,isLast:E>=n-1,completed:b,start:L,stop:T,next:P,prev:j,goTo:R,skip:M,finish:I,reset:O}},exports.useTimeout=function(t,n){const r=e.useRef(t);e.useEffect((()=>{r.current=t}),[t]),e.useEffect((()=>{if(null===n)return;const e=window.setTimeout((()=>r.current()),n);return()=>window.clearTimeout(e)}),[n])},exports.useToast=()=>{const t=e.useContext(l);if(!t)throw new Error("useToast must be used within a ToastProvider");return t},exports.useWindowScroll=function(){const[t,n]=e.useState(a);return e.useEffect((()=>{const e=()=>n(a);return e(),window.addEventListener("scroll",e,{passive:!0,capture:!0}),()=>window.removeEventListener("scroll",e,{capture:!0})}),[]),t},exports.useWindowSize=function(){const[t,n]=e.useState(d);return e.useEffect((()=>{const e=()=>n(d());return e(),window.addEventListener("resize",e,{passive:!0}),()=>window.removeEventListener("resize",e)}),[]),t};
|
|
2
2
|
//# sourceMappingURL=index.cjs.js.map
|
package/dist/hooks/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{useEffect as
|
|
1
|
+
import{useEffect as n,useState as e,useRef as t,useCallback as r,createContext as o,useContext as i}from"react";function u(e,t,r="mousedown"){n((()=>{const n=n=>{const r=n.target,o=e.current;o&&!o.contains(r)&&t(n)};return document.addEventListener(r,n),document.addEventListener("touchstart",n),()=>{document.removeEventListener(r,n),document.removeEventListener("touchstart",n)}}),[e,t,r])}function c(o=2e3){const[i,u]=e({copied:!1,error:null}),c=t(null);n((()=>()=>{null!==c.current&&clearTimeout(c.current)}),[]);const s=r((async n=>{var e;if(null!==c.current&&(clearTimeout(c.current),c.current=null),"undefined"==typeof navigator||!(null===(e=navigator.clipboard)||void 0===e?void 0:e.writeText)){const n=new Error("Clipboard API is not available");return u({copied:!1,error:n}),!1}try{return await navigator.clipboard.writeText(n),u({copied:!0,error:null}),o>0&&(c.current=setTimeout((()=>{u((n=>Object.assign(Object.assign({},n),{copied:!1}))),c.current=null}),o)),!0}catch(n){const e=n instanceof Error?n:new Error(String(n));return u({copied:!1,error:e}),!1}}),[o]);return Object.assign(Object.assign({},i),{copy:s})}function s(t,r){const[o,i]=e(t);return n((()=>{const n=window.setTimeout((()=>i(t)),r);return()=>window.clearTimeout(n)}),[t,r]),o}function l(e,r,o,i){const u=t(r);n((()=>{u.current=r}),[r]),n((()=>{const n=function(n){return void 0===n?"undefined"!=typeof window?window:null:null===n?null:null!=(e=n)&&"object"==typeof e&&"current"in e?n.current:n;var e}(o);if(null==n)return;const t=n=>u.current(n);return n.addEventListener(e,t,i),()=>{n.removeEventListener(e,t,i)}}),[e,o,i])}function d(t,r,o="auto",i=320){const[u,c]=e("bottom");return n((()=>{if("auto"!==o)return void c(o);if(!r)return;const n=()=>{if(!t.current)return;const n=t.current.getBoundingClientRect(),e=window.innerHeight-n.bottom,r=n.top;c(e>=i||e>=r?"bottom":"top")};return document.addEventListener("scroll",n,!0),n(),()=>{document.removeEventListener("scroll",n,!0)}}),[r,o,t,i]),u}const a=["a[href]","button:not([disabled])","input:not([disabled])","textarea:not([disabled])","select:not([disabled])",'[tabindex]:not([tabindex="-1"])'].join(", ");function v(e,t){n((()=>{if(!t||!e.current)return;const n=e.current,r=()=>Array.from(n.querySelectorAll(a)),o=r();o.length>0&&o[0].focus();const i=e=>{if("Tab"!==e.key)return;const t=r();if(0===t.length)return;const o=t[0],i=t[t.length-1],u=document.activeElement;e.shiftKey?u!==o&&n.contains(u)||(e.preventDefault(),i.focus()):u!==i&&n.contains(u)||(e.preventDefault(),o.focus())};return document.addEventListener("keydown",i),()=>document.removeEventListener("keydown",i)}),[e,t])}const w=["mousemove","mousedown","keydown","touchstart","scroll","wheel"];function f(o,i={}){var u;const c=null!==(u=i.events)&&void 0!==u?u:w,s=c.join(),[l,d]=e(!1),a=t(null),v=r((()=>{null!==a.current&&clearTimeout(a.current),d(!1),a.current=setTimeout((()=>d(!0)),o)}),[o]);return n((()=>{if("undefined"==typeof window)return;const n=c;v();for(const e of n)window.addEventListener(e,v,{passive:!0});return()=>{for(const e of n)window.removeEventListener(e,v);null!==a.current&&clearTimeout(a.current)}}),[o,s,v]),l}function m(e,r){const o=t(e);n((()=>{o.current=e}),[e]),n((()=>{if(null===r)return;const n=window.setInterval((()=>o.current()),r);return()=>window.clearInterval(n)}),[r])}function p(){const[t,r]=e(!1),[o,i]=e(null);return n((()=>{if(!o)return;const n=()=>r(!0),e=()=>r(!1);return o.addEventListener("mouseenter",n),o.addEventListener("mouseleave",e),()=>{o.removeEventListener("mouseenter",n),o.removeEventListener("mouseleave",e)}}),[o]),[t,i]}function h(n,e){return"string"==typeof e?n===e:e.includes(n)}function g(r,o){const[i,u]=e(!1),c=t(o);n((()=>{c.current=o}),[o]);const s="string"==typeof r?r:r.join("\0"),l=t(r);if(l.current=r,n((()=>{const n=n=>{h(n.key,l.current)&&(c.current?c.current(n):u(!0))},e=n=>{h(n.key,l.current)&&(c.current||u(!1))};return window.addEventListener("keydown",n),window.addEventListener("keyup",e),()=>{window.removeEventListener("keydown",n),window.removeEventListener("keyup",e)}}),[s]),void 0===o)return i}const y=400,E=8;function L(n,e={}){const{delay:o=y,moveThreshold:i=E,onClick:u}=e,c=t(null),s=t(null),l=t(!1),d=r((()=>{null!==c.current&&(clearTimeout(c.current),c.current=null)}),[]),a=r((e=>{0===e.button&&(l.current=!1,s.current={x:e.clientX,y:e.clientY,id:e.pointerId},d(),c.current=setTimeout((()=>{l.current=!0,n(e),c.current=null}),o))}),[d,o,n]),v=r((n=>{if(null===s.current||n.pointerId!==s.current.id)return;const e=n.clientX-s.current.x,t=n.clientY-s.current.y;e*e+t*t>i*i&&(s.current=null,d())}),[d,i]),w=r((n=>{null!==s.current&&n.pointerId===s.current.id&&(!l.current&&u&&u(n),s.current=null),d()}),[d,u]);return{onPointerDown:a,onPointerMove:v,onPointerUp:w,onPointerCancel:w,onPointerLeave:w}}const S=t=>{const[r,o]=e(!1);return n((()=>{if("undefined"==typeof window)return;const n=window.matchMedia(t);n.matches!==r&&o(n.matches);const e=()=>{o(n.matches)};return n.addEventListener("change",e),()=>n.removeEventListener("change",e)}),[r,t]),r};function b(t=!0){const[r,o]=e({x:0,y:0,hasMoved:!1});return n((()=>{if(!t)return;if("undefined"==typeof window)return;const n=n=>{o({x:n.clientX,y:n.clientY,hasMoved:!0})};return window.addEventListener("pointermove",n,{passive:!0}),()=>{window.removeEventListener("pointermove",n)}}),[t]),r}const T="h2o-onboarding:",C={isCompleted:n=>{try{return"undefined"!=typeof window&&"1"===window.localStorage.getItem(T+n)}catch(n){return!1}},setCompleted:(n,e)=>{try{if("undefined"==typeof window)return;e?window.localStorage.setItem(T+n,"1"):window.localStorage.removeItem(T+n)}catch(n){}}},k={isCompleted:()=>!1,setCompleted:()=>{}};function x({id:o,total:i,run:u,autoStart:c=!0,persist:s=!0,storage:l=C,startDelay:d=0,onStart:a,onStepChange:v,onSkip:w,onFinish:f,onStop:m}){const p=void 0!==u,[h,g]=e(!1),[y,E]=e(0),[L,S]=e((()=>!!s&&l.isCompleted(o))),b=p?u:h,T=t(0),k=r((n=>{T.current=n,E(n)}),[]),x=t({onStart:a,onStepChange:v,onSkip:w,onFinish:f,onStop:m,storage:l,persist:s,id:o});x.current={onStart:a,onStepChange:v,onSkip:w,onFinish:f,onStop:m,storage:l,persist:s,id:o};const I=r((n=>{const{persist:e,storage:t,id:r}=x.current;e&&t.setCompleted(r,n),S(n)}),[]),j=r((()=>{var n,e;k(0),p||g(!0),null===(e=(n=x.current).onStart)||void 0===e||e.call(n)}),[p,k]),P=r((()=>{var n,e;p||g(!1),null===(e=(n=x.current).onStop)||void 0===e||e.call(n)}),[p]),M=r((n=>{var e,t;const r=Math.max(0,Math.min(n,i-1)),o=T.current;r!==o&&(k(r),null===(t=(e=x.current).onStepChange)||void 0===t||t.call(e,r,o))}),[i,k]),F=r((()=>{var n,e,t,r;I(!0),p||g(!1),null===(e=(n=x.current).onFinish)||void 0===e||e.call(n),null===(r=(t=x.current).onStop)||void 0===r||r.call(t)}),[p,I]),D=r((()=>{var n,e;const t=T.current;t>=i-1?F():(k(t+1),null===(e=(n=x.current).onStepChange)||void 0===e||e.call(n,t+1,t))}),[i,F,k]),O=r((()=>{var n,e;const t=T.current;t<=0||(k(t-1),null===(e=(n=x.current).onStepChange)||void 0===e||e.call(n,t-1,t))}),[k]),X=r((()=>{var n,e,t,r;I(!0),p||g(!1),null===(e=(n=x.current).onSkip)||void 0===e||e.call(n,T.current),null===(r=(t=x.current).onStop)||void 0===r||r.call(t)}),[p,I]),Y=r((()=>{I(!1)}),[I]),A=t(!1);n((()=>{if(!p&&c&&!A.current&&!(s&&l.isCompleted(o)||i<=0)){if(A.current=!0,d>0){const n=setTimeout(j,d);return()=>clearTimeout(n)}j()}}),[p,c,s,o,i,d]);const z=t(b);return n((()=>{b&&!z.current&&k(0),z.current=b}),[b,k]),{active:b,index:y,total:i,isFirst:y<=0,isLast:y>=i-1,completed:L,start:j,stop:P,next:D,prev:O,goTo:M,skip:X,finish:F,reset:Y}}function I(e,r){const o=t(e);n((()=>{o.current=e}),[e]),n((()=>{if(null===r)return;const n=window.setTimeout((()=>o.current()),r);return()=>window.clearTimeout(n)}),[r])}const j=o(void 0),P=()=>{const n=i(j);if(!n)throw new Error("useToast must be used within a ToastProvider");return n},M=()=>"undefined"==typeof window?{x:0,y:0}:{x:window.scrollX,y:window.scrollY};function F(){const[t,r]=e(M);return n((()=>{const n=()=>r(M);return n(),window.addEventListener("scroll",n,{passive:!0,capture:!0}),()=>window.removeEventListener("scroll",n,{capture:!0})}),[]),t}const D=()=>"undefined"==typeof window?{width:0,height:0}:{width:window.innerWidth,height:window.innerHeight};function O(){const[t,r]=e(D);return n((()=>{const n=()=>r(D());return n(),window.addEventListener("resize",n,{passive:!0}),()=>window.removeEventListener("resize",n)}),[]),t}export{j as ToastContext,C as localStorageOnboarding,k as memoryOnboarding,u as useClickOutside,c as useCopyToClipboard,s as useDebounce,d as useDropdownPosition,l as useEventListener,l as useEventListner,v as useFocusTrap,p as useHover,f as useIdle,m as useInterval,g as useKeyPress,L as useLongPress,S as useMediaQuery,b as useMouse,x as useOnboarding,I as useTimeout,P as useToast,F as useWindowScroll,O as useWindowSize};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluggable persistence adapter for onboarding flows. Swap to back completion
|
|
3
|
+
* with cookies, a backend, or anything else by implementing these two methods.
|
|
4
|
+
*
|
|
5
|
+
* @property {(id: string) => boolean} isCompleted - Whether the flow `id` was already finished/skipped.
|
|
6
|
+
* @property {(id: string, completed: boolean) => void} setCompleted - Persist (or clear) completion for `id`.
|
|
7
|
+
*/
|
|
8
|
+
export interface OnboardingStorage {
|
|
9
|
+
isCompleted: (id: string) => boolean;
|
|
10
|
+
setCompleted: (id: string, completed: boolean) => void;
|
|
11
|
+
}
|
|
12
|
+
export declare const localStorageOnboarding: OnboardingStorage;
|
|
13
|
+
export declare const memoryOnboarding: OnboardingStorage;
|
|
14
|
+
/**
|
|
15
|
+
* Options for {@link useOnboarding}.
|
|
16
|
+
*
|
|
17
|
+
* @property {string} id - Stable flow identifier; used as the persistence key.
|
|
18
|
+
* @property {number} total - Number of steps in the flow.
|
|
19
|
+
* @property {boolean} [run] - Controlled active state. Omit to let the hook manage start/stop internally.
|
|
20
|
+
* @property {boolean} [autoStart] - In uncontrolled mode, start on mount when the flow is not yet completed. Default `true`.
|
|
21
|
+
* @property {boolean} [persist] - Persist completion so a finished/skipped flow does not auto-restart. Default `true`.
|
|
22
|
+
* @property {OnboardingStorage} [storage] - Persistence adapter. Default `localStorageOnboarding`.
|
|
23
|
+
* @property {number} [startDelay] - Delay (ms) before auto-start, e.g. to let the page settle. Default `0`.
|
|
24
|
+
*/
|
|
25
|
+
export interface UseOnboardingOptions {
|
|
26
|
+
id: string;
|
|
27
|
+
total: number;
|
|
28
|
+
run?: boolean;
|
|
29
|
+
autoStart?: boolean;
|
|
30
|
+
persist?: boolean;
|
|
31
|
+
storage?: OnboardingStorage;
|
|
32
|
+
startDelay?: number;
|
|
33
|
+
onStart?: () => void;
|
|
34
|
+
onStepChange?: (index: number, previous: number) => void;
|
|
35
|
+
onSkip?: (index: number) => void;
|
|
36
|
+
onFinish?: () => void;
|
|
37
|
+
onStop?: () => void;
|
|
38
|
+
}
|
|
39
|
+
/** Imperative control surface + state returned by {@link useOnboarding}. */
|
|
40
|
+
export interface UseOnboardingReturn {
|
|
41
|
+
active: boolean;
|
|
42
|
+
index: number;
|
|
43
|
+
total: number;
|
|
44
|
+
isFirst: boolean;
|
|
45
|
+
isLast: boolean;
|
|
46
|
+
completed: boolean;
|
|
47
|
+
start: () => void;
|
|
48
|
+
stop: () => void;
|
|
49
|
+
next: () => void;
|
|
50
|
+
prev: () => void;
|
|
51
|
+
goTo: (index: number) => void;
|
|
52
|
+
skip: () => void;
|
|
53
|
+
finish: () => void;
|
|
54
|
+
reset: () => void;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Headless onboarding/tour state machine: drives a flow by `id`, tracks the
|
|
58
|
+
* current step, persists completion, and supports controlled or uncontrolled use.
|
|
59
|
+
*
|
|
60
|
+
* Pair with the `Onboarding` component for ready-made coachmarks, or render your
|
|
61
|
+
* own UI from the returned state for a fully custom flow.
|
|
62
|
+
*
|
|
63
|
+
* @param {UseOnboardingOptions} options
|
|
64
|
+
* @returns {UseOnboardingReturn} State (`active`, `index`, `isFirst`, …) and controls (`next`, `prev`, `skip`, `finish`, `reset`, …).
|
|
65
|
+
*/
|
|
66
|
+
export declare function useOnboarding({ id, total, run, autoStart, persist, storage, startDelay, onStart, onStepChange, onSkip, onFinish, onStop, }: UseOnboardingOptions): UseOnboardingReturn;
|