@lumx/react 2.2.25 → 2.2.26-alpha-a11y-slideshow.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/_internal/ClickAwayProvider.js +9 -5
- package/esm/_internal/ClickAwayProvider.js.map +1 -1
- package/esm/_internal/List2.js.map +1 -1
- package/esm/_internal/ProgressTrackerStepPanel.js +2 -1
- package/esm/_internal/ProgressTrackerStepPanel.js.map +1 -1
- package/esm/_internal/Slides.js +206 -76
- package/esm/_internal/Slides.js.map +1 -1
- package/esm/_internal/TabPanel.js +2 -1
- package/esm/_internal/TabPanel.js.map +1 -1
- package/esm/_internal/progress-tracker.js +2 -1
- package/esm/_internal/progress-tracker.js.map +1 -1
- package/esm/_internal/slideshow.js +2 -0
- package/esm/_internal/slideshow.js.map +1 -1
- package/esm/_internal/state.js +145 -0
- package/esm/_internal/state.js.map +1 -0
- package/esm/_internal/tabs.js +1 -0
- package/esm/_internal/tabs.js.map +1 -1
- package/esm/_internal/useRovingTabIndex.js +9 -144
- package/esm/_internal/useRovingTabIndex.js.map +1 -1
- package/esm/index.js +3 -1
- package/esm/index.js.map +1 -1
- package/package.json +4 -4
- package/src/components/slideshow/Slides.tsx +33 -3
- package/src/components/slideshow/Slideshow.stories.tsx +90 -2
- package/src/components/slideshow/Slideshow.tsx +15 -3
- package/src/components/slideshow/SlideshowControls.stories.tsx +1 -1
- package/src/components/slideshow/SlideshowControls.tsx +43 -6
- package/src/components/slideshow/SlideshowItem.tsx +0 -5
- package/src/components/slideshow/SlideshowItemGroup.tsx +56 -0
- package/src/components/slideshow/__snapshots__/Slideshow.test.tsx.snap +10 -1
- package/src/components/slideshow/useSlideFocusManagement.tsx +67 -0
- package/src/hooks/useRovingTabIndex.tsx +9 -0
- package/src/utils/focus/constants.ts +5 -0
- package/src/utils/focus/getFirstAndLastFocusable.ts +4 -10
- package/src/utils/focus/getFocusableElements.test.ts +174 -0
- package/src/utils/focus/getFocusableElements.ts +7 -0
- package/types.d.ts +19 -5
|
@@ -1,150 +1,11 @@
|
|
|
1
|
-
import { e as _toConsumableArray
|
|
2
|
-
import {
|
|
3
|
-
import { u as uid } from '../index2.js';
|
|
4
|
-
|
|
5
|
-
var INIT_STATE = {
|
|
6
|
-
isLazy: true,
|
|
7
|
-
shouldActivateOnFocus: false,
|
|
8
|
-
activeTabIndex: 0,
|
|
9
|
-
ids: {
|
|
10
|
-
tab: [],
|
|
11
|
-
tabPanel: []
|
|
12
|
-
}
|
|
13
|
-
};
|
|
14
|
-
var reducer = function reducer(state, action) {
|
|
15
|
-
switch (action.type) {
|
|
16
|
-
case 'update':
|
|
17
|
-
return _objectSpread2({}, state, {}, action.payload);
|
|
18
|
-
|
|
19
|
-
case 'setActiveTabIndex':
|
|
20
|
-
{
|
|
21
|
-
if (state.activeTabIndex === action.payload) {
|
|
22
|
-
return state;
|
|
23
|
-
} // Change active tab index.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return _objectSpread2({}, state, {
|
|
27
|
-
activeTabIndex: action.payload
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
case 'register':
|
|
32
|
-
{
|
|
33
|
-
var _action$payload = action.payload,
|
|
34
|
-
type = _action$payload.type,
|
|
35
|
-
id = _action$payload.id; // Append tab/tabPanel id in state.
|
|
36
|
-
|
|
37
|
-
return _objectSpread2({}, state, {
|
|
38
|
-
ids: _objectSpread2({}, state.ids, _defineProperty({}, type, [].concat(_toConsumableArray(state.ids[type]), [id])))
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
case 'unregister':
|
|
43
|
-
{
|
|
44
|
-
var _action$payload2 = action.payload,
|
|
45
|
-
_type = _action$payload2.type,
|
|
46
|
-
_id = _action$payload2.id;
|
|
47
|
-
|
|
48
|
-
var index = state.ids[_type].indexOf(_id);
|
|
49
|
-
|
|
50
|
-
if (index === -1) return state; // Remove tab & tab panel at index.
|
|
51
|
-
|
|
52
|
-
var tabIds = _toConsumableArray(state.ids.tab);
|
|
53
|
-
|
|
54
|
-
tabIds.splice(index, 1);
|
|
55
|
-
|
|
56
|
-
var tabPanelIds = _toConsumableArray(state.ids.tabPanel);
|
|
57
|
-
|
|
58
|
-
tabPanelIds.splice(index, 1);
|
|
59
|
-
return _objectSpread2({}, state, {
|
|
60
|
-
ids: {
|
|
61
|
-
tab: tabIds,
|
|
62
|
-
tabPanel: tabPanelIds
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
default:
|
|
68
|
-
return state;
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
var TabProviderContext = createContext(null);
|
|
72
|
-
|
|
73
|
-
/* eslint-disable react-hooks/rules-of-hooks */
|
|
74
|
-
var useTabProviderContext = function useTabProviderContext(type, originalId) {
|
|
75
|
-
var context = useContext(TabProviderContext);
|
|
76
|
-
|
|
77
|
-
if (!context) {
|
|
78
|
-
return undefined;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
var _context = _slicedToArray(context, 2),
|
|
82
|
-
state = _context[0],
|
|
83
|
-
dispatch = _context[1]; // Current tab or tab panel id.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
var id = useMemo(function () {
|
|
87
|
-
return originalId || "".concat(type, "-").concat(uid());
|
|
88
|
-
}, // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
89
|
-
[]);
|
|
90
|
-
useEffect(function () {
|
|
91
|
-
// On mount: register tab or tab panel id.
|
|
92
|
-
dispatch({
|
|
93
|
-
type: 'register',
|
|
94
|
-
payload: {
|
|
95
|
-
type: type,
|
|
96
|
-
id: id
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
return function () {
|
|
100
|
-
// On unmount: unregister tab or tab panel id.
|
|
101
|
-
dispatch({
|
|
102
|
-
type: 'unregister',
|
|
103
|
-
payload: {
|
|
104
|
-
type: type,
|
|
105
|
-
id: id
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
};
|
|
109
|
-
}, // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
110
|
-
[]); // Find tab/tabPanel index using it's id.
|
|
111
|
-
|
|
112
|
-
var index = useMemo(function () {
|
|
113
|
-
return state.ids[type].indexOf(id);
|
|
114
|
-
}, [state.ids, type, id]);
|
|
115
|
-
var tabId = useMemo(function () {
|
|
116
|
-
return state.ids.tab[index] || '';
|
|
117
|
-
}, [state, index]);
|
|
118
|
-
var tabPanelId = useMemo(function () {
|
|
119
|
-
return state.ids.tabPanel[index] || '';
|
|
120
|
-
}, [state, index]);
|
|
121
|
-
var isActive = useMemo(function () {
|
|
122
|
-
return state.activeTabIndex === index;
|
|
123
|
-
}, [state, index]);
|
|
124
|
-
var changeToTab = useCallback(function () {
|
|
125
|
-
return dispatch({
|
|
126
|
-
type: 'setActiveTabIndex',
|
|
127
|
-
payload: index
|
|
128
|
-
});
|
|
129
|
-
}, [dispatch, index]);
|
|
130
|
-
return {
|
|
131
|
-
isLazy: state.isLazy,
|
|
132
|
-
shouldActivateOnFocus: state.shouldActivateOnFocus,
|
|
133
|
-
tabId: tabId,
|
|
134
|
-
tabPanelId: tabPanelId,
|
|
135
|
-
isActive: isActive,
|
|
136
|
-
changeToTab: changeToTab
|
|
137
|
-
};
|
|
138
|
-
};
|
|
139
|
-
var useTabProviderContextState = function useTabProviderContextState() {
|
|
140
|
-
var context = useContext(TabProviderContext);
|
|
141
|
-
return context === null || context === void 0 ? void 0 : context[0];
|
|
142
|
-
};
|
|
1
|
+
import { e as _toConsumableArray } from './_rollupPluginBabelHelpers.js';
|
|
2
|
+
import { useEffect } from 'react';
|
|
143
3
|
|
|
144
4
|
var useRovingTabIndex = function useRovingTabIndex(_ref) {
|
|
145
5
|
var parentRef = _ref.parentRef,
|
|
146
6
|
elementSelector = _ref.elementSelector,
|
|
147
7
|
keepTabIndex = _ref.keepTabIndex,
|
|
8
|
+
onElementFocus = _ref.onElementFocus,
|
|
148
9
|
_ref$extraDependencie = _ref.extraDependencies,
|
|
149
10
|
extraDependencies = _ref$extraDependencie === void 0 ? [] : _ref$extraDependencie;
|
|
150
11
|
useEffect(function () {
|
|
@@ -183,7 +44,11 @@ var useRovingTabIndex = function useRovingTabIndex(_ref) {
|
|
|
183
44
|
}
|
|
184
45
|
|
|
185
46
|
var newElement = elements[newTabFocus];
|
|
186
|
-
newElement === null || newElement === void 0 ? void 0 : newElement.focus();
|
|
47
|
+
newElement === null || newElement === void 0 ? void 0 : newElement.focus(); // When an element is focused using roving tab index, trigger the onElementFocus callback
|
|
48
|
+
|
|
49
|
+
if (newElement && onElementFocus) {
|
|
50
|
+
onElementFocus(newElement);
|
|
51
|
+
}
|
|
187
52
|
|
|
188
53
|
if (keepTabIndex) {
|
|
189
54
|
evt.currentTarget.setAttribute('tabindex', '-1');
|
|
@@ -218,5 +83,5 @@ var useRovingTabIndex = function useRovingTabIndex(_ref) {
|
|
|
218
83
|
[parentRef].concat(_toConsumableArray(extraDependencies)));
|
|
219
84
|
};
|
|
220
85
|
|
|
221
|
-
export {
|
|
86
|
+
export { useRovingTabIndex as u };
|
|
222
87
|
//# sourceMappingURL=useRovingTabIndex.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useRovingTabIndex.js","sources":["../../../src/components/tabs/state.ts","../../../src/hooks/useRovingTabIndex.tsx"],"sourcesContent":["import { Dispatch, createContext, useCallback, useContext, useEffect, useMemo } from 'react';\nimport { uid } from 'uid';\n\ntype TabType = 'tab' | 'tabPanel';\n\nexport interface State {\n isLazy: boolean;\n shouldActivateOnFocus: boolean;\n activeTabIndex: number;\n ids: Record<TabType, string[]>;\n}\n\nexport const INIT_STATE: State = {\n isLazy: true,\n shouldActivateOnFocus: false,\n activeTabIndex: 0,\n ids: { tab: [], tabPanel: [] },\n};\n\nexport type Action =\n | { type: 'update'; payload: Partial<State> }\n | { type: 'setActiveTabIndex'; payload: number }\n | { type: 'register'; payload: { type: TabType; id: string } }\n | { type: 'unregister'; payload: { type: TabType; id: string } };\n\nexport const reducer = (state: State, action: Action): State => {\n switch (action.type) {\n case 'update':\n return { ...state, ...action.payload };\n case 'setActiveTabIndex': {\n if (state.activeTabIndex === action.payload) {\n return state;\n }\n // Change active tab index.\n return { ...state, activeTabIndex: action.payload };\n }\n case 'register': {\n const { type, id } = action.payload;\n // Append tab/tabPanel id in state.\n return { ...state, ids: { ...state.ids, [type]: [...state.ids[type], id] } };\n }\n case 'unregister': {\n const { type, id } = action.payload;\n const index = state.ids[type].indexOf(id);\n if (index === -1) return state;\n // Remove tab & tab panel at index.\n const tabIds = [...state.ids.tab];\n tabIds.splice(index, 1);\n const tabPanelIds = [...state.ids.tabPanel];\n tabPanelIds.splice(index, 1);\n return {\n ...state,\n ids: { tab: tabIds, tabPanel: tabPanelIds },\n };\n }\n default:\n return state;\n }\n};\n\nexport const TabProviderContext = createContext<[State, Dispatch<Action>] | null>(null);\n\nexport type TabState = Pick<Required<State>, 'isLazy' | 'shouldActivateOnFocus'> & {\n isActive: boolean;\n tabId: string;\n tabPanelId: string;\n changeToTab(): void;\n};\n\n/* eslint-disable react-hooks/rules-of-hooks */\nexport const useTabProviderContext = (type: TabType, originalId?: string): undefined | TabState => {\n const context = useContext(TabProviderContext);\n if (!context) {\n return undefined;\n }\n const [state, dispatch] = context;\n\n // Current tab or tab panel id.\n const id = useMemo(\n () => originalId || `${type}-${uid()}`,\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [],\n );\n useEffect(\n () => {\n // On mount: register tab or tab panel id.\n dispatch({ type: 'register', payload: { type, id } });\n return () => {\n // On unmount: unregister tab or tab panel id.\n dispatch({ type: 'unregister', payload: { type, id } });\n };\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [],\n );\n\n // Find tab/tabPanel index using it's id.\n const index = useMemo(() => state.ids[type].indexOf(id), [state.ids, type, id]);\n const tabId = useMemo(() => state.ids.tab[index] || '', [state, index]);\n const tabPanelId = useMemo(() => state.ids.tabPanel[index] || '', [state, index]);\n const isActive = useMemo(() => state.activeTabIndex === index, [state, index]);\n const changeToTab = useCallback(() => dispatch({ type: 'setActiveTabIndex', payload: index }), [dispatch, index]);\n return {\n isLazy: state.isLazy,\n shouldActivateOnFocus: state.shouldActivateOnFocus,\n tabId,\n tabPanelId,\n isActive,\n changeToTab,\n };\n};\n\nexport const useTabProviderContextState = (): State | undefined => {\n const context = useContext(TabProviderContext);\n return context?.[0];\n};\n","import { RefObject, useEffect } from 'react';\n\ninterface UseRovingTabIndexOptions {\n parentRef: RefObject<HTMLElement>;\n elementSelector: string;\n keepTabIndex?: boolean;\n /** List of values to be used as extra dependencies of the useEffect */\n extraDependencies?: any[];\n}\n\nexport const useRovingTabIndex = ({\n parentRef,\n elementSelector,\n keepTabIndex,\n extraDependencies = [],\n}: UseRovingTabIndexOptions): void => {\n useEffect(\n () => {\n const parent = parentRef?.current;\n if (!parent) {\n return undefined;\n }\n\n const elements = parent.querySelectorAll(elementSelector) as NodeListOf<HTMLElement>;\n const initialFocusableElement = parent?.querySelector(`${elementSelector}[tabindex=\"0\"]`);\n\n const handleKeyDown = (index: number) => (evt: KeyboardEvent) => {\n let newTabFocus = index;\n if (!(evt.key === 'ArrowRight' || evt.key === 'ArrowLeft')) {\n return;\n }\n\n if (evt.key === 'ArrowRight') {\n // Move right\n newTabFocus += 1;\n // If we're at the end, go to the start\n if (newTabFocus >= elements.length) {\n newTabFocus = 0;\n }\n } else if (evt.key === 'ArrowLeft') {\n // Move left\n newTabFocus -= 1;\n if (newTabFocus < 0) {\n // If we're at the start, move to the end\n newTabFocus = elements.length - 1;\n }\n }\n const newElement = elements[newTabFocus];\n newElement?.focus();\n if (keepTabIndex) {\n (evt.currentTarget as HTMLElement).setAttribute('tabindex', '-1');\n newElement?.setAttribute('tabindex', '0');\n }\n };\n\n if (elements?.length > 0) {\n elements.forEach((el, key) => {\n // if no element has tabindex set to 0, set the first element as focusable\n if (!initialFocusableElement && key === 0) {\n el.setAttribute('tabindex', '0');\n // set all other to -1\n } else if (initialFocusableElement !== el) {\n el.setAttribute('tabindex', '-1');\n }\n // add event listener\n el.addEventListener('keydown', handleKeyDown(key) as EventListener);\n });\n }\n\n // Cleanup listeners\n return () => {\n if (elements?.length > 0) {\n elements.forEach((el, key) => {\n el.removeEventListener('keydown', handleKeyDown(key) as EventListener);\n });\n }\n };\n }, // eslint-disable-next-line react-hooks/exhaustive-deps\n [parentRef, ...extraDependencies],\n );\n};\n"],"names":["INIT_STATE","isLazy","shouldActivateOnFocus","activeTabIndex","ids","tab","tabPanel","reducer","state","action","type","payload","id","index","indexOf","tabIds","splice","tabPanelIds","TabProviderContext","createContext","useTabProviderContext","originalId","context","useContext","undefined","dispatch","useMemo","uid","useEffect","tabId","tabPanelId","isActive","changeToTab","useCallback","useTabProviderContextState","useRovingTabIndex","parentRef","elementSelector","keepTabIndex","extraDependencies","parent","current","elements","querySelectorAll","initialFocusableElement","querySelector","handleKeyDown","evt","newTabFocus","key","length","newElement","focus","currentTarget","setAttribute","forEach","el","addEventListener","removeEventListener"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useRovingTabIndex.js","sources":["../../../src/hooks/useRovingTabIndex.tsx"],"sourcesContent":["import { RefObject, useEffect } from 'react';\n\ninterface UseRovingTabIndexOptions {\n parentRef: RefObject<HTMLElement>;\n elementSelector: string;\n keepTabIndex?: boolean;\n /** Action to trigger when an element is focused using roving tab index */\n onElementFocus?: (element: HTMLElement) => void;\n /** List of values to be used as extra dependencies of the useEffect */\n extraDependencies?: any[];\n}\n\nexport const useRovingTabIndex = ({\n parentRef,\n elementSelector,\n keepTabIndex,\n onElementFocus,\n extraDependencies = [],\n}: UseRovingTabIndexOptions): void => {\n useEffect(\n () => {\n const parent = parentRef?.current;\n if (!parent) {\n return undefined;\n }\n\n const elements = parent.querySelectorAll(elementSelector) as NodeListOf<HTMLElement>;\n const initialFocusableElement = parent?.querySelector(`${elementSelector}[tabindex=\"0\"]`);\n\n const handleKeyDown = (index: number) => (evt: KeyboardEvent) => {\n let newTabFocus = index;\n if (!(evt.key === 'ArrowRight' || evt.key === 'ArrowLeft')) {\n return;\n }\n\n if (evt.key === 'ArrowRight') {\n // Move right\n newTabFocus += 1;\n // If we're at the end, go to the start\n if (newTabFocus >= elements.length) {\n newTabFocus = 0;\n }\n } else if (evt.key === 'ArrowLeft') {\n // Move left\n newTabFocus -= 1;\n if (newTabFocus < 0) {\n // If we're at the start, move to the end\n newTabFocus = elements.length - 1;\n }\n }\n const newElement = elements[newTabFocus];\n newElement?.focus();\n\n // When an element is focused using roving tab index, trigger the onElementFocus callback\n if (newElement && onElementFocus) {\n onElementFocus(newElement);\n }\n\n if (keepTabIndex) {\n (evt.currentTarget as HTMLElement).setAttribute('tabindex', '-1');\n newElement?.setAttribute('tabindex', '0');\n }\n };\n\n if (elements?.length > 0) {\n elements.forEach((el, key) => {\n // if no element has tabindex set to 0, set the first element as focusable\n if (!initialFocusableElement && key === 0) {\n el.setAttribute('tabindex', '0');\n // set all other to -1\n } else if (initialFocusableElement !== el) {\n el.setAttribute('tabindex', '-1');\n }\n // add event listener\n el.addEventListener('keydown', handleKeyDown(key) as EventListener);\n });\n }\n\n // Cleanup listeners\n return () => {\n if (elements?.length > 0) {\n elements.forEach((el, key) => {\n el.removeEventListener('keydown', handleKeyDown(key) as EventListener);\n });\n }\n };\n }, // eslint-disable-next-line react-hooks/exhaustive-deps\n [parentRef, ...extraDependencies],\n );\n};\n"],"names":["useRovingTabIndex","parentRef","elementSelector","keepTabIndex","onElementFocus","extraDependencies","useEffect","parent","current","undefined","elements","querySelectorAll","initialFocusableElement","querySelector","handleKeyDown","index","evt","newTabFocus","key","length","newElement","focus","currentTarget","setAttribute","forEach","el","addEventListener","removeEventListener"],"mappings":"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"}
|
package/esm/index.js
CHANGED
|
@@ -68,8 +68,9 @@ export { M as Mosaic } from './_internal/Mosaic2.js';
|
|
|
68
68
|
export { N as Notification } from './_internal/Notification2.js';
|
|
69
69
|
export { P as PostBlock } from './_internal/PostBlock.js';
|
|
70
70
|
export { a as Progress, P as ProgressVariant } from './_internal/Progress2.js';
|
|
71
|
-
import './_internal/
|
|
71
|
+
import './_internal/state.js';
|
|
72
72
|
export { a as ProgressTracker, P as ProgressTrackerProvider, b as ProgressTrackerStep, c as ProgressTrackerStepPanel } from './_internal/ProgressTrackerStepPanel.js';
|
|
73
|
+
import './_internal/useRovingTabIndex.js';
|
|
73
74
|
export { R as RadioButton, a as RadioGroup } from './_internal/RadioGroup.js';
|
|
74
75
|
export { a as Select, c as SelectMultiple, b as SelectMultipleField, S as SelectVariant } from './_internal/SelectMultiple.js';
|
|
75
76
|
export { S as SideNavigation, a as SideNavigationItem } from './_internal/SideNavigationItem.js';
|
|
@@ -77,6 +78,7 @@ export { S as SkeletonCircle, b as SkeletonRectangle, a as SkeletonRectangleVari
|
|
|
77
78
|
export { S as Slider, c as clamp } from './_internal/Slider2.js';
|
|
78
79
|
export { c as Slides, S as Slideshow, b as SlideshowControls, a as SlideshowItem } from './_internal/Slides.js';
|
|
79
80
|
import 'lodash/uniqueId';
|
|
81
|
+
import 'lodash/chunk';
|
|
80
82
|
export { S as Switch } from './_internal/Switch2.js';
|
|
81
83
|
export { T as Table, a as TableBody, d as TableCell, c as TableCellVariant, e as TableHeader, f as TableRow, b as ThOrder } from './_internal/TableRow.js';
|
|
82
84
|
export { c as Tab, b as TabList, a as TabListLayout, d as TabPanel, T as TabProvider } from './_internal/TabPanel.js';
|
package/esm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/package.json
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@juggle/resize-observer": "^3.2.0",
|
|
10
|
-
"@lumx/core": "^2.2.
|
|
11
|
-
"@lumx/icons": "^2.2.
|
|
10
|
+
"@lumx/core": "^2.2.26-alpha-a11y-slideshow.2",
|
|
11
|
+
"@lumx/icons": "^2.2.26-alpha-a11y-slideshow.2",
|
|
12
12
|
"@popperjs/core": "^2.5.4",
|
|
13
13
|
"body-scroll-lock": "^3.1.5",
|
|
14
14
|
"classnames": "^2.2.6",
|
|
@@ -120,6 +120,6 @@
|
|
|
120
120
|
"build:storybook": "cd storybook && ./build"
|
|
121
121
|
},
|
|
122
122
|
"sideEffects": false,
|
|
123
|
-
"version": "2.2.
|
|
124
|
-
"gitHead": "
|
|
123
|
+
"version": "2.2.26-alpha-a11y-slideshow.2",
|
|
124
|
+
"gitHead": "24fd0e39f9d0da7d8ada60b03f7dce26ddaf811e"
|
|
125
125
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import React, { CSSProperties, forwardRef } from 'react';
|
|
1
|
+
import React, { Children, CSSProperties, forwardRef } from 'react';
|
|
2
|
+
import chunk from 'lodash/chunk';
|
|
2
3
|
|
|
3
4
|
import classNames from 'classnames';
|
|
4
5
|
|
|
5
6
|
import { FULL_WIDTH_PERCENT } from '@lumx/react/components/slideshow/constants';
|
|
6
7
|
import { Comp, GenericProps, getRootClassName, handleBasicClasses, HasTheme } from '@lumx/react/utils';
|
|
8
|
+
import { useSlideFocusManagement } from './useSlideFocusManagement';
|
|
9
|
+
import { buildSlideShowGroupId, SlideshowItemGroup } from './SlideshowItemGroup';
|
|
7
10
|
|
|
8
11
|
export interface SlidesProps extends GenericProps, HasTheme {
|
|
9
12
|
/** current slide active */
|
|
@@ -24,6 +27,13 @@ export interface SlidesProps extends GenericProps, HasTheme {
|
|
|
24
27
|
toggleAutoPlay: () => void;
|
|
25
28
|
/** component to be rendered after the slides */
|
|
26
29
|
afterSlides?: React.ReactNode;
|
|
30
|
+
/** Whether the slides have controls linked */
|
|
31
|
+
hasControls?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Accessible label to set on a slide group.
|
|
34
|
+
* Receives the group position starting from 1 and the total number of groups.
|
|
35
|
+
* */
|
|
36
|
+
slideGroupLabel?: (groupPosition: number, groupTotal: number) => string;
|
|
27
37
|
}
|
|
28
38
|
|
|
29
39
|
/**
|
|
@@ -56,11 +66,22 @@ export const Slides: Comp<SlidesProps, HTMLDivElement> = forwardRef((props, ref)
|
|
|
56
66
|
slidesId,
|
|
57
67
|
children,
|
|
58
68
|
afterSlides,
|
|
69
|
+
hasControls,
|
|
70
|
+
slideGroupLabel,
|
|
59
71
|
...forwardedProps
|
|
60
72
|
} = props;
|
|
73
|
+
const wrapperRef = React.useRef<HTMLDivElement>(null);
|
|
74
|
+
|
|
75
|
+
useSlideFocusManagement({ wrapperRef, activeIndex, groupBy });
|
|
76
|
+
|
|
61
77
|
// Inline style of wrapper element.
|
|
62
78
|
const wrapperStyle: CSSProperties = { transform: `translateX(-${FULL_WIDTH_PERCENT * activeIndex}%)` };
|
|
63
79
|
|
|
80
|
+
const groups = React.useMemo(
|
|
81
|
+
() => (groupBy && groupBy > 1 ? chunk(Children.toArray(children), groupBy) : [children]),
|
|
82
|
+
[children, groupBy],
|
|
83
|
+
);
|
|
84
|
+
|
|
64
85
|
return (
|
|
65
86
|
<section
|
|
66
87
|
id={id}
|
|
@@ -79,8 +100,17 @@ export const Slides: Comp<SlidesProps, HTMLDivElement> = forwardRef((props, ref)
|
|
|
79
100
|
onMouseLeave={toggleAutoPlay}
|
|
80
101
|
aria-live={isAutoPlaying ? 'off' : 'polite'}
|
|
81
102
|
>
|
|
82
|
-
<div className={`${CLASSNAME}__wrapper`} style={wrapperStyle}>
|
|
83
|
-
{
|
|
103
|
+
<div ref={wrapperRef} className={`${CLASSNAME}__wrapper`} style={wrapperStyle}>
|
|
104
|
+
{groups.map((group, index) => (
|
|
105
|
+
<SlideshowItemGroup
|
|
106
|
+
key={index}
|
|
107
|
+
id={slidesId && buildSlideShowGroupId(slidesId, index)}
|
|
108
|
+
role={hasControls ? 'tabpanel' : 'group'}
|
|
109
|
+
label={slideGroupLabel ? slideGroupLabel(index + 1, groups.length) : undefined}
|
|
110
|
+
>
|
|
111
|
+
{group}
|
|
112
|
+
</SlideshowItemGroup>
|
|
113
|
+
))}
|
|
84
114
|
</div>
|
|
85
115
|
</div>
|
|
86
116
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import range from 'lodash/range';
|
|
3
|
-
import { AspectRatio, Button, FlexBox, ImageBlock, Slideshow, SlideshowItem } from '@lumx/react';
|
|
3
|
+
import { AspectRatio, Button, FlexBox, ImageBlock, Slideshow, SlideshowItem, Orientation } from '@lumx/react';
|
|
4
4
|
import { boolean, number } from '@storybook/addon-knobs';
|
|
5
5
|
import { thumbnailsKnob } from '@lumx/react/stories/knobs/thumbnailsKnob';
|
|
6
6
|
|
|
@@ -15,6 +15,7 @@ export const Simple = ({ theme }: any) => {
|
|
|
15
15
|
|
|
16
16
|
return (
|
|
17
17
|
<Slideshow
|
|
18
|
+
aria-label="Simple carousel example"
|
|
18
19
|
activeIndex={activeIndex}
|
|
19
20
|
autoPlay={autoPlay}
|
|
20
21
|
interval={interval}
|
|
@@ -25,6 +26,7 @@ export const Simple = ({ theme }: any) => {
|
|
|
25
26
|
theme={theme}
|
|
26
27
|
groupBy={groupBy}
|
|
27
28
|
style={{ width: '50%' }}
|
|
29
|
+
slideGroupLabel={(currentGroup, totalGroup) => `${currentGroup} of ${totalGroup}`}
|
|
28
30
|
>
|
|
29
31
|
{images.map(({ image, alt }, index) => (
|
|
30
32
|
<SlideshowItem key={`${image}-${index}`}>
|
|
@@ -48,6 +50,7 @@ export const SimpleWithAutoPlay = ({ theme }: any) => {
|
|
|
48
50
|
|
|
49
51
|
return (
|
|
50
52
|
<Slideshow
|
|
53
|
+
aria-label="Simple with autoplay example"
|
|
51
54
|
activeIndex={activeIndex}
|
|
52
55
|
autoPlay
|
|
53
56
|
interval={interval}
|
|
@@ -59,6 +62,7 @@ export const SimpleWithAutoPlay = ({ theme }: any) => {
|
|
|
59
62
|
theme={theme}
|
|
60
63
|
groupBy={groupBy}
|
|
61
64
|
style={{ width: '50%' }}
|
|
65
|
+
slideGroupLabel={(currentGroup, totalGroup) => `${currentGroup} of ${totalGroup}`}
|
|
62
66
|
>
|
|
63
67
|
{images.map(({ image, alt }, index) => (
|
|
64
68
|
<SlideshowItem key={`${image}-${index}`}>
|
|
@@ -75,7 +79,7 @@ export const SimpleWithAutoPlay = ({ theme }: any) => {
|
|
|
75
79
|
};
|
|
76
80
|
|
|
77
81
|
export const ResponsiveSlideShowSwipe = () => {
|
|
78
|
-
const slides = range(
|
|
82
|
+
const slides = range(5);
|
|
79
83
|
return (
|
|
80
84
|
<>
|
|
81
85
|
In responsive mode
|
|
@@ -86,11 +90,13 @@ export const ResponsiveSlideShowSwipe = () => {
|
|
|
86
90
|
</ul>
|
|
87
91
|
<FlexBox vAlign="center">
|
|
88
92
|
<Slideshow
|
|
93
|
+
aria-label="Responsive SlideShow Swipe"
|
|
89
94
|
activeIndex={0}
|
|
90
95
|
slideshowControlsProps={{
|
|
91
96
|
nextButtonProps: { label: 'Next' },
|
|
92
97
|
previousButtonProps: { label: 'Previous' },
|
|
93
98
|
}}
|
|
99
|
+
slideGroupLabel={(currentGroup, totalGroup) => `${currentGroup} of ${totalGroup}`}
|
|
94
100
|
>
|
|
95
101
|
{slides.map((slide) => (
|
|
96
102
|
<SlideshowItem key={`${slide}`}>
|
|
@@ -114,3 +120,85 @@ export const ResponsiveSlideShowSwipe = () => {
|
|
|
114
120
|
</>
|
|
115
121
|
);
|
|
116
122
|
};
|
|
123
|
+
|
|
124
|
+
const slides = [
|
|
125
|
+
{
|
|
126
|
+
id: 0,
|
|
127
|
+
src: 'https://www.w3.org/WAI/ARIA/apg/example-index/carousel/images/foyleswarslide__800x600.jpg',
|
|
128
|
+
alt: 'A man in a suit and fedora and a woman with coiffed hair look sternly into the camera.',
|
|
129
|
+
title: 'Foyle’s War Revisited',
|
|
130
|
+
subtitle: '8 pm Sunday, March 8, on TV: Sneak peek at the final season',
|
|
131
|
+
link: '#',
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: 1,
|
|
135
|
+
src: 'https://www.w3.org/WAI/ARIA/apg/example-index/carousel/images/britcomdavidslide__800x600.jpg',
|
|
136
|
+
alt: 'British flag with WILL-TV host David Thiel.',
|
|
137
|
+
title: 'Great Britain Vote: 7 pm Sat.',
|
|
138
|
+
link: '#',
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
id: 2,
|
|
142
|
+
src: 'https://www.w3.org/WAI/ARIA/apg/example-index/carousel/images/mag800-2__800x600.jpg',
|
|
143
|
+
alt: 'Mid-American Gardener panelists on the set.',
|
|
144
|
+
title: 'Mid-American Gardener: Thursdays at 7 pm',
|
|
145
|
+
subtitle: 'Watch the latest episode',
|
|
146
|
+
link: '#',
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
id: 3,
|
|
150
|
+
src: 'https://www.w3.org/WAI/ARIA/apg/example-index/carousel/images/foyleswarslide__800x600.jpg',
|
|
151
|
+
alt: 'A man in a suit and fedora and a woman with coiffed hair look sternly into the camera.',
|
|
152
|
+
title: 'Foyle’s War Revisited',
|
|
153
|
+
subtitle: '8 pm Sunday, March 8, on TV: Sneak peek at the final season',
|
|
154
|
+
link: '#',
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
id: 4,
|
|
158
|
+
src: 'https://www.w3.org/WAI/ARIA/apg/example-index/carousel/images/britcomdavidslide__800x600.jpg',
|
|
159
|
+
alt: 'British flag with WILL-TV host David Thiel.',
|
|
160
|
+
title: 'Great Britain Vote: 7 pm Sat.',
|
|
161
|
+
link: '#',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: 5,
|
|
165
|
+
src: 'https://www.w3.org/WAI/ARIA/apg/example-index/carousel/images/mag800-2__800x600.jpg',
|
|
166
|
+
alt: 'Mid-American Gardener panelists on the set.',
|
|
167
|
+
title: 'Mid-American Gardener: Thursdays at 7 pm',
|
|
168
|
+
subtitle: 'Watch the latest episode',
|
|
169
|
+
link: '#',
|
|
170
|
+
},
|
|
171
|
+
];
|
|
172
|
+
export const WithComplexContent = () => (
|
|
173
|
+
<Slideshow
|
|
174
|
+
aria-label="Carousel with complex content"
|
|
175
|
+
activeIndex={0}
|
|
176
|
+
groupBy={2}
|
|
177
|
+
slideshowControlsProps={{
|
|
178
|
+
nextButtonProps: { label: 'Next' },
|
|
179
|
+
previousButtonProps: { label: 'Previous' },
|
|
180
|
+
playButtonProps: { label: 'Play/Pause' },
|
|
181
|
+
paginationItemProps: (index) => ({ 'aria-label': `Slide ${index + 1}` }),
|
|
182
|
+
}}
|
|
183
|
+
autoPlay
|
|
184
|
+
slideGroupLabel={(currentGroup, totalGroup) => `${currentGroup} of ${totalGroup}`}
|
|
185
|
+
>
|
|
186
|
+
{slides.map((slide) => (
|
|
187
|
+
<SlideshowItem key={slide.id}>
|
|
188
|
+
<a href={slide.link}>
|
|
189
|
+
<img src={slide.src} alt={slide.alt} />
|
|
190
|
+
</a>
|
|
191
|
+
<FlexBox orientation={Orientation.vertical}>
|
|
192
|
+
<h3>
|
|
193
|
+
<a href={slide.link}>{slide.title}</a>
|
|
194
|
+
{/* Add a non focusable element to test that it stays that way after a page change. */}
|
|
195
|
+
<button type="button" tabIndex={-1} aria-hidden="true">
|
|
196
|
+
Not focusable
|
|
197
|
+
</button>
|
|
198
|
+
</h3>
|
|
199
|
+
{slide.subtitle && <p>{slide.subtitle}</p>}
|
|
200
|
+
</FlexBox>
|
|
201
|
+
</SlideshowItem>
|
|
202
|
+
))}
|
|
203
|
+
</Slideshow>
|
|
204
|
+
);
|
|
@@ -5,19 +5,23 @@ import { DEFAULT_OPTIONS } from '@lumx/react/hooks/useSlideshowControls';
|
|
|
5
5
|
import { Comp, GenericProps } from '@lumx/react/utils';
|
|
6
6
|
import { useFocusWithin } from '@lumx/react/hooks/useFocusWithin';
|
|
7
7
|
import { mergeRefs } from '@lumx/react/utils/mergeRefs';
|
|
8
|
+
import { buildSlideShowGroupId } from './SlideshowItemGroup';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Defines the props of the component.
|
|
11
12
|
*/
|
|
12
13
|
export interface SlideshowProps
|
|
13
14
|
extends GenericProps,
|
|
14
|
-
Pick<SlidesProps, 'autoPlay' | 'slidesId' | 'id' | 'theme' | 'fillHeight' | 'groupBy'> {
|
|
15
|
+
Pick<SlidesProps, 'autoPlay' | 'slidesId' | 'id' | 'theme' | 'fillHeight' | 'groupBy' | 'slideGroupLabel'> {
|
|
15
16
|
/** current slide active */
|
|
16
17
|
activeIndex?: SlidesProps['activeIndex'];
|
|
17
18
|
/** Interval between each slide when automatic rotation is enabled. */
|
|
18
19
|
interval?: number;
|
|
19
20
|
/** Props to pass to the slideshow controls (minus those already set by the Slideshow props). */
|
|
20
|
-
slideshowControlsProps?: Pick<
|
|
21
|
+
slideshowControlsProps?: Pick<
|
|
22
|
+
SlideshowControlsProps,
|
|
23
|
+
'nextButtonProps' | 'previousButtonProps' | 'paginationItemProps'
|
|
24
|
+
> &
|
|
21
25
|
Omit<
|
|
22
26
|
SlideshowControlsProps,
|
|
23
27
|
| 'activeIndex'
|
|
@@ -61,6 +65,7 @@ export const Slideshow: Comp<SlideshowProps, HTMLDivElement> = forwardRef((props
|
|
|
61
65
|
theme,
|
|
62
66
|
id,
|
|
63
67
|
slidesId,
|
|
68
|
+
slideGroupLabel,
|
|
64
69
|
...forwardedProps
|
|
65
70
|
} = props;
|
|
66
71
|
// Number of slideshow items.
|
|
@@ -99,6 +104,8 @@ export const Slideshow: Comp<SlideshowProps, HTMLDivElement> = forwardRef((props
|
|
|
99
104
|
onFocusOut: startAutoPlay,
|
|
100
105
|
});
|
|
101
106
|
|
|
107
|
+
const showControls = slideshowControlsProps && slidesCount > 1;
|
|
108
|
+
|
|
102
109
|
return (
|
|
103
110
|
<Slides
|
|
104
111
|
activeIndex={currentIndex}
|
|
@@ -111,8 +118,9 @@ export const Slideshow: Comp<SlideshowProps, HTMLDivElement> = forwardRef((props
|
|
|
111
118
|
autoPlay={autoPlay}
|
|
112
119
|
slidesId={slideshowSlidesId}
|
|
113
120
|
toggleAutoPlay={toggleAutoPlay}
|
|
114
|
-
interval={interval}
|
|
115
121
|
ref={mergeRefs(ref, setSlideshow)}
|
|
122
|
+
hasControls={showControls}
|
|
123
|
+
slideGroupLabel={slideGroupLabel}
|
|
116
124
|
afterSlides={
|
|
117
125
|
slideshowControlsProps && slidesCount > 1 ? (
|
|
118
126
|
<div className={`${Slides.className}__controls`}>
|
|
@@ -143,6 +151,10 @@ export const Slideshow: Comp<SlideshowProps, HTMLDivElement> = forwardRef((props
|
|
|
143
151
|
}
|
|
144
152
|
: undefined
|
|
145
153
|
}
|
|
154
|
+
paginationItemProps={(index) => ({
|
|
155
|
+
'aria-controls': buildSlideShowGroupId(slideshowSlidesId, index),
|
|
156
|
+
...slideshowControlsProps.paginationItemProps?.(index),
|
|
157
|
+
})}
|
|
146
158
|
/>
|
|
147
159
|
</div>
|
|
148
160
|
) : undefined
|
|
@@ -26,6 +26,7 @@ export const Simple = () => {
|
|
|
26
26
|
onPaginationClick={onPaginationClick}
|
|
27
27
|
nextButtonProps={{ label: 'Next' }}
|
|
28
28
|
previousButtonProps={{ label: 'Previous' }}
|
|
29
|
+
paginationItemLabel={(index) => `Slide ${index}`}
|
|
29
30
|
/>
|
|
30
31
|
);
|
|
31
32
|
};
|
|
@@ -62,7 +63,6 @@ export const ControllingSlideshow = ({ theme }: any) => {
|
|
|
62
63
|
onFocusOut: startAutoPlay,
|
|
63
64
|
});
|
|
64
65
|
|
|
65
|
-
/* eslint-disable jsx-a11y/no-noninteractive-tabindex */
|
|
66
66
|
return (
|
|
67
67
|
<Slides
|
|
68
68
|
activeIndex={currentIndex}
|