@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.
- package/README.md +123 -1
- package/dist/components/AccessibilityProvider.d.ts +64 -0
- package/dist/components/AccessibilityProvider.d.ts.map +1 -0
- package/dist/components/Breadcrumbs.d.ts +39 -0
- package/dist/components/Breadcrumbs.d.ts.map +1 -0
- package/dist/components/ErrorBoundary.d.ts +39 -0
- package/dist/components/ErrorBoundary.d.ts.map +1 -0
- package/dist/components/QwickApp.d.ts.map +1 -1
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/index.bundled.css +12 -0
- package/dist/index.esm.js +910 -44
- package/dist/index.js +916 -47
- package/dist/templates/TemplateResolver.d.ts.map +1 -1
- package/dist/utils/htmlTransform.d.ts.map +1 -1
- package/dist/utils/logger.d.ts +15 -3
- package/dist/utils/logger.d.ts.map +1 -1
- package/package.json +4 -2
- package/src/components/AccessibilityProvider.tsx +466 -0
- package/src/components/Breadcrumbs.tsx +223 -0
- package/src/components/ErrorBoundary.tsx +216 -0
- package/src/components/QwickApp.tsx +17 -11
- package/src/components/__tests__/AccessibilityProvider.test.tsx +330 -0
- package/src/components/__tests__/Breadcrumbs.test.tsx +268 -0
- package/src/components/__tests__/ErrorBoundary.test.tsx +163 -0
- package/src/components/index.ts +3 -0
- package/src/stories/AccessibilityProvider.stories.tsx +284 -0
- package/src/stories/Breadcrumbs.stories.tsx +304 -0
- package/src/stories/ErrorBoundary.stories.tsx +159 -0
- package/src/stories/{form/FormComponents.stories.tsx → FormComponents.stories.tsx} +8 -8
- package/src/templates/TemplateResolver.ts +2 -6
- package/src/utils/__tests__/nested-dom-fix.test.tsx +53 -0
- package/src/utils/__tests__/optional-logging.test.ts +83 -0
- package/src/utils/htmlTransform.tsx +69 -3
- package/src/utils/logger.ts +60 -5
- package/dist/schemas/Builders.d.ts +0 -7
- package/dist/schemas/Builders.d.ts.map +0 -1
- package/dist/schemas/types.d.ts +0 -7
- package/dist/schemas/types.d.ts.map +0 -1
- package/dist/types/DataBinding.d.ts +0 -7
- package/dist/types/DataBinding.d.ts.map +0 -1
- package/dist/types/DataProvider.d.ts +0 -7
- 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 =
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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(
|
|
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(
|
|
20562
|
-
|
|
20563
|
-
|
|
20564
|
-
|
|
20565
|
-
|
|
20566
|
-
|
|
20567
|
-
|
|
20568
|
-
|
|
20569
|
-
|
|
20570
|
-
|
|
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
|
-
|
|
24241
|
-
|
|
24242
|
-
enumerable: true,
|
|
24243
|
-
get: function () { return logging[k]; }
|
|
24244
|
-
});
|
|
24245
|
-
});
|
|
25113
|
+
exports.withAccessibility = withAccessibility;
|
|
25114
|
+
exports.withErrorBoundary = withErrorBoundary;
|