uicore-ts 1.1.232 → 1.1.238

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.
@@ -42,7 +42,9 @@ const _UIAutocompleteTextField = class extends import_UITextField.UITextField {
42
42
  this.controlEventTargetAccumulator.Focus = () => {
43
43
  textBeforeFocus = this.text;
44
44
  itemBeforeFocus = this.selectedItem;
45
- this.text = "";
45
+ if (!this._selectedItem) {
46
+ this.text = "";
47
+ }
46
48
  this.openDropdown();
47
49
  this.textElementView.viewHTMLElement.select();
48
50
  const matchIndex = this._dropdownView.filteredItems.findIndex(
@@ -87,6 +89,16 @@ const _UIAutocompleteTextField = class extends import_UITextField.UITextField {
87
89
  this.closeDropdown();
88
90
  }
89
91
  });
92
+ this.addTargetForControlEvent(import_UIView.UIView.controlEvent.TabDown, (sender, event) => {
93
+ if (this._isDropdownOpen) {
94
+ const highlightedItem = this._dropdownView.highlightedItem;
95
+ if ((0, import_UIObject.IS)(highlightedItem)) {
96
+ this.commitSelection(highlightedItem);
97
+ } else {
98
+ this.closeDropdown();
99
+ }
100
+ }
101
+ });
90
102
  this.addTargetForControlEvent(import_UIView.UIView.controlEvent.EscDown, () => {
91
103
  if (this._isDropdownOpen) {
92
104
  this.closeDropdown();
@@ -118,7 +130,6 @@ const _UIAutocompleteTextField = class extends import_UITextField.UITextField {
118
130
  this.updateValidationVisuals();
119
131
  }
120
132
  commitSelection(item) {
121
- this.blur();
122
133
  this._selectedItem = item;
123
134
  this.text = item.label;
124
135
  this.closeDropdown();
@@ -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 * When YES, the filter text is split on whitespace and all words must appear\n * in the item label (AND logic). When NO (default), the full filter string is\n * matched as a single substring.\n */\n usesMultiWordAndSearch: boolean = NO\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 /**\n * Splits the given lowercase-trimmed filter text into individual words when\n * usesMultiWordAndSearch is YES, or returns it as a single-element array otherwise.\n * Returns an empty array when the input is empty.\n */\n _filterWordsFromText(filterText: string): string[] {\n if (filterText.length === 0) {\n return []\n }\n if (this.usesMultiWordAndSearch) {\n return filterText.split(/\\s+/).filter(word => word.length > 0)\n }\n return [filterText]\n }\n \n \n /**\n * Returns true when the given label (already lowercased) satisfies all filter\n * words \u2014 i.e. every word appears somewhere in the label.\n */\n _labelMatchesFilterWords(label: string, filterWords: string[]): boolean {\n return filterWords.every(word => label.includes(word))\n }\n \n \n /**\n * Returns true when the character immediately before `position` in `label`\n * is a word separator (or the position is at the start of the string).\n * Used to give a bonus to matches that start at a word boundary.\n */\n _isWordBoundary(label: string, position: number): boolean {\n if (position === 0) {\n return YES\n }\n const charBefore = label[position - 1]\n return \" -/\\\\|._,;:()[]\".includes(charBefore)\n }\n \n \n /**\n * Scores a label against the filter words. Lower score = better match.\n *\n * Scoring factors (in priority order):\n * 1. Non-sequential penalty \u2014 words must appear in typed order to avoid\n * a large penalty that pushes them below all sequential matches.\n * 2. Per-word boundary score \u2014 for each filter word, a mid-word match\n * scores worse than a word-boundary match. The sum across all words\n * determines the boundary tier.\n * 3. Position of the first matched word \u2014 within the same boundary tier,\n * earlier appearances rank higher.\n * 4. Total label length \u2014 shorter labels are more specific (tiebreaker).\n *\n * Example: query \"p\u00F5 pu\"\n * \"P\u00F5hjavee puhastusvahendid\" \u2192 \"p\u00F5\" at boundary(0), \"pu\" at boundary(9) \u2192 low boundary score\n * \"p\u00F5randapuhastusvahendid\" \u2192 \"p\u00F5\" at boundary(0), \"pu\" mid-word(7) \u2192 higher boundary score\n * \u2192 \"P\u00F5hjavee puhastusvahendid\" ranks first.\n */\n _scoreLabel(label: string, filterWords: string[]): number {\n \n if (filterWords.length === 0) {\n return label.length\n }\n \n // --- Sequential check ---\n // Scan left-to-right; if all words appear in order record the positions.\n let cursor = 0\n let isSequential = YES\n const sequentialPositions: number[] = []\n for (const word of filterWords) {\n const position = label.indexOf(word, cursor)\n if (position === -1) {\n isSequential = NO\n break\n }\n sequentialPositions.push(position)\n cursor = position + word.length\n }\n \n // --- Boundary score ---\n // For each filter word find its best (leftmost) match and check whether\n // it lands on a word boundary. Non-boundary matches incur a per-word\n // penalty of 1, so the boundary score is 0..filterWords.length.\n let boundaryScore = 0\n for (const word of filterWords) {\n const position = label.indexOf(word)\n if (position !== -1 && !this._isWordBoundary(label, position)) {\n boundaryScore += 1\n }\n }\n \n // Position of the first word's earliest match.\n const firstMatchPosition = label.indexOf(filterWords[0])\n \n // Compose score \u2014 each tier must not overflow into the next:\n // Non-sequential penalty : 10 000 000 (dominates everything)\n // Boundary score : 10 000 (per word, max ~10 words \u2192 100 000 max, safe)\n // First-match position : 100 (labels rarely exceed 200 chars)\n // Label length : 1 (tiebreaker)\n const sequentialPenalty = isSequential ? 0 : 10_000_000\n \n return sequentialPenalty +\n boundaryScore * 10_000 +\n firstMatchPosition * 100 +\n label.length\n \n }\n \n \n updateFilteredItems() {\n \n const rawFilterText = this.text.toLowerCase().trim()\n const filterWords = this._filterWordsFromText(rawFilterText)\n \n let filtered: UIAutocompleteItem<T>[]\n \n if (filterWords.length === 0) {\n filtered = this._autocompleteItems\n }\n else {\n filtered = this._autocompleteItems\n .filter(item => this._labelMatchesFilterWords(item.label.toLowerCase(), filterWords))\n .map((item, originalIndex) => ({ item, originalIndex, score: this._scoreLabel(item.label.toLowerCase(), filterWords) }))\n .sort((a, b) => a.score - b.score || a.originalIndex - b.originalIndex)\n .map(({ item }) => item)\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() === rawFilterText\n \n this._dropdownView.filterWords = filterWords\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._dropdownView.filterWords = []\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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",
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 * When YES, the filter text is split on whitespace and all words must appear\n * in the item label (AND logic). When NO (default), the full filter string is\n * matched as a single substring.\n */\n usesMultiWordAndSearch: boolean = NO\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 \n // Open dropdown on focus.\n // If a selection is already committed we keep the confirmed text visible\n // and do not clear it \u2014 this covers Tab-into-field after a prior selection,\n // as well as returning focus after commitSelection.\n this.controlEventTargetAccumulator.Focus = () => {\n textBeforeFocus = this.text\n itemBeforeFocus = this.selectedItem\n if (!this._selectedItem) {\n this.text = \"\"\n }\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 // Tab: commit highlighted item if dropdown is open, then let focus move\n // naturally to the next item in the managed sequence.\n this.addTargetForControlEvent(UIView.controlEvent.TabDown, (sender, event) => {\n if (this._isDropdownOpen) {\n const highlightedItem = this._dropdownView.highlightedItem\n if (IS(highlightedItem)) {\n this.commitSelection(highlightedItem)\n }\n else {\n this.closeDropdown()\n }\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 // Set the selection state and close the dropdown without blurring.\n // Keeping focus on this view means the next Tab press is handled by\n // our TabDown handler rather than being picked up natively by the browser.\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 /**\n * Splits the given lowercase-trimmed filter text into individual words when\n * usesMultiWordAndSearch is YES, or returns it as a single-element array otherwise.\n * Returns an empty array when the input is empty.\n */\n _filterWordsFromText(filterText: string): string[] {\n if (filterText.length === 0) {\n return []\n }\n if (this.usesMultiWordAndSearch) {\n return filterText.split(/\\s+/).filter(word => word.length > 0)\n }\n return [filterText]\n }\n \n \n /**\n * Returns true when the given label (already lowercased) satisfies all filter\n * words \u2014 i.e. every word appears somewhere in the label.\n */\n _labelMatchesFilterWords(label: string, filterWords: string[]): boolean {\n return filterWords.every(word => label.includes(word))\n }\n \n \n /**\n * Returns true when the character immediately before `position` in `label`\n * is a word separator (or the position is at the start of the string).\n * Used to give a bonus to matches that start at a word boundary.\n */\n _isWordBoundary(label: string, position: number): boolean {\n if (position === 0) {\n return YES\n }\n const charBefore = label[position - 1]\n return \" -/\\\\|._,;:()[]\".includes(charBefore)\n }\n \n \n /**\n * Scores a label against the filter words. Lower score = better match.\n *\n * Scoring factors (in priority order):\n * 1. Non-sequential penalty \u2014 words must appear in typed order to avoid\n * a large penalty that pushes them below all sequential matches.\n * 2. Per-word boundary score \u2014 for each filter word, a mid-word match\n * scores worse than a word-boundary match. The sum across all words\n * determines the boundary tier.\n * 3. Position of the first matched word \u2014 within the same boundary tier,\n * earlier appearances rank higher.\n * 4. Total label length \u2014 shorter labels are more specific (tiebreaker).\n *\n * Example: query \"p\u00F5 pu\"\n * \"P\u00F5hjavee puhastusvahendid\" \u2192 \"p\u00F5\" at boundary(0), \"pu\" at boundary(9) \u2192 low boundary score\n * \"p\u00F5randapuhastusvahendid\" \u2192 \"p\u00F5\" at boundary(0), \"pu\" mid-word(7) \u2192 higher boundary score\n * \u2192 \"P\u00F5hjavee puhastusvahendid\" ranks first.\n */\n _scoreLabel(label: string, filterWords: string[]): number {\n \n if (filterWords.length === 0) {\n return label.length\n }\n \n // --- Sequential check ---\n // Scan left-to-right; if all words appear in order record the positions.\n let cursor = 0\n let isSequential = YES\n const sequentialPositions: number[] = []\n for (const word of filterWords) {\n const position = label.indexOf(word, cursor)\n if (position === -1) {\n isSequential = NO\n break\n }\n sequentialPositions.push(position)\n cursor = position + word.length\n }\n \n // --- Boundary score ---\n // For each filter word find its best (leftmost) match and check whether\n // it lands on a word boundary. Non-boundary matches incur a per-word\n // penalty of 1, so the boundary score is 0..filterWords.length.\n let boundaryScore = 0\n for (const word of filterWords) {\n const position = label.indexOf(word)\n if (position !== -1 && !this._isWordBoundary(label, position)) {\n boundaryScore += 1\n }\n }\n \n // Position of the first word's earliest match.\n const firstMatchPosition = label.indexOf(filterWords[0])\n \n // Compose score \u2014 each tier must not overflow into the next:\n // Non-sequential penalty : 10 000 000 (dominates everything)\n // Boundary score : 10 000 (per word, max ~10 words \u2192 100 000 max, safe)\n // First-match position : 100 (labels rarely exceed 200 chars)\n // Label length : 1 (tiebreaker)\n const sequentialPenalty = isSequential ? 0 : 10_000_000\n \n return sequentialPenalty +\n boundaryScore * 10_000 +\n firstMatchPosition * 100 +\n label.length\n \n }\n \n \n updateFilteredItems() {\n \n const rawFilterText = this.text.toLowerCase().trim()\n const filterWords = this._filterWordsFromText(rawFilterText)\n \n let filtered: UIAutocompleteItem<T>[]\n \n if (filterWords.length === 0) {\n filtered = this._autocompleteItems\n }\n else {\n filtered = this._autocompleteItems\n .filter(item => this._labelMatchesFilterWords(item.label.toLowerCase(), filterWords))\n .map((item, originalIndex) => ({ item, originalIndex, score: this._scoreLabel(item.label.toLowerCase(), filterWords) }))\n .sort((a, b) => a.score - b.score || a.originalIndex - b.originalIndex)\n .map(({ item }) => item)\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() === rawFilterText\n \n this._dropdownView.filterWords = filterWords\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._dropdownView.filterWords = []\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"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -192,9 +192,13 @@ class UIBaseButton extends import_UIView.UIView {
192
192
  didReceiveBroadcastEvent(event) {
193
193
  super.didReceiveBroadcastEvent(event);
194
194
  if (event.name == import_UIView.UIView.broadcastEventName.PageDidScroll || event.name == import_UIView.UIView.broadcastEventName.AddedToViewTree) {
195
- this.hovered = import_UIObject.NO;
196
- this.highlighted = import_UIObject.NO;
197
- this.updateContentForCurrentState();
195
+ const wasHovered = this._hovered;
196
+ const wasHighlighted = this._highlighted;
197
+ this._hovered = import_UIObject.NO;
198
+ this._highlighted = import_UIObject.NO;
199
+ if (wasHovered || wasHighlighted) {
200
+ this.updateContentForCurrentState();
201
+ }
198
202
  }
199
203
  }
200
204
  toggleSelectedState() {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../scripts/UIBaseButton.ts"],
4
- "sourcesContent": ["import { UIColor } from \"./UIColor\"\nimport { IS, nil, NO, YES } from \"./UIObject\"\nimport { UIView, UIViewBroadcastEvent } from \"./UIView\"\n\n\nexport class UIBaseButton extends UIView {\n \n _selected: boolean = NO\n _highlighted: boolean = NO\n \n override _isPointerInside: boolean\n \n \n _isToggleable: boolean = NO\n _hovered?: boolean\n _focused?: boolean\n \n \n constructor(elementID?: string, elementType?: string) {\n \n super(elementID, undefined, elementType)\n \n // Instance variables\n \n \n this._isPointerInside = NO\n \n \n const setHovered = () => {\n this.hovered = YES\n }\n this.addTargetForControlEvent(UIView.controlEvent.PointerHover, setHovered)\n \n const setNotHovered = () => {\n \n this.hovered = NO\n \n }\n \n this.addTargetForControlEvents([\n UIView.controlEvent.PointerLeave, UIView.controlEvent.PointerCancel, UIView.controlEvent.MultipleTouches\n ], setNotHovered)\n \n \n let highlightingTime: number\n const setHighlighted = () => {\n this.highlighted = YES\n highlightingTime = Date.now()\n }\n this.addTargetForControlEvent(UIView.controlEvent.PointerDown, setHighlighted)\n this.addTargetForControlEvent(UIView.controlEvent.PointerEnter, setHighlighted)\n \n const setNotHighlighted = () => {\n this.highlighted = NO\n }\n const setNotHighlightedWithMinimumDuration = () => {\n const minimumDurationInMilliseconds = 50\n const elapsedTime = Date.now() - highlightingTime\n if (minimumDurationInMilliseconds < elapsedTime) {\n this.highlighted = NO\n }\n else {\n setTimeout(() => {\n this.highlighted = NO\n }, minimumDurationInMilliseconds - elapsedTime)\n }\n }\n this.addTargetForControlEvents([\n UIView.controlEvent.PointerLeave, UIView.controlEvent.PointerCancel, UIView.controlEvent.MultipleTouches\n ], setNotHighlighted)\n this.addTargetForControlEvent(UIView.controlEvent.PointerUp, setNotHighlightedWithMinimumDuration)\n \n // Handle enter key press\n this.addTargetForControlEvent(UIView.controlEvent.EnterDown, () => {\n \n setHighlighted()\n setNotHighlightedWithMinimumDuration()\n \n })\n \n \n this.addTargetForControlEvent(\n UIView.controlEvent.Focus,\n (sender: UIView, event: Event) => {\n \n this.focused = YES\n \n }\n )\n \n this.addTargetForControlEvent(\n UIView.controlEvent.Blur,\n (sender: UIView, event: Event) => {\n \n this.focused = NO\n \n }\n )\n \n \n this.pausesPointerEvents = YES\n this.tabIndex = 1\n \n this.style.cursor = \"pointer\"\n \n //this.style.outline = \"none\";\n \n \n this.nativeSelectionEnabled = NO\n \n \n this.addTargetForControlEvents([\n UIView.controlEvent.EnterDown, UIView.controlEvent.PointerUpInside\n ], () => {\n \n if (this.isToggleable) {\n \n this.toggleSelectedState()\n \n }\n \n })\n \n }\n \n public set hovered(hovered: boolean) {\n this._hovered = hovered\n this.updateContentForCurrentState()\n }\n \n public get hovered(): boolean {\n return this._hovered ?? NO\n }\n \n public set highlighted(highlighted: boolean) {\n this._highlighted = highlighted\n this.updateContentForCurrentState()\n }\n \n public get highlighted(): boolean {\n return this._highlighted\n }\n \n public set focused(focused: boolean) {\n this._focused = focused\n if (focused) {\n this.focus()\n }\n else {\n this.blur()\n }\n this.updateContentForCurrentState()\n }\n \n public get focused(): boolean {\n return this._focused ?? NO\n }\n \n public set selected(selected: boolean) {\n this._selected = selected\n this.updateContentForCurrentState()\n }\n \n public get selected(): boolean {\n return this._selected\n }\n \n \n updateContentForCurrentState() {\n \n let updateFunction: Function = this.updateContentForNormalState\n if (this.selected && this.highlighted) {\n updateFunction = this.updateContentForSelectedAndHighlightedState\n }\n else if (this.selected) {\n updateFunction = this.updateContentForSelectedState\n }\n else if (this.focused) {\n updateFunction = this.updateContentForFocusedState\n }\n else if (this.highlighted) {\n updateFunction = this.updateContentForHighlightedState\n }\n else if (this.hovered) {\n updateFunction = this.updateContentForHoveredState\n }\n \n if (!IS(updateFunction)) {\n this.backgroundColor = UIColor.nilColor\n }\n else {\n updateFunction.call(this)\n }\n \n }\n \n updateContentForNormalState() {\n \n \n }\n \n updateContentForHoveredState() {\n \n this.updateContentForNormalState()\n \n }\n \n updateContentForFocusedState() {\n \n this.updateContentForHoveredState()\n \n }\n \n updateContentForHighlightedState() {\n \n \n }\n \n updateContentForSelectedState() {\n \n \n }\n \n updateContentForSelectedAndHighlightedState() {\n \n this.updateContentForSelectedState()\n \n }\n \n \n override set enabled(enabled: boolean) {\n super.enabled = enabled\n this.updateContentForCurrentEnabledState()\n }\n \n override get enabled() {\n return super.enabled\n }\n \n override updateContentForCurrentEnabledState() {\n \n if (this.enabled) {\n this.alpha = 1\n }\n else {\n this.alpha = 0.5\n }\n \n this.userInteractionEnabled = this.enabled\n \n }\n \n \n override addStyleClass(styleClassName: string) {\n \n super.addStyleClass(styleClassName)\n \n if (this.styleClassName != styleClassName) {\n \n this.updateContentForCurrentState.call(this)\n \n }\n \n }\n \n \n override didReceiveBroadcastEvent(event: UIViewBroadcastEvent) {\n \n super.didReceiveBroadcastEvent(event)\n \n if (event.name == UIView.broadcastEventName.PageDidScroll || event.name ==\n UIView.broadcastEventName.AddedToViewTree) {\n \n this.hovered = NO\n \n this.highlighted = NO\n \n this.updateContentForCurrentState()\n \n }\n \n \n }\n \n \n toggleSelectedState() {\n \n \n this.selected = !this.selected\n \n \n }\n \n set isToggleable(isToggleable: boolean) {\n \n this._isToggleable = isToggleable\n \n }\n \n get isToggleable() {\n \n return this._isToggleable\n \n }\n \n \n override layoutSubviews() {\n \n super.layoutSubviews()\n \n const bounds = this.bounds\n \n \n }\n \n \n override sendControlEventForKey(eventKey: string, nativeEvent: Event) {\n \n if (eventKey == UIView.controlEvent.PointerUpInside && !this.highlighted) {\n \n // Do not send the event in this case\n //super.sendControlEventForKey(eventKey, nativeEvent);\n \n const asd = 1\n \n }\n else {\n \n super.sendControlEventForKey(eventKey, nativeEvent)\n \n }\n \n }\n \n \n static getEventCoordinatesInDocument(touchOrMouseEvent: any) {\n // http://www.quirksmode.org/js/events_properties.html\n var posx = 0\n var posy = 0\n var e = touchOrMouseEvent\n if (!e) {\n e = window.event\n }\n if (e.pageX || e.pageY) {\n posx = e.pageX\n posy = e.pageY\n }\n else if (e.clientX || e.clientY) {\n posx = e.clientX + document.body.scrollLeft\n + document.documentElement.scrollLeft\n posy = e.clientY + document.body.scrollTop\n + document.documentElement.scrollTop\n }\n // posx and posy contain the mouse position relative to the document\n \n const coordinates = { \"x\": posx, \"y\": posy }\n \n return coordinates\n \n }\n \n \n static getElementPositionInDocument(el: { tagName: string; offsetLeft: number; scrollLeft: number; clientLeft: number; offsetTop: number; scrollTop: number; clientTop: number; offsetParent: any }) {\n //https://www.kirupa.com/html5/getting_mouse_click_position.htm\n var xPosition = 0\n var yPosition = 0\n \n while (el) {\n if (el.tagName == \"BODY\") {\n \n // Coordinates in document are coordinates in body, therefore subtracting the scroll position of the body is not needed\n \n // // deal with browser quirks with body/window/document and page scroll\n // var xScrollPos = el.scrollLeft || document.documentElement.scrollLeft;\n // var yScrollPos = el.scrollTop || document.documentElement.scrollTop;\n //\n // xPosition += (el.offsetLeft - xScrollPos + el.clientLeft);\n // yPosition += (el.offsetTop - yScrollPos + el.clientTop);\n }\n else {\n xPosition += (el.offsetLeft - el.scrollLeft + el.clientLeft)\n yPosition += (el.offsetTop - el.scrollTop + el.clientTop)\n }\n \n el = el.offsetParent\n }\n return {\n x: xPosition,\n y: yPosition\n }\n }\n \n static convertCoordinatesFromDocumentToElement(x: number, y: number, element: any) {\n const elementPositionInDocument = this.getElementPositionInDocument(element)\n const coordinatesInElement = { \"x\": x - elementPositionInDocument.x, \"y\": y - elementPositionInDocument.y }\n return coordinatesInElement\n }\n \n static getEventCoordinatesInElement(touchOrMouseEvent: any, element: any) {\n const coordinatesInDocument = this.getEventCoordinatesInDocument(touchOrMouseEvent)\n const coordinatesInElement = this.convertCoordinatesFromDocumentToElement(\n coordinatesInDocument.x,\n coordinatesInDocument.y,\n element\n )\n return coordinatesInElement\n }\n \n \n}\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { UIColor } from \"./UIColor\"\nimport { IS, nil, NO, YES } from \"./UIObject\"\nimport { UIView, UIViewBroadcastEvent } from \"./UIView\"\n\n\nexport class UIBaseButton extends UIView {\n \n _selected: boolean = NO\n _highlighted: boolean = NO\n \n override _isPointerInside: boolean\n \n \n _isToggleable: boolean = NO\n _hovered?: boolean\n _focused?: boolean\n \n \n constructor(elementID?: string, elementType?: string) {\n \n super(elementID, undefined, elementType)\n \n // Instance variables\n \n \n this._isPointerInside = NO\n \n \n const setHovered = () => {\n this.hovered = YES\n }\n this.addTargetForControlEvent(UIView.controlEvent.PointerHover, setHovered)\n \n const setNotHovered = () => {\n \n this.hovered = NO\n \n }\n \n this.addTargetForControlEvents([\n UIView.controlEvent.PointerLeave, UIView.controlEvent.PointerCancel, UIView.controlEvent.MultipleTouches\n ], setNotHovered)\n \n \n let highlightingTime: number\n const setHighlighted = () => {\n this.highlighted = YES\n highlightingTime = Date.now()\n }\n this.addTargetForControlEvent(UIView.controlEvent.PointerDown, setHighlighted)\n this.addTargetForControlEvent(UIView.controlEvent.PointerEnter, setHighlighted)\n \n const setNotHighlighted = () => {\n this.highlighted = NO\n }\n const setNotHighlightedWithMinimumDuration = () => {\n const minimumDurationInMilliseconds = 50\n const elapsedTime = Date.now() - highlightingTime\n if (minimumDurationInMilliseconds < elapsedTime) {\n this.highlighted = NO\n }\n else {\n setTimeout(() => {\n this.highlighted = NO\n }, minimumDurationInMilliseconds - elapsedTime)\n }\n }\n this.addTargetForControlEvents([\n UIView.controlEvent.PointerLeave, UIView.controlEvent.PointerCancel, UIView.controlEvent.MultipleTouches\n ], setNotHighlighted)\n this.addTargetForControlEvent(UIView.controlEvent.PointerUp, setNotHighlightedWithMinimumDuration)\n \n // Handle enter key press\n this.addTargetForControlEvent(UIView.controlEvent.EnterDown, () => {\n \n setHighlighted()\n setNotHighlightedWithMinimumDuration()\n \n })\n \n \n this.addTargetForControlEvent(\n UIView.controlEvent.Focus,\n (sender: UIView, event: Event) => {\n \n this.focused = YES\n \n }\n )\n \n this.addTargetForControlEvent(\n UIView.controlEvent.Blur,\n (sender: UIView, event: Event) => {\n \n this.focused = NO\n \n }\n )\n \n \n this.pausesPointerEvents = YES\n this.tabIndex = 1\n \n this.style.cursor = \"pointer\"\n \n //this.style.outline = \"none\";\n \n \n this.nativeSelectionEnabled = NO\n \n \n this.addTargetForControlEvents([\n UIView.controlEvent.EnterDown, UIView.controlEvent.PointerUpInside\n ], () => {\n \n if (this.isToggleable) {\n \n this.toggleSelectedState()\n \n }\n \n })\n \n }\n \n public set hovered(hovered: boolean) {\n this._hovered = hovered\n this.updateContentForCurrentState()\n }\n \n public get hovered(): boolean {\n return this._hovered ?? NO\n }\n \n public set highlighted(highlighted: boolean) {\n this._highlighted = highlighted\n this.updateContentForCurrentState()\n }\n \n public get highlighted(): boolean {\n return this._highlighted\n }\n \n public set focused(focused: boolean) {\n this._focused = focused\n if (focused) {\n this.focus()\n }\n else {\n this.blur()\n }\n this.updateContentForCurrentState()\n }\n \n public get focused(): boolean {\n return this._focused ?? NO\n }\n \n public set selected(selected: boolean) {\n this._selected = selected\n this.updateContentForCurrentState()\n }\n \n public get selected(): boolean {\n return this._selected\n }\n \n \n updateContentForCurrentState() {\n \n let updateFunction: Function = this.updateContentForNormalState\n if (this.selected && this.highlighted) {\n updateFunction = this.updateContentForSelectedAndHighlightedState\n }\n else if (this.selected) {\n updateFunction = this.updateContentForSelectedState\n }\n else if (this.focused) {\n updateFunction = this.updateContentForFocusedState\n }\n else if (this.highlighted) {\n updateFunction = this.updateContentForHighlightedState\n }\n else if (this.hovered) {\n updateFunction = this.updateContentForHoveredState\n }\n \n if (!IS(updateFunction)) {\n this.backgroundColor = UIColor.nilColor\n }\n else {\n updateFunction.call(this)\n }\n \n }\n \n updateContentForNormalState() {\n \n \n }\n \n updateContentForHoveredState() {\n \n this.updateContentForNormalState()\n \n }\n \n updateContentForFocusedState() {\n \n this.updateContentForHoveredState()\n \n }\n \n updateContentForHighlightedState() {\n \n \n }\n \n updateContentForSelectedState() {\n \n \n }\n \n updateContentForSelectedAndHighlightedState() {\n \n this.updateContentForSelectedState()\n \n }\n \n \n override set enabled(enabled: boolean) {\n super.enabled = enabled\n this.updateContentForCurrentEnabledState()\n }\n \n override get enabled() {\n return super.enabled\n }\n \n override updateContentForCurrentEnabledState() {\n \n if (this.enabled) {\n this.alpha = 1\n }\n else {\n this.alpha = 0.5\n }\n \n this.userInteractionEnabled = this.enabled\n \n }\n \n \n override addStyleClass(styleClassName: string) {\n \n super.addStyleClass(styleClassName)\n \n if (this.styleClassName != styleClassName) {\n \n this.updateContentForCurrentState.call(this)\n \n }\n \n }\n \n \n override didReceiveBroadcastEvent(event: UIViewBroadcastEvent) {\n \n super.didReceiveBroadcastEvent(event)\n \n if (event.name == UIView.broadcastEventName.PageDidScroll || event.name ==\n UIView.broadcastEventName.AddedToViewTree) {\n \n const wasHovered = this._hovered\n const wasHighlighted = this._highlighted\n \n this._hovered = NO\n this._highlighted = NO\n \n if (wasHovered || wasHighlighted) {\n this.updateContentForCurrentState()\n }\n \n }\n \n \n }\n \n \n toggleSelectedState() {\n \n \n this.selected = !this.selected\n \n \n }\n \n set isToggleable(isToggleable: boolean) {\n \n this._isToggleable = isToggleable\n \n }\n \n get isToggleable() {\n \n return this._isToggleable\n \n }\n \n \n override layoutSubviews() {\n \n super.layoutSubviews()\n \n const bounds = this.bounds\n \n \n }\n \n \n override sendControlEventForKey(eventKey: string, nativeEvent: Event) {\n \n if (eventKey == UIView.controlEvent.PointerUpInside && !this.highlighted) {\n \n // Do not send the event in this case\n //super.sendControlEventForKey(eventKey, nativeEvent);\n \n const asd = 1\n \n }\n else {\n \n super.sendControlEventForKey(eventKey, nativeEvent)\n \n }\n \n }\n \n \n static getEventCoordinatesInDocument(touchOrMouseEvent: any) {\n // http://www.quirksmode.org/js/events_properties.html\n var posx = 0\n var posy = 0\n var e = touchOrMouseEvent\n if (!e) {\n e = window.event\n }\n if (e.pageX || e.pageY) {\n posx = e.pageX\n posy = e.pageY\n }\n else if (e.clientX || e.clientY) {\n posx = e.clientX + document.body.scrollLeft\n + document.documentElement.scrollLeft\n posy = e.clientY + document.body.scrollTop\n + document.documentElement.scrollTop\n }\n // posx and posy contain the mouse position relative to the document\n \n const coordinates = { \"x\": posx, \"y\": posy }\n \n return coordinates\n \n }\n \n \n static getElementPositionInDocument(el: { tagName: string; offsetLeft: number; scrollLeft: number; clientLeft: number; offsetTop: number; scrollTop: number; clientTop: number; offsetParent: any }) {\n //https://www.kirupa.com/html5/getting_mouse_click_position.htm\n var xPosition = 0\n var yPosition = 0\n \n while (el) {\n if (el.tagName == \"BODY\") {\n \n // Coordinates in document are coordinates in body, therefore subtracting the scroll position of the body is not needed\n \n // // deal with browser quirks with body/window/document and page scroll\n // var xScrollPos = el.scrollLeft || document.documentElement.scrollLeft;\n // var yScrollPos = el.scrollTop || document.documentElement.scrollTop;\n //\n // xPosition += (el.offsetLeft - xScrollPos + el.clientLeft);\n // yPosition += (el.offsetTop - yScrollPos + el.clientTop);\n }\n else {\n xPosition += (el.offsetLeft - el.scrollLeft + el.clientLeft)\n yPosition += (el.offsetTop - el.scrollTop + el.clientTop)\n }\n \n el = el.offsetParent\n }\n return {\n x: xPosition,\n y: yPosition\n }\n }\n \n static convertCoordinatesFromDocumentToElement(x: number, y: number, element: any) {\n const elementPositionInDocument = this.getElementPositionInDocument(element)\n const coordinatesInElement = { \"x\": x - elementPositionInDocument.x, \"y\": y - elementPositionInDocument.y }\n return coordinatesInElement\n }\n \n static getEventCoordinatesInElement(touchOrMouseEvent: any, element: any) {\n const coordinatesInDocument = this.getEventCoordinatesInDocument(touchOrMouseEvent)\n const coordinatesInElement = this.convertCoordinatesFromDocumentToElement(\n coordinatesInDocument.x,\n coordinatesInDocument.y,\n element\n )\n return coordinatesInElement\n }\n \n \n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../scripts/UIButton.ts"],
4
- "sourcesContent": ["import { UIBaseButton } from \"./UIBaseButton\"\nimport { UIColor } from \"./UIColor\"\nimport { UIImageView } from \"./UIImageView\"\nimport { IS, IS_NOT, IS_NOT_NIL, nil, NO, ValueOf, YES } from \"./UIObject\"\nimport { UIRectangle } from \"./UIRectangle\"\nimport { UITextView } from \"./UITextView\"\n\n\nexport interface UIButtonColorSpecifier {\n \n titleLabel: UIButtonElementColorSpecifier;\n background: UIButtonElementColorSpecifier;\n \n}\n\n\nexport interface UIButtonElementColorSpecifier {\n \n normal: UIColor;\n hovered?: UIColor;\n highlighted: UIColor;\n focused?: UIColor;\n selected: UIColor;\n selectedAndHighlighted?: UIColor;\n \n}\n\n\nexport class UIButton extends UIBaseButton {\n \n _contentPadding = 0\n _titleLabel: UITextView = nil\n _imageView: UIImageView\n \n usesAutomaticTitleFontSize = NO\n minAutomaticFontSize?: number\n maxAutomaticFontSize?: number = 25\n \n colors: UIButtonColorSpecifier = {\n \n titleLabel: {\n \n normal: UIColor.whiteColor,\n highlighted: UIColor.whiteColor,\n selected: UIColor.whiteColor\n \n },\n \n background: {\n \n normal: UIColor.blueColor,\n highlighted: UIColor.greenColor,\n selected: UIColor.redColor\n \n }\n \n }\n \n \n constructor(\n elementID?: string,\n elementType?: string,\n titleType: string | ValueOf<typeof UITextView.type> = UITextView.type.span\n ) {\n \n super(elementID, elementType)\n \n // Instance variables\n \n this._imageView = new UIImageView(this.elementID + \"ImageView\")\n this._imageView.hidden = YES\n this.addSubview(this.imageView)\n \n this.imageView.fillMode = UIImageView.fillMode.aspectFitIfLarger\n \n \n if (IS_NOT_NIL(titleType)) {\n \n this._titleLabel = new UITextView(this.elementID + \"TitleLabel\", titleType)\n this.titleLabel!.style.whiteSpace = \"nowrap\"\n this.addSubview(this.titleLabel!)\n \n this.titleLabel!.userInteractionEnabled = NO\n \n }\n \n this.contentPadding = 10\n \n this.imageView.userInteractionEnabled = NO\n if (this.titleLabel) {\n this.titleLabel.textAlignment = UITextView.textAlignment.center\n this.titleLabel.nativeSelectionEnabled = NO\n }\n \n }\n \n \n get contentPadding() {\n return this._contentPadding.integerValue\n }\n \n set contentPadding(contentPadding) {\n this._contentPadding = contentPadding\n this.setNeedsLayout()\n }\n \n \n public override set hovered(hovered: boolean) {\n this._hovered = hovered\n this.updateContentForCurrentState()\n }\n \n public override get hovered(): boolean {\n return this._hovered ?? NO\n }\n \n public override set highlighted(highlighted: boolean) {\n this._highlighted = highlighted\n this.updateContentForCurrentState()\n }\n \n public override get highlighted(): boolean {\n return this._highlighted\n }\n \n public override set focused(focused: boolean) {\n this._focused = focused\n if (focused) {\n this.focus()\n }\n else {\n this.blur()\n }\n this.updateContentForCurrentState()\n }\n \n public override get focused(): boolean {\n return this._focused ?? NO\n }\n \n public override set selected(selected: boolean) {\n this._selected = selected\n this.updateContentForCurrentState()\n }\n \n public override get selected(): boolean {\n return this._selected\n }\n \n \n override updateContentForCurrentState() {\n \n let updateFunction: Function = this.updateContentForNormalState\n if (this.selected && this.highlighted) {\n updateFunction = this.updateContentForSelectedAndHighlightedState\n }\n else if (this.selected) {\n updateFunction = this.updateContentForSelectedState\n }\n else if (this.focused) {\n updateFunction = this.updateContentForFocusedState\n }\n else if (this.highlighted) {\n updateFunction = this.updateContentForHighlightedState\n }\n else if (this.hovered) {\n updateFunction = this.updateContentForHoveredState\n }\n \n if (!IS(updateFunction)) {\n if (this.titleLabel) {\n this.titleLabel.textColor = UIColor.nilColor\n }\n this.backgroundColor = UIColor.nilColor\n }\n else {\n updateFunction.call(this)\n }\n \n this.updateContentForCurrentEnabledState()\n \n }\n \n override updateContentForNormalState() {\n \n this.backgroundColor = this.colors.background.normal\n if (this.titleLabel) {\n this.titleLabel.textColor = this.colors.titleLabel.normal\n }\n \n }\n \n override updateContentForHoveredState() {\n \n this.updateContentForNormalState()\n \n if (this.colors.background.hovered) {\n this.backgroundColor = this.colors.background.hovered\n }\n \n if (this.colors.titleLabel.hovered && this.titleLabel) {\n this.titleLabel.textColor = this.colors.titleLabel.hovered\n }\n \n }\n \n override updateContentForFocusedState() {\n \n this.updateContentForHoveredState()\n \n if (this.colors.background.focused) {\n this.backgroundColor = this.colors.background.focused\n }\n \n if (this.colors.titleLabel.focused && this.titleLabel) {\n this.titleLabel.textColor = this.colors.titleLabel.focused\n }\n \n }\n \n override updateContentForHighlightedState() {\n \n this.backgroundColor = this.colors.background.highlighted\n if (this.titleLabel) {\n this.titleLabel.textColor = this.colors.titleLabel.highlighted\n }\n \n }\n \n override updateContentForSelectedState() {\n \n this.backgroundColor = this.colors.background.selected\n if (this.titleLabel) {\n this.titleLabel.textColor = this.colors.titleLabel.selected\n }\n \n }\n \n override updateContentForSelectedAndHighlightedState() {\n \n this.updateContentForSelectedState()\n \n if (this.colors.background.selectedAndHighlighted) {\n this.backgroundColor = this.colors.background.selectedAndHighlighted\n }\n \n if (this.colors.titleLabel.selectedAndHighlighted && this.titleLabel) {\n this.titleLabel.textColor = this.colors.titleLabel.selectedAndHighlighted\n }\n \n }\n \n \n override set enabled(enabled: boolean) {\n \n // @ts-ignore\n super.enabled = enabled\n \n this.updateContentForCurrentState()\n \n }\n \n override get enabled() {\n \n // @ts-ignore\n return super.enabled\n \n }\n \n override updateContentForCurrentEnabledState() {\n \n if (this.enabled) {\n this.alpha = 1\n }\n else {\n this.alpha = 0.5\n }\n \n this.userInteractionEnabled = this.enabled\n \n }\n \n \n override addStyleClass(styleClassName: string) {\n \n super.addStyleClass(styleClassName)\n \n if (this.styleClassName != styleClassName) {\n \n this.updateContentForCurrentState.call(this)\n \n }\n \n }\n \n \n get titleLabel(): UITextView {\n return this._titleLabel\n }\n \n get imageView() {\n \n return this._imageView\n \n }\n \n \n override layoutSubviews() {\n \n super.layoutSubviews()\n \n let bounds = this.bounds\n \n this.hoverText = this.hoverText ?? this.titleLabel?.text ?? \"\"\n \n // Image only if text is not present\n if (IS_NOT(this.imageView.hidden) && !IS(this.titleLabel?.text)) {\n \n this.imageView.frame = bounds\n \n }\n \n // Text only if image is not present\n if (IS(this.imageView.hidden) && this.titleLabel?.text) {\n \n this.titleLabel.style.left = this.contentPadding + \"px\"\n this.titleLabel.style.right = this.contentPadding + \"px\"\n // this.titleLabel.style.marginLeft = \"\"\n // this.titleLabel.style.right = this.contentPadding\n this.titleLabel.style.bottom = \"0px\"\n this.titleLabel.style.top = \"0px\"\n this.titleLabel.frame = new UIRectangle(nil, nil, nil, nil)\n \n if (this.usesAutomaticTitleFontSize) {\n \n const hidden = this.titleLabel.hidden\n \n this.titleLabel.hidden = YES\n \n this.titleLabel.fontSize = 15\n \n this.titleLabel.fontSize = UITextView.automaticallyCalculatedFontSize(\n new UIRectangle(\n 0,\n 0,\n this.bounds.height,\n this.titleLabel.viewHTMLElement.offsetWidth\n ),\n this.titleLabel.intrinsicContentSize(),\n this.titleLabel.fontSize,\n this.minAutomaticFontSize,\n this.maxAutomaticFontSize\n )\n \n this.titleLabel.hidden = hidden\n \n }\n \n \n }\n \n // Image and text both present\n if (IS_NOT(this.imageView.hidden) && this.titleLabel?.text) {\n \n //const imageShareOfWidth = 0.25\n \n bounds = bounds.rectangleWithInset(this.contentPadding)\n \n const imageFrame = bounds.copy()\n imageFrame.width = bounds.height - this.contentPadding * 0.5\n this.imageView.frame = imageFrame\n \n this.titleLabel.style.left = imageFrame.max.x + this.contentPadding + \"px\"\n this.titleLabel.style.right = this.contentPadding + \"px\"\n this.titleLabel.style.bottom = \"0px\"\n this.titleLabel.style.top = \"0px\"\n \n if (this.usesAutomaticTitleFontSize) {\n \n const hidden = this.titleLabel.hidden\n \n this.titleLabel.hidden = YES\n \n this.titleLabel.fontSize = 15\n \n this.titleLabel.fontSize = UITextView.automaticallyCalculatedFontSize(\n new UIRectangle(\n 0,\n 0,\n this.bounds.height,\n this.titleLabel.viewHTMLElement.offsetWidth\n ),\n this.titleLabel.intrinsicContentSize(),\n this.titleLabel.fontSize,\n this.minAutomaticFontSize,\n this.maxAutomaticFontSize\n )\n \n this.titleLabel.hidden = hidden\n \n }\n \n }\n \n this.applyClassesAndStyles()\n \n }\n \n override initViewStyleSelectors() {\n \n this.initStyleSelector(\".\" + this.styleClassName, \"background-color: lightblue;\")\n \n // var selectorWithoutImage = \".\" + this.styleClassName + \" .\" + this.imageView.styleClassName + \" + .\" +\n // this.titleLabel.styleClassName;\n \n // this.initStyleSelector(\n // selectorWithoutImage,\n // \"left: \" + this.contentPadding + \";\" +\n // \"right: \" + this.contentPadding + \";\" +\n // \"top: 50%;\" +\n // \"transform: translateY(-50%);\");\n \n }\n \n \n}\n\n"],
4
+ "sourcesContent": ["import { UIBaseButton } from \"./UIBaseButton\"\nimport { UIColor } from \"./UIColor\"\nimport { UIImageView } from \"./UIImageView\"\nimport { IS, IS_NOT, IS_NOT_NIL, nil, NO, ValueOf, YES } from \"./UIObject\"\nimport { UIRectangle } from \"./UIRectangle\"\nimport { UITextView } from \"./UITextView\"\n\n\nexport interface UIButtonColorSpecifier {\n \n titleLabel: UIButtonElementColorSpecifier;\n background: UIButtonElementColorSpecifier;\n \n}\n\n\nexport interface UIButtonElementColorSpecifier {\n \n normal: UIColor;\n hovered?: UIColor;\n highlighted: UIColor;\n focused?: UIColor;\n selected: UIColor;\n selectedAndHighlighted?: UIColor;\n \n}\n\n\nexport class UIButton extends UIBaseButton {\n \n _contentPadding = 0\n _titleLabel: UITextView = nil\n _imageView: UIImageView\n \n usesAutomaticTitleFontSize = NO\n minAutomaticFontSize?: number\n maxAutomaticFontSize?: number = 25\n \n colors: UIButtonColorSpecifier = {\n \n titleLabel: {\n \n normal: UIColor.whiteColor,\n highlighted: UIColor.whiteColor,\n selected: UIColor.whiteColor\n \n },\n \n background: {\n \n normal: UIColor.blueColor,\n highlighted: UIColor.greenColor,\n selected: UIColor.redColor\n \n }\n \n }\n \n \n constructor(\n elementID?: string,\n elementType?: string,\n titleType: string | ValueOf<typeof UITextView.type> = UITextView.type.span\n ) {\n \n super(elementID, elementType)\n \n // Instance variables\n \n this._imageView = new UIImageView(this.elementID + \"ImageView\")\n this._imageView.hidden = YES\n this.addSubview(this.imageView)\n \n this.imageView.fillMode = UIImageView.fillMode.aspectFitIfLarger\n \n \n if (IS_NOT_NIL(titleType)) {\n \n this._titleLabel = new UITextView(this.elementID + \"TitleLabel\", titleType)\n this.titleLabel!.style.whiteSpace = \"nowrap\"\n this.addSubview(this.titleLabel!)\n \n this.titleLabel!.userInteractionEnabled = NO\n \n }\n \n this.contentPadding = 10\n \n this.imageView.userInteractionEnabled = NO\n if (this.titleLabel) {\n this.titleLabel.textAlignment = UITextView.textAlignment.center\n this.titleLabel.nativeSelectionEnabled = NO\n }\n \n }\n \n \n get contentPadding() {\n return this._contentPadding.integerValue\n }\n \n set contentPadding(contentPadding) {\n this._contentPadding = contentPadding\n this.setNeedsLayout()\n }\n \n \n public override set hovered(hovered: boolean) {\n this._hovered = hovered\n this.updateContentForCurrentState()\n }\n \n public override get hovered(): boolean {\n return this._hovered ?? NO\n }\n \n public override set highlighted(highlighted: boolean) {\n this._highlighted = highlighted\n this.updateContentForCurrentState()\n }\n \n public override get highlighted(): boolean {\n return this._highlighted\n }\n \n public override set focused(focused: boolean) {\n this._focused = focused\n if (focused) {\n this.focus()\n }\n else {\n this.blur()\n }\n this.updateContentForCurrentState()\n }\n \n public override get focused(): boolean {\n return this._focused ?? NO\n }\n \n public override set selected(selected: boolean) {\n this._selected = selected\n this.updateContentForCurrentState()\n }\n \n public override get selected(): boolean {\n return this._selected\n }\n \n \n override updateContentForCurrentState() {\n \n let updateFunction: Function = this.updateContentForNormalState\n if (this.selected && this.highlighted) {\n updateFunction = this.updateContentForSelectedAndHighlightedState\n }\n else if (this.selected) {\n updateFunction = this.updateContentForSelectedState\n }\n else if (this.focused) {\n updateFunction = this.updateContentForFocusedState\n }\n else if (this.highlighted) {\n updateFunction = this.updateContentForHighlightedState\n }\n else if (this.hovered) {\n updateFunction = this.updateContentForHoveredState\n }\n \n if (!IS(updateFunction)) {\n if (this.titleLabel) {\n this.titleLabel.textColor = UIColor.nilColor\n }\n this.backgroundColor = UIColor.nilColor\n }\n else {\n updateFunction.call(this)\n }\n \n this.updateContentForCurrentEnabledState()\n \n }\n \n override updateContentForNormalState() {\n \n this.backgroundColor = this.colors.background.normal\n if (this.titleLabel) {\n this.titleLabel.textColor = this.colors.titleLabel.normal\n }\n \n }\n \n override updateContentForHoveredState() {\n \n this.updateContentForNormalState()\n \n if (this.colors.background.hovered) {\n this.backgroundColor = this.colors.background.hovered\n }\n \n if (this.colors.titleLabel.hovered && this.titleLabel) {\n this.titleLabel.textColor = this.colors.titleLabel.hovered\n }\n \n }\n \n override updateContentForFocusedState() {\n \n this.updateContentForHoveredState()\n \n if (this.colors.background.focused) {\n this.backgroundColor = this.colors.background.focused\n }\n \n if (this.colors.titleLabel.focused && this.titleLabel) {\n this.titleLabel.textColor = this.colors.titleLabel.focused\n }\n \n }\n \n override updateContentForHighlightedState() {\n \n this.backgroundColor = this.colors.background.highlighted\n if (this.titleLabel) {\n this.titleLabel.textColor = this.colors.titleLabel.highlighted\n }\n \n }\n \n override updateContentForSelectedState() {\n \n this.backgroundColor = this.colors.background.selected\n if (this.titleLabel) {\n this.titleLabel.textColor = this.colors.titleLabel.selected\n }\n \n }\n \n override updateContentForSelectedAndHighlightedState() {\n \n this.updateContentForSelectedState()\n \n if (this.colors.background.selectedAndHighlighted) {\n this.backgroundColor = this.colors.background.selectedAndHighlighted\n }\n \n if (this.colors.titleLabel.selectedAndHighlighted && this.titleLabel) {\n this.titleLabel.textColor = this.colors.titleLabel.selectedAndHighlighted\n }\n \n }\n \n \n override set enabled(enabled: boolean) {\n \n // @ts-ignore\n super.enabled = enabled\n \n this.updateContentForCurrentState()\n \n }\n \n override get enabled() {\n \n // @ts-ignore\n return super.enabled\n \n }\n \n override updateContentForCurrentEnabledState() {\n \n if (this.enabled) {\n this.alpha = 1\n }\n else {\n this.alpha = 0.5\n }\n \n this.userInteractionEnabled = this.enabled\n \n }\n \n \n override addStyleClass(styleClassName: string) {\n \n super.addStyleClass(styleClassName)\n \n if (this.styleClassName != styleClassName) {\n \n this.updateContentForCurrentState.call(this)\n \n }\n \n }\n \n \n get titleLabel(): UITextView {\n return this._titleLabel\n }\n \n get imageView() {\n \n return this._imageView\n \n }\n \n \n override layoutSubviews() {\n \n super.layoutSubviews()\n \n let bounds = this.bounds\n \n this.hoverText = this.hoverText ?? this.titleLabel?.text ?? \"\"\n \n // Image only if text is not present\n if (IS_NOT(this.imageView.hidden) && !IS(this.titleLabel?.text)) {\n \n this.imageView.frame = bounds\n \n }\n \n // Text only if image is not present\n if (IS(this.imageView.hidden) && this.titleLabel?.text) {\n \n this.titleLabel.style.left = this.contentPadding + \"px\"\n this.titleLabel.style.right = this.contentPadding + \"px\"\n // this.titleLabel.style.marginLeft = \"\"\n // this.titleLabel.style.right = this.contentPadding\n this.titleLabel.style.bottom = \"0px\"\n this.titleLabel.style.top = \"0px\"\n this.titleLabel.frame = new UIRectangle(nil, nil, nil, nil)\n \n if (this.usesAutomaticTitleFontSize) {\n \n const hidden = this.titleLabel.hidden\n \n this.titleLabel.hidden = YES\n \n this.titleLabel.fontSize = 15\n \n this.titleLabel.fontSize = UITextView.automaticallyCalculatedFontSize(\n new UIRectangle(\n 0,\n 0,\n this.bounds.height,\n this.titleLabel.viewHTMLElement.offsetWidth\n ),\n this.titleLabel.intrinsicContentSize(),\n this.titleLabel.fontSize,\n this.minAutomaticFontSize,\n this.maxAutomaticFontSize\n )\n \n this.titleLabel.hidden = hidden\n \n }\n \n \n }\n \n // Image and text both present\n if (IS_NOT(this.imageView.hidden) && this.titleLabel?.text) {\n \n //const imageShareOfWidth = 0.25\n \n bounds = bounds.rectangleWithInset(this.contentPadding)\n \n const imageFrame = bounds.copy()\n imageFrame.width = bounds.height - this.contentPadding * 0.5\n this.imageView.frame = imageFrame\n \n this.titleLabel.style.left = imageFrame.max.x + this.contentPadding + \"px\"\n this.titleLabel.style.right = this.contentPadding + \"px\"\n this.titleLabel.style.bottom = \"0px\"\n this.titleLabel.style.top = \"0px\"\n \n if (this.usesAutomaticTitleFontSize) {\n \n const hidden = this.titleLabel.hidden\n \n this.titleLabel.hidden = YES\n \n this.titleLabel.fontSize = 15\n \n this.titleLabel.fontSize = UITextView.automaticallyCalculatedFontSize(\n new UIRectangle(\n 0,\n 0,\n this.bounds.height,\n this.titleLabel.viewHTMLElement.offsetWidth\n ),\n this.titleLabel.intrinsicContentSize(),\n this.titleLabel.fontSize,\n this.minAutomaticFontSize,\n this.maxAutomaticFontSize\n )\n \n this.titleLabel.hidden = hidden\n \n }\n \n }\n \n this.applyClassesAndStyles()\n \n }\n \n override initViewStyleSelectors() {\n \n this.initStyleSelector(\".\" + this.styleClassName, \"background-color: lightblue;\")\n \n // var selectorWithoutImage = \".\" + this.styleClassName + \" .\" + this.imageView.styleClassName + \" + .\" +\n // this.titleLabel.styleClassName;\n \n // this.initStyleSelector(\n // selectorWithoutImage,\n // \"left: \" + this.contentPadding + \";\" +\n // \"right: \" + this.contentPadding + \";\" +\n // \"top: 50%;\" +\n // \"transform: translateY(-50%);\");\n \n }\n \n \n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -40,7 +40,7 @@ export declare class UILayoutCycleTracer {
40
40
  static maxReportsPerPass: number;
41
41
  static _noiseFramePrefixes: string[];
42
42
  static get isEnabled(): boolean;
43
- static enable(): void;
43
+ static enable(reportThreshold?: number): void;
44
44
  static disable(): void;
45
45
  /**
46
46
  * Called at the very start of each layoutViewsIfNeeded() invocation.
@@ -25,12 +25,13 @@ const _UILayoutCycleTracer = class {
25
25
  static get isEnabled() {
26
26
  return _UILayoutCycleTracer._isEnabled;
27
27
  }
28
- static enable() {
28
+ static enable(reportThreshold = 1) {
29
29
  ;
30
30
  Error.stackTraceLimit = 100;
31
+ _UILayoutCycleTracer.reportThreshold = reportThreshold;
31
32
  _UILayoutCycleTracer._isEnabled = true;
32
33
  console.log(
33
- "%c[UILayoutCycleTracer] Layout cycle tracing ENABLED (Error.stackTraceLimit = 100)",
34
+ `%c[UILayoutCycleTracer] Layout cycle tracing ENABLED (threshold=${reportThreshold}, Error.stackTraceLimit=100)`,
34
35
  "color: #4CAF50; font-weight: bold"
35
36
  );
36
37
  }
@@ -58,7 +59,7 @@ const _UILayoutCycleTracer = class {
58
59
  return;
59
60
  }
60
61
  _UILayoutCycleTracer._currentIteration = iteration;
61
- if (iteration > 0) {
62
+ if (iteration > 0 && _UILayoutCycleTracer._totalReportsThisPass > 0) {
62
63
  console.warn(
63
64
  `%c[UILayoutCycleTracer] Layout pass iteration ${iteration + 1} \u2014 views were re-queued during the previous iteration`,
64
65
  "color: #FF9800; font-weight: bold"
@@ -109,7 +110,7 @@ const _UILayoutCycleTracer = class {
109
110
  return;
110
111
  }
111
112
  _UILayoutCycleTracer._isPassActive = false;
112
- if (iterationCount > 1) {
113
+ if (iterationCount > 1 && _UILayoutCycleTracer._totalReportsThisPass > 0) {
113
114
  console.warn(
114
115
  `%c[UILayoutCycleTracer] Layout pass completed in ${iterationCount} iteration(s). ${_UILayoutCycleTracer._totalReportsThisPass} cycle event(s) recorded.`,
115
116
  "color: #FF9800"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../scripts/UILayoutCycleTracer.ts"],
4
- "sourcesContent": ["/// #if DEV\n\n/**\n * UILayoutCycleTracer\n *\n * A development-only utility that detects and reports layout cycles in the\n * UIView layout system.\n *\n * A layout cycle occurs when layouting a view causes another setNeedsLayout()\n * call on the same view (or an ancestor) within the same layout pass, causing\n * the loop in layoutViewsIfNeeded() to iterate multiple times.\n *\n * Usage:\n * UILayoutCycleTracer.enable() \u2014 start tracing\n * UILayoutCycleTracer.disable() \u2014 stop tracing\n * UILayoutCycleTracer.isEnabled \u2014 check current state\n *\n * When a cycle is detected, a detailed report is printed to the console\n * including:\n * - Which view was re-queued\n * - Its full superview chain\n * - The call stack at the point of the re-queue (setNeedsLayout call)\n * - How many times the view has been laid out in this pass\n *\n * Integration:\n * Call UILayoutCycleTracer.willBeginLayoutPass() at the start of\n * layoutViewsIfNeeded(), UILayoutCycleTracer.didLayoutView(view) after each\n * view is laid out, and UILayoutCycleTracer.viewDidCallSetNeedsLayout(view)\n * from setNeedsLayout() while a pass is active.\n */\nexport class UILayoutCycleTracer {\n \n static _isEnabled: boolean = false\n static _isPassActive: boolean = false\n static _layoutCountsThisPass: Map<any, number> = new Map()\n static _setNeedsLayoutCallsThisPass: Map<any, { count: number; stacks: string[] }> = new Map()\n static _currentIteration: number = 0\n static _totalReportsThisPass: number = 0\n \n // How many times a view must be re-queued before reporting.\n // 1 means: report the first time a view is re-queued after being laid out.\n static reportThreshold: number = 1\n \n // Maximum number of cycle reports per layout pass to avoid console flooding.\n static maxReportsPerPass: number = 10\n \n // Prefixes of stack frames that belong to the tracer or framework internals\n // and should be stripped from the top of the captured stack so that the\n // first visible frame is always application code.\n static _noiseFramePrefixes: string[] = [\n \"UILayoutCycleTracer\",\n \"UIView.setNeedsLayout\",\n \"setNeedsLayout\",\n \"UIView.didLayoutSubviews\",\n \"didLayoutSubviews\",\n \"UIView.layoutSubviews\",\n \"UIView.layoutIfNeeded\",\n \"layoutIfNeeded\",\n \"UIView.layoutViewsIfNeeded\",\n \"layoutViewsIfNeeded\",\n ]\n \n static get isEnabled(): boolean {\n return UILayoutCycleTracer._isEnabled\n }\n \n static enable() {\n // Maximise the V8 stack trace depth so long chains are fully visible.\n // The default is 10 which truncates most interesting call stacks.\n ;(Error as any).stackTraceLimit = 100\n UILayoutCycleTracer._isEnabled = true\n console.log(\n \"%c[UILayoutCycleTracer] Layout cycle tracing ENABLED (Error.stackTraceLimit = 100)\",\n \"color: #4CAF50; font-weight: bold\"\n )\n }\n \n static disable() {\n ;(Error as any).stackTraceLimit = 10\n UILayoutCycleTracer._isEnabled = false\n console.log(\n \"%c[UILayoutCycleTracer] Layout cycle tracing DISABLED\",\n \"color: #9E9E9E; font-weight: bold\"\n )\n }\n \n /**\n * Called at the very start of each layoutViewsIfNeeded() invocation.\n */\n static willBeginLayoutPass() {\n if (!UILayoutCycleTracer._isEnabled) { return }\n UILayoutCycleTracer._isPassActive = true\n UILayoutCycleTracer._layoutCountsThisPass = new Map()\n UILayoutCycleTracer._setNeedsLayoutCallsThisPass = new Map()\n UILayoutCycleTracer._currentIteration = 0\n UILayoutCycleTracer._totalReportsThisPass = 0\n }\n \n /**\n * Called after each iteration increment in the layoutViewsIfNeeded() while loop.\n */\n static willBeginIteration(iteration: number) {\n if (!UILayoutCycleTracer._isEnabled) { return }\n UILayoutCycleTracer._currentIteration = iteration\n if (iteration > 0) {\n console.warn(\n `%c[UILayoutCycleTracer] Layout pass iteration ${iteration + 1} \u2014 views were re-queued during the previous iteration`,\n \"color: #FF9800; font-weight: bold\"\n )\n }\n }\n \n /**\n * Called after a view's layoutIfNeeded() completes.\n */\n static didLayoutView(view: any) {\n if (!UILayoutCycleTracer._isEnabled || !UILayoutCycleTracer._isPassActive) { return }\n const previous = UILayoutCycleTracer._layoutCountsThisPass.get(view) ?? 0\n UILayoutCycleTracer._layoutCountsThisPass.set(view, previous + 1)\n }\n \n /**\n * Called from setNeedsLayout() when a view enters the queue.\n * If a layout pass is currently active, this is a potential cycle.\n */\n static viewDidCallSetNeedsLayout(view: any) {\n if (!UILayoutCycleTracer._isEnabled || !UILayoutCycleTracer._isPassActive) { return }\n \n // Only report if this view has already been laid out at least once this pass.\n const layoutCount = UILayoutCycleTracer._layoutCountsThisPass.get(view) ?? 0\n if (layoutCount < UILayoutCycleTracer.reportThreshold) { return }\n \n if (UILayoutCycleTracer._totalReportsThisPass >= UILayoutCycleTracer.maxReportsPerPass) {\n if (UILayoutCycleTracer._totalReportsThisPass === UILayoutCycleTracer.maxReportsPerPass) {\n console.warn(\n `%c[UILayoutCycleTracer] Maximum reports per pass (${UILayoutCycleTracer.maxReportsPerPass}) reached. Further reports suppressed.`,\n \"color: #F44336\"\n )\n UILayoutCycleTracer._totalReportsThisPass++\n }\n return\n }\n \n UILayoutCycleTracer._totalReportsThisPass++\n \n const rawStack = new Error().stack ?? \"(stack unavailable)\"\n const cleanStack = UILayoutCycleTracer._cleanStack(rawStack)\n \n const existing = UILayoutCycleTracer._setNeedsLayoutCallsThisPass.get(view)\n if (existing) {\n existing.count++\n existing.stacks.push(cleanStack)\n }\n else {\n UILayoutCycleTracer._setNeedsLayoutCallsThisPass.set(view, { count: 1, stacks: [cleanStack] })\n }\n \n UILayoutCycleTracer._reportCycle(view, layoutCount, cleanStack)\n }\n \n /**\n * Called at the end of a layout pass.\n */\n static didFinishLayoutPass(iterationCount: number) {\n if (!UILayoutCycleTracer._isEnabled) { return }\n UILayoutCycleTracer._isPassActive = false\n \n if (iterationCount > 1) {\n console.warn(\n `%c[UILayoutCycleTracer] Layout pass completed in ${iterationCount} iteration(s). ` +\n `${UILayoutCycleTracer._totalReportsThisPass} cycle event(s) recorded.`,\n \"color: #FF9800\"\n )\n }\n }\n \n /**\n * Strips the \"Error\" header line and any leading framework/tracer noise\n * frames from a raw Error.stack string, so the first frame shown is always\n * the application code that triggered the re-queue.\n */\n static _cleanStack(rawStack: string): string {\n const lines = rawStack.split(\"\\n\")\n \n // Find the first line that is NOT the \"Error\" header and NOT a noise frame.\n let firstAppFrameIndex = 1 // skip \"Error\" on line 0\n for (let i = 1; i < lines.length; i++) {\n const trimmed = lines[i].trim()\n const isNoise = UILayoutCycleTracer._noiseFramePrefixes.some(prefix =>\n trimmed.includes(prefix)\n )\n if (!isNoise) {\n firstAppFrameIndex = i\n break\n }\n }\n \n return lines.slice(firstAppFrameIndex).join(\"\\n\")\n }\n \n static _viewIdentifier(view: any): string {\n const className = view?.constructor?.name ?? \"UnknownView\"\n const elementID = view?.elementID ?? view?._UIViewIndex ?? \"?\"\n return `${className}#${elementID}`\n }\n \n static _superviewChain(view: any): string {\n const parts: string[] = []\n let current = view\n let depth = 0\n while (current && depth < 20) {\n parts.push(UILayoutCycleTracer._viewIdentifier(current))\n current = current.superview\n depth++\n }\n return parts.join(\" \u2192 \")\n }\n \n static _reportCycle(view: any, layoutCountThisPass: number, cleanStack: string) {\n const identifier = UILayoutCycleTracer._viewIdentifier(view)\n const chain = UILayoutCycleTracer._superviewChain(view)\n \n console.groupCollapsed(\n `%c[UILayoutCycleTracer] \u26A0\uFE0F Layout cycle: ${identifier} re-queued after being laid out ${layoutCountThisPass}x in iteration ${UILayoutCycleTracer._currentIteration + 1}`,\n \"color: #F44336; font-weight: bold\"\n )\n console.log(\"%cView:\", \"font-weight: bold\", view)\n console.log(\"%cSuperview chain:\", \"font-weight: bold\", chain)\n console.log(\"%cLayout count this pass:\", \"font-weight: bold\", layoutCountThisPass)\n console.log(\"%cIteration:\", \"font-weight: bold\", UILayoutCycleTracer._currentIteration + 1)\n console.log(\"%cCall stack (noise frames stripped):\", \"font-weight: bold\")\n cleanStack.split(\"\\n\").forEach(frame => console.log(\" \" + frame.trim()))\n console.groupEnd()\n }\n \n}\n\nwindow.UILayoutCycleTracer = UILayoutCycleTracer\n\ndeclare global {\n interface Window {\n UILayoutCycleTracer?: typeof UILayoutCycleTracer\n }\n}\n\n/// #endif\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/// #if DEV\n\n/**\n * UILayoutCycleTracer\n *\n * A development-only utility that detects and reports layout cycles in the\n * UIView layout system.\n *\n * A layout cycle occurs when layouting a view causes another setNeedsLayout()\n * call on the same view (or an ancestor) within the same layout pass, causing\n * the loop in layoutViewsIfNeeded() to iterate multiple times.\n *\n * Usage:\n * UILayoutCycleTracer.enable() \u2014 start tracing\n * UILayoutCycleTracer.disable() \u2014 stop tracing\n * UILayoutCycleTracer.isEnabled \u2014 check current state\n *\n * When a cycle is detected, a detailed report is printed to the console\n * including:\n * - Which view was re-queued\n * - Its full superview chain\n * - The call stack at the point of the re-queue (setNeedsLayout call)\n * - How many times the view has been laid out in this pass\n *\n * Integration:\n * Call UILayoutCycleTracer.willBeginLayoutPass() at the start of\n * layoutViewsIfNeeded(), UILayoutCycleTracer.didLayoutView(view) after each\n * view is laid out, and UILayoutCycleTracer.viewDidCallSetNeedsLayout(view)\n * from setNeedsLayout() while a pass is active.\n */\nexport class UILayoutCycleTracer {\n \n static _isEnabled: boolean = false\n static _isPassActive: boolean = false\n static _layoutCountsThisPass: Map<any, number> = new Map()\n static _setNeedsLayoutCallsThisPass: Map<any, { count: number; stacks: string[] }> = new Map()\n static _currentIteration: number = 0\n static _totalReportsThisPass: number = 0\n \n // How many times a view must be re-queued before reporting.\n // 1 means: report the first time a view is re-queued after being laid out.\n static reportThreshold: number = 1\n \n // Maximum number of cycle reports per layout pass to avoid console flooding.\n static maxReportsPerPass: number = 10\n \n // Prefixes of stack frames that belong to the tracer or framework internals\n // and should be stripped from the top of the captured stack so that the\n // first visible frame is always application code.\n static _noiseFramePrefixes: string[] = [\n \"UILayoutCycleTracer\",\n \"UIView.setNeedsLayout\",\n \"setNeedsLayout\",\n \"UIView.didLayoutSubviews\",\n \"didLayoutSubviews\",\n \"UIView.layoutSubviews\",\n \"UIView.layoutIfNeeded\",\n \"layoutIfNeeded\",\n \"UIView.layoutViewsIfNeeded\",\n \"layoutViewsIfNeeded\",\n ]\n \n static get isEnabled(): boolean {\n return UILayoutCycleTracer._isEnabled\n }\n \n \n static enable(reportThreshold: number = 1) {\n // Maximise the V8 stack trace depth so long chains are fully visible.\n // The default is 10 which truncates most interesting call stacks.\n ;(Error as any).stackTraceLimit = 100\n UILayoutCycleTracer.reportThreshold = reportThreshold\n UILayoutCycleTracer._isEnabled = true\n console.log(\n `%c[UILayoutCycleTracer] Layout cycle tracing ENABLED (threshold=${reportThreshold}, Error.stackTraceLimit=100)`,\n \"color: #4CAF50; font-weight: bold\"\n )\n }\n \n static disable() {\n ;(Error as any).stackTraceLimit = 10\n UILayoutCycleTracer._isEnabled = false\n console.log(\n \"%c[UILayoutCycleTracer] Layout cycle tracing DISABLED\",\n \"color: #9E9E9E; font-weight: bold\"\n )\n }\n \n /**\n * Called at the very start of each layoutViewsIfNeeded() invocation.\n */\n static willBeginLayoutPass() {\n if (!UILayoutCycleTracer._isEnabled) { return }\n UILayoutCycleTracer._isPassActive = true\n UILayoutCycleTracer._layoutCountsThisPass = new Map()\n UILayoutCycleTracer._setNeedsLayoutCallsThisPass = new Map()\n UILayoutCycleTracer._currentIteration = 0\n UILayoutCycleTracer._totalReportsThisPass = 0\n }\n \n /**\n * Called after each iteration increment in the layoutViewsIfNeeded() while loop.\n */\n static willBeginIteration(iteration: number) {\n if (!UILayoutCycleTracer._isEnabled) { return }\n UILayoutCycleTracer._currentIteration = iteration\n if (iteration > 0 && UILayoutCycleTracer._totalReportsThisPass > 0) {\n console.warn(\n `%c[UILayoutCycleTracer] Layout pass iteration ${iteration + 1} \u2014 views were re-queued during the previous iteration`,\n \"color: #FF9800; font-weight: bold\"\n )\n }\n }\n \n /**\n * Called after a view's layoutIfNeeded() completes.\n */\n static didLayoutView(view: any) {\n if (!UILayoutCycleTracer._isEnabled || !UILayoutCycleTracer._isPassActive) { return }\n const previous = UILayoutCycleTracer._layoutCountsThisPass.get(view) ?? 0\n UILayoutCycleTracer._layoutCountsThisPass.set(view, previous + 1)\n }\n \n /**\n * Called from setNeedsLayout() when a view enters the queue.\n * If a layout pass is currently active, this is a potential cycle.\n */\n static viewDidCallSetNeedsLayout(view: any) {\n if (!UILayoutCycleTracer._isEnabled || !UILayoutCycleTracer._isPassActive) { return }\n \n // Only report if this view has already been laid out at least once this pass.\n const layoutCount = UILayoutCycleTracer._layoutCountsThisPass.get(view) ?? 0\n if (layoutCount < UILayoutCycleTracer.reportThreshold) { return }\n \n if (UILayoutCycleTracer._totalReportsThisPass >= UILayoutCycleTracer.maxReportsPerPass) {\n if (UILayoutCycleTracer._totalReportsThisPass === UILayoutCycleTracer.maxReportsPerPass) {\n console.warn(\n `%c[UILayoutCycleTracer] Maximum reports per pass (${UILayoutCycleTracer.maxReportsPerPass}) reached. Further reports suppressed.`,\n \"color: #F44336\"\n )\n UILayoutCycleTracer._totalReportsThisPass++\n }\n return\n }\n \n UILayoutCycleTracer._totalReportsThisPass++\n \n const rawStack = new Error().stack ?? \"(stack unavailable)\"\n const cleanStack = UILayoutCycleTracer._cleanStack(rawStack)\n \n const existing = UILayoutCycleTracer._setNeedsLayoutCallsThisPass.get(view)\n if (existing) {\n existing.count++\n existing.stacks.push(cleanStack)\n }\n else {\n UILayoutCycleTracer._setNeedsLayoutCallsThisPass.set(view, { count: 1, stacks: [cleanStack] })\n }\n \n UILayoutCycleTracer._reportCycle(view, layoutCount, cleanStack)\n }\n \n /**\n * Called at the end of a layout pass.\n */\n static didFinishLayoutPass(iterationCount: number) {\n if (!UILayoutCycleTracer._isEnabled) { return }\n UILayoutCycleTracer._isPassActive = false\n \n if (iterationCount > 1 && UILayoutCycleTracer._totalReportsThisPass > 0) {\n console.warn(\n `%c[UILayoutCycleTracer] Layout pass completed in ${iterationCount} iteration(s). ` +\n `${UILayoutCycleTracer._totalReportsThisPass} cycle event(s) recorded.`,\n \"color: #FF9800\"\n )\n }\n }\n \n /**\n * Strips the \"Error\" header line and any leading framework/tracer noise\n * frames from a raw Error.stack string, so the first frame shown is always\n * the application code that triggered the re-queue.\n */\n static _cleanStack(rawStack: string): string {\n const lines = rawStack.split(\"\\n\")\n \n // Find the first line that is NOT the \"Error\" header and NOT a noise frame.\n let firstAppFrameIndex = 1 // skip \"Error\" on line 0\n for (let i = 1; i < lines.length; i++) {\n const trimmed = lines[i].trim()\n const isNoise = UILayoutCycleTracer._noiseFramePrefixes.some(prefix =>\n trimmed.includes(prefix)\n )\n if (!isNoise) {\n firstAppFrameIndex = i\n break\n }\n }\n \n return lines.slice(firstAppFrameIndex).join(\"\\n\")\n }\n \n static _viewIdentifier(view: any): string {\n const className = view?.constructor?.name ?? \"UnknownView\"\n const elementID = view?.elementID ?? view?._UIViewIndex ?? \"?\"\n return `${className}#${elementID}`\n }\n \n static _superviewChain(view: any): string {\n const parts: string[] = []\n let current = view\n let depth = 0\n while (current && depth < 20) {\n parts.push(UILayoutCycleTracer._viewIdentifier(current))\n current = current.superview\n depth++\n }\n return parts.join(\" \u2192 \")\n }\n \n static _reportCycle(view: any, layoutCountThisPass: number, cleanStack: string) {\n const identifier = UILayoutCycleTracer._viewIdentifier(view)\n const chain = UILayoutCycleTracer._superviewChain(view)\n \n console.groupCollapsed(\n `%c[UILayoutCycleTracer] \u26A0\uFE0F Layout cycle: ${identifier} re-queued after being laid out ${layoutCountThisPass}x in iteration ${UILayoutCycleTracer._currentIteration + 1}`,\n \"color: #F44336; font-weight: bold\"\n )\n console.log(\"%cView:\", \"font-weight: bold\", view)\n console.log(\"%cSuperview chain:\", \"font-weight: bold\", chain)\n console.log(\"%cLayout count this pass:\", \"font-weight: bold\", layoutCountThisPass)\n console.log(\"%cIteration:\", \"font-weight: bold\", UILayoutCycleTracer._currentIteration + 1)\n console.log(\"%cCall stack (noise frames stripped):\", \"font-weight: bold\")\n cleanStack.split(\"\\n\").forEach(frame => console.log(\" \" + frame.trim()))\n console.groupEnd()\n }\n \n}\n\nwindow.UILayoutCycleTracer = UILayoutCycleTracer\n\ndeclare global {\n interface Window {\n UILayoutCycleTracer?: typeof UILayoutCycleTracer\n }\n}\n\n/// #endif\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -77,7 +77,7 @@ const _UIView = class extends import_UIObject.UIObject {
77
77
  super();
78
78
  this._nativeSelectionEnabled = import_UIObject.YES;
79
79
  this._enabled = import_UIObject.YES;
80
- this._backgroundColor = import_UIColor.UIColor.transparentColor;
80
+ this._backgroundColor = import_UIObject.nil;
81
81
  this._liveCSSValues = /* @__PURE__ */ new Map();
82
82
  this._liveCSSCallback = () => {
83
83
  for (const [property, { producer }] of this._liveCSSValues) {
@@ -842,6 +842,16 @@ const _UIView = class extends import_UIObject.UIObject {
842
842
  return this._backgroundColor;
843
843
  }
844
844
  set backgroundColor(backgroundColor) {
845
+ const previous = this._backgroundColor;
846
+ if (previous === backgroundColor) {
847
+ return;
848
+ }
849
+ if ((previous == null ? void 0 : previous.semanticKey) && previous.semanticKey === (backgroundColor == null ? void 0 : backgroundColor.semanticKey) && previous._semanticClass === (backgroundColor == null ? void 0 : backgroundColor._semanticClass)) {
850
+ return;
851
+ }
852
+ if (!(previous == null ? void 0 : previous.semanticKey) && !(backgroundColor == null ? void 0 : backgroundColor.semanticKey) && (previous == null ? void 0 : previous.stringValue) === (backgroundColor == null ? void 0 : backgroundColor.stringValue)) {
853
+ return;
854
+ }
845
855
  this._backgroundColor = backgroundColor;
846
856
  this.colorStyleProxy.backgroundColor = backgroundColor;
847
857
  }
@@ -2210,6 +2220,12 @@ const _UIView = class extends import_UIObject.UIObject {
2210
2220
  }
2211
2221
  return import_UIObject.YES;
2212
2222
  }
2223
+ function eventKeyIsSpace(event) {
2224
+ if (event.keyCode !== 32) {
2225
+ return import_UIObject.NO;
2226
+ }
2227
+ return import_UIObject.YES;
2228
+ }
2213
2229
  function eventKeyIsTab(event) {
2214
2230
  if (event.keyCode !== 9) {
2215
2231
  return import_UIObject.NO;
@@ -2250,8 +2266,13 @@ const _UIView = class extends import_UIObject.UIObject {
2250
2266
  return import_UIObject.YES;
2251
2267
  }
2252
2268
  const onKeyDown = (event) => {
2253
- if (eventKeyIsEnter(event)) {
2269
+ if (eventKeyIsEnter(event) || eventKeyIsSpace(event)) {
2254
2270
  this.sendControlEventForKey(_UIView.controlEvent.EnterDown, event);
2271
+ if (eventKeyIsSpace(
2272
+ event
2273
+ ) && this._controlEventTargets.EnterDown && this._controlEventTargets.EnterDown.length) {
2274
+ pauseEvent(event, import_UIObject.YES);
2275
+ }
2255
2276
  }
2256
2277
  if (eventKeyIsEsc(event)) {
2257
2278
  this.sendControlEventForKey(_UIView.controlEvent.EscDown, event);