instaskeleton 0.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/README.md ADDED
@@ -0,0 +1,298 @@
1
+ # instaskeleton
2
+
3
+ [![npm version](https://img.shields.io/npm/v/instaskeleton.svg)](https://www.npmjs.com/package/instaskeleton)
4
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/instaskeleton)](https://bundlephobia.com/package/instaskeleton)
5
+ [![license](https://img.shields.io/npm/l/instaskeleton.svg)](https://github.com/LittleBoy9/instaskeleton/blob/main/LICENSE)
6
+
7
+ Ultra-light React skeleton loader with **zero DOM scanning**, **zero layout measurement**, and **zero lag**.
8
+
9
+ ```
10
+ ~1.2 KB gzipped (JS) + ~0.45 KB (CSS) = ~1.65 KB total
11
+ ```
12
+
13
+ ## Why instaskeleton?
14
+
15
+ Most skeleton libraries either:
16
+ - Require separate skeleton components for every UI element
17
+ - Scan the DOM at runtime to generate placeholders (slow, causes layout shifts)
18
+
19
+ **instaskeleton** takes a different approach:
20
+ - **Zero DOM scanning** — no runtime layout measurement
21
+ - **Zero work when not loading** — early exit skips all computation
22
+ - **Infer from JSX** — automatic skeleton generation from your React tree
23
+ - **Manual schema** — pixel-perfect control when you need it
24
+ - **LRU cache** — repeated renders are instant (100-entry limit prevents memory leaks)
25
+ - **Reduced motion support** — respects `prefers-reduced-motion`
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ npm install instaskeleton
31
+ ```
32
+
33
+ Import the bundled styles once:
34
+
35
+ ```tsx
36
+ import 'instaskeleton/styles.css';
37
+ ```
38
+
39
+ ## Quick Start
40
+
41
+ Use inference when you want the fastest setup:
42
+
43
+ ```tsx
44
+ import { InstaSkeleton } from 'instaskeleton';
45
+ import 'instaskeleton/styles.css';
46
+
47
+ type Product = {
48
+ title: string;
49
+ price: string;
50
+ };
51
+
52
+ function ProductCard({
53
+ loading,
54
+ product
55
+ }: {
56
+ loading: boolean;
57
+ product: Product;
58
+ }) {
59
+ return (
60
+ <InstaSkeleton loading={loading} infer cacheKey="product-card-v1">
61
+ <article>
62
+ <img src="/cover.png" alt="" />
63
+ <h3>{product.title}</h3>
64
+ <p>{product.price}</p>
65
+ <button>Add to cart</button>
66
+ </article>
67
+ </InstaSkeleton>
68
+ );
69
+ }
70
+ ```
71
+
72
+ ## Manual Schema
73
+
74
+ Use manual schema when you want the skeleton to resemble the inner component structure more closely.
75
+
76
+ ```tsx
77
+ import { InstaSkeleton, type SkeletonNode } from 'instaskeleton';
78
+
79
+ const articleCardSchema: SkeletonNode[] = [
80
+ { type: 'rect', height: '12rem', radius: '1rem' },
81
+ { type: 'line', width: '68%' },
82
+ { type: 'line', width: '92%' },
83
+ { type: 'line', width: '40%' }
84
+ ];
85
+
86
+ function ArticleCard({ loading }: { loading: boolean }) {
87
+ return (
88
+ <InstaSkeleton loading={loading} schema={articleCardSchema} infer={false}>
89
+ <article>
90
+ <img src="/hero.png" alt="" />
91
+ <h3>How we removed DOM scanning from loading states</h3>
92
+ <p>Manual schema mirrors the actual card anatomy.</p>
93
+ <small>5 min read</small>
94
+ </article>
95
+ </InstaSkeleton>
96
+ );
97
+ }
98
+ ```
99
+
100
+ ## HOC Pattern
101
+
102
+ Use `withInstaSkeleton` when you want to preconfigure loading behavior for a reusable component.
103
+
104
+ ```tsx
105
+ import { withInstaSkeleton } from 'instaskeleton';
106
+
107
+ function StatCard({ label, value }: { label: string; value: string }) {
108
+ return (
109
+ <div>
110
+ <span>{label}</span>
111
+ <strong>{value}</strong>
112
+ </div>
113
+ );
114
+ }
115
+
116
+ const StatCardWithSkeleton = withInstaSkeleton(StatCard, {
117
+ skeleton: [
118
+ { type: 'line', width: '35%', height: '0.75rem' },
119
+ { type: 'rect', height: '3rem', radius: '0.75rem' }
120
+ ],
121
+ infer: false,
122
+ cacheKey: 'stat-card'
123
+ });
124
+
125
+ <StatCardWithSkeleton loading={isLoading} label="Downloads" value="12.4k" />;
126
+ ```
127
+
128
+ ## API Reference
129
+
130
+ ### `<InstaSkeleton>`
131
+
132
+ The main component for wrapping content with skeleton loading states.
133
+
134
+ | Prop | Type | Default | Description |
135
+ |------|------|---------|-------------|
136
+ | `loading` | `boolean` | required | Show skeleton when `true`, children when `false` |
137
+ | `children` | `ReactNode` | required | Content to display when not loading |
138
+ | `schema` | `SkeletonNode \| SkeletonNode[]` | `undefined` | Manual skeleton definition |
139
+ | `infer` | `boolean` | `true` | Auto-generate skeleton from children |
140
+ | `cacheKey` | `string` | `undefined` | Cache inferred schema for reuse |
141
+ | `className` | `string` | `undefined` | Additional CSS class for the skeleton root |
142
+ | `animation` | `'shimmer' \| 'pulse' \| 'none'` | `'shimmer'` | Animation style |
143
+ | `inferOptions` | `InferOptions` | `{}` | Control inference behavior |
144
+
145
+ ### `InferOptions`
146
+
147
+ Fine-tune the inference algorithm:
148
+
149
+ | Option | Type | Default | Description |
150
+ |--------|------|---------|-------------|
151
+ | `maxDepth` | `number` | `6` | Maximum JSX tree depth to traverse |
152
+ | `maxNodes` | `number` | `120` | Maximum nodes to process |
153
+ | `textLineHeight` | `string \| number` | `'0.95rem'` | Height for text line skeletons |
154
+
155
+ ### `withInstaSkeleton(Component, options)`
156
+
157
+ HOC to create a skeleton-wrapped version of any component.
158
+
159
+ ```tsx
160
+ const WrappedComponent = withInstaSkeleton(MyComponent, {
161
+ skeleton: [...], // Manual schema
162
+ infer: false, // Disable inference
163
+ cacheKey: 'my-comp', // Cache key
164
+ className: 'custom', // Root class
165
+ animation: 'pulse', // Animation style
166
+ });
167
+
168
+ // Adds `loading` prop to the component
169
+ <WrappedComponent loading={isLoading} {...props} />
170
+ ```
171
+
172
+ ### `clearInstaSkeletonCache(key?)`
173
+
174
+ Clear cached schemas:
175
+
176
+ ```tsx
177
+ import { clearInstaSkeletonCache } from 'instaskeleton';
178
+
179
+ // Clear specific cache entry
180
+ clearInstaSkeletonCache('product-card');
181
+
182
+ // Clear all cached schemas
183
+ clearInstaSkeletonCache();
184
+ ```
185
+
186
+ ### `SkeletonNode`
187
+
188
+ Supported node types:
189
+
190
+ #### `line` — Text placeholder
191
+ ```tsx
192
+ { type: 'line', width: '80%', height: '1rem' }
193
+ ```
194
+
195
+ #### `rect` — Block placeholder (images, buttons, cards)
196
+ ```tsx
197
+ { type: 'rect', width: '100%', height: '8rem', radius: '1rem' }
198
+ ```
199
+
200
+ #### `circle` — Avatar or icon placeholder
201
+ ```tsx
202
+ { type: 'circle', width: '3rem', height: '3rem' }
203
+ ```
204
+
205
+ #### `group` — Container for nested nodes
206
+ ```tsx
207
+ {
208
+ type: 'group',
209
+ gap: '0.75rem',
210
+ children: [
211
+ { type: 'circle', width: '3rem', height: '3rem' },
212
+ { type: 'line', width: '60%' },
213
+ { type: 'line', width: '40%' },
214
+ ]
215
+ }
216
+ ```
217
+
218
+ ## Performance
219
+
220
+ ### Zero Work When Not Loading
221
+
222
+ ```tsx
223
+ // When loading=false, InstaSkeleton returns children immediately
224
+ // No inference, no schema processing, no DOM overhead
225
+ if (!loading) return <>{children}</>;
226
+ ```
227
+
228
+ ### LRU Cache with Size Limit
229
+
230
+ Schemas are cached with a 100-entry limit to prevent memory leaks in long-running SPAs.
231
+
232
+ ### GPU-Accelerated Animations
233
+
234
+ Shimmer animation uses `will-change: transform` for 60fps performance.
235
+
236
+ ### Reduced Motion Support
237
+
238
+ Animations are disabled automatically when `prefers-reduced-motion: reduce` is set.
239
+
240
+ ## Choosing The Right Mode
241
+
242
+ | Use Case | Mode | Why |
243
+ |----------|------|-----|
244
+ | Quick prototyping | `infer` | Zero config, good approximation |
245
+ | Production cards with specific layout | `schema` | Pixel-perfect control |
246
+ | Reusable components | `withInstaSkeleton` | Consistent API, one config |
247
+ | Lists with repeated items | `infer` + `cacheKey` | Cached after first render |
248
+ | Complex nested structures | `infer` + `inferOptions` | Tune depth/node limits |
249
+
250
+ ## Example App
251
+
252
+ The local example app is a comprehensive visual reference covering:
253
+
254
+ - Profile cards, product grids, social feeds
255
+ - Tables, file lists, pricing cards
256
+ - Settings forms with mixed controls
257
+ - Chat threads, deeply nested comment threads
258
+ - Dashboard layouts with nested cards
259
+ - E-commerce product detail pages
260
+ - Multi-level navigation structures
261
+ - Mixed media galleries
262
+ - Complex forms with validation states
263
+ - Manual schema mirrors showing exact structure
264
+ - HOC usage patterns
265
+ - All primitive node types
266
+ - Animation comparison (shimmer, pulse, none)
267
+
268
+ Run it from the repo root:
269
+
270
+ ```bash
271
+ npm run example:install
272
+ npm run example:dev
273
+ ```
274
+
275
+ ## Development
276
+
277
+ ```bash
278
+ npm run build # Build the library
279
+ npm run typecheck # Run TypeScript checks
280
+ npm run dev # Watch mode
281
+ npm run example:dev # Run example app
282
+ ```
283
+
284
+ ## Browser Support
285
+
286
+ - Chrome/Edge 88+
287
+ - Firefox 78+
288
+ - Safari 14+
289
+
290
+ ## License
291
+
292
+ MIT © [LittleBoy9](https://github.com/LittleBoy9)
293
+
294
+ ## Notes
295
+
296
+ - Inference walks the React element tree, not the rendered DOM.
297
+ - Function components may be unwrapped, but hook-heavy components can fall back to child inference.
298
+ - If you need strict visual matching, use manual schema.
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";var g=Object.defineProperty;var x=Object.getOwnPropertyDescriptor;var b=Object.getOwnPropertyNames;var w=Object.prototype.hasOwnProperty;var v=(e,t)=>{for(var n in t)g(e,n,{get:t[n],enumerable:!0})},P=(e,t,n,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of b(t))!w.call(e,r)&&r!==n&&g(e,r,{get:()=>t[r],enumerable:!(o=x(t,r))||o.enumerable});return e};var C=e=>P(g({},"__esModule",{value:!0}),e);var D={};v(D,{InstaSkeleton:()=>y,clearInstaSkeletonCache:()=>I,withInstaSkeleton:()=>N});module.exports=C(D);var l=require("react"),a=require("react/jsx-runtime"),R={maxDepth:6,maxNodes:120,textLineHeight:"0.95rem"},A=100,m=new Map;function E(e,t){if(m.size>=A){let n=m.keys().next().value;n&&m.delete(n)}m.set(e,t)}function L(e){return e?Array.isArray(e)?e:[e]:[]}function h(e,t){return e??t}function W(e,t){let n={...R,...t},o=0,r=(i,d)=>{if(o>=n.maxNodes||d>n.maxDepth)return[];if(i==null||typeof i=="boolean")return[];if(typeof i=="string"||typeof i=="number")return o+=1,[{type:"line",height:n.textLineHeight}];if(Array.isArray(i))return i.flatMap(p=>r(p,d+1));if(!(0,l.isValidElement)(i))return[];o+=1;let s=typeof i.type=="string"?i.type:"component";if(s==="img"||s==="video"||s==="svg"||s==="canvas")return[{type:"rect",height:"8rem"}];if(s==="button"||s==="input"||s==="textarea"||s==="select")return[{type:"rect",height:"2.5rem",radius:"0.5rem"}];let u=r(i.props.children,d+1);return u.length===0?[{type:"line",width:"70%",height:n.textLineHeight}]:u.length===1?u:[{type:"group",children:u}]};return r(l.Children.toArray(e),0)}function k(e){let t={};return e.width!==void 0&&(t.width=h(e.width,"")),e.height!==void 0&&(t.height=h(e.height,"")),e.radius!==void 0&&(t.borderRadius=h(e.radius,"")),e.gap!==void 0&&(t.gap=h(e.gap,"")),t}var S=(0,l.memo)(function e({node:t,animation:n}){if(t.type==="group")return(0,a.jsx)("div",{className:"is-group",style:k(t),children:(t.children??[]).map((r,i)=>(0,a.jsx)(e,{node:r,animation:n},i))});let o=n==="none"?"":` is-anim-${n}`;return(0,a.jsx)("div",{className:`is-node is-${t.type}${o}`,style:k(t),"aria-hidden":"true"})}),y=(0,l.memo)(function({loading:t,children:n,schema:o,infer:r=!0,cacheKey:i,className:d,animation:s="shimmer",inferOptions:u}){if(!t)return(0,a.jsx)(a.Fragment,{children:n});let p=(0,l.useMemo)(()=>{let f=L(o);if(f.length>0)return f;if(!r)return[];if(i&&m.has(i))return m.get(i)??[];let c=W(n,u);return i&&E(i,c),c},[o,r,n,i,u]);return(0,a.jsx)("div",{className:["is-root",d].filter(Boolean).join(" "),role:"status","aria-live":"polite","aria-busy":"true",children:p.length>0?p.map((f,c)=>(0,a.jsx)(S,{node:f,animation:s},c)):(0,a.jsx)(S,{node:{type:"line"},animation:s})})});function N(e,t){let n=o=>{let{loading:r,...i}=o;return(0,a.jsx)(y,{loading:r,schema:t?.skeleton,infer:t?.infer,cacheKey:t?.cacheKey,className:t?.className,animation:t?.animation,children:(0,a.jsx)(e,{...i})})};return n.displayName=`withInstaSkeleton(${e.displayName||e.name||"Component"})`,(0,l.memo)(n)}function I(e){if(e){m.delete(e);return}m.clear()}0&&(module.exports={InstaSkeleton,clearInstaSkeletonCache,withInstaSkeleton});
2
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/core.tsx"],"sourcesContent":["import './styles.css';\n\nexport { InstaSkeleton, clearInstaSkeletonCache, withInstaSkeleton } from './core';\nexport type { InferOptions, InstaSkeletonProps, SkeletonNode, SkeletonNodeType, WithInstaSkeletonProps } from './types';\n","import React, { Children, isValidElement, memo, useMemo } from 'react';\nimport type {\n InferOptions,\n InstaSkeletonProps,\n SkeletonNode,\n WithInstaSkeletonProps\n} from './types';\n\nconst DEFAULT_INFER: Required<InferOptions> = {\n maxDepth: 6,\n maxNodes: 120,\n textLineHeight: '0.95rem'\n};\n\nconst CACHE_MAX_SIZE = 100;\nconst schemaCache = new Map<string, SkeletonNode[]>();\n\nfunction setCacheWithLimit(key: string, value: SkeletonNode[]): void {\n if (schemaCache.size >= CACHE_MAX_SIZE) {\n const firstKey = schemaCache.keys().next().value;\n if (firstKey) schemaCache.delete(firstKey);\n }\n schemaCache.set(key, value);\n}\n\nfunction normalizeSchema(schema?: SkeletonNode | SkeletonNode[]): SkeletonNode[] {\n if (!schema) return [];\n return Array.isArray(schema) ? schema : [schema];\n}\n\nfunction resolveDimension(value: string | number | undefined, fallback: string | number): string | number {\n return value ?? fallback;\n}\n\nfunction inferFromChildren(children: React.ReactNode, options?: InferOptions): SkeletonNode[] {\n const merged = { ...DEFAULT_INFER, ...options };\n let nodeCount = 0;\n\n const walk = (node: React.ReactNode, depth: number): SkeletonNode[] => {\n if (nodeCount >= merged.maxNodes || depth > merged.maxDepth) return [];\n if (node == null || typeof node === 'boolean') return [];\n\n if (typeof node === 'string' || typeof node === 'number') {\n nodeCount += 1;\n return [{ type: 'line', height: merged.textLineHeight }];\n }\n\n if (Array.isArray(node)) {\n return node.flatMap((item) => walk(item, depth + 1));\n }\n\n if (!isValidElement(node)) {\n return [];\n }\n\n nodeCount += 1;\n\n const elType = typeof node.type === 'string' ? node.type : 'component';\n\n if (elType === 'img' || elType === 'video' || elType === 'svg' || elType === 'canvas') {\n return [{ type: 'rect', height: '8rem' }];\n }\n\n if (elType === 'button' || elType === 'input' || elType === 'textarea' || elType === 'select') {\n return [{ type: 'rect', height: '2.5rem', radius: '0.5rem' }];\n }\n\n const childNodes = walk(node.props.children, depth + 1);\n\n if (childNodes.length === 0) {\n return [{ type: 'line', width: '70%', height: merged.textLineHeight }];\n }\n\n if (childNodes.length === 1) {\n return childNodes;\n }\n\n return [{ type: 'group', children: childNodes }];\n };\n\n return walk(Children.toArray(children), 0);\n}\n\nfunction nodeStyle(node: SkeletonNode): React.CSSProperties {\n const style: React.CSSProperties = {};\n\n if (node.width !== undefined) style.width = resolveDimension(node.width, '');\n if (node.height !== undefined) style.height = resolveDimension(node.height, '');\n if (node.radius !== undefined) style.borderRadius = resolveDimension(node.radius, '');\n if (node.gap !== undefined) style.gap = resolveDimension(node.gap, '');\n\n return style;\n}\n\nconst NodeView = memo(function NodeView({ node, animation }: { node: SkeletonNode; animation: InstaSkeletonProps['animation'] }) {\n if (node.type === 'group') {\n return (\n <div className=\"is-group\" style={nodeStyle(node)}>\n {(node.children ?? []).map((child, index) => (\n <NodeView key={index} node={child} animation={animation} />\n ))}\n </div>\n );\n }\n\n const animClass = animation === 'none' ? '' : ` is-anim-${animation}`;\n return <div className={`is-node is-${node.type}${animClass}`} style={nodeStyle(node)} aria-hidden=\"true\" />;\n});\n\nexport const InstaSkeleton = memo(function InstaSkeleton({\n loading,\n children,\n schema,\n infer = true,\n cacheKey,\n className,\n animation = 'shimmer',\n inferOptions\n}: InstaSkeletonProps) {\n // Early exit: skip all inference work when not loading\n if (!loading) return <>{children}</>;\n\n const skeletonSchema = useMemo(() => {\n const normalized = normalizeSchema(schema);\n if (normalized.length > 0) return normalized;\n if (!infer) return [];\n\n if (cacheKey && schemaCache.has(cacheKey)) {\n return schemaCache.get(cacheKey) ?? [];\n }\n\n const inferred = inferFromChildren(children, inferOptions);\n if (cacheKey) setCacheWithLimit(cacheKey, inferred);\n return inferred;\n }, [schema, infer, children, cacheKey, inferOptions]);\n\n return (\n <div className={['is-root', className].filter(Boolean).join(' ')} role=\"status\" aria-live=\"polite\" aria-busy=\"true\">\n {skeletonSchema.length > 0 ? (\n skeletonSchema.map((node, index) => <NodeView key={index} node={node} animation={animation} />)\n ) : (\n <NodeView node={{ type: 'line' }} animation={animation} />\n )}\n </div>\n );\n});\n\nexport function withInstaSkeleton<P extends object>(\n Component: React.ComponentType<P>,\n options?: Omit<WithInstaSkeletonProps, 'loading'>\n) {\n const Wrapped = (props: P & { loading: boolean }) => {\n const { loading, ...rest } = props;\n\n return (\n <InstaSkeleton\n loading={loading}\n schema={options?.skeleton}\n infer={options?.infer}\n cacheKey={options?.cacheKey}\n className={options?.className}\n animation={options?.animation}\n >\n <Component {...(rest as P)} />\n </InstaSkeleton>\n );\n };\n\n Wrapped.displayName = `withInstaSkeleton(${Component.displayName || Component.name || 'Component'})`;\n\n return memo(Wrapped);\n}\n\nexport function clearInstaSkeletonCache(key?: string): void {\n if (key) {\n schemaCache.delete(key);\n return;\n }\n\n schemaCache.clear();\n}\n"],"mappings":"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","names":["index_exports","__export","InstaSkeleton","clearInstaSkeletonCache","withInstaSkeleton","__toCommonJS","import_react","import_jsx_runtime","DEFAULT_INFER","CACHE_MAX_SIZE","schemaCache","setCacheWithLimit","key","value","firstKey","normalizeSchema","schema","resolveDimension","fallback","inferFromChildren","children","options","merged","nodeCount","walk","node","depth","item","elType","childNodes","nodeStyle","style","NodeView","animation","child","index","animClass","InstaSkeleton","loading","infer","cacheKey","className","inferOptions","skeletonSchema","normalized","inferred","withInstaSkeleton","Component","Wrapped","props","rest","clearInstaSkeletonCache"]}
package/dist/index.css ADDED
@@ -0,0 +1,2 @@
1
+ .is-root{display:flex;flex-direction:column;gap:.75rem;width:100%}.is-node{position:relative;overflow:hidden;background:#e6e6e6;border-radius:.5rem}.is-line{height:.95rem;width:100%;border-radius:999px}.is-rect{width:100%;height:5rem}.is-circle{width:2.5rem;height:2.5rem;border-radius:999px}.is-group{display:flex;flex-direction:column;gap:.5rem}.is-anim-shimmer:after{content:"";position:absolute;inset:0;transform:translate(-100%);background:linear-gradient(90deg,transparent,rgba(255,255,255,.6),transparent);animation:is-shimmer 1.2s linear infinite;will-change:transform}.is-anim-pulse{animation:is-pulse 1.1s ease-in-out infinite}@keyframes is-shimmer{to{transform:translate(100%)}}@keyframes is-pulse{0%{opacity:1}50%{opacity:.6}to{opacity:1}}@media(prefers-reduced-motion:reduce){.is-anim-shimmer:after,.is-anim-pulse{animation:none}}
2
+ /*# sourceMappingURL=index.css.map */
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/styles.css"],"sourcesContent":[".is-root {\n display: flex;\n flex-direction: column;\n gap: 0.75rem;\n width: 100%;\n}\n\n.is-node {\n position: relative;\n overflow: hidden;\n background: #e6e6e6;\n border-radius: 0.5rem;\n}\n\n.is-line {\n height: 0.95rem;\n width: 100%;\n border-radius: 999px;\n}\n\n.is-rect {\n width: 100%;\n height: 5rem;\n}\n\n.is-circle {\n width: 2.5rem;\n height: 2.5rem;\n border-radius: 999px;\n}\n\n.is-group {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.is-anim-shimmer::after {\n content: '';\n position: absolute;\n inset: 0;\n transform: translateX(-100%);\n background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.6), transparent);\n animation: is-shimmer 1.2s linear infinite;\n will-change: transform;\n}\n\n.is-anim-pulse {\n animation: is-pulse 1.1s ease-in-out infinite;\n}\n\n@keyframes is-shimmer {\n 100% {\n transform: translateX(100%);\n }\n}\n\n@keyframes is-pulse {\n 0% {\n opacity: 1;\n }\n 50% {\n opacity: 0.6;\n }\n 100% {\n opacity: 1;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .is-anim-shimmer::after,\n .is-anim-pulse {\n animation: none;\n }\n}\n"],"mappings":"AAAA,CAAC,QACC,QAAS,KACT,eAAgB,OAChB,IAAK,OACL,MAAO,IACT,CAEA,CAAC,QACC,SAAU,SACV,SAAU,OACV,WAAY,QAVd,cAWiB,KACjB,CAEA,CAAC,QACC,OAAQ,OACR,MAAO,KAhBT,cAiBiB,KACjB,CAEA,CAAC,QACC,MAAO,KACP,OAAQ,IACV,CAEA,CAAC,UACC,MAAO,OACP,OAAQ,OA3BV,cA4BiB,KACjB,CAEA,CAAC,SACC,QAAS,KACT,eAAgB,OAChB,IAAK,KACP,CAEA,CAAC,eAAe,OACd,QAAS,GACT,SAAU,SAvCZ,MAwCS,EACP,UAAW,UAAW,OACtB,WAAY,gBAAgB,KAAK,CAAE,WAAW,CAAE,KAAK,GAAG,CAAE,GAAG,CAAE,GAAG,CAAE,GAAI,CAAE,aAC1E,UAAW,WAAW,KAAK,OAAO,SAClC,YAAa,SACf,CAEA,CAAC,cACC,UAAW,SAAS,KAAK,YAAY,QACvC,CAEA,WARa,WASX,GACE,UAAW,UAAW,KACxB,CACF,CAEA,WATa,SAUX,GACE,QAAS,CACX,CACA,IACE,QAAS,EACX,CACA,GACE,QAAS,CACX,CACF,CAEA,OAAO,uBAAyB,QAC9B,CAjCD,eAiCiB,OAChB,CAxBD,cAyBG,UAAW,IACb,CACF","names":[]}
@@ -0,0 +1,46 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React, { ReactNode } from 'react';
3
+
4
+ type SkeletonNodeType = 'line' | 'rect' | 'circle' | 'group';
5
+ interface SkeletonNode {
6
+ type: SkeletonNodeType;
7
+ width?: string | number;
8
+ height?: string | number;
9
+ radius?: string | number;
10
+ gap?: string | number;
11
+ children?: SkeletonNode[];
12
+ }
13
+ interface InferOptions {
14
+ maxDepth?: number;
15
+ maxNodes?: number;
16
+ textLineHeight?: string | number;
17
+ }
18
+ interface InstaSkeletonProps {
19
+ loading: boolean;
20
+ children: ReactNode;
21
+ schema?: SkeletonNode | SkeletonNode[];
22
+ infer?: boolean;
23
+ cacheKey?: string;
24
+ className?: string;
25
+ animation?: 'shimmer' | 'pulse' | 'none';
26
+ inferOptions?: InferOptions;
27
+ }
28
+ interface WithInstaSkeletonProps {
29
+ loading: boolean;
30
+ skeleton?: SkeletonNode | SkeletonNode[];
31
+ infer?: boolean;
32
+ cacheKey?: string;
33
+ className?: string;
34
+ animation?: 'shimmer' | 'pulse' | 'none';
35
+ }
36
+
37
+ declare const InstaSkeleton: React.NamedExoticComponent<InstaSkeletonProps>;
38
+ declare function withInstaSkeleton<P extends object>(Component: React.ComponentType<P>, options?: Omit<WithInstaSkeletonProps, 'loading'>): React.MemoExoticComponent<{
39
+ (props: P & {
40
+ loading: boolean;
41
+ }): react_jsx_runtime.JSX.Element;
42
+ displayName: string;
43
+ }>;
44
+ declare function clearInstaSkeletonCache(key?: string): void;
45
+
46
+ export { type InferOptions, InstaSkeleton, type InstaSkeletonProps, type SkeletonNode, type SkeletonNodeType, type WithInstaSkeletonProps, clearInstaSkeletonCache, withInstaSkeleton };
@@ -0,0 +1,46 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React, { ReactNode } from 'react';
3
+
4
+ type SkeletonNodeType = 'line' | 'rect' | 'circle' | 'group';
5
+ interface SkeletonNode {
6
+ type: SkeletonNodeType;
7
+ width?: string | number;
8
+ height?: string | number;
9
+ radius?: string | number;
10
+ gap?: string | number;
11
+ children?: SkeletonNode[];
12
+ }
13
+ interface InferOptions {
14
+ maxDepth?: number;
15
+ maxNodes?: number;
16
+ textLineHeight?: string | number;
17
+ }
18
+ interface InstaSkeletonProps {
19
+ loading: boolean;
20
+ children: ReactNode;
21
+ schema?: SkeletonNode | SkeletonNode[];
22
+ infer?: boolean;
23
+ cacheKey?: string;
24
+ className?: string;
25
+ animation?: 'shimmer' | 'pulse' | 'none';
26
+ inferOptions?: InferOptions;
27
+ }
28
+ interface WithInstaSkeletonProps {
29
+ loading: boolean;
30
+ skeleton?: SkeletonNode | SkeletonNode[];
31
+ infer?: boolean;
32
+ cacheKey?: string;
33
+ className?: string;
34
+ animation?: 'shimmer' | 'pulse' | 'none';
35
+ }
36
+
37
+ declare const InstaSkeleton: React.NamedExoticComponent<InstaSkeletonProps>;
38
+ declare function withInstaSkeleton<P extends object>(Component: React.ComponentType<P>, options?: Omit<WithInstaSkeletonProps, 'loading'>): React.MemoExoticComponent<{
39
+ (props: P & {
40
+ loading: boolean;
41
+ }): react_jsx_runtime.JSX.Element;
42
+ displayName: string;
43
+ }>;
44
+ declare function clearInstaSkeletonCache(key?: string): void;
45
+
46
+ export { type InferOptions, InstaSkeleton, type InstaSkeletonProps, type SkeletonNode, type SkeletonNodeType, type WithInstaSkeletonProps, clearInstaSkeletonCache, withInstaSkeleton };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import{Children as S,isValidElement as N,memo as h,useMemo as I}from"react";import{Fragment as A,jsx as s}from"react/jsx-runtime";var x={maxDepth:6,maxNodes:120,textLineHeight:"0.95rem"},b=100,l=new Map;function w(e,t){if(l.size>=b){let i=l.keys().next().value;i&&l.delete(i)}l.set(e,t)}function v(e){return e?Array.isArray(e)?e:[e]:[]}function c(e,t){return e??t}function P(e,t){let i={...x,...t},o=0,a=(n,u)=>{if(o>=i.maxNodes||u>i.maxDepth)return[];if(n==null||typeof n=="boolean")return[];if(typeof n=="string"||typeof n=="number")return o+=1,[{type:"line",height:i.textLineHeight}];if(Array.isArray(n))return n.flatMap(d=>a(d,u+1));if(!N(n))return[];o+=1;let r=typeof n.type=="string"?n.type:"component";if(r==="img"||r==="video"||r==="svg"||r==="canvas")return[{type:"rect",height:"8rem"}];if(r==="button"||r==="input"||r==="textarea"||r==="select")return[{type:"rect",height:"2.5rem",radius:"0.5rem"}];let m=a(n.props.children,u+1);return m.length===0?[{type:"line",width:"70%",height:i.textLineHeight}]:m.length===1?m:[{type:"group",children:m}]};return a(S.toArray(e),0)}function g(e){let t={};return e.width!==void 0&&(t.width=c(e.width,"")),e.height!==void 0&&(t.height=c(e.height,"")),e.radius!==void 0&&(t.borderRadius=c(e.radius,"")),e.gap!==void 0&&(t.gap=c(e.gap,"")),t}var y=h(function e({node:t,animation:i}){if(t.type==="group")return s("div",{className:"is-group",style:g(t),children:(t.children??[]).map((a,n)=>s(e,{node:a,animation:i},n))});let o=i==="none"?"":` is-anim-${i}`;return s("div",{className:`is-node is-${t.type}${o}`,style:g(t),"aria-hidden":"true"})}),k=h(function({loading:t,children:i,schema:o,infer:a=!0,cacheKey:n,className:u,animation:r="shimmer",inferOptions:m}){if(!t)return s(A,{children:i});let d=I(()=>{let p=v(o);if(p.length>0)return p;if(!a)return[];if(n&&l.has(n))return l.get(n)??[];let f=P(i,m);return n&&w(n,f),f},[o,a,i,n,m]);return s("div",{className:["is-root",u].filter(Boolean).join(" "),role:"status","aria-live":"polite","aria-busy":"true",children:d.length>0?d.map((p,f)=>s(y,{node:p,animation:r},f)):s(y,{node:{type:"line"},animation:r})})});function C(e,t){let i=o=>{let{loading:a,...n}=o;return s(k,{loading:a,schema:t?.skeleton,infer:t?.infer,cacheKey:t?.cacheKey,className:t?.className,animation:t?.animation,children:s(e,{...n})})};return i.displayName=`withInstaSkeleton(${e.displayName||e.name||"Component"})`,h(i)}function R(e){if(e){l.delete(e);return}l.clear()}export{k as InstaSkeleton,R as clearInstaSkeletonCache,C as withInstaSkeleton};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core.tsx"],"sourcesContent":["import React, { Children, isValidElement, memo, useMemo } from 'react';\nimport type {\n InferOptions,\n InstaSkeletonProps,\n SkeletonNode,\n WithInstaSkeletonProps\n} from './types';\n\nconst DEFAULT_INFER: Required<InferOptions> = {\n maxDepth: 6,\n maxNodes: 120,\n textLineHeight: '0.95rem'\n};\n\nconst CACHE_MAX_SIZE = 100;\nconst schemaCache = new Map<string, SkeletonNode[]>();\n\nfunction setCacheWithLimit(key: string, value: SkeletonNode[]): void {\n if (schemaCache.size >= CACHE_MAX_SIZE) {\n const firstKey = schemaCache.keys().next().value;\n if (firstKey) schemaCache.delete(firstKey);\n }\n schemaCache.set(key, value);\n}\n\nfunction normalizeSchema(schema?: SkeletonNode | SkeletonNode[]): SkeletonNode[] {\n if (!schema) return [];\n return Array.isArray(schema) ? schema : [schema];\n}\n\nfunction resolveDimension(value: string | number | undefined, fallback: string | number): string | number {\n return value ?? fallback;\n}\n\nfunction inferFromChildren(children: React.ReactNode, options?: InferOptions): SkeletonNode[] {\n const merged = { ...DEFAULT_INFER, ...options };\n let nodeCount = 0;\n\n const walk = (node: React.ReactNode, depth: number): SkeletonNode[] => {\n if (nodeCount >= merged.maxNodes || depth > merged.maxDepth) return [];\n if (node == null || typeof node === 'boolean') return [];\n\n if (typeof node === 'string' || typeof node === 'number') {\n nodeCount += 1;\n return [{ type: 'line', height: merged.textLineHeight }];\n }\n\n if (Array.isArray(node)) {\n return node.flatMap((item) => walk(item, depth + 1));\n }\n\n if (!isValidElement(node)) {\n return [];\n }\n\n nodeCount += 1;\n\n const elType = typeof node.type === 'string' ? node.type : 'component';\n\n if (elType === 'img' || elType === 'video' || elType === 'svg' || elType === 'canvas') {\n return [{ type: 'rect', height: '8rem' }];\n }\n\n if (elType === 'button' || elType === 'input' || elType === 'textarea' || elType === 'select') {\n return [{ type: 'rect', height: '2.5rem', radius: '0.5rem' }];\n }\n\n const childNodes = walk(node.props.children, depth + 1);\n\n if (childNodes.length === 0) {\n return [{ type: 'line', width: '70%', height: merged.textLineHeight }];\n }\n\n if (childNodes.length === 1) {\n return childNodes;\n }\n\n return [{ type: 'group', children: childNodes }];\n };\n\n return walk(Children.toArray(children), 0);\n}\n\nfunction nodeStyle(node: SkeletonNode): React.CSSProperties {\n const style: React.CSSProperties = {};\n\n if (node.width !== undefined) style.width = resolveDimension(node.width, '');\n if (node.height !== undefined) style.height = resolveDimension(node.height, '');\n if (node.radius !== undefined) style.borderRadius = resolveDimension(node.radius, '');\n if (node.gap !== undefined) style.gap = resolveDimension(node.gap, '');\n\n return style;\n}\n\nconst NodeView = memo(function NodeView({ node, animation }: { node: SkeletonNode; animation: InstaSkeletonProps['animation'] }) {\n if (node.type === 'group') {\n return (\n <div className=\"is-group\" style={nodeStyle(node)}>\n {(node.children ?? []).map((child, index) => (\n <NodeView key={index} node={child} animation={animation} />\n ))}\n </div>\n );\n }\n\n const animClass = animation === 'none' ? '' : ` is-anim-${animation}`;\n return <div className={`is-node is-${node.type}${animClass}`} style={nodeStyle(node)} aria-hidden=\"true\" />;\n});\n\nexport const InstaSkeleton = memo(function InstaSkeleton({\n loading,\n children,\n schema,\n infer = true,\n cacheKey,\n className,\n animation = 'shimmer',\n inferOptions\n}: InstaSkeletonProps) {\n // Early exit: skip all inference work when not loading\n if (!loading) return <>{children}</>;\n\n const skeletonSchema = useMemo(() => {\n const normalized = normalizeSchema(schema);\n if (normalized.length > 0) return normalized;\n if (!infer) return [];\n\n if (cacheKey && schemaCache.has(cacheKey)) {\n return schemaCache.get(cacheKey) ?? [];\n }\n\n const inferred = inferFromChildren(children, inferOptions);\n if (cacheKey) setCacheWithLimit(cacheKey, inferred);\n return inferred;\n }, [schema, infer, children, cacheKey, inferOptions]);\n\n return (\n <div className={['is-root', className].filter(Boolean).join(' ')} role=\"status\" aria-live=\"polite\" aria-busy=\"true\">\n {skeletonSchema.length > 0 ? (\n skeletonSchema.map((node, index) => <NodeView key={index} node={node} animation={animation} />)\n ) : (\n <NodeView node={{ type: 'line' }} animation={animation} />\n )}\n </div>\n );\n});\n\nexport function withInstaSkeleton<P extends object>(\n Component: React.ComponentType<P>,\n options?: Omit<WithInstaSkeletonProps, 'loading'>\n) {\n const Wrapped = (props: P & { loading: boolean }) => {\n const { loading, ...rest } = props;\n\n return (\n <InstaSkeleton\n loading={loading}\n schema={options?.skeleton}\n infer={options?.infer}\n cacheKey={options?.cacheKey}\n className={options?.className}\n animation={options?.animation}\n >\n <Component {...(rest as P)} />\n </InstaSkeleton>\n );\n };\n\n Wrapped.displayName = `withInstaSkeleton(${Component.displayName || Component.name || 'Component'})`;\n\n return memo(Wrapped);\n}\n\nexport function clearInstaSkeletonCache(key?: string): void {\n if (key) {\n schemaCache.delete(key);\n return;\n }\n\n schemaCache.clear();\n}\n"],"mappings":"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","names":["Children","isValidElement","memo","useMemo","Fragment","jsx","DEFAULT_INFER","CACHE_MAX_SIZE","schemaCache","setCacheWithLimit","key","value","firstKey","normalizeSchema","schema","resolveDimension","fallback","inferFromChildren","children","options","merged","nodeCount","walk","node","depth","item","elType","childNodes","nodeStyle","style","NodeView","animation","child","index","animClass","InstaSkeleton","loading","infer","cacheKey","className","inferOptions","skeletonSchema","normalized","inferred","withInstaSkeleton","Component","Wrapped","props","rest","clearInstaSkeletonCache"]}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "instaskeleton",
3
+ "version": "0.1.0",
4
+ "description": "Ultra-light React skeleton generator with zero DOM scanning",
5
+ "license": "MIT",
6
+ "author": "Sounak Das",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/LittleBoy9/instaskeleton.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/LittleBoy9/instaskeleton/issues"
13
+ },
14
+ "homepage": "https://github.com/LittleBoy9/instaskeleton#readme",
15
+ "type": "module",
16
+ "main": "./dist/index.cjs",
17
+ "module": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js",
23
+ "require": "./dist/index.cjs"
24
+ },
25
+ "./styles.css": "./dist/index.css"
26
+ },
27
+ "files": [
28
+ "dist"
29
+ ],
30
+ "sideEffects": [
31
+ "**/*.css"
32
+ ],
33
+ "scripts": {
34
+ "build": "tsup",
35
+ "dev": "tsup --watch",
36
+ "typecheck": "tsc --noEmit",
37
+ "example:install": "npm --prefix example install",
38
+ "example:dev": "npm run build && npm --prefix example run dev",
39
+ "example:build": "npm run build && npm --prefix example run build"
40
+ },
41
+ "keywords": [
42
+ "skeleton",
43
+ "react",
44
+ "loading",
45
+ "placeholder",
46
+ "ui"
47
+ ],
48
+ "peerDependencies": {
49
+ "react": ">=18"
50
+ },
51
+ "devDependencies": {
52
+ "@types/react": "^18.3.0",
53
+ "tsup": "^8.1.0",
54
+ "typescript": "^5.6.0"
55
+ }
56
+ }