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.
- package/compiledScripts/UIAutocompleteTextField.js +20 -5
- package/compiledScripts/UIAutocompleteTextField.js.map +2 -2
- package/compiledScripts/UITableView.d.ts +37 -1
- package/compiledScripts/UITableView.js +235 -4
- package/compiledScripts/UITableView.js.map +2 -2
- package/package.json +1 -1
- package/scripts/UIAutocompleteTextField.ts +21 -7
- package/scripts/UITableView.ts +350 -6
|
@@ -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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wCAA2C;AAE3C,sBAAoC;AACpC,yBAA4B;AAC5B,oBAA0D;AAGnD,MAAM,2BAAN,cAAkD,+BAAY;AAAA,EAmBjE,YAAY,WAAoB;AAE5B,UAAM,SAAS;AAnBnB,8BAA8C,CAAC;AAG/C,2BAA2B;AAC3B,4BAA4B;AAC5B,oBAAoB;AAgBhB,SAAK,gBAAgB,KAAK,gBAAgB;AAE1C,SAAK,cAAc,gBAAgB,CAAC,SAAS;AACzC,WAAK,gBAAgB,IAAI;AAAA,IAC7B;
|
|
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
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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.
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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.
|
package/scripts/UITableView.ts
CHANGED
|
@@ -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
|
|