auto-loading-skeleton 1.0.2 → 2.0.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/src/hooks.js ADDED
@@ -0,0 +1,161 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * hooks.js — V2
5
+ *
6
+ * useSkeleton — basic loading state manager
7
+ * useSkeletonDelay — avoids flash of skeleton for fast responses
8
+ * useSkeletonData — async data fetcher that integrates loading state
9
+ * useSkeletonTimeout — shows skeleton for a minimum duration to avoid flicker
10
+ */
11
+
12
+ var React = require('react');
13
+
14
+ /**
15
+ * useSkeleton(initialLoading, options)
16
+ * Returns { loading, setLoading, skeletonProps }
17
+ */
18
+ function useSkeleton(initialLoading, options) {
19
+ initialLoading = initialLoading !== undefined ? initialLoading : true;
20
+ options = options || {};
21
+ var state = React.useState(initialLoading);
22
+ var loading = state[0];
23
+ var setLoading = state[1];
24
+ return {
25
+ loading: loading,
26
+ setLoading: setLoading,
27
+ skeletonProps: Object.assign({ loading: loading }, options),
28
+ };
29
+ }
30
+
31
+ /**
32
+ * useSkeletonDelay(loading, delay)
33
+ *
34
+ * Prevents skeleton flash for responses faster than `delay` ms.
35
+ * The skeleton only appears if loading has been true for longer than `delay`.
36
+ *
37
+ * Example: delay=200 means quick responses never show a skeleton at all.
38
+ */
39
+ function useSkeletonDelay(loading, delay) {
40
+ delay = delay != null ? delay : 200;
41
+ var ref = React.useRef(null);
42
+ var state = React.useState(false);
43
+ var show = state[0];
44
+ var setShow = state[1];
45
+
46
+ React.useEffect(function() {
47
+ if (loading) {
48
+ ref.current = setTimeout(function() { setShow(true); }, delay);
49
+ } else {
50
+ if (ref.current) clearTimeout(ref.current);
51
+ setShow(false);
52
+ }
53
+ return function() { if (ref.current) clearTimeout(ref.current); };
54
+ }, [loading, delay]);
55
+
56
+ return show;
57
+ }
58
+
59
+ /**
60
+ * useSkeletonTimeout(loading, minDuration)
61
+ *
62
+ * Keeps the skeleton visible for at least `minDuration` ms even if data
63
+ * arrives early — avoids jarring instant transitions.
64
+ */
65
+ function useSkeletonTimeout(loading, minDuration) {
66
+ minDuration = minDuration != null ? minDuration : 500;
67
+ var startRef = React.useRef(null);
68
+ var state = React.useState(loading);
69
+ var show = state[0];
70
+ var setShow = state[1];
71
+
72
+ React.useEffect(function() {
73
+ if (loading) {
74
+ startRef.current = Date.now();
75
+ setShow(true);
76
+ } else {
77
+ var elapsed = Date.now() - (startRef.current || Date.now());
78
+ var remaining = Math.max(0, minDuration - elapsed);
79
+ var tid = setTimeout(function() { setShow(false); }, remaining);
80
+ return function() { clearTimeout(tid); };
81
+ }
82
+ }, [loading, minDuration]);
83
+
84
+ return show;
85
+ }
86
+
87
+ /**
88
+ * useSkeletonData(fetchFn, deps)
89
+ *
90
+ * Runs an async fetch function and returns { data, loading, error }.
91
+ * fetchFn should return a Promise.
92
+ *
93
+ * Example:
94
+ * const { data, loading } = useSkeletonData(() => fetch('/api/user').then(r => r.json()), [userId]);
95
+ */
96
+ function useSkeletonData(fetchFn, deps) {
97
+ deps = deps || [];
98
+ var dataState = React.useState(null);
99
+ var loadState = React.useState(true);
100
+ var errState = React.useState(null);
101
+ var data = dataState[0]; var setData = dataState[1];
102
+ var loading = loadState[0]; var setLoad = loadState[1];
103
+ var error = errState[0]; var setError = errState[1];
104
+ var cancelRef = React.useRef(false);
105
+
106
+ React.useEffect(function() {
107
+ cancelRef.current = false;
108
+ setLoad(true);
109
+ setError(null);
110
+ Promise.resolve(fetchFn()).then(function(result) {
111
+ if (!cancelRef.current) {
112
+ setData(result);
113
+ setLoad(false);
114
+ }
115
+ }).catch(function(err) {
116
+ if (!cancelRef.current) {
117
+ setError(err);
118
+ setLoad(false);
119
+ }
120
+ });
121
+ return function() { cancelRef.current = true; };
122
+ // eslint-disable-next-line react-hooks/exhaustive-deps
123
+ }, deps);
124
+
125
+ return { data: data, loading: loading, error: error };
126
+ }
127
+
128
+ /**
129
+ * withSkeleton(WrappedComponent, defaultOptions)
130
+ * Higher-order component version.
131
+ */
132
+ function withSkeleton(WrappedComponent, defaultOptions) {
133
+ defaultOptions = defaultOptions || {};
134
+ var name = WrappedComponent.displayName || WrappedComponent.name || 'Component';
135
+ var AutoSkeleton = require('./AutoSkeleton');
136
+
137
+ function WithSkeletonWrapper(props) {
138
+ var loading = props.loading !== undefined ? props.loading : false;
139
+ var animation = props.skeletonAnimation;
140
+ var theme = props.skeletonTheme;
141
+ var themeOverrides = props.skeletonThemeOverrides;
142
+ var count = props.skeletonCount;
143
+ var rest = Object.assign({}, props);
144
+ delete rest.loading;
145
+ delete rest.skeletonAnimation;
146
+ delete rest.skeletonTheme;
147
+ delete rest.skeletonThemeOverrides;
148
+ delete rest.skeletonCount;
149
+ return React.createElement(AutoSkeleton, {
150
+ loading: loading,
151
+ animation: animation || defaultOptions.animation || 'shimmer',
152
+ theme: theme || defaultOptions.theme || 'default',
153
+ themeOverrides:themeOverrides || defaultOptions.themeOverrides || {},
154
+ count: count || defaultOptions.count || 1,
155
+ }, React.createElement(WrappedComponent, rest));
156
+ }
157
+ WithSkeletonWrapper.displayName = 'WithSkeleton(' + name + ')';
158
+ return WithSkeletonWrapper;
159
+ }
160
+
161
+ module.exports = { useSkeleton, useSkeletonDelay, useSkeletonTimeout, useSkeletonData, withSkeleton };
package/src/index.d.ts CHANGED
@@ -1,51 +1,167 @@
1
- import { ReactNode, CSSProperties, FC } from 'react';
1
+ import { ReactNode, CSSProperties, FC, Context, ComponentType } from 'react';
2
2
 
3
- export type SkeletonAnimation = 'shimmer' | 'pulse' | 'wave' | 'none';
3
+ /* ── Animations ── */
4
+ export type SkeletonAnimation = 'shimmer' | 'pulse' | 'wave' | 'glow' | 'none';
4
5
 
5
- export interface SkeletonTheme {
6
- /** Base skeleton color. Default: #e2e8f0 */
7
- baseColor?: string;
8
- /** Highlight color for shimmer/wave. Default: #f8fafc */
9
- highlightColor?: string;
6
+ /* ── Theme presets ── */
7
+ export type SkeletonThemePreset = 'default' | 'dark' | 'minimal' | 'soft' | 'brand' | 'warm';
8
+
9
+ export interface SkeletonThemeOverrides {
10
+ baseColor?: string;
11
+ highlightColor?: string;
12
+ shimmerColor?: string; // alias for highlightColor
13
+ duration?: string;
14
+ borderRadius?: string;
15
+ [cssVar: string]: string | undefined;
16
+ }
17
+
18
+ /* ── Context ── */
19
+ export interface SkeletonContextValue {
20
+ animation: SkeletonAnimation;
21
+ theme: SkeletonThemePreset;
22
+ themeOverrides: SkeletonThemeOverrides;
23
+ fadeIn: boolean;
24
+ maxDepth: number;
25
+ detectRepeats: boolean;
10
26
  }
11
27
 
28
+ export declare const SkeletonContext: Context<SkeletonContextValue>;
29
+
30
+ export interface SkeletonProviderProps {
31
+ animation?: SkeletonAnimation;
32
+ theme?: SkeletonThemePreset;
33
+ themeOverrides?: SkeletonThemeOverrides;
34
+ fadeIn?: boolean;
35
+ maxDepth?: number;
36
+ detectRepeats?: boolean;
37
+ children: ReactNode;
38
+ }
39
+ export declare const SkeletonProvider: FC<SkeletonProviderProps>;
40
+
41
+ /* ── AutoSkeleton ── */
12
42
  export interface AutoSkeletonProps {
13
- /** Show skeleton when true, render real children when false */
43
+ /** Show skeleton when true */
14
44
  loading: boolean;
15
45
  children: ReactNode;
16
- /** Animation style. Default: 'shimmer' */
46
+ animation?: SkeletonAnimation;
47
+ theme?: SkeletonThemePreset;
48
+ themeOverrides?: SkeletonThemeOverrides;
49
+ /** Number of skeleton copies (for lists) */
50
+ count?: number;
51
+ /** Gap between count copies */
52
+ gap?: string;
53
+ /** Animate real content appearing with fade-in */
54
+ fadeIn?: boolean;
55
+ /** Custom ARIA label */
56
+ ariaLabel?: string;
57
+ /** Fired once when loading transitions to false */
58
+ onLoaded?: () => void;
59
+ /** Render prop: full manual skeleton override */
60
+ renderSkeleton?: (opts: { animation: SkeletonAnimation }) => ReactNode;
61
+ /** Max recursion depth for analyzer */
62
+ maxDepth?: number;
63
+ detectRepeats?: boolean;
64
+ className?: string;
65
+ style?: CSSProperties;
66
+ }
67
+ export declare const AutoSkeleton: FC<AutoSkeletonProps>;
68
+
69
+ /* ── Primitives ── */
70
+ export interface SkeletonBlockProps {
71
+ width?: string | number;
72
+ height?: string | number;
73
+ shape?: 'circle' | 'pill' | 'rounded';
17
74
  animation?: SkeletonAnimation;
18
- /** Repeat the skeleton pattern N times. Useful for lists. Default: 1 */
19
- repeat?: number;
20
- /** Theme overrides */
21
- theme?: SkeletonTheme;
22
- style?: CSSProperties;
23
75
  className?: string;
24
- /** Accessible label for the loading region. Default: 'Loading…' */
25
- ariaLabel?: string;
76
+ style?: CSSProperties;
77
+ }
78
+ export declare const SkeletonBlock: FC<SkeletonBlockProps>;
79
+
80
+ export interface SkeletonTextProps {
81
+ lines?: number;
82
+ lineHeight?: string;
83
+ gap?: string;
84
+ lastLineWidth?: string;
85
+ animation?: SkeletonAnimation;
86
+ style?: CSSProperties;
87
+ }
88
+ export declare const SkeletonText: FC<SkeletonTextProps>;
89
+
90
+ export interface SkeletonAvatarProps {
91
+ size?: string | number;
92
+ animation?: SkeletonAnimation;
93
+ style?: CSSProperties;
26
94
  }
95
+ export declare const SkeletonAvatar: FC<SkeletonAvatarProps>;
27
96
 
28
- export type SkeletonItemType = 'text' | 'image' | 'avatar' | 'button' | 'input';
97
+ export interface SkeletonImageProps {
98
+ height?: string | number;
99
+ animation?: SkeletonAnimation;
100
+ style?: CSSProperties;
101
+ }
102
+ export declare const SkeletonImage: FC<SkeletonImageProps>;
29
103
 
30
- export interface SkeletonItemProps {
31
- type?: SkeletonItemType;
32
- lines?: number;
33
- width?: number | string;
34
- height?: number | string;
35
- /** For avatar type: shorthand for equal width and height */
36
- size?: number;
104
+ export declare const SkeletonBadge: FC<{ width?: string; animation?: SkeletonAnimation; style?: CSSProperties }>;
105
+ export declare const SkeletonButton: FC<{ width?: string; height?: string; animation?: SkeletonAnimation; style?: CSSProperties }>;
106
+ export declare const SkeletonInput: FC<{ height?: string; animation?: SkeletonAnimation; style?: CSSProperties }>;
107
+
108
+ export interface SkeletonListProps {
109
+ count?: number;
110
+ gap?: string;
111
+ rowHeight?: string;
112
+ animation?: SkeletonAnimation;
113
+ renderItem?: (opts: { animation: SkeletonAnimation; index: number }) => ReactNode;
114
+ }
115
+ export declare const SkeletonList: FC<SkeletonListProps>;
116
+
117
+ export interface SkeletonFormProps {
118
+ fields?: number;
37
119
  animation?: SkeletonAnimation;
38
- theme?: SkeletonTheme;
39
- style?: CSSProperties;
120
+ style?: CSSProperties;
121
+ }
122
+ export declare const SkeletonForm: FC<SkeletonFormProps>;
123
+
124
+ export interface SkeletonCardProps {
125
+ image?: boolean;
126
+ avatar?: boolean;
127
+ footer?: boolean;
128
+ lines?: number;
129
+ imageHeight?: string;
130
+ animation?: SkeletonAnimation;
131
+ style?: CSSProperties;
40
132
  }
133
+ export declare const SkeletonCard: FC<SkeletonCardProps>;
41
134
 
42
- /** Main wrapper component — auto-generates a skeleton from its children */
43
- export const AutoSkeleton: FC<AutoSkeletonProps>;
135
+ /* ── Hooks ── */
136
+ export interface UseSkeletonReturn {
137
+ loading: boolean;
138
+ setLoading: (v: boolean) => void;
139
+ skeletonProps: { loading: boolean } & Partial<AutoSkeletonProps>;
140
+ }
141
+ export declare function useSkeleton(initialLoading?: boolean, options?: Partial<AutoSkeletonProps>): UseSkeletonReturn;
142
+
143
+ /** Prevents flash of skeleton for fast responses (< delay ms). */
144
+ export declare function useSkeletonDelay(loading: boolean, delay?: number): boolean;
145
+
146
+ /** Keeps skeleton visible for at least minDuration ms. */
147
+ export declare function useSkeletonTimeout(loading: boolean, minDuration?: number): boolean;
148
+
149
+ export interface UseSkeletonDataReturn<T> {
150
+ data: T | null;
151
+ loading: boolean;
152
+ error: Error | null;
153
+ }
154
+ export declare function useSkeletonData<T>(fetchFn: () => Promise<T>, deps?: unknown[]): UseSkeletonDataReturn<T>;
44
155
 
45
- /** Primitive building block for manually-crafted skeletons */
46
- export const SkeletonItem: FC<SkeletonItemProps>;
156
+ export declare function withSkeleton<P extends object>(
157
+ Component: ComponentType<P>,
158
+ defaultOptions?: Partial<AutoSkeletonProps>
159
+ ): FC<P & { loading?: boolean; skeletonAnimation?: SkeletonAnimation; skeletonTheme?: SkeletonThemePreset; skeletonCount?: number }>;
47
160
 
48
- /** Inject global CSS (called automatically; export for SSR use) */
49
- export function injectStyles(): void;
161
+ /* ── Low-level / Advanced ── */
162
+ export declare function analyzeElement(element: ReactNode, depth?: number, opts?: object): object | null;
163
+ export declare function renderNode(descriptor: object | null, options?: object): ReactNode;
164
+ export declare function injectStyles(): void;
165
+ export declare function clearCache(): void;
50
166
 
51
- export default AutoSkeleton;
167
+ export declare const THEMES: Record<SkeletonThemePreset, Record<string, string>>;
package/src/index.js CHANGED
@@ -1,102 +1,52 @@
1
- const React = require('react');
2
- const { analyzeElement } = require('./analyzer');
3
- const { renderNode } = require('./renderer');
4
- const { injectStyles, blockClass, buildThemeVars } = require('./styles');
5
-
6
- function AutoSkeleton(props) {
7
- var loading = props.loading;
8
- var animation = props.animation || 'shimmer';
9
- var theme = props.theme || {};
10
- var count = props.count || 1;
11
- var children = props.children;
12
- var className = props.className || '';
13
- var style = props.style || {};
14
-
15
- React.useEffect(function() { injectStyles(); }, []);
16
- if (typeof document !== 'undefined') injectStyles();
17
-
18
- if (!loading) return children;
19
-
20
- var cssVarStyle = {};
21
- var varMap = { baseColor: '--ask-base-color', shimmerColor: '--ask-shimmer-color', duration: '--ask-duration', borderRadius: '--ask-border-radius' };
22
- Object.entries(theme).forEach(function(pair) { if (varMap[pair[0]]) cssVarStyle[varMap[pair[0]]] = pair[1]; });
23
-
24
- var descriptor = analyzeElement(children);
25
- var skeletonEl = renderNode(descriptor, { animation: animation });
26
-
27
- var items = [];
28
- for (var i = 0; i < Math.max(1, count); i++) {
29
- items.push(React.createElement('div', { key: i, style: i < count - 1 ? { marginBottom: '16px' } : {} }, skeletonEl));
30
- }
31
-
32
- return React.createElement('div', {
33
- className: 'ask-wrapper' + (className ? ' ' + className : ''),
34
- style: Object.assign({}, style, cssVarStyle),
35
- 'aria-busy': 'true',
36
- 'aria-label': 'Loading\u2026',
37
- }, items);
38
- }
39
-
40
- function SkeletonBlock(props) {
41
- var width = props.width || '100%';
42
- var height = props.height || '16px';
43
- var shape = props.shape;
44
- var animation = props.animation || 'shimmer';
45
- var style = props.style || {};
46
- var className = props.className || '';
47
- React.useEffect(function() { injectStyles(); }, []);
48
- if (typeof document !== 'undefined') injectStyles();
49
- return React.createElement('span', {
50
- className: blockClass(animation, shape) + (className ? ' ' + className : ''),
51
- style: Object.assign({ width: width, height: height, display: 'block' }, style),
52
- 'aria-hidden': 'true',
53
- });
54
- }
55
-
56
- function SkeletonText(props) {
57
- var lines = props.lines || 3;
58
- var animation = props.animation || 'shimmer';
59
- var lastLineWidth = props.lastLineWidth || '60%';
60
- var lineHeight = props.lineHeight || '1em';
61
- var gap = props.gap || '8px';
62
- var style = props.style || {};
63
- React.useEffect(function() { injectStyles(); }, []);
64
- if (typeof document !== 'undefined') injectStyles();
65
- var lineEls = [];
66
- for (var i = 0; i < lines; i++) {
67
- lineEls.push(React.createElement('span', { key: i, className: blockClass(animation), style: { display: 'block', width: i === lines - 1 ? lastLineWidth : '100%', height: lineHeight, marginBottom: i < lines - 1 ? gap : 0 }, 'aria-hidden': 'true' }));
68
- }
69
- return React.createElement('div', { style: style, 'aria-hidden': 'true' }, lineEls);
70
- }
71
-
72
- function SkeletonAvatar(props) {
73
- var size = props.size || '40px';
74
- return React.createElement(SkeletonBlock, { width: size, height: size, shape: 'circle', animation: props.animation || 'shimmer', style: props.style || {} });
75
- }
76
-
77
- function useSkeleton(initialLoading, options) {
78
- initialLoading = initialLoading !== undefined ? initialLoading : true;
79
- options = options || {};
80
- var state = React.useState(initialLoading);
81
- var loading = state[0];
82
- var setLoading = state[1];
83
- return { loading: loading, setLoading: setLoading, skeletonProps: Object.assign({ loading: loading }, options) };
84
- }
85
-
86
- function withSkeleton(WrappedComponent, defaultOptions) {
87
- defaultOptions = defaultOptions || {};
88
- var displayName = WrappedComponent.displayName || WrappedComponent.name || 'Component';
89
- function WithSkeletonWrapper(props) {
90
- var loading = props.loading !== undefined ? props.loading : false;
91
- var skeletonAnimation = props.skeletonAnimation;
92
- var skeletonTheme = props.skeletonTheme;
93
- var skeletonCount = props.skeletonCount;
94
- var rest = Object.assign({}, props);
95
- delete rest.loading; delete rest.skeletonAnimation; delete rest.skeletonTheme; delete rest.skeletonCount;
96
- return React.createElement(AutoSkeleton, { loading: loading, animation: skeletonAnimation || defaultOptions.animation || 'shimmer', theme: skeletonTheme || defaultOptions.theme || {}, count: skeletonCount || defaultOptions.count || 1 }, React.createElement(WrappedComponent, rest));
97
- }
98
- WithSkeletonWrapper.displayName = 'WithSkeleton(' + displayName + ')';
99
- return WithSkeletonWrapper;
100
- }
101
-
102
- module.exports = { AutoSkeleton, SkeletonBlock, SkeletonText, SkeletonAvatar, useSkeleton, withSkeleton, analyzeElement, renderNode, injectStyles };
1
+ 'use strict';
2
+
3
+ /**
4
+ * auto-loading-skeleton v2
5
+ * Main entry point.
6
+ */
7
+
8
+ var AutoSkeleton = require('./AutoSkeleton');
9
+ var primitives = require('./primitives');
10
+ var hooks = require('./hooks');
11
+ var context = require('./context');
12
+ var analyzer = require('./analyzer');
13
+ var renderer = require('./renderer');
14
+ var styles = require('./styles');
15
+ var cache = require('./cache');
16
+
17
+ module.exports = {
18
+ // Main component
19
+ AutoSkeleton,
20
+
21
+ // Provider
22
+ SkeletonProvider: context.SkeletonProvider,
23
+ SkeletonContext: context.SkeletonContext,
24
+
25
+ // Primitives
26
+ SkeletonBlock: primitives.SkeletonBlock,
27
+ SkeletonText: primitives.SkeletonText,
28
+ SkeletonAvatar: primitives.SkeletonAvatar,
29
+ SkeletonImage: primitives.SkeletonImage,
30
+ SkeletonBadge: primitives.SkeletonBadge,
31
+ SkeletonButton: primitives.SkeletonButton,
32
+ SkeletonInput: primitives.SkeletonInput,
33
+ SkeletonList: primitives.SkeletonList,
34
+ SkeletonForm: primitives.SkeletonForm,
35
+ SkeletonCard: primitives.SkeletonCard,
36
+
37
+ // Hooks
38
+ useSkeleton: hooks.useSkeleton,
39
+ useSkeletonDelay: hooks.useSkeletonDelay,
40
+ useSkeletonTimeout: hooks.useSkeletonTimeout,
41
+ useSkeletonData: hooks.useSkeletonData,
42
+ withSkeleton: hooks.withSkeleton,
43
+
44
+ // Low-level (for advanced use)
45
+ analyzeElement: analyzer.analyzeElement,
46
+ renderNode: renderer.renderNode,
47
+ injectStyles: styles.injectStyles,
48
+ clearCache: cache.clear,
49
+
50
+ // Theme presets reference
51
+ THEMES: styles.THEMES,
52
+ };