@workday/canvas-kit-react 9.0.0-alpha.411-next.13 → 9.0.0-alpha.412-next.14

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 (175) hide show
  1. package/collection/lib/useBaseListModel.tsx +24 -12
  2. package/collection/lib/useCursorListModel.tsx +102 -78
  3. package/dist/commonjs/action-bar/lib/ActionBar.d.ts +104 -0
  4. package/dist/commonjs/action-bar/lib/ActionBar.d.ts.map +1 -1
  5. package/dist/commonjs/action-bar/lib/ActionBarItem.d.ts +3 -0
  6. package/dist/commonjs/action-bar/lib/ActionBarItem.d.ts.map +1 -1
  7. package/dist/commonjs/action-bar/lib/ActionBarList.d.ts +3 -0
  8. package/dist/commonjs/action-bar/lib/ActionBarList.d.ts.map +1 -1
  9. package/dist/commonjs/action-bar/lib/ActionBarOverflowButton.d.ts +4 -0
  10. package/dist/commonjs/action-bar/lib/ActionBarOverflowButton.d.ts.map +1 -1
  11. package/dist/commonjs/action-bar/lib/useActionBarModel.d.ts +55 -0
  12. package/dist/commonjs/action-bar/lib/useActionBarModel.d.ts.map +1 -1
  13. package/dist/commonjs/breadcrumbs/lib/Breadcrumbs.d.ts +107 -0
  14. package/dist/commonjs/breadcrumbs/lib/Breadcrumbs.d.ts.map +1 -1
  15. package/dist/commonjs/breadcrumbs/lib/BreadcrumbsCurrentItem.d.ts +4 -0
  16. package/dist/commonjs/breadcrumbs/lib/BreadcrumbsCurrentItem.d.ts.map +1 -1
  17. package/dist/commonjs/breadcrumbs/lib/BreadcrumbsItem.d.ts +3 -0
  18. package/dist/commonjs/breadcrumbs/lib/BreadcrumbsItem.d.ts.map +1 -1
  19. package/dist/commonjs/breadcrumbs/lib/BreadcrumbsList.d.ts +2 -0
  20. package/dist/commonjs/breadcrumbs/lib/BreadcrumbsList.d.ts.map +1 -1
  21. package/dist/commonjs/breadcrumbs/lib/BreadcrumbsMenu.d.ts +49 -0
  22. package/dist/commonjs/breadcrumbs/lib/BreadcrumbsMenu.d.ts.map +1 -1
  23. package/dist/commonjs/breadcrumbs/lib/BreadcrumbsOverflowButton.d.ts +4 -0
  24. package/dist/commonjs/breadcrumbs/lib/BreadcrumbsOverflowButton.d.ts.map +1 -1
  25. package/dist/commonjs/breadcrumbs/lib/hooks/useBreadcrumbsModel.d.ts +55 -0
  26. package/dist/commonjs/breadcrumbs/lib/hooks/useBreadcrumbsModel.d.ts.map +1 -1
  27. package/dist/commonjs/collection/lib/ListBox.d.ts +39 -0
  28. package/dist/commonjs/collection/lib/ListBox.d.ts.map +1 -1
  29. package/dist/commonjs/collection/lib/useBaseListModel.d.ts +20 -10
  30. package/dist/commonjs/collection/lib/useBaseListModel.d.ts.map +1 -1
  31. package/dist/commonjs/collection/lib/useBaseListModel.js +21 -10
  32. package/dist/commonjs/collection/lib/useCursorListModel.d.ts +171 -10
  33. package/dist/commonjs/collection/lib/useCursorListModel.d.ts.map +1 -1
  34. package/dist/commonjs/collection/lib/useCursorListModel.js +97 -64
  35. package/dist/commonjs/collection/lib/useGridModel.d.ts +39 -0
  36. package/dist/commonjs/collection/lib/useGridModel.d.ts.map +1 -1
  37. package/dist/commonjs/collection/lib/useListItemRegister.d.ts +1 -0
  38. package/dist/commonjs/collection/lib/useListItemRegister.d.ts.map +1 -1
  39. package/dist/commonjs/collection/lib/useListItemRovingFocus.d.ts +1 -0
  40. package/dist/commonjs/collection/lib/useListItemRovingFocus.d.ts.map +1 -1
  41. package/dist/commonjs/collection/lib/useListItemSelect.d.ts +1 -0
  42. package/dist/commonjs/collection/lib/useListItemSelect.d.ts.map +1 -1
  43. package/dist/commonjs/collection/lib/useListModel.d.ts +39 -0
  44. package/dist/commonjs/collection/lib/useListModel.d.ts.map +1 -1
  45. package/dist/commonjs/collection/lib/useListResetCursorOnBlur.d.ts +1 -0
  46. package/dist/commonjs/collection/lib/useListResetCursorOnBlur.d.ts.map +1 -1
  47. package/dist/commonjs/collection/lib/useOverflowListItemMeasure.d.ts +1 -0
  48. package/dist/commonjs/collection/lib/useOverflowListItemMeasure.d.ts.map +1 -1
  49. package/dist/commonjs/collection/lib/useOverflowListMeasure.d.ts +1 -0
  50. package/dist/commonjs/collection/lib/useOverflowListMeasure.d.ts.map +1 -1
  51. package/dist/commonjs/collection/lib/useOverflowListModel.d.ts +51 -0
  52. package/dist/commonjs/collection/lib/useOverflowListModel.d.ts.map +1 -1
  53. package/dist/commonjs/collection/lib/useOverflowListTarget.d.ts +1 -0
  54. package/dist/commonjs/collection/lib/useOverflowListTarget.d.ts.map +1 -1
  55. package/dist/commonjs/collection/lib/useSelectionListModel.d.ts +39 -0
  56. package/dist/commonjs/collection/lib/useSelectionListModel.d.ts.map +1 -1
  57. package/dist/commonjs/menu/lib/Menu.d.ts +48 -0
  58. package/dist/commonjs/menu/lib/Menu.d.ts.map +1 -1
  59. package/dist/commonjs/menu/lib/MenuCard.d.ts +2 -0
  60. package/dist/commonjs/menu/lib/MenuCard.d.ts.map +1 -1
  61. package/dist/commonjs/menu/lib/MenuContextTarget.d.ts +3 -0
  62. package/dist/commonjs/menu/lib/MenuContextTarget.d.ts.map +1 -1
  63. package/dist/commonjs/menu/lib/MenuItem.d.ts +2 -0
  64. package/dist/commonjs/menu/lib/MenuItem.d.ts.map +1 -1
  65. package/dist/commonjs/menu/lib/MenuList.d.ts +2 -0
  66. package/dist/commonjs/menu/lib/MenuList.d.ts.map +1 -1
  67. package/dist/commonjs/menu/lib/MenuPopper.d.ts +1 -0
  68. package/dist/commonjs/menu/lib/MenuPopper.d.ts.map +1 -1
  69. package/dist/commonjs/menu/lib/MenuTarget.d.ts +5 -0
  70. package/dist/commonjs/menu/lib/MenuTarget.d.ts.map +1 -1
  71. package/dist/commonjs/menu/lib/useMenuModel.d.ts +45 -0
  72. package/dist/commonjs/menu/lib/useMenuModel.d.ts.map +1 -1
  73. package/dist/commonjs/tabs/lib/Tabs.d.ts +158 -7
  74. package/dist/commonjs/tabs/lib/Tabs.d.ts.map +1 -1
  75. package/dist/commonjs/tabs/lib/TabsItem.d.ts +4 -0
  76. package/dist/commonjs/tabs/lib/TabsItem.d.ts.map +1 -1
  77. package/dist/commonjs/tabs/lib/TabsList.d.ts +4 -0
  78. package/dist/commonjs/tabs/lib/TabsList.d.ts.map +1 -1
  79. package/dist/commonjs/tabs/lib/TabsMenuPopper.d.ts +1 -0
  80. package/dist/commonjs/tabs/lib/TabsMenuPopper.d.ts.map +1 -1
  81. package/dist/commonjs/tabs/lib/TabsOverflowButton.d.ts +4 -0
  82. package/dist/commonjs/tabs/lib/TabsOverflowButton.d.ts.map +1 -1
  83. package/dist/commonjs/tabs/lib/TabsPanel.d.ts +4 -0
  84. package/dist/commonjs/tabs/lib/TabsPanel.d.ts.map +1 -1
  85. package/dist/commonjs/tabs/lib/TabsPanels.d.ts +2 -0
  86. package/dist/commonjs/tabs/lib/TabsPanels.d.ts.map +1 -1
  87. package/dist/commonjs/tabs/lib/useTabsModel.d.ts +143 -12
  88. package/dist/commonjs/tabs/lib/useTabsModel.d.ts.map +1 -1
  89. package/dist/es6/action-bar/lib/ActionBar.d.ts +104 -0
  90. package/dist/es6/action-bar/lib/ActionBar.d.ts.map +1 -1
  91. package/dist/es6/action-bar/lib/ActionBarItem.d.ts +3 -0
  92. package/dist/es6/action-bar/lib/ActionBarItem.d.ts.map +1 -1
  93. package/dist/es6/action-bar/lib/ActionBarList.d.ts +3 -0
  94. package/dist/es6/action-bar/lib/ActionBarList.d.ts.map +1 -1
  95. package/dist/es6/action-bar/lib/ActionBarOverflowButton.d.ts +4 -0
  96. package/dist/es6/action-bar/lib/ActionBarOverflowButton.d.ts.map +1 -1
  97. package/dist/es6/action-bar/lib/useActionBarModel.d.ts +55 -0
  98. package/dist/es6/action-bar/lib/useActionBarModel.d.ts.map +1 -1
  99. package/dist/es6/breadcrumbs/lib/Breadcrumbs.d.ts +107 -0
  100. package/dist/es6/breadcrumbs/lib/Breadcrumbs.d.ts.map +1 -1
  101. package/dist/es6/breadcrumbs/lib/BreadcrumbsCurrentItem.d.ts +4 -0
  102. package/dist/es6/breadcrumbs/lib/BreadcrumbsCurrentItem.d.ts.map +1 -1
  103. package/dist/es6/breadcrumbs/lib/BreadcrumbsItem.d.ts +3 -0
  104. package/dist/es6/breadcrumbs/lib/BreadcrumbsItem.d.ts.map +1 -1
  105. package/dist/es6/breadcrumbs/lib/BreadcrumbsList.d.ts +2 -0
  106. package/dist/es6/breadcrumbs/lib/BreadcrumbsList.d.ts.map +1 -1
  107. package/dist/es6/breadcrumbs/lib/BreadcrumbsMenu.d.ts +49 -0
  108. package/dist/es6/breadcrumbs/lib/BreadcrumbsMenu.d.ts.map +1 -1
  109. package/dist/es6/breadcrumbs/lib/BreadcrumbsOverflowButton.d.ts +4 -0
  110. package/dist/es6/breadcrumbs/lib/BreadcrumbsOverflowButton.d.ts.map +1 -1
  111. package/dist/es6/breadcrumbs/lib/hooks/useBreadcrumbsModel.d.ts +55 -0
  112. package/dist/es6/breadcrumbs/lib/hooks/useBreadcrumbsModel.d.ts.map +1 -1
  113. package/dist/es6/collection/lib/ListBox.d.ts +39 -0
  114. package/dist/es6/collection/lib/ListBox.d.ts.map +1 -1
  115. package/dist/es6/collection/lib/useBaseListModel.d.ts +20 -10
  116. package/dist/es6/collection/lib/useBaseListModel.d.ts.map +1 -1
  117. package/dist/es6/collection/lib/useBaseListModel.js +22 -11
  118. package/dist/es6/collection/lib/useCursorListModel.d.ts +171 -10
  119. package/dist/es6/collection/lib/useCursorListModel.d.ts.map +1 -1
  120. package/dist/es6/collection/lib/useCursorListModel.js +97 -64
  121. package/dist/es6/collection/lib/useGridModel.d.ts +39 -0
  122. package/dist/es6/collection/lib/useGridModel.d.ts.map +1 -1
  123. package/dist/es6/collection/lib/useListItemRegister.d.ts +1 -0
  124. package/dist/es6/collection/lib/useListItemRegister.d.ts.map +1 -1
  125. package/dist/es6/collection/lib/useListItemRovingFocus.d.ts +1 -0
  126. package/dist/es6/collection/lib/useListItemRovingFocus.d.ts.map +1 -1
  127. package/dist/es6/collection/lib/useListItemSelect.d.ts +1 -0
  128. package/dist/es6/collection/lib/useListItemSelect.d.ts.map +1 -1
  129. package/dist/es6/collection/lib/useListModel.d.ts +39 -0
  130. package/dist/es6/collection/lib/useListModel.d.ts.map +1 -1
  131. package/dist/es6/collection/lib/useListResetCursorOnBlur.d.ts +1 -0
  132. package/dist/es6/collection/lib/useListResetCursorOnBlur.d.ts.map +1 -1
  133. package/dist/es6/collection/lib/useOverflowListItemMeasure.d.ts +1 -0
  134. package/dist/es6/collection/lib/useOverflowListItemMeasure.d.ts.map +1 -1
  135. package/dist/es6/collection/lib/useOverflowListMeasure.d.ts +1 -0
  136. package/dist/es6/collection/lib/useOverflowListMeasure.d.ts.map +1 -1
  137. package/dist/es6/collection/lib/useOverflowListModel.d.ts +51 -0
  138. package/dist/es6/collection/lib/useOverflowListModel.d.ts.map +1 -1
  139. package/dist/es6/collection/lib/useOverflowListTarget.d.ts +1 -0
  140. package/dist/es6/collection/lib/useOverflowListTarget.d.ts.map +1 -1
  141. package/dist/es6/collection/lib/useSelectionListModel.d.ts +39 -0
  142. package/dist/es6/collection/lib/useSelectionListModel.d.ts.map +1 -1
  143. package/dist/es6/menu/lib/Menu.d.ts +48 -0
  144. package/dist/es6/menu/lib/Menu.d.ts.map +1 -1
  145. package/dist/es6/menu/lib/MenuCard.d.ts +2 -0
  146. package/dist/es6/menu/lib/MenuCard.d.ts.map +1 -1
  147. package/dist/es6/menu/lib/MenuContextTarget.d.ts +3 -0
  148. package/dist/es6/menu/lib/MenuContextTarget.d.ts.map +1 -1
  149. package/dist/es6/menu/lib/MenuItem.d.ts +2 -0
  150. package/dist/es6/menu/lib/MenuItem.d.ts.map +1 -1
  151. package/dist/es6/menu/lib/MenuList.d.ts +2 -0
  152. package/dist/es6/menu/lib/MenuList.d.ts.map +1 -1
  153. package/dist/es6/menu/lib/MenuPopper.d.ts +1 -0
  154. package/dist/es6/menu/lib/MenuPopper.d.ts.map +1 -1
  155. package/dist/es6/menu/lib/MenuTarget.d.ts +5 -0
  156. package/dist/es6/menu/lib/MenuTarget.d.ts.map +1 -1
  157. package/dist/es6/menu/lib/useMenuModel.d.ts +45 -0
  158. package/dist/es6/menu/lib/useMenuModel.d.ts.map +1 -1
  159. package/dist/es6/tabs/lib/Tabs.d.ts +158 -7
  160. package/dist/es6/tabs/lib/Tabs.d.ts.map +1 -1
  161. package/dist/es6/tabs/lib/TabsItem.d.ts +4 -0
  162. package/dist/es6/tabs/lib/TabsItem.d.ts.map +1 -1
  163. package/dist/es6/tabs/lib/TabsList.d.ts +4 -0
  164. package/dist/es6/tabs/lib/TabsList.d.ts.map +1 -1
  165. package/dist/es6/tabs/lib/TabsMenuPopper.d.ts +1 -0
  166. package/dist/es6/tabs/lib/TabsMenuPopper.d.ts.map +1 -1
  167. package/dist/es6/tabs/lib/TabsOverflowButton.d.ts +4 -0
  168. package/dist/es6/tabs/lib/TabsOverflowButton.d.ts.map +1 -1
  169. package/dist/es6/tabs/lib/TabsPanel.d.ts +4 -0
  170. package/dist/es6/tabs/lib/TabsPanel.d.ts.map +1 -1
  171. package/dist/es6/tabs/lib/TabsPanels.d.ts +2 -0
  172. package/dist/es6/tabs/lib/TabsPanels.d.ts.map +1 -1
  173. package/dist/es6/tabs/lib/useTabsModel.d.ts +143 -12
  174. package/dist/es6/tabs/lib/useTabsModel.d.ts.map +1 -1
  175. package/package.json +3 -3
@@ -1,13 +1,12 @@
1
1
  import React from 'react';
2
2
  import {useVirtual} from './react-virtual';
3
3
 
4
- import {useUniqueId, assert, createModelHook, Generic} from '@workday/canvas-kit-react/common';
4
+ import {useUniqueId, createModelHook, Generic} from '@workday/canvas-kit-react/common';
5
5
 
6
6
  export type Orientation = 'horizontal' | 'vertical';
7
7
 
8
8
  export const defaultGetId = (item: any): string => {
9
- assert(item.id, 'A list item must have an `id` field or a `getId` function defined');
10
- return item.id;
9
+ return item.id || '';
11
10
  };
12
11
 
13
12
  export const defaultGetTextValue = (item: any): string => {
@@ -79,14 +78,18 @@ export const useBaseListModel = createModelHook({
79
78
  /** IDREF of the list. Children ids can be derived from this id */
80
79
  id: '',
81
80
  /**
82
- * Optional function to return an id of an item. If not provided, the default function will return
83
- * the `id` property from the object of each item. If you did not provide `items`, do not override
84
- * this function. If you don't provided `items` and instead provide static items via JSX, the list
85
- * will create an internal array of items where `id` is the only property and the default `getId`
86
- * will return the desired result.
81
+ * Optional function to return an id of an item. If not provided, the default function will
82
+ * return the `id` property from the object of each item. If you did not provide `items`, do not
83
+ * override this function. If you don't provided `items` and instead provide static items via
84
+ * JSX, the list will create an internal array of items where `id` is the only property and the
85
+ * default `getId` will return the desired result.
87
86
  */
88
87
  getId: (item: Generic) => item.id as string,
89
-
88
+ /**
89
+ * Optional function to return the text representation of an item. If not provided, the default
90
+ * function will return the `text` property of the object of each item or an empty string if
91
+ * there is no `text` property. If you did not provide `items`, do not override this function.
92
+ */
90
93
  getTextValue: (item: Generic) => (item.text || '') as string,
91
94
  /**
92
95
  * Array of all ids which are currently disabled. This is used for navigation to skip over items
@@ -112,8 +115,17 @@ export const useBaseListModel = createModelHook({
112
115
  })(config => {
113
116
  const id = useUniqueId(config.id);
114
117
 
118
+ // Optimization to not redo items when `getId` and `getTextValue` references change. They will not
119
+ // likely change during the lifecycle and we don't want to recalculate items when a lamba is
120
+ // passed instead of a stable reference.
121
+ const getIdRef = React.useRef(defaultGetId);
122
+ const getTextValueRef = React.useRef(defaultGetTextValue);
123
+
115
124
  const getId = config.getId || defaultGetId;
116
125
  const getTextValue = config.getTextValue || defaultGetTextValue;
126
+ getIdRef.current = getId;
127
+ getTextValueRef.current = getTextValue;
128
+
117
129
  const [orientation] = React.useState(config.orientation || 'vertical');
118
130
  const [UNSTABLE_defaultItemHeight, setDefaultItemHeight] = React.useState(
119
131
  config.defaultItemHeight
@@ -125,13 +137,13 @@ export const useBaseListModel = createModelHook({
125
137
  () =>
126
138
  (config.items || []).map((item, index) => {
127
139
  return {
128
- id: getId(item),
140
+ id: getIdRef.current(item),
129
141
  index,
130
142
  value: item,
131
- textValue: getTextValue(item),
143
+ textValue: getTextValueRef.current(item),
132
144
  };
133
145
  }),
134
- [config.items, getId, getTextValue]
146
+ [config.items]
135
147
  );
136
148
  const [staticItems, setStaticItems] = React.useState<Item<Generic>[]>([]);
137
149
  const UNSTABLE_virtual = useVirtual({
@@ -18,7 +18,7 @@ export interface NavigationManager {
18
18
  * and `Ctrl+End` for Grids. */
19
19
  getLast: NavigationRequestor;
20
20
  /** Get an item with the provided `id`. */
21
- getItem: NavigationRequestor;
21
+ getItem: (id: string, model: NavigationInput) => Item<Generic>;
22
22
  /** Get the next item after the provided `id`. This will be called when the `Right` arrow key is
23
23
  * pressed for RTL languages and when the `Left` arrow is pressed for LTR languages. */
24
24
  getNext: NavigationRequestor;
@@ -69,90 +69,89 @@ export const createNavigationManager = (manager: NavigationManager) => manager;
69
69
  /**
70
70
  * Given an id and a model, return an item from the collection
71
71
  */
72
- export type NavigationRequestor = (id: string, model: NavigationInput) => Item<Generic>;
72
+ export type NavigationRequestor = (index: number, model: NavigationInput) => number;
73
73
 
74
74
  /**
75
75
  * Get the first item in a list regardless of column count
76
76
  */
77
- export const getFirst: NavigationRequestor = (_, {state}) => state.items[0];
77
+ export const getFirst: NavigationRequestor = (_, {state}) => 0;
78
78
  /**
79
79
  * Get the last item in a list regardless of column count
80
80
  */
81
- export const getLast: NavigationRequestor = (_, {state}) => state.items[state.items.length - 1];
81
+ export const getLast: NavigationRequestor = (_, {state}) => state.items.length - 1;
82
82
 
83
83
  /**
84
84
  * Get the first item in a row. If column count is 0, it will return the results of `getFirst`
85
85
  */
86
- export const getFirstOfRow: NavigationRequestor = (id, {state}) => {
86
+ export const getFirstOfRow: NavigationRequestor = (index, {state}) => {
87
87
  if (state.columnCount) {
88
- const item = getItem(id, {state});
89
- const currentIndex = item.index;
90
- const offset = currentIndex % state.columnCount;
91
- return state.items[currentIndex - offset];
88
+ const offset = index % state.columnCount;
89
+ return index - offset;
92
90
  }
93
- return getFirst(id, {state});
91
+ return getFirst(index, {state});
94
92
  };
95
93
 
96
94
  /**
97
95
  * get the last item in a row - if column count is 0, it will return the results of `getLast`
98
96
  */
99
- export const getLastOfRow: NavigationRequestor = (id, {state}) => {
97
+ export const getLastOfRow: NavigationRequestor = (index, {state}) => {
100
98
  if (state.columnCount) {
101
- const item = getItem(id, {state});
102
- const currentIndex = item.index;
103
- const offset = (currentIndex % state.columnCount) - state.columnCount + 1;
104
- let nextIndex = currentIndex - offset;
99
+ const offset = (index % state.columnCount) - state.columnCount + 1;
100
+ let nextIndex = index - offset;
105
101
  if (nextIndex >= state.items.length) {
106
102
  nextIndex = state.items.length - 1;
107
103
  }
108
- return state.items[nextIndex];
104
+ return nextIndex;
109
105
  }
110
- return getLast(id, {state});
106
+ return getLast(index, {state});
111
107
  };
112
108
 
113
109
  /**
114
110
  * get the item in the previous page. This can be author defined. By default it will return the
115
111
  * first item for a list, and the first item in the same column for a grid.
116
112
  */
117
- export const getPreviousPage: NavigationRequestor = (id, {state}) => {
113
+ export const getPreviousPage: NavigationRequestor = (index, {state}) => {
118
114
  if (state.columnCount) {
119
- const item = getItem(id, {state});
120
- const currentIndex = item.index;
121
- return state.items[currentIndex % state.columnCount];
115
+ return index % state.columnCount;
122
116
  }
123
- return getFirst(id, {state});
117
+ return getFirst(index, {state});
124
118
  };
125
119
 
126
120
  /**
127
121
  * get the item in the next page. This can be author defined. By default, it will return the last
128
122
  * item for a list, and the last item in the same column for a grid.
129
123
  */
130
- export const getNextPage: NavigationRequestor = (id, {state}) => {
124
+ export const getNextPage: NavigationRequestor = (index, {state}) => {
131
125
  if (state.columnCount) {
132
- const item = getItem(id, {state});
133
- const currentIndex = item.index;
134
126
  const lastRowIndex = state.items.length - state.columnCount;
135
- return state.items[lastRowIndex + (currentIndex % state.columnCount)];
127
+ return lastRowIndex + (index % state.columnCount);
136
128
  }
137
- return getLast(id, {state});
129
+ return getLast(index, {state});
138
130
  };
139
131
 
140
- const getItem: NavigationRequestor = (id, {state}) => {
141
- const item = id ? state.items.find(item => getId(item) === id) : getFirst(id, {state}); // no id, return first item
132
+ const getItem: (id: string, model: NavigationInput) => Item<Generic> = (id, {state}) => {
133
+ const item = id ? state.items.find(item => item.id === id) : state.items[0]; // no id, return first item
142
134
  assert(item, `Item not found: ${id}`);
143
135
  return item;
144
136
  };
145
137
 
146
138
  export const getWrappingOffsetItem = (offset: number) => (
147
- id: string,
139
+ index: number,
148
140
  {state}: NavigationInput,
149
141
  tries = state.items.length
150
- ): Item<Generic> => {
142
+ ): number => {
143
+ if (Number.isNaN(index)) {
144
+ // we have no valid index. If the offset is positive, we'll return the first item
145
+ if (offset === 1) {
146
+ return getFirst(index, {state});
147
+ }
148
+ if (offset === -1) {
149
+ return getLast(index, {state});
150
+ }
151
+ }
151
152
  const items = state.items;
152
- const item = getItem(id, {state});
153
153
 
154
- const currentIndex = item.index;
155
- let nextIndex = currentIndex + offset;
154
+ let nextIndex = index + offset;
156
155
 
157
156
  // calculate idealLength as in if the grid was a perfect rectangle
158
157
  const rows = Math.ceil(items.length / state.columnCount);
@@ -185,34 +184,32 @@ export const getWrappingOffsetItem = (offset: number) => (
185
184
  }
186
185
  }
187
186
 
188
- if (state.nonInteractiveIds.includes(getId(items[nextIndex])) && tries > 0) {
187
+ if (state.nonInteractiveIds.includes(items[nextIndex].id) && tries > 0) {
189
188
  // The next item is disabled, try again, but only if we haven't already tried everything.
190
189
  // Avoid an infinite loop with `tries`
191
- return getWrappingOffsetItem(offset)(getId(items[nextIndex]), {state}, tries - 1);
190
+ return getWrappingOffsetItem(offset)(nextIndex, {state}, tries - 1);
192
191
  }
193
192
 
194
- return items[nextIndex];
193
+ return nextIndex;
195
194
  };
196
195
 
197
196
  export const getOffsetItem = (offset: number) => (
198
- id: string,
197
+ index: number,
199
198
  {state}: NavigationInput,
200
199
  tries = state.items.length
201
- ): Item<Generic> => {
200
+ ): number => {
202
201
  const {items, columnCount} = state;
203
- const item = getItem(id, {state});
204
202
 
205
- const currentIndex = item.index;
206
- let nextIndex = currentIndex + offset;
203
+ let nextIndex = index + offset;
207
204
 
208
205
  if (Math.abs(offset) < columnCount) {
209
206
  // if we're here, the columnCount is non-zero and the absolute value of offset is less than the
210
207
  // column count. We don't want to wrap, so we'll bound within the row
211
208
 
212
- const currentIndexInRow = currentIndex % columnCount;
213
- const nextIndexInRow = nextIndex - currentIndex + currentIndexInRow;
209
+ const currentIndexInRow = index % columnCount;
210
+ const nextIndexInRow = nextIndex - index + currentIndexInRow;
214
211
  if (nextIndexInRow >= columnCount || nextIndexInRow < 0) {
215
- nextIndex = currentIndex;
212
+ nextIndex = index;
216
213
  }
217
214
  } else if (columnCount) {
218
215
  // if we're here, there's a column count, but the offset will move into another row. We need to
@@ -220,12 +217,12 @@ export const getOffsetItem = (offset: number) => (
220
217
  const nextRow = Math.floor(nextIndex / columnCount);
221
218
 
222
219
  if (nextRow < 0 || nextRow >= columnCount) {
223
- nextIndex = currentIndex;
220
+ nextIndex = index;
224
221
  }
225
222
 
226
223
  // make sure we don't go out of bounds if the grid isn't a perfect rectangle
227
224
  if (nextIndex > items.length - 1) {
228
- nextIndex = currentIndex;
225
+ nextIndex = index;
229
226
  }
230
227
  }
231
228
 
@@ -236,13 +233,13 @@ export const getOffsetItem = (offset: number) => (
236
233
  nextIndex = items.length - 1;
237
234
  }
238
235
 
239
- if (state.nonInteractiveIds.includes(getId(items[nextIndex])) && tries > 0) {
236
+ if (state.nonInteractiveIds.includes(items[nextIndex].id) && tries > 0) {
240
237
  // The next item is disabled, try again, but only if we haven't already tried everything.
241
238
  // Avoid an infinite loop with `tries`
242
- return getOffsetItem(offset)(getId(items[nextIndex]), {state}, tries - 1);
239
+ return getOffsetItem(offset)(nextIndex, {state}, tries - 1);
243
240
  }
244
241
 
245
- return items[nextIndex];
242
+ return nextIndex;
246
243
  };
247
244
 
248
245
  /**
@@ -255,9 +252,9 @@ export const wrappingNavigationManager = createNavigationManager({
255
252
  getLast,
256
253
  getItem,
257
254
  getNext: getWrappingOffsetItem(1),
258
- getNextRow: (id, {state}) => getWrappingOffsetItem(state.columnCount)(id, {state}),
255
+ getNextRow: (index, {state}) => getWrappingOffsetItem(state.columnCount)(index, {state}),
259
256
  getPrevious: getWrappingOffsetItem(-1),
260
- getPreviousRow: (id, {state}) => getWrappingOffsetItem(-state.columnCount)(id, {state}),
257
+ getPreviousRow: (index, {state}) => getWrappingOffsetItem(-state.columnCount)(index, {state}),
261
258
  getPreviousPage,
262
259
  getNextPage,
263
260
  getFirstOfRow,
@@ -273,9 +270,9 @@ export const navigationManager = createNavigationManager({
273
270
  getLast,
274
271
  getItem,
275
272
  getNext: getOffsetItem(1),
276
- getNextRow: (id, {state}) => getOffsetItem(state.columnCount)(id, {state}),
273
+ getNextRow: (index, {state}) => getOffsetItem(state.columnCount)(index, {state}),
277
274
  getPrevious: getOffsetItem(-1),
278
- getPreviousRow: (id, {state}) => getOffsetItem(-state.columnCount)(id, {state}),
275
+ getPreviousRow: (index, {state}) => getOffsetItem(-state.columnCount)(index, {state}),
279
276
  getPreviousPage,
280
277
  getNextPage,
281
278
  getFirstOfRow,
@@ -312,16 +309,35 @@ export const useCursorListModel = createModelHook({
312
309
  * well as left/right keys to give a more consistent experience to all users.
313
310
  */
314
311
  navigation: wrappingNavigationManager,
312
+ /**
313
+ * Controls how much a pageUp/pageDown navigation request will jump. If not provided, the size
314
+ * of the list and number of items rendered will determine this value.
315
+ */
316
+ pageSize: 0,
315
317
  },
316
318
  requiredConfig: useBaseListModel.requiredConfig,
319
+ contextOverride: useBaseListModel.Context,
317
320
  })(config => {
318
321
  const [cursorId, setCursorId] = React.useState(config.initialCursorId);
322
+ const pageSizeRef = React.useRef(config.pageSize);
319
323
  const columnCount = config.columnCount || 0;
320
324
  const list = useBaseListModel(config);
321
325
  const initialCurrentRef = React.useRef(
322
- config.initialCursorId || (config.items?.length ? getId(list.state.items![0]) : '')
326
+ config.initialCursorId || (config.items?.length ? list.state.items![0].id : '')
323
327
  );
324
328
  const navigation = config.navigation;
329
+ const cursorIndexRef = React.useRef(-1);
330
+ const setCursor = (index: number) => {
331
+ const id = state.items[index]?.id || '';
332
+ setCursorId(id);
333
+ };
334
+
335
+ // Keep the cursorIndex up to date with the cursor ID
336
+ if (cursorId && list.state.items[cursorIndexRef.current]?.id !== cursorId) {
337
+ cursorIndexRef.current = list.state.items.findIndex(item => item.id === cursorId);
338
+ } else if (!cursorId) {
339
+ cursorIndexRef.current = -1;
340
+ }
325
341
 
326
342
  const state = {
327
343
  ...list.state,
@@ -333,6 +349,11 @@ export const useCursorListModel = createModelHook({
333
349
  * @private Use useGridModel instead to make a grid instead of a list
334
350
  */
335
351
  columnCount,
352
+ /**
353
+ * A React.Ref of the current page size. Either provided as config, or determined at runtime
354
+ * based on the size of the list container and the number of items fitting within the container.
355
+ */
356
+ pageSizeRef,
336
357
  };
337
358
 
338
359
  const events = {
@@ -351,7 +372,8 @@ export const useCursorListModel = createModelHook({
351
372
  },
352
373
  /** Directly sets the cursor to the list item by its identifier. */
353
374
  goTo(data: {id: string}) {
354
- setCursorId(data.id);
375
+ const index = state.items.findIndex(item => item.id === data.id);
376
+ setCursor(index);
355
377
  },
356
378
  /**
357
379
  * Set the cursor to the "next" item in the list. This event delegates to the `getNext` method of
@@ -360,14 +382,16 @@ export const useCursorListModel = createModelHook({
360
382
  * item in a row.
361
383
  */
362
384
  goToNext() {
363
- setCursorId(getId(navigation.getNext(state.cursorId, {state})));
385
+ const index = navigation.getNext(cursorIndexRef.current, {state});
386
+ setCursor(index);
364
387
  },
365
388
  /**
366
389
  * Set the cursor to the "previous" item in the list. If the beginning of the list is detected,
367
390
  * it will wrap to the last item
368
391
  */
369
392
  goToPrevious() {
370
- setCursorId(getId(navigation.getPrevious(state.cursorId, {state})));
393
+ const index = navigation.getPrevious(cursorIndexRef.current, {state});
394
+ setCursor(index);
371
395
  },
372
396
  /**
373
397
  * Previous item perpendicular to the orientation of a list, or the previous row in a grid. For
@@ -376,13 +400,10 @@ export const useCursorListModel = createModelHook({
376
400
  * this would be the previous row (current position - column count).
377
401
  */
378
402
  goToPreviousRow() {
379
- setCursorId(
380
- getId(
381
- navigation.getPreviousRow(state.cursorId, {
382
- state,
383
- })
384
- )
385
- );
403
+ const index = navigation.getPreviousRow(cursorIndexRef.current, {
404
+ state,
405
+ });
406
+ setCursor(index);
386
407
  },
387
408
  /**
388
409
  * Next item perpendicular to the orientation of a list, or the next row in a grid. For example,
@@ -391,33 +412,36 @@ export const useCursorListModel = createModelHook({
391
412
  * next row (current position + column count).
392
413
  */
393
414
  goToNextRow() {
394
- setCursorId(getId(navigation.getNextRow(state.cursorId, {state})));
415
+ const index = navigation.getNextRow(cursorIndexRef.current, {state});
416
+ setCursor(index);
395
417
  },
396
418
  /** Set the cursor to the first item in the list */
397
419
  goToFirst() {
398
- setCursorId(getId(navigation.getFirst(state.cursorId, {state})));
420
+ const index = navigation.getFirst(cursorIndexRef.current, {state});
421
+ setCursor(index);
399
422
  },
400
423
  /** Set the cursor to the last item in the list */
401
424
  goToLast() {
402
- setCursorId(getId(navigation.getLast(state.cursorId, {state})));
425
+ const index = navigation.getLast(cursorIndexRef.current, {state});
426
+ setCursor(index);
403
427
  },
404
428
  goToFirstOfRow() {
405
- setCursorId(getId(navigation.getFirstOfRow(state.cursorId, {state})));
429
+ const index = navigation.getFirstOfRow(cursorIndexRef.current, {state});
430
+ setCursor(index);
406
431
  },
407
432
  goToLastOfRow() {
408
- setCursorId(getId(navigation.getLastOfRow(state.cursorId, {state})));
433
+ const index = navigation.getLastOfRow(cursorIndexRef.current, {state});
434
+ setCursor(index);
409
435
  },
410
436
  goToNextPage() {
411
- setCursorId(getId(navigation.getNextPage(state.cursorId, {state})));
437
+ const index = navigation.getNextPage(cursorIndexRef.current, {state});
438
+ setCursor(index);
412
439
  },
413
440
  goToPreviousPage() {
414
- setCursorId(
415
- getId(
416
- navigation.getPreviousPage(state.cursorId, {
417
- state,
418
- })
419
- )
420
- );
441
+ const index = navigation.getPreviousPage(cursorIndexRef.current, {
442
+ state,
443
+ });
444
+ setCursor(index);
421
445
  },
422
446
  };
423
447