@qwickapps/react-framework 1.3.2 → 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 +106 -0
- 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 +795 -12
- package/dist/index.js +800 -10
- package/package.json +1 -1
- 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/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
|
@@ -20581,6 +20581,605 @@ const Scaffold = ({
|
|
|
20581
20581
|
|
|
20582
20582
|
loggers.menu;
|
|
20583
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
|
+
|
|
20584
21183
|
const QwickApp = ({
|
|
20585
21184
|
children,
|
|
20586
21185
|
className,
|
|
@@ -20642,16 +21241,20 @@ const QwickApp = ({
|
|
|
20642
21241
|
dataSource: dataSource,
|
|
20643
21242
|
children: content
|
|
20644
21243
|
}) : content;
|
|
20645
|
-
const appContent = jsxRuntime.jsx(
|
|
20646
|
-
|
|
20647
|
-
|
|
20648
|
-
|
|
20649
|
-
|
|
20650
|
-
|
|
20651
|
-
|
|
20652
|
-
|
|
20653
|
-
|
|
20654
|
-
|
|
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
|
+
})
|
|
20655
21258
|
})
|
|
20656
21259
|
})
|
|
20657
21260
|
});
|
|
@@ -20759,6 +21362,186 @@ const setCSSVariable = (property, value) => {
|
|
|
20759
21362
|
document.documentElement.style.setProperty(property, value);
|
|
20760
21363
|
};
|
|
20761
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
|
+
|
|
20762
21545
|
const QwickAppsLogo = props => {
|
|
20763
21546
|
const {
|
|
20764
21547
|
styleProps,
|
|
@@ -24226,9 +25009,11 @@ exports.ActionType = void 0;
|
|
|
24226
25009
|
})(exports.ActionType || (exports.ActionType = {}));
|
|
24227
25010
|
|
|
24228
25011
|
exports.AVAILABLE_PALETTES = AVAILABLE_PALETTES;
|
|
25012
|
+
exports.AccessibilityProvider = AccessibilityProvider;
|
|
24229
25013
|
exports.AllPalettes = AllPalettes;
|
|
24230
25014
|
exports.Article = Article;
|
|
24231
25015
|
exports.BasePage = BasePage;
|
|
25016
|
+
exports.Breadcrumbs = Breadcrumbs;
|
|
24232
25017
|
exports.Button = Button;
|
|
24233
25018
|
exports.CardListGrid = CardListGrid;
|
|
24234
25019
|
exports.ChoiceInputField = ChoiceInputField;
|
|
@@ -24238,6 +25023,7 @@ exports.CoverImageHeader = CoverImageHeader;
|
|
|
24238
25023
|
exports.DataProvider = DataProvider;
|
|
24239
25024
|
exports.DataProxy = DataProxy;
|
|
24240
25025
|
exports.DimensionsProvider = DimensionsProvider;
|
|
25026
|
+
exports.ErrorBoundary = ErrorBoundary;
|
|
24241
25027
|
exports.FeatureCard = FeatureCard;
|
|
24242
25028
|
exports.FeatureGrid = FeatureGrid;
|
|
24243
25029
|
exports.Footer = Footer;
|
|
@@ -24309,7 +25095,9 @@ exports.setCSSVariable = setCSSVariable;
|
|
|
24309
25095
|
exports.setPalette = setPalette;
|
|
24310
25096
|
exports.setTheme = setTheme;
|
|
24311
25097
|
exports.t = t;
|
|
25098
|
+
exports.useAccessibility = useAccessibility;
|
|
24312
25099
|
exports.useBaseProps = useBaseProps;
|
|
25100
|
+
exports.useBreadcrumbs = useBreadcrumbs;
|
|
24313
25101
|
exports.useData = useData;
|
|
24314
25102
|
exports.useDataBinding = useDataBinding;
|
|
24315
25103
|
exports.useDataContext = useDataContext;
|
|
@@ -24322,3 +25110,5 @@ exports.useSafeLocation = useSafeLocation;
|
|
|
24322
25110
|
exports.useSafeNavigate = useSafeNavigate;
|
|
24323
25111
|
exports.useTemplate = useTemplate;
|
|
24324
25112
|
exports.useTheme = useTheme;
|
|
25113
|
+
exports.withAccessibility = withAccessibility;
|
|
25114
|
+
exports.withErrorBoundary = withErrorBoundary;
|