react-native-skelo 0.0.1 → 0.0.3
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/LICENSE +21 -0
- package/README.md +149 -20
- package/lib/commonjs/Skelo.js +104 -0
- package/lib/commonjs/Skelo.js.map +1 -0
- package/lib/commonjs/animations/AnimationWrapper.js +76 -0
- package/lib/commonjs/animations/AnimationWrapper.js.map +1 -0
- package/lib/commonjs/animations/PulseAnimation.js +52 -0
- package/lib/commonjs/animations/PulseAnimation.js.map +1 -0
- package/lib/commonjs/animations/ShimmerAnimation.js +73 -0
- package/lib/commonjs/animations/ShimmerAnimation.js.map +1 -0
- package/lib/commonjs/animations/index.js +27 -0
- package/lib/commonjs/animations/index.js.map +1 -0
- package/lib/commonjs/components/MeasuredBlock.js +64 -0
- package/lib/commonjs/components/MeasuredBlock.js.map +1 -0
- package/lib/commonjs/components/Skeleton.js +144 -0
- package/lib/commonjs/components/Skeleton.js.map +1 -0
- package/lib/commonjs/components/SkeletonIgnore.js +30 -0
- package/lib/commonjs/components/SkeletonIgnore.js.map +1 -0
- package/lib/commonjs/components/SkeletonRenderer.js +270 -0
- package/lib/commonjs/components/SkeletonRenderer.js.map +1 -0
- package/lib/commonjs/components/primitives/SkeletonBox.js +47 -0
- package/lib/commonjs/components/primitives/SkeletonBox.js.map +1 -0
- package/lib/commonjs/components/primitives/SkeletonCircle.js +44 -0
- package/lib/commonjs/components/primitives/SkeletonCircle.js.map +1 -0
- package/lib/commonjs/components/primitives/SkeletonImage.js +46 -0
- package/lib/commonjs/components/primitives/SkeletonImage.js.map +1 -0
- package/lib/commonjs/components/primitives/SkeletonText.js +60 -0
- package/lib/commonjs/components/primitives/SkeletonText.js.map +1 -0
- package/lib/commonjs/components/primitives/index.js +34 -0
- package/lib/commonjs/components/primitives/index.js.map +1 -0
- package/lib/commonjs/components/withSkeleton.js +45 -0
- package/lib/commonjs/components/withSkeleton.js.map +1 -0
- package/lib/commonjs/constants/defaults.js +43 -0
- package/lib/commonjs/constants/defaults.js.map +1 -0
- package/lib/commonjs/core/deepRegistry.js +18 -0
- package/lib/commonjs/core/deepRegistry.js.map +1 -0
- package/lib/commonjs/core/generator/StyleSkeleton.js +101 -0
- package/lib/commonjs/core/generator/StyleSkeleton.js.map +1 -0
- package/lib/commonjs/core/parser/Parser.js +84 -0
- package/lib/commonjs/core/parser/Parser.js.map +1 -0
- package/lib/commonjs/core/parser/expandOffline.js +83 -0
- package/lib/commonjs/core/parser/expandOffline.js.map +1 -0
- package/lib/commonjs/core/parser/hostTreeReconciler.js +228 -0
- package/lib/commonjs/core/parser/hostTreeReconciler.js.map +1 -0
- package/lib/commonjs/core/parser/index.js +13 -0
- package/lib/commonjs/core/parser/index.js.map +1 -0
- package/lib/commonjs/core/registry/SkeletonRegistry.js +113 -0
- package/lib/commonjs/core/registry/SkeletonRegistry.js.map +1 -0
- package/lib/commonjs/core/registry/index.js +19 -0
- package/lib/commonjs/core/registry/index.js.map +1 -0
- package/lib/commonjs/core/resolver/StyleResolver.js +83 -0
- package/lib/commonjs/core/resolver/StyleResolver.js.map +1 -0
- package/lib/commonjs/core/resolver/index.js +13 -0
- package/lib/commonjs/core/resolver/index.js.map +1 -0
- package/lib/commonjs/deep.js +18 -0
- package/lib/commonjs/deep.js.map +1 -0
- package/lib/commonjs/index.js +66 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/plugins/ImagePlugin.js +60 -0
- package/lib/commonjs/plugins/ImagePlugin.js.map +1 -0
- package/lib/commonjs/plugins/TextPlugin.js +70 -0
- package/lib/commonjs/plugins/TextPlugin.js.map +1 -0
- package/lib/commonjs/plugins/ViewPlugin.js +71 -0
- package/lib/commonjs/plugins/ViewPlugin.js.map +1 -0
- package/lib/commonjs/plugins/index.js +27 -0
- package/lib/commonjs/plugins/index.js.map +1 -0
- package/lib/commonjs/types/index.js +2 -0
- package/lib/commonjs/types/index.js.map +1 -0
- package/lib/commonjs/types/plugin.js +6 -0
- package/lib/commonjs/types/plugin.js.map +1 -0
- package/lib/commonjs/types/skeleton.js +6 -0
- package/lib/commonjs/types/skeleton.js.map +1 -0
- package/lib/commonjs/utils/componentUtils.js +138 -0
- package/lib/commonjs/utils/componentUtils.js.map +1 -0
- package/lib/commonjs/utils/index.js +62 -0
- package/lib/commonjs/utils/index.js.map +1 -0
- package/lib/commonjs/utils/styleUtils.js +76 -0
- package/lib/commonjs/utils/styleUtils.js.map +1 -0
- package/lib/module/Skelo.js +98 -0
- package/lib/module/Skelo.js.map +1 -0
- package/lib/module/animations/AnimationWrapper.js +69 -0
- package/lib/module/animations/AnimationWrapper.js.map +1 -0
- package/lib/module/animations/PulseAnimation.js +45 -0
- package/lib/module/animations/PulseAnimation.js.map +1 -0
- package/lib/module/animations/ShimmerAnimation.js +66 -0
- package/lib/module/animations/ShimmerAnimation.js.map +1 -0
- package/lib/module/animations/index.js +4 -0
- package/lib/module/animations/index.js.map +1 -0
- package/lib/module/components/MeasuredBlock.js +57 -0
- package/lib/module/components/MeasuredBlock.js.map +1 -0
- package/lib/module/components/Skeleton.js +138 -0
- package/lib/module/components/Skeleton.js.map +1 -0
- package/lib/module/components/SkeletonIgnore.js +23 -0
- package/lib/module/components/SkeletonIgnore.js.map +1 -0
- package/lib/module/components/SkeletonRenderer.js +264 -0
- package/lib/module/components/SkeletonRenderer.js.map +1 -0
- package/lib/module/components/primitives/SkeletonBox.js +40 -0
- package/lib/module/components/primitives/SkeletonBox.js.map +1 -0
- package/lib/module/components/primitives/SkeletonCircle.js +37 -0
- package/lib/module/components/primitives/SkeletonCircle.js.map +1 -0
- package/lib/module/components/primitives/SkeletonImage.js +39 -0
- package/lib/module/components/primitives/SkeletonImage.js.map +1 -0
- package/lib/module/components/primitives/SkeletonText.js +53 -0
- package/lib/module/components/primitives/SkeletonText.js.map +1 -0
- package/lib/module/components/primitives/index.js +5 -0
- package/lib/module/components/primitives/index.js.map +1 -0
- package/lib/module/components/withSkeleton.js +38 -0
- package/lib/module/components/withSkeleton.js.map +1 -0
- package/lib/module/constants/defaults.js +37 -0
- package/lib/module/constants/defaults.js.map +1 -0
- package/lib/module/core/deepRegistry.js +11 -0
- package/lib/module/core/deepRegistry.js.map +1 -0
- package/lib/module/core/generator/StyleSkeleton.js +95 -0
- package/lib/module/core/generator/StyleSkeleton.js.map +1 -0
- package/lib/module/core/parser/Parser.js +77 -0
- package/lib/module/core/parser/Parser.js.map +1 -0
- package/lib/module/core/parser/expandOffline.js +77 -0
- package/lib/module/core/parser/expandOffline.js.map +1 -0
- package/lib/module/core/parser/hostTreeReconciler.js +222 -0
- package/lib/module/core/parser/hostTreeReconciler.js.map +1 -0
- package/lib/module/core/parser/index.js +2 -0
- package/lib/module/core/parser/index.js.map +1 -0
- package/lib/module/core/registry/SkeletonRegistry.js +106 -0
- package/lib/module/core/registry/SkeletonRegistry.js.map +1 -0
- package/lib/module/core/registry/index.js +2 -0
- package/lib/module/core/registry/index.js.map +1 -0
- package/lib/module/core/resolver/StyleResolver.js +77 -0
- package/lib/module/core/resolver/StyleResolver.js.map +1 -0
- package/lib/module/core/resolver/index.js +2 -0
- package/lib/module/core/resolver/index.js.map +1 -0
- package/lib/module/deep.js +11 -0
- package/lib/module/deep.js.map +1 -0
- package/lib/module/index.js +21 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/plugins/ImagePlugin.js +53 -0
- package/lib/module/plugins/ImagePlugin.js.map +1 -0
- package/lib/module/plugins/TextPlugin.js +63 -0
- package/lib/module/plugins/TextPlugin.js.map +1 -0
- package/lib/module/plugins/ViewPlugin.js +64 -0
- package/lib/module/plugins/ViewPlugin.js.map +1 -0
- package/lib/module/plugins/index.js +4 -0
- package/lib/module/plugins/index.js.map +1 -0
- package/lib/module/types/index.js +2 -0
- package/lib/module/types/index.js.map +1 -0
- package/lib/module/types/plugin.js +2 -0
- package/lib/module/types/plugin.js.map +1 -0
- package/lib/module/types/skeleton.js +2 -0
- package/lib/module/types/skeleton.js.map +1 -0
- package/lib/module/utils/componentUtils.js +123 -0
- package/lib/module/utils/componentUtils.js.map +1 -0
- package/lib/module/utils/index.js +3 -0
- package/lib/module/utils/index.js.map +1 -0
- package/lib/module/utils/styleUtils.js +66 -0
- package/lib/module/utils/styleUtils.js.map +1 -0
- package/lib/typescript/Skelo.d.ts +76 -0
- package/lib/typescript/Skelo.d.ts.map +1 -0
- package/lib/typescript/animations/AnimationWrapper.d.ts +66 -0
- package/lib/typescript/animations/AnimationWrapper.d.ts.map +1 -0
- package/lib/typescript/animations/PulseAnimation.d.ts +15 -0
- package/lib/typescript/animations/PulseAnimation.d.ts.map +1 -0
- package/lib/typescript/animations/ShimmerAnimation.d.ts +15 -0
- package/lib/typescript/animations/ShimmerAnimation.d.ts.map +1 -0
- package/lib/typescript/animations/index.d.ts +5 -0
- package/lib/typescript/animations/index.d.ts.map +1 -0
- package/lib/typescript/components/MeasuredBlock.d.ts +32 -0
- package/lib/typescript/components/MeasuredBlock.d.ts.map +1 -0
- package/lib/typescript/components/Skeleton.d.ts +37 -0
- package/lib/typescript/components/Skeleton.d.ts.map +1 -0
- package/lib/typescript/components/SkeletonIgnore.d.ts +26 -0
- package/lib/typescript/components/SkeletonIgnore.d.ts.map +1 -0
- package/lib/typescript/components/SkeletonRenderer.d.ts +39 -0
- package/lib/typescript/components/SkeletonRenderer.d.ts.map +1 -0
- package/lib/typescript/components/primitives/SkeletonBox.d.ts +21 -0
- package/lib/typescript/components/primitives/SkeletonBox.d.ts.map +1 -0
- package/lib/typescript/components/primitives/SkeletonCircle.d.ts +21 -0
- package/lib/typescript/components/primitives/SkeletonCircle.d.ts.map +1 -0
- package/lib/typescript/components/primitives/SkeletonImage.d.ts +21 -0
- package/lib/typescript/components/primitives/SkeletonImage.d.ts.map +1 -0
- package/lib/typescript/components/primitives/SkeletonText.d.ts +21 -0
- package/lib/typescript/components/primitives/SkeletonText.d.ts.map +1 -0
- package/lib/typescript/components/primitives/index.d.ts +5 -0
- package/lib/typescript/components/primitives/index.d.ts.map +1 -0
- package/lib/typescript/components/withSkeleton.d.ts +37 -0
- package/lib/typescript/components/withSkeleton.d.ts.map +1 -0
- package/lib/typescript/constants/defaults.d.ts +29 -0
- package/lib/typescript/constants/defaults.d.ts.map +1 -0
- package/lib/typescript/core/deepRegistry.d.ts +7 -0
- package/lib/typescript/core/deepRegistry.d.ts.map +1 -0
- package/lib/typescript/core/generator/StyleSkeleton.d.ts +36 -0
- package/lib/typescript/core/generator/StyleSkeleton.d.ts.map +1 -0
- package/lib/typescript/core/parser/Parser.d.ts +30 -0
- package/lib/typescript/core/parser/Parser.d.ts.map +1 -0
- package/lib/typescript/core/parser/expandOffline.d.ts +10 -0
- package/lib/typescript/core/parser/expandOffline.d.ts.map +1 -0
- package/lib/typescript/core/parser/hostTreeReconciler.d.ts +28 -0
- package/lib/typescript/core/parser/hostTreeReconciler.d.ts.map +1 -0
- package/lib/typescript/core/parser/index.d.ts +2 -0
- package/lib/typescript/core/parser/index.d.ts.map +1 -0
- package/lib/typescript/core/registry/SkeletonRegistry.d.ts +57 -0
- package/lib/typescript/core/registry/SkeletonRegistry.d.ts.map +1 -0
- package/lib/typescript/core/registry/index.d.ts +2 -0
- package/lib/typescript/core/registry/index.d.ts.map +1 -0
- package/lib/typescript/core/resolver/StyleResolver.d.ts +54 -0
- package/lib/typescript/core/resolver/StyleResolver.d.ts.map +1 -0
- package/lib/typescript/core/resolver/index.d.ts +2 -0
- package/lib/typescript/core/resolver/index.d.ts.map +1 -0
- package/lib/typescript/deep.d.ts +2 -0
- package/lib/typescript/deep.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +17 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/plugins/ImagePlugin.d.ts +11 -0
- package/lib/typescript/plugins/ImagePlugin.d.ts.map +1 -0
- package/lib/typescript/plugins/TextPlugin.d.ts +12 -0
- package/lib/typescript/plugins/TextPlugin.d.ts.map +1 -0
- package/lib/typescript/plugins/ViewPlugin.d.ts +11 -0
- package/lib/typescript/plugins/ViewPlugin.d.ts.map +1 -0
- package/lib/typescript/plugins/index.d.ts +4 -0
- package/lib/typescript/plugins/index.d.ts.map +1 -0
- package/lib/typescript/types/index.d.ts +3 -0
- package/lib/typescript/types/index.d.ts.map +1 -0
- package/lib/typescript/types/plugin.d.ts +144 -0
- package/lib/typescript/types/plugin.d.ts.map +1 -0
- package/lib/typescript/types/skeleton.d.ts +176 -0
- package/lib/typescript/types/skeleton.d.ts.map +1 -0
- package/lib/typescript/utils/componentUtils.d.ts +40 -0
- package/lib/typescript/utils/componentUtils.d.ts.map +1 -0
- package/lib/typescript/utils/index.d.ts +3 -0
- package/lib/typescript/utils/index.d.ts.map +1 -0
- package/lib/typescript/utils/styleUtils.d.ts +41 -0
- package/lib/typescript/utils/styleUtils.d.ts.map +1 -0
- package/package.json +169 -11
- package/src/Skelo.ts +103 -0
- package/src/animations/AnimationWrapper.tsx +139 -0
- package/src/animations/PulseAnimation.tsx +76 -0
- package/src/animations/ShimmerAnimation.tsx +101 -0
- package/src/animations/index.ts +4 -0
- package/src/components/MeasuredBlock.tsx +85 -0
- package/src/components/Skeleton.tsx +149 -0
- package/src/components/SkeletonIgnore.tsx +27 -0
- package/src/components/SkeletonRenderer.tsx +341 -0
- package/src/components/primitives/SkeletonBox.tsx +53 -0
- package/src/components/primitives/SkeletonCircle.tsx +48 -0
- package/src/components/primitives/SkeletonImage.tsx +50 -0
- package/src/components/primitives/SkeletonText.tsx +69 -0
- package/src/components/primitives/index.ts +4 -0
- package/src/components/withSkeleton.tsx +62 -0
- package/src/constants/defaults.ts +38 -0
- package/src/core/deepRegistry.ts +16 -0
- package/src/core/generator/StyleSkeleton.tsx +138 -0
- package/src/core/parser/Parser.ts +86 -0
- package/src/core/parser/expandOffline.tsx +83 -0
- package/src/core/parser/hostTreeReconciler.tsx +271 -0
- package/src/core/parser/index.ts +1 -0
- package/src/core/registry/SkeletonRegistry.ts +111 -0
- package/src/core/registry/index.ts +1 -0
- package/src/core/resolver/StyleResolver.ts +80 -0
- package/src/core/resolver/index.ts +1 -0
- package/src/deep.ts +12 -0
- package/src/index.ts +30 -0
- package/src/plugins/ImagePlugin.tsx +65 -0
- package/src/plugins/TextPlugin.tsx +71 -0
- package/src/plugins/ViewPlugin.tsx +67 -0
- package/src/plugins/index.ts +3 -0
- package/src/types/index.ts +14 -0
- package/src/types/plugin.ts +164 -0
- package/src/types/skeleton.ts +197 -0
- package/src/utils/componentUtils.ts +166 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/styleUtils.ts +72 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ReactElement } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A JS-only React renderer built on `react-reconciler`.
|
|
6
|
+
*
|
|
7
|
+
* It renders any element (including opaque function/class components) into a
|
|
8
|
+
* plain host-node tree — no native views, no `act()`, no deprecated APIs. Used
|
|
9
|
+
* to introspect a component's real View/Text/Image structure + styles so Skelo
|
|
10
|
+
* can generate a matching skeleton.
|
|
11
|
+
*
|
|
12
|
+
* Rendered as a synchronous LegacyRoot, so `updateContainer` flushes inline and
|
|
13
|
+
* we can read the tree immediately.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export interface HostTreeNode {
|
|
17
|
+
type: string;
|
|
18
|
+
props: { style?: unknown; children?: unknown; [key: string]: unknown };
|
|
19
|
+
children: HostTreeNode[];
|
|
20
|
+
text?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface Container {
|
|
24
|
+
children: HostTreeNode[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const NO_CONTEXT = {};
|
|
28
|
+
|
|
29
|
+
const DefaultEventPriority = 32; // DefaultLane
|
|
30
|
+
|
|
31
|
+
function makeHostConfig() {
|
|
32
|
+
// Reconciler 0.31 (React 19) tracks update priority via host methods.
|
|
33
|
+
let currentUpdatePriority = 0; // NoLane
|
|
34
|
+
return {
|
|
35
|
+
supportsMutation: true,
|
|
36
|
+
supportsPersistence: false,
|
|
37
|
+
supportsHydration: false,
|
|
38
|
+
isPrimaryRenderer: false,
|
|
39
|
+
noTimeout: -1,
|
|
40
|
+
now: () => Date.now(),
|
|
41
|
+
scheduleTimeout: setTimeout,
|
|
42
|
+
cancelTimeout: clearTimeout,
|
|
43
|
+
// Must be false: microtask scheduling defers the render, leaving the tree
|
|
44
|
+
// empty when we read it synchronously. Disabling it makes the LegacyRoot
|
|
45
|
+
// render commit inline inside updateContainer.
|
|
46
|
+
supportsMicrotasks: false,
|
|
47
|
+
|
|
48
|
+
getRootHostContext: () => NO_CONTEXT,
|
|
49
|
+
getChildHostContext: (parent: unknown) => parent,
|
|
50
|
+
getPublicInstance: (instance: unknown) => instance,
|
|
51
|
+
prepareForCommit: () => null,
|
|
52
|
+
resetAfterCommit: () => {},
|
|
53
|
+
preparePortalMount: () => {},
|
|
54
|
+
|
|
55
|
+
createInstance(type: string, props: HostTreeNode['props']): HostTreeNode {
|
|
56
|
+
return { type, props, children: [] };
|
|
57
|
+
},
|
|
58
|
+
createTextInstance(text: string): HostTreeNode {
|
|
59
|
+
return { type: '#text', props: {}, children: [], text };
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
appendInitialChild(parent: HostTreeNode, child: HostTreeNode) {
|
|
63
|
+
parent.children.push(child);
|
|
64
|
+
},
|
|
65
|
+
appendChild(parent: HostTreeNode, child: HostTreeNode) {
|
|
66
|
+
parent.children.push(child);
|
|
67
|
+
},
|
|
68
|
+
appendChildToContainer(container: Container, child: HostTreeNode) {
|
|
69
|
+
container.children.push(child);
|
|
70
|
+
},
|
|
71
|
+
insertBefore(parent: HostTreeNode, child: HostTreeNode, before: HostTreeNode) {
|
|
72
|
+
const i = parent.children.indexOf(before);
|
|
73
|
+
if (i === -1) parent.children.push(child);
|
|
74
|
+
else parent.children.splice(i, 0, child);
|
|
75
|
+
},
|
|
76
|
+
insertInContainerBefore(container: Container, child: HostTreeNode, before: HostTreeNode) {
|
|
77
|
+
const i = container.children.indexOf(before);
|
|
78
|
+
if (i === -1) container.children.push(child);
|
|
79
|
+
else container.children.splice(i, 0, child);
|
|
80
|
+
},
|
|
81
|
+
removeChild(parent: HostTreeNode, child: HostTreeNode) {
|
|
82
|
+
const i = parent.children.indexOf(child);
|
|
83
|
+
if (i !== -1) parent.children.splice(i, 1);
|
|
84
|
+
},
|
|
85
|
+
removeChildFromContainer(container: Container, child: HostTreeNode) {
|
|
86
|
+
const i = container.children.indexOf(child);
|
|
87
|
+
if (i !== -1) container.children.splice(i, 1);
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
finalizeInitialChildren: () => false,
|
|
91
|
+
shouldSetTextContent: () => false,
|
|
92
|
+
prepareUpdate: () => ({}),
|
|
93
|
+
commitUpdate(
|
|
94
|
+
instance: HostTreeNode,
|
|
95
|
+
_payload: unknown,
|
|
96
|
+
_type: string,
|
|
97
|
+
_old: unknown,
|
|
98
|
+
newProps: HostTreeNode['props']
|
|
99
|
+
) {
|
|
100
|
+
instance.props = newProps;
|
|
101
|
+
},
|
|
102
|
+
commitTextUpdate(textInstance: HostTreeNode, _old: string, next: string) {
|
|
103
|
+
textInstance.text = next;
|
|
104
|
+
},
|
|
105
|
+
clearContainer(container: Container) {
|
|
106
|
+
container.children = [];
|
|
107
|
+
},
|
|
108
|
+
detachDeletedInstance: () => {},
|
|
109
|
+
|
|
110
|
+
// Required by React 18/19 reconcilers:
|
|
111
|
+
getCurrentEventPriority: () => DefaultEventPriority,
|
|
112
|
+
// Reconciler 0.31 (React 19) replaced the above with update-priority hooks;
|
|
113
|
+
// requestUpdateLane() calls these, so they must exist or it throws
|
|
114
|
+
// "undefined is not a function".
|
|
115
|
+
setCurrentUpdatePriority: (priority: number) => {
|
|
116
|
+
currentUpdatePriority = priority;
|
|
117
|
+
},
|
|
118
|
+
getCurrentUpdatePriority: () => currentUpdatePriority,
|
|
119
|
+
resolveUpdatePriority: () => currentUpdatePriority || DefaultEventPriority,
|
|
120
|
+
beforeActiveInstanceBlur: () => {},
|
|
121
|
+
afterActiveInstanceBlur: () => {},
|
|
122
|
+
prepareScopeUpdate: () => {},
|
|
123
|
+
getInstanceFromScope: () => null,
|
|
124
|
+
getInstanceFromNode: () => null,
|
|
125
|
+
|
|
126
|
+
// Newer reconciler (0.31 / React 19) additions — harmless on older versions:
|
|
127
|
+
maySuspendCommit: () => false,
|
|
128
|
+
startSuspendingCommit: () => {},
|
|
129
|
+
suspendInstance: () => {},
|
|
130
|
+
waitForCommitToBeReady: () => null,
|
|
131
|
+
NotPendingTransition: null,
|
|
132
|
+
// React 19 reads this context during render; it must be a real context.
|
|
133
|
+
HostTransitionContext: React.createContext(null),
|
|
134
|
+
resetFormInstance: () => {},
|
|
135
|
+
requestPostPaintCallback: () => {},
|
|
136
|
+
shouldAttemptEagerTransition: () => false,
|
|
137
|
+
trackSchedulerEvent: () => {},
|
|
138
|
+
resolveEventType: () => null,
|
|
139
|
+
resolveEventTimeStamp: () => -1.1,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
class RenderBoundary extends React.Component<{ children: React.ReactNode }> {
|
|
144
|
+
static getDerivedStateFromError() {
|
|
145
|
+
return {};
|
|
146
|
+
}
|
|
147
|
+
render() {
|
|
148
|
+
return this.props.children;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
let cachedReconciler:
|
|
153
|
+
| ((config: unknown) => {
|
|
154
|
+
createContainer: (...args: unknown[]) => unknown;
|
|
155
|
+
updateContainer: (...args: unknown[]) => unknown;
|
|
156
|
+
})
|
|
157
|
+
| null = null;
|
|
158
|
+
|
|
159
|
+
function getReconciler() {
|
|
160
|
+
if (cachedReconciler) return cachedReconciler;
|
|
161
|
+
|
|
162
|
+
const Reconciler = require('react-reconciler');
|
|
163
|
+
cachedReconciler = Reconciler.default || Reconciler;
|
|
164
|
+
return cachedReconciler;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Render an element to a plain host-node tree, or return null if the reconciler
|
|
169
|
+
* is unavailable or rendering failed.
|
|
170
|
+
*/
|
|
171
|
+
export function renderToHostTree(element: ReactElement): HostTreeNode[] | null {
|
|
172
|
+
let Reconciler;
|
|
173
|
+
try {
|
|
174
|
+
Reconciler = getReconciler();
|
|
175
|
+
} catch {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
if (!Reconciler) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
// Wrap the host config so any method a newer reconciler calls that we
|
|
184
|
+
// didn't explicitly define resolves to a safe no-op instead of `undefined`
|
|
185
|
+
// (which throws "undefined is not a function"). All value-returning methods
|
|
186
|
+
// are defined above, so only side-effect methods hit this fallback.
|
|
187
|
+
const baseConfig = makeHostConfig() as Record<string, unknown>;
|
|
188
|
+
const noopFn = () => {};
|
|
189
|
+
const safeConfig = new Proxy(baseConfig, {
|
|
190
|
+
get(target, prop: string) {
|
|
191
|
+
if (prop in target) return target[prop];
|
|
192
|
+
return noopFn;
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
const reconciler = Reconciler(safeConfig);
|
|
196
|
+
const container: Container = { children: [] };
|
|
197
|
+
const noop = () => {};
|
|
198
|
+
// createContainer's signature differs across reconciler versions:
|
|
199
|
+
// React 19 (reconciler 0.31) added onUncaughtError/onCaughtError before
|
|
200
|
+
// onRecoverableError; React 18 (0.29) has a single onRecoverableError.
|
|
201
|
+
const isReact19 = parseInt(React.version, 10) >= 19;
|
|
202
|
+
const root = isReact19
|
|
203
|
+
? reconciler.createContainer(
|
|
204
|
+
container,
|
|
205
|
+
0, // LegacyRoot -> synchronous
|
|
206
|
+
null,
|
|
207
|
+
false,
|
|
208
|
+
null,
|
|
209
|
+
'',
|
|
210
|
+
noop, // onUncaughtError
|
|
211
|
+
noop, // onCaughtError
|
|
212
|
+
noop, // onRecoverableError
|
|
213
|
+
null
|
|
214
|
+
)
|
|
215
|
+
: reconciler.createContainer(
|
|
216
|
+
container,
|
|
217
|
+
0,
|
|
218
|
+
null,
|
|
219
|
+
false,
|
|
220
|
+
null,
|
|
221
|
+
'',
|
|
222
|
+
noop, // onRecoverableError
|
|
223
|
+
null
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
const rc = reconciler as {
|
|
227
|
+
updateContainer: (...a: unknown[]) => unknown;
|
|
228
|
+
updateContainerSync?: (...a: unknown[]) => unknown;
|
|
229
|
+
flushSyncWork?: () => void;
|
|
230
|
+
flushSync?: (fn: () => void) => void;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const element0 = React.createElement(RenderBoundary, null, element);
|
|
234
|
+
|
|
235
|
+
// Render synchronously so the tree is populated before we read it.
|
|
236
|
+
// react-reconciler 0.31 (React 19) defers `updateContainer`; it exposes
|
|
237
|
+
// `updateContainerSync` + `flushSyncWork` for a guaranteed inline commit.
|
|
238
|
+
// Older 0.29 flushes inline via `flushSync(updateContainer)`.
|
|
239
|
+
if (typeof rc.updateContainerSync === 'function') {
|
|
240
|
+
rc.updateContainerSync(element0, root, null, noop);
|
|
241
|
+
rc.flushSyncWork?.();
|
|
242
|
+
} else if (typeof rc.flushSync === 'function') {
|
|
243
|
+
rc.flushSync(() => rc.updateContainer(element0, root, null, noop));
|
|
244
|
+
} else {
|
|
245
|
+
rc.updateContainer(element0, root, null, noop);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const tree = container.children.slice();
|
|
249
|
+
|
|
250
|
+
// Unmount to run cleanup.
|
|
251
|
+
if (typeof rc.updateContainerSync === 'function') {
|
|
252
|
+
rc.updateContainerSync(null, root, null, noop);
|
|
253
|
+
rc.flushSyncWork?.();
|
|
254
|
+
} else {
|
|
255
|
+
rc.updateContainer(null, root, null, noop);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (__DEV__ && tree.length === 0) {
|
|
259
|
+
console.warn(
|
|
260
|
+
'[Skelo] Offline render produced an empty tree; falling back to static parsing.'
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return tree;
|
|
265
|
+
} catch (error) {
|
|
266
|
+
if (__DEV__) {
|
|
267
|
+
console.warn('[Skelo] Offline render failed:', error);
|
|
268
|
+
}
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Parser } from './Parser';
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { ComponentType } from 'react';
|
|
2
|
+
import type { SkeletonPlugin } from '../../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Registry for skeleton plugins
|
|
6
|
+
*
|
|
7
|
+
* Manages mapping between React components and their skeleton generation strategies
|
|
8
|
+
*/
|
|
9
|
+
export class SkeletonRegistry {
|
|
10
|
+
private plugins: Map<ComponentType<any>, SkeletonPlugin> = new Map();
|
|
11
|
+
private nameIndex: Map<string, SkeletonPlugin> = new Map();
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Register a plugin for a component
|
|
15
|
+
*
|
|
16
|
+
* @param plugin - Plugin configuration
|
|
17
|
+
* @throws Error if plugin is invalid
|
|
18
|
+
*/
|
|
19
|
+
register<P>(plugin: SkeletonPlugin<P>): void {
|
|
20
|
+
// Validation
|
|
21
|
+
if (!plugin.name) {
|
|
22
|
+
throw new Error('[Skelo] Plugin must have a name');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!plugin.component) {
|
|
26
|
+
throw new Error('[Skelo] Plugin must specify a component');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!plugin.strategy) {
|
|
30
|
+
throw new Error('[Skelo] Plugin must have a strategy function');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Check for conflicts
|
|
34
|
+
if (this.plugins.has(plugin.component)) {
|
|
35
|
+
if (__DEV__) {
|
|
36
|
+
console.warn(`[Skelo] Plugin "${plugin.name}" is overriding existing plugin for component`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Register
|
|
41
|
+
this.plugins.set(plugin.component, plugin);
|
|
42
|
+
this.nameIndex.set(plugin.name, plugin);
|
|
43
|
+
|
|
44
|
+
if (__DEV__) {
|
|
45
|
+
console.log(`[Skelo] Registered plugin: ${plugin.name}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Unregister a plugin
|
|
51
|
+
*
|
|
52
|
+
* @param component - Component to unregister
|
|
53
|
+
*/
|
|
54
|
+
unregister(component: ComponentType): void {
|
|
55
|
+
const plugin = this.plugins.get(component);
|
|
56
|
+
if (plugin) {
|
|
57
|
+
this.plugins.delete(component);
|
|
58
|
+
this.nameIndex.delete(plugin.name);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get plugin for a component
|
|
64
|
+
*
|
|
65
|
+
* @param component - Component type
|
|
66
|
+
* @returns Plugin or undefined
|
|
67
|
+
*/
|
|
68
|
+
getPlugin(component: ComponentType): SkeletonPlugin | undefined {
|
|
69
|
+
return this.plugins.get(component);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get plugin by name
|
|
74
|
+
*
|
|
75
|
+
* @param name - Plugin name
|
|
76
|
+
* @returns Plugin or undefined
|
|
77
|
+
*/
|
|
78
|
+
getPluginByName(name: string): SkeletonPlugin | undefined {
|
|
79
|
+
return this.nameIndex.get(name);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Check if component has a plugin
|
|
84
|
+
*
|
|
85
|
+
* @param component - Component type
|
|
86
|
+
* @returns True if plugin exists
|
|
87
|
+
*/
|
|
88
|
+
hasPlugin(component: ComponentType): boolean {
|
|
89
|
+
return this.plugins.has(component);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get all registered plugins
|
|
94
|
+
*
|
|
95
|
+
* @returns Array of all plugins
|
|
96
|
+
*/
|
|
97
|
+
getAllPlugins(): SkeletonPlugin[] {
|
|
98
|
+
return Array.from(this.plugins.values());
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Clear all plugins
|
|
103
|
+
*/
|
|
104
|
+
clear(): void {
|
|
105
|
+
this.plugins.clear();
|
|
106
|
+
this.nameIndex.clear();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Singleton instance
|
|
111
|
+
export const registry = new SkeletonRegistry();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SkeletonRegistry, registry } from './SkeletonRegistry';
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
import type { DimensionValue } from 'react-native';
|
|
3
|
+
import type { ResolvedStyle } from '../../types';
|
|
4
|
+
import { resolveStyle, extractDimensions } from '../../utils';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* StyleResolver class for extracting and resolving component styles
|
|
8
|
+
*/
|
|
9
|
+
export class StyleResolver {
|
|
10
|
+
/**
|
|
11
|
+
* Resolve styles from a React element
|
|
12
|
+
*
|
|
13
|
+
* @param element - React element
|
|
14
|
+
* @returns Resolved style object
|
|
15
|
+
*/
|
|
16
|
+
static resolve(element: ReactElement): ResolvedStyle {
|
|
17
|
+
const props = element.props || {};
|
|
18
|
+
const style = props.style;
|
|
19
|
+
|
|
20
|
+
return resolveStyle(style);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get dimensions from element props and style
|
|
25
|
+
*
|
|
26
|
+
* @param element - React element
|
|
27
|
+
* @returns Object with width and height
|
|
28
|
+
*/
|
|
29
|
+
static getDimensions(element: ReactElement): {
|
|
30
|
+
width?: DimensionValue;
|
|
31
|
+
height?: DimensionValue;
|
|
32
|
+
} {
|
|
33
|
+
const style = this.resolve(element);
|
|
34
|
+
return extractDimensions(style);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Check if element has explicit dimensions
|
|
39
|
+
*
|
|
40
|
+
* @param element - React element
|
|
41
|
+
* @returns True if both width and height are defined
|
|
42
|
+
*/
|
|
43
|
+
static hasExplicitDimensions(element: ReactElement): boolean {
|
|
44
|
+
const { width, height } = this.getDimensions(element);
|
|
45
|
+
return width !== undefined && height !== undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get border radius from style
|
|
50
|
+
*
|
|
51
|
+
* @param element - React element
|
|
52
|
+
* @returns Border radius value or undefined
|
|
53
|
+
*/
|
|
54
|
+
static getBorderRadius(element: ReactElement): number | undefined {
|
|
55
|
+
const style = this.resolve(element);
|
|
56
|
+
return style.borderRadius;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get font size from style
|
|
61
|
+
*
|
|
62
|
+
* @param element - React element
|
|
63
|
+
* @returns Font size value or undefined
|
|
64
|
+
*/
|
|
65
|
+
static getFontSize(element: ReactElement): number | undefined {
|
|
66
|
+
const style = this.resolve(element);
|
|
67
|
+
return style.fontSize;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get line height from style
|
|
72
|
+
*
|
|
73
|
+
* @param element - React element
|
|
74
|
+
* @returns Line height value or undefined
|
|
75
|
+
*/
|
|
76
|
+
static getLineHeight(element: ReactElement): number | undefined {
|
|
77
|
+
const style = this.resolve(element);
|
|
78
|
+
return style.lineHeight;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { StyleResolver } from './StyleResolver';
|
package/src/deep.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Opt-in deep mode. Import once in your app entry to enable `<Skeleton deep>`:
|
|
2
|
+
//
|
|
3
|
+
// import 'react-native-skelo/deep';
|
|
4
|
+
//
|
|
5
|
+
// This is the only module that pulls in react-reconciler, so apps that don't
|
|
6
|
+
// use deep mode never bundle it. Requires `react-reconciler` to be installed.
|
|
7
|
+
import { setDeepExpander } from './core/deepRegistry';
|
|
8
|
+
import { expandToComponentNodes } from './core/parser/expandOffline';
|
|
9
|
+
|
|
10
|
+
setDeepExpander(expandToComponentNodes);
|
|
11
|
+
|
|
12
|
+
export const deepEnabled = true;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skelo - Automatic skeleton loading for React Native
|
|
3
|
+
*
|
|
4
|
+
* Write your UI once. Skelo builds the loading state automatically.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Main component
|
|
10
|
+
export { Skeleton } from './components/Skeleton';
|
|
11
|
+
export { SkeletonIgnore } from './components/SkeletonIgnore';
|
|
12
|
+
export { withSkeleton } from './components/withSkeleton';
|
|
13
|
+
export type { WithSkeletonOptions } from './components/withSkeleton';
|
|
14
|
+
export { StyleSkeleton } from './core/generator/StyleSkeleton';
|
|
15
|
+
export type { StyleInput } from './core/generator/StyleSkeleton';
|
|
16
|
+
|
|
17
|
+
// Global API
|
|
18
|
+
export { Skelo } from './Skelo';
|
|
19
|
+
|
|
20
|
+
// Types
|
|
21
|
+
export type {
|
|
22
|
+
SkeletonProps,
|
|
23
|
+
SkeletonConfig,
|
|
24
|
+
SkeletonPlugin,
|
|
25
|
+
SkeletonStrategy,
|
|
26
|
+
SkeletonContext,
|
|
27
|
+
} from './types';
|
|
28
|
+
|
|
29
|
+
// Primitives (for plugin development)
|
|
30
|
+
export { SkeletonBox, SkeletonText, SkeletonImage, SkeletonCircle } from './components/primitives';
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Image } from 'react-native';
|
|
3
|
+
import type { SkeletonPlugin } from '../types';
|
|
4
|
+
import type { ImageProps } from 'react-native';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Built-in plugin for React Native Image component
|
|
8
|
+
*
|
|
9
|
+
* Renders Image as:
|
|
10
|
+
* - SkeletonCircle if circular (avatar-like)
|
|
11
|
+
* - SkeletonImage otherwise
|
|
12
|
+
*/
|
|
13
|
+
export const ImagePlugin: SkeletonPlugin<ImageProps> = {
|
|
14
|
+
name: 'Image',
|
|
15
|
+
component: Image,
|
|
16
|
+
version: '1.0.0',
|
|
17
|
+
|
|
18
|
+
strategy: (props, context) => {
|
|
19
|
+
const { primitives, config } = context;
|
|
20
|
+
const style = props.style || {};
|
|
21
|
+
const flatStyle = Array.isArray(style) ? Object.assign({}, ...style) : style;
|
|
22
|
+
|
|
23
|
+
const width = flatStyle.width ?? 100;
|
|
24
|
+
const height = flatStyle.height ?? 100;
|
|
25
|
+
const borderRadius = flatStyle.borderRadius ?? 0;
|
|
26
|
+
|
|
27
|
+
// Check if it's a circular image (avatar)
|
|
28
|
+
const isCircle =
|
|
29
|
+
typeof width === 'number' &&
|
|
30
|
+
typeof height === 'number' &&
|
|
31
|
+
width === height &&
|
|
32
|
+
borderRadius === width / 2;
|
|
33
|
+
|
|
34
|
+
if (isCircle) {
|
|
35
|
+
return (
|
|
36
|
+
<primitives.Circle
|
|
37
|
+
size={width as number}
|
|
38
|
+
animation={config.animation}
|
|
39
|
+
baseColor={config.baseColor}
|
|
40
|
+
highlightColor={config.highlightColor}
|
|
41
|
+
duration={config.duration}
|
|
42
|
+
style={flatStyle}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<primitives.Image
|
|
49
|
+
width={width}
|
|
50
|
+
height={height}
|
|
51
|
+
borderRadius={borderRadius as number}
|
|
52
|
+
animation={config.animation}
|
|
53
|
+
baseColor={config.baseColor}
|
|
54
|
+
highlightColor={config.highlightColor}
|
|
55
|
+
duration={config.duration}
|
|
56
|
+
style={flatStyle}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
metadata: {
|
|
62
|
+
description: 'Automatic skeleton generation for Image components',
|
|
63
|
+
author: 'Skelo',
|
|
64
|
+
},
|
|
65
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text } from 'react-native';
|
|
3
|
+
import type { SkeletonPlugin } from '../types';
|
|
4
|
+
import type { TextProps } from 'react-native';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Built-in plugin for React Native Text component
|
|
8
|
+
*
|
|
9
|
+
* Renders Text as SkeletonText with:
|
|
10
|
+
* - Estimated line count from text content
|
|
11
|
+
* - Font size from style
|
|
12
|
+
* - Line height from style
|
|
13
|
+
*/
|
|
14
|
+
export const TextPlugin: SkeletonPlugin<TextProps> = {
|
|
15
|
+
name: 'Text',
|
|
16
|
+
component: Text,
|
|
17
|
+
version: '1.0.0',
|
|
18
|
+
|
|
19
|
+
strategy: (props, context) => {
|
|
20
|
+
const { primitives, config } = context;
|
|
21
|
+
const style = props.style || {};
|
|
22
|
+
const flatStyle = Array.isArray(style) ? Object.assign({}, ...style) : style;
|
|
23
|
+
|
|
24
|
+
const fontSize = flatStyle.fontSize ?? 14;
|
|
25
|
+
const lineHeight = flatStyle.lineHeight ?? fontSize * 1.4;
|
|
26
|
+
|
|
27
|
+
// Estimate number of lines from text content
|
|
28
|
+
const textContent = props.children;
|
|
29
|
+
const estimatedLines = estimateTextLines(textContent);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<primitives.Text
|
|
33
|
+
lines={estimatedLines}
|
|
34
|
+
fontSize={fontSize}
|
|
35
|
+
lineHeight={lineHeight}
|
|
36
|
+
width={flatStyle.width ?? '100%'}
|
|
37
|
+
animation={config.animation}
|
|
38
|
+
baseColor={config.baseColor}
|
|
39
|
+
highlightColor={config.highlightColor}
|
|
40
|
+
duration={config.duration}
|
|
41
|
+
style={flatStyle}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
metadata: {
|
|
47
|
+
description: 'Automatic skeleton generation for Text components',
|
|
48
|
+
author: 'Skelo',
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Estimate number of lines from text content
|
|
54
|
+
*/
|
|
55
|
+
function estimateTextLines(children: any): number {
|
|
56
|
+
if (!children) return 1;
|
|
57
|
+
|
|
58
|
+
if (typeof children === 'string' || typeof children === 'number') {
|
|
59
|
+
const text = String(children);
|
|
60
|
+
const avgCharsPerLine = 40;
|
|
61
|
+
const estimatedLines = Math.ceil(text.length / avgCharsPerLine);
|
|
62
|
+
return Math.max(1, Math.min(estimatedLines, 5)); // Cap at 5 lines
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (Array.isArray(children)) {
|
|
66
|
+
// Multiple text nodes - count as separate lines
|
|
67
|
+
return Math.min(children.length, 5);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return 1;
|
|
71
|
+
}
|