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/LICENSE +21 -21
- package/README.md +242 -242
- package/package.json +48 -48
- package/src/AutoSkeleton.js +117 -0
- package/src/analyzer.js +302 -79
- package/src/cache.js +47 -0
- package/src/context.js +38 -0
- package/src/hooks.js +161 -0
- package/src/index.d.ts +149 -33
- package/src/index.js +52 -102
- package/src/primitives.js +293 -0
- package/src/renderer.js +231 -56
- package/src/styles.js +162 -48
- package/src/AutoSkeleton.jsx +0 -72
- package/src/SkeletonItem.jsx +0 -99
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* primitives.js — V2
|
|
5
|
+
*
|
|
6
|
+
* Standalone skeleton building blocks for manual composition.
|
|
7
|
+
* All respect the global SkeletonContext for animation / theme.
|
|
8
|
+
*
|
|
9
|
+
* Exports:
|
|
10
|
+
* SkeletonBlock — generic configurable rectangle
|
|
11
|
+
* SkeletonText — multi-line text placeholder
|
|
12
|
+
* SkeletonAvatar — circular avatar
|
|
13
|
+
* SkeletonImage — rectangular image placeholder
|
|
14
|
+
* SkeletonBadge — inline pill badge
|
|
15
|
+
* SkeletonButton — button placeholder
|
|
16
|
+
* SkeletonInput — input field placeholder
|
|
17
|
+
* SkeletonList — repeated item skeleton
|
|
18
|
+
* SkeletonForm — form with label+input rows
|
|
19
|
+
* SkeletonCard — card with header row + body lines + optional footer
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
var React = require('react');
|
|
23
|
+
var styles = require('./styles');
|
|
24
|
+
var ctx = require('./context');
|
|
25
|
+
|
|
26
|
+
function useAnim(propAnim) {
|
|
27
|
+
var context = React.useContext(ctx.SkeletonContext);
|
|
28
|
+
React.useEffect(function() { styles.injectStyles(); }, []);
|
|
29
|
+
if (typeof document !== 'undefined') styles.injectStyles();
|
|
30
|
+
return propAnim || context.animation || 'shimmer';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* ── SkeletonBlock ── */
|
|
34
|
+
function SkeletonBlock(props) {
|
|
35
|
+
var anim = useAnim(props.animation);
|
|
36
|
+
var shape = props.shape; // 'circle' | 'pill' | 'rounded' | undefined
|
|
37
|
+
return React.createElement('span', {
|
|
38
|
+
'data-ask': '',
|
|
39
|
+
className: styles.blockClass(anim, shape) + (props.className ? ' ' + props.className : ''),
|
|
40
|
+
style: Object.assign({
|
|
41
|
+
width: props.width || '100%',
|
|
42
|
+
height: props.height || '16px',
|
|
43
|
+
display: 'block',
|
|
44
|
+
}, props.style || {}),
|
|
45
|
+
'aria-hidden': 'true',
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
SkeletonBlock.displayName = 'SkeletonBlock';
|
|
49
|
+
|
|
50
|
+
/* ── SkeletonText ── */
|
|
51
|
+
function SkeletonText(props) {
|
|
52
|
+
var anim = useAnim(props.animation);
|
|
53
|
+
var lines = props.lines || 3;
|
|
54
|
+
var lineH = props.lineHeight || '1em';
|
|
55
|
+
var gap = props.gap || '7px';
|
|
56
|
+
var lastWidth = props.lastLineWidth || '60%';
|
|
57
|
+
var lineEls = [];
|
|
58
|
+
for (var i = 0; i < lines; i++) {
|
|
59
|
+
lineEls.push(React.createElement('span', {
|
|
60
|
+
key: 'tl-' + i,
|
|
61
|
+
'data-ask': '',
|
|
62
|
+
className: styles.blockClass(anim),
|
|
63
|
+
style: {
|
|
64
|
+
display: 'block',
|
|
65
|
+
width: i === lines - 1 ? lastWidth : (i === 0 ? '95%' : '100%'),
|
|
66
|
+
height: lineH,
|
|
67
|
+
marginBottom: i < lines - 1 ? gap : 0,
|
|
68
|
+
},
|
|
69
|
+
'aria-hidden': 'true',
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
return React.createElement('div', { 'aria-hidden': 'true', style: props.style || {} }, lineEls);
|
|
73
|
+
}
|
|
74
|
+
SkeletonText.displayName = 'SkeletonText';
|
|
75
|
+
|
|
76
|
+
/* ── SkeletonAvatar ── */
|
|
77
|
+
function SkeletonAvatar(props) {
|
|
78
|
+
var anim = useAnim(props.animation);
|
|
79
|
+
var size = props.size || '40px';
|
|
80
|
+
return React.createElement('span', {
|
|
81
|
+
'data-ask': '',
|
|
82
|
+
className: styles.blockClass(anim, 'circle'),
|
|
83
|
+
style: Object.assign({ width: size, height: size, display: 'block', flexShrink: 0 }, props.style || {}),
|
|
84
|
+
'aria-hidden': 'true',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
SkeletonAvatar.displayName = 'SkeletonAvatar';
|
|
88
|
+
|
|
89
|
+
/* ── SkeletonImage ── */
|
|
90
|
+
function SkeletonImage(props) {
|
|
91
|
+
var anim = useAnim(props.animation);
|
|
92
|
+
return React.createElement('span', {
|
|
93
|
+
'data-ask': '',
|
|
94
|
+
className: styles.blockClass(anim, 'rounded'),
|
|
95
|
+
style: Object.assign({ width: '100%', height: props.height || '200px', display: 'block' }, props.style || {}),
|
|
96
|
+
'aria-hidden': 'true',
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
SkeletonImage.displayName = 'SkeletonImage';
|
|
100
|
+
|
|
101
|
+
/* ── SkeletonBadge ── */
|
|
102
|
+
function SkeletonBadge(props) {
|
|
103
|
+
var anim = useAnim(props.animation);
|
|
104
|
+
return React.createElement('span', {
|
|
105
|
+
'data-ask': '',
|
|
106
|
+
className: styles.blockClass(anim, 'pill'),
|
|
107
|
+
style: Object.assign({ width: props.width || '60px', height: '20px', display: 'inline-block' }, props.style || {}),
|
|
108
|
+
'aria-hidden': 'true',
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
SkeletonBadge.displayName = 'SkeletonBadge';
|
|
112
|
+
|
|
113
|
+
/* ── SkeletonButton ── */
|
|
114
|
+
function SkeletonButton(props) {
|
|
115
|
+
var anim = useAnim(props.animation);
|
|
116
|
+
return React.createElement('span', {
|
|
117
|
+
'data-ask': '',
|
|
118
|
+
className: styles.blockClass(anim, 'pill'),
|
|
119
|
+
style: Object.assign({ width: props.width || '100px', height: props.height || '36px', display: 'block' }, props.style || {}),
|
|
120
|
+
'aria-hidden': 'true',
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
SkeletonButton.displayName = 'SkeletonButton';
|
|
124
|
+
|
|
125
|
+
/* ── SkeletonInput ── */
|
|
126
|
+
function SkeletonInput(props) {
|
|
127
|
+
var anim = useAnim(props.animation);
|
|
128
|
+
return React.createElement('span', {
|
|
129
|
+
'data-ask': '',
|
|
130
|
+
className: styles.blockClass(anim, 'rounded'),
|
|
131
|
+
style: Object.assign({ width: '100%', height: props.height || '38px', display: 'block' }, props.style || {}),
|
|
132
|
+
'aria-hidden': 'true',
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
SkeletonInput.displayName = 'SkeletonInput';
|
|
136
|
+
|
|
137
|
+
/* ── SkeletonList ── */
|
|
138
|
+
function SkeletonList(props) {
|
|
139
|
+
var anim = useAnim(props.animation);
|
|
140
|
+
var count = props.count || 3;
|
|
141
|
+
var gap = props.gap || '12px';
|
|
142
|
+
var rowH = props.rowHeight || '60px';
|
|
143
|
+
var items = [];
|
|
144
|
+
for (var i = 0; i < count; i++) {
|
|
145
|
+
items.push(
|
|
146
|
+
props.renderItem
|
|
147
|
+
? React.createElement('div', { key: i }, props.renderItem({ animation: anim, index: i }))
|
|
148
|
+
: React.createElement('span', {
|
|
149
|
+
key: i,
|
|
150
|
+
'data-ask': '',
|
|
151
|
+
className: styles.blockClass(anim, 'rounded'),
|
|
152
|
+
style: { width: '100%', height: rowH, display: 'block', marginBottom: i < count - 1 ? gap : 0 },
|
|
153
|
+
'aria-hidden': 'true',
|
|
154
|
+
})
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return React.createElement('div', { role: 'status', 'aria-label': 'Loading list', 'aria-busy': 'true' }, items);
|
|
158
|
+
}
|
|
159
|
+
SkeletonList.displayName = 'SkeletonList';
|
|
160
|
+
|
|
161
|
+
/* ── SkeletonForm ── */
|
|
162
|
+
function SkeletonForm(props) {
|
|
163
|
+
var anim = useAnim(props.animation);
|
|
164
|
+
var fields = props.fields || 3;
|
|
165
|
+
var rows = [];
|
|
166
|
+
for (var i = 0; i < fields; i++) {
|
|
167
|
+
rows.push(React.createElement('div', { key: i, style: { marginBottom: '16px' } },
|
|
168
|
+
// label line
|
|
169
|
+
React.createElement('span', {
|
|
170
|
+
'data-ask': '',
|
|
171
|
+
className: styles.blockClass(anim),
|
|
172
|
+
style: { width: '120px', height: '0.8em', display: 'block', marginBottom: '6px' },
|
|
173
|
+
'aria-hidden': 'true',
|
|
174
|
+
}),
|
|
175
|
+
// input box
|
|
176
|
+
React.createElement('span', {
|
|
177
|
+
'data-ask': '',
|
|
178
|
+
className: styles.blockClass(anim, 'rounded'),
|
|
179
|
+
style: { width: '100%', height: '38px', display: 'block' },
|
|
180
|
+
'aria-hidden': 'true',
|
|
181
|
+
})
|
|
182
|
+
));
|
|
183
|
+
}
|
|
184
|
+
// Submit button
|
|
185
|
+
rows.push(React.createElement('span', {
|
|
186
|
+
key: 'submit',
|
|
187
|
+
'data-ask': '',
|
|
188
|
+
className: styles.blockClass(anim, 'pill'),
|
|
189
|
+
style: { width: '120px', height: '38px', display: 'block', marginTop: '8px' },
|
|
190
|
+
'aria-hidden': 'true',
|
|
191
|
+
}));
|
|
192
|
+
return React.createElement('div', { role: 'status', 'aria-busy': 'true', 'aria-label': 'Loading form' }, rows);
|
|
193
|
+
}
|
|
194
|
+
SkeletonForm.displayName = 'SkeletonForm';
|
|
195
|
+
|
|
196
|
+
/* ── SkeletonCard ── */
|
|
197
|
+
function SkeletonCard(props) {
|
|
198
|
+
var anim = useAnim(props.animation);
|
|
199
|
+
var showImg = props.image !== false;
|
|
200
|
+
var showAvatar = props.avatar !== false;
|
|
201
|
+
var lines = props.lines || 3;
|
|
202
|
+
var showFooter = props.footer !== false;
|
|
203
|
+
var imgH = props.imageHeight || '180px';
|
|
204
|
+
|
|
205
|
+
var parts = [];
|
|
206
|
+
if (showImg) parts.push(
|
|
207
|
+
React.createElement('span', {
|
|
208
|
+
key: 'img',
|
|
209
|
+
'data-ask': '',
|
|
210
|
+
className: styles.blockClass(anim, 'rounded'),
|
|
211
|
+
style: { width: '100%', height: imgH, display: 'block', marginBottom: '12px' },
|
|
212
|
+
'aria-hidden': 'true',
|
|
213
|
+
})
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
// header row: avatar + two text lines
|
|
217
|
+
parts.push(React.createElement('div', {
|
|
218
|
+
key: 'hdr',
|
|
219
|
+
style: { display: 'flex', gap: '10px', alignItems: 'center', marginBottom: '12px' },
|
|
220
|
+
},
|
|
221
|
+
showAvatar && React.createElement('span', {
|
|
222
|
+
'data-ask': '',
|
|
223
|
+
className: styles.blockClass(anim, 'circle'),
|
|
224
|
+
style: { width: '36px', height: '36px', flexShrink: 0 },
|
|
225
|
+
'aria-hidden': 'true',
|
|
226
|
+
}),
|
|
227
|
+
React.createElement('div', { style: { flex: 1 } },
|
|
228
|
+
React.createElement('span', {
|
|
229
|
+
'data-ask': '',
|
|
230
|
+
className: styles.blockClass(anim),
|
|
231
|
+
style: { width: '55%', height: '0.9em', display: 'block', marginBottom: '5px' },
|
|
232
|
+
'aria-hidden': 'true',
|
|
233
|
+
}),
|
|
234
|
+
React.createElement('span', {
|
|
235
|
+
'data-ask': '',
|
|
236
|
+
className: styles.blockClass(anim),
|
|
237
|
+
style: { width: '35%', height: '0.75em', display: 'block' },
|
|
238
|
+
'aria-hidden': 'true',
|
|
239
|
+
})
|
|
240
|
+
)
|
|
241
|
+
));
|
|
242
|
+
|
|
243
|
+
// body text
|
|
244
|
+
for (var i = 0; i < lines; i++) {
|
|
245
|
+
parts.push(React.createElement('span', {
|
|
246
|
+
key: 'line-' + i,
|
|
247
|
+
'data-ask': '',
|
|
248
|
+
className: styles.blockClass(anim),
|
|
249
|
+
style: {
|
|
250
|
+
width: i === lines - 1 ? '65%' : '100%',
|
|
251
|
+
height: '0.85em',
|
|
252
|
+
display: 'block',
|
|
253
|
+
marginBottom: i < lines - 1 ? '6px' : '12px',
|
|
254
|
+
},
|
|
255
|
+
'aria-hidden': 'true',
|
|
256
|
+
}));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (showFooter) {
|
|
260
|
+
parts.push(React.createElement('div', {
|
|
261
|
+
key: 'footer',
|
|
262
|
+
style: { display: 'flex', gap: '8px' },
|
|
263
|
+
},
|
|
264
|
+
React.createElement('span', {
|
|
265
|
+
'data-ask': '', className: styles.blockClass(anim, 'pill'),
|
|
266
|
+
style: { width: '60px', height: '24px' }, 'aria-hidden': 'true',
|
|
267
|
+
}),
|
|
268
|
+
React.createElement('span', {
|
|
269
|
+
'data-ask': '', className: styles.blockClass(anim, 'pill'),
|
|
270
|
+
style: { width: '60px', height: '24px' }, 'aria-hidden': 'true',
|
|
271
|
+
})
|
|
272
|
+
));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return React.createElement('div', {
|
|
276
|
+
role: 'status', 'aria-busy': 'true', 'aria-label': 'Loading card',
|
|
277
|
+
style: props.style || {},
|
|
278
|
+
}, parts);
|
|
279
|
+
}
|
|
280
|
+
SkeletonCard.displayName = 'SkeletonCard';
|
|
281
|
+
|
|
282
|
+
module.exports = {
|
|
283
|
+
SkeletonBlock,
|
|
284
|
+
SkeletonText,
|
|
285
|
+
SkeletonAvatar,
|
|
286
|
+
SkeletonImage,
|
|
287
|
+
SkeletonBadge,
|
|
288
|
+
SkeletonButton,
|
|
289
|
+
SkeletonInput,
|
|
290
|
+
SkeletonList,
|
|
291
|
+
SkeletonForm,
|
|
292
|
+
SkeletonCard,
|
|
293
|
+
};
|
package/src/renderer.js
CHANGED
|
@@ -1,84 +1,259 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* renderer.js — V2
|
|
5
|
+
*
|
|
6
|
+
* Improvements over V1:
|
|
7
|
+
* - Handles REPEAT node type (renders N identical skeleton children)
|
|
8
|
+
* - Handles HEADING with level-aware sizing
|
|
9
|
+
* - Handles MEDIA (video/iframe) as wide rectangle
|
|
10
|
+
* - Handles BADGE as inline pill
|
|
11
|
+
* - Handles TEXTAREA as tall input
|
|
12
|
+
* - Handles SELECT as input with chevron indicator
|
|
13
|
+
* - Handles CARD node type with appropriate padding wrapper
|
|
14
|
+
* - Smart sizing: prefers inline style hints, falls back to type defaults
|
|
15
|
+
* - Aspect ratio support from style hints
|
|
16
|
+
* - Passes aria-busy + aria-label to all wrappers for accessibility
|
|
17
|
+
* - Minimal key counter reset per render call for SSR safety
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
var React = require('react');
|
|
21
|
+
var N = require('./analyzer').NODE;
|
|
22
|
+
var styles = require('./styles');
|
|
23
|
+
|
|
24
|
+
/* ── size defaults per node type ── */
|
|
25
|
+
var DEFAULTS = {
|
|
26
|
+
[N.TEXT]: { width: '80%', height: '1em' },
|
|
27
|
+
[N.HEADING]: { width: '55%', height: '1.4em' },
|
|
28
|
+
[N.IMAGE]: { width: '100%', height: '200px' },
|
|
29
|
+
[N.AVATAR]: { width: '40px', height: '40px' },
|
|
30
|
+
[N.MEDIA]: { width: '100%', height: '220px' },
|
|
31
|
+
[N.BUTTON]: { width: '100px', height: '36px' },
|
|
32
|
+
[N.INPUT]: { width: '100%', height: '38px' },
|
|
33
|
+
[N.TEXTAREA]: { width: '100%', height: '100px' },
|
|
34
|
+
[N.SELECT]: { width: '100%', height: '38px' },
|
|
35
|
+
[N.BADGE]: { width: '60px', height: '20px' },
|
|
36
|
+
[N.ICON]: { width: '24px', height: '24px' },
|
|
13
37
|
};
|
|
14
38
|
|
|
15
|
-
|
|
16
|
-
|
|
39
|
+
/* heading level → size */
|
|
40
|
+
var HEADING_SIZES = ['1.8em','1.5em','1.3em','1.15em','1em','0.9em'];
|
|
41
|
+
|
|
42
|
+
/* ── key factory ── */
|
|
43
|
+
var _k = 0;
|
|
44
|
+
function nk() { return 'sk2-' + (++_k); }
|
|
45
|
+
function resetKeys() { _k = 0; }
|
|
17
46
|
|
|
18
|
-
|
|
47
|
+
/* ── style builder ── */
|
|
48
|
+
function sz(defaults, hints, extra) {
|
|
19
49
|
hints = hints || {};
|
|
20
50
|
extra = extra || {};
|
|
21
|
-
|
|
51
|
+
var s = {
|
|
52
|
+
width: hints.width || defaults.width,
|
|
53
|
+
height: hints.height || defaults.height,
|
|
54
|
+
display: 'block',
|
|
55
|
+
};
|
|
56
|
+
if (hints.aspectRatio) { delete s.height; s.aspectRatio = hints.aspectRatio; }
|
|
57
|
+
if (hints.margin) s.margin = hints.margin;
|
|
58
|
+
if (hints.borderRadius) s.borderRadius = hints.borderRadius;
|
|
59
|
+
return Object.assign(s, extra);
|
|
22
60
|
}
|
|
23
61
|
|
|
62
|
+
/* ── multi-line text skeleton ── */
|
|
63
|
+
function textLines(node, anim, extra) {
|
|
64
|
+
var content = node.content || '';
|
|
65
|
+
var len = content.length;
|
|
66
|
+
var lines = Math.min(Math.max(1, Math.ceil(len / 45)), 5);
|
|
67
|
+
var h = (node.styleHints && node.styleHints.height) || (node.styleHints && node.styleHints.fontSize) || '1em';
|
|
68
|
+
|
|
69
|
+
if (lines <= 1) {
|
|
70
|
+
return React.createElement('span', {
|
|
71
|
+
key: nk(),
|
|
72
|
+
className: styles.blockClass(anim),
|
|
73
|
+
style: Object.assign(sz(DEFAULTS[N.TEXT], node.styleHints, extra), { width: node.width || '80%' }),
|
|
74
|
+
'aria-hidden': 'true',
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
var lineEls = [];
|
|
79
|
+
for (var i = 0; i < lines; i++) {
|
|
80
|
+
lineEls.push(React.createElement('span', {
|
|
81
|
+
key: nk(),
|
|
82
|
+
className: styles.blockClass(anim),
|
|
83
|
+
style: {
|
|
84
|
+
display: 'block',
|
|
85
|
+
width: i === lines - 1 ? '60%' : (i === 0 ? '95%' : '100%'),
|
|
86
|
+
height: h,
|
|
87
|
+
marginBottom: i < lines - 1 ? '6px' : 0,
|
|
88
|
+
},
|
|
89
|
+
'aria-hidden': 'true',
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
return React.createElement('div', { key: nk(), 'aria-hidden': 'true' }, lineEls);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* ── main render function ── */
|
|
24
96
|
function renderNode(node, options) {
|
|
25
97
|
if (!node) return null;
|
|
26
98
|
options = options || {};
|
|
27
|
-
var
|
|
28
|
-
var
|
|
29
|
-
var
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
99
|
+
var anim = options.animation || 'shimmer';
|
|
100
|
+
var hints = node.styleHints || {};
|
|
101
|
+
var nt = node.nodeType;
|
|
102
|
+
|
|
103
|
+
/* TEXT */
|
|
104
|
+
if (nt === N.TEXT) return textLines(node, anim, {});
|
|
105
|
+
|
|
106
|
+
/* HEADING */
|
|
107
|
+
if (nt === N.HEADING) {
|
|
108
|
+
var lvl = node.level || 2;
|
|
109
|
+
var hStyle = sz(DEFAULTS[N.HEADING], hints, {
|
|
110
|
+
width: node.width || hints.width || (lvl === 1 ? '70%' : lvl <= 3 ? '55%' : '45%'),
|
|
111
|
+
height: hints.fontSize || HEADING_SIZES[Math.min(lvl - 1, 5)],
|
|
112
|
+
});
|
|
113
|
+
return React.createElement('span', {
|
|
114
|
+
key: nk(),
|
|
115
|
+
className: styles.blockClass(anim),
|
|
116
|
+
style: hStyle,
|
|
117
|
+
'aria-hidden': 'true',
|
|
118
|
+
});
|
|
44
119
|
}
|
|
45
120
|
|
|
46
|
-
|
|
47
|
-
|
|
121
|
+
/* IMAGE */
|
|
122
|
+
if (nt === N.IMAGE) {
|
|
123
|
+
return React.createElement('span', {
|
|
124
|
+
key: nk(),
|
|
125
|
+
className: styles.blockClass(anim, 'rounded'),
|
|
126
|
+
style: sz(DEFAULTS[N.IMAGE], hints),
|
|
127
|
+
'aria-hidden': 'true',
|
|
128
|
+
});
|
|
48
129
|
}
|
|
49
130
|
|
|
50
|
-
|
|
51
|
-
|
|
131
|
+
/* AVATAR */
|
|
132
|
+
if (nt === N.AVATAR) {
|
|
133
|
+
var avSize = hints.width || hints.height || '40px';
|
|
134
|
+
return React.createElement('span', {
|
|
135
|
+
key: nk(),
|
|
136
|
+
className: styles.blockClass(anim, 'circle'),
|
|
137
|
+
style: { width: avSize, height: avSize, display: 'block', flexShrink: 0 },
|
|
138
|
+
'aria-hidden': 'true',
|
|
139
|
+
});
|
|
52
140
|
}
|
|
53
141
|
|
|
54
|
-
|
|
55
|
-
|
|
142
|
+
/* MEDIA (video / iframe) */
|
|
143
|
+
if (nt === N.MEDIA) {
|
|
144
|
+
return React.createElement('span', {
|
|
145
|
+
key: nk(),
|
|
146
|
+
className: styles.blockClass(anim, 'rounded'),
|
|
147
|
+
style: sz(DEFAULTS[N.MEDIA], hints),
|
|
148
|
+
'aria-hidden': 'true',
|
|
149
|
+
});
|
|
56
150
|
}
|
|
57
151
|
|
|
58
|
-
|
|
59
|
-
|
|
152
|
+
/* BUTTON */
|
|
153
|
+
if (nt === N.BUTTON) {
|
|
154
|
+
var btnW = hints.width || (node.label ? Math.max(80, node.label.length * 9) + 'px' : '100px');
|
|
155
|
+
return React.createElement('span', {
|
|
156
|
+
key: nk(),
|
|
157
|
+
className: styles.blockClass(anim, 'pill'),
|
|
158
|
+
style: sz(DEFAULTS[N.BUTTON], hints, { width: btnW }),
|
|
159
|
+
'aria-hidden': 'true',
|
|
160
|
+
});
|
|
60
161
|
}
|
|
61
162
|
|
|
62
|
-
|
|
63
|
-
|
|
163
|
+
/* INPUT */
|
|
164
|
+
if (nt === N.INPUT) {
|
|
165
|
+
return React.createElement('span', {
|
|
166
|
+
key: nk(),
|
|
167
|
+
className: styles.blockClass(anim, 'rounded'),
|
|
168
|
+
style: sz(DEFAULTS[N.INPUT], hints),
|
|
169
|
+
'aria-hidden': 'true',
|
|
170
|
+
});
|
|
64
171
|
}
|
|
65
172
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
173
|
+
/* TEXTAREA */
|
|
174
|
+
if (nt === N.TEXTAREA) {
|
|
175
|
+
return React.createElement('span', {
|
|
176
|
+
key: nk(),
|
|
177
|
+
className: styles.blockClass(anim, 'rounded'),
|
|
178
|
+
style: sz(DEFAULTS[N.TEXTAREA], hints),
|
|
179
|
+
'aria-hidden': 'true',
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/* SELECT */
|
|
184
|
+
if (nt === N.SELECT) {
|
|
185
|
+
return React.createElement('span', {
|
|
186
|
+
key: nk(),
|
|
187
|
+
className: styles.blockClass(anim, 'rounded'),
|
|
188
|
+
style: sz(DEFAULTS[N.SELECT], hints),
|
|
189
|
+
'aria-hidden': 'true',
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* BADGE */
|
|
194
|
+
if (nt === N.BADGE) {
|
|
195
|
+
return React.createElement('span', {
|
|
196
|
+
key: nk(),
|
|
197
|
+
className: styles.blockClass(anim, 'pill'),
|
|
198
|
+
style: sz(DEFAULTS[N.BADGE], hints),
|
|
199
|
+
'aria-hidden': 'true',
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/* ICON */
|
|
204
|
+
if (nt === N.ICON) {
|
|
205
|
+
var iconSz = hints.width || hints.height || hints.fontSize || '24px';
|
|
206
|
+
return React.createElement('span', {
|
|
207
|
+
key: nk(),
|
|
208
|
+
className: styles.blockClass(anim, 'circle'),
|
|
209
|
+
style: { width: iconSz, height: iconSz, display: 'block', flexShrink: 0 },
|
|
210
|
+
'aria-hidden': 'true',
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/* REPEAT — detected list pattern */
|
|
215
|
+
if (nt === N.REPEAT) {
|
|
216
|
+
var items = [];
|
|
217
|
+
for (var ri = 0; ri < node.count; ri++) {
|
|
218
|
+
items.push(React.createElement('div', { key: nk() }, renderNode(node.template, options)));
|
|
77
219
|
}
|
|
78
|
-
|
|
220
|
+
var repStyle = {};
|
|
221
|
+
if (hints.display) repStyle.display = hints.display;
|
|
222
|
+
if (hints.gap) repStyle.gap = hints.gap;
|
|
223
|
+
return React.createElement('div', { key: nk(), className: 'ask-wrap', style: repStyle }, items);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/* CARD */
|
|
227
|
+
if (nt === N.CARD || nt === N.CONTAINER) {
|
|
228
|
+
var kids = (node.children || []).map(function(c) { return renderNode(c, options); }).filter(Boolean);
|
|
229
|
+
var t = node.tag || 'div';
|
|
230
|
+
|
|
231
|
+
// Empty container → show a single block
|
|
232
|
+
if (kids.length === 0) {
|
|
233
|
+
return React.createElement('span', {
|
|
234
|
+
key: nk(),
|
|
235
|
+
className: styles.blockClass(anim),
|
|
236
|
+
style: sz({ width: '100%', height: '24px' }, hints),
|
|
237
|
+
'aria-hidden': 'true',
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
var cStyle = {};
|
|
242
|
+
if (hints.display) cStyle.display = hints.display;
|
|
243
|
+
if (hints.flexDir) cStyle.flexDirection = hints.flexDir;
|
|
244
|
+
if (hints.gap) cStyle.gap = hints.gap;
|
|
245
|
+
if (hints.padding) cStyle.padding = hints.padding;
|
|
246
|
+
if (hints.margin) cStyle.margin = hints.margin;
|
|
247
|
+
if (hints.alignItems) cStyle.alignItems = hints.alignItems;
|
|
248
|
+
|
|
249
|
+
return React.createElement(t, {
|
|
250
|
+
key: nk(),
|
|
251
|
+
className: 'ask-wrap',
|
|
252
|
+
style: cStyle,
|
|
253
|
+
}, kids);
|
|
79
254
|
}
|
|
80
255
|
|
|
81
256
|
return null;
|
|
82
257
|
}
|
|
83
258
|
|
|
84
|
-
module.exports = { renderNode };
|
|
259
|
+
module.exports = { renderNode, resetKeys };
|