@lumx/react 2.2.25 → 2.2.26-alpha-a11y-slideshow.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/esm/_internal/ClickAwayProvider.js +9 -5
  2. package/esm/_internal/ClickAwayProvider.js.map +1 -1
  3. package/esm/_internal/List2.js.map +1 -1
  4. package/esm/_internal/ProgressTrackerStepPanel.js +2 -1
  5. package/esm/_internal/ProgressTrackerStepPanel.js.map +1 -1
  6. package/esm/_internal/Slides.js +207 -76
  7. package/esm/_internal/Slides.js.map +1 -1
  8. package/esm/_internal/TabPanel.js +2 -1
  9. package/esm/_internal/TabPanel.js.map +1 -1
  10. package/esm/_internal/progress-tracker.js +2 -1
  11. package/esm/_internal/progress-tracker.js.map +1 -1
  12. package/esm/_internal/slideshow.js +2 -0
  13. package/esm/_internal/slideshow.js.map +1 -1
  14. package/esm/_internal/state.js +145 -0
  15. package/esm/_internal/state.js.map +1 -0
  16. package/esm/_internal/tabs.js +1 -0
  17. package/esm/_internal/tabs.js.map +1 -1
  18. package/esm/_internal/useRovingTabIndex.js +9 -144
  19. package/esm/_internal/useRovingTabIndex.js.map +1 -1
  20. package/esm/index.js +3 -1
  21. package/esm/index.js.map +1 -1
  22. package/package.json +4 -4
  23. package/src/components/slideshow/Slides.tsx +33 -3
  24. package/src/components/slideshow/Slideshow.stories.tsx +90 -2
  25. package/src/components/slideshow/Slideshow.tsx +15 -3
  26. package/src/components/slideshow/SlideshowControls.stories.tsx +1 -1
  27. package/src/components/slideshow/SlideshowControls.tsx +43 -6
  28. package/src/components/slideshow/SlideshowItem.tsx +0 -5
  29. package/src/components/slideshow/SlideshowItemGroup.tsx +56 -0
  30. package/src/components/slideshow/__snapshots__/Slideshow.test.tsx.snap +10 -1
  31. package/src/components/slideshow/useSlideFocusManagement.tsx +67 -0
  32. package/src/hooks/useRovingTabIndex.tsx +9 -0
  33. package/src/utils/focus/constants.ts +5 -0
  34. package/src/utils/focus/getFirstAndLastFocusable.ts +4 -10
  35. package/src/utils/focus/getFocusableElements.test.ts +174 -0
  36. package/src/utils/focus/getFocusableElements.ts +7 -0
  37. package/types.d.ts +19 -5
@@ -1,150 +1,11 @@
1
- import { e as _toConsumableArray, _ as _objectSpread2, a as _defineProperty, d as _slicedToArray } from './_rollupPluginBabelHelpers.js';
2
- import { createContext, useContext, useMemo, useEffect, useCallback } from 'react';
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 { INIT_STATE as I, TabProviderContext as T, useTabProviderContextState as a, useTabProviderContext as b, reducer as r, useRovingTabIndex as u };
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":";;;;IAYaA,UAAiB,GAAG;AAC7BC,EAAAA,MAAM,EAAE,IADqB;AAE7BC,EAAAA,qBAAqB,EAAE,KAFM;AAG7BC,EAAAA,cAAc,EAAE,CAHa;AAI7BC,EAAAA,GAAG,EAAE;AAAEC,IAAAA,GAAG,EAAE,EAAP;AAAWC,IAAAA,QAAQ,EAAE;AAArB;AAJwB;IAapBC,OAAO,GAAG,SAAVA,OAAU,CAACC,KAAD,EAAeC,MAAf,EAAyC;AAC5D,UAAQA,MAAM,CAACC,IAAf;AACI,SAAK,QAAL;AACI,gCAAYF,KAAZ,MAAsBC,MAAM,CAACE,OAA7B;;AACJ,SAAK,mBAAL;AAA0B;AACtB,YAAIH,KAAK,CAACL,cAAN,KAAyBM,MAAM,CAACE,OAApC,EAA6C;AACzC,iBAAOH,KAAP;AACH,SAHqB;;;AAKtB,kCAAYA,KAAZ;AAAmBL,UAAAA,cAAc,EAAEM,MAAM,CAACE;AAA1C;AACH;;AACD,SAAK,UAAL;AAAiB;AAAA,8BACQF,MAAM,CAACE,OADf;AAAA,YACLD,IADK,mBACLA,IADK;AAAA,YACCE,EADD,mBACCA,EADD;;AAGb,kCAAYJ,KAAZ;AAAmBJ,UAAAA,GAAG,qBAAOI,KAAK,CAACJ,GAAb,sBAAmBM,IAAnB,+BAA8BF,KAAK,CAACJ,GAAN,CAAUM,IAAV,CAA9B,IAA+CE,EAA/C;AAAtB;AACH;;AACD,SAAK,YAAL;AAAmB;AAAA,+BACMH,MAAM,CAACE,OADb;AAAA,YACPD,KADO,oBACPA,IADO;AAAA,YACDE,GADC,oBACDA,EADC;;AAEf,YAAMC,KAAK,GAAGL,KAAK,CAACJ,GAAN,CAAUM,KAAV,EAAgBI,OAAhB,CAAwBF,GAAxB,CAAd;;AACA,YAAIC,KAAK,KAAK,CAAC,CAAf,EAAkB,OAAOL,KAAP,CAHH;;AAKf,YAAMO,MAAM,sBAAOP,KAAK,CAACJ,GAAN,CAAUC,GAAjB,CAAZ;;AACAU,QAAAA,MAAM,CAACC,MAAP,CAAcH,KAAd,EAAqB,CAArB;;AACA,YAAMI,WAAW,sBAAOT,KAAK,CAACJ,GAAN,CAAUE,QAAjB,CAAjB;;AACAW,QAAAA,WAAW,CAACD,MAAZ,CAAmBH,KAAnB,EAA0B,CAA1B;AACA,kCACOL,KADP;AAEIJ,UAAAA,GAAG,EAAE;AAAEC,YAAAA,GAAG,EAAEU,MAAP;AAAeT,YAAAA,QAAQ,EAAEW;AAAzB;AAFT;AAIH;;AACD;AACI,aAAOT,KAAP;AA9BR;AAgCH;IAEYU,kBAAkB,GAAGC,aAAa,CAAmC,IAAnC;;AAS/C;IACaC,qBAAqB,GAAG,SAAxBA,qBAAwB,CAACV,IAAD,EAAgBW,UAAhB,EAA8D;AAC/F,MAAMC,OAAO,GAAGC,UAAU,CAACL,kBAAD,CAA1B;;AACA,MAAI,CAACI,OAAL,EAAc;AACV,WAAOE,SAAP;AACH;;AAJ8F,gCAKrEF,OALqE;AAAA,MAKxFd,KALwF;AAAA,MAKjFiB,QALiF;;;AAQ/F,MAAMb,EAAE,GAAGc,OAAO,CACd;AAAA,WAAML,UAAU,cAAOX,IAAP,cAAeiB,GAAG,EAAlB,CAAhB;AAAA,GADc;AAGd,IAHc,CAAlB;AAKAC,EAAAA,SAAS,CACL,YAAM;AACF;AACAH,IAAAA,QAAQ,CAAC;AAAEf,MAAAA,IAAI,EAAE,UAAR;AAAoBC,MAAAA,OAAO,EAAE;AAAED,QAAAA,IAAI,EAAJA,IAAF;AAAQE,QAAAA,EAAE,EAAFA;AAAR;AAA7B,KAAD,CAAR;AACA,WAAO,YAAM;AACT;AACAa,MAAAA,QAAQ,CAAC;AAAEf,QAAAA,IAAI,EAAE,YAAR;AAAsBC,QAAAA,OAAO,EAAE;AAAED,UAAAA,IAAI,EAAJA,IAAF;AAAQE,UAAAA,EAAE,EAAFA;AAAR;AAA/B,OAAD,CAAR;AACH,KAHD;AAIH,GARI;AAUL,IAVK,CAAT,CAb+F;;AA2B/F,MAAMC,KAAK,GAAGa,OAAO,CAAC;AAAA,WAAMlB,KAAK,CAACJ,GAAN,CAAUM,IAAV,EAAgBI,OAAhB,CAAwBF,EAAxB,CAAN;AAAA,GAAD,EAAoC,CAACJ,KAAK,CAACJ,GAAP,EAAYM,IAAZ,EAAkBE,EAAlB,CAApC,CAArB;AACA,MAAMiB,KAAK,GAAGH,OAAO,CAAC;AAAA,WAAMlB,KAAK,CAACJ,GAAN,CAAUC,GAAV,CAAcQ,KAAd,KAAwB,EAA9B;AAAA,GAAD,EAAmC,CAACL,KAAD,EAAQK,KAAR,CAAnC,CAArB;AACA,MAAMiB,UAAU,GAAGJ,OAAO,CAAC;AAAA,WAAMlB,KAAK,CAACJ,GAAN,CAAUE,QAAV,CAAmBO,KAAnB,KAA6B,EAAnC;AAAA,GAAD,EAAwC,CAACL,KAAD,EAAQK,KAAR,CAAxC,CAA1B;AACA,MAAMkB,QAAQ,GAAGL,OAAO,CAAC;AAAA,WAAMlB,KAAK,CAACL,cAAN,KAAyBU,KAA/B;AAAA,GAAD,EAAuC,CAACL,KAAD,EAAQK,KAAR,CAAvC,CAAxB;AACA,MAAMmB,WAAW,GAAGC,WAAW,CAAC;AAAA,WAAMR,QAAQ,CAAC;AAAEf,MAAAA,IAAI,EAAE,mBAAR;AAA6BC,MAAAA,OAAO,EAAEE;AAAtC,KAAD,CAAd;AAAA,GAAD,EAAgE,CAACY,QAAD,EAAWZ,KAAX,CAAhE,CAA/B;AACA,SAAO;AACHZ,IAAAA,MAAM,EAAEO,KAAK,CAACP,MADX;AAEHC,IAAAA,qBAAqB,EAAEM,KAAK,CAACN,qBAF1B;AAGH2B,IAAAA,KAAK,EAALA,KAHG;AAIHC,IAAAA,UAAU,EAAVA,UAJG;AAKHC,IAAAA,QAAQ,EAARA,QALG;AAMHC,IAAAA,WAAW,EAAXA;AANG,GAAP;AAQH;IAEYE,0BAA0B,GAAG,SAA7BA,0BAA6B,GAAyB;AAC/D,MAAMZ,OAAO,GAAGC,UAAU,CAACL,kBAAD,CAA1B;AACA,SAAOI,OAAP,aAAOA,OAAP,uBAAOA,OAAO,CAAG,CAAH,CAAd;AACH;;ICzGYa,iBAAiB,GAAG,SAApBA,iBAAoB,OAKK;AAAA,MAJlCC,SAIkC,QAJlCA,SAIkC;AAAA,MAHlCC,eAGkC,QAHlCA,eAGkC;AAAA,MAFlCC,YAEkC,QAFlCA,YAEkC;AAAA,mCADlCC,iBACkC;AAAA,MADlCA,iBACkC,sCADd,EACc;AAClCX,EAAAA,SAAS,CACL,YAAM;AACF,QAAMY,MAAM,GAAGJ,SAAH,aAAGA,SAAH,uBAAGA,SAAS,CAAEK,OAA1B;;AACA,QAAI,CAACD,MAAL,EAAa;AACT,aAAOhB,SAAP;AACH;;AAED,QAAMkB,QAAQ,GAAGF,MAAM,CAACG,gBAAP,CAAwBN,eAAxB,CAAjB;AACA,QAAMO,uBAAuB,GAAGJ,MAAH,aAAGA,MAAH,uBAAGA,MAAM,CAAEK,aAAR,WAAyBR,eAAzB,sBAAhC;;AAEA,QAAMS,aAAa,GAAG,SAAhBA,aAAgB,CAACjC,KAAD;AAAA,aAAmB,UAACkC,GAAD,EAAwB;AAC7D,YAAIC,WAAW,GAAGnC,KAAlB;;AACA,YAAI,EAAEkC,GAAG,CAACE,GAAJ,KAAY,YAAZ,IAA4BF,GAAG,CAACE,GAAJ,KAAY,WAA1C,CAAJ,EAA4D;AACxD;AACH;;AAED,YAAIF,GAAG,CAACE,GAAJ,KAAY,YAAhB,EAA8B;AAC1B;AACAD,UAAAA,WAAW,IAAI,CAAf,CAF0B;;AAI1B,cAAIA,WAAW,IAAIN,QAAQ,CAACQ,MAA5B,EAAoC;AAChCF,YAAAA,WAAW,GAAG,CAAd;AACH;AACJ,SAPD,MAOO,IAAID,GAAG,CAACE,GAAJ,KAAY,WAAhB,EAA6B;AAChC;AACAD,UAAAA,WAAW,IAAI,CAAf;;AACA,cAAIA,WAAW,GAAG,CAAlB,EAAqB;AACjB;AACAA,YAAAA,WAAW,GAAGN,QAAQ,CAACQ,MAAT,GAAkB,CAAhC;AACH;AACJ;;AACD,YAAMC,UAAU,GAAGT,QAAQ,CAACM,WAAD,CAA3B;AACAG,QAAAA,UAAU,SAAV,IAAAA,UAAU,WAAV,YAAAA,UAAU,CAAEC,KAAZ;;AACA,YAAId,YAAJ,EAAkB;AACbS,UAAAA,GAAG,CAACM,aAAL,CAAmCC,YAAnC,CAAgD,UAAhD,EAA4D,IAA5D;AACAH,UAAAA,UAAU,SAAV,IAAAA,UAAU,WAAV,YAAAA,UAAU,CAAEG,YAAZ,CAAyB,UAAzB,EAAqC,GAArC;AACH;AACJ,OA3BqB;AAAA,KAAtB;;AA6BA,QAAI,CAAAZ,QAAQ,SAAR,IAAAA,QAAQ,WAAR,YAAAA,QAAQ,CAAEQ,MAAV,IAAmB,CAAvB,EAA0B;AACtBR,MAAAA,QAAQ,CAACa,OAAT,CAAiB,UAACC,EAAD,EAAKP,GAAL,EAAa;AAC1B;AACA,YAAI,CAACL,uBAAD,IAA4BK,GAAG,KAAK,CAAxC,EAA2C;AACvCO,UAAAA,EAAE,CAACF,YAAH,CAAgB,UAAhB,EAA4B,GAA5B,EADuC;AAG1C,SAHD,MAGO,IAAIV,uBAAuB,KAAKY,EAAhC,EAAoC;AACvCA,UAAAA,EAAE,CAACF,YAAH,CAAgB,UAAhB,EAA4B,IAA5B;AACH,SAPyB;;;AAS1BE,QAAAA,EAAE,CAACC,gBAAH,CAAoB,SAApB,EAA+BX,aAAa,CAACG,GAAD,CAA5C;AACH,OAVD;AAWH,KAlDC;;;AAqDF,WAAO,YAAM;AACT,UAAI,CAAAP,QAAQ,SAAR,IAAAA,QAAQ,WAAR,YAAAA,QAAQ,CAAEQ,MAAV,IAAmB,CAAvB,EAA0B;AACtBR,QAAAA,QAAQ,CAACa,OAAT,CAAiB,UAACC,EAAD,EAAKP,GAAL,EAAa;AAC1BO,UAAAA,EAAE,CAACE,mBAAH,CAAuB,SAAvB,EAAkCZ,aAAa,CAACG,GAAD,CAA/C;AACH,SAFD;AAGH;AACJ,KAND;AAOH,GA7DI;AAAA,GA8DJb,SA9DI,4BA8DUG,iBA9DV,GAAT;AAgEH;;;;"}
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":";;;IAYaA,iBAAiB,GAAG,SAApBA,iBAAoB,OAMK;AAAA,MALlCC,SAKkC,QALlCA,SAKkC;AAAA,MAJlCC,eAIkC,QAJlCA,eAIkC;AAAA,MAHlCC,YAGkC,QAHlCA,YAGkC;AAAA,MAFlCC,cAEkC,QAFlCA,cAEkC;AAAA,mCADlCC,iBACkC;AAAA,MADlCA,iBACkC,sCADd,EACc;AAClCC,EAAAA,SAAS,CACL,YAAM;AACF,QAAMC,MAAM,GAAGN,SAAH,aAAGA,SAAH,uBAAGA,SAAS,CAAEO,OAA1B;;AACA,QAAI,CAACD,MAAL,EAAa;AACT,aAAOE,SAAP;AACH;;AAED,QAAMC,QAAQ,GAAGH,MAAM,CAACI,gBAAP,CAAwBT,eAAxB,CAAjB;AACA,QAAMU,uBAAuB,GAAGL,MAAH,aAAGA,MAAH,uBAAGA,MAAM,CAAEM,aAAR,WAAyBX,eAAzB,sBAAhC;;AAEA,QAAMY,aAAa,GAAG,SAAhBA,aAAgB,CAACC,KAAD;AAAA,aAAmB,UAACC,GAAD,EAAwB;AAC7D,YAAIC,WAAW,GAAGF,KAAlB;;AACA,YAAI,EAAEC,GAAG,CAACE,GAAJ,KAAY,YAAZ,IAA4BF,GAAG,CAACE,GAAJ,KAAY,WAA1C,CAAJ,EAA4D;AACxD;AACH;;AAED,YAAIF,GAAG,CAACE,GAAJ,KAAY,YAAhB,EAA8B;AAC1B;AACAD,UAAAA,WAAW,IAAI,CAAf,CAF0B;;AAI1B,cAAIA,WAAW,IAAIP,QAAQ,CAACS,MAA5B,EAAoC;AAChCF,YAAAA,WAAW,GAAG,CAAd;AACH;AACJ,SAPD,MAOO,IAAID,GAAG,CAACE,GAAJ,KAAY,WAAhB,EAA6B;AAChC;AACAD,UAAAA,WAAW,IAAI,CAAf;;AACA,cAAIA,WAAW,GAAG,CAAlB,EAAqB;AACjB;AACAA,YAAAA,WAAW,GAAGP,QAAQ,CAACS,MAAT,GAAkB,CAAhC;AACH;AACJ;;AACD,YAAMC,UAAU,GAAGV,QAAQ,CAACO,WAAD,CAA3B;AACAG,QAAAA,UAAU,SAAV,IAAAA,UAAU,WAAV,YAAAA,UAAU,CAAEC,KAAZ,GAtB6D;;AAyB7D,YAAID,UAAU,IAAIhB,cAAlB,EAAkC;AAC9BA,UAAAA,cAAc,CAACgB,UAAD,CAAd;AACH;;AAED,YAAIjB,YAAJ,EAAkB;AACba,UAAAA,GAAG,CAACM,aAAL,CAAmCC,YAAnC,CAAgD,UAAhD,EAA4D,IAA5D;AACAH,UAAAA,UAAU,SAAV,IAAAA,UAAU,WAAV,YAAAA,UAAU,CAAEG,YAAZ,CAAyB,UAAzB,EAAqC,GAArC;AACH;AACJ,OAjCqB;AAAA,KAAtB;;AAmCA,QAAI,CAAAb,QAAQ,SAAR,IAAAA,QAAQ,WAAR,YAAAA,QAAQ,CAAES,MAAV,IAAmB,CAAvB,EAA0B;AACtBT,MAAAA,QAAQ,CAACc,OAAT,CAAiB,UAACC,EAAD,EAAKP,GAAL,EAAa;AAC1B;AACA,YAAI,CAACN,uBAAD,IAA4BM,GAAG,KAAK,CAAxC,EAA2C;AACvCO,UAAAA,EAAE,CAACF,YAAH,CAAgB,UAAhB,EAA4B,GAA5B,EADuC;AAG1C,SAHD,MAGO,IAAIX,uBAAuB,KAAKa,EAAhC,EAAoC;AACvCA,UAAAA,EAAE,CAACF,YAAH,CAAgB,UAAhB,EAA4B,IAA5B;AACH,SAPyB;;;AAS1BE,QAAAA,EAAE,CAACC,gBAAH,CAAoB,SAApB,EAA+BZ,aAAa,CAACI,GAAD,CAA5C;AACH,OAVD;AAWH,KAxDC;;;AA2DF,WAAO,YAAM;AACT,UAAI,CAAAR,QAAQ,SAAR,IAAAA,QAAQ,WAAR,YAAAA,QAAQ,CAAES,MAAV,IAAmB,CAAvB,EAA0B;AACtBT,QAAAA,QAAQ,CAACc,OAAT,CAAiB,UAACC,EAAD,EAAKP,GAAL,EAAa;AAC1BO,UAAAA,EAAE,CAACE,mBAAH,CAAuB,SAAvB,EAAkCb,aAAa,CAACI,GAAD,CAA/C;AACH,SAFD;AAGH;AACJ,KAND;AAOH,GAnEI;AAAA,GAoEJjB,SApEI,4BAoEUI,iBApEV,GAAT;AAsEH;;;;"}
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/useRovingTabIndex.js';
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.25",
11
- "@lumx/icons": "^2.2.25",
10
+ "@lumx/core": "^2.2.26-alpha-a11y-slideshow.3",
11
+ "@lumx/icons": "^2.2.26-alpha-a11y-slideshow.3",
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.25",
124
- "gitHead": "b4b01794bb3639e816f4242dc57b7f9a03e56f46"
123
+ "version": "2.2.26-alpha-a11y-slideshow.3",
124
+ "gitHead": "ea78a765afc65f2d2861b69c27d07149a23cc9f2"
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
+ const childrenArray = Children.toArray(children);
82
+ return groupBy && groupBy > 1 ? chunk(childrenArray, groupBy) : childrenArray;
83
+ }, [children, groupBy]);
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
- {children}
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(3);
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<SlideshowControlsProps, 'nextButtonProps' | 'previousButtonProps'> &
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}