@qwickapps/react-framework 1.3.1 → 1.3.3

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 (43) hide show
  1. package/README.md +123 -1
  2. package/dist/components/AccessibilityProvider.d.ts +64 -0
  3. package/dist/components/AccessibilityProvider.d.ts.map +1 -0
  4. package/dist/components/Breadcrumbs.d.ts +39 -0
  5. package/dist/components/Breadcrumbs.d.ts.map +1 -0
  6. package/dist/components/ErrorBoundary.d.ts +39 -0
  7. package/dist/components/ErrorBoundary.d.ts.map +1 -0
  8. package/dist/components/QwickApp.d.ts.map +1 -1
  9. package/dist/components/index.d.ts +3 -0
  10. package/dist/components/index.d.ts.map +1 -1
  11. package/dist/index.bundled.css +12 -0
  12. package/dist/index.esm.js +910 -44
  13. package/dist/index.js +916 -47
  14. package/dist/templates/TemplateResolver.d.ts.map +1 -1
  15. package/dist/utils/htmlTransform.d.ts.map +1 -1
  16. package/dist/utils/logger.d.ts +15 -3
  17. package/dist/utils/logger.d.ts.map +1 -1
  18. package/package.json +4 -2
  19. package/src/components/AccessibilityProvider.tsx +466 -0
  20. package/src/components/Breadcrumbs.tsx +223 -0
  21. package/src/components/ErrorBoundary.tsx +216 -0
  22. package/src/components/QwickApp.tsx +17 -11
  23. package/src/components/__tests__/AccessibilityProvider.test.tsx +330 -0
  24. package/src/components/__tests__/Breadcrumbs.test.tsx +268 -0
  25. package/src/components/__tests__/ErrorBoundary.test.tsx +163 -0
  26. package/src/components/index.ts +3 -0
  27. package/src/stories/AccessibilityProvider.stories.tsx +284 -0
  28. package/src/stories/Breadcrumbs.stories.tsx +304 -0
  29. package/src/stories/ErrorBoundary.stories.tsx +159 -0
  30. package/src/stories/{form/FormComponents.stories.tsx → FormComponents.stories.tsx} +8 -8
  31. package/src/templates/TemplateResolver.ts +2 -6
  32. package/src/utils/__tests__/nested-dom-fix.test.tsx +53 -0
  33. package/src/utils/__tests__/optional-logging.test.ts +83 -0
  34. package/src/utils/htmlTransform.tsx +69 -3
  35. package/src/utils/logger.ts +60 -5
  36. package/dist/schemas/Builders.d.ts +0 -7
  37. package/dist/schemas/Builders.d.ts.map +0 -1
  38. package/dist/schemas/types.d.ts +0 -7
  39. package/dist/schemas/types.d.ts.map +0 -1
  40. package/dist/types/DataBinding.d.ts +0 -7
  41. package/dist/types/DataBinding.d.ts.map +0 -1
  42. package/dist/types/DataProvider.d.ts +0 -7
  43. package/dist/types/DataProvider.d.ts.map +0 -1
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 = {
@@ -20497,6 +20581,605 @@ const Scaffold = ({
20497
20581
 
20498
20582
  loggers.menu;
20499
20583
 
20584
+ /**
20585
+ * Generic ErrorBoundary component for catching and handling React errors
20586
+ *
20587
+ * Features:
20588
+ * - Catches JavaScript errors anywhere in child component tree
20589
+ * - Displays fallback UI with retry functionality
20590
+ * - Shows error details in development mode
20591
+ * - Customizable error handling and fallback UI
20592
+ * - Automatic error logging
20593
+ */
20594
+ class ErrorBoundary extends React.Component {
20595
+ constructor(props) {
20596
+ super(props);
20597
+ this.handleRetry = () => {
20598
+ this.setState({
20599
+ hasError: false,
20600
+ error: null,
20601
+ errorInfo: null
20602
+ });
20603
+ };
20604
+ this.handleRefresh = () => {
20605
+ if (typeof window !== 'undefined') {
20606
+ window.location.reload();
20607
+ }
20608
+ };
20609
+ this.state = {
20610
+ hasError: false,
20611
+ error: null,
20612
+ errorInfo: null
20613
+ };
20614
+ }
20615
+ static getDerivedStateFromError(error) {
20616
+ // Update state so the next render will show the fallback UI
20617
+ return {
20618
+ hasError: true,
20619
+ error,
20620
+ errorInfo: null
20621
+ };
20622
+ }
20623
+ componentDidCatch(error, errorInfo) {
20624
+ // Log error details
20625
+ this.setState({
20626
+ error,
20627
+ errorInfo
20628
+ });
20629
+ // Log to console for debugging
20630
+ console.error('ErrorBoundary caught an error:', error, errorInfo);
20631
+ // Custom error handler
20632
+ if (this.props.onError) {
20633
+ this.props.onError(error, errorInfo);
20634
+ }
20635
+ // Send error to logging service if available
20636
+ if (typeof window !== 'undefined') {
20637
+ // @ts-ignore - Global error logging service
20638
+ if (window.qwickapps?.logError) {
20639
+ window.qwickapps.logError(error, errorInfo);
20640
+ }
20641
+ }
20642
+ }
20643
+ render() {
20644
+ if (this.state.hasError) {
20645
+ // Custom fallback UI
20646
+ if (this.props.fallback) {
20647
+ return this.props.fallback;
20648
+ }
20649
+ // Default error UI
20650
+ return jsxRuntime.jsxs("div", {
20651
+ className: "error-boundary",
20652
+ role: "alert",
20653
+ style: {
20654
+ padding: '2rem',
20655
+ textAlign: 'center',
20656
+ backgroundColor: '#fef2f2',
20657
+ border: '1px solid #fecaca',
20658
+ borderRadius: '8px',
20659
+ margin: '1rem',
20660
+ color: '#991b1b'
20661
+ },
20662
+ children: [jsxRuntime.jsxs("div", {
20663
+ style: {
20664
+ marginBottom: '1.5rem'
20665
+ },
20666
+ children: [jsxRuntime.jsx("h2", {
20667
+ style: {
20668
+ fontSize: '1.5rem',
20669
+ fontWeight: 'bold',
20670
+ marginBottom: '0.5rem',
20671
+ color: '#991b1b'
20672
+ },
20673
+ children: "Something went wrong"
20674
+ }), jsxRuntime.jsx("p", {
20675
+ style: {
20676
+ color: '#7f1d1d',
20677
+ marginBottom: '1rem'
20678
+ },
20679
+ children: "An unexpected error occurred in the application. Please try again or refresh the page."
20680
+ })]
20681
+ }), jsxRuntime.jsxs("div", {
20682
+ style: {
20683
+ display: 'flex',
20684
+ gap: '0.75rem',
20685
+ justifyContent: 'center',
20686
+ marginBottom: '1rem'
20687
+ },
20688
+ children: [jsxRuntime.jsx(Button, {
20689
+ variant: "contained",
20690
+ onClick: this.handleRetry,
20691
+ style: {
20692
+ backgroundColor: '#dc2626',
20693
+ color: 'white'
20694
+ },
20695
+ children: "Try Again"
20696
+ }), jsxRuntime.jsx(Button, {
20697
+ variant: "outlined",
20698
+ onClick: this.handleRefresh,
20699
+ style: {
20700
+ borderColor: '#dc2626',
20701
+ color: '#dc2626'
20702
+ },
20703
+ children: "Refresh Page"
20704
+ })]
20705
+ }), this.state.error && jsxRuntime.jsxs("details", {
20706
+ style: {
20707
+ textAlign: 'left',
20708
+ marginTop: '1rem',
20709
+ padding: '1rem',
20710
+ backgroundColor: '#f9fafb',
20711
+ border: '1px solid #d1d5db',
20712
+ borderRadius: '6px'
20713
+ },
20714
+ children: [jsxRuntime.jsx("summary", {
20715
+ style: {
20716
+ cursor: 'pointer',
20717
+ fontWeight: 'bold',
20718
+ marginBottom: '0.5rem',
20719
+ color: '#374151'
20720
+ },
20721
+ children: "Error Details (Development Mode)"
20722
+ }), jsxRuntime.jsxs("pre", {
20723
+ style: {
20724
+ fontSize: '0.75rem',
20725
+ color: '#374151',
20726
+ whiteSpace: 'pre-wrap',
20727
+ overflow: 'auto'
20728
+ },
20729
+ children: [this.state.error.toString(), this.state.errorInfo?.componentStack && jsxRuntime.jsxs(jsxRuntime.Fragment, {
20730
+ children: [jsxRuntime.jsx("br", {}), jsxRuntime.jsx("br", {}), "Component Stack:", this.state.errorInfo.componentStack]
20731
+ })]
20732
+ })]
20733
+ })]
20734
+ });
20735
+ }
20736
+ return this.props.children;
20737
+ }
20738
+ }
20739
+ /**
20740
+ * Higher-order component that wraps a component with ErrorBoundary
20741
+ */
20742
+ function withErrorBoundary(WrappedComponent, errorBoundaryProps) {
20743
+ const WithErrorBoundaryComponent = props => jsxRuntime.jsx(ErrorBoundary, {
20744
+ ...errorBoundaryProps,
20745
+ children: jsxRuntime.jsx(WrappedComponent, {
20746
+ ...props
20747
+ })
20748
+ });
20749
+ WithErrorBoundaryComponent.displayName = `withErrorBoundary(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`;
20750
+ return WithErrorBoundaryComponent;
20751
+ }
20752
+
20753
+ const AccessibilityContext = /*#__PURE__*/React.createContext(null);
20754
+ // Reducer
20755
+ const accessibilityReducer = (state, action) => {
20756
+ switch (action.type) {
20757
+ case 'SET_HIGH_CONTRAST':
20758
+ return {
20759
+ ...state,
20760
+ highContrast: action.payload
20761
+ };
20762
+ case 'SET_REDUCED_MOTION':
20763
+ return {
20764
+ ...state,
20765
+ reducedMotion: action.payload
20766
+ };
20767
+ case 'SET_LARGE_TEXT':
20768
+ return {
20769
+ ...state,
20770
+ largeText: action.payload
20771
+ };
20772
+ case 'SET_FOCUS_VISIBLE':
20773
+ return {
20774
+ ...state,
20775
+ focusVisible: action.payload
20776
+ };
20777
+ case 'SET_KEYBOARD_USER':
20778
+ return {
20779
+ ...state,
20780
+ isKeyboardUser: action.payload
20781
+ };
20782
+ case 'ADD_ISSUE':
20783
+ return {
20784
+ ...state,
20785
+ issues: [...state.issues, action.payload]
20786
+ };
20787
+ case 'CLEAR_ISSUES':
20788
+ return {
20789
+ ...state,
20790
+ issues: []
20791
+ };
20792
+ case 'SET_ANNOUNCEMENT':
20793
+ return {
20794
+ ...state,
20795
+ lastAnnouncement: action.payload
20796
+ };
20797
+ default:
20798
+ return state;
20799
+ }
20800
+ };
20801
+ // Initial state
20802
+ const initialState = {
20803
+ highContrast: false,
20804
+ reducedMotion: false,
20805
+ largeText: false,
20806
+ focusVisible: true,
20807
+ isKeyboardUser: false,
20808
+ issues: [],
20809
+ lastAnnouncement: null,
20810
+ preferences: {}
20811
+ };
20812
+ // ARIA Live Manager
20813
+ class AriaLiveManager {
20814
+ constructor() {
20815
+ this.politeRegion = null;
20816
+ this.assertiveRegion = null;
20817
+ this.createLiveRegions();
20818
+ }
20819
+ createLiveRegions() {
20820
+ if (typeof document === 'undefined') return;
20821
+ // Polite announcements
20822
+ this.politeRegion = document.createElement('div');
20823
+ this.politeRegion.setAttribute('aria-live', 'polite');
20824
+ this.politeRegion.setAttribute('aria-atomic', 'true');
20825
+ this.politeRegion.setAttribute('id', 'qwickapps-aria-live-polite');
20826
+ this.politeRegion.style.cssText = `
20827
+ position: absolute !important;
20828
+ left: -10000px !important;
20829
+ width: 1px !important;
20830
+ height: 1px !important;
20831
+ overflow: hidden !important;
20832
+ `;
20833
+ document.body.appendChild(this.politeRegion);
20834
+ // Assertive announcements
20835
+ this.assertiveRegion = document.createElement('div');
20836
+ this.assertiveRegion.setAttribute('aria-live', 'assertive');
20837
+ this.assertiveRegion.setAttribute('aria-atomic', 'true');
20838
+ this.assertiveRegion.setAttribute('id', 'qwickapps-aria-live-assertive');
20839
+ this.assertiveRegion.style.cssText = `
20840
+ position: absolute !important;
20841
+ left: -10000px !important;
20842
+ width: 1px !important;
20843
+ height: 1px !important;
20844
+ overflow: hidden !important;
20845
+ `;
20846
+ document.body.appendChild(this.assertiveRegion);
20847
+ }
20848
+ announce(message, level = 'polite') {
20849
+ if (level === 'assertive') {
20850
+ this.announceAssertive(message);
20851
+ } else {
20852
+ this.announcePolite(message);
20853
+ }
20854
+ }
20855
+ announcePolite(message) {
20856
+ if (!this.politeRegion) return;
20857
+ this.politeRegion.textContent = '';
20858
+ // Small delay ensures screen readers detect the change
20859
+ setTimeout(() => {
20860
+ if (this.politeRegion) {
20861
+ this.politeRegion.textContent = message;
20862
+ }
20863
+ }, 100);
20864
+ }
20865
+ announceAssertive(message) {
20866
+ if (!this.assertiveRegion) return;
20867
+ this.assertiveRegion.textContent = '';
20868
+ // Small delay ensures screen readers detect the change
20869
+ setTimeout(() => {
20870
+ if (this.assertiveRegion) {
20871
+ this.assertiveRegion.textContent = message;
20872
+ }
20873
+ }, 100);
20874
+ }
20875
+ }
20876
+ const ariaLiveManager = new AriaLiveManager();
20877
+ /**
20878
+ * Accessibility Provider Component
20879
+ * Provides comprehensive accessibility context and utilities
20880
+ *
20881
+ * Features:
20882
+ * - System preference detection (high contrast, reduced motion)
20883
+ * - Keyboard navigation detection
20884
+ * - ARIA live announcements
20885
+ * - Focus management
20886
+ * - Accessibility auditing
20887
+ * - Settings persistence
20888
+ */
20889
+ const AccessibilityProvider = ({
20890
+ children,
20891
+ enableAudit = "development" === 'development'
20892
+ }) => {
20893
+ const [state, dispatch] = React.useReducer(accessibilityReducer, initialState);
20894
+ React.useEffect(() => {
20895
+ // Detect user preferences from system
20896
+ detectUserPreferences();
20897
+ // Set up keyboard detection
20898
+ const keyboardCleanup = setupKeyboardDetection();
20899
+ // Initialize focus management
20900
+ initializeFocusManagement();
20901
+ // Run initial accessibility audit
20902
+ if (enableAudit) {
20903
+ runAccessibilityAudit();
20904
+ }
20905
+ // Cleanup
20906
+ return () => {
20907
+ if (keyboardCleanup) keyboardCleanup();
20908
+ };
20909
+ }, [enableAudit]);
20910
+ const detectUserPreferences = () => {
20911
+ if (typeof window === 'undefined') return;
20912
+ // High contrast mode
20913
+ if (window.matchMedia && window.matchMedia('(prefers-contrast: high)').matches) {
20914
+ dispatch({
20915
+ type: 'SET_HIGH_CONTRAST',
20916
+ payload: true
20917
+ });
20918
+ }
20919
+ // Reduced motion
20920
+ if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
20921
+ dispatch({
20922
+ type: 'SET_REDUCED_MOTION',
20923
+ payload: true
20924
+ });
20925
+ }
20926
+ // Listen for changes
20927
+ if (window.matchMedia) {
20928
+ const contrastMedia = window.matchMedia('(prefers-contrast: high)');
20929
+ const motionMedia = window.matchMedia('(prefers-reduced-motion: reduce)');
20930
+ const contrastHandler = e => {
20931
+ dispatch({
20932
+ type: 'SET_HIGH_CONTRAST',
20933
+ payload: e.matches
20934
+ });
20935
+ };
20936
+ const motionHandler = e => {
20937
+ dispatch({
20938
+ type: 'SET_REDUCED_MOTION',
20939
+ payload: e.matches
20940
+ });
20941
+ };
20942
+ contrastMedia.addEventListener('change', contrastHandler);
20943
+ motionMedia.addEventListener('change', motionHandler);
20944
+ // Return cleanup function
20945
+ return () => {
20946
+ contrastMedia.removeEventListener('change', contrastHandler);
20947
+ motionMedia.removeEventListener('change', motionHandler);
20948
+ };
20949
+ }
20950
+ };
20951
+ const setupKeyboardDetection = () => {
20952
+ if (typeof document === 'undefined') return;
20953
+ let keyboardUser = false;
20954
+ const handleKeyDown = e => {
20955
+ if (e.key === 'Tab') {
20956
+ keyboardUser = true;
20957
+ dispatch({
20958
+ type: 'SET_KEYBOARD_USER',
20959
+ payload: true
20960
+ });
20961
+ document.body.classList.add('keyboard-user');
20962
+ }
20963
+ };
20964
+ const handleMouseDown = () => {
20965
+ if (keyboardUser) {
20966
+ keyboardUser = false;
20967
+ dispatch({
20968
+ type: 'SET_KEYBOARD_USER',
20969
+ payload: false
20970
+ });
20971
+ document.body.classList.remove('keyboard-user');
20972
+ }
20973
+ };
20974
+ document.addEventListener('keydown', handleKeyDown);
20975
+ document.addEventListener('mousedown', handleMouseDown);
20976
+ return () => {
20977
+ document.removeEventListener('keydown', handleKeyDown);
20978
+ document.removeEventListener('mousedown', handleMouseDown);
20979
+ };
20980
+ };
20981
+ const initializeFocusManagement = () => {
20982
+ if (typeof document === 'undefined') return;
20983
+ // Enhanced focus indicators for keyboard users
20984
+ const style = document.createElement('style');
20985
+ style.textContent = `
20986
+ .keyboard-user *:focus {
20987
+ outline: 3px solid #005cee !important;
20988
+ outline-offset: 2px !important;
20989
+ }
20990
+
20991
+ .high-contrast *:focus {
20992
+ outline: 3px solid #ffffff !important;
20993
+ outline-offset: 2px !important;
20994
+ box-shadow: 0 0 0 1px #000000 !important;
20995
+ }
20996
+
20997
+ .reduced-motion * {
20998
+ animation-duration: 0.01ms !important;
20999
+ animation-iteration-count: 1 !important;
21000
+ transition-duration: 0.01ms !important;
21001
+ }
21002
+
21003
+ .large-text {
21004
+ font-size: 1.2em !important;
21005
+ }
21006
+ `;
21007
+ document.head.appendChild(style);
21008
+ };
21009
+ const runAccessibilityAudit = () => {
21010
+ if (typeof document === 'undefined') return;
21011
+ setTimeout(() => {
21012
+ const issues = [];
21013
+ // Check for images without alt text
21014
+ const images = document.querySelectorAll('img:not([alt])');
21015
+ images.forEach(img => {
21016
+ issues.push({
21017
+ type: 'missing-alt-text',
21018
+ message: 'Image missing alt attribute',
21019
+ level: 'error',
21020
+ element: img
21021
+ });
21022
+ });
21023
+ // Check for buttons without accessible names
21024
+ const buttons = document.querySelectorAll('button:not([aria-label]):not([title])');
21025
+ buttons.forEach(button => {
21026
+ if (!button.textContent?.trim()) {
21027
+ issues.push({
21028
+ type: 'unnamed-button',
21029
+ message: 'Button missing accessible name',
21030
+ level: 'error',
21031
+ element: button
21032
+ });
21033
+ }
21034
+ });
21035
+ // Check for form inputs without labels
21036
+ const inputs = document.querySelectorAll('input:not([aria-label]):not([title])');
21037
+ inputs.forEach(input => {
21038
+ const id = input.getAttribute('id');
21039
+ if (id) {
21040
+ const label = document.querySelector(`label[for="${id}"]`);
21041
+ if (!label) {
21042
+ issues.push({
21043
+ type: 'unlabeled-input',
21044
+ message: 'Form input missing label',
21045
+ level: 'error',
21046
+ element: input
21047
+ });
21048
+ }
21049
+ } else {
21050
+ issues.push({
21051
+ type: 'unlabeled-input',
21052
+ message: 'Form input missing label',
21053
+ level: 'error',
21054
+ element: input
21055
+ });
21056
+ }
21057
+ });
21058
+ dispatch({
21059
+ type: 'CLEAR_ISSUES'
21060
+ });
21061
+ issues.forEach(issue => {
21062
+ dispatch({
21063
+ type: 'ADD_ISSUE',
21064
+ payload: issue
21065
+ });
21066
+ });
21067
+ if (issues.length > 0) {
21068
+ console.group('🔍 Accessibility Issues Found');
21069
+ issues.forEach(issue => {
21070
+ const logMethod = issue.level === 'error' ? console.error : console.warn;
21071
+ logMethod(`${issue.type}: ${issue.message}`);
21072
+ });
21073
+ console.groupEnd();
21074
+ }
21075
+ }, 1000);
21076
+ };
21077
+ // Context value
21078
+ const contextValue = {
21079
+ ...state,
21080
+ // Actions
21081
+ setHighContrast: enabled => dispatch({
21082
+ type: 'SET_HIGH_CONTRAST',
21083
+ payload: enabled
21084
+ }),
21085
+ setReducedMotion: enabled => dispatch({
21086
+ type: 'SET_REDUCED_MOTION',
21087
+ payload: enabled
21088
+ }),
21089
+ setLargeText: enabled => dispatch({
21090
+ type: 'SET_LARGE_TEXT',
21091
+ payload: enabled
21092
+ }),
21093
+ setFocusVisible: enabled => dispatch({
21094
+ type: 'SET_FOCUS_VISIBLE',
21095
+ payload: enabled
21096
+ }),
21097
+ // Utilities
21098
+ announce: (message, level = 'polite') => {
21099
+ ariaLiveManager.announce(message, level);
21100
+ dispatch({
21101
+ type: 'SET_ANNOUNCEMENT',
21102
+ payload: {
21103
+ message,
21104
+ level,
21105
+ timestamp: Date.now()
21106
+ }
21107
+ });
21108
+ },
21109
+ announcePolite: message => {
21110
+ ariaLiveManager.announcePolite(message);
21111
+ dispatch({
21112
+ type: 'SET_ANNOUNCEMENT',
21113
+ payload: {
21114
+ message,
21115
+ level: 'polite',
21116
+ timestamp: Date.now()
21117
+ }
21118
+ });
21119
+ },
21120
+ announceAssertive: message => {
21121
+ ariaLiveManager.announceAssertive(message);
21122
+ dispatch({
21123
+ type: 'SET_ANNOUNCEMENT',
21124
+ payload: {
21125
+ message,
21126
+ level: 'assertive',
21127
+ timestamp: Date.now()
21128
+ }
21129
+ });
21130
+ },
21131
+ addIssue: issue => dispatch({
21132
+ type: 'ADD_ISSUE',
21133
+ payload: issue
21134
+ }),
21135
+ clearIssues: () => dispatch({
21136
+ type: 'CLEAR_ISSUES'
21137
+ }),
21138
+ // Audit function
21139
+ runAudit: runAccessibilityAudit
21140
+ };
21141
+ // Apply CSS classes based on preferences
21142
+ React.useEffect(() => {
21143
+ if (typeof document === 'undefined') return;
21144
+ const {
21145
+ highContrast,
21146
+ reducedMotion,
21147
+ largeText
21148
+ } = state;
21149
+ document.body.classList.toggle('high-contrast', highContrast);
21150
+ document.body.classList.toggle('reduced-motion', reducedMotion);
21151
+ document.body.classList.toggle('large-text', largeText);
21152
+ }, [state.highContrast, state.reducedMotion, state.largeText]);
21153
+ return jsxRuntime.jsx(AccessibilityContext.Provider, {
21154
+ value: contextValue,
21155
+ children: children
21156
+ });
21157
+ };
21158
+ /**
21159
+ * Hook to access accessibility context
21160
+ */
21161
+ const useAccessibility = () => {
21162
+ const context = React.useContext(AccessibilityContext);
21163
+ if (!context) {
21164
+ throw new Error('useAccessibility must be used within an AccessibilityProvider');
21165
+ }
21166
+ return context;
21167
+ };
21168
+ /**
21169
+ * Higher-Order Component for accessibility enhancements
21170
+ */
21171
+ const withAccessibility = WrappedComponent => {
21172
+ const AccessibilityEnhancedComponent = props => {
21173
+ const accessibility = useAccessibility();
21174
+ return jsxRuntime.jsx(WrappedComponent, {
21175
+ ...props,
21176
+ accessibility: accessibility
21177
+ });
21178
+ };
21179
+ AccessibilityEnhancedComponent.displayName = `withAccessibility(${WrappedComponent.displayName || WrappedComponent.name || 'Component'})`;
21180
+ return AccessibilityEnhancedComponent;
21181
+ };
21182
+
20500
21183
  const QwickApp = ({
20501
21184
  children,
20502
21185
  className,
@@ -20558,16 +21241,20 @@ const QwickApp = ({
20558
21241
  dataSource: dataSource,
20559
21242
  children: content
20560
21243
  }) : content;
20561
- const appContent = jsxRuntime.jsx("div", {
20562
- className: `qwick-app ${className || ''}`,
20563
- style: style,
20564
- children: jsxRuntime.jsx(ThemeProvider, {
20565
- appId: appId,
20566
- defaultTheme: defaultTheme,
20567
- defaultPalette: defaultPalette,
20568
- children: jsxRuntime.jsx(QwickAppContext.Provider, {
20569
- value: contextValue,
20570
- children: wrappedContent
21244
+ const appContent = jsxRuntime.jsx(ErrorBoundary, {
21245
+ children: jsxRuntime.jsx(AccessibilityProvider, {
21246
+ children: jsxRuntime.jsx("div", {
21247
+ className: `qwick-app ${className || ''}`,
21248
+ style: style,
21249
+ children: jsxRuntime.jsx(ThemeProvider, {
21250
+ appId: appId,
21251
+ defaultTheme: defaultTheme,
21252
+ defaultPalette: defaultPalette,
21253
+ children: jsxRuntime.jsx(QwickAppContext.Provider, {
21254
+ value: contextValue,
21255
+ children: wrappedContent
21256
+ })
21257
+ })
20571
21258
  })
20572
21259
  })
20573
21260
  });
@@ -20675,6 +21362,186 @@ const setCSSVariable = (property, value) => {
20675
21362
  document.documentElement.style.setProperty(property, value);
20676
21363
  };
20677
21364
 
21365
+ /**
21366
+ * Generic Breadcrumbs component for navigation hierarchy
21367
+ *
21368
+ * Features:
21369
+ * - Accessible navigation with proper ARIA labels
21370
+ * - Customizable separators and icons
21371
+ * - Responsive design with item truncation
21372
+ * - Support for custom navigation handlers
21373
+ * - Keyboard navigation support
21374
+ * - Screen reader friendly
21375
+ */
21376
+ const Breadcrumbs = ({
21377
+ items,
21378
+ separator = '/',
21379
+ className = '',
21380
+ onNavigate,
21381
+ maxItems,
21382
+ showRoot = true
21383
+ }) => {
21384
+ // Filter and prepare items
21385
+ let displayItems = showRoot ? items : items.slice(1);
21386
+ // Handle max items with ellipsis
21387
+ if (maxItems && displayItems.length > maxItems) {
21388
+ const firstItems = displayItems.slice(0, 1);
21389
+ const lastItems = displayItems.slice(-Math.max(1, maxItems - 2));
21390
+ displayItems = [...firstItems, {
21391
+ label: '...',
21392
+ href: undefined,
21393
+ current: false
21394
+ }, ...lastItems];
21395
+ }
21396
+ const handleItemClick = (e, item, index) => {
21397
+ if (onNavigate) {
21398
+ e.preventDefault();
21399
+ onNavigate(item, index);
21400
+ }
21401
+ };
21402
+ const handleKeyDown = (e, item, index) => {
21403
+ if (e.key === 'Enter' || e.key === ' ') {
21404
+ e.preventDefault();
21405
+ if (onNavigate) {
21406
+ onNavigate(item, index);
21407
+ } else if (item.href) {
21408
+ window.location.href = item.href;
21409
+ }
21410
+ }
21411
+ };
21412
+ if (displayItems.length <= 1) {
21413
+ return null;
21414
+ }
21415
+ return jsxRuntime.jsx("nav", {
21416
+ className: `breadcrumbs ${className}`,
21417
+ role: "navigation",
21418
+ "aria-label": "Breadcrumb navigation",
21419
+ style: {
21420
+ display: 'flex',
21421
+ alignItems: 'center',
21422
+ fontSize: '14px',
21423
+ color: '#6b7280',
21424
+ ...defaultStyles.nav
21425
+ },
21426
+ children: jsxRuntime.jsx("ol", {
21427
+ style: {
21428
+ display: 'flex',
21429
+ alignItems: 'center',
21430
+ listStyle: 'none',
21431
+ margin: 0,
21432
+ padding: 0,
21433
+ gap: '8px'
21434
+ },
21435
+ children: displayItems.map((item, index) => {
21436
+ const isLast = index === displayItems.length - 1;
21437
+ const isClickable = (item.href || onNavigate) && !item.current && item.label !== '...';
21438
+ return jsxRuntime.jsxs("li", {
21439
+ style: {
21440
+ display: 'flex',
21441
+ alignItems: 'center'
21442
+ },
21443
+ children: [isClickable ? jsxRuntime.jsxs("a", {
21444
+ href: item.href,
21445
+ onClick: e => handleItemClick(e, item, index),
21446
+ onKeyDown: e => handleKeyDown(e, item, index),
21447
+ style: {
21448
+ ...defaultStyles.link,
21449
+ color: isLast ? '#374151' : '#6b7280',
21450
+ textDecoration: 'none',
21451
+ display: 'flex',
21452
+ alignItems: 'center',
21453
+ gap: '4px'
21454
+ },
21455
+ tabIndex: 0,
21456
+ "aria-current": item.current ? 'page' : undefined,
21457
+ children: [item.icon && jsxRuntime.jsx("span", {
21458
+ style: {
21459
+ display: 'flex',
21460
+ alignItems: 'center'
21461
+ },
21462
+ "aria-hidden": "true",
21463
+ children: item.icon
21464
+ }), jsxRuntime.jsx("span", {
21465
+ children: item.label
21466
+ })]
21467
+ }) : jsxRuntime.jsxs("span", {
21468
+ style: {
21469
+ ...defaultStyles.current,
21470
+ color: isLast ? '#111827' : '#6b7280',
21471
+ fontWeight: isLast ? 600 : 400,
21472
+ display: 'flex',
21473
+ alignItems: 'center',
21474
+ gap: '4px'
21475
+ },
21476
+ "aria-current": item.current ? 'page' : undefined,
21477
+ children: [item.icon && jsxRuntime.jsx("span", {
21478
+ style: {
21479
+ display: 'flex',
21480
+ alignItems: 'center'
21481
+ },
21482
+ "aria-hidden": "true",
21483
+ children: item.icon
21484
+ }), jsxRuntime.jsx("span", {
21485
+ children: item.label
21486
+ })]
21487
+ }), !isLast && jsxRuntime.jsx("span", {
21488
+ style: {
21489
+ display: 'flex',
21490
+ alignItems: 'center',
21491
+ marginLeft: '8px',
21492
+ color: '#d1d5db',
21493
+ fontSize: '12px'
21494
+ },
21495
+ "aria-hidden": "true",
21496
+ children: separator
21497
+ })]
21498
+ }, `${item.label}-${index}`);
21499
+ })
21500
+ })
21501
+ });
21502
+ };
21503
+ // Default styles
21504
+ const defaultStyles = {
21505
+ nav: {
21506
+ padding: '8px 0'
21507
+ },
21508
+ link: {
21509
+ transition: 'color 0.2s ease',
21510
+ cursor: 'pointer',
21511
+ borderRadius: '4px',
21512
+ padding: '4px',
21513
+ margin: '-4px'
21514
+ },
21515
+ current: {
21516
+ padding: '4px'
21517
+ }
21518
+ };
21519
+ /**
21520
+ * Hook for managing breadcrumb state
21521
+ */
21522
+ const useBreadcrumbs = () => {
21523
+ const [breadcrumbs, setBreadcrumbs] = React.useState([]);
21524
+ const addBreadcrumb = React.useCallback(item => {
21525
+ setBreadcrumbs(prev => [...prev, item]);
21526
+ }, []);
21527
+ const removeBreadcrumb = React.useCallback(index => {
21528
+ setBreadcrumbs(prev => prev.filter((_, i) => i !== index));
21529
+ }, []);
21530
+ const setBreadcrumbsCurrent = React.useCallback(items => {
21531
+ setBreadcrumbs(items);
21532
+ }, []);
21533
+ const clearBreadcrumbs = React.useCallback(() => {
21534
+ setBreadcrumbs([]);
21535
+ }, []);
21536
+ return {
21537
+ breadcrumbs,
21538
+ addBreadcrumb,
21539
+ removeBreadcrumb,
21540
+ setBreadcrumbs: setBreadcrumbsCurrent,
21541
+ clearBreadcrumbs
21542
+ };
21543
+ };
21544
+
20678
21545
  const QwickAppsLogo = props => {
20679
21546
  const {
20680
21547
  styleProps,
@@ -24142,9 +25009,11 @@ exports.ActionType = void 0;
24142
25009
  })(exports.ActionType || (exports.ActionType = {}));
24143
25010
 
24144
25011
  exports.AVAILABLE_PALETTES = AVAILABLE_PALETTES;
25012
+ exports.AccessibilityProvider = AccessibilityProvider;
24145
25013
  exports.AllPalettes = AllPalettes;
24146
25014
  exports.Article = Article;
24147
25015
  exports.BasePage = BasePage;
25016
+ exports.Breadcrumbs = Breadcrumbs;
24148
25017
  exports.Button = Button;
24149
25018
  exports.CardListGrid = CardListGrid;
24150
25019
  exports.ChoiceInputField = ChoiceInputField;
@@ -24154,6 +25023,7 @@ exports.CoverImageHeader = CoverImageHeader;
24154
25023
  exports.DataProvider = DataProvider;
24155
25024
  exports.DataProxy = DataProxy;
24156
25025
  exports.DimensionsProvider = DimensionsProvider;
25026
+ exports.ErrorBoundary = ErrorBoundary;
24157
25027
  exports.FeatureCard = FeatureCard;
24158
25028
  exports.FeatureGrid = FeatureGrid;
24159
25029
  exports.Footer = Footer;
@@ -24191,6 +25061,7 @@ exports.ThemeSwitcher = ThemeSwitcher;
24191
25061
  exports.applyCustomPalette = applyCustomPalette;
24192
25062
  exports.clearUserPalettePreference = clearUserPalettePreference;
24193
25063
  exports.clearUserThemePreference = clearUserThemePreference;
25064
+ exports.createLogger = createLogger;
24194
25065
  exports.createPaletteFromCurrentTheme = createPaletteFromCurrentTheme;
24195
25066
  exports.deleteCustomPalette = deleteCustomPalette;
24196
25067
  exports.exportPalette = exportPalette;
@@ -24224,7 +25095,9 @@ exports.setCSSVariable = setCSSVariable;
24224
25095
  exports.setPalette = setPalette;
24225
25096
  exports.setTheme = setTheme;
24226
25097
  exports.t = t;
25098
+ exports.useAccessibility = useAccessibility;
24227
25099
  exports.useBaseProps = useBaseProps;
25100
+ exports.useBreadcrumbs = useBreadcrumbs;
24228
25101
  exports.useData = useData;
24229
25102
  exports.useDataBinding = useDataBinding;
24230
25103
  exports.useDataContext = useDataContext;
@@ -24237,9 +25110,5 @@ exports.useSafeLocation = useSafeLocation;
24237
25110
  exports.useSafeNavigate = useSafeNavigate;
24238
25111
  exports.useTemplate = useTemplate;
24239
25112
  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
- });
25113
+ exports.withAccessibility = withAccessibility;
25114
+ exports.withErrorBoundary = withErrorBoundary;