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