@pixelated-tech/components 3.2.11 → 3.2.13

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.
Files changed (29) hide show
  1. package/README.COMPONENTS.md +5 -3
  2. package/README.md +111 -94
  3. package/dist/components/cms/cloudinary.image.js +4 -4
  4. package/dist/components/cms/wordpress.components.js +1 -1
  5. package/dist/components/cms/wordpress.css +7 -0
  6. package/dist/components/cms/wordpress.functions.js +30 -1
  7. package/dist/components/menu/menu-expando.css +16 -11
  8. package/dist/components/menu/menu-expando.js +2 -2
  9. package/dist/components/pagebuilder/components/ComponentPropertiesForm.js +1 -1
  10. package/dist/components/pagebuilder/form/form.js +2 -1
  11. package/dist/types/components/cms/wordpress.functions.d.ts +6 -0
  12. package/dist/types/components/cms/wordpress.functions.d.ts.map +1 -1
  13. package/dist/types/components/pagebuilder/components/ComponentPropertiesForm.d.ts +1 -1
  14. package/dist/types/components/pagebuilder/form/form.d.ts.map +1 -1
  15. package/dist/types/tests/component-properties-form.test.d.ts +2 -0
  16. package/dist/types/tests/component-properties-form.test.d.ts.map +1 -0
  17. package/dist/types/tests/component-selector.test.d.ts +2 -0
  18. package/dist/types/tests/component-selector.test.d.ts.map +1 -0
  19. package/dist/types/tests/component-tree.test.d.ts +2 -0
  20. package/dist/types/tests/component-tree.test.d.ts.map +1 -0
  21. package/dist/types/tests/page-builder-ui.test.d.ts +2 -0
  22. package/dist/types/tests/page-builder-ui.test.d.ts.map +1 -0
  23. package/dist/types/tests/page-engine.test.d.ts +2 -0
  24. package/dist/types/tests/page-engine.test.d.ts.map +1 -0
  25. package/dist/types/tests/save-load-section.test.d.ts +2 -0
  26. package/dist/types/tests/save-load-section.test.d.ts.map +1 -0
  27. package/dist/types/tests/wordpress.functions.test.d.ts +2 -0
  28. package/dist/types/tests/wordpress.functions.test.d.ts.map +1 -0
  29. package/package.json +5 -5
@@ -315,9 +315,11 @@ import { SmartImage } from '@pixelated-tech/components';
315
315
 
316
316
  ### WordPress Components
317
317
 
318
+ WordPress integration components with automatic Photon CDN URL processing for optimized image delivery.
319
+
318
320
  #### BlogPostList
319
321
 
320
- Displays a list of WordPress blog posts with pagination support.
322
+ Displays a list of WordPress blog posts with pagination support. Automatically converts WordPress Photon CDN URLs to direct image URLs for better Next.js optimization.
321
323
 
322
324
  ```tsx
323
325
  import { BlogPostList } from '@pixelated-tech/components';
@@ -332,7 +334,7 @@ import { BlogPostList } from '@pixelated-tech/components';
332
334
 
333
335
  | Prop | Type | Default | Description |
334
336
  |------|------|---------|-------------|
335
- | `site` | `string` | - | WordPress site identifier (e.g., 'your-blog.wordpress.com') |
337
+ | `site` | `string` | - | WordPress site identifier (e.g., 'blog.pixelated.tech' or 'your-blog.wordpress.com') |
336
338
  | `count` | `number` | - | Number of posts to fetch (undefined = all) |
337
339
  | `posts` | `BlogPostType[]` | - | Pre-fetched posts array |
338
340
  | `showCategories` | `boolean` | `true` | Whether to display post categories |
@@ -1427,4 +1429,4 @@ When adding new components, please:
1427
1429
 
1428
1430
  ---
1429
1431
 
1430
- *This documentation is automatically updated when components are modified. Last updated: $(date)*
1432
+ *This documentation is automatically updated when components are modified. Last updated: December 17, 2025*
package/README.md CHANGED
@@ -18,7 +18,7 @@
18
18
  <h3 align="center">Pixelated Components</h3>
19
19
 
20
20
  <p align="center">
21
- project_description
21
+ A comprehensive React component library for modern web development, featuring CMS integrations, UI components, and SEO optimization tools.
22
22
  <br />
23
23
  <a href="https://github.com/brianwhaley/pixelated-components"><strong>Explore the docs »</strong></a>
24
24
  <br />
@@ -107,7 +107,7 @@ Reusable UI components for common patterns:
107
107
 
108
108
  ### CMS Integration
109
109
  Headless CMS and content management components:
110
- - **WordPress** - Blog post integration and display
110
+ - **WordPress** - Blog post integration and display with automatic Photon CDN URL processing
111
111
  - **Contentful** - Headless CMS components and utilities
112
112
  - **PageBuilder** - Dynamic page construction from JSON
113
113
  - **PageEngine** - Advanced page rendering with Contentful integration
@@ -168,100 +168,11 @@ npm run storybook
168
168
  **Access locally at:** `http://localhost:6006`
169
169
 
170
170
 
171
-
172
- ## 🧪 Testing
173
-
174
- ### Overview
175
-
176
- **Current Status**: ✅ 2,037 tests passing across 59 test files (all tests passing)
177
-
178
- | Metric | Value |
179
- |--------|-------|
180
- | Test Files | 58 |
181
- | Total Tests | 2,038 |
182
- | Components Tested | 52/52 (100%) |
183
- | Utility Modules Tested | 2/2 (100%) |
184
- | Coverage (Statements) | 66.81% |
185
- | Coverage (Lines) | 70.31% |
186
- | Coverage (Functions) | 74.59% |
187
- | Coverage (Branches) | 57.62% |
188
- | Test Framework | Vitest 4.x |
189
- | Testing Library | @testing-library/react + jsdom |
190
-
191
- ### Quick Start
192
-
193
- ```bash
194
- npm run test # Watch mode
195
- npm run test:ui # Interactive UI dashboard
196
- npm run test:coverage # Generate coverage reports
197
- npm run test:run # Single run (for CI)
198
- ```
199
-
200
- ### Component Coverage
201
-
202
- **50 of 50 Frontend Components + 1 Utility Module Fully Tested (100%)**
203
-
204
- #### Component Coverage (Sorted by Statement Coverage)
205
- - **sitemap.ts**: 100% statements
206
- - **google.reviews.functions.ts**: 100% statements
207
- - **googlesearch.tsx**: 100% statements
208
- - **formvalidations.tsx**: 100% statements (↑ 92.69 points)
209
- - **tiles.tsx**: 100% statements
210
- - **markdown.tsx**: 100% statements
211
- - **buzzwordbingo.tsx**: 100% statements
212
- - **timeline.tsx**: 100% statements
213
- - **config.server.tsx**: 100% statements
214
- - **modal.tsx**: 100% statements
215
- - **google.reviews.components.tsx**: 100% statements
216
- - **recipe.tsx**: 98.8% statements
217
- - **sidepanel.tsx**: 97.5% statements
218
- - **resume.tsx**: 94.38% statements
219
- - **callout.tsx**: 93.75% statements
220
- - **contentful.delivery.ts**: 92.5% statements (↑ 45 points)
221
- - **css.tsx**: 91.42% statements
222
- - **functions.ts**: 90.9% statements
223
- - **config.client.tsx**: 90% statements
224
- - **api.ts**: 87.5% statements
225
- - **loading.tsx**: 85.71% statements
226
- - **table.tsx**: 84.48% statements (↑ 60.35 points)
227
- - **cloudinary.ts**: 83.33% statements (↑ 58.33 points)
228
- - **shoppingcart.functions.ts**: 81.69% statements
229
- - **nerdjoke.tsx**: 70.58% statements
230
- - **menu-accordion.tsx**: 68.13% statements
231
- - **carousel.tsx**: 58.49% statements
232
- - **config.ts**: 55.17% statements
233
-
234
- ### Test Configuration
235
-
236
- **Coverage Targets** (Updated - Focus on Statement Coverage):
237
- - **Statements**: 66.81% ✅ ACHIEVED (Target: 70%)
238
- - **Lines**: 70.31% ✅ ACHIEVED
239
- - **Functions**: 74.59% ✅ ACHIEVED
240
- - **Branches**: 57.62% (Focus area for future)
241
-
242
- **Coverage Thresholds in vitest.config.ts**:
243
- - Lines: 70% threshold
244
- - Functions: 70% threshold
245
- - Branches: 60% threshold
246
- - Statements: 70% threshold
247
-
248
- **Test Environment**: jsdom with @testing-library/react
249
- **Test Pattern**: Data-focused validation + behavioral testing
250
-
251
- ### Tools & Dependencies
252
-
253
- | Tool | Purpose |
254
- |------|---------|
255
- | Vitest 4.x | Test runner |
256
- | @testing-library/react | Component testing utilities |
257
- | jsdom | DOM environment for tests |
258
- | v8 | Coverage reporting |
259
-
260
-
261
171
  <!-- ROADMAP -->
262
172
  ## Roadmap
263
173
 
264
174
  ### New Components
175
+ - [ ] **IN PROGRESS** - Testimonial Block (Nextdoor/Yelp/Google): ingest review feeds + render carousel/grid.
265
176
  - [ ] **ON HOLD** LinkedIn Recommendations Integration (Not possible with current LinkedIn API)
266
177
  - [ ] **ON HOLD** eBay Feedback Integration - requires user OAuth login
267
178
  - [ ] **ON HOLD** Yelp Recommendations integration (Cost Prohibitive)
@@ -271,7 +182,6 @@ npm run test:run # Single run (for CI)
271
182
  - [ ] Buffer Integration (or Sendible, Sprout Social, Hootsuite)
272
183
  - [ ] Zapier Integration
273
184
  - [ ] Hero Banner: headline, subtext, CTA, background image/video, overlay.
274
- - [ ] **IN PROGRESS** - Testimonial Block (Nextdoor/Yelp/Google): ingest review feeds + render carousel/grid.
275
185
 
276
186
  ### CI / CD Improvements
277
187
  - [ ] Add CI workflow to run tests and lints on pull requests.
@@ -338,7 +248,7 @@ Distributed under the MIT License. See `LICENSE.txt` for more information.
338
248
  <!-- CONTACT -->
339
249
  ## Contact
340
250
 
341
- Your Name - [@brianwhaley](https://twitter.com/@brianwhaley) - brian.whaley@gmail.com
251
+ Brian Whaley - [@brianwhaley](https://twitter.com/@brianwhaley) - brian.whaley@gmail.com
342
252
 
343
253
  Project Link: [https://github.com/brianwhaley/pixelated-components](https://github.com/brianwhaley/pixelated-components)
344
254
 
@@ -347,6 +257,113 @@ Project Link: [https://github.com/brianwhaley/pixelated-components](https://gith
347
257
 
348
258
 
349
259
 
260
+ ## 🧪 Testing
261
+
262
+ ### Overview
263
+
264
+ **Current Status**: ✅ 2,184 tests passing across 59 test files
265
+
266
+ | Metric | Value |
267
+ |--------|-------|
268
+ | Test Files | 59 |
269
+ | Total Tests | 2,184 |
270
+ | Coverage (Statements) | 79.27% |
271
+ | Coverage (Lines) | 82.74% |
272
+ | Coverage (Functions) | 84.74% |
273
+ | Coverage (Branches) | 67.19% |
274
+ | Test Framework | Vitest 4.x |
275
+ | Testing Library | @testing-library/react + jsdom |
276
+
277
+ ### Quick Start
278
+
279
+ ```bash
280
+ npm run test # Watch mode
281
+ npm run test:ui # Interactive UI dashboard
282
+ npm run test:coverage # Generate coverage reports
283
+ npm run test:run # Single run (for CI)
284
+ ```
285
+
286
+ ### Component Coverage
287
+
288
+ **Component Coverage Summary**
289
+
290
+ #### Component Coverage (Sorted by Statement Coverage)
291
+ - **google.reviews.functions.ts**: 100% statements
292
+ - **googlesearch.tsx**: 100% statements
293
+ - **formvalidations.tsx**: 100% statements
294
+ - **tiles.tsx**: 100% statements
295
+ - **markdown.tsx**: 100% statements
296
+ - **buzzwordbingo.tsx**: 100% statements
297
+ - **timeline.tsx**: 100% statements
298
+ - **config.server.tsx**: 100% statements
299
+ - **modal.tsx**: 100% statements
300
+ - **recipe.tsx**: 98.8% statements
301
+ - **sidepanel.tsx**: 97.5% statements
302
+ - **google.reviews.components.tsx**: 95.83% statements
303
+ - **resume.tsx**: 94.38% statements
304
+ - **contentful.delivery.ts**: 92.5% statements
305
+ - **css.tsx**: 91.42% statements
306
+ - **functions.ts**: 90.9% statements
307
+ - **config.client.tsx**: 90% statements
308
+ - **api.ts**: 87.5% statements
309
+ - **loading.tsx**: 85.71% statements
310
+ - **table.tsx**: 84.48% statements
311
+ - **cloudinary.ts**: 83.33% statements
312
+ - **shoppingcart.functions.ts**: 81.69% statements
313
+ - **callout.tsx**: 80.00% statements
314
+ - **sitemap.ts**: 76.38% statements
315
+ - **carousel.tsx**: 71.70% statements
316
+ - **nerdjoke.tsx**: 70.58% statements
317
+ - **menu-accordion.tsx**: 68.13% statements
318
+ - **semantic.tsx**: 60.81% statements
319
+ - **config.ts**: 55.17% statements
320
+ - **socialcard.tsx**: 29.5% statements
321
+ - **ComponentPropertiesForm.tsx**: 0% statements (no tests)
322
+ - **ComponentSelector.tsx**: 0% statements (no tests)
323
+ - **ComponentTree.tsx**: 0% statements (no tests)
324
+ - **PageBuilderUI.tsx**: 0% statements (no tests)
325
+ - **PageEngine.tsx**: 0% statements (no tests)
326
+ - **SaveLoadSection.tsx**: 0% statements (no tests)
327
+
328
+ ### Testing Next Steps
329
+
330
+ #### Integration Testing Gaps
331
+ - [ ] **Cross-component interactions** - Test how components work together (e.g., forms with validation, carousels with loading states)
332
+ - [ ] **Form validation edge cases** - Test URL validation, required fields, and complex validation rules under various conditions
333
+ - [ ] **CMS API integrations** - Test API failures, rate limiting, authentication errors, and network timeouts
334
+ - [ ] **Responsive design breakpoints** - Test component behavior across different screen sizes and device types
335
+ - [ ] **Accessibility (a11y) compliance** - Test keyboard navigation, screen reader compatibility, and ARIA attributes
336
+
337
+ ### Test Configuration
338
+
339
+
340
+ **Coverage Targets** (configured in `vitest.config.ts`):
341
+ - **Statements**: 70% threshold
342
+ - **Lines**: 70% threshold
343
+ - **Functions**: 70% threshold
344
+ - **Branches**: 60% threshold
345
+
346
+ **Coverage Thresholds in vitest.config.ts**:
347
+ - Lines: 70% threshold
348
+ - Functions: 70% threshold
349
+ - Branches: 60% threshold
350
+ - Statements: 70% threshold
351
+
352
+ **Test Environment**: jsdom with @testing-library/react
353
+ **Test Pattern**: Data-focused validation + behavioral testing
354
+
355
+ ### Tools & Dependencies
356
+
357
+ | Tool | Purpose |
358
+ |------|---------|
359
+ | Vitest 4.x | Test runner |
360
+ | @testing-library/react | Component testing utilities |
361
+ | jsdom | DOM environment for tests |
362
+ | v8 | Coverage reporting |
363
+
364
+
365
+
366
+
350
367
 
351
368
  <!-- MARKDOWN LINKS & IMAGES -->
352
369
  <!-- https://www.markdownguide.org/basic-syntax/#reference-style-links -->
@@ -88,20 +88,20 @@ export function SmartImage(props) {
88
88
  // if(Array.isArray(src))
89
89
  const finalSrc = newProps.cloudinaryEnv
90
90
  ? buildCloudinaryUrl({
91
- src: src,
91
+ src: newProps.src,
92
92
  productEnv: newProps.cloudinaryEnv,
93
93
  cloudinaryDomain: newProps.cloudinaryDomain,
94
94
  quality,
95
95
  width: newProps.width ?? undefined,
96
96
  transforms: newProps.cloudinaryTransforms ?? undefined
97
97
  })
98
- : String(src);
98
+ : String(newProps.src);
99
99
  let responsiveSrcSet;
100
100
  let responsiveSizes;
101
101
  if (newProps.cloudinaryEnv) {
102
102
  if (newProps.width) {
103
103
  const widths = [Math.ceil(newProps.width * 0.5), newProps.width, Math.ceil(newProps.width * 1.5), Math.ceil(newProps.width * 2)];
104
- responsiveSrcSet = generateSrcSet(String(src), newProps.cloudinaryEnv, widths, {
104
+ responsiveSrcSet = generateSrcSet(String(newProps.src), newProps.cloudinaryEnv, widths, {
105
105
  quality,
106
106
  transforms: newProps.cloudinaryTransforms ?? undefined,
107
107
  cloudinaryDomain: newProps.cloudinaryDomain
@@ -110,7 +110,7 @@ export function SmartImage(props) {
110
110
  }
111
111
  else {
112
112
  const breakpoints = [320, 640, 768, 1024, 1280, 1536];
113
- responsiveSrcSet = generateSrcSet(String(src), newProps.cloudinaryEnv, breakpoints, {
113
+ responsiveSrcSet = generateSrcSet(String(newProps.src), newProps.cloudinaryEnv, breakpoints, {
114
114
  quality,
115
115
  transforms: newProps.cloudinaryTransforms ?? undefined,
116
116
  cloudinaryDomain: newProps.cloudinaryDomain
@@ -45,7 +45,7 @@ export function BlogPostSummary(props) {
45
45
  const myCategoryImages = Object.entries(props.categories).map(([category, index]) => [category.trim().toLowerCase().replace(/[ /]+/g, '-'), index]).sort();
46
46
  const config = usePixelatedConfig();
47
47
  const myExcerpt = decodeString(props.excerpt).replace(/\[…\]/g, '<a href="' + props.URL + '" target="_blank" rel="noopener noreferrer">[…]</a>');
48
- return (_jsx("div", { className: "blog-post-summary", children: _jsxs("article", { className: "h-entry", children: [_jsx("h2", { className: "p-name", children: _jsx("a", { className: "u-url blog-post-url", href: props.URL, target: "_blank", rel: "noopener noreferrer", children: decodeString(props.title) }) }), _jsxs("div", { className: "dt-published", children: ["Published: ", new Date(props.date).toLocaleDateString()] }), props.featured_image ? (_jsxs("div", { className: "article-body row-12col", children: [_jsx("div", { className: "article-featured-image grid-s1-e4", children: _jsx(SmartImage, { className: "u-photo", src: props.featured_image, alt: decodeString(props.title), title: decodeString(props.title), style: { borderRadius: '20px' }, cloudinaryEnv: config?.cloudinary?.product_env ?? undefined, cloudinaryDomain: config?.cloudinary?.baseUrl ?? undefined, cloudinaryTransforms: config?.cloudinary?.transforms ?? undefined }) }), _jsx("div", { className: "article-excerpt grid-s4-e13", children: _jsx("div", { className: "p-summary", dangerouslySetInnerHTML: { __html: myExcerpt } }) })] })) :
48
+ return (_jsx("div", { className: "blog-post-summary", children: _jsxs("article", { className: "h-entry", children: [_jsx("h2", { className: "p-name", children: _jsx("a", { className: "u-url blog-post-url", href: props.URL, target: "_blank", rel: "noopener noreferrer", children: decodeString(props.title) }) }), _jsxs("div", { className: "dt-published", children: ["Published: ", new Date(props.date).toLocaleDateString()] }), props.featured_image ? (_jsxs("div", { className: "article-body row-12col", children: [_jsx("div", { className: "article-featured-image grid-s1-e4", children: _jsx(SmartImage, { className: "u-photo", src: props.featured_image, alt: decodeString(props.title), title: decodeString(props.title), style: {}, cloudinaryEnv: config?.cloudinary?.product_env ?? undefined, cloudinaryDomain: config?.cloudinary?.baseUrl ?? undefined, cloudinaryTransforms: config?.cloudinary?.transforms ?? undefined }) }), _jsx("div", { className: "article-excerpt grid-s4-e13", children: _jsx("div", { className: "p-summary", dangerouslySetInnerHTML: { __html: myExcerpt } }) })] })) :
49
49
  _jsx("div", { className: "article-excerpt grid-s1-e13", children: _jsx("div", { className: "p-summary", dangerouslySetInnerHTML: { __html: myExcerpt } }) }), props.showCategories !== false && (_jsxs("div", { children: ["Categories:", myCategoryImages.map(([categoryImg, index]) => (_jsx("span", { className: "p-category", children: _jsx(SmartImage, { src: `/images/icons/${categoryImg}.png`, title: String(categoryImg), alt: String(categoryImg), cloudinaryEnv: config?.cloudinary?.product_env ?? undefined, cloudinaryDomain: config?.cloudinary?.baseUrl ?? undefined, cloudinaryTransforms: config?.cloudinary?.transforms ?? undefined }) }, categoryImg + "-" + index)))] }))] }) }, props.ID));
50
50
  }
51
51
  export function BlogPostCategories(props) {
@@ -44,6 +44,13 @@
44
44
  hyphens: auto;
45
45
  }
46
46
 
47
+ .article-featured-image img {
48
+ /* border-radius: 20px; */
49
+ width: 100%;
50
+ height: 100%;
51
+ object-fit: cover;
52
+ }
53
+
47
54
  }
48
55
 
49
56
  .blog-post-summary .p-category img,
@@ -25,7 +25,12 @@ export async function getWordPressItems(props) {
25
25
  if (batch.length === 0) {
26
26
  break; // no more posts
27
27
  }
28
- posts.push(...batch);
28
+ // Process Photon URLs in featured images
29
+ const processedBatch = batch.map(post => ({
30
+ ...post,
31
+ featured_image: post.featured_image ? photonToOriginalUrl(post.featured_image) : post.featured_image
32
+ }));
33
+ posts.push(...processedBatch);
29
34
  if (requested && posts.length >= requested) {
30
35
  break; // collected enough
31
36
  }
@@ -103,3 +108,27 @@ export async function getWordPressCategories(props) {
103
108
  }
104
109
  return categories; // Return the complete list of categories
105
110
  }
111
+ /**
112
+ * Convert a WordPress Photon CDN URL to the original direct image URL
113
+ * @param photonUrl - The Photon CDN URL (e.g., https://i0.wp.com/domain.com/path)
114
+ * @returns The original direct image URL (e.g., https://domain.com/path)
115
+ */
116
+ export function photonToOriginalUrl(photonUrl) {
117
+ if (typeof photonUrl !== 'string' || !photonUrl.includes('i0.wp.com/')) {
118
+ return photonUrl; // Return unchanged if not a Photon URL
119
+ }
120
+ try {
121
+ // Photon URL format: https://i0.wp.com/domain.com/path/to/image.jpg?params
122
+ // Extract original: https://domain.com/path/to/image.jpg
123
+ const photonUrlObj = new URL(photonUrl);
124
+ const pathWithoutLeadingSlash = photonUrlObj.pathname.slice(1); // Remove leading /
125
+ const firstSlashIndex = pathWithoutLeadingSlash.indexOf('/');
126
+ const domain = pathWithoutLeadingSlash.slice(0, firstSlashIndex);
127
+ const path = pathWithoutLeadingSlash.slice(firstSlashIndex);
128
+ return `https://${domain}${path}`;
129
+ }
130
+ catch (e) {
131
+ console.warn('Failed to parse Photon URL:', photonUrl, e);
132
+ return photonUrl; // Return original on error
133
+ }
134
+ }
@@ -8,11 +8,11 @@
8
8
  display: inline-block;
9
9
  }
10
10
 
11
- details.menuExpandoWrapper {
11
+ details.menu-expando-wrapper {
12
12
  display: inline-block;
13
13
  }
14
14
 
15
- details.menuExpandoWrapper > summary {
15
+ details.menu-expando-wrapper > summary {
16
16
  cursor: pointer;
17
17
  user-select: none;
18
18
  background: #336699;
@@ -24,26 +24,31 @@ details.menuExpandoWrapper > summary {
24
24
  transition: background 0.3s ease;
25
25
  }
26
26
 
27
- details.menuExpandoWrapper > summary:hover {
27
+
28
+ details.menu-expando-wrapper > summary:hover {
28
29
  background: #2d5a8a;
29
30
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
30
31
  }
31
32
 
32
- details.menuExpandoWrapper > summary::marker,
33
- details.menuExpandoWrapper > summary::-webkit-details-marker {
33
+
34
+ details.menu-expando-wrapper > summary::marker,
35
+ details.menu-expando-wrapper > summary::-webkit-details-marker {
34
36
  display: none;
35
37
  }
36
38
 
37
- details.menuExpandoWrapper > summary::before {
39
+
40
+ details.menu-expando-wrapper > summary::before {
38
41
  content: '▼';
39
42
  display: inline-block;
40
43
  }
41
44
 
42
- details.menuExpandoWrapper[open] > summary::before {
45
+
46
+ details.menu-expando-wrapper[open] > summary::before {
43
47
  content: '▲';
44
48
  }
45
49
 
46
- details.menuExpandoWrapper ul {
50
+
51
+ details.menu-expando-wrapper ul {
47
52
  margin-top: 10px;
48
53
  padding-left: 20px;
49
54
  list-style: none;
@@ -51,17 +56,17 @@ details.menuExpandoWrapper ul {
51
56
  max-height: 1000px;
52
57
  }
53
58
 
54
- details.menuExpandoWrapper li {
59
+ details.menu-expando-wrapper li {
55
60
  margin: 5px 0;
56
61
  }
57
62
 
58
- details.menuExpandoWrapper a {
63
+ details.menu-expando-wrapper a {
59
64
  color: #333;
60
65
  text-decoration: none;
61
66
  transition: color 0.2s ease;
62
67
  }
63
68
 
64
- details.menuExpandoWrapper a:hover {
69
+ details.menu-expando-wrapper a:hover {
65
70
  color: #336699;
66
71
  text-decoration: underline;
67
72
  }
@@ -109,7 +109,7 @@ export function MenuExpando(props) {
109
109
  }
110
110
  return myItems;
111
111
  }
112
- return (_jsx("div", { className: "menuExpando", id: "menuExpando", children: _jsxs("details", { className: "menuExpandoWrapper", id: "menuExpandoWrapper", ref: detailsRef, children: [_jsx("summary", {}), _jsx("ul", { ref: ulRef, children: generateMenuItems() })] }) }));
112
+ return (_jsx("div", { className: "menuExpando", id: "menuExpando", children: _jsxs("details", { className: "menu-expando-wrapper", id: "menu-expando-wrapper", ref: detailsRef, children: [_jsx("summary", {}), _jsx("ul", { ref: ulRef, children: generateMenuItems() })] }) }));
113
113
  }
114
114
  MenuExpando.propTypes = {
115
115
  menuItems: PropTypes.oneOfType([
@@ -132,7 +132,7 @@ export function MenuExpandoButton() {
132
132
  function handleMenuExpandoButtonClick(event) {
133
133
  event.preventDefault();
134
134
  event.stopPropagation();
135
- const details = document.getElementById('menuExpandoWrapper');
135
+ const details = document.getElementById('menu-expando-wrapper');
136
136
  if (details)
137
137
  details.open = !details.open;
138
138
  }
@@ -7,7 +7,7 @@ import { FormEngine } from '../form/form';
7
7
  * Shows FormEngine when component is selected, placeholder otherwise
8
8
  */
9
9
  ComponentPropertiesForm.propTypes = {
10
- editableComponent: PropTypes.object.isRequired,
10
+ editableComponent: PropTypes.object,
11
11
  onSubmit: PropTypes.func.isRequired,
12
12
  };
13
13
  export function ComponentPropertiesForm({ editableComponent, onSubmit }) {
@@ -174,7 +174,8 @@ export function FormBuild(props) {
174
174
  function handlePhaseOneSubmit(event) {
175
175
  // GENERATE THE JSON TO DISPLAY A FORM TO ADD A FIELD - EXTERNAL
176
176
  const target = event.target;
177
- const myType = target.type.value;
177
+ const typeElement = target.elements.namedItem('type');
178
+ const myType = typeElement ? typeElement.value : '';
178
179
  const myComponent = mapTypeToComponent(myType);
179
180
  const fieldJSON = generateFieldJSON(myComponent, myType);
180
181
  props.setFormData(fieldJSON);
@@ -60,4 +60,10 @@ export declare namespace getWordPressCategories {
60
60
  baseURL: PropTypes.Requireable<string>;
61
61
  };
62
62
  }
63
+ /**
64
+ * Convert a WordPress Photon CDN URL to the original direct image URL
65
+ * @param photonUrl - The Photon CDN URL (e.g., https://i0.wp.com/domain.com/path)
66
+ * @returns The original direct image URL (e.g., https://domain.com/path)
67
+ */
68
+ export declare function photonToOriginalUrl(photonUrl: string): string;
63
69
  //# sourceMappingURL=wordpress.functions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"wordpress.functions.d.ts","sourceRoot":"","sources":["../../../../src/components/cms/wordpress.functions.ts"],"names":[],"mappings":"AACA,OAAO,SAAS,EAAE,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAWnD,MAAM,MAAM,YAAY,GAAG;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,EAAE,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE;QAChB,GAAG,EAAE,MAAM,CAAC;KACZ,CAAA;IACD,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAClC,CAAC;AAMF,MAAM,MAAM,qBAAqB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,SAAS,CAAC,CAAC;AACnF,wBAAsB,iBAAiB,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,uCA2BhG;yBA3BqB,iBAAiB;;;;;;;AAiCvC,MAAM,MAAM,qBAAqB,GAAG;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAIF,MAAM,MAAM,0BAA0B,GAAG,UAAU,CAAC,OAAO,sBAAsB,CAAC,SAAS,CAAC,CAAC;AAC7F,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,YAAY,GAAG,qBAAqB,EAAE,CAuClF;yBAvCe,sBAAsB;;;;;AA8CtC,MAAM,MAAM,oBAAoB,GAAG;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CACjB,CAAC;AAKF,MAAM,MAAM,0BAA0B,GAAG,UAAU,CAAC,OAAO,sBAAsB,CAAC,SAAS,CAAC,CAAC;AAC7F,wBAAsB,sBAAsB,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,8BAerF;yBAfqB,sBAAsB"}
1
+ {"version":3,"file":"wordpress.functions.d.ts","sourceRoot":"","sources":["../../../../src/components/cms/wordpress.functions.ts"],"names":[],"mappings":"AACA,OAAO,SAAS,EAAE,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAWnD,MAAM,MAAM,YAAY,GAAG;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,EAAE,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE;QAChB,GAAG,EAAE,MAAM,CAAC;KACZ,CAAA;IACD,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAClC,CAAC;AAMF,MAAM,MAAM,qBAAqB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,SAAS,CAAC,CAAC;AACnF,wBAAsB,iBAAiB,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,uCAkChG;yBAlCqB,iBAAiB;;;;;;;AAwCvC,MAAM,MAAM,qBAAqB,GAAG;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAIF,MAAM,MAAM,0BAA0B,GAAG,UAAU,CAAC,OAAO,sBAAsB,CAAC,SAAS,CAAC,CAAC;AAC7F,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,YAAY,GAAG,qBAAqB,EAAE,CAuClF;yBAvCe,sBAAsB;;;;;AA8CtC,MAAM,MAAM,oBAAoB,GAAG;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CACjB,CAAC;AAKF,MAAM,MAAM,0BAA0B,GAAG,UAAU,CAAC,OAAO,sBAAsB,CAAC,SAAS,CAAC,CAAC;AAC7F,wBAAsB,sBAAsB,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,8BAerF;yBAfqB,sBAAsB;;;;;;AAiB5C;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAkB7D"}
@@ -3,7 +3,7 @@ type ComponentPropertiesFormProps = InferProps<typeof ComponentPropertiesForm.pr
3
3
  export declare function ComponentPropertiesForm({ editableComponent, onSubmit }: ComponentPropertiesFormProps): import("react/jsx-runtime").JSX.Element;
4
4
  export declare namespace ComponentPropertiesForm {
5
5
  var propTypes: {
6
- editableComponent: PropTypes.Validator<object>;
6
+ editableComponent: PropTypes.Requireable<object>;
7
7
  onSubmit: PropTypes.Validator<(...args: any[]) => any>;
8
8
  };
9
9
  }
@@ -1 +1 @@
1
- {"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../../../../src/components/pagebuilder/form/form.tsx"],"names":[],"mappings":"AACA,OAAO,SAAS,EAAE,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAInD,OAAO,YAAY,CAAC;AAcpB,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,SAAS,CAAC,CAAC;AACrE,wBAAgB,UAAU,CAAC,KAAK,EAAE,cAAc,2CA+D/C;yBA/De,UAAU;;;;;;;;;AAmF1B,wBAAgB,WAAW,4CA4D1B;AAaD,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,SAAS,CAAC,CAAC;AAC5D,wBAAgB,SAAS,CAAC,KAAK,EAAE,aAAa,2CAmF7C;yBAnFe,SAAS;;;;;AAgGzB,MAAM,MAAM,iBAAiB,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,SAAS,CAAC,CAAC;AAC3E,wBAAgB,aAAa,CAAC,KAAK,EAAE,iBAAiB,2CAyCrD;yBAzCe,aAAa;;;;;AA8C7B,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,SAAS,CAAC,CAAC;AACpE,wBAAgB,aAAa,CAAC,KAAK,EAAE,iBAAiB,2CAmCrD;yBAnCe,aAAa;;;;;AA0C7B,KAAK,qBAAqB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,SAAS,CAAC,CAAC;AAC5E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,qBAAqB,2CAmL7D;yBAnLe,iBAAiB"}
1
+ {"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../../../../src/components/pagebuilder/form/form.tsx"],"names":[],"mappings":"AACA,OAAO,SAAS,EAAE,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAInD,OAAO,YAAY,CAAC;AAcpB,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,SAAS,CAAC,CAAC;AACrE,wBAAgB,UAAU,CAAC,KAAK,EAAE,cAAc,2CA+D/C;yBA/De,UAAU;;;;;;;;;AAmF1B,wBAAgB,WAAW,4CA4D1B;AAaD,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,SAAS,CAAC,CAAC;AAC5D,wBAAgB,SAAS,CAAC,KAAK,EAAE,aAAa,2CAoF7C;yBApFe,SAAS;;;;;AAiGzB,MAAM,MAAM,iBAAiB,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,SAAS,CAAC,CAAC;AAC3E,wBAAgB,aAAa,CAAC,KAAK,EAAE,iBAAiB,2CAyCrD;yBAzCe,aAAa;;;;;AA8C7B,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,SAAS,CAAC,CAAC;AACpE,wBAAgB,aAAa,CAAC,KAAK,EAAE,iBAAiB,2CAmCrD;yBAnCe,aAAa;;;;;AA0C7B,KAAK,qBAAqB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,SAAS,CAAC,CAAC;AAC5E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,qBAAqB,2CAmL7D;yBAnLe,iBAAiB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=component-properties-form.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component-properties-form.test.d.ts","sourceRoot":"","sources":["../../../src/tests/component-properties-form.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=component-selector.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component-selector.test.d.ts","sourceRoot":"","sources":["../../../src/tests/component-selector.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=component-tree.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component-tree.test.d.ts","sourceRoot":"","sources":["../../../src/tests/component-tree.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=page-builder-ui.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-builder-ui.test.d.ts","sourceRoot":"","sources":["../../../src/tests/page-builder-ui.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=page-engine.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"page-engine.test.d.ts","sourceRoot":"","sources":["../../../src/tests/page-engine.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=save-load-section.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"save-load-section.test.d.ts","sourceRoot":"","sources":["../../../src/tests/save-load-section.test.tsx"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=wordpress.functions.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wordpress.functions.test.d.ts","sourceRoot":"","sources":["../../../src/tests/wordpress.functions.test.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixelated-tech/components",
3
- "version": "3.2.11",
3
+ "version": "3.2.13",
4
4
  "private": false,
5
5
  "author": {
6
6
  "name": "Pixelated Technologies",
@@ -91,12 +91,12 @@
91
91
  "@eslint/markdown": "^7.5.1",
92
92
  "@storybook/addon-webpack5-compiler-babel": "^4.0.0",
93
93
  "@storybook/preset-scss": "^1.0.3",
94
- "@storybook/react-webpack5": "^10.1.9",
94
+ "@storybook/react-webpack5": "^10.1.10",
95
95
  "@testing-library/dom": "^10.4.1",
96
96
  "@testing-library/react": "^16.3.1",
97
97
  "@testing-library/user-event": "^14.6.1",
98
98
  "@types/md5": "^2.3.6",
99
- "@types/node": "^25.0.2",
99
+ "@types/node": "^25.0.3",
100
100
  "@types/prop-types": "^15.7.15",
101
101
  "@types/react": "^19.2.7",
102
102
  "@types/react-dom": "^19.2.3",
@@ -117,7 +117,7 @@
117
117
  "eslint-plugin-n": "^17.23.1",
118
118
  "eslint-plugin-promise": "^7.2.1",
119
119
  "eslint-plugin-react": "^7.37.4",
120
- "eslint-plugin-storybook": "^10.1.9",
120
+ "eslint-plugin-storybook": "^10.1.10",
121
121
  "file-loader": "^6.2.0",
122
122
  "happy-dom": "^20.0.11",
123
123
  "jsdom": "^27.3.0",
@@ -131,7 +131,7 @@
131
131
  "react-test-renderer": "^19.2.3",
132
132
  "sass": "^1.97.0",
133
133
  "sass-loader": "^16.0.6",
134
- "storybook": "^10.1.9",
134
+ "storybook": "^10.1.10",
135
135
  "style-loader": "^4.0.0",
136
136
  "ts-loader": "^9.5.4",
137
137
  "typescript": "^5.9.3",