cf-pagetree-parser 1.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/README.md +79 -0
- package/dist/cf-pagetree-parser.js +5044 -0
- package/package.json +33 -0
- package/src/index.js +708 -0
- package/src/parsers/button.js +379 -0
- package/src/parsers/form.js +658 -0
- package/src/parsers/index.js +65 -0
- package/src/parsers/interactive.js +484 -0
- package/src/parsers/layout.js +788 -0
- package/src/parsers/list.js +359 -0
- package/src/parsers/media.js +376 -0
- package/src/parsers/placeholders.js +196 -0
- package/src/parsers/popup.js +133 -0
- package/src/parsers/text.js +278 -0
- package/src/styles.js +444 -0
- package/src/utils.js +402 -0
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================================
|
|
3
|
+
* PAGETREE PARSER - Button Parser
|
|
4
|
+
* ============================================================================
|
|
5
|
+
*
|
|
6
|
+
* Button/V1
|
|
7
|
+
*
|
|
8
|
+
* ============================================================================
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
generateId,
|
|
13
|
+
generateFractionalIndex,
|
|
14
|
+
parseInlineStyle,
|
|
15
|
+
parseValueWithUnit,
|
|
16
|
+
normalizeColor,
|
|
17
|
+
extractTextContent,
|
|
18
|
+
parseAnimationAttrs,
|
|
19
|
+
} from '../utils.js';
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
parseSpacing,
|
|
23
|
+
spacingToAttrsAndParams,
|
|
24
|
+
parseBorderRadius,
|
|
25
|
+
parseShadow,
|
|
26
|
+
normalizeFontWeight,
|
|
27
|
+
parseTextAlign,
|
|
28
|
+
} from '../styles.js';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Parse Button/V1
|
|
32
|
+
*
|
|
33
|
+
* Reads from data attributes first (most reliable), falls back to inline styles
|
|
34
|
+
* @param {HTMLElement} element - The button element
|
|
35
|
+
* @param {string} parentId - Parent element ID
|
|
36
|
+
* @param {number} index - Child index
|
|
37
|
+
* @param {Object} styleguideData - Optional styleguide data for looking up button styles
|
|
38
|
+
*/
|
|
39
|
+
export function parseButton(element, parentId, index, styleguideData = null) {
|
|
40
|
+
const id = generateId();
|
|
41
|
+
const mainTextId = generateId();
|
|
42
|
+
const subTextId = generateId();
|
|
43
|
+
|
|
44
|
+
const wrapperStyles = parseInlineStyle(element.getAttribute('style') || '');
|
|
45
|
+
const spacing = parseSpacing(wrapperStyles);
|
|
46
|
+
|
|
47
|
+
// Get href, target from data attributes
|
|
48
|
+
const href = element.getAttribute('data-href') || '#';
|
|
49
|
+
const target = element.getAttribute('data-target') || '_self';
|
|
50
|
+
|
|
51
|
+
// Show/hide specific attributes
|
|
52
|
+
const showIds = element.getAttribute('data-show-ids');
|
|
53
|
+
const hideIds = element.getAttribute('data-hide-ids');
|
|
54
|
+
const elButtonType = element.getAttribute('data-elbuttontype');
|
|
55
|
+
|
|
56
|
+
// Check for styleguide button
|
|
57
|
+
const styleGuideButton = element.getAttribute('data-style-guide-button');
|
|
58
|
+
|
|
59
|
+
// Look up button style from styleguide if available
|
|
60
|
+
let styleguideButtonStyle = null;
|
|
61
|
+
if (styleGuideButton && styleguideData?.buttons) {
|
|
62
|
+
styleguideButtonStyle = styleguideData.buttons.find(btn => btn.id === styleGuideButton);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Find the anchor element for fallback parsing
|
|
66
|
+
const anchor = element.querySelector('a');
|
|
67
|
+
const anchorStyles = anchor ? parseInlineStyle(anchor.getAttribute('style') || '') : {};
|
|
68
|
+
|
|
69
|
+
// Find text span for fallback parsing
|
|
70
|
+
const textSpan = anchor ? anchor.querySelector('span') : null;
|
|
71
|
+
const textStyles = textSpan ? parseInlineStyle(textSpan.getAttribute('style') || '') : {};
|
|
72
|
+
|
|
73
|
+
// Read from styleguide button style first, then data attributes, then inline styles
|
|
74
|
+
// Styleguide button structure: { regular: { bg, color }, hover: { bg, color }, borderRadius, borderWidth, borderColor }
|
|
75
|
+
const bgAttr = element.getAttribute('data-bg');
|
|
76
|
+
const bgColor = styleguideButtonStyle?.regular?.bg
|
|
77
|
+
? normalizeColor(styleguideButtonStyle.regular.bg)
|
|
78
|
+
: bgAttr
|
|
79
|
+
? normalizeColor(bgAttr)
|
|
80
|
+
: (normalizeColor(anchorStyles['background-color']) || '#3b82f6');
|
|
81
|
+
|
|
82
|
+
const textColorAttr = element.getAttribute('data-color');
|
|
83
|
+
const textColor = styleguideButtonStyle?.regular?.color
|
|
84
|
+
? normalizeColor(styleguideButtonStyle.regular.color)
|
|
85
|
+
: textColorAttr
|
|
86
|
+
? normalizeColor(textColorAttr)
|
|
87
|
+
: (normalizeColor(textStyles.color) || '#ffffff');
|
|
88
|
+
|
|
89
|
+
// Font styling - prefer data attributes
|
|
90
|
+
const fontSizeAttr = element.getAttribute('data-size');
|
|
91
|
+
const fontSize = fontSizeAttr ? parseValueWithUnit(fontSizeAttr) : parseValueWithUnit(textStyles['font-size'] || '20px');
|
|
92
|
+
const fontWeight = element.getAttribute('data-weight') ||
|
|
93
|
+
normalizeFontWeight(textStyles['font-weight'] || '700');
|
|
94
|
+
|
|
95
|
+
// Padding - prefer data attributes
|
|
96
|
+
const pxAttr = element.getAttribute('data-px');
|
|
97
|
+
const pyAttr = element.getAttribute('data-py');
|
|
98
|
+
const paddingHorizontal = pxAttr ? parseValueWithUnit(pxAttr) : parseValueWithUnit(anchorStyles['padding-right'] || '32px');
|
|
99
|
+
const paddingVertical = pyAttr ? parseValueWithUnit(pyAttr) : parseValueWithUnit(anchorStyles['padding-top'] || '16px');
|
|
100
|
+
|
|
101
|
+
// Border and corners - styleguide first, then data attributes
|
|
102
|
+
const roundedAttr = element.getAttribute('data-rounded');
|
|
103
|
+
const borderRadius = styleguideButtonStyle?.borderRadius != null
|
|
104
|
+
? { value: styleguideButtonStyle.borderRadius, unit: 'px' }
|
|
105
|
+
: roundedAttr
|
|
106
|
+
? parseValueWithUnit(roundedAttr)
|
|
107
|
+
: parseBorderRadius(anchorStyles);
|
|
108
|
+
|
|
109
|
+
const borderColorAttr = element.getAttribute('data-border-color');
|
|
110
|
+
const borderColor = styleguideButtonStyle?.borderColor
|
|
111
|
+
? normalizeColor(styleguideButtonStyle.borderColor)
|
|
112
|
+
: borderColorAttr
|
|
113
|
+
? normalizeColor(borderColorAttr)
|
|
114
|
+
: normalizeColor(anchorStyles['border-color']);
|
|
115
|
+
|
|
116
|
+
const borderWidthAttr = element.getAttribute('data-border-width');
|
|
117
|
+
const borderWidth = styleguideButtonStyle?.borderWidth != null
|
|
118
|
+
? { value: styleguideButtonStyle.borderWidth, unit: 'px' }
|
|
119
|
+
: borderWidthAttr
|
|
120
|
+
? parseValueWithUnit(borderWidthAttr)
|
|
121
|
+
: parseValueWithUnit(anchorStyles['border-width'] || '0');
|
|
122
|
+
|
|
123
|
+
// Shadow
|
|
124
|
+
const shadowAttr = element.getAttribute('data-shadow');
|
|
125
|
+
const shadow = shadowAttr ? parseShadow(shadowAttr) : parseShadow(anchorStyles['box-shadow']);
|
|
126
|
+
|
|
127
|
+
// Hover state from styleguide
|
|
128
|
+
const hoverBgColor = styleguideButtonStyle?.hover?.bg
|
|
129
|
+
? normalizeColor(styleguideButtonStyle.hover.bg)
|
|
130
|
+
: null;
|
|
131
|
+
const hoverTextColor = styleguideButtonStyle?.hover?.color
|
|
132
|
+
? normalizeColor(styleguideButtonStyle.hover.color)
|
|
133
|
+
: null;
|
|
134
|
+
|
|
135
|
+
// Alignment
|
|
136
|
+
const textAlign = element.getAttribute('data-align') || parseTextAlign(wrapperStyles['text-align']);
|
|
137
|
+
|
|
138
|
+
// Get main text (extract from span, excluding icons)
|
|
139
|
+
let mainText = 'Button';
|
|
140
|
+
if (textSpan) {
|
|
141
|
+
// Clone the span and remove icons to get clean text
|
|
142
|
+
const textContent = extractTextContent(textSpan).trim();
|
|
143
|
+
mainText = textContent || 'Button';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Get subtext - prefer data attribute
|
|
147
|
+
const subTextAttr = element.getAttribute('data-subtext');
|
|
148
|
+
const subTextEl = anchor ? anchor.querySelector('span:last-child:not(:first-child)') : null;
|
|
149
|
+
const subText = subTextAttr || (subTextEl ? extractTextContent(subTextEl).trim() : '');
|
|
150
|
+
const subTextColorAttr = element.getAttribute('data-subtext-color');
|
|
151
|
+
const subTextColor = subTextColorAttr
|
|
152
|
+
? normalizeColor(subTextColorAttr)
|
|
153
|
+
: 'rgba(255, 255, 255, 0.8)';
|
|
154
|
+
|
|
155
|
+
// Check for icons - prefer data attributes
|
|
156
|
+
const iconAttr = element.getAttribute('data-icon');
|
|
157
|
+
const iconPositionAttr = element.getAttribute('data-icon-position') || 'left';
|
|
158
|
+
const iconColorAttr = element.getAttribute('data-icon-color');
|
|
159
|
+
|
|
160
|
+
// Fallback to DOM parsing for icons
|
|
161
|
+
const iconBefore = anchor ? anchor.querySelector('span > i:first-child') : null;
|
|
162
|
+
const iconAfter = anchor ? anchor.querySelector('span > i:last-child') : null;
|
|
163
|
+
|
|
164
|
+
// Full width
|
|
165
|
+
const fullWidth = element.getAttribute('data-full-width') === 'true';
|
|
166
|
+
|
|
167
|
+
// Build button selector - always include padding params
|
|
168
|
+
// When using styleguide button, also include data-style-guide-button attribute
|
|
169
|
+
const buttonSelector = {
|
|
170
|
+
attrs: {
|
|
171
|
+
style: {},
|
|
172
|
+
},
|
|
173
|
+
params: {
|
|
174
|
+
'--style-padding-horizontal': paddingHorizontal ? paddingHorizontal.value : 32,
|
|
175
|
+
'--style-padding-horizontal--unit': paddingHorizontal ? paddingHorizontal.unit : 'px',
|
|
176
|
+
'--style-padding-vertical': paddingVertical ? paddingVertical.value : 16,
|
|
177
|
+
'--style-padding-vertical--unit': paddingVertical ? paddingVertical.unit : 'px',
|
|
178
|
+
'style-guide-override-button': true,
|
|
179
|
+
'--style-background-color': bgColor,
|
|
180
|
+
'--style-border-color': borderColor || 'transparent',
|
|
181
|
+
'--style-border-width': borderWidth ? borderWidth.value : 0,
|
|
182
|
+
'--style-border-width--unit': borderWidth ? borderWidth.unit : 'px',
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// Add styleguide button reference if present
|
|
187
|
+
if (styleGuideButton) {
|
|
188
|
+
buttonSelector.attrs['data-style-guide-button'] = styleGuideButton;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Parse animation attributes
|
|
192
|
+
const { attrs: animationAttrs, params: animationParams } = parseAnimationAttrs(element);
|
|
193
|
+
|
|
194
|
+
const node = {
|
|
195
|
+
type: 'Button/V1',
|
|
196
|
+
id,
|
|
197
|
+
version: 0,
|
|
198
|
+
parentId,
|
|
199
|
+
fractionalIndex: generateFractionalIndex(index),
|
|
200
|
+
attrs: {
|
|
201
|
+
style: {
|
|
202
|
+
'text-align': textAlign,
|
|
203
|
+
},
|
|
204
|
+
...animationAttrs,
|
|
205
|
+
},
|
|
206
|
+
params: {
|
|
207
|
+
buttonState: 'default',
|
|
208
|
+
href,
|
|
209
|
+
target,
|
|
210
|
+
...animationParams,
|
|
211
|
+
},
|
|
212
|
+
selectors: {
|
|
213
|
+
'.elButton': buttonSelector,
|
|
214
|
+
'.elButton .elButtonText': {
|
|
215
|
+
attrs: {
|
|
216
|
+
style: {
|
|
217
|
+
color: textColor,
|
|
218
|
+
'font-weight': fontWeight,
|
|
219
|
+
'font-size': fontSize ? fontSize.value : 20,
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
params: {
|
|
223
|
+
'font-size--unit': fontSize ? fontSize.unit : 'px',
|
|
224
|
+
'line-height--unit': '%',
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
'.elButton .elButtonSub': {
|
|
228
|
+
attrs: { style: {} },
|
|
229
|
+
params: { 'font-size--unit': 'px' },
|
|
230
|
+
},
|
|
231
|
+
'.fa_prepended': {
|
|
232
|
+
attrs: {
|
|
233
|
+
style: {
|
|
234
|
+
'margin-left': 0,
|
|
235
|
+
'margin-right': 10,
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
params: {
|
|
239
|
+
'margin-left--unit': 'px',
|
|
240
|
+
'margin-right--unit': 'px',
|
|
241
|
+
},
|
|
242
|
+
},
|
|
243
|
+
'.fa_apended': {
|
|
244
|
+
attrs: {
|
|
245
|
+
style: {
|
|
246
|
+
'margin-left': 10,
|
|
247
|
+
'margin-right': 0,
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
params: {
|
|
251
|
+
'margin-left--unit': 'px',
|
|
252
|
+
'margin-right--unit': 'px',
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
children: [
|
|
257
|
+
{
|
|
258
|
+
type: 'text',
|
|
259
|
+
innerText: mainText,
|
|
260
|
+
slotName: 'button-main',
|
|
261
|
+
id: mainTextId,
|
|
262
|
+
version: 0,
|
|
263
|
+
parentId: id,
|
|
264
|
+
fractionalIndex: 'a0',
|
|
265
|
+
},
|
|
266
|
+
],
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
// Apply spacing
|
|
270
|
+
const { attrs: spacingAttrs, params: spacingParams } = spacingToAttrsAndParams(spacing);
|
|
271
|
+
Object.assign(node.attrs.style, spacingAttrs.style);
|
|
272
|
+
Object.assign(node.params, spacingParams);
|
|
273
|
+
|
|
274
|
+
// Apply show/hide action params
|
|
275
|
+
if (showIds) {
|
|
276
|
+
node.params.showIds = showIds;
|
|
277
|
+
}
|
|
278
|
+
if (hideIds) {
|
|
279
|
+
node.params.hideIds = hideIds;
|
|
280
|
+
}
|
|
281
|
+
if (elButtonType) {
|
|
282
|
+
node.attrs['data-elbuttontype'] = elButtonType;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Apply border-radius
|
|
286
|
+
if (borderRadius) {
|
|
287
|
+
node.selectors['.elButton'].attrs.style['border-radius'] = borderRadius.value;
|
|
288
|
+
node.selectors['.elButton'].params['border-radius--unit'] = borderRadius.unit;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Apply shadow (button doesn't support shadow in CF, but keep for reference)
|
|
292
|
+
if (shadow) {
|
|
293
|
+
// CF buttons don't have native shadow support
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Add subtext if present
|
|
297
|
+
if (subText) {
|
|
298
|
+
node.children.push({
|
|
299
|
+
type: 'text',
|
|
300
|
+
innerText: subText,
|
|
301
|
+
slotName: 'button-sub',
|
|
302
|
+
id: subTextId,
|
|
303
|
+
version: 0,
|
|
304
|
+
parentId: id,
|
|
305
|
+
fractionalIndex: 'Zz',
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Add icons - prefer data attributes
|
|
310
|
+
if (iconAttr) {
|
|
311
|
+
// Use data attributes for icon (more reliable)
|
|
312
|
+
const normalizedIconColor = iconColorAttr ? normalizeColor(iconColorAttr) : null;
|
|
313
|
+
if (iconPositionAttr === 'left') {
|
|
314
|
+
node.params.iconBefore = iconAttr;
|
|
315
|
+
if (normalizedIconColor) {
|
|
316
|
+
node.selectors['.fa_prepended'].attrs.style.color = normalizedIconColor;
|
|
317
|
+
}
|
|
318
|
+
} else {
|
|
319
|
+
node.params.iconAfter = iconAttr;
|
|
320
|
+
if (normalizedIconColor) {
|
|
321
|
+
node.selectors['.fa_apended'].attrs.style.color = normalizedIconColor;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
} else if (iconBefore) {
|
|
325
|
+
// Fallback to DOM parsing
|
|
326
|
+
const iconClass = iconBefore.getAttribute('class') || '';
|
|
327
|
+
node.params.iconBefore = iconClass;
|
|
328
|
+
const iconColor = normalizeColor(iconBefore.style.color);
|
|
329
|
+
if (iconColor) {
|
|
330
|
+
node.selectors['.fa_prepended'].attrs.style.color = iconColor;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
if (!iconAttr && iconAfter) {
|
|
335
|
+
const iconClass = iconAfter.getAttribute('class') || '';
|
|
336
|
+
node.params.iconAfter = iconClass;
|
|
337
|
+
const iconColor = normalizeColor(iconAfter.style.color);
|
|
338
|
+
if (iconColor) {
|
|
339
|
+
node.selectors['.fa_apended'].attrs.style.color = iconColor;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Handle full width
|
|
344
|
+
if (fullWidth) {
|
|
345
|
+
// Ensure style object exists for width
|
|
346
|
+
if (!node.selectors['.elButton'].attrs.style) {
|
|
347
|
+
node.selectors['.elButton'].attrs.style = {};
|
|
348
|
+
}
|
|
349
|
+
node.selectors['.elButton'].attrs.style.width = '100%';
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Handle subtext color
|
|
353
|
+
if (subText && subTextColor) {
|
|
354
|
+
node.selectors['.elButton .elButtonSub'].attrs.style.color = subTextColor;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Add hover state selectors if available from styleguide
|
|
358
|
+
if (hoverBgColor) {
|
|
359
|
+
node.selectors['.elButton:hover'] = {
|
|
360
|
+
attrs: {
|
|
361
|
+
style: {},
|
|
362
|
+
},
|
|
363
|
+
params: {
|
|
364
|
+
'--style-background-color': hoverBgColor,
|
|
365
|
+
},
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
if (hoverTextColor) {
|
|
369
|
+
node.selectors['.elButton:hover .elButtonText'] = {
|
|
370
|
+
attrs: {
|
|
371
|
+
style: {
|
|
372
|
+
color: hoverTextColor,
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return node;
|
|
379
|
+
}
|