@qwickapps/react-framework 1.3.1 → 1.3.2

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 CHANGED
@@ -4,7 +4,13 @@ A complete React framework for building modern, responsive applications with int
4
4
 
5
5
  ## What's New
6
6
 
7
- ### August 31, 2025 - CMS Content Components
7
+ ### September 1, 2025 - Optional Dependencies & Performance
8
+ - **Optional Logging**: Made `@qwickapps/logging` an optional dependency with console fallback
9
+ - **Reduced Bundle Size**: Core framework no longer includes logging overhead by default
10
+ - **React Error Fixes**: Fixed React error #62 in Markdown mixed content scenarios
11
+ - **DOM Nesting**: Added automatic cleanup of invalid DOM nesting from marked.js output
12
+
13
+ ### August 31, 2025 - CMS Content Components
8
14
  - **Html Component**: Transform HTML strings into React components with configurable transformation rules
9
15
  - **Markdown Component**: Convert Markdown to React components using marked library with Html component integration
10
16
  - **Transform System**: Extensible HTML transformation architecture with optimized rule sets for Article and Markdown content
@@ -52,6 +58,16 @@ A complete React framework for building modern, responsive applications with int
52
58
  npm install @qwickapps/react-framework
53
59
  ```
54
60
 
61
+ ### Optional Dependencies
62
+
63
+ The framework uses console-based logging by default. For advanced logging features, optionally install:
64
+
65
+ ```bash
66
+ npm install @qwickapps/logging
67
+ ```
68
+
69
+ This will enable structured logging with configurable levels and outputs. If not installed, the framework automatically falls back to console logging.
70
+
55
71
  ## Quick Start
56
72
 
57
73
  ### Basic Setup
package/dist/index.esm.js CHANGED
@@ -1,8 +1,6 @@
1
1
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
2
2
  import { CircularProgress, Button as Button$1, Paper, Typography, useTheme as useTheme$1, Box, Container as Container$8, Stack, Tooltip, IconButton, Snackbar, Alert, useMediaQuery, Avatar, Chip, Menu, MenuItem, Grid, Divider, Link, ListItemIcon, ListItemText, Card, CardContent, ButtonGroup, Collapse, TextField as TextField$1, FormControl, InputLabel, Select, FormHelperText } from '@mui/material';
3
3
  import React, { useMemo, useContext, useState, useEffect, createContext, useRef, useCallback, cloneElement } from 'react';
4
- import { Logger, createLogger } from '@qwickapps/logging';
5
- export * from '@qwickapps/logging';
6
4
  import { MustacheTemplateProvider, MemoryCacheProvider, CachedDataProvider, Field, Editor, FieldType, Schema, Model, DataType } from '@qwickapps/schema';
7
5
  import { IsOptional, IsString, IsIn, IsUrl, IsArray, ValidateNested, IsNumber, Min, Max, IsBoolean, IsNotEmpty, IsObject } from 'class-validator';
8
6
  import { Type } from 'class-transformer';
@@ -468,6 +466,63 @@ function useBaseProps(props) {
468
466
  };
469
467
  }
470
468
 
469
+ /**
470
+ * QwickApps React Framework - Logger Utility
471
+ *
472
+ * Optional logging with fallback to console when @qwickapps/logging is not available
473
+ *
474
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
475
+ */
476
+ /**
477
+ * Console-based logger fallback
478
+ */
479
+ class ConsoleLogger {
480
+ constructor(name) {
481
+ this.name = name;
482
+ }
483
+ debug(message, ...args) {
484
+ {
485
+ console.debug(`[${this.name}] ${message}`, ...args);
486
+ }
487
+ }
488
+ info(message, ...args) {
489
+ console.info(`[${this.name}] ${message}`, ...args);
490
+ }
491
+ warn(message, ...args) {
492
+ console.warn(`[${this.name}] ${message}`, ...args);
493
+ }
494
+ error(message, ...args) {
495
+ console.error(`[${this.name}] ${message}`, ...args);
496
+ }
497
+ }
498
+ /**
499
+ * Create logger with optional @qwickapps/logging dependency
500
+ */
501
+ function createLogger(name) {
502
+ try {
503
+ // Try to use @qwickapps/logging if available
504
+ const logging = require('@qwickapps/logging');
505
+ return logging.createLogger(name);
506
+ } catch {
507
+ // Fallback to console-based logger
508
+ return new ConsoleLogger(name);
509
+ }
510
+ }
511
+ /**
512
+ * Framework-specific loggers
513
+ */
514
+ const loggers = {
515
+ scaffold: createLogger('Scaffold'),
516
+ navigation: createLogger('Navigation'),
517
+ auth: createLogger('Auth'),
518
+ theme: createLogger('Theme'),
519
+ palette: createLogger('Palette'),
520
+ form: createLogger('Form'),
521
+ layout: createLogger('Layout'),
522
+ menu: createLogger('Menu'),
523
+ router: createLogger('Router')
524
+ };
525
+
471
526
  /**
472
527
  * ContentResolver provides a unified interface to fetch and render content
473
528
  * using data providers, template resolvers, and optional caching.
@@ -506,10 +561,7 @@ function useBaseProps(props) {
506
561
  */
507
562
  class TemplateResolver {
508
563
  constructor(config) {
509
- this.log = new Logger({
510
- namespace: 'ContentResolver',
511
- enabled: config.enableLogging || false
512
- });
564
+ this.log = createLogger('ContentResolver');
513
565
  this.enableLogging = config.enableLogging || false;
514
566
  this.templateResolver = config.templateResolver || new MustacheTemplateProvider();
515
567
  if (config.cacheProvider === true) {
@@ -13121,6 +13173,22 @@ const defaultMarkdownRules = [
13121
13173
  return null;
13122
13174
  }
13123
13175
  }];
13176
+ /**
13177
+ * Parse style string into React style object
13178
+ */
13179
+ function parseStyleString(styleStr) {
13180
+ const styles = {};
13181
+ if (!styleStr) return styles;
13182
+ styleStr.split(';').forEach(declaration => {
13183
+ const [property, value] = declaration.split(':').map(s => s.trim());
13184
+ if (property && value) {
13185
+ // Convert kebab-case to camelCase
13186
+ const camelProperty = property.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
13187
+ styles[camelProperty] = value;
13188
+ }
13189
+ });
13190
+ return styles;
13191
+ }
13124
13192
  /**
13125
13193
  * Default fallback component - renders element as-is with SafeSpan content
13126
13194
  */
@@ -13128,6 +13196,9 @@ const defaultFallback = (element, key) => {
13128
13196
  const tagName = element.tagName.toLowerCase();
13129
13197
  // Check if this is a void element (self-closing)
13130
13198
  const voidElements = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr']);
13199
+ // Handle style attribute separately to convert to object
13200
+ const styleAttr = element.getAttribute('style');
13201
+ const style = styleAttr ? parseStyleString(styleAttr) : undefined;
13131
13202
  const props = {
13132
13203
  key,
13133
13204
  ...(element.className ? {
@@ -13136,7 +13207,10 @@ const defaultFallback = (element, key) => {
13136
13207
  ...(element.id ? {
13137
13208
  id: element.id
13138
13209
  } : {}),
13139
- ...Object.fromEntries(Array.from(element.attributes).filter(attr => !['class', 'id'].includes(attr.name)).map(attr => [attr.name, attr.value]))
13210
+ ...(style ? {
13211
+ style
13212
+ } : {}),
13213
+ ...Object.fromEntries(Array.from(element.attributes).filter(attr => !['class', 'id', 'style'].includes(attr.name)).map(attr => [attr.name, attr.value]))
13140
13214
  };
13141
13215
  if (voidElements.has(tagName)) {
13142
13216
  // Void elements can't have children
@@ -13181,6 +13255,9 @@ function transformElement(element, key, config = {
13181
13255
  const children = Array.from(element.children);
13182
13256
  const tagName = element.tagName.toLowerCase();
13183
13257
  const transformedChildren = children.map((child, index) => transformElement(child, `${key}-${index}`, config));
13258
+ // Handle style attribute separately to convert to object
13259
+ const styleAttr = element.getAttribute('style');
13260
+ const style = styleAttr ? parseStyleString(styleAttr) : undefined;
13184
13261
  return /*#__PURE__*/React.createElement(tagName, {
13185
13262
  key,
13186
13263
  ...(element.className ? {
@@ -13189,12 +13266,39 @@ function transformElement(element, key, config = {
13189
13266
  ...(element.id ? {
13190
13267
  id: element.id
13191
13268
  } : {}),
13192
- ...Object.fromEntries(Array.from(element.attributes).filter(attr => !['class', 'id'].includes(attr.name)).map(attr => [attr.name, attr.value]))
13269
+ ...(style ? {
13270
+ style
13271
+ } : {}),
13272
+ ...Object.fromEntries(Array.from(element.attributes).filter(attr => !['class', 'id', 'style'].includes(attr.name)).map(attr => [attr.name, attr.value]))
13193
13273
  }, ...transformedChildren);
13194
13274
  }
13195
13275
  // No transformation needed - use fallback
13196
13276
  return fallbackComponent(element, key);
13197
13277
  }
13278
+ /**
13279
+ * Clean up invalid DOM nesting that can cause React errors
13280
+ */
13281
+ function cleanupInvalidNesting(html) {
13282
+ const parser = new DOMParser();
13283
+ const doc = parser.parseFromString(html, 'text/html');
13284
+ // Find paragraphs that contain block-level elements (invalid nesting)
13285
+ const paragraphs = doc.querySelectorAll('p');
13286
+ const blockElements = ['div', 'section', 'article', 'aside', 'header', 'footer', 'nav', 'main', 'figure', 'blockquote', 'pre', 'table', 'form', 'fieldset', 'address'];
13287
+ paragraphs.forEach(p => {
13288
+ const hasBlockChildren = Array.from(p.children).some(child => blockElements.includes(child.tagName.toLowerCase()));
13289
+ if (hasBlockChildren) {
13290
+ // Convert paragraph to div to allow block children
13291
+ const div = doc.createElement('div');
13292
+ div.innerHTML = p.innerHTML;
13293
+ // Copy attributes
13294
+ Array.from(p.attributes).forEach(attr => {
13295
+ div.setAttribute(attr.name, attr.value);
13296
+ });
13297
+ p.parentNode?.replaceChild(div, p);
13298
+ }
13299
+ });
13300
+ return doc.body.innerHTML;
13301
+ }
13198
13302
  /**
13199
13303
  * Transform HTML string to React components
13200
13304
  */
@@ -13202,8 +13306,10 @@ function transformHtmlToReact(html, config = {
13202
13306
  rules: defaultArticleRules
13203
13307
  }) {
13204
13308
  if (!html.trim()) return [];
13309
+ // Clean up invalid DOM nesting first
13310
+ const cleanHtml = cleanupInvalidNesting(html);
13205
13311
  const parser = new DOMParser();
13206
- const doc = parser.parseFromString(html, 'text/html');
13312
+ const doc = parser.parseFromString(cleanHtml, 'text/html');
13207
13313
  return Array.from(doc.body.children).map((element, index) => transformElement(element, index.toString(), config));
13208
13314
  }
13209
13315
  /**
@@ -20128,29 +20234,6 @@ const FormPage = ({
20128
20234
  });
20129
20235
  };
20130
20236
 
20131
- /**
20132
- * QwickApps React Framework - Logger Utility
20133
- *
20134
- * Re-exports from @qwickapps/logging with framework-specific loggers
20135
- *
20136
- * Copyright (c) 2025 QwickApps.com. All rights reserved.
20137
- */
20138
- // Re-export everything from the logging package
20139
- /**
20140
- * Framework-specific loggers
20141
- */
20142
- const loggers = {
20143
- scaffold: createLogger('Scaffold'),
20144
- navigation: createLogger('Navigation'),
20145
- auth: createLogger('Auth'),
20146
- theme: createLogger('Theme'),
20147
- palette: createLogger('Palette'),
20148
- form: createLogger('Form'),
20149
- layout: createLogger('Layout'),
20150
- menu: createLogger('Menu'),
20151
- router: createLogger('Router')
20152
- };
20153
-
20154
20237
  const logger = loggers.scaffold;
20155
20238
  // Material UI breakpoints
20156
20239
  const BREAKPOINTS = {
@@ -24140,4 +24223,4 @@ var ActionType;
24140
24223
  ActionType["CANCEL"] = "cancel";
24141
24224
  })(ActionType || (ActionType = {}));
24142
24225
 
24143
- export { AVAILABLE_PALETTES, ActionModel, ActionType, AllPalettes, Article, ArticleModel, BasePage, Button, CardListGrid, CardListGridModel, ChoiceInputField, ChoiceInputFieldModel, Code, CodeModel, Content, ContentModel, CoverImageHeader, CoverImageHeaderModel, DataProvider, DataProxy, DimensionsProvider, FeatureCard, FeatureCardActionModel, FeatureCardModel, FeatureGrid, FeatureGridModel, FeatureItemModel, Footer, FooterItemModel, FooterModel, FooterSectionModel, FormBlock, FormBlockModel, FormMethod, FormPage, GridCell, GridLayout, HeaderActionModel, HeroBlock, HeroBlockModel, Html, HtmlInputField, Logo, Markdown, MetadataItemModel, Page, PageBannerHeader, PageBannerHeaderModel, PaletteAutumn, PaletteCosmic, PaletteDefault, PaletteOcean, PaletteProvider, PaletteSpring, PaletteSwitcher, PaletteSwitcherModel, PaletteWinter, ProductCard, ProductCardActionModel, ProductCardModel, ProductModel, QWICKAPP_COMPONENT, QwickApp, QwickAppsLogo, SafeSpan, SafeSpanModel, Section, SectionModel, SelectInputField, T, TextField, TextInputField, TextInputFieldModel, ThemeProvider, ThemeSwitcher, ThemeSwitcherModel, applyCustomPalette, clearUserPalettePreference, clearUserThemePreference, createPaletteFromCurrentTheme, deleteCustomPalette, exportPalette, getCSSVariable, getComputedTheme, getCurrentPalette, getCurrentTheme, getCustomPalettes, getPaletteConfig, getPaletteName, getSystemTheme, getThemePerformanceStats, importPalette, initializePalette, initializeTheme, loadUserPalettePreference, loadUserThemePreference, logThemePerformanceStats, loggers, resetThemePerformanceStats, resolveDimension, resolveDimensions, resolveSpacing, resolveSpacingProps, saveCustomPalette, savePalettePreference, saveThemePreference, saveUserPalettePreference, saveUserThemePreference, setCSSVariable, setPalette, setTheme, t, useBaseProps, useData, useDataBinding, useDataContext, useDataProvider, useDimensions, usePalette, useQwickApp, useResolveTemplate, useSafeLocation, useSafeNavigate, useTemplate, useTheme };
24226
+ export { AVAILABLE_PALETTES, ActionModel, ActionType, AllPalettes, Article, ArticleModel, BasePage, Button, CardListGrid, CardListGridModel, ChoiceInputField, ChoiceInputFieldModel, Code, CodeModel, Content, ContentModel, CoverImageHeader, CoverImageHeaderModel, DataProvider, DataProxy, DimensionsProvider, FeatureCard, FeatureCardActionModel, FeatureCardModel, FeatureGrid, FeatureGridModel, FeatureItemModel, Footer, FooterItemModel, FooterModel, FooterSectionModel, FormBlock, FormBlockModel, FormMethod, FormPage, GridCell, GridLayout, HeaderActionModel, HeroBlock, HeroBlockModel, Html, HtmlInputField, Logo, Markdown, MetadataItemModel, Page, PageBannerHeader, PageBannerHeaderModel, PaletteAutumn, PaletteCosmic, PaletteDefault, PaletteOcean, PaletteProvider, PaletteSpring, PaletteSwitcher, PaletteSwitcherModel, PaletteWinter, ProductCard, ProductCardActionModel, ProductCardModel, ProductModel, QWICKAPP_COMPONENT, QwickApp, QwickAppsLogo, SafeSpan, SafeSpanModel, Section, SectionModel, SelectInputField, T, TextField, TextInputField, TextInputFieldModel, ThemeProvider, ThemeSwitcher, ThemeSwitcherModel, applyCustomPalette, clearUserPalettePreference, clearUserThemePreference, createLogger, createPaletteFromCurrentTheme, deleteCustomPalette, exportPalette, getCSSVariable, getComputedTheme, getCurrentPalette, getCurrentTheme, getCustomPalettes, getPaletteConfig, getPaletteName, getSystemTheme, getThemePerformanceStats, importPalette, initializePalette, initializeTheme, loadUserPalettePreference, loadUserThemePreference, logThemePerformanceStats, loggers, resetThemePerformanceStats, resolveDimension, resolveDimensions, resolveSpacing, resolveSpacingProps, saveCustomPalette, savePalettePreference, saveThemePreference, saveUserPalettePreference, saveUserThemePreference, setCSSVariable, setPalette, setTheme, t, useBaseProps, useData, useDataBinding, useDataContext, useDataProvider, useDimensions, usePalette, useQwickApp, useResolveTemplate, useSafeLocation, useSafeNavigate, useTemplate, useTheme };
package/dist/index.js CHANGED
@@ -3,7 +3,6 @@
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
4
  var material = require('@mui/material');
5
5
  var React = require('react');
6
- var logging = require('@qwickapps/logging');
7
6
  var schema = require('@qwickapps/schema');
8
7
  var classValidator = require('class-validator');
9
8
  var classTransformer = require('class-transformer');
@@ -469,6 +468,63 @@ function useBaseProps(props) {
469
468
  };
470
469
  }
471
470
 
471
+ /**
472
+ * QwickApps React Framework - Logger Utility
473
+ *
474
+ * Optional logging with fallback to console when @qwickapps/logging is not available
475
+ *
476
+ * Copyright (c) 2025 QwickApps.com. All rights reserved.
477
+ */
478
+ /**
479
+ * Console-based logger fallback
480
+ */
481
+ class ConsoleLogger {
482
+ constructor(name) {
483
+ this.name = name;
484
+ }
485
+ debug(message, ...args) {
486
+ {
487
+ console.debug(`[${this.name}] ${message}`, ...args);
488
+ }
489
+ }
490
+ info(message, ...args) {
491
+ console.info(`[${this.name}] ${message}`, ...args);
492
+ }
493
+ warn(message, ...args) {
494
+ console.warn(`[${this.name}] ${message}`, ...args);
495
+ }
496
+ error(message, ...args) {
497
+ console.error(`[${this.name}] ${message}`, ...args);
498
+ }
499
+ }
500
+ /**
501
+ * Create logger with optional @qwickapps/logging dependency
502
+ */
503
+ function createLogger(name) {
504
+ try {
505
+ // Try to use @qwickapps/logging if available
506
+ const logging = require('@qwickapps/logging');
507
+ return logging.createLogger(name);
508
+ } catch {
509
+ // Fallback to console-based logger
510
+ return new ConsoleLogger(name);
511
+ }
512
+ }
513
+ /**
514
+ * Framework-specific loggers
515
+ */
516
+ const loggers = {
517
+ scaffold: createLogger('Scaffold'),
518
+ navigation: createLogger('Navigation'),
519
+ auth: createLogger('Auth'),
520
+ theme: createLogger('Theme'),
521
+ palette: createLogger('Palette'),
522
+ form: createLogger('Form'),
523
+ layout: createLogger('Layout'),
524
+ menu: createLogger('Menu'),
525
+ router: createLogger('Router')
526
+ };
527
+
472
528
  /**
473
529
  * ContentResolver provides a unified interface to fetch and render content
474
530
  * using data providers, template resolvers, and optional caching.
@@ -507,10 +563,7 @@ function useBaseProps(props) {
507
563
  */
508
564
  class TemplateResolver {
509
565
  constructor(config) {
510
- this.log = new logging.Logger({
511
- namespace: 'ContentResolver',
512
- enabled: config.enableLogging || false
513
- });
566
+ this.log = createLogger('ContentResolver');
514
567
  this.enableLogging = config.enableLogging || false;
515
568
  this.templateResolver = config.templateResolver || new schema.MustacheTemplateProvider();
516
569
  if (config.cacheProvider === true) {
@@ -13122,6 +13175,22 @@ const defaultMarkdownRules = [
13122
13175
  return null;
13123
13176
  }
13124
13177
  }];
13178
+ /**
13179
+ * Parse style string into React style object
13180
+ */
13181
+ function parseStyleString(styleStr) {
13182
+ const styles = {};
13183
+ if (!styleStr) return styles;
13184
+ styleStr.split(';').forEach(declaration => {
13185
+ const [property, value] = declaration.split(':').map(s => s.trim());
13186
+ if (property && value) {
13187
+ // Convert kebab-case to camelCase
13188
+ const camelProperty = property.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
13189
+ styles[camelProperty] = value;
13190
+ }
13191
+ });
13192
+ return styles;
13193
+ }
13125
13194
  /**
13126
13195
  * Default fallback component - renders element as-is with SafeSpan content
13127
13196
  */
@@ -13129,6 +13198,9 @@ const defaultFallback = (element, key) => {
13129
13198
  const tagName = element.tagName.toLowerCase();
13130
13199
  // Check if this is a void element (self-closing)
13131
13200
  const voidElements = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr']);
13201
+ // Handle style attribute separately to convert to object
13202
+ const styleAttr = element.getAttribute('style');
13203
+ const style = styleAttr ? parseStyleString(styleAttr) : undefined;
13132
13204
  const props = {
13133
13205
  key,
13134
13206
  ...(element.className ? {
@@ -13137,7 +13209,10 @@ const defaultFallback = (element, key) => {
13137
13209
  ...(element.id ? {
13138
13210
  id: element.id
13139
13211
  } : {}),
13140
- ...Object.fromEntries(Array.from(element.attributes).filter(attr => !['class', 'id'].includes(attr.name)).map(attr => [attr.name, attr.value]))
13212
+ ...(style ? {
13213
+ style
13214
+ } : {}),
13215
+ ...Object.fromEntries(Array.from(element.attributes).filter(attr => !['class', 'id', 'style'].includes(attr.name)).map(attr => [attr.name, attr.value]))
13141
13216
  };
13142
13217
  if (voidElements.has(tagName)) {
13143
13218
  // Void elements can't have children
@@ -13182,6 +13257,9 @@ function transformElement(element, key, config = {
13182
13257
  const children = Array.from(element.children);
13183
13258
  const tagName = element.tagName.toLowerCase();
13184
13259
  const transformedChildren = children.map((child, index) => transformElement(child, `${key}-${index}`, config));
13260
+ // Handle style attribute separately to convert to object
13261
+ const styleAttr = element.getAttribute('style');
13262
+ const style = styleAttr ? parseStyleString(styleAttr) : undefined;
13185
13263
  return /*#__PURE__*/React.createElement(tagName, {
13186
13264
  key,
13187
13265
  ...(element.className ? {
@@ -13190,12 +13268,39 @@ function transformElement(element, key, config = {
13190
13268
  ...(element.id ? {
13191
13269
  id: element.id
13192
13270
  } : {}),
13193
- ...Object.fromEntries(Array.from(element.attributes).filter(attr => !['class', 'id'].includes(attr.name)).map(attr => [attr.name, attr.value]))
13271
+ ...(style ? {
13272
+ style
13273
+ } : {}),
13274
+ ...Object.fromEntries(Array.from(element.attributes).filter(attr => !['class', 'id', 'style'].includes(attr.name)).map(attr => [attr.name, attr.value]))
13194
13275
  }, ...transformedChildren);
13195
13276
  }
13196
13277
  // No transformation needed - use fallback
13197
13278
  return fallbackComponent(element, key);
13198
13279
  }
13280
+ /**
13281
+ * Clean up invalid DOM nesting that can cause React errors
13282
+ */
13283
+ function cleanupInvalidNesting(html) {
13284
+ const parser = new DOMParser();
13285
+ const doc = parser.parseFromString(html, 'text/html');
13286
+ // Find paragraphs that contain block-level elements (invalid nesting)
13287
+ const paragraphs = doc.querySelectorAll('p');
13288
+ const blockElements = ['div', 'section', 'article', 'aside', 'header', 'footer', 'nav', 'main', 'figure', 'blockquote', 'pre', 'table', 'form', 'fieldset', 'address'];
13289
+ paragraphs.forEach(p => {
13290
+ const hasBlockChildren = Array.from(p.children).some(child => blockElements.includes(child.tagName.toLowerCase()));
13291
+ if (hasBlockChildren) {
13292
+ // Convert paragraph to div to allow block children
13293
+ const div = doc.createElement('div');
13294
+ div.innerHTML = p.innerHTML;
13295
+ // Copy attributes
13296
+ Array.from(p.attributes).forEach(attr => {
13297
+ div.setAttribute(attr.name, attr.value);
13298
+ });
13299
+ p.parentNode?.replaceChild(div, p);
13300
+ }
13301
+ });
13302
+ return doc.body.innerHTML;
13303
+ }
13199
13304
  /**
13200
13305
  * Transform HTML string to React components
13201
13306
  */
@@ -13203,8 +13308,10 @@ function transformHtmlToReact(html, config = {
13203
13308
  rules: defaultArticleRules
13204
13309
  }) {
13205
13310
  if (!html.trim()) return [];
13311
+ // Clean up invalid DOM nesting first
13312
+ const cleanHtml = cleanupInvalidNesting(html);
13206
13313
  const parser = new DOMParser();
13207
- const doc = parser.parseFromString(html, 'text/html');
13314
+ const doc = parser.parseFromString(cleanHtml, 'text/html');
13208
13315
  return Array.from(doc.body.children).map((element, index) => transformElement(element, index.toString(), config));
13209
13316
  }
13210
13317
  /**
@@ -20129,29 +20236,6 @@ const FormPage = ({
20129
20236
  });
20130
20237
  };
20131
20238
 
20132
- /**
20133
- * QwickApps React Framework - Logger Utility
20134
- *
20135
- * Re-exports from @qwickapps/logging with framework-specific loggers
20136
- *
20137
- * Copyright (c) 2025 QwickApps.com. All rights reserved.
20138
- */
20139
- // Re-export everything from the logging package
20140
- /**
20141
- * Framework-specific loggers
20142
- */
20143
- const loggers = {
20144
- scaffold: logging.createLogger('Scaffold'),
20145
- navigation: logging.createLogger('Navigation'),
20146
- auth: logging.createLogger('Auth'),
20147
- theme: logging.createLogger('Theme'),
20148
- palette: logging.createLogger('Palette'),
20149
- form: logging.createLogger('Form'),
20150
- layout: logging.createLogger('Layout'),
20151
- menu: logging.createLogger('Menu'),
20152
- router: logging.createLogger('Router')
20153
- };
20154
-
20155
20239
  const logger = loggers.scaffold;
20156
20240
  // Material UI breakpoints
20157
20241
  const BREAKPOINTS = {
@@ -24191,6 +24275,7 @@ exports.ThemeSwitcher = ThemeSwitcher;
24191
24275
  exports.applyCustomPalette = applyCustomPalette;
24192
24276
  exports.clearUserPalettePreference = clearUserPalettePreference;
24193
24277
  exports.clearUserThemePreference = clearUserThemePreference;
24278
+ exports.createLogger = createLogger;
24194
24279
  exports.createPaletteFromCurrentTheme = createPaletteFromCurrentTheme;
24195
24280
  exports.deleteCustomPalette = deleteCustomPalette;
24196
24281
  exports.exportPalette = exportPalette;
@@ -24237,9 +24322,3 @@ exports.useSafeLocation = useSafeLocation;
24237
24322
  exports.useSafeNavigate = useSafeNavigate;
24238
24323
  exports.useTemplate = useTemplate;
24239
24324
  exports.useTheme = useTheme;
24240
- Object.keys(logging).forEach(function (k) {
24241
- if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
24242
- enumerable: true,
24243
- get: function () { return logging[k]; }
24244
- });
24245
- });
@@ -1 +1 @@
1
- {"version":3,"file":"TemplateResolver.d.ts","sourceRoot":"","sources":["../../src/templates/TemplateResolver.ts"],"names":[],"mappings":"AAOA,OAAO,EAAsB,YAAY,EAAsD,KAAK,EAA4B,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACzK,OAAO,EAEL,iBAAiB,EAEjB,sBAAsB,EACvB,MAAM,UAAU,CAAC;AAElB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAAa,gBAAiB,YAAW,iBAAiB;IACxD,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,aAAa,CAAC,CAAwB;IAC9C,OAAO,CAAC,aAAa,CAAU;IAC/B,OAAO,CAAC,GAAG,CAAS;gBAER,MAAM,EAAE,sBAAsB;IA4BpC,GAAG,CAAC,CAAC,SAAS,KAAK,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAI5D,MAAM,CAAC,CAAC,SAAS,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC;IAIlG;;OAEG;IACG,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAG,MAAM,CAAE;IAsB3D;;;OAGG;YACW,iBAAiB;CAsChC"}
1
+ {"version":3,"file":"TemplateResolver.d.ts","sourceRoot":"","sources":["../../src/templates/TemplateResolver.ts"],"names":[],"mappings":"AAOA,OAAO,EAAsB,YAAY,EAAsD,KAAK,EAA4B,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACzK,OAAO,EAEL,iBAAiB,EAEjB,sBAAsB,EACvB,MAAM,UAAU,CAAC;AAElB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAAa,gBAAiB,YAAW,iBAAiB;IACxD,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,aAAa,CAAC,CAAwB;IAC9C,OAAO,CAAC,aAAa,CAAU;IAC/B,OAAO,CAAC,GAAG,CAAS;gBAER,MAAM,EAAE,sBAAsB;IAwBpC,GAAG,CAAC,CAAC,SAAS,KAAK,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAI5D,MAAM,CAAC,CAAC,SAAS,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC;IAIlG;;OAEG;IACG,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAG,MAAM,CAAE;IAsB3D;;;OAGG;YACW,iBAAiB;CAsChC"}
@@ -1 +1 @@
1
- {"version":3,"file":"htmlTransform.d.ts","sourceRoot":"","sources":["../../src/utils/htmlTransform.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;CAC/D;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,GAAG,CAAC;IACtB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;CACxE;AAED;;GAEG;AACH,eAAO,MAAM,mBAAmB,EAAE,aAAa,EA0G9C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oBAAoB,EAAE,aAAa,EAqC/C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,SAAS,OAAO,EAAE,KAAK,MAAM,KAAG,KAAK,CAAC,SA+BrE,CAAC;AAkBF;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,EACX,MAAM,GAAE,eAAgD,GACvD,KAAK,CAAC,SAAS,CAqCjB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,MAAM,GAAE,eAAgD,GACvD,KAAK,CAAC,SAAS,EAAE,CASnB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAuB3D"}
1
+ {"version":3,"file":"htmlTransform.d.ts","sourceRoot":"","sources":["../../src/utils/htmlTransform.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,MAAM,OAAO,CAAC;AAM1B,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;CAC/D;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,eAAe,CAAC,EAAE,GAAG,CAAC;IACtB,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;CACxE;AAED;;GAEG;AACH,eAAO,MAAM,mBAAmB,EAAE,aAAa,EA0G9C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,oBAAoB,EAAE,aAAa,EAqC/C,CAAC;AAsBF;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,SAAS,OAAO,EAAE,KAAK,MAAM,KAAG,KAAK,CAAC,SAoCrE,CAAC;AAkBF;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,OAAO,EAChB,GAAG,EAAE,MAAM,EACX,MAAM,GAAE,eAAgD,GACvD,KAAK,CAAC,SAAS,CA0CjB;AAmCD;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,MAAM,GAAE,eAAgD,GACvD,KAAK,CAAC,SAAS,EAAE,CAYnB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAuB3D"}
@@ -1,12 +1,24 @@
1
1
  /**
2
2
  * QwickApps React Framework - Logger Utility
3
3
  *
4
- * Re-exports from @qwickapps/logging with framework-specific loggers
4
+ * Optional logging with fallback to console when @qwickapps/logging is not available
5
5
  *
6
6
  * Copyright (c) 2025 QwickApps.com. All rights reserved.
7
7
  */
8
- export * from '@qwickapps/logging';
9
- import { Logger } from '@qwickapps/logging';
8
+ export interface Logger {
9
+ debug(message: string, ...args: any[]): void;
10
+ info(message: string, ...args: any[]): void;
11
+ warn(message: string, ...args: any[]): void;
12
+ error(message: string, ...args: any[]): void;
13
+ }
14
+ /**
15
+ * Create logger with optional @qwickapps/logging dependency
16
+ */
17
+ declare function createLogger(name: string): Logger;
18
+ /**
19
+ * Re-export Logger type and createLogger function for compatibility
20
+ */
21
+ export { createLogger };
10
22
  /**
11
23
  * Framework-specific loggers
12
24
  */
@@ -1 +1 @@
1
- {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,cAAc,oBAAoB,CAAC;AAGnC,OAAO,EAAgB,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE1D;;GAEG;AACH,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAU1C,CAAC"}
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC7C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC5C,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC5C,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;CAC9C;AAqCD;;GAEG;AACH,iBAAS,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAS1C;AAED;;GAEG;AACH,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB;;GAEG;AACH,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAU1C,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qwickapps/react-framework",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "type": "module",
5
5
  "description": "Complete React framework with responsive navigation, flexible layouts, theming system, and reusable components for building modern applications.",
6
6
  "main": "dist/index.js",
@@ -48,12 +48,14 @@
48
48
  "author": "QwickApps",
49
49
  "license": "PolyForm-Shield-1.0.0",
50
50
  "dependencies": {
51
- "@qwickapps/logging": "^1.0.0",
52
51
  "@qwickapps/schema": "^1.3.1",
53
52
  "marked": "^4.3.0",
54
53
  "md5": "^2.3.0",
55
54
  "sanitize-html": "^2.17.0"
56
55
  },
56
+ "optionalDependencies": {
57
+ "@qwickapps/logging": "^1.0.0"
58
+ },
57
59
  "peerDependencies": {
58
60
  "@emotion/react": ">=11.0.0",
59
61
  "@emotion/styled": ">=11.0.0",
@@ -4,13 +4,13 @@
4
4
 
5
5
  import { Box, Paper, Typography } from '@mui/material';
6
6
  import type { Meta, StoryObj } from '@storybook/react';
7
- import { Button } from '../../components/buttons/Button';
8
- import { TextField } from '../../components/input/TextField';
9
- import { GridCell } from '../../components/layout/GridCell';
10
- import GridLayout from '../../components/layout/GridLayout';
7
+ import { Button } from '../components/buttons/Button';
8
+ import { TextField } from '../components/input/TextField';
9
+ import { GridCell } from '../components/layout/GridCell';
10
+ import GridLayout from '../components/layout/GridLayout';
11
11
 
12
12
  const meta: Meta = {
13
- title: 'Form/Enhanced Components',
13
+ title: 'Forms/Enhanced Components',
14
14
  parameters: {
15
15
  layout: 'padded',
16
16
  docs: {
@@ -43,14 +43,14 @@ export const BasicTextField: StoryObj = {
43
43
  <TextField
44
44
  label="Small Width"
45
45
  width="small"
46
- margin="medium"
46
+ margin="normal"
47
47
  padding="small"
48
48
  />
49
49
  <br />
50
50
  <TextField
51
51
  label="Large Width"
52
52
  width="large"
53
- margin="medium"
53
+ margin="normal"
54
54
  padding="large"
55
55
  background="primary.light"
56
56
  />
@@ -73,7 +73,7 @@ export const BasicButton: StoryObj = {
73
73
  <Button
74
74
  variant="contained"
75
75
  width="large"
76
- margin="medium"
76
+ margin="normal"
77
77
  padding="large"
78
78
  >
79
79
  Large Width
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Copyright (c) 2025 QwickApps.com. All rights reserved.
6
6
  */
7
- import { Logger } from '@qwickapps/logging';
7
+ import { Logger, createLogger } from '../utils/logger';
8
8
  import { CachedDataProvider, DataResponse, ICacheProvider, IDataProvider, MemoryCacheProvider, Model, MustacheTemplateProvider, SelectOptions } from '@qwickapps/schema';
9
9
  import {
10
10
  DataProxy,
@@ -51,11 +51,7 @@ export class TemplateResolver implements ITemplateResolver {
51
51
  private log: Logger;
52
52
 
53
53
  constructor(config: TemplateResolverConfig) {
54
- this.log = new Logger({
55
- namespace: 'ContentResolver',
56
- enabled: config.enableLogging || false
57
- });
58
-
54
+ this.log = createLogger('ContentResolver');
59
55
  this.enableLogging = config.enableLogging || false;
60
56
  this.templateResolver = config.templateResolver || new MustacheTemplateProvider();
61
57
  if (config.cacheProvider === true) {
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Test for DOM nesting fix in HTML transformation
3
+ */
4
+
5
+ import { transformHtmlToReact, defaultMarkdownRules } from '../htmlTransform';
6
+
7
+ describe('DOM nesting fix', () => {
8
+ it('should fix invalid paragraph nesting with block elements', () => {
9
+ // This is the problematic HTML that marked.js creates from mixed content
10
+ const invalidHtml = `
11
+ <p>You can mix HTML with Markdown when needed:</p>
12
+ <p><div style="padding: 16px; border: 1px solid #ccc;">
13
+ This is <strong>HTML with Markdown</strong> inside it!
14
+ </div></p>
15
+ <p>Back to regular content.</p>
16
+ `;
17
+
18
+ // Should not throw React error
19
+ expect(() => {
20
+ const result = transformHtmlToReact(invalidHtml, { rules: defaultMarkdownRules });
21
+ expect(result).toBeDefined();
22
+ expect(Array.isArray(result)).toBe(true);
23
+ }).not.toThrow();
24
+ });
25
+
26
+ it('should preserve valid nesting', () => {
27
+ const validHtml = `
28
+ <p>This is a valid paragraph with <strong>inline elements</strong>.</p>
29
+ <div>This is a valid div with block content.</div>
30
+ `;
31
+
32
+ const result = transformHtmlToReact(validHtml, { rules: defaultMarkdownRules });
33
+ expect(result).toBeDefined();
34
+ expect(result.length).toBeGreaterThan(0);
35
+ });
36
+
37
+ it('should handle deeply nested invalid structures', () => {
38
+ const deeplyNested = `
39
+ <p>
40
+ Outer paragraph
41
+ <div>
42
+ Block in paragraph
43
+ <p>Paragraph in div in paragraph</p>
44
+ </div>
45
+ </p>
46
+ `;
47
+
48
+ expect(() => {
49
+ const result = transformHtmlToReact(deeplyNested, { rules: defaultMarkdownRules });
50
+ expect(result).toBeDefined();
51
+ }).not.toThrow();
52
+ });
53
+ });
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Test for optional logging dependency fallback
3
+ */
4
+
5
+ import { createLogger, loggers } from '../logger';
6
+
7
+ // Mock the require function to simulate missing @qwickapps/logging package
8
+ const originalRequire = require;
9
+
10
+ describe('Optional logging dependency', () => {
11
+ beforeEach(() => {
12
+ // Reset console spy before each test
13
+ jest.clearAllMocks();
14
+ });
15
+
16
+ afterEach(() => {
17
+ // Restore original require
18
+ (global as any).require = originalRequire;
19
+ });
20
+
21
+ it('should use console fallback when @qwickapps/logging is not available', () => {
22
+ // Mock require to throw an error (simulating missing package)
23
+ (global as any).require = jest.fn().mockImplementation((module: string) => {
24
+ if (module === '@qwickapps/logging') {
25
+ throw new Error('Cannot find module');
26
+ }
27
+ return originalRequire(module);
28
+ });
29
+
30
+ // Spy on console methods
31
+ const consoleSpy = jest.spyOn(console, 'info').mockImplementation();
32
+
33
+ // Create a new logger (should use fallback)
34
+ const logger = createLogger('TestLogger');
35
+ logger.info('Test message');
36
+
37
+ expect(consoleSpy).toHaveBeenCalledWith('[TestLogger] Test message');
38
+ consoleSpy.mockRestore();
39
+ });
40
+
41
+ it('should have all framework loggers available', () => {
42
+ expect(loggers).toHaveProperty('scaffold');
43
+ expect(loggers).toHaveProperty('navigation');
44
+ expect(loggers).toHaveProperty('auth');
45
+ expect(loggers).toHaveProperty('theme');
46
+ expect(loggers).toHaveProperty('palette');
47
+ expect(loggers).toHaveProperty('form');
48
+ expect(loggers).toHaveProperty('layout');
49
+ expect(loggers).toHaveProperty('menu');
50
+ expect(loggers).toHaveProperty('router');
51
+
52
+ // Each logger should have the required methods
53
+ Object.values(loggers).forEach(logger => {
54
+ expect(typeof logger.debug).toBe('function');
55
+ expect(typeof logger.info).toBe('function');
56
+ expect(typeof logger.warn).toBe('function');
57
+ expect(typeof logger.error).toBe('function');
58
+ });
59
+ });
60
+
61
+ it('should suppress debug logs in production', () => {
62
+ const originalEnv = process.env.NODE_ENV;
63
+ process.env.NODE_ENV = 'production';
64
+
65
+ // Mock require to throw an error (use fallback)
66
+ (global as any).require = jest.fn().mockImplementation((module: string) => {
67
+ if (module === '@qwickapps/logging') {
68
+ throw new Error('Cannot find module');
69
+ }
70
+ return originalRequire(module);
71
+ });
72
+
73
+ const debugSpy = jest.spyOn(console, 'debug').mockImplementation();
74
+
75
+ const logger = createLogger('TestLogger');
76
+ logger.debug('Debug message');
77
+
78
+ expect(debugSpy).not.toHaveBeenCalled();
79
+
80
+ debugSpy.mockRestore();
81
+ process.env.NODE_ENV = originalEnv;
82
+ });
83
+ });
@@ -179,6 +179,26 @@ export const defaultMarkdownRules: TransformRule[] = [
179
179
  }
180
180
  ];
181
181
 
182
+ /**
183
+ * Parse style string into React style object
184
+ */
185
+ function parseStyleString(styleStr: string): React.CSSProperties {
186
+ const styles: React.CSSProperties = {};
187
+
188
+ if (!styleStr) return styles;
189
+
190
+ styleStr.split(';').forEach(declaration => {
191
+ const [property, value] = declaration.split(':').map(s => s.trim());
192
+ if (property && value) {
193
+ // Convert kebab-case to camelCase
194
+ const camelProperty = property.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
195
+ styles[camelProperty as keyof React.CSSProperties] = value;
196
+ }
197
+ });
198
+
199
+ return styles;
200
+ }
201
+
182
202
  /**
183
203
  * Default fallback component - renders element as-is with SafeSpan content
184
204
  */
@@ -191,13 +211,18 @@ export const defaultFallback = (element: Element, key: string): React.ReactNode
191
211
  'link', 'meta', 'param', 'source', 'track', 'wbr'
192
212
  ]);
193
213
 
214
+ // Handle style attribute separately to convert to object
215
+ const styleAttr = element.getAttribute('style');
216
+ const style = styleAttr ? parseStyleString(styleAttr) : undefined;
217
+
194
218
  const props = {
195
219
  key,
196
220
  ...(element.className ? { className: element.className } : {}),
197
221
  ...(element.id ? { id: element.id } : {}),
222
+ ...(style ? { style } : {}),
198
223
  ...Object.fromEntries(
199
224
  Array.from(element.attributes)
200
- .filter(attr => !['class', 'id'].includes(attr.name))
225
+ .filter(attr => !['class', 'id', 'style'].includes(attr.name))
201
226
  .map(attr => [attr.name, attr.value])
202
227
  )
203
228
  };
@@ -257,15 +282,20 @@ export function transformElement(
257
282
  transformElement(child, `${key}-${index}`, config)
258
283
  );
259
284
 
285
+ // Handle style attribute separately to convert to object
286
+ const styleAttr = element.getAttribute('style');
287
+ const style = styleAttr ? parseStyleString(styleAttr) : undefined;
288
+
260
289
  return React.createElement(
261
290
  tagName,
262
291
  {
263
292
  key,
264
293
  ...(element.className ? { className: element.className } : {}),
265
294
  ...(element.id ? { id: element.id } : {}),
295
+ ...(style ? { style } : {}),
266
296
  ...Object.fromEntries(
267
297
  Array.from(element.attributes)
268
- .filter(attr => !['class', 'id'].includes(attr.name))
298
+ .filter(attr => !['class', 'id', 'style'].includes(attr.name))
269
299
  .map(attr => [attr.name, attr.value])
270
300
  )
271
301
  },
@@ -277,6 +307,39 @@ export function transformElement(
277
307
  return fallbackComponent(element, key);
278
308
  }
279
309
 
310
+ /**
311
+ * Clean up invalid DOM nesting that can cause React errors
312
+ */
313
+ function cleanupInvalidNesting(html: string): string {
314
+ const parser = new DOMParser();
315
+ const doc = parser.parseFromString(html, 'text/html');
316
+
317
+ // Find paragraphs that contain block-level elements (invalid nesting)
318
+ const paragraphs = doc.querySelectorAll('p');
319
+ const blockElements = ['div', 'section', 'article', 'aside', 'header', 'footer', 'nav', 'main', 'figure', 'blockquote', 'pre', 'table', 'form', 'fieldset', 'address'];
320
+
321
+ paragraphs.forEach(p => {
322
+ const hasBlockChildren = Array.from(p.children).some(child =>
323
+ blockElements.includes(child.tagName.toLowerCase())
324
+ );
325
+
326
+ if (hasBlockChildren) {
327
+ // Convert paragraph to div to allow block children
328
+ const div = doc.createElement('div');
329
+ div.innerHTML = p.innerHTML;
330
+
331
+ // Copy attributes
332
+ Array.from(p.attributes).forEach(attr => {
333
+ div.setAttribute(attr.name, attr.value);
334
+ });
335
+
336
+ p.parentNode?.replaceChild(div, p);
337
+ }
338
+ });
339
+
340
+ return doc.body.innerHTML;
341
+ }
342
+
280
343
  /**
281
344
  * Transform HTML string to React components
282
345
  */
@@ -286,8 +349,11 @@ export function transformHtmlToReact(
286
349
  ): React.ReactNode[] {
287
350
  if (!html.trim()) return [];
288
351
 
352
+ // Clean up invalid DOM nesting first
353
+ const cleanHtml = cleanupInvalidNesting(html);
354
+
289
355
  const parser = new DOMParser();
290
- const doc = parser.parseFromString(html, 'text/html');
356
+ const doc = parser.parseFromString(cleanHtml, 'text/html');
291
357
 
292
358
  return Array.from(doc.body.children).map((element, index) =>
293
359
  transformElement(element, index.toString(), config)
@@ -1,16 +1,71 @@
1
1
  /**
2
2
  * QwickApps React Framework - Logger Utility
3
3
  *
4
- * Re-exports from @qwickapps/logging with framework-specific loggers
4
+ * Optional logging with fallback to console when @qwickapps/logging is not available
5
5
  *
6
6
  * Copyright (c) 2025 QwickApps.com. All rights reserved.
7
7
  */
8
8
 
9
- // Re-export everything from the logging package
10
- export * from '@qwickapps/logging';
9
+ export interface Logger {
10
+ debug(message: string, ...args: any[]): void;
11
+ info(message: string, ...args: any[]): void;
12
+ warn(message: string, ...args: any[]): void;
13
+ error(message: string, ...args: any[]): void;
14
+ }
11
15
 
12
- // Import createLogger and Logger type to create framework-specific loggers
13
- import { createLogger, Logger } from '@qwickapps/logging';
16
+ /**
17
+ * Console-based logger fallback
18
+ */
19
+ class ConsoleLogger implements Logger {
20
+ constructor(private name: string) {}
21
+
22
+ debug(message: string, ...args: any[]): void {
23
+ if (process.env.NODE_ENV !== 'production') {
24
+ console.debug(`[${this.name}] ${message}`, ...args);
25
+ }
26
+ }
27
+
28
+ info(message: string, ...args: any[]): void {
29
+ console.info(`[${this.name}] ${message}`, ...args);
30
+ }
31
+
32
+ warn(message: string, ...args: any[]): void {
33
+ console.warn(`[${this.name}] ${message}`, ...args);
34
+ }
35
+
36
+ error(message: string, ...args: any[]): void {
37
+ console.error(`[${this.name}] ${message}`, ...args);
38
+ }
39
+ }
40
+
41
+ /**
42
+ * No-op logger for when logging should be disabled
43
+ */
44
+ class NoOpLogger implements Logger {
45
+ debug(): void {}
46
+ info(): void {}
47
+ warn(): void {}
48
+ error(): void {}
49
+ }
50
+
51
+ /**
52
+ * Create logger with optional @qwickapps/logging dependency
53
+ */
54
+ function createLogger(name: string): Logger {
55
+ try {
56
+ // Try to use @qwickapps/logging if available
57
+ const logging = require('@qwickapps/logging');
58
+ return logging.createLogger(name);
59
+ } catch {
60
+ // Fallback to console-based logger
61
+ return new ConsoleLogger(name);
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Re-export Logger type and createLogger function for compatibility
67
+ */
68
+ export { createLogger };
14
69
 
15
70
  /**
16
71
  * Framework-specific loggers