@enact/limestone 1.9.3 → 1.9.4

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.
@@ -3,8 +3,8 @@ name: CI
3
3
  on:
4
4
  push:
5
5
  branches:
6
- - develop
7
- - master
6
+ - release/1.9.x.develop
7
+ - release/1.9.x
8
8
  pull_request:
9
9
  types: [opened, synchronize, reopened]
10
10
  workflow_dispatch:
@@ -35,7 +35,7 @@ jobs:
35
35
 
36
36
  - name: Clone and setup Enact CLI
37
37
  run: |
38
- git clone --branch=7.2.1 --depth 1 https://github.com/enactjs/cli ../cli
38
+ git clone --branch=release/7.2.x.develop --depth 1 https://github.com/enactjs/cli ../cli
39
39
  pushd ../cli
40
40
  npm install
41
41
  npm link
package/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  The following is a curated list of changes in the Enact limestone module, newest changes on the top.
4
4
 
5
+ ## [1.9.4] - 2026-06-18
6
+
7
+ ### Fixed
8
+
9
+ - `limestone/VirtualList` focus jump and scroll freeze when scrolled by long press
10
+
5
11
  ## [1.9.3] - 2026-04-20
6
12
 
7
13
  ### Fixed
@@ -27,6 +27,14 @@ var keyDownUp = function keyDownUp(keyCode) {
27
27
  });
28
28
  };
29
29
  };
30
+ var keyDownRepeat = function keyDownRepeat(keyCode) {
31
+ return function (elm) {
32
+ return _react.fireEvent.keyDown(elm, {
33
+ keyCode: keyCode,
34
+ repeat: true
35
+ });
36
+ };
37
+ };
30
38
  var pressLeftKey = keyDownUp(37);
31
39
  var pressRightKey = keyDownUp(39);
32
40
  var pressUpKey = keyDownUp(38);
@@ -148,6 +156,37 @@ describe('VirtualList useEvent', function () {
148
156
  expect(spy).toHaveBeenCalled();
149
157
  global.Element.prototype.scrollTo = scrollToFn;
150
158
  });
159
+ test('should handle repeat keydown on first VirtualList entry without error', function () {
160
+ (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_VirtualList["default"], {
161
+ clientSize: clientSize,
162
+ dataSize: dataSize,
163
+ itemRenderer: renderItem,
164
+ itemSize: itemSize
165
+ }));
166
+ var list = _react.screen.getByRole('list');
167
+ var item0 = list.children.item(0).children.item(0);
168
+ focus(item0);
169
+ expect(currentFocusIndex).toBe(0);
170
+ keyDownRepeat(40)(item0);
171
+ expect(currentFocusIndex).toBe(0);
172
+ });
173
+ test('should handle repeat keydown when data-index jumps unexpectedly without error', function () {
174
+ (0, _react.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_VirtualList["default"], {
175
+ clientSize: clientSize,
176
+ dataSize: dataSize,
177
+ itemRenderer: renderItem,
178
+ itemSize: itemSize
179
+ }));
180
+ var list = _react.screen.getByRole('list');
181
+ var item0 = list.children.item(0).children.item(0);
182
+ var item1 = list.children.item(1).children.item(0);
183
+ focus(item0);
184
+ pressDownKey(item0);
185
+ expect(currentFocusIndex).toBe(1);
186
+ item1.dataset.index = '20';
187
+ keyDownRepeat(40)(item1);
188
+ expect(currentFocusIndex).toBe(1);
189
+ });
151
190
  test('should scroll by page-down key', function () {
152
191
  var spy = jest.fn(function () {});
153
192
  var scrollToFn = global.Element.prototype.scrollTo;
@@ -28,13 +28,14 @@ var isDown = (0, _keymap.is)('down'),
28
28
  // should return -1 if index is not a number or a negative value
29
29
  return number >= 0 ? number : -1;
30
30
  };
31
- var prevKeyDownIndex = -1;
32
31
  var useEventKey = exports.useEventKey = function useEventKey(props, instances, context) {
33
32
  // Mutable value
34
33
 
35
34
  var mutableRef = (0, _react.useRef)({
36
35
  fn: null
37
36
  });
37
+ var prevKeyDownIndexRef = (0, _react.useRef)(-1);
38
+ var hasProcessedKeyDownRef = (0, _react.useRef)(false);
38
39
 
39
40
  // Functions
40
41
 
@@ -121,6 +122,7 @@ var useEventKey = exports.useEventKey = function useEventKey(props, instances, c
121
122
  var handle5WayKeyUp = context.handle5WayKeyUp,
122
123
  handleDirectionKeyDown = context.handleDirectionKeyDown,
123
124
  handlePageUpDownKeyDown = context.handlePageUpDownKeyDown,
125
+ resetAccelerator = context.resetAccelerator,
124
126
  spotlightAcceleratorProcessKey = context.spotlightAcceleratorProcessKey;
125
127
  function handleKeyDown(ev) {
126
128
  var keyCode = ev.keyCode,
@@ -163,15 +165,50 @@ var useEventKey = exports.useEventKey = function useEventKey(props, instances, c
163
165
  isRightMovement = _getNextIndex.isRightMovement,
164
166
  isWrapped = _getNextIndex.isWrapped,
165
167
  nextIndex = _getNextIndex.nextIndex;
168
+ var _scrollContentHandle$2 = scrollContentHandle.current,
169
+ dimensionToExtent = _scrollContentHandle$2.dimensionToExtent,
170
+ isPrimaryDirectionVertical = _scrollContentHandle$2.isPrimaryDirectionVertical;
171
+
172
+ // VirtualList recycles DOM nodes during scroll. If a node gets reused for a different index
173
+ // while browser focus stays on it, target.dataset.index reflects the new (wrong) index.
174
+ // Detect this by checking if the index jumped unnaturally during key repeat.
175
+ var isForwardKey = isPrimaryDirectionVertical ? isDownKey : isRightMovement;
176
+ var isBackwardKey = isPrimaryDirectionVertical ? isUpKey : isLeftMovement;
177
+ var isOutdatedIndex = repeat && prevKeyDownIndexRef.current !== -1 && (isForwardKey && (prevKeyDownIndexRef.current > index || index > prevKeyDownIndexRef.current + dimensionToExtent) || isBackwardKey && (prevKeyDownIndexRef.current < index || index < prevKeyDownIndexRef.current - dimensionToExtent));
178
+
179
+ // Block the first repeat event when entering VirtualList from outside with acceleration.
180
+ // prevKeyDownIndexRef is -1 only on first entry; a repeat here means key was held before entering.
181
+ var isFirstEntryRepeat = repeat && !hasProcessedKeyDownRef.current;
182
+ if (isFirstEntryRepeat) {
183
+ ev.preventDefault();
184
+ ev.stopPropagation();
185
+ resetAccelerator();
186
+ return;
187
+ }
188
+ if (isOutdatedIndex) {
189
+ ev.preventDefault();
190
+ ev.stopPropagation();
191
+ resetAccelerator();
192
+ var correctedNextIndex = isForwardKey ? prevKeyDownIndexRef.current + dimensionToExtent : prevKeyDownIndexRef.current - dimensionToExtent;
193
+ if (correctedNextIndex >= 0 && correctedNextIndex < props.dataSize) {
194
+ var currentItemNode = instances.itemRefs.current[prevKeyDownIndexRef.current % scrollContentHandle.current.state.numOfItems];
195
+ var correctedTarget = currentItemNode && parseInt(currentItemNode.dataset.index) === prevKeyDownIndexRef.current ? currentItemNode : target;
196
+ handleDirectionKeyDown(ev, 'acceleratedKeyDown', {
197
+ isWrapped: false,
198
+ keyCode: keyCode,
199
+ nextIndex: correctedNextIndex,
200
+ repeat: repeat,
201
+ target: correctedTarget
202
+ });
203
+ prevKeyDownIndexRef.current = correctedNextIndex;
204
+ }
205
+ // At list boundary: keep prevKeyDownIndexRef.current unchanged so next event stays recoverable
206
+ return;
207
+ }
166
208
  if (nextIndex >= 0) {
167
209
  // if the candidate is another item
168
210
  ev.preventDefault();
169
211
  ev.stopPropagation();
170
- if (repeat && prevKeyDownIndex !== -1 && (isDownKey && prevKeyDownIndex > index || isUpKey && prevKeyDownIndex < index)) {
171
- // Ignore keyEvent from item with wrong data-index (Workaround for data-index bug)
172
- // Sometimes keyDown event occurs before the data-index updated, it causes reverse focus change
173
- return;
174
- }
175
212
  if (props.scrollContainerHandle && props.scrollContainerHandle.current) {
176
213
  props.scrollContainerHandle.current.lastInputType = 'arrowKey';
177
214
  }
@@ -188,9 +225,6 @@ var useEventKey = exports.useEventKey = function useEventKey(props, instances, c
188
225
  focusableScrollbar = props.focusableScrollbar,
189
226
  isHorizontalScrollbarVisible = props.isHorizontalScrollbarVisible,
190
227
  isVerticalScrollbarVisible = props.isVerticalScrollbarVisible;
191
- var _scrollContentHandle$2 = scrollContentHandle.current,
192
- dimensionToExtent = _scrollContentHandle$2.dimensionToExtent,
193
- isPrimaryDirectionVertical = _scrollContentHandle$2.isPrimaryDirectionVertical;
194
228
  var column = index % dimensionToExtent;
195
229
  var row = (index - column) % dataSize / dimensionToExtent;
196
230
  var directions = {};
@@ -236,7 +270,8 @@ var useEventKey = exports.useEventKey = function useEventKey(props, instances, c
236
270
  }
237
271
  }
238
272
  }
239
- prevKeyDownIndex = index;
273
+ prevKeyDownIndexRef.current = index;
274
+ hasProcessedKeyDownRef.current = true;
240
275
  if (isLeaving) {
241
276
  handleDirectionKeyDown(ev, 'keyLeave');
242
277
  }
@@ -99,6 +99,9 @@ var useSpottable = function useSpottable(props, instances) {
99
99
  handle5WayKeyUp: function handle5WayKeyUp() {
100
100
  SpotlightAccelerator.reset();
101
101
  },
102
+ resetAccelerator: function resetAccelerator() {
103
+ SpotlightAccelerator.reset();
104
+ },
102
105
  spotlightAcceleratorProcessKey: function spotlightAcceleratorProcessKey(ev) {
103
106
  return SpotlightAccelerator.processKey(ev, nop);
104
107
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enact/limestone",
3
- "version": "1.9.3",
3
+ "version": "1.9.4",
4
4
  "description": "Large-screen/TV support library for Enact, containing a variety of UI components.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -47,12 +47,12 @@
47
47
  "@enovaui/core-tokens": "^0.14.0",
48
48
  "@enovaui/webos-tokens": "^0.14.0",
49
49
  "classnames": "^2.5.1",
50
- "ilib": "^14.21.3",
50
+ "ilib": "^14.22.0",
51
51
  "invariant": "^2.2.4",
52
52
  "prop-types": "^15.8.1",
53
53
  "ramda": "^0.32.0",
54
- "react": "^19.2.5",
55
- "react-dom": "^19.2.5",
54
+ "react": "^19.2.7",
55
+ "react-dom": "^19.2.7",
56
56
  "warning": "^4.0.3"
57
57
  },
58
58
  "peerDependencies": {