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.
@@ -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
+ }