uicore-ts 1.1.205 → 1.1.207

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.
@@ -36,9 +36,20 @@ const _UIAutocompleteTextField = class extends import_UITextField.UITextField {
36
36
  this._dropdownView.didSelectItem = (item) => {
37
37
  this.commitSelection(item);
38
38
  };
39
+ let textBeforeFocus = this.text;
40
+ let itemBeforeFocus = this.selectedItem;
39
41
  this.controlEventTargetAccumulator.Focus = () => {
42
+ textBeforeFocus = this.text;
43
+ itemBeforeFocus = this.selectedItem;
44
+ this.text = "";
40
45
  this.openDropdown();
41
46
  this.textElementView.viewHTMLElement.select();
47
+ const matchIndex = this._dropdownView.filteredItems.findIndex(
48
+ (item) => item.label === textBeforeFocus
49
+ );
50
+ if (matchIndex !== -1) {
51
+ this._dropdownView.highlightedRowIndex = matchIndex;
52
+ }
42
53
  };
43
54
  this.controlEventTargetAccumulator.Blur = () => {
44
55
  this.closeDropdown();
@@ -78,6 +89,11 @@ const _UIAutocompleteTextField = class extends import_UITextField.UITextField {
78
89
  this.addTargetForControlEvent(import_UIView.UIView.controlEvent.EscDown, () => {
79
90
  if (this._isDropdownOpen) {
80
91
  this.closeDropdown();
92
+ if (this.strictSelection) {
93
+ this.commitSelection(itemBeforeFocus);
94
+ } else {
95
+ this.text = textBeforeFocus;
96
+ }
81
97
  }
82
98
  });
83
99
  }
@@ -101,6 +117,7 @@ const _UIAutocompleteTextField = class extends import_UITextField.UITextField {
101
117
  this.updateValidationVisuals();
102
118
  }
103
119
  commitSelection(item) {
120
+ this.blur();
104
121
  this._selectedItem = item;
105
122
  this.text = item.label;
106
123
  this.closeDropdown();
@@ -130,11 +147,9 @@ const _UIAutocompleteTextField = class extends import_UITextField.UITextField {
130
147
  if (filterText.length === 0) {
131
148
  filtered = this._autocompleteItems;
132
149
  } else {
133
- const words = filterText.split(/\s+/);
134
- filtered = this._autocompleteItems.filter((item) => {
135
- const label = item.label.toLowerCase();
136
- return words.every((word) => label.includes(word));
137
- });
150
+ filtered = this._autocompleteItems.filter(
151
+ (item) => item.label.toLowerCase().includes(filterText)
152
+ );
138
153
  }
139
154
  const isExactSingleMatch = filtered.length === 1 && filtered[0].label.toLowerCase() === filterText;
140
155
  this._dropdownView.filteredItems = isExactSingleMatch ? [] : filtered;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../scripts/UIAutocompleteTextField.ts"],
4
- "sourcesContent": ["import { UIAutocompleteDropdownView } from \"./UIAutocompleteDropdownView\"\nimport { UIAutocompleteItem } from \"./UIAutocompleteRowView\"\nimport { IS, IS_NOT, NO, YES } from \"./UIObject\"\nimport { UITextField } from \"./UITextField\"\nimport { UIView, UIViewAddControlEventTargetObject } from \"./UIView\"\n\n\nexport class UIAutocompleteTextField<T = string> extends UITextField {\n \n _autocompleteItems: UIAutocompleteItem<T>[] = []\n _selectedItem?: UIAutocompleteItem<T>\n _dropdownView: UIAutocompleteDropdownView<T>\n _isDropdownOpen: boolean = NO\n _strictSelection: boolean = NO\n _isValid: boolean = YES\n \n \n static override controlEvent = Object.assign({}, UITextField.controlEvent, {\n \"SelectionDidChange\": \"SelectionDidChange\"\n })\n \n override get controlEventTargetAccumulator(): UIViewAddControlEventTargetObject<typeof UIAutocompleteTextField> {\n return (super.controlEventTargetAccumulator as any)\n }\n \n \n constructor(elementID?: string) {\n \n super(elementID)\n \n this._dropdownView = this.newDropdownView()\n \n this._dropdownView.didSelectItem = (item) => {\n this.commitSelection(item)\n }\n \n // Open dropdown on focus\n this.controlEventTargetAccumulator.Focus = () => {\n this.openDropdown()\n this.textElementView.viewHTMLElement.select()\n }\n \n // Close on blur\n this.controlEventTargetAccumulator.Blur = () => {\n this.closeDropdown()\n }\n \n // Filter on text change\n this.addTargetForControlEvent(UITextField.controlEvent.TextChange, () => {\n this._selectedItem = undefined\n this.updateFilteredItems()\n if (!this._isDropdownOpen) {\n this.openDropdown()\n }\n })\n \n // Keyboard navigation: down arrow\n this.textElementView.addTargetForControlEvent(UIView.controlEvent.DownArrowDown, (sender, event) => {\n event.preventDefault()\n if (!this._isDropdownOpen) {\n this.openDropdown()\n return\n }\n const maxIndex = this._dropdownView.filteredItems.length - 1\n if (this._dropdownView.highlightedRowIndex < maxIndex) {\n this._dropdownView.highlightedRowIndex = this._dropdownView.highlightedRowIndex + 1\n }\n })\n \n // Keyboard navigation: up arrow\n this.textElementView.addTargetForControlEvent(UIView.controlEvent.UpArrowDown, (sender, event) => {\n event.preventDefault()\n if (this._dropdownView.highlightedRowIndex > 0) {\n this._dropdownView.highlightedRowIndex = this._dropdownView.highlightedRowIndex - 1\n }\n })\n \n // Enter: commit focused item\n this.addTargetForControlEvent(UIView.controlEvent.EnterDown, () => {\n const highlightedItem = this._dropdownView.highlightedItem\n if (IS(highlightedItem)) {\n this.commitSelection(highlightedItem)\n }\n else if (this._isDropdownOpen) {\n this.closeDropdown()\n }\n })\n \n // Escape: dismiss dropdown\n this.addTargetForControlEvent(UIView.controlEvent.EscDown, () => {\n if (this._isDropdownOpen) {\n this.closeDropdown()\n }\n })\n \n }\n \n \n /** Override in subclass to provide a custom dropdown view. */\n newDropdownView(): UIAutocompleteDropdownView<T> {\n return new UIAutocompleteDropdownView<T>(\n this.elementID ? this.elementID + \"Dropdown\" : undefined\n )\n }\n \n \n // MARK: - Selection\n \n get selectedItem(): T | undefined {\n return this._selectedItem?.value\n }\n \n \n get strictSelection(): boolean {\n return this._strictSelection\n }\n \n set strictSelection(strict: boolean) {\n this._strictSelection = strict\n this.updateValidationVisuals()\n }\n \n \n commitSelection(item: UIAutocompleteItem<T>) {\n \n this._selectedItem = item\n this.text = item.label\n this.closeDropdown()\n this.updateValidationVisuals()\n this.sendControlEventForKey(UIAutocompleteTextField.controlEvent.SelectionDidChange)\n \n }\n \n \n // MARK: - Data\n \n /** Convenience: set string suggestions. Each string becomes { label: s, value: s }. */\n set autocompleteStrings(strings: string[]) {\n this._autocompleteItems = strings.map(s => ({\n label: s,\n value: s as unknown as T\n }))\n this.updateFilteredItems()\n }\n \n get autocompleteStrings(): string[] {\n return this._autocompleteItems.map(item => item.label)\n }\n \n set autocompleteData(items: UIAutocompleteItem<T>[]) {\n this._autocompleteItems = items\n this.updateFilteredItems()\n }\n \n get autocompleteData(): UIAutocompleteItem<T>[] {\n return this._autocompleteItems\n }\n \n \n // MARK: - Filtering\n \n updateFilteredItems() {\n \n const filterText = this.text.toLowerCase().trim()\n \n let filtered: UIAutocompleteItem<T>[]\n \n if (filterText.length === 0) {\n filtered = this._autocompleteItems\n }\n else {\n const words = filterText.split(/\\s+/)\n filtered = this._autocompleteItems.filter(item => {\n const label = item.label.toLowerCase()\n return words.every(word => label.includes(word))\n })\n }\n \n // If the only remaining result is an exact match for the current text,\n // the user has already made their selection \u2014 no need to show the dropdown.\n const isExactSingleMatch = filtered.length === 1 &&\n filtered[0].label.toLowerCase() === filterText\n \n this._dropdownView.filteredItems = isExactSingleMatch ? [] : filtered\n \n if (this._dropdownView.filteredItems.length > 0) {\n this._dropdownView.highlightedRowIndex = 0\n }\n \n if (this._isDropdownOpen) {\n this._dropdownView.showAnchoredToView(this)\n }\n \n }\n \n \n // MARK: - Dropdown Lifecycle\n \n openDropdown() {\n \n if (this._isDropdownOpen) {\n return\n }\n \n this._isDropdownOpen = YES\n this.updateFilteredItems()\n this._dropdownView.showAnchoredToView(this)\n \n }\n \n closeDropdown() {\n \n if (!this._isDropdownOpen) {\n return\n }\n \n this._isDropdownOpen = NO\n this._dropdownView.dismiss()\n \n // In strict mode, clear text if it doesn't match any item\n if (this._strictSelection && IS_NOT(this._selectedItem)) {\n const currentText = this.text.trim()\n if (currentText.length > 0) {\n const matchingItem = this._autocompleteItems.find(\n item => item.label === currentText\n )\n if (IS(matchingItem)) {\n this._selectedItem = matchingItem\n }\n else {\n this.text = \"\"\n this._selectedItem = undefined\n }\n }\n }\n \n this.updateValidationVisuals()\n \n }\n \n \n // MARK: - Validation\n \n /** Whether the current text is valid given the strictSelection setting. */\n get isValid(): boolean {\n \n if (!this._strictSelection) {\n return YES\n }\n \n const currentText = this.text.trim()\n \n if (currentText.length === 0) {\n return YES\n }\n \n return this._autocompleteItems.some(item => item.label === currentText)\n \n }\n \n \n \n \n /**\n * Hook for subclasses to apply custom visual styling based on validation state.\n * Called after dropdown closes and after selection changes.\n */\n updateValidationVisuals() {\n // Base implementation does nothing. Subclasses override.\n }\n \n \n // MARK: - Cleanup\n \n override wasRemovedFromViewTree() {\n \n super.wasRemovedFromViewTree()\n \n this._dropdownView.removeFromSuperview()\n \n }\n \n \n // MARK: - Layout\n \n override layoutSubviews() {\n \n super.layoutSubviews()\n \n if (this._isDropdownOpen) {\n this._dropdownView.showAnchoredToView(this)\n }\n \n }\n \n \n}\n\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { UIAutocompleteDropdownView } from \"./UIAutocompleteDropdownView\"\nimport { UIAutocompleteItem } from \"./UIAutocompleteRowView\"\nimport { IS, IS_NOT, NO, YES } from \"./UIObject\"\nimport { UITextField } from \"./UITextField\"\nimport { UIView, UIViewAddControlEventTargetObject } from \"./UIView\"\n\n\nexport class UIAutocompleteTextField<T = string> extends UITextField {\n \n _autocompleteItems: UIAutocompleteItem<T>[] = []\n _selectedItem?: UIAutocompleteItem<T>\n _dropdownView: UIAutocompleteDropdownView<T>\n _isDropdownOpen: boolean = NO\n _strictSelection: boolean = NO\n _isValid: boolean = YES\n \n \n static override controlEvent = Object.assign({}, UITextField.controlEvent, {\n \"SelectionDidChange\": \"SelectionDidChange\"\n })\n \n override get controlEventTargetAccumulator(): UIViewAddControlEventTargetObject<typeof UIAutocompleteTextField> {\n return (super.controlEventTargetAccumulator as any)\n }\n \n \n constructor(elementID?: string) {\n \n super(elementID)\n \n this._dropdownView = this.newDropdownView()\n \n this._dropdownView.didSelectItem = (item) => {\n this.commitSelection(item)\n }\n \n let textBeforeFocus = this.text\n let itemBeforeFocus = this.selectedItem\n // Open dropdown on focus\n this.controlEventTargetAccumulator.Focus = () => {\n textBeforeFocus = this.text\n itemBeforeFocus = this.selectedItem\n this.text = \"\"\n this.openDropdown()\n this.textElementView.viewHTMLElement.select()\n const matchIndex = this._dropdownView.filteredItems.findIndex(\n item => item.label === textBeforeFocus\n )\n if (matchIndex !== -1) {\n this._dropdownView.highlightedRowIndex = matchIndex\n }\n }\n \n // Close on blur\n this.controlEventTargetAccumulator.Blur = () => {\n this.closeDropdown()\n }\n \n // Filter on text change\n this.addTargetForControlEvent(UITextField.controlEvent.TextChange, () => {\n this._selectedItem = undefined\n this.updateFilteredItems()\n if (!this._isDropdownOpen) {\n this.openDropdown()\n }\n })\n \n // Keyboard navigation: down arrow\n this.textElementView.addTargetForControlEvent(UIView.controlEvent.DownArrowDown, (sender, event) => {\n event.preventDefault()\n if (!this._isDropdownOpen) {\n this.openDropdown()\n return\n }\n const maxIndex = this._dropdownView.filteredItems.length - 1\n if (this._dropdownView.highlightedRowIndex < maxIndex) {\n this._dropdownView.highlightedRowIndex = this._dropdownView.highlightedRowIndex + 1\n }\n })\n \n // Keyboard navigation: up arrow\n this.textElementView.addTargetForControlEvent(UIView.controlEvent.UpArrowDown, (sender, event) => {\n event.preventDefault()\n if (this._dropdownView.highlightedRowIndex > 0) {\n this._dropdownView.highlightedRowIndex = this._dropdownView.highlightedRowIndex - 1\n }\n })\n \n // Enter: commit focused item\n this.addTargetForControlEvent(UIView.controlEvent.EnterDown, () => {\n const highlightedItem = this._dropdownView.highlightedItem\n if (IS(highlightedItem)) {\n this.commitSelection(highlightedItem)\n }\n else if (this._isDropdownOpen) {\n this.closeDropdown()\n }\n })\n \n // Escape: dismiss dropdown\n this.addTargetForControlEvent(UIView.controlEvent.EscDown, () => {\n if (this._isDropdownOpen) {\n this.closeDropdown()\n if (this.strictSelection) {\n this.commitSelection(itemBeforeFocus as any)\n }\n else {\n this.text = textBeforeFocus\n }\n }\n })\n \n }\n \n \n /** Override in subclass to provide a custom dropdown view. */\n newDropdownView(): UIAutocompleteDropdownView<T> {\n return new UIAutocompleteDropdownView<T>(\n this.elementID ? this.elementID + \"Dropdown\" : undefined\n )\n }\n \n \n // MARK: - Selection\n \n get selectedItem(): T | undefined {\n return this._selectedItem?.value\n }\n \n \n get strictSelection(): boolean {\n return this._strictSelection\n }\n \n set strictSelection(strict: boolean) {\n this._strictSelection = strict\n this.updateValidationVisuals()\n }\n \n \n commitSelection(item: UIAutocompleteItem<T>) {\n \n this.blur()\n this._selectedItem = item\n this.text = item.label\n this.closeDropdown()\n this.updateValidationVisuals()\n this.sendControlEventForKey(UIAutocompleteTextField.controlEvent.SelectionDidChange)\n \n }\n \n \n // MARK: - Data\n \n /** Convenience: set string suggestions. Each string becomes { label: s, value: s }. */\n set autocompleteStrings(strings: string[]) {\n this._autocompleteItems = strings.map(s => ({\n label: s,\n value: s as unknown as T\n }))\n this.updateFilteredItems()\n }\n \n get autocompleteStrings(): string[] {\n return this._autocompleteItems.map(item => item.label)\n }\n \n set autocompleteData(items: UIAutocompleteItem<T>[]) {\n this._autocompleteItems = items\n this.updateFilteredItems()\n }\n \n get autocompleteData(): UIAutocompleteItem<T>[] {\n return this._autocompleteItems\n }\n \n \n // MARK: - Filtering\n \n updateFilteredItems() {\n \n const filterText = this.text.toLowerCase().trim()\n \n let filtered: UIAutocompleteItem<T>[]\n \n if (filterText.length === 0) {\n filtered = this._autocompleteItems\n }\n else {\n filtered = this._autocompleteItems.filter(item =>\n item.label.toLowerCase().includes(filterText)\n )\n }\n \n // If the only remaining result is an exact match for the current text,\n // the user has already made their selection \u2014 no need to show the dropdown.\n const isExactSingleMatch = filtered.length === 1 &&\n filtered[0].label.toLowerCase() === filterText\n \n this._dropdownView.filteredItems = isExactSingleMatch ? [] : filtered\n \n if (this._dropdownView.filteredItems.length > 0) {\n this._dropdownView.highlightedRowIndex = 0\n }\n \n if (this._isDropdownOpen) {\n this._dropdownView.showAnchoredToView(this)\n }\n \n }\n \n \n // MARK: - Dropdown Lifecycle\n \n openDropdown() {\n \n if (this._isDropdownOpen) {\n return\n }\n \n this._isDropdownOpen = YES\n this.updateFilteredItems()\n this._dropdownView.showAnchoredToView(this)\n \n }\n \n closeDropdown() {\n \n if (!this._isDropdownOpen) {\n return\n }\n \n this._isDropdownOpen = NO\n this._dropdownView.dismiss()\n \n // In strict mode, clear text if it doesn't match any item\n if (this._strictSelection && IS_NOT(this._selectedItem)) {\n const currentText = this.text.trim()\n if (currentText.length > 0) {\n const matchingItem = this._autocompleteItems.find(\n item => item.label === currentText\n )\n if (IS(matchingItem)) {\n this._selectedItem = matchingItem\n }\n else {\n this.text = \"\"\n this._selectedItem = undefined\n }\n }\n }\n \n this.updateValidationVisuals()\n \n }\n \n \n // MARK: - Validation\n \n /** Whether the current text is valid given the strictSelection setting. */\n get isValid(): boolean {\n \n if (!this._strictSelection) {\n return YES\n }\n \n const currentText = this.text.trim()\n \n if (currentText.length === 0) {\n return YES\n }\n \n return this._autocompleteItems.some(item => item.label === currentText)\n \n }\n \n \n /**\n * Hook for subclasses to apply custom visual styling based on validation state.\n * Called after dropdown closes and after selection changes.\n */\n updateValidationVisuals() {\n // Base implementation does nothing. Subclasses override.\n }\n \n \n // MARK: - Cleanup\n \n override wasRemovedFromViewTree() {\n \n super.wasRemovedFromViewTree()\n \n this._dropdownView.removeFromSuperview()\n \n }\n \n \n // MARK: - Layout\n \n override layoutSubviews() {\n \n super.layoutSubviews()\n \n if (this._isDropdownOpen) {\n this._dropdownView.showAnchoredToView(this)\n }\n \n }\n \n \n}\n\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -36,12 +36,49 @@ export declare class UITableView extends UINativeScrollView {
36
36
  usesVirtualLayoutingForIntrinsicSizing: boolean;
37
37
  animationDuration: number;
38
38
  _intersectionObserver?: IntersectionObserver;
39
+ /** Row index with -1 meaning the header row. undefined means no focus. */
40
+ _keyboardFocusedRowIndex: number | undefined;
41
+ /** Cell index within the focused row. */
42
+ _keyboardFocusedCellIndex: number;
43
+ /** Total number of data columns (excludes left/right side cells). Set by CBDataView. */
44
+ _columnCount: number;
45
+ /** Called by UITableView when the focused row/cell changes. CBDataView overrides this. */
46
+ keyboardFocusDidChange?: (rowIndex: number | undefined, cellIndex: number) => void;
47
+ /** Fired when Enter is pressed on a focused cell. Passes rowIndex and cellIndex. */
48
+ keyboardDidActivateCell?: (rowIndex: number, cellIndex: number) => void;
49
+ _keydownHandler?: (event: KeyboardEvent) => void;
50
+ _keyboardListenersAttached: boolean;
39
51
  get _reusableViews(): UITableViewReusableViewsContainerObject;
40
52
  constructor(elementID?: string);
53
+ /**
54
+ * The element that receives tabIndex, ARIA grid role, and all keyboard/pointer
55
+ * listeners. Defaults to the table's own element. CBDataView overrides this
56
+ * to a container that wraps both the header and the table, so the focus ring
57
+ * encompasses both.
58
+ */
59
+ _keyboardListenerElement: HTMLElement;
60
+ _setupGridAccessibility(): void;
61
+ /** Called by CBDataView after descriptors change. */
62
+ setColumnCount(count: number): void;
63
+ /** Called by CBDataView after data loads. */
64
+ setRowCount(count: number): void;
65
+ _setupKeyboardNavigation(): void;
66
+ /**
67
+ * Move keyboard focus to a specific row and cell.
68
+ * rowIndex = -1 means the header row.
69
+ */
70
+ _setKeyboardFocus(rowIndex: number, cellIndex: number): void;
71
+ _clearKeyboardFocus(): void;
72
+ _clearKeyboardFocusOnRow(rowIndex: number): void;
73
+ _applyKeyboardFocusToVisibleRows(clearAll?: boolean): void;
74
+ _scrollRowIntoView(rowIndex: number): void;
75
+ /** Expose so CBDataView can call it after loading data. */
76
+ focusRowAtIndex(rowIndex: number, cellIndex?: number): void;
41
77
  _windowScrollHandler: () => void;
42
78
  _resizeHandler: () => void;
43
79
  _setupViewportScrollAndResizeHandlersIfNeeded(): void;
44
80
  _cleanupViewportScrollListeners(): void;
81
+ wasRemovedFromViewTree(): void;
45
82
  loadData(): void;
46
83
  reloadData(): void;
47
84
  highlightChanges(previousData: any[], newData: any[]): void;
@@ -69,7 +106,6 @@ export declare class UITableView extends UINativeScrollView {
69
106
  didScrollToPosition(offsetPosition: UIPoint): void;
70
107
  willMoveToSuperview(superview: UIView): void;
71
108
  wasAddedToViewTree(): void;
72
- wasRemovedFromViewTree(): void;
73
109
  setFrame(rectangle: UIRectangle, zIndex?: number, performUncheckedLayout?: boolean): void;
74
110
  didReceiveBroadcastEvent(event: UIViewBroadcastEvent): void;
75
111
  clearIntrinsicSizeCache(): void;
@@ -41,6 +41,11 @@ class UITableView extends import_UINativeScrollView.UINativeScrollView {
41
41
  this._isDrawVisibleRowsScheduled = import_UIObject.NO;
42
42
  this.usesVirtualLayoutingForIntrinsicSizing = import_UIObject.NO;
43
43
  this.animationDuration = 0.25;
44
+ this._keyboardFocusedRowIndex = void 0;
45
+ this._keyboardFocusedCellIndex = 0;
46
+ this._columnCount = 0;
47
+ this._keyboardListenersAttached = false;
48
+ this._keyboardListenerElement = this.viewHTMLElement;
44
49
  this._windowScrollHandler = () => {
45
50
  if (!this.isMemberOfViewTree) {
46
51
  return;
@@ -62,6 +67,8 @@ class UITableView extends import_UINativeScrollView.UINativeScrollView {
62
67
  this.addSubview(this._fullHeightView);
63
68
  this.scrollsX = import_UIObject.NO;
64
69
  this._setupViewportScrollAndResizeHandlersIfNeeded();
70
+ this._setupGridAccessibility();
71
+ this._setupKeyboardNavigation();
65
72
  }
66
73
  get _reusableViews() {
67
74
  const result = {};
@@ -79,6 +86,183 @@ class UITableView extends import_UINativeScrollView.UINativeScrollView {
79
86
  this._unusedReusableViews.forEach((views) => views.forEach(addView));
80
87
  return result;
81
88
  }
89
+ _setupGridAccessibility() {
90
+ const el = this._keyboardListenerElement;
91
+ el.setAttribute("role", "grid");
92
+ el.setAttribute("aria-rowcount", "0");
93
+ el.setAttribute("aria-colcount", "0");
94
+ el.tabIndex = 0;
95
+ }
96
+ setColumnCount(count) {
97
+ this._columnCount = count;
98
+ this._keyboardListenerElement.setAttribute("aria-colcount", String(count));
99
+ }
100
+ setRowCount(count) {
101
+ this._keyboardListenerElement.setAttribute("aria-rowcount", String(count));
102
+ }
103
+ _setupKeyboardNavigation() {
104
+ this._keydownHandler = (event) => {
105
+ var _a;
106
+ if (!this.isMemberOfViewTree) {
107
+ return;
108
+ }
109
+ const rowCount = this.numberOfRows();
110
+ const hasHeader = this._keyboardFocusedRowIndex !== void 0;
111
+ if (event.key === "ArrowDown") {
112
+ event.preventDefault();
113
+ if (this._keyboardFocusedRowIndex === void 0) {
114
+ this._setKeyboardFocus(0, this._keyboardFocusedCellIndex);
115
+ } else if (event.metaKey || event.ctrlKey) {
116
+ this._setKeyboardFocus(rowCount - 1, this._keyboardFocusedCellIndex);
117
+ } else if (event.altKey) {
118
+ const pageSize = Math.max(1, Math.floor(this.bounds.height / (this._heightForAnyRow() || 50)));
119
+ const next = Math.min((this._keyboardFocusedRowIndex < 0 ? 0 : this._keyboardFocusedRowIndex) + pageSize, rowCount - 1);
120
+ this._setKeyboardFocus(next, this._keyboardFocusedCellIndex);
121
+ } else if (this._keyboardFocusedRowIndex === -1) {
122
+ this._setKeyboardFocus(0, this._keyboardFocusedCellIndex);
123
+ } else if (this._keyboardFocusedRowIndex < rowCount - 1) {
124
+ this._setKeyboardFocus(this._keyboardFocusedRowIndex + 1, this._keyboardFocusedCellIndex);
125
+ }
126
+ } else if (event.key === "ArrowUp") {
127
+ event.preventDefault();
128
+ if (this._keyboardFocusedRowIndex === void 0) {
129
+ this._setKeyboardFocus(rowCount - 1, this._keyboardFocusedCellIndex);
130
+ } else if (event.metaKey || event.ctrlKey) {
131
+ this._setKeyboardFocus(-1, this._keyboardFocusedCellIndex);
132
+ } else if (event.altKey) {
133
+ const pageSize = Math.max(1, Math.floor(this.bounds.height / (this._heightForAnyRow() || 50)));
134
+ const prev = Math.max((this._keyboardFocusedRowIndex < 0 ? 0 : this._keyboardFocusedRowIndex) - pageSize, -1);
135
+ this._setKeyboardFocus(prev, this._keyboardFocusedCellIndex);
136
+ } else if (this._keyboardFocusedRowIndex === 0) {
137
+ this._setKeyboardFocus(-1, this._keyboardFocusedCellIndex);
138
+ } else if (this._keyboardFocusedRowIndex > 0) {
139
+ this._setKeyboardFocus(this._keyboardFocusedRowIndex - 1, this._keyboardFocusedCellIndex);
140
+ }
141
+ } else if (event.key === "ArrowRight") {
142
+ event.preventDefault();
143
+ if (this._keyboardFocusedRowIndex !== void 0 && this._columnCount > 0) {
144
+ const nextCell = event.metaKey || event.ctrlKey ? this._columnCount - 1 : Math.min(this._keyboardFocusedCellIndex + 1, this._columnCount - 1);
145
+ this._setKeyboardFocus(this._keyboardFocusedRowIndex, nextCell);
146
+ }
147
+ } else if (event.key === "ArrowLeft") {
148
+ event.preventDefault();
149
+ if (this._keyboardFocusedRowIndex !== void 0 && this._columnCount > 0) {
150
+ const prevCell = event.metaKey || event.ctrlKey ? 0 : Math.max(this._keyboardFocusedCellIndex - 1, 0);
151
+ this._setKeyboardFocus(this._keyboardFocusedRowIndex, prevCell);
152
+ }
153
+ } else if (event.key === "Home") {
154
+ event.preventDefault();
155
+ if (this._keyboardFocusedRowIndex !== void 0) {
156
+ this._setKeyboardFocus(this._keyboardFocusedRowIndex, 0);
157
+ }
158
+ } else if (event.key === "End") {
159
+ event.preventDefault();
160
+ if (this._keyboardFocusedRowIndex !== void 0 && this._columnCount > 0) {
161
+ this._setKeyboardFocus(this._keyboardFocusedRowIndex, this._columnCount - 1);
162
+ }
163
+ } else if (event.key === "PageDown") {
164
+ event.preventDefault();
165
+ if (this._keyboardFocusedRowIndex !== void 0) {
166
+ const pageSize = Math.max(1, Math.floor(this.bounds.height / (this._heightForAnyRow() || 50)));
167
+ const next = Math.min((this._keyboardFocusedRowIndex < 0 ? 0 : this._keyboardFocusedRowIndex) + pageSize, rowCount - 1);
168
+ this._setKeyboardFocus(next, this._keyboardFocusedCellIndex);
169
+ }
170
+ } else if (event.key === "PageUp") {
171
+ event.preventDefault();
172
+ if (this._keyboardFocusedRowIndex !== void 0) {
173
+ const pageSize = Math.max(1, Math.floor(this.bounds.height / (this._heightForAnyRow() || 50)));
174
+ const prev = Math.max((this._keyboardFocusedRowIndex < 0 ? 0 : this._keyboardFocusedRowIndex) - pageSize, -1);
175
+ this._setKeyboardFocus(prev, this._keyboardFocusedCellIndex);
176
+ }
177
+ } else if (event.key === "Enter" || event.key === " ") {
178
+ if (this._keyboardFocusedRowIndex !== void 0 && this._keyboardFocusedRowIndex >= 0) {
179
+ event.preventDefault();
180
+ (_a = this.keyboardDidActivateCell) == null ? void 0 : _a.call(this, this._keyboardFocusedRowIndex, this._keyboardFocusedCellIndex);
181
+ }
182
+ } else if (event.key === "Escape") {
183
+ this._clearKeyboardFocus();
184
+ this._keyboardListenerElement.blur();
185
+ }
186
+ };
187
+ }
188
+ _setKeyboardFocus(rowIndex, cellIndex) {
189
+ var _a;
190
+ const previousRowIndex = this._keyboardFocusedRowIndex;
191
+ const previousCellIndex = this._keyboardFocusedCellIndex;
192
+ if (rowIndex >= 0 && rowIndex !== previousRowIndex) {
193
+ const row = this.visibleRowWithIndex(rowIndex);
194
+ if (row && typeof row.firstButtonCellIndex === "function") {
195
+ cellIndex = row.firstButtonCellIndex();
196
+ }
197
+ }
198
+ this._keyboardFocusedRowIndex = rowIndex;
199
+ this._keyboardFocusedCellIndex = cellIndex;
200
+ if (previousRowIndex !== void 0 && previousRowIndex !== rowIndex) {
201
+ this._clearKeyboardFocusOnRow(previousRowIndex);
202
+ } else if (previousRowIndex === rowIndex && previousCellIndex !== cellIndex) {
203
+ this._clearKeyboardFocusOnRow(rowIndex);
204
+ }
205
+ if (rowIndex >= 0) {
206
+ this._scrollRowIntoView(rowIndex);
207
+ }
208
+ this._applyKeyboardFocusToVisibleRows();
209
+ (_a = this.keyboardFocusDidChange) == null ? void 0 : _a.call(this, rowIndex, cellIndex);
210
+ }
211
+ _clearKeyboardFocus() {
212
+ var _a;
213
+ const previous = this._keyboardFocusedRowIndex;
214
+ this._keyboardFocusedRowIndex = void 0;
215
+ if (previous !== void 0) {
216
+ this._clearKeyboardFocusOnRow(previous);
217
+ }
218
+ (_a = this.keyboardFocusDidChange) == null ? void 0 : _a.call(this, void 0, this._keyboardFocusedCellIndex);
219
+ }
220
+ _clearKeyboardFocusOnRow(rowIndex) {
221
+ var _a;
222
+ if (rowIndex === -1) {
223
+ (_a = this.keyboardFocusDidChange) == null ? void 0 : _a.call(this, -1, -1);
224
+ return;
225
+ }
226
+ const row = this.visibleRowWithIndex(rowIndex);
227
+ if (row && typeof row.setKeyboardFocusedCellIndex === "function") {
228
+ row.setKeyboardFocusedCellIndex(void 0);
229
+ }
230
+ }
231
+ _applyKeyboardFocusToVisibleRows(clearAll = false) {
232
+ this._visibleRows.forEach((row) => {
233
+ if (typeof row.setKeyboardFocusedCellIndex !== "function") {
234
+ return;
235
+ }
236
+ if (clearAll || row._UITableViewRowIndex !== this._keyboardFocusedRowIndex) {
237
+ row.setKeyboardFocusedCellIndex(void 0);
238
+ } else {
239
+ row.setKeyboardFocusedCellIndex(this._keyboardFocusedCellIndex);
240
+ }
241
+ });
242
+ }
243
+ _scrollRowIntoView(rowIndex) {
244
+ const position = this._rowPositionWithIndex(rowIndex);
245
+ if (!position) {
246
+ return;
247
+ }
248
+ const offsetY = this.contentOffset.y;
249
+ const visibleHeight = this.bounds.height;
250
+ if (position.topY < offsetY) {
251
+ const duration = this.animationDuration;
252
+ this.animationDuration = 0;
253
+ this.contentOffset = this.contentOffset.pointWithY(position.topY);
254
+ this.animationDuration = duration;
255
+ } else if (position.bottomY > offsetY + visibleHeight) {
256
+ const duration = this.animationDuration;
257
+ this.animationDuration = 0;
258
+ this.contentOffset = this.contentOffset.pointWithY(position.bottomY - visibleHeight);
259
+ this.animationDuration = duration;
260
+ }
261
+ }
262
+ focusRowAtIndex(rowIndex, cellIndex = 0) {
263
+ this._setKeyboardFocus(rowIndex, cellIndex);
264
+ this._keyboardListenerElement.focus({ preventScroll: true });
265
+ }
82
266
  _setupViewportScrollAndResizeHandlersIfNeeded() {
83
267
  if (this._intersectionObserver) {
84
268
  return;
@@ -109,6 +293,14 @@ class UITableView extends import_UINativeScrollView.UINativeScrollView {
109
293
  this._intersectionObserver = void 0;
110
294
  }
111
295
  }
296
+ wasRemovedFromViewTree() {
297
+ super.wasRemovedFromViewTree();
298
+ this._cleanupViewportScrollListeners();
299
+ if (this._keydownHandler) {
300
+ this.viewHTMLElement.removeEventListener("keydown", this._keydownHandler);
301
+ }
302
+ this._keyboardListenersAttached = false;
303
+ }
112
304
  loadData() {
113
305
  this._persistedData = [];
114
306
  this._calculatePositionsUntilIndex(this.numberOfRows() - 1);
@@ -353,12 +545,17 @@ class UITableView extends import_UINativeScrollView.UINativeScrollView {
353
545
  const view = this.viewForRowWithIndex(rowIndex);
354
546
  this._visibleRows.push(view);
355
547
  this.addSubview(view);
548
+ view.tabIndex = -1;
549
+ view.forEachViewInSubtree((subview) => {
550
+ subview.tabIndex = -1;
551
+ });
356
552
  });
357
553
  removedViews.forEach((row) => {
358
554
  if (this._visibleRows.indexOf(row) == -1) {
359
555
  row.removeFromSuperview();
360
556
  }
361
557
  });
558
+ this._applyKeyboardFocusToVisibleRows();
362
559
  }
363
560
  visibleRowWithIndex(rowIndex) {
364
561
  for (let i = 0; i < this._visibleRows.length; i++) {
@@ -440,10 +637,42 @@ class UITableView extends import_UINativeScrollView.UINativeScrollView {
440
637
  super.wasAddedToViewTree();
441
638
  this.loadData();
442
639
  this._setupViewportScrollAndResizeHandlersIfNeeded();
443
- }
444
- wasRemovedFromViewTree() {
445
- super.wasRemovedFromViewTree();
446
- this._cleanupViewportScrollListeners();
640
+ if (!this._keyboardListenersAttached) {
641
+ this._keyboardListenersAttached = true;
642
+ const el = this._keyboardListenerElement;
643
+ el.addEventListener("keydown", this._keydownHandler);
644
+ el.addEventListener("pointerdown", (event) => {
645
+ let target = event.target;
646
+ while (target && target !== el) {
647
+ const viewObject = target.UIViewObject;
648
+ if ((viewObject == null ? void 0 : viewObject._UITableViewRowIndex) !== void 0) {
649
+ el.focus({ preventScroll: true });
650
+ this._setKeyboardFocus(viewObject._UITableViewRowIndex, this._keyboardFocusedCellIndex);
651
+ return;
652
+ }
653
+ target = target.parentElement;
654
+ }
655
+ el.focus({ preventScroll: true });
656
+ });
657
+ el.addEventListener("focus", () => {
658
+ if (this._keyboardFocusedRowIndex === void 0 && this.numberOfRows() > 0) {
659
+ this._setKeyboardFocus(0, this._keyboardFocusedCellIndex);
660
+ } else if (this._keyboardFocusedRowIndex !== void 0) {
661
+ this._applyKeyboardFocusToVisibleRows();
662
+ }
663
+ });
664
+ el.addEventListener("blur", (event) => {
665
+ if (!el.contains(event.relatedTarget)) {
666
+ this._applyKeyboardFocusToVisibleRows(true);
667
+ }
668
+ });
669
+ }
670
+ this.forEachViewInSubtree((view) => {
671
+ if (view !== this) {
672
+ view.tabIndex = -1;
673
+ }
674
+ });
675
+ this._keyboardListenerElement.tabIndex = 0;
447
676
  }
448
677
  setFrame(rectangle, zIndex, performUncheckedLayout) {
449
678
  const frame = this.frame;
@@ -469,6 +698,7 @@ class UITableView extends import_UINativeScrollView.UINativeScrollView {
469
698
  _layoutAllRows(positions = this._rowPositions) {
470
699
  const bounds = this.bounds;
471
700
  this._visibleRows.sort((rowA, rowB) => rowA._UITableViewRowIndex - rowB._UITableViewRowIndex).forEach((row) => {
701
+ var _a;
472
702
  const frame = bounds.copy();
473
703
  const positionObject = this._rowPositionWithIndex(row._UITableViewRowIndex, positions);
474
704
  frame.min.y = positionObject.topY;
@@ -476,6 +706,7 @@ class UITableView extends import_UINativeScrollView.UINativeScrollView {
476
706
  row.frame = frame;
477
707
  row.style.width = "" + (bounds.width - this.sidePadding * 2).integerValue + "px";
478
708
  row.style.left = "" + this.sidePadding.integerValue + "px";
709
+ row.viewHTMLElement.setAttribute("aria-rowindex", String(((_a = row._UITableViewRowIndex) != null ? _a : 0) + 1));
479
710
  this.viewHTMLElement.appendChild(row.viewHTMLElement);
480
711
  });
481
712
  const numberOfRows = this.numberOfRows();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../scripts/UITableView.ts"],
4
- "sourcesContent": ["import { UIButton } from \"./UIButton\"\nimport { UINativeScrollView } from \"./UINativeScrollView\"\nimport { FIRST_OR_NIL, IF, IS, IS_DEFINED, MAKE_ID, nil, NO, YES } from \"./UIObject\"\nimport { UIPoint } from \"./UIPoint\"\nimport { UIRectangle } from \"./UIRectangle\"\nimport { UIView, UIViewBroadcastEvent } from \"./UIView\"\n\n\ninterface UITableViewRowView extends UIView {\n \n _UITableViewRowIndex?: number;\n \n}\n\n\nexport interface UITableViewReusableViewsContainerObject {\n \n [key: string]: UIView[];\n \n}\n\n\nexport interface UITableViewReusableViewPositionObject {\n \n bottomY: number;\n topY: number;\n \n isValid: boolean;\n \n}\n\n\nexport class UITableView extends UINativeScrollView {\n \n \n allRowsHaveEqualHeight: boolean = NO\n _visibleRows: UITableViewRowView[] = []\n \n /** Shared intrinsic size cache identifier used for all row views when\n * allRowsHaveEqualHeight is YES. Stable for the lifetime of the table;\n * the shared cache bucket is invalidated on reloadData and\n * clearIntrinsicSizeCache so the height is re-measured after data changes. */\n _equalRowHeightCacheIdentifier: string\n \n _rowPositions: UITableViewReusableViewPositionObject[] = []\n \n _highestValidRowPositionIndex: number = 0\n \n _unusedReusableViews: UITableViewReusableViewsContainerObject = {}\n \n _fullHeightView: UIView\n _rowIDIndex: number = 0\n reloadsOnLanguageChange = YES\n sidePadding = 0\n \n cellWeights?: number[]\n \n _persistedData: any[] = []\n _needsDrawingOfVisibleRowsBeforeLayout = NO\n _isDrawVisibleRowsScheduled = NO\n _shouldAnimateNextLayout?: boolean\n \n override usesVirtualLayoutingForIntrinsicSizing = NO\n \n override animationDuration = 0.25\n \n // Viewport scrolling properties\n _intersectionObserver?: IntersectionObserver\n \n \n get _reusableViews(): UITableViewReusableViewsContainerObject {\n \n const result: UITableViewReusableViewsContainerObject = {}\n \n const addView = (view: UIView) => {\n const identifier = view._UITableViewReusabilityIdentifier\n if (!identifier) {\n return\n }\n if (!result[identifier]) {\n result[identifier] = []\n }\n result[identifier].push(view)\n }\n \n this._visibleRows.forEach(addView)\n \n this._unusedReusableViews.forEach((views: UIView[]) => views.forEach(addView))\n \n return result\n \n }\n \n \n constructor(elementID?: string) {\n \n super(elementID)\n \n this._equalRowHeightCacheIdentifier = (elementID ?? MAKE_ID()) + \"_rowHeight\"\n \n this._fullHeightView = new UIView()\n this._fullHeightView.hidden = YES\n this._fullHeightView.userInteractionEnabled = NO\n this.addSubview(this._fullHeightView)\n \n this.scrollsX = NO\n \n this._setupViewportScrollAndResizeHandlersIfNeeded()\n \n }\n \n \n _windowScrollHandler = () => {\n if (!this.isMemberOfViewTree) {\n return\n }\n this._scheduleDrawVisibleRows()\n }\n \n _resizeHandler = () => {\n if (!this.isMemberOfViewTree) {\n return\n }\n // Invalidate all row positions on resize as widths may have changed\n this._rowPositions.everyElement.isValid = NO\n this._highestValidRowPositionIndex = -1\n this._scheduleDrawVisibleRows()\n }\n \n _setupViewportScrollAndResizeHandlersIfNeeded() {\n if (this._intersectionObserver) {\n return\n }\n \n window.addEventListener(\"scroll\", this._windowScrollHandler, { passive: true })\n window.addEventListener(\"resize\", this._resizeHandler, { passive: true })\n \n // Use IntersectionObserver to detect when table enters/exits viewport\n this._intersectionObserver = new IntersectionObserver(\n (entries) => {\n entries.forEach(entry => {\n if (entry.isIntersecting && this.isMemberOfViewTree) {\n this._scheduleDrawVisibleRows()\n }\n })\n },\n {\n root: null,\n rootMargin: \"100% 0px\", // Load rows 100% viewport height before/after\n threshold: 0\n }\n )\n this._intersectionObserver.observe(this.viewHTMLElement)\n }\n \n \n _cleanupViewportScrollListeners() {\n window.removeEventListener(\"scroll\", this._windowScrollHandler)\n window.removeEventListener(\"resize\", this._resizeHandler)\n if (this._intersectionObserver) {\n this._intersectionObserver.disconnect()\n this._intersectionObserver = undefined\n }\n }\n \n \n loadData() {\n \n this._persistedData = []\n \n this._calculatePositionsUntilIndex(this.numberOfRows() - 1)\n this._needsDrawingOfVisibleRowsBeforeLayout = YES\n \n this.setNeedsLayout()\n \n }\n \n reloadData() {\n \n this._removeVisibleRows()\n this._removeAllReusableRows()\n \n this._rowPositions = []\n this._highestValidRowPositionIndex = -1\n \n if (this.allRowsHaveEqualHeight) {\n UIView.invalidateSharedIntrinsicSizeCache(this._equalRowHeightCacheIdentifier)\n }\n \n this.loadData()\n \n }\n \n \n highlightChanges(previousData: any[], newData: any[]) {\n \n previousData = previousData.map(dataPoint => JSON.stringify(dataPoint))\n newData = newData.map(dataPoint => JSON.stringify(dataPoint))\n \n const newIndexes: number[] = []\n \n newData.forEach((value, index) => {\n \n if (!previousData.contains(value)) {\n \n newIndexes.push(index)\n \n }\n \n })\n \n newIndexes.forEach(index => {\n \n if (this.isRowWithIndexVisible(index)) {\n this.highlightRowAsNew(\n this.visibleRowWithIndex(index) ?? this.viewForRowWithIndex(index)\n )\n }\n \n })\n \n }\n \n \n highlightRowAsNew(row: UIView) {\n \n \n }\n \n \n invalidateSizeOfRowWithIndex(index: number, animateChange = NO) {\n if (this._rowPositions?.[index]) {\n FIRST_OR_NIL(this._rowPositions[index]).isValid = NO\n this._rowPositions.slice(index).everyElement.isValid = NO\n }\n this._highestValidRowPositionIndex = Math.min(this._highestValidRowPositionIndex, index - 1)\n this._needsDrawingOfVisibleRowsBeforeLayout = YES\n this._shouldAnimateNextLayout = animateChange\n }\n \n \n _rowPositionWithIndex(index: number, positions = this._rowPositions) {\n if (this.allRowsHaveEqualHeight && index > 0) {\n const firstPositionObject = positions[0]\n const rowHeight = firstPositionObject.bottomY - firstPositionObject.topY\n const result = {\n bottomY: rowHeight * (index + 1),\n topY: rowHeight * index,\n isValid: firstPositionObject.isValid\n }\n return result\n }\n return positions[index]\n }\n \n _calculateAllPositions() {\n this._calculatePositionsUntilIndex(this.numberOfRows() - 1)\n }\n \n _calculatePositionsUntilIndex(maxIndex: number) {\n \n if (this.allRowsHaveEqualHeight) {\n const positionObject: UITableViewReusableViewPositionObject = {\n bottomY: this._heightForAnyRow(),\n topY: 0,\n isValid: YES\n }\n this._rowPositions = [positionObject]\n return\n }\n \n let validPositionObject = this._rowPositions[this._highestValidRowPositionIndex]\n if (!IS(validPositionObject)) {\n validPositionObject = {\n bottomY: 0,\n topY: 0,\n isValid: YES\n }\n }\n \n let previousBottomY = validPositionObject.bottomY\n if (!this._rowPositions.length) {\n this._highestValidRowPositionIndex = -1\n }\n \n for (let i = this._highestValidRowPositionIndex + 1; i <= maxIndex; i++) {\n \n let height: number\n \n const rowPositionObject = this._rowPositions[i]\n \n if (IS((rowPositionObject || nil).isValid)) {\n height = rowPositionObject.bottomY - rowPositionObject.topY\n }\n // Do not calculate heights if all rows have equal heights, and we already have a height\n else if (this.allRowsHaveEqualHeight && i > 0) {\n height = this._rowPositions[0].bottomY - this._rowPositions[0].topY\n }\n else {\n height = this.heightForRowWithIndex(i)\n }\n \n const positionObject: UITableViewReusableViewPositionObject = {\n bottomY: previousBottomY + height,\n topY: previousBottomY,\n isValid: YES\n }\n \n if (i < this._rowPositions.length) {\n this._rowPositions[i] = positionObject\n }\n else {\n this._rowPositions.push(positionObject)\n }\n this._highestValidRowPositionIndex = i\n previousBottomY = previousBottomY + height\n \n }\n \n }\n \n \n _heightForAnyRow(calculateVisibleRows = YES) {\n return this.heightForRowWithIndex(\n this._visibleRows.firstElement?._UITableViewRowIndex ??\n (calculateVisibleRows ? this.indexesForVisibleRows().firstElement : 0) ??\n 0\n )\n }\n \n indexesForVisibleRows(paddingRatio = 0.5): number[] {\n \n // 1. Calculate the visible frame relative to the Table's bounds (0,0 is top-left of the table view)\n // This accounts for the Window viewport clipping the table if it is partially off-screen.\n const tableRect = this.viewHTMLElement.getBoundingClientRect()\n const viewportHeight = window.innerHeight\n const pageScale = UIView.pageScale\n \n // The top of the visible window relative to the view's top edge.\n // If tableRect.top is negative, the table is scrolled up and clipped by the window top.\n const visibleFrameTop = Math.max(0, -tableRect.top / pageScale)\n \n // The bottom of the visible window relative to the view's top edge.\n // We clip it to the table's actual bounds height so we don't look past the table content.\n const visibleFrameBottom = Math.min(\n this.bounds.height,\n (viewportHeight - tableRect.top) / pageScale\n )\n \n // If the table is completely off-screen, return empty\n if (visibleFrameBottom <= visibleFrameTop) {\n return []\n }\n \n // 2. Convert to Content Coordinates (Scroll Offset)\n // contentOffset.y is the internal scroll position.\n // If using viewport scrolling (full height), contentOffset.y is typically 0.\n // If using internal scrolling, this shifts the visible frame to the correct content rows.\n let firstVisibleY = this.contentOffset.y + visibleFrameTop\n let lastVisibleY = this.contentOffset.y + visibleFrameBottom\n \n // 3. Apply Padding\n // We calculate padding based on the viewport height to ensure smooth scrolling\n const paddingPx = (viewportHeight / pageScale) * paddingRatio\n firstVisibleY = Math.max(0, firstVisibleY - paddingPx)\n lastVisibleY = lastVisibleY + paddingPx\n \n const numberOfRows = this.numberOfRows()\n \n // 4. Find Indexes\n if (this.allRowsHaveEqualHeight) {\n \n const rowHeight = this._heightForAnyRow(NO)\n \n let firstIndex = Math.floor(firstVisibleY / rowHeight)\n let lastIndex = Math.floor(lastVisibleY / rowHeight)\n \n // Clamp BOTH indexes to [0, numberOfRows-1].\n // Without the upper clamp on firstIndex, when the viewport extends below the\n // last row firstIndex can exceed numberOfRows-1 while lastIndex is already\n // clamped there. firstIndex > lastIndex \u2192 empty result \u2192 _removeVisibleRows \u2192\n // browser collapses scrollHeight \u2192 scrollTop resets to 0 \u2192 rows pile at top.\n firstIndex = Math.max(0, Math.min(firstIndex, numberOfRows - 1))\n lastIndex = Math.max(0, Math.min(lastIndex, numberOfRows - 1))\n \n const result = []\n for (let i = firstIndex; i <= lastIndex; i++) {\n result.push(i)\n }\n return result\n }\n \n // Variable Heights\n this._calculateAllPositions()\n const result = []\n \n // Clamp firstVisibleY to the actual content height so that when the viewport\n // extends below the last row the intersection check still matches the final rows\n // rather than producing an empty result.\n const totalContentHeight = IF(this._rowPositions.lastElement)(() =>\n this._rowPositions.lastElement.bottomY\n ).ELSE(() =>\n 0\n )\n firstVisibleY = Math.min(firstVisibleY, totalContentHeight)\n \n for (let i = 0; i < numberOfRows; i++) {\n \n const position = this._rowPositionWithIndex(i)\n if (!position) {\n break\n }\n \n const rowTop = position.topY\n const rowBottom = position.bottomY\n \n // Check intersection\n if (rowBottom >= firstVisibleY && rowTop <= lastVisibleY) {\n result.push(i)\n }\n \n if (rowTop > lastVisibleY) {\n break\n }\n \n }\n \n return result\n \n }\n \n \n // This is called when no rows are supposed to be visible as a performance shortcut\n _removeVisibleRows() {\n \n this._visibleRows.forEach((row: UIView) => {\n \n this._persistedData[row._UITableViewRowIndex as number] = this.persistenceDataItemForRowWithIndex(\n row._UITableViewRowIndex as number,\n row\n )\n row.removeFromSuperview()\n this._markReusableViewAsUnused(row)\n \n })\n this._visibleRows = []\n \n }\n \n \n _removeAllReusableRows() {\n this._unusedReusableViews.forEach((rows: UIView[]) =>\n rows.forEach((row: UIView) => {\n \n this._persistedData[row._UITableViewRowIndex as number] = this.persistenceDataItemForRowWithIndex(\n row._UITableViewRowIndex as number,\n row\n )\n row.removeFromSuperview()\n \n })\n )\n this._unusedReusableViews = {}\n }\n \n \n _markReusableViewAsUnused(row: UIView) {\n const identifier = row._UITableViewReusabilityIdentifier\n if (!this._unusedReusableViews[identifier]) {\n this._unusedReusableViews[identifier] = []\n }\n if (!this._unusedReusableViews[identifier].contains(row)) {\n this._unusedReusableViews[identifier].push(row)\n }\n }\n \n _scheduleDrawVisibleRows() {\n if (!this._isDrawVisibleRowsScheduled) {\n this._isDrawVisibleRowsScheduled = YES\n \n UIView.runFunctionBeforeNextFrame(() => {\n this._calculateAllPositions()\n this._drawVisibleRows()\n this.setNeedsLayout()\n this._isDrawVisibleRowsScheduled = NO\n })\n }\n }\n \n _drawVisibleRows() {\n \n if (!this.isMemberOfViewTree) {\n return\n }\n \n // Uses the unified method above\n const visibleIndexes = this.indexesForVisibleRows()\n \n // If no rows are visible, remove all current rows\n if (visibleIndexes.length === 0) {\n this._removeVisibleRows()\n return\n }\n \n const minIndex = visibleIndexes[0]\n const maxIndex = visibleIndexes[visibleIndexes.length - 1]\n \n const removedViews: UITableViewRowView[] = []\n const visibleRows: UITableViewRowView[] = []\n \n // 1. Identify rows that have moved off-screen\n this._visibleRows.forEach((row) => {\n if (IS_DEFINED(row._UITableViewRowIndex) &&\n (row._UITableViewRowIndex < minIndex || row._UITableViewRowIndex > maxIndex)) {\n \n // Persist state before removal\n this._persistedData[row._UITableViewRowIndex] = this.persistenceDataItemForRowWithIndex(\n row._UITableViewRowIndex,\n row\n )\n this._markReusableViewAsUnused(row)\n removedViews.push(row)\n }\n else {\n visibleRows.push(row)\n }\n })\n \n this._visibleRows = visibleRows\n \n // 2. Add new rows that have moved onto the screen\n visibleIndexes.forEach((rowIndex: number) => {\n // If the view is already in this._visibleRows, do nothing to it\n if (this.isRowWithIndexVisible(rowIndex)) {\n return\n }\n \n // Get view from reuse pool (marked as unused before) or make a new one\n const view: UITableViewRowView = this.viewForRowWithIndex(rowIndex)\n this._visibleRows.push(view)\n this.addSubview(view)\n })\n \n // 3. Clean up DOM\n removedViews.forEach(row => {\n // Check that the row has not been added back\n if (this._visibleRows.indexOf(row) == -1) {\n row.removeFromSuperview()\n }\n })\n \n }\n \n \n visibleRowWithIndex(rowIndex: number | undefined): UIView | undefined {\n for (let i = 0; i < this._visibleRows.length; i++) {\n const row = this._visibleRows[i]\n if (row._UITableViewRowIndex == rowIndex) {\n return row\n }\n }\n }\n \n \n isRowWithIndexVisible(rowIndex: number) {\n return IS(this.visibleRowWithIndex(rowIndex))\n }\n \n \n reusableViewForIdentifier(identifier: string, rowIndex: number): UITableViewRowView {\n \n const visibleRowView = this.visibleRowWithIndex(rowIndex)\n if (visibleRowView?._UITableViewReusabilityIdentifier === identifier) {\n return visibleRowView\n }\n \n if (!this._unusedReusableViews[identifier]) {\n this._unusedReusableViews[identifier] = []\n }\n \n let view: UITableViewRowView\n \n if (this._unusedReusableViews[identifier]?.length) {\n \n view = this._unusedReusableViews[identifier].pop() as UITableViewRowView\n view._UITableViewRowIndex = rowIndex\n Object.assign(view, this._persistedData[rowIndex] || this.defaultRowPersistenceDataItem())\n \n }\n else {\n \n view = this.newReusableViewForIdentifier(identifier, this._rowIDIndex) as UITableViewRowView\n this._rowIDIndex = this._rowIDIndex + 1\n \n view._UITableViewReusabilityIdentifier = identifier\n view._UITableViewRowIndex = rowIndex\n \n Object.assign(view, this._persistedData[rowIndex] || this.defaultRowPersistenceDataItem())\n \n }\n \n // When all rows are uniform, opt the view into the shared height cache so\n // only the first measurement is ever computed for the whole table.\n if (this.allRowsHaveEqualHeight) {\n view.sharedIntrinsicSizeCacheIdentifier = this._equalRowHeightCacheIdentifier\n }\n else {\n view.sharedIntrinsicSizeCacheIdentifier = undefined\n }\n \n return view\n \n }\n \n \n // Functions that should be overridden to draw the correct content START\n newReusableViewForIdentifier(identifier: string, rowIDIndex: number): UIView {\n \n const view = new UIButton(this.elementID + \"Row\" + rowIDIndex)\n \n view.stopsPointerEventPropagation = NO\n view.pausesPointerEvents = NO\n \n return view\n \n }\n \n heightForRowWithIndex(index: number): number {\n return 50\n }\n \n numberOfRows() {\n return 10000\n }\n \n defaultRowPersistenceDataItem(): any {\n \n \n }\n \n persistenceDataItemForRowWithIndex(rowIndex: number, row: UIView): any {\n \n \n }\n \n viewForRowWithIndex(rowIndex: number): UITableViewRowView {\n const row = this.reusableViewForIdentifier(\"Row\", rowIndex)\n row._UITableViewRowIndex = rowIndex\n FIRST_OR_NIL((row as unknown as UIButton).titleLabel).text = \"Row \" + rowIndex\n return row\n }\n \n // Functions that should be overridden to draw the correct content END\n \n \n // Functions that trigger redrawing of the content\n override didScrollToPosition(offsetPosition: UIPoint) {\n \n super.didScrollToPosition(offsetPosition)\n \n this.forEachViewInSubtree((view: UIView) => {\n view._isPointerValid = NO\n })\n \n this._scheduleDrawVisibleRows()\n \n }\n \n override willMoveToSuperview(superview: UIView) {\n super.willMoveToSuperview(superview)\n \n if (IS(superview)) {\n // Set up viewport listeners when added to a superview\n this._setupViewportScrollAndResizeHandlersIfNeeded()\n }\n else {\n // Clean up when removed from superview\n this._cleanupViewportScrollListeners()\n }\n }\n \n override wasAddedToViewTree() {\n super.wasAddedToViewTree()\n this.loadData()\n \n // Ensure listeners are set up\n this._setupViewportScrollAndResizeHandlersIfNeeded()\n \n }\n \n override wasRemovedFromViewTree() {\n super.wasRemovedFromViewTree()\n this._cleanupViewportScrollListeners()\n }\n \n override setFrame(rectangle: UIRectangle, zIndex?: number, performUncheckedLayout?: boolean) {\n \n const frame = this.frame\n super.setFrame(rectangle, zIndex, performUncheckedLayout)\n if (frame.isEqualTo(rectangle) && !performUncheckedLayout) {\n return\n }\n \n this._needsDrawingOfVisibleRowsBeforeLayout = YES\n \n }\n \n \n override didReceiveBroadcastEvent(event: UIViewBroadcastEvent) {\n \n super.didReceiveBroadcastEvent(event)\n \n if (event.name == UIView.broadcastEventName.LanguageChanged && this.reloadsOnLanguageChange) {\n \n this.reloadData()\n \n }\n \n \n }\n \n \n override clearIntrinsicSizeCache() {\n super.clearIntrinsicSizeCache()\n if (this.allRowsHaveEqualHeight) {\n UIView.invalidateSharedIntrinsicSizeCache(this._equalRowHeightCacheIdentifier)\n }\n this.invalidateSizeOfRowWithIndex(0)\n }\n \n private _layoutAllRows(positions = this._rowPositions) {\n \n const bounds = this.bounds\n \n this._visibleRows.sort((rowA, rowB) => rowA._UITableViewRowIndex! - rowB._UITableViewRowIndex!)\n .forEach(row => {\n \n const frame = bounds.copy()\n \n const positionObject = this._rowPositionWithIndex(row._UITableViewRowIndex!, positions)\n frame.min.y = positionObject.topY\n frame.max.y = positionObject.bottomY\n row.frame = frame\n \n row.style.width = \"\" + (bounds.width - this.sidePadding * 2).integerValue + \"px\"\n row.style.left = \"\" + this.sidePadding.integerValue + \"px\"\n \n // This is to reorder the elements in the DOM\n this.viewHTMLElement.appendChild(row.viewHTMLElement)\n \n })\n \n // Use _rowPositionWithIndex rather than positions.lastElement.\n // When allRowsHaveEqualHeight = YES, _rowPositions contains only a single\n // entry (row 0). positions.lastElement.bottomY would therefore equal just\n // ONE row's height (e.g. 50px) instead of the full content height.\n // _rowPositionWithIndex correctly uses the computed formula for equal-height\n // tables (numberOfRows \u00D7 rowHeight) and falls back to positions[N-1] otherwise.\n // This ensures _fullHeightView maintains the correct scroll height even when\n // all visible rows have been removed from the DOM (e.g. after crossing the\n // bottom edge), preventing the browser from clamping scrollTop to 0.\n const numberOfRows = this.numberOfRows()\n const fullContentHeight = numberOfRows\n ? this._rowPositionWithIndex(numberOfRows - 1, positions).bottomY\n : 0\n this._fullHeightView.frame = bounds.rectangleWithHeight(fullContentHeight)\n .rectangleWithWidth(bounds.width * 0.5)\n \n }\n \n private _animateLayoutAllRows() {\n \n UIView.animateViewOrViewsWithDurationDelayAndFunction(\n this._visibleRows,\n this.animationDuration,\n 0,\n undefined,\n () => {\n \n this._layoutAllRows()\n \n },\n () => {\n \n \n }\n )\n \n }\n \n \n override layoutSubviews() {\n \n if (this.isVirtualLayouting) {\n console.error(\"layout subviews called during virtual layouting on UITableView, \" +\n \"indicating a possible error in the layout system.\")\n return\n }\n \n const previousPositions: UITableViewReusableViewPositionObject[] = JSON.parse(\n JSON.stringify(this._rowPositions))\n \n const previousVisibleRowsLength = this._visibleRows.length\n \n if (this._needsDrawingOfVisibleRowsBeforeLayout) {\n \n this._drawVisibleRows()\n \n this._needsDrawingOfVisibleRowsBeforeLayout = NO\n \n }\n \n \n super.layoutSubviews()\n \n \n if (!this.numberOfRows() || !this.isMemberOfViewTree) {\n \n return\n \n }\n \n \n if (this._shouldAnimateNextLayout) {\n \n \n // Need to do layout with the previous positions\n \n this._layoutAllRows(previousPositions)\n \n \n if (previousVisibleRowsLength < this._visibleRows.length) {\n \n \n UIView.runFunctionBeforeNextFrame(() => {\n \n this._animateLayoutAllRows()\n \n })\n \n }\n else {\n \n this._animateLayoutAllRows()\n \n }\n \n \n this._shouldAnimateNextLayout = NO\n \n }\n else {\n \n this._calculateAllPositions()\n \n this._layoutAllRows()\n \n \n }\n \n \n }\n \n \n override intrinsicContentHeight(constrainingWidth = 0) {\n \n let result = 0\n this._calculateAllPositions()\n \n const numberOfRows = this.numberOfRows()\n if (numberOfRows) {\n result = this._rowPositionWithIndex(numberOfRows - 1).bottomY\n }\n \n return result\n \n }\n \n \n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { UIButton } from \"./UIButton\"\nimport { UINativeScrollView } from \"./UINativeScrollView\"\nimport { FIRST_OR_NIL, IF, IS, IS_DEFINED, MAKE_ID, nil, NO, YES } from \"./UIObject\"\nimport { UIPoint } from \"./UIPoint\"\nimport { UIRectangle } from \"./UIRectangle\"\nimport { UIView, UIViewBroadcastEvent } from \"./UIView\"\n\n\ninterface UITableViewRowView extends UIView {\n \n _UITableViewRowIndex?: number;\n \n}\n\n\nexport interface UITableViewReusableViewsContainerObject {\n \n [key: string]: UIView[];\n \n}\n\n\nexport interface UITableViewReusableViewPositionObject {\n \n bottomY: number;\n topY: number;\n \n isValid: boolean;\n \n}\n\n\nexport class UITableView extends UINativeScrollView {\n \n \n allRowsHaveEqualHeight: boolean = NO\n _visibleRows: UITableViewRowView[] = []\n \n /** Shared intrinsic size cache identifier used for all row views when\n * allRowsHaveEqualHeight is YES. Stable for the lifetime of the table;\n * the shared cache bucket is invalidated on reloadData and\n * clearIntrinsicSizeCache so the height is re-measured after data changes. */\n _equalRowHeightCacheIdentifier: string\n \n _rowPositions: UITableViewReusableViewPositionObject[] = []\n \n _highestValidRowPositionIndex: number = 0\n \n _unusedReusableViews: UITableViewReusableViewsContainerObject = {}\n \n _fullHeightView: UIView\n _rowIDIndex: number = 0\n reloadsOnLanguageChange = YES\n sidePadding = 0\n \n cellWeights?: number[]\n \n _persistedData: any[] = []\n _needsDrawingOfVisibleRowsBeforeLayout = NO\n _isDrawVisibleRowsScheduled = NO\n _shouldAnimateNextLayout?: boolean\n \n override usesVirtualLayoutingForIntrinsicSizing = NO\n \n override animationDuration = 0.25\n \n // Viewport scrolling properties\n _intersectionObserver?: IntersectionObserver\n \n // -------------------------------------------------------------------------\n // Keyboard navigation state\n // -------------------------------------------------------------------------\n \n /** Row index with -1 meaning the header row. undefined means no focus. */\n _keyboardFocusedRowIndex: number | undefined = undefined\n /** Cell index within the focused row. */\n _keyboardFocusedCellIndex: number = 0\n /** Total number of data columns (excludes left/right side cells). Set by CBDataView. */\n _columnCount: number = 0\n /** Called by UITableView when the focused row/cell changes. CBDataView overrides this. */\n keyboardFocusDidChange?: (rowIndex: number | undefined, cellIndex: number) => void\n /** Fired when Enter is pressed on a focused cell. Passes rowIndex and cellIndex. */\n keyboardDidActivateCell?: (rowIndex: number, cellIndex: number) => void\n \n _keydownHandler?: (event: KeyboardEvent) => void\n _keyboardListenersAttached = false\n \n \n get _reusableViews(): UITableViewReusableViewsContainerObject {\n \n const result: UITableViewReusableViewsContainerObject = {}\n \n const addView = (view: UIView) => {\n const identifier = view._UITableViewReusabilityIdentifier\n if (!identifier) {\n return\n }\n if (!result[identifier]) {\n result[identifier] = []\n }\n result[identifier].push(view)\n }\n \n this._visibleRows.forEach(addView)\n \n this._unusedReusableViews.forEach((views: UIView[]) => views.forEach(addView))\n \n return result\n \n }\n \n \n constructor(elementID?: string) {\n \n super(elementID)\n \n this._equalRowHeightCacheIdentifier = (elementID ?? MAKE_ID()) + \"_rowHeight\"\n \n this._fullHeightView = new UIView()\n this._fullHeightView.hidden = YES\n this._fullHeightView.userInteractionEnabled = NO\n this.addSubview(this._fullHeightView)\n \n this.scrollsX = NO\n \n this._setupViewportScrollAndResizeHandlersIfNeeded()\n this._setupGridAccessibility()\n this._setupKeyboardNavigation()\n \n }\n \n \n // -------------------------------------------------------------------------\n // ARIA / Accessibility setup\n // -------------------------------------------------------------------------\n \n /**\n * The element that receives tabIndex, ARIA grid role, and all keyboard/pointer\n * listeners. Defaults to the table's own element. CBDataView overrides this\n * to a container that wraps both the header and the table, so the focus ring\n * encompasses both.\n */\n _keyboardListenerElement: HTMLElement = this.viewHTMLElement\n \n _setupGridAccessibility() {\n const el = this._keyboardListenerElement\n el.setAttribute(\"role\", \"grid\")\n el.setAttribute(\"aria-rowcount\", \"0\")\n el.setAttribute(\"aria-colcount\", \"0\")\n el.tabIndex = 0\n }\n \n /** Called by CBDataView after descriptors change. */\n setColumnCount(count: number) {\n this._columnCount = count\n this._keyboardListenerElement.setAttribute(\"aria-colcount\", String(count))\n }\n \n /** Called by CBDataView after data loads. */\n setRowCount(count: number) {\n this._keyboardListenerElement.setAttribute(\"aria-rowcount\", String(count))\n }\n \n \n // -------------------------------------------------------------------------\n // Keyboard navigation\n // -------------------------------------------------------------------------\n \n _setupKeyboardNavigation() {\n \n this._keydownHandler = (event: KeyboardEvent) => {\n \n if (!this.isMemberOfViewTree) {\n return\n }\n \n const rowCount = this.numberOfRows()\n const hasHeader = this._keyboardFocusedRowIndex !== undefined\n \n if (event.key === \"ArrowDown\") {\n event.preventDefault()\n if (this._keyboardFocusedRowIndex === undefined) {\n this._setKeyboardFocus(0, this._keyboardFocusedCellIndex)\n }\n else if (event.metaKey || event.ctrlKey) {\n this._setKeyboardFocus(rowCount - 1, this._keyboardFocusedCellIndex)\n }\n else if (event.altKey) {\n const pageSize = Math.max(1, Math.floor(this.bounds.height / (this._heightForAnyRow() || 50)))\n const next = Math.min((this._keyboardFocusedRowIndex < 0 ? 0 : this._keyboardFocusedRowIndex) + pageSize, rowCount - 1)\n this._setKeyboardFocus(next, this._keyboardFocusedCellIndex)\n }\n else if (this._keyboardFocusedRowIndex === -1) {\n this._setKeyboardFocus(0, this._keyboardFocusedCellIndex)\n }\n else if (this._keyboardFocusedRowIndex < rowCount - 1) {\n this._setKeyboardFocus(this._keyboardFocusedRowIndex + 1, this._keyboardFocusedCellIndex)\n }\n }\n else if (event.key === \"ArrowUp\") {\n event.preventDefault()\n if (this._keyboardFocusedRowIndex === undefined) {\n this._setKeyboardFocus(rowCount - 1, this._keyboardFocusedCellIndex)\n }\n else if (event.metaKey || event.ctrlKey) {\n this._setKeyboardFocus(-1, this._keyboardFocusedCellIndex)\n }\n else if (event.altKey) {\n const pageSize = Math.max(1, Math.floor(this.bounds.height / (this._heightForAnyRow() || 50)))\n const prev = Math.max((this._keyboardFocusedRowIndex < 0 ? 0 : this._keyboardFocusedRowIndex) - pageSize, -1)\n this._setKeyboardFocus(prev, this._keyboardFocusedCellIndex)\n }\n else if (this._keyboardFocusedRowIndex === 0) {\n this._setKeyboardFocus(-1, this._keyboardFocusedCellIndex)\n }\n else if (this._keyboardFocusedRowIndex > 0) {\n this._setKeyboardFocus(this._keyboardFocusedRowIndex - 1, this._keyboardFocusedCellIndex)\n }\n }\n else if (event.key === \"ArrowRight\") {\n event.preventDefault()\n if (this._keyboardFocusedRowIndex !== undefined && this._columnCount > 0) {\n const nextCell = event.metaKey || event.ctrlKey\n ? this._columnCount - 1\n : Math.min(this._keyboardFocusedCellIndex + 1, this._columnCount - 1)\n this._setKeyboardFocus(this._keyboardFocusedRowIndex, nextCell)\n }\n }\n else if (event.key === \"ArrowLeft\") {\n event.preventDefault()\n if (this._keyboardFocusedRowIndex !== undefined && this._columnCount > 0) {\n const prevCell = event.metaKey || event.ctrlKey\n ? 0\n : Math.max(this._keyboardFocusedCellIndex - 1, 0)\n this._setKeyboardFocus(this._keyboardFocusedRowIndex, prevCell)\n }\n }\n else if (event.key === \"Home\") {\n event.preventDefault()\n if (this._keyboardFocusedRowIndex !== undefined) {\n this._setKeyboardFocus(this._keyboardFocusedRowIndex, 0)\n }\n }\n else if (event.key === \"End\") {\n event.preventDefault()\n if (this._keyboardFocusedRowIndex !== undefined && this._columnCount > 0) {\n this._setKeyboardFocus(this._keyboardFocusedRowIndex, this._columnCount - 1)\n }\n }\n else if (event.key === \"PageDown\") {\n event.preventDefault()\n if (this._keyboardFocusedRowIndex !== undefined) {\n const pageSize = Math.max(1, Math.floor(this.bounds.height / (this._heightForAnyRow() || 50)))\n const next = Math.min((this._keyboardFocusedRowIndex < 0 ? 0 : this._keyboardFocusedRowIndex) + pageSize, rowCount - 1)\n this._setKeyboardFocus(next, this._keyboardFocusedCellIndex)\n }\n }\n else if (event.key === \"PageUp\") {\n event.preventDefault()\n if (this._keyboardFocusedRowIndex !== undefined) {\n const pageSize = Math.max(1, Math.floor(this.bounds.height / (this._heightForAnyRow() || 50)))\n const prev = Math.max((this._keyboardFocusedRowIndex < 0 ? 0 : this._keyboardFocusedRowIndex) - pageSize, -1)\n this._setKeyboardFocus(prev, this._keyboardFocusedCellIndex)\n }\n }\n else if (event.key === \"Enter\" || event.key === \" \") {\n if (this._keyboardFocusedRowIndex !== undefined && this._keyboardFocusedRowIndex >= 0) {\n event.preventDefault()\n this.keyboardDidActivateCell?.(this._keyboardFocusedRowIndex, this._keyboardFocusedCellIndex)\n }\n }\n else if (event.key === \"Escape\") {\n // Release focus from the table \u2014 move to next focusable element\n this._clearKeyboardFocus()\n this._keyboardListenerElement.blur()\n }\n \n }\n \n // Listeners are attached in wasAddedToViewTree to guarantee they land\n // on the final stable viewHTMLElement after the framework has fully\n // initialised the view.\n \n }\n \n /**\n * Move keyboard focus to a specific row and cell.\n * rowIndex = -1 means the header row.\n */\n _setKeyboardFocus(rowIndex: number, cellIndex: number) {\n \n const previousRowIndex = this._keyboardFocusedRowIndex\n const previousCellIndex = this._keyboardFocusedCellIndex\n \n // When moving to a different data row, land on the first button cell by default\n if (rowIndex >= 0 && rowIndex !== previousRowIndex) {\n const row = this.visibleRowWithIndex(rowIndex) as any\n if (row && typeof row.firstButtonCellIndex === \"function\") {\n cellIndex = row.firstButtonCellIndex()\n }\n }\n \n this._keyboardFocusedRowIndex = rowIndex\n this._keyboardFocusedCellIndex = cellIndex\n \n // Clear highlight from old position\n if (previousRowIndex !== undefined && previousRowIndex !== rowIndex) {\n this._clearKeyboardFocusOnRow(previousRowIndex)\n }\n else if (previousRowIndex === rowIndex && previousCellIndex !== cellIndex) {\n this._clearKeyboardFocusOnRow(rowIndex)\n }\n \n // Scroll the focused row into view if it is a data row\n if (rowIndex >= 0) {\n this._scrollRowIntoView(rowIndex)\n }\n \n // Apply highlight to new position\n this._applyKeyboardFocusToVisibleRows()\n \n // Notify observers (CBDataView uses this to sync header highlight)\n this.keyboardFocusDidChange?.(rowIndex, cellIndex)\n \n }\n \n _clearKeyboardFocus() {\n const previous = this._keyboardFocusedRowIndex\n this._keyboardFocusedRowIndex = undefined\n if (previous !== undefined) {\n this._clearKeyboardFocusOnRow(previous)\n }\n this.keyboardFocusDidChange?.(undefined, this._keyboardFocusedCellIndex)\n }\n \n _clearKeyboardFocusOnRow(rowIndex: number) {\n if (rowIndex === -1) {\n // Header \u2014 notify via callback; CBDataView handles the header view\n this.keyboardFocusDidChange?.(-1, -1)\n return\n }\n const row = this.visibleRowWithIndex(rowIndex) as any\n if (row && typeof row.setKeyboardFocusedCellIndex === \"function\") {\n row.setKeyboardFocusedCellIndex(undefined)\n }\n }\n \n _applyKeyboardFocusToVisibleRows(clearAll = false) {\n this._visibleRows.forEach((row: any) => {\n if (typeof row.setKeyboardFocusedCellIndex !== \"function\") {\n return\n }\n if (clearAll || row._UITableViewRowIndex !== this._keyboardFocusedRowIndex) {\n row.setKeyboardFocusedCellIndex(undefined)\n }\n else {\n row.setKeyboardFocusedCellIndex(this._keyboardFocusedCellIndex)\n }\n })\n }\n \n _scrollRowIntoView(rowIndex: number) {\n const position = this._rowPositionWithIndex(rowIndex)\n if (!position) {\n return\n }\n const offsetY = this.contentOffset.y\n const visibleHeight = this.bounds.height\n if (position.topY < offsetY) {\n const duration = this.animationDuration\n this.animationDuration = 0\n this.contentOffset = this.contentOffset.pointWithY(position.topY)\n this.animationDuration = duration\n }\n else if (position.bottomY > offsetY + visibleHeight) {\n const duration = this.animationDuration\n this.animationDuration = 0\n this.contentOffset = this.contentOffset.pointWithY(position.bottomY - visibleHeight)\n this.animationDuration = duration\n }\n }\n \n /** Expose so CBDataView can call it after loading data. */\n focusRowAtIndex(rowIndex: number, cellIndex: number = 0) {\n this._setKeyboardFocus(rowIndex, cellIndex)\n this._keyboardListenerElement.focus({ preventScroll: true })\n }\n \n \n _windowScrollHandler = () => {\n if (!this.isMemberOfViewTree) {\n return\n }\n this._scheduleDrawVisibleRows()\n }\n \n _resizeHandler = () => {\n if (!this.isMemberOfViewTree) {\n return\n }\n // Invalidate all row positions on resize as widths may have changed\n this._rowPositions.everyElement.isValid = NO\n this._highestValidRowPositionIndex = -1\n this._scheduleDrawVisibleRows()\n }\n \n _setupViewportScrollAndResizeHandlersIfNeeded() {\n if (this._intersectionObserver) {\n return\n }\n \n window.addEventListener(\"scroll\", this._windowScrollHandler, { passive: true })\n window.addEventListener(\"resize\", this._resizeHandler, { passive: true })\n \n // Use IntersectionObserver to detect when table enters/exits viewport\n this._intersectionObserver = new IntersectionObserver(\n (entries) => {\n entries.forEach(entry => {\n if (entry.isIntersecting && this.isMemberOfViewTree) {\n this._scheduleDrawVisibleRows()\n }\n })\n },\n {\n root: null,\n rootMargin: \"100% 0px\", // Load rows 100% viewport height before/after\n threshold: 0\n }\n )\n this._intersectionObserver.observe(this.viewHTMLElement)\n }\n \n \n _cleanupViewportScrollListeners() {\n window.removeEventListener(\"scroll\", this._windowScrollHandler)\n window.removeEventListener(\"resize\", this._resizeHandler)\n if (this._intersectionObserver) {\n this._intersectionObserver.disconnect()\n this._intersectionObserver = undefined\n }\n }\n \n override wasRemovedFromViewTree() {\n super.wasRemovedFromViewTree()\n this._cleanupViewportScrollListeners()\n if (this._keydownHandler) {\n this.viewHTMLElement.removeEventListener(\"keydown\", this._keydownHandler)\n }\n // Reset so listeners are re-attached if added back to the tree\n this._keyboardListenersAttached = false\n }\n \n \n loadData() {\n \n this._persistedData = []\n \n this._calculatePositionsUntilIndex(this.numberOfRows() - 1)\n this._needsDrawingOfVisibleRowsBeforeLayout = YES\n \n this.setNeedsLayout()\n \n }\n \n reloadData() {\n \n this._removeVisibleRows()\n this._removeAllReusableRows()\n \n this._rowPositions = []\n this._highestValidRowPositionIndex = -1\n \n if (this.allRowsHaveEqualHeight) {\n UIView.invalidateSharedIntrinsicSizeCache(this._equalRowHeightCacheIdentifier)\n }\n \n this.loadData()\n \n }\n \n \n highlightChanges(previousData: any[], newData: any[]) {\n \n previousData = previousData.map(dataPoint => JSON.stringify(dataPoint))\n newData = newData.map(dataPoint => JSON.stringify(dataPoint))\n \n const newIndexes: number[] = []\n \n newData.forEach((value, index) => {\n \n if (!previousData.contains(value)) {\n \n newIndexes.push(index)\n \n }\n \n })\n \n newIndexes.forEach(index => {\n \n if (this.isRowWithIndexVisible(index)) {\n this.highlightRowAsNew(\n this.visibleRowWithIndex(index) ?? this.viewForRowWithIndex(index)\n )\n }\n \n })\n \n }\n \n \n highlightRowAsNew(row: UIView) {\n \n \n }\n \n \n invalidateSizeOfRowWithIndex(index: number, animateChange = NO) {\n if (this._rowPositions?.[index]) {\n FIRST_OR_NIL(this._rowPositions[index]).isValid = NO\n this._rowPositions.slice(index).everyElement.isValid = NO\n }\n this._highestValidRowPositionIndex = Math.min(this._highestValidRowPositionIndex, index - 1)\n this._needsDrawingOfVisibleRowsBeforeLayout = YES\n this._shouldAnimateNextLayout = animateChange\n }\n \n \n _rowPositionWithIndex(index: number, positions = this._rowPositions) {\n if (this.allRowsHaveEqualHeight && index > 0) {\n const firstPositionObject = positions[0]\n const rowHeight = firstPositionObject.bottomY - firstPositionObject.topY\n const result = {\n bottomY: rowHeight * (index + 1),\n topY: rowHeight * index,\n isValid: firstPositionObject.isValid\n }\n return result\n }\n return positions[index]\n }\n \n _calculateAllPositions() {\n this._calculatePositionsUntilIndex(this.numberOfRows() - 1)\n }\n \n _calculatePositionsUntilIndex(maxIndex: number) {\n \n if (this.allRowsHaveEqualHeight) {\n const positionObject: UITableViewReusableViewPositionObject = {\n bottomY: this._heightForAnyRow(),\n topY: 0,\n isValid: YES\n }\n this._rowPositions = [positionObject]\n return\n }\n \n let validPositionObject = this._rowPositions[this._highestValidRowPositionIndex]\n if (!IS(validPositionObject)) {\n validPositionObject = {\n bottomY: 0,\n topY: 0,\n isValid: YES\n }\n }\n \n let previousBottomY = validPositionObject.bottomY\n if (!this._rowPositions.length) {\n this._highestValidRowPositionIndex = -1\n }\n \n for (let i = this._highestValidRowPositionIndex + 1; i <= maxIndex; i++) {\n \n let height: number\n \n const rowPositionObject = this._rowPositions[i]\n \n if (IS((rowPositionObject || nil).isValid)) {\n height = rowPositionObject.bottomY - rowPositionObject.topY\n }\n // Do not calculate heights if all rows have equal heights, and we already have a height\n else if (this.allRowsHaveEqualHeight && i > 0) {\n height = this._rowPositions[0].bottomY - this._rowPositions[0].topY\n }\n else {\n height = this.heightForRowWithIndex(i)\n }\n \n const positionObject: UITableViewReusableViewPositionObject = {\n bottomY: previousBottomY + height,\n topY: previousBottomY,\n isValid: YES\n }\n \n if (i < this._rowPositions.length) {\n this._rowPositions[i] = positionObject\n }\n else {\n this._rowPositions.push(positionObject)\n }\n this._highestValidRowPositionIndex = i\n previousBottomY = previousBottomY + height\n \n }\n \n }\n \n \n _heightForAnyRow(calculateVisibleRows = YES) {\n return this.heightForRowWithIndex(\n this._visibleRows.firstElement?._UITableViewRowIndex ??\n (calculateVisibleRows ? this.indexesForVisibleRows().firstElement : 0) ??\n 0\n )\n }\n \n indexesForVisibleRows(paddingRatio = 0.5): number[] {\n \n // 1. Calculate the visible frame relative to the Table's bounds (0,0 is top-left of the table view)\n // This accounts for the Window viewport clipping the table if it is partially off-screen.\n const tableRect = this.viewHTMLElement.getBoundingClientRect()\n const viewportHeight = window.innerHeight\n const pageScale = UIView.pageScale\n \n // The top of the visible window relative to the view's top edge.\n // If tableRect.top is negative, the table is scrolled up and clipped by the window top.\n const visibleFrameTop = Math.max(0, -tableRect.top / pageScale)\n \n // The bottom of the visible window relative to the view's top edge.\n // We clip it to the table's actual bounds height so we don't look past the table content.\n const visibleFrameBottom = Math.min(\n this.bounds.height,\n (viewportHeight - tableRect.top) / pageScale\n )\n \n // If the table is completely off-screen, return empty\n if (visibleFrameBottom <= visibleFrameTop) {\n return []\n }\n \n // 2. Convert to Content Coordinates (Scroll Offset)\n // contentOffset.y is the internal scroll position.\n // If using viewport scrolling (full height), contentOffset.y is typically 0.\n // If using internal scrolling, this shifts the visible frame to the correct content rows.\n let firstVisibleY = this.contentOffset.y + visibleFrameTop\n let lastVisibleY = this.contentOffset.y + visibleFrameBottom\n \n // 3. Apply Padding\n // We calculate padding based on the viewport height to ensure smooth scrolling\n const paddingPx = (viewportHeight / pageScale) * paddingRatio\n firstVisibleY = Math.max(0, firstVisibleY - paddingPx)\n lastVisibleY = lastVisibleY + paddingPx\n \n const numberOfRows = this.numberOfRows()\n \n // 4. Find Indexes\n if (this.allRowsHaveEqualHeight) {\n \n const rowHeight = this._heightForAnyRow(NO)\n \n let firstIndex = Math.floor(firstVisibleY / rowHeight)\n let lastIndex = Math.floor(lastVisibleY / rowHeight)\n \n // Clamp BOTH indexes to [0, numberOfRows-1].\n // Without the upper clamp on firstIndex, when the viewport extends below the\n // last row firstIndex can exceed numberOfRows-1 while lastIndex is already\n // clamped there. firstIndex > lastIndex \u2192 empty result \u2192 _removeVisibleRows \u2192\n // browser collapses scrollHeight \u2192 scrollTop resets to 0 \u2192 rows pile at top.\n firstIndex = Math.max(0, Math.min(firstIndex, numberOfRows - 1))\n lastIndex = Math.max(0, Math.min(lastIndex, numberOfRows - 1))\n \n const result = []\n for (let i = firstIndex; i <= lastIndex; i++) {\n result.push(i)\n }\n return result\n }\n \n // Variable Heights\n this._calculateAllPositions()\n const result = []\n \n // Clamp firstVisibleY to the actual content height so that when the viewport\n // extends below the last row the intersection check still matches the final rows\n // rather than producing an empty result.\n const totalContentHeight = IF(this._rowPositions.lastElement)(() =>\n this._rowPositions.lastElement.bottomY\n ).ELSE(() =>\n 0\n )\n firstVisibleY = Math.min(firstVisibleY, totalContentHeight)\n \n for (let i = 0; i < numberOfRows; i++) {\n \n const position = this._rowPositionWithIndex(i)\n if (!position) {\n break\n }\n \n const rowTop = position.topY\n const rowBottom = position.bottomY\n \n // Check intersection\n if (rowBottom >= firstVisibleY && rowTop <= lastVisibleY) {\n result.push(i)\n }\n \n if (rowTop > lastVisibleY) {\n break\n }\n \n }\n \n return result\n \n }\n \n \n // This is called when no rows are supposed to be visible as a performance shortcut\n _removeVisibleRows() {\n \n this._visibleRows.forEach((row: UIView) => {\n \n this._persistedData[row._UITableViewRowIndex as number] = this.persistenceDataItemForRowWithIndex(\n row._UITableViewRowIndex as number,\n row\n )\n row.removeFromSuperview()\n this._markReusableViewAsUnused(row)\n \n })\n this._visibleRows = []\n \n }\n \n \n _removeAllReusableRows() {\n this._unusedReusableViews.forEach((rows: UIView[]) =>\n rows.forEach((row: UIView) => {\n \n this._persistedData[row._UITableViewRowIndex as number] = this.persistenceDataItemForRowWithIndex(\n row._UITableViewRowIndex as number,\n row\n )\n row.removeFromSuperview()\n \n })\n )\n this._unusedReusableViews = {}\n }\n \n \n _markReusableViewAsUnused(row: UIView) {\n const identifier = row._UITableViewReusabilityIdentifier\n if (!this._unusedReusableViews[identifier]) {\n this._unusedReusableViews[identifier] = []\n }\n if (!this._unusedReusableViews[identifier].contains(row)) {\n this._unusedReusableViews[identifier].push(row)\n }\n }\n \n _scheduleDrawVisibleRows() {\n if (!this._isDrawVisibleRowsScheduled) {\n this._isDrawVisibleRowsScheduled = YES\n \n UIView.runFunctionBeforeNextFrame(() => {\n this._calculateAllPositions()\n this._drawVisibleRows()\n this.setNeedsLayout()\n this._isDrawVisibleRowsScheduled = NO\n })\n }\n }\n \n _drawVisibleRows() {\n \n if (!this.isMemberOfViewTree) {\n return\n }\n \n // Uses the unified method above\n const visibleIndexes = this.indexesForVisibleRows()\n \n // If no rows are visible, remove all current rows\n if (visibleIndexes.length === 0) {\n this._removeVisibleRows()\n return\n }\n \n const minIndex = visibleIndexes[0]\n const maxIndex = visibleIndexes[visibleIndexes.length - 1]\n \n const removedViews: UITableViewRowView[] = []\n const visibleRows: UITableViewRowView[] = []\n \n // 1. Identify rows that have moved off-screen\n this._visibleRows.forEach((row) => {\n if (IS_DEFINED(row._UITableViewRowIndex) &&\n (row._UITableViewRowIndex < minIndex || row._UITableViewRowIndex > maxIndex)) {\n \n // Persist state before removal\n this._persistedData[row._UITableViewRowIndex] = this.persistenceDataItemForRowWithIndex(\n row._UITableViewRowIndex,\n row\n )\n this._markReusableViewAsUnused(row)\n removedViews.push(row)\n }\n else {\n visibleRows.push(row)\n }\n })\n \n this._visibleRows = visibleRows\n \n // 2. Add new rows that have moved onto the screen\n visibleIndexes.forEach((rowIndex: number) => {\n // If the view is already in this._visibleRows, do nothing to it\n if (this.isRowWithIndexVisible(rowIndex)) {\n return\n }\n \n // Get view from reuse pool (marked as unused before) or make a new one\n const view: UITableViewRowView = this.viewForRowWithIndex(rowIndex)\n this._visibleRows.push(view)\n this.addSubview(view)\n \n // Ensure the row and all its children stay out of the natural tab order\n view.tabIndex = -1\n view.forEachViewInSubtree(subview => {\n subview.tabIndex = -1\n })\n })\n \n // 3. Clean up DOM\n removedViews.forEach(row => {\n // Check that the row has not been added back\n if (this._visibleRows.indexOf(row) == -1) {\n row.removeFromSuperview()\n }\n })\n \n // 4. Re-apply keyboard focus highlight after rows are re-rendered\n this._applyKeyboardFocusToVisibleRows()\n \n }\n \n \n visibleRowWithIndex(rowIndex: number | undefined): UIView | undefined {\n for (let i = 0; i < this._visibleRows.length; i++) {\n const row = this._visibleRows[i]\n if (row._UITableViewRowIndex == rowIndex) {\n return row\n }\n }\n }\n \n \n isRowWithIndexVisible(rowIndex: number) {\n return IS(this.visibleRowWithIndex(rowIndex))\n }\n \n \n reusableViewForIdentifier(identifier: string, rowIndex: number): UITableViewRowView {\n \n const visibleRowView = this.visibleRowWithIndex(rowIndex)\n if (visibleRowView?._UITableViewReusabilityIdentifier === identifier) {\n return visibleRowView\n }\n \n if (!this._unusedReusableViews[identifier]) {\n this._unusedReusableViews[identifier] = []\n }\n \n let view: UITableViewRowView\n \n if (this._unusedReusableViews[identifier]?.length) {\n \n view = this._unusedReusableViews[identifier].pop() as UITableViewRowView\n view._UITableViewRowIndex = rowIndex\n Object.assign(view, this._persistedData[rowIndex] || this.defaultRowPersistenceDataItem())\n \n }\n else {\n \n view = this.newReusableViewForIdentifier(identifier, this._rowIDIndex) as UITableViewRowView\n this._rowIDIndex = this._rowIDIndex + 1\n \n view._UITableViewReusabilityIdentifier = identifier\n view._UITableViewRowIndex = rowIndex\n \n Object.assign(view, this._persistedData[rowIndex] || this.defaultRowPersistenceDataItem())\n \n }\n \n // When all rows are uniform, opt the view into the shared height cache so\n // only the first measurement is ever computed for the whole table.\n if (this.allRowsHaveEqualHeight) {\n view.sharedIntrinsicSizeCacheIdentifier = this._equalRowHeightCacheIdentifier\n }\n else {\n view.sharedIntrinsicSizeCacheIdentifier = undefined\n }\n \n return view\n \n }\n \n \n // Functions that should be overridden to draw the correct content START\n newReusableViewForIdentifier(identifier: string, rowIDIndex: number): UIView {\n \n const view = new UIButton(this.elementID + \"Row\" + rowIDIndex)\n \n view.stopsPointerEventPropagation = NO\n view.pausesPointerEvents = NO\n \n return view\n \n }\n \n heightForRowWithIndex(index: number): number {\n return 50\n }\n \n numberOfRows() {\n return 10000\n }\n \n defaultRowPersistenceDataItem(): any {\n \n \n }\n \n persistenceDataItemForRowWithIndex(rowIndex: number, row: UIView): any {\n \n \n }\n \n viewForRowWithIndex(rowIndex: number): UITableViewRowView {\n const row = this.reusableViewForIdentifier(\"Row\", rowIndex)\n row._UITableViewRowIndex = rowIndex\n FIRST_OR_NIL((row as unknown as UIButton).titleLabel).text = \"Row \" + rowIndex\n return row\n }\n \n // Functions that should be overridden to draw the correct content END\n \n \n // Functions that trigger redrawing of the content\n override didScrollToPosition(offsetPosition: UIPoint) {\n \n super.didScrollToPosition(offsetPosition)\n \n this.forEachViewInSubtree((view: UIView) => {\n view._isPointerValid = NO\n })\n \n this._scheduleDrawVisibleRows()\n \n }\n \n override willMoveToSuperview(superview: UIView) {\n super.willMoveToSuperview(superview)\n \n if (IS(superview)) {\n // Set up viewport listeners when added to a superview\n this._setupViewportScrollAndResizeHandlersIfNeeded()\n }\n else {\n // Clean up when removed from superview\n this._cleanupViewportScrollListeners()\n }\n }\n \n override wasAddedToViewTree() {\n super.wasAddedToViewTree()\n this.loadData()\n \n // Ensure listeners are set up\n this._setupViewportScrollAndResizeHandlersIfNeeded()\n \n // Attach keyboard and pointer listeners now that the element is stable\n // in the DOM. Guarded so repeated wasAddedToViewTree calls (e.g. after\n // navigation returns) don't stack duplicate listeners.\n if (!this._keyboardListenersAttached) {\n this._keyboardListenersAttached = true\n const el = this._keyboardListenerElement\n \n el.addEventListener(\"keydown\", this._keydownHandler!)\n \n el.addEventListener(\"pointerdown\", (event: PointerEvent) => {\n let target = event.target as HTMLElement | null\n while (target && target !== el) {\n const viewObject = (target as any).UIViewObject as UITableViewRowView | undefined\n if (viewObject?._UITableViewRowIndex !== undefined) {\n el.focus({ preventScroll: true })\n this._setKeyboardFocus(viewObject._UITableViewRowIndex, this._keyboardFocusedCellIndex)\n return\n }\n target = target.parentElement\n }\n el.focus({ preventScroll: true })\n })\n \n el.addEventListener(\"focus\", () => {\n if (this._keyboardFocusedRowIndex === undefined && this.numberOfRows() > 0) {\n this._setKeyboardFocus(0, this._keyboardFocusedCellIndex)\n }\n else if (this._keyboardFocusedRowIndex !== undefined) {\n this._applyKeyboardFocusToVisibleRows()\n }\n })\n \n el.addEventListener(\"blur\", (event: FocusEvent) => {\n if (!el.contains(event.relatedTarget as Node)) {\n this._applyKeyboardFocusToVisibleRows(true)\n }\n })\n }\n \n // Remove all subviews from the browser's natural tab order.\n // The container (_keyboardListenerElement) is the single tab stop.\n // Internal navigation is handled exclusively via arrow keys.\n this.forEachViewInSubtree(view => {\n if (view !== this) {\n view.tabIndex = -1\n }\n })\n // Re-assert tabIndex=0 on the listener element \u2014 wasAddedToViewTree fires\n // on every tree insertion including navigation returns.\n this._keyboardListenerElement.tabIndex = 0\n \n }\n override setFrame(rectangle: UIRectangle, zIndex?: number, performUncheckedLayout?: boolean) {\n \n const frame = this.frame\n super.setFrame(rectangle, zIndex, performUncheckedLayout)\n if (frame.isEqualTo(rectangle) && !performUncheckedLayout) {\n return\n }\n \n this._needsDrawingOfVisibleRowsBeforeLayout = YES\n \n }\n \n \n override didReceiveBroadcastEvent(event: UIViewBroadcastEvent) {\n \n super.didReceiveBroadcastEvent(event)\n \n if (event.name == UIView.broadcastEventName.LanguageChanged && this.reloadsOnLanguageChange) {\n \n this.reloadData()\n \n }\n \n \n }\n \n \n override clearIntrinsicSizeCache() {\n super.clearIntrinsicSizeCache()\n if (this.allRowsHaveEqualHeight) {\n UIView.invalidateSharedIntrinsicSizeCache(this._equalRowHeightCacheIdentifier)\n }\n this.invalidateSizeOfRowWithIndex(0)\n }\n \n private _layoutAllRows(positions = this._rowPositions) {\n \n const bounds = this.bounds\n \n this._visibleRows.sort((rowA, rowB) => rowA._UITableViewRowIndex! - rowB._UITableViewRowIndex!)\n .forEach(row => {\n \n const frame = bounds.copy()\n \n const positionObject = this._rowPositionWithIndex(row._UITableViewRowIndex!, positions)\n frame.min.y = positionObject.topY\n frame.max.y = positionObject.bottomY\n row.frame = frame\n \n row.style.width = \"\" + (bounds.width - this.sidePadding * 2).integerValue + \"px\"\n row.style.left = \"\" + this.sidePadding.integerValue + \"px\"\n \n // Set aria-rowindex (1-based per ARIA spec)\n row.viewHTMLElement.setAttribute(\"aria-rowindex\", String((row._UITableViewRowIndex ?? 0) + 1))\n \n // This is to reorder the elements in the DOM\n this.viewHTMLElement.appendChild(row.viewHTMLElement)\n \n })\n \n // Use _rowPositionWithIndex rather than positions.lastElement.\n // When allRowsHaveEqualHeight = YES, _rowPositions contains only a single\n // entry (row 0). positions.lastElement.bottomY would therefore equal just\n // ONE row's height (e.g. 50px) instead of the full content height.\n // _rowPositionWithIndex correctly uses the computed formula for equal-height\n // tables (numberOfRows \u00D7 rowHeight) and falls back to positions[N-1] otherwise.\n // This ensures _fullHeightView maintains the correct scroll height even when\n // all visible rows have been removed from the DOM (e.g. after crossing the\n // bottom edge), preventing the browser from clamping scrollTop to 0.\n const numberOfRows = this.numberOfRows()\n const fullContentHeight = numberOfRows\n ? this._rowPositionWithIndex(numberOfRows - 1, positions).bottomY\n : 0\n this._fullHeightView.frame = bounds.rectangleWithHeight(fullContentHeight)\n .rectangleWithWidth(bounds.width * 0.5)\n \n }\n \n private _animateLayoutAllRows() {\n \n UIView.animateViewOrViewsWithDurationDelayAndFunction(\n this._visibleRows,\n this.animationDuration,\n 0,\n undefined,\n () => {\n \n this._layoutAllRows()\n \n },\n () => {\n \n \n }\n )\n \n }\n \n \n override layoutSubviews() {\n \n if (this.isVirtualLayouting) {\n console.error(\"layout subviews called during virtual layouting on UITableView, \" +\n \"indicating a possible error in the layout system.\")\n return\n }\n \n const previousPositions: UITableViewReusableViewPositionObject[] = JSON.parse(\n JSON.stringify(this._rowPositions))\n \n const previousVisibleRowsLength = this._visibleRows.length\n \n if (this._needsDrawingOfVisibleRowsBeforeLayout) {\n \n this._drawVisibleRows()\n \n this._needsDrawingOfVisibleRowsBeforeLayout = NO\n \n }\n \n \n super.layoutSubviews()\n \n \n if (!this.numberOfRows() || !this.isMemberOfViewTree) {\n \n return\n \n }\n \n \n if (this._shouldAnimateNextLayout) {\n \n \n // Need to do layout with the previous positions\n \n this._layoutAllRows(previousPositions)\n \n \n if (previousVisibleRowsLength < this._visibleRows.length) {\n \n \n UIView.runFunctionBeforeNextFrame(() => {\n \n this._animateLayoutAllRows()\n \n })\n \n }\n else {\n \n this._animateLayoutAllRows()\n \n }\n \n \n this._shouldAnimateNextLayout = NO\n \n }\n else {\n \n this._calculateAllPositions()\n \n this._layoutAllRows()\n \n \n }\n \n \n }\n \n \n override intrinsicContentHeight(constrainingWidth = 0) {\n \n let result = 0\n this._calculateAllPositions()\n \n const numberOfRows = this.numberOfRows()\n if (numberOfRows) {\n result = this._rowPositionWithIndex(numberOfRows - 1).bottomY\n }\n \n return result\n \n }\n \n \n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["result"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uicore-ts",
3
- "version": "1.1.205",
3
+ "version": "1.1.207",
4
4
  "description": "UICore is a library to build native-like user interfaces using pure Typescript. No HTML is needed at all. Components are described as TS classes and all user interactions are handled explicitly. This library is strongly inspired by the UIKit framework that is used in IOS. In addition, UICore has tools to handle URL based routing, array sorting and filtering and adds a number of other utilities for convenience.",
5
5
  "main": "compiledScripts/index.js",
6
6
  "types": "compiledScripts/index.d.ts",
@@ -34,10 +34,21 @@ export class UIAutocompleteTextField<T = string> extends UITextField {
34
34
  this.commitSelection(item)
35
35
  }
36
36
 
37
+ let textBeforeFocus = this.text
38
+ let itemBeforeFocus = this.selectedItem
37
39
  // Open dropdown on focus
38
40
  this.controlEventTargetAccumulator.Focus = () => {
41
+ textBeforeFocus = this.text
42
+ itemBeforeFocus = this.selectedItem
43
+ this.text = ""
39
44
  this.openDropdown()
40
45
  this.textElementView.viewHTMLElement.select()
46
+ const matchIndex = this._dropdownView.filteredItems.findIndex(
47
+ item => item.label === textBeforeFocus
48
+ )
49
+ if (matchIndex !== -1) {
50
+ this._dropdownView.highlightedRowIndex = matchIndex
51
+ }
41
52
  }
42
53
 
43
54
  // Close on blur
@@ -90,6 +101,12 @@ export class UIAutocompleteTextField<T = string> extends UITextField {
90
101
  this.addTargetForControlEvent(UIView.controlEvent.EscDown, () => {
91
102
  if (this._isDropdownOpen) {
92
103
  this.closeDropdown()
104
+ if (this.strictSelection) {
105
+ this.commitSelection(itemBeforeFocus as any)
106
+ }
107
+ else {
108
+ this.text = textBeforeFocus
109
+ }
93
110
  }
94
111
  })
95
112
 
@@ -123,6 +140,7 @@ export class UIAutocompleteTextField<T = string> extends UITextField {
123
140
 
124
141
  commitSelection(item: UIAutocompleteItem<T>) {
125
142
 
143
+ this.blur()
126
144
  this._selectedItem = item
127
145
  this.text = item.label
128
146
  this.closeDropdown()
@@ -169,11 +187,9 @@ export class UIAutocompleteTextField<T = string> extends UITextField {
169
187
  filtered = this._autocompleteItems
170
188
  }
171
189
  else {
172
- const words = filterText.split(/\s+/)
173
- filtered = this._autocompleteItems.filter(item => {
174
- const label = item.label.toLowerCase()
175
- return words.every(word => label.includes(word))
176
- })
190
+ filtered = this._autocompleteItems.filter(item =>
191
+ item.label.toLowerCase().includes(filterText)
192
+ )
177
193
  }
178
194
 
179
195
  // If the only remaining result is an exact match for the current text,
@@ -259,8 +275,6 @@ export class UIAutocompleteTextField<T = string> extends UITextField {
259
275
  }
260
276
 
261
277
 
262
-
263
-
264
278
  /**
265
279
  * Hook for subclasses to apply custom visual styling based on validation state.
266
280
  * Called after dropdown closes and after selection changes.
@@ -67,6 +67,24 @@ export class UITableView extends UINativeScrollView {
67
67
  // Viewport scrolling properties
68
68
  _intersectionObserver?: IntersectionObserver
69
69
 
70
+ // -------------------------------------------------------------------------
71
+ // Keyboard navigation state
72
+ // -------------------------------------------------------------------------
73
+
74
+ /** Row index with -1 meaning the header row. undefined means no focus. */
75
+ _keyboardFocusedRowIndex: number | undefined = undefined
76
+ /** Cell index within the focused row. */
77
+ _keyboardFocusedCellIndex: number = 0
78
+ /** Total number of data columns (excludes left/right side cells). Set by CBDataView. */
79
+ _columnCount: number = 0
80
+ /** Called by UITableView when the focused row/cell changes. CBDataView overrides this. */
81
+ keyboardFocusDidChange?: (rowIndex: number | undefined, cellIndex: number) => void
82
+ /** Fired when Enter is pressed on a focused cell. Passes rowIndex and cellIndex. */
83
+ keyboardDidActivateCell?: (rowIndex: number, cellIndex: number) => void
84
+
85
+ _keydownHandler?: (event: KeyboardEvent) => void
86
+ _keyboardListenersAttached = false
87
+
70
88
 
71
89
  get _reusableViews(): UITableViewReusableViewsContainerObject {
72
90
 
@@ -106,7 +124,266 @@ export class UITableView extends UINativeScrollView {
106
124
  this.scrollsX = NO
107
125
 
108
126
  this._setupViewportScrollAndResizeHandlersIfNeeded()
127
+ this._setupGridAccessibility()
128
+ this._setupKeyboardNavigation()
129
+
130
+ }
131
+
132
+
133
+ // -------------------------------------------------------------------------
134
+ // ARIA / Accessibility setup
135
+ // -------------------------------------------------------------------------
136
+
137
+ /**
138
+ * The element that receives tabIndex, ARIA grid role, and all keyboard/pointer
139
+ * listeners. Defaults to the table's own element. CBDataView overrides this
140
+ * to a container that wraps both the header and the table, so the focus ring
141
+ * encompasses both.
142
+ */
143
+ _keyboardListenerElement: HTMLElement = this.viewHTMLElement
144
+
145
+ _setupGridAccessibility() {
146
+ const el = this._keyboardListenerElement
147
+ el.setAttribute("role", "grid")
148
+ el.setAttribute("aria-rowcount", "0")
149
+ el.setAttribute("aria-colcount", "0")
150
+ el.tabIndex = 0
151
+ }
152
+
153
+ /** Called by CBDataView after descriptors change. */
154
+ setColumnCount(count: number) {
155
+ this._columnCount = count
156
+ this._keyboardListenerElement.setAttribute("aria-colcount", String(count))
157
+ }
158
+
159
+ /** Called by CBDataView after data loads. */
160
+ setRowCount(count: number) {
161
+ this._keyboardListenerElement.setAttribute("aria-rowcount", String(count))
162
+ }
163
+
164
+
165
+ // -------------------------------------------------------------------------
166
+ // Keyboard navigation
167
+ // -------------------------------------------------------------------------
168
+
169
+ _setupKeyboardNavigation() {
109
170
 
171
+ this._keydownHandler = (event: KeyboardEvent) => {
172
+
173
+ if (!this.isMemberOfViewTree) {
174
+ return
175
+ }
176
+
177
+ const rowCount = this.numberOfRows()
178
+ const hasHeader = this._keyboardFocusedRowIndex !== undefined
179
+
180
+ if (event.key === "ArrowDown") {
181
+ event.preventDefault()
182
+ if (this._keyboardFocusedRowIndex === undefined) {
183
+ this._setKeyboardFocus(0, this._keyboardFocusedCellIndex)
184
+ }
185
+ else if (event.metaKey || event.ctrlKey) {
186
+ this._setKeyboardFocus(rowCount - 1, this._keyboardFocusedCellIndex)
187
+ }
188
+ else if (event.altKey) {
189
+ const pageSize = Math.max(1, Math.floor(this.bounds.height / (this._heightForAnyRow() || 50)))
190
+ const next = Math.min((this._keyboardFocusedRowIndex < 0 ? 0 : this._keyboardFocusedRowIndex) + pageSize, rowCount - 1)
191
+ this._setKeyboardFocus(next, this._keyboardFocusedCellIndex)
192
+ }
193
+ else if (this._keyboardFocusedRowIndex === -1) {
194
+ this._setKeyboardFocus(0, this._keyboardFocusedCellIndex)
195
+ }
196
+ else if (this._keyboardFocusedRowIndex < rowCount - 1) {
197
+ this._setKeyboardFocus(this._keyboardFocusedRowIndex + 1, this._keyboardFocusedCellIndex)
198
+ }
199
+ }
200
+ else if (event.key === "ArrowUp") {
201
+ event.preventDefault()
202
+ if (this._keyboardFocusedRowIndex === undefined) {
203
+ this._setKeyboardFocus(rowCount - 1, this._keyboardFocusedCellIndex)
204
+ }
205
+ else if (event.metaKey || event.ctrlKey) {
206
+ this._setKeyboardFocus(-1, this._keyboardFocusedCellIndex)
207
+ }
208
+ else if (event.altKey) {
209
+ const pageSize = Math.max(1, Math.floor(this.bounds.height / (this._heightForAnyRow() || 50)))
210
+ const prev = Math.max((this._keyboardFocusedRowIndex < 0 ? 0 : this._keyboardFocusedRowIndex) - pageSize, -1)
211
+ this._setKeyboardFocus(prev, this._keyboardFocusedCellIndex)
212
+ }
213
+ else if (this._keyboardFocusedRowIndex === 0) {
214
+ this._setKeyboardFocus(-1, this._keyboardFocusedCellIndex)
215
+ }
216
+ else if (this._keyboardFocusedRowIndex > 0) {
217
+ this._setKeyboardFocus(this._keyboardFocusedRowIndex - 1, this._keyboardFocusedCellIndex)
218
+ }
219
+ }
220
+ else if (event.key === "ArrowRight") {
221
+ event.preventDefault()
222
+ if (this._keyboardFocusedRowIndex !== undefined && this._columnCount > 0) {
223
+ const nextCell = event.metaKey || event.ctrlKey
224
+ ? this._columnCount - 1
225
+ : Math.min(this._keyboardFocusedCellIndex + 1, this._columnCount - 1)
226
+ this._setKeyboardFocus(this._keyboardFocusedRowIndex, nextCell)
227
+ }
228
+ }
229
+ else if (event.key === "ArrowLeft") {
230
+ event.preventDefault()
231
+ if (this._keyboardFocusedRowIndex !== undefined && this._columnCount > 0) {
232
+ const prevCell = event.metaKey || event.ctrlKey
233
+ ? 0
234
+ : Math.max(this._keyboardFocusedCellIndex - 1, 0)
235
+ this._setKeyboardFocus(this._keyboardFocusedRowIndex, prevCell)
236
+ }
237
+ }
238
+ else if (event.key === "Home") {
239
+ event.preventDefault()
240
+ if (this._keyboardFocusedRowIndex !== undefined) {
241
+ this._setKeyboardFocus(this._keyboardFocusedRowIndex, 0)
242
+ }
243
+ }
244
+ else if (event.key === "End") {
245
+ event.preventDefault()
246
+ if (this._keyboardFocusedRowIndex !== undefined && this._columnCount > 0) {
247
+ this._setKeyboardFocus(this._keyboardFocusedRowIndex, this._columnCount - 1)
248
+ }
249
+ }
250
+ else if (event.key === "PageDown") {
251
+ event.preventDefault()
252
+ if (this._keyboardFocusedRowIndex !== undefined) {
253
+ const pageSize = Math.max(1, Math.floor(this.bounds.height / (this._heightForAnyRow() || 50)))
254
+ const next = Math.min((this._keyboardFocusedRowIndex < 0 ? 0 : this._keyboardFocusedRowIndex) + pageSize, rowCount - 1)
255
+ this._setKeyboardFocus(next, this._keyboardFocusedCellIndex)
256
+ }
257
+ }
258
+ else if (event.key === "PageUp") {
259
+ event.preventDefault()
260
+ if (this._keyboardFocusedRowIndex !== undefined) {
261
+ const pageSize = Math.max(1, Math.floor(this.bounds.height / (this._heightForAnyRow() || 50)))
262
+ const prev = Math.max((this._keyboardFocusedRowIndex < 0 ? 0 : this._keyboardFocusedRowIndex) - pageSize, -1)
263
+ this._setKeyboardFocus(prev, this._keyboardFocusedCellIndex)
264
+ }
265
+ }
266
+ else if (event.key === "Enter" || event.key === " ") {
267
+ if (this._keyboardFocusedRowIndex !== undefined && this._keyboardFocusedRowIndex >= 0) {
268
+ event.preventDefault()
269
+ this.keyboardDidActivateCell?.(this._keyboardFocusedRowIndex, this._keyboardFocusedCellIndex)
270
+ }
271
+ }
272
+ else if (event.key === "Escape") {
273
+ // Release focus from the table — move to next focusable element
274
+ this._clearKeyboardFocus()
275
+ this._keyboardListenerElement.blur()
276
+ }
277
+
278
+ }
279
+
280
+ // Listeners are attached in wasAddedToViewTree to guarantee they land
281
+ // on the final stable viewHTMLElement after the framework has fully
282
+ // initialised the view.
283
+
284
+ }
285
+
286
+ /**
287
+ * Move keyboard focus to a specific row and cell.
288
+ * rowIndex = -1 means the header row.
289
+ */
290
+ _setKeyboardFocus(rowIndex: number, cellIndex: number) {
291
+
292
+ const previousRowIndex = this._keyboardFocusedRowIndex
293
+ const previousCellIndex = this._keyboardFocusedCellIndex
294
+
295
+ // When moving to a different data row, land on the first button cell by default
296
+ if (rowIndex >= 0 && rowIndex !== previousRowIndex) {
297
+ const row = this.visibleRowWithIndex(rowIndex) as any
298
+ if (row && typeof row.firstButtonCellIndex === "function") {
299
+ cellIndex = row.firstButtonCellIndex()
300
+ }
301
+ }
302
+
303
+ this._keyboardFocusedRowIndex = rowIndex
304
+ this._keyboardFocusedCellIndex = cellIndex
305
+
306
+ // Clear highlight from old position
307
+ if (previousRowIndex !== undefined && previousRowIndex !== rowIndex) {
308
+ this._clearKeyboardFocusOnRow(previousRowIndex)
309
+ }
310
+ else if (previousRowIndex === rowIndex && previousCellIndex !== cellIndex) {
311
+ this._clearKeyboardFocusOnRow(rowIndex)
312
+ }
313
+
314
+ // Scroll the focused row into view if it is a data row
315
+ if (rowIndex >= 0) {
316
+ this._scrollRowIntoView(rowIndex)
317
+ }
318
+
319
+ // Apply highlight to new position
320
+ this._applyKeyboardFocusToVisibleRows()
321
+
322
+ // Notify observers (CBDataView uses this to sync header highlight)
323
+ this.keyboardFocusDidChange?.(rowIndex, cellIndex)
324
+
325
+ }
326
+
327
+ _clearKeyboardFocus() {
328
+ const previous = this._keyboardFocusedRowIndex
329
+ this._keyboardFocusedRowIndex = undefined
330
+ if (previous !== undefined) {
331
+ this._clearKeyboardFocusOnRow(previous)
332
+ }
333
+ this.keyboardFocusDidChange?.(undefined, this._keyboardFocusedCellIndex)
334
+ }
335
+
336
+ _clearKeyboardFocusOnRow(rowIndex: number) {
337
+ if (rowIndex === -1) {
338
+ // Header — notify via callback; CBDataView handles the header view
339
+ this.keyboardFocusDidChange?.(-1, -1)
340
+ return
341
+ }
342
+ const row = this.visibleRowWithIndex(rowIndex) as any
343
+ if (row && typeof row.setKeyboardFocusedCellIndex === "function") {
344
+ row.setKeyboardFocusedCellIndex(undefined)
345
+ }
346
+ }
347
+
348
+ _applyKeyboardFocusToVisibleRows(clearAll = false) {
349
+ this._visibleRows.forEach((row: any) => {
350
+ if (typeof row.setKeyboardFocusedCellIndex !== "function") {
351
+ return
352
+ }
353
+ if (clearAll || row._UITableViewRowIndex !== this._keyboardFocusedRowIndex) {
354
+ row.setKeyboardFocusedCellIndex(undefined)
355
+ }
356
+ else {
357
+ row.setKeyboardFocusedCellIndex(this._keyboardFocusedCellIndex)
358
+ }
359
+ })
360
+ }
361
+
362
+ _scrollRowIntoView(rowIndex: number) {
363
+ const position = this._rowPositionWithIndex(rowIndex)
364
+ if (!position) {
365
+ return
366
+ }
367
+ const offsetY = this.contentOffset.y
368
+ const visibleHeight = this.bounds.height
369
+ if (position.topY < offsetY) {
370
+ const duration = this.animationDuration
371
+ this.animationDuration = 0
372
+ this.contentOffset = this.contentOffset.pointWithY(position.topY)
373
+ this.animationDuration = duration
374
+ }
375
+ else if (position.bottomY > offsetY + visibleHeight) {
376
+ const duration = this.animationDuration
377
+ this.animationDuration = 0
378
+ this.contentOffset = this.contentOffset.pointWithY(position.bottomY - visibleHeight)
379
+ this.animationDuration = duration
380
+ }
381
+ }
382
+
383
+ /** Expose so CBDataView can call it after loading data. */
384
+ focusRowAtIndex(rowIndex: number, cellIndex: number = 0) {
385
+ this._setKeyboardFocus(rowIndex, cellIndex)
386
+ this._keyboardListenerElement.focus({ preventScroll: true })
110
387
  }
111
388
 
112
389
 
@@ -163,6 +440,16 @@ export class UITableView extends UINativeScrollView {
163
440
  }
164
441
  }
165
442
 
443
+ override wasRemovedFromViewTree() {
444
+ super.wasRemovedFromViewTree()
445
+ this._cleanupViewportScrollListeners()
446
+ if (this._keydownHandler) {
447
+ this.viewHTMLElement.removeEventListener("keydown", this._keydownHandler)
448
+ }
449
+ // Reset so listeners are re-attached if added back to the tree
450
+ this._keyboardListenersAttached = false
451
+ }
452
+
166
453
 
167
454
  loadData() {
168
455
 
@@ -539,6 +826,12 @@ export class UITableView extends UINativeScrollView {
539
826
  const view: UITableViewRowView = this.viewForRowWithIndex(rowIndex)
540
827
  this._visibleRows.push(view)
541
828
  this.addSubview(view)
829
+
830
+ // Ensure the row and all its children stay out of the natural tab order
831
+ view.tabIndex = -1
832
+ view.forEachViewInSubtree(subview => {
833
+ subview.tabIndex = -1
834
+ })
542
835
  })
543
836
 
544
837
  // 3. Clean up DOM
@@ -549,6 +842,9 @@ export class UITableView extends UINativeScrollView {
549
842
  }
550
843
  })
551
844
 
845
+ // 4. Re-apply keyboard focus highlight after rows are re-rendered
846
+ this._applyKeyboardFocusToVisibleRows()
847
+
552
848
  }
553
849
 
554
850
 
@@ -686,13 +982,58 @@ export class UITableView extends UINativeScrollView {
686
982
  // Ensure listeners are set up
687
983
  this._setupViewportScrollAndResizeHandlersIfNeeded()
688
984
 
985
+ // Attach keyboard and pointer listeners now that the element is stable
986
+ // in the DOM. Guarded so repeated wasAddedToViewTree calls (e.g. after
987
+ // navigation returns) don't stack duplicate listeners.
988
+ if (!this._keyboardListenersAttached) {
989
+ this._keyboardListenersAttached = true
990
+ const el = this._keyboardListenerElement
991
+
992
+ el.addEventListener("keydown", this._keydownHandler!)
993
+
994
+ el.addEventListener("pointerdown", (event: PointerEvent) => {
995
+ let target = event.target as HTMLElement | null
996
+ while (target && target !== el) {
997
+ const viewObject = (target as any).UIViewObject as UITableViewRowView | undefined
998
+ if (viewObject?._UITableViewRowIndex !== undefined) {
999
+ el.focus({ preventScroll: true })
1000
+ this._setKeyboardFocus(viewObject._UITableViewRowIndex, this._keyboardFocusedCellIndex)
1001
+ return
1002
+ }
1003
+ target = target.parentElement
1004
+ }
1005
+ el.focus({ preventScroll: true })
1006
+ })
1007
+
1008
+ el.addEventListener("focus", () => {
1009
+ if (this._keyboardFocusedRowIndex === undefined && this.numberOfRows() > 0) {
1010
+ this._setKeyboardFocus(0, this._keyboardFocusedCellIndex)
1011
+ }
1012
+ else if (this._keyboardFocusedRowIndex !== undefined) {
1013
+ this._applyKeyboardFocusToVisibleRows()
1014
+ }
1015
+ })
1016
+
1017
+ el.addEventListener("blur", (event: FocusEvent) => {
1018
+ if (!el.contains(event.relatedTarget as Node)) {
1019
+ this._applyKeyboardFocusToVisibleRows(true)
1020
+ }
1021
+ })
1022
+ }
1023
+
1024
+ // Remove all subviews from the browser's natural tab order.
1025
+ // The container (_keyboardListenerElement) is the single tab stop.
1026
+ // Internal navigation is handled exclusively via arrow keys.
1027
+ this.forEachViewInSubtree(view => {
1028
+ if (view !== this) {
1029
+ view.tabIndex = -1
1030
+ }
1031
+ })
1032
+ // Re-assert tabIndex=0 on the listener element — wasAddedToViewTree fires
1033
+ // on every tree insertion including navigation returns.
1034
+ this._keyboardListenerElement.tabIndex = 0
1035
+
689
1036
  }
690
-
691
- override wasRemovedFromViewTree() {
692
- super.wasRemovedFromViewTree()
693
- this._cleanupViewportScrollListeners()
694
- }
695
-
696
1037
  override setFrame(rectangle: UIRectangle, zIndex?: number, performUncheckedLayout?: boolean) {
697
1038
 
698
1039
  const frame = this.frame
@@ -745,6 +1086,9 @@ export class UITableView extends UINativeScrollView {
745
1086
  row.style.width = "" + (bounds.width - this.sidePadding * 2).integerValue + "px"
746
1087
  row.style.left = "" + this.sidePadding.integerValue + "px"
747
1088
 
1089
+ // Set aria-rowindex (1-based per ARIA spec)
1090
+ row.viewHTMLElement.setAttribute("aria-rowindex", String((row._UITableViewRowIndex ?? 0) + 1))
1091
+
748
1092
  // This is to reorder the elements in the DOM
749
1093
  this.viewHTMLElement.appendChild(row.viewHTMLElement)
750
1094