diff --git a/apps/toolbox/src/app-root.xml b/apps/toolbox/src/app-root.xml index 54e70d9760..e895c0b967 100644 --- a/apps/toolbox/src/app-root.xml +++ b/apps/toolbox/src/app-root.xml @@ -1,2 +1,8 @@ + + + diff --git a/apps/toolbox/src/main.ts b/apps/toolbox/src/main.ts index a4c5c529a8..bdf3214810 100644 --- a/apps/toolbox/src/main.ts +++ b/apps/toolbox/src/main.ts @@ -1,3 +1,6 @@ -import { Application } from '@nativescript/core'; +import { Application, SplitView } from '@nativescript/core'; -Application.run({ moduleName: 'app-root' }); +// Application.run({ moduleName: 'app-root' }); + +SplitView.SplitStyle = 'triple'; +Application.run({ moduleName: 'split-view/split-view-root' }); diff --git a/apps/toolbox/src/pages/list-page-model-sticky.ts b/apps/toolbox/src/pages/list-page-model-sticky.ts index 3550c2f83a..cf8b3cb9c1 100644 --- a/apps/toolbox/src/pages/list-page-model-sticky.ts +++ b/apps/toolbox/src/pages/list-page-model-sticky.ts @@ -1,4 +1,5 @@ import { Observable, Dialogs, DialogStrings, View, EventData, SearchEventData } from '@nativescript/core'; +import { getItemCallbacks } from '../split-view/split-view-root'; type CountryListType = Array<{ title: string; items: Array<{ name: string; code: string; flag: string; isVisible?: boolean }> }>; export class ListPageModelSticky extends Observable { countries: CountryListType = [ @@ -1380,11 +1381,14 @@ export class ListPageModelSticky extends Observable { } componentsItemTap(args): void { - Dialogs.alert({ - title: 'Want to play?', - message: 'Nothing to see here yet. Feel free to add more examples to play around.', - okButtonText: DialogStrings.OK, - }); + const letter = this.countries[args.section]; + console.log('Tapped on category: ' + letter.title); + if (letter.items?.length) { + const country = letter.items[args.index]; + console.log('Tapped on country: ' + country.name); + // used in splitview demo + getItemCallbacks().forEach((callback) => callback(`${country.name} was selected.`)); + } } itemLoading(args: EventData): void { diff --git a/apps/toolbox/src/pages/list-page-sticky.xml b/apps/toolbox/src/pages/list-page-sticky.xml index 887a568e17..d7b6f0abc2 100644 --- a/apps/toolbox/src/pages/list-page-sticky.xml +++ b/apps/toolbox/src/pages/list-page-sticky.xml @@ -1,9 +1,6 @@ - - - - + + diff --git a/apps/toolbox/src/split-view/split-view-primary.ts b/apps/toolbox/src/split-view/split-view-primary.ts new file mode 100644 index 0000000000..3f5748cb28 --- /dev/null +++ b/apps/toolbox/src/split-view/split-view-primary.ts @@ -0,0 +1,23 @@ +import { Observable, EventData, Page, SplitView, ItemEventData } from '@nativescript/core'; +import { getItemCallbacks } from './split-view-root'; +let page: Page; + +export function navigatingTo(args: EventData) { + page = args.object; + page.bindingContext = new SplitViewPrimaryModel(); +} + +export class SplitViewPrimaryModel extends Observable { + items: string[] = []; + + constructor() { + super(); + this.items = Array.from({ length: 20 }, (_, i) => `Item ${i + 1}`); + } + + onItemTap(args: ItemEventData) { + console.log('args.index', args.index); + SplitView.getInstance()?.showSecondary(); + getItemCallbacks().forEach((callback) => callback(this.items[args.index])); + } +} diff --git a/apps/toolbox/src/split-view/split-view-primary.xml b/apps/toolbox/src/split-view/split-view-primary.xml new file mode 100644 index 0000000000..915ff7e692 --- /dev/null +++ b/apps/toolbox/src/split-view/split-view-primary.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/toolbox/src/split-view/split-view-root.ts b/apps/toolbox/src/split-view/split-view-root.ts new file mode 100644 index 0000000000..9cb11fce71 --- /dev/null +++ b/apps/toolbox/src/split-view/split-view-root.ts @@ -0,0 +1,17 @@ +import { Observable, EventData, Page } from '@nativescript/core'; +let page: Page; + +export function navigatingTo(args: EventData) { + page = args.object; + page.bindingContext = new SplitViewModel(); +} + +export class SplitViewModel extends Observable {} + +let itemCallbacks: Array<(item: any) => void> = []; +export function setItemCallbacks(changeItem: Array<(item: any) => void>) { + itemCallbacks.push(...changeItem); +} +export function getItemCallbacks() { + return itemCallbacks; +} diff --git a/apps/toolbox/src/split-view/split-view-root.xml b/apps/toolbox/src/split-view/split-view-root.xml new file mode 100644 index 0000000000..02d1ae3da9 --- /dev/null +++ b/apps/toolbox/src/split-view/split-view-root.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/toolbox/src/split-view/split-view-secondary.ts b/apps/toolbox/src/split-view/split-view-secondary.ts new file mode 100644 index 0000000000..cb0a624182 --- /dev/null +++ b/apps/toolbox/src/split-view/split-view-secondary.ts @@ -0,0 +1,35 @@ +import { Observable, EventData, Page, SplitView } from '@nativescript/core'; +import { setItemCallbacks } from './split-view-root'; +let page: Page; + +export function navigatingTo(args: EventData) { + page = args.object; + page.bindingContext = new SplitViewSecondaryModel(); +} + +export class SplitViewSecondaryModel extends Observable { + selectedItem = `Select an item from Primary.`; + showInspectorButton = false; + + constructor() { + super(); + setItemCallbacks([this.changeItem.bind(this)]); + SplitView.getInstance().on('inspectorChange', (args: any) => { + console.log('inspectorChange', args.data?.showing); + this.showInspectorButton = !args.data?.showing; + this.notifyPropertyChange('showInspectorButton', this.showInspectorButton); + }); + } + toggle() { + SplitView.getInstance()?.showPrimary(); + } + + toggleInspector() { + SplitView.getInstance()?.showInspector(); + } + + changeItem(item: any) { + this.selectedItem = item; + this.notifyPropertyChange('selectedItem', item); + } +} diff --git a/apps/toolbox/src/split-view/split-view-secondary.xml b/apps/toolbox/src/split-view/split-view-secondary.xml new file mode 100644 index 0000000000..75328de39f --- /dev/null +++ b/apps/toolbox/src/split-view/split-view-secondary.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/apps/toolbox/src/split-view/split-view-supplement.ts b/apps/toolbox/src/split-view/split-view-supplement.ts new file mode 100644 index 0000000000..658a7b8859 --- /dev/null +++ b/apps/toolbox/src/split-view/split-view-supplement.ts @@ -0,0 +1,24 @@ +import { Observable, EventData, Page, SplitView } from '@nativescript/core'; +import { setItemCallbacks } from './split-view-root'; +let page: Page; + +export function navigatingTo(args: EventData) { + page = args.object; + page.bindingContext = new SplitViewSupplementaryModel(); +} + +export class SplitViewSupplementaryModel extends Observable { + selectedItem = `Supplementary - Select an item.`; + constructor() { + super(); + setItemCallbacks([this.changeItem.bind(this)]); + } + toggle() { + SplitView.getInstance()?.showPrimary(); + } + + changeItem(item: any) { + this.selectedItem = item; + this.notifyPropertyChange('selectedItem', item); + } +} diff --git a/apps/toolbox/src/split-view/split-view-supplement.xml b/apps/toolbox/src/split-view/split-view-supplement.xml new file mode 100644 index 0000000000..03a99b1b37 --- /dev/null +++ b/apps/toolbox/src/split-view/split-view-supplement.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2b0220a47c..8589fe6c73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -239,6 +239,7 @@ "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -2263,6 +2264,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -2286,6 +2288,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -5620,6 +5623,7 @@ "integrity": "sha512-clqOhLHvGXelJDq0blfrPMvJ88TTMhlxKvbuj+mxpfXCcHIYlhuHeH63u99eO4wEbVtSopOG4szpABSjRXJESw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "ejs": "^3.1.7", "enquirer": "~2.3.6", @@ -5653,6 +5657,7 @@ "integrity": "sha512-YYxPkohOjUYpbYXgDbrgbgoyA7DlxK8pDYasVYcyEGwf9M5H5KIacgkK2EdBBcIfEzFiASKbVm817tjhKT5ndQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@nx/devkit": "21.3.7", "@nx/js": "21.3.7", @@ -5805,6 +5810,7 @@ "integrity": "sha512-oy+WcZqfYvOzhO+cefgwYVRIBULfVQk8J8prgw9kMuFcJRgOYXkkfB1HLdkxx+OrHGDPqs7Oe0+8KS1lilnumA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/core": "^7.23.2", "@babel/plugin-proposal-decorators": "^7.22.7", @@ -6209,6 +6215,7 @@ "integrity": "sha512-DIMb9Ts6w0FtKIglNEkAQ22w+b/4kx97MJDdK3tU1t0o0hG64XbYZ9xyVjnENVEkSKnSInAid/dBg+pMTgwxhA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@nx/devkit": "21.3.7", "@zkochan/js-yaml": "0.0.7", @@ -8062,6 +8069,7 @@ "integrity": "sha512-jYWaI2WNEKz8KZL3sExd2KVL1JMma1/J7z+9iTpv0+fRN7LGMF8VTGGuHI2bug/ztpdZU1G44FG/Kk6ElXL9CQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@swc-node/core": "^1.13.3", "@swc-node/sourcemap-support": "^0.5.1", @@ -8119,6 +8127,7 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.23" @@ -8344,6 +8353,7 @@ "integrity": "sha512-u1iIVZV9Q0jxY+yM2vw/hZGDNudsN85bBpTqzAQ9rzkxW9D+e3aEM4Han+ow518gSewkXgjmEK0BD79ZcNVgPw==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@swc/counter": "^0.1.3" } @@ -8779,6 +8789,7 @@ "integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.35.0", "@typescript-eslint/types": "8.35.0", @@ -9904,6 +9915,7 @@ "integrity": "sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "argparse": "^2.0.1" }, @@ -9948,6 +9960,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -10107,6 +10120,7 @@ "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -11055,6 +11069,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001669", "electron-to-chromium": "^1.5.41", @@ -11414,6 +11429,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -13129,31 +13145,6 @@ "node": ">= 4" } }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "node_modules/encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -13385,6 +13376,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -13441,6 +13433,7 @@ "integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -15464,7 +15457,6 @@ "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", "dev": true, "license": "ISC", - "peer": true, "engines": { "node": "^10 || ^12 || >= 14" }, @@ -16463,6 +16455,7 @@ "integrity": "sha512-y2mfcJywuTUkvLm2Lp1/pFX8kTgMO5yyQGq/Sk/n2mN7XWYp4JsCZ/QXW34M8YScgk8bPZlREH04f6blPnoHnQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "30.0.5", "@jest/types": "30.0.5", @@ -18629,6 +18622,7 @@ "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", "dev": true, "license": "MIT", + "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -18691,6 +18685,7 @@ "integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "abab": "^2.0.6", "cssstyle": "^3.0.0", @@ -19865,6 +19860,7 @@ "integrity": "sha512-dgLIeKGLx5FwziAnsk4ONoGwHwGPJzselimvlVskE9XLN4Orv9u2VA3GWw/lYUqjfA0rUT/6fqKwfZJapP9BEg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -21496,6 +21492,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@napi-rs/wasm-runtime": "0.2.4", "@yarnpkg/lockfile": "^1.1.0", @@ -22779,6 +22776,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -22904,7 +22902,6 @@ "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", "dev": true, "license": "ISC", - "peer": true, "engines": { "node": "^10 || ^12 || >= 14" }, @@ -22918,7 +22915,6 @@ "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "icss-utils": "^5.0.0", "postcss-selector-parser": "^7.0.0", @@ -22937,7 +22933,6 @@ "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -22952,7 +22947,6 @@ "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "postcss-selector-parser": "^7.0.0" }, @@ -22969,7 +22963,6 @@ "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -22984,7 +22977,6 @@ "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", "dev": true, "license": "ISC", - "peer": true, "dependencies": { "icss-utils": "^5.0.0" }, @@ -23043,6 +23035,7 @@ "integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -24204,6 +24197,7 @@ "integrity": "sha512-ccZgdHNiBF1NHBsWvacvT5rju3y1d/Eu+8Ex6c21nHp2lZGLBEtuwc415QfiI1PJa1TpCo3iXwwSRjRpn2Ckjg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^4.0.0", @@ -24335,6 +24329,7 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -25576,6 +25571,7 @@ "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -25930,6 +25926,7 @@ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -26622,6 +26619,7 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -26988,6 +26986,7 @@ "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -27119,6 +27118,7 @@ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -27132,6 +27132,7 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -27447,6 +27448,7 @@ "integrity": "sha512-CNqKBRMQjwcmKR0idID5va1qlhrqVUKpovi+Ec79ksW8ux7iS1+A6VqzfZXgVYCFRKl7XL5ap3ZoMpwBJxcg0w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", @@ -27592,6 +27594,7 @@ "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^1.2.0", diff --git a/packages/core/ui/frame/index.ios.ts b/packages/core/ui/frame/index.ios.ts index 3f5693978c..b038a88d1b 100644 --- a/packages/core/ui/frame/index.ios.ts +++ b/packages/core/ui/frame/index.ios.ts @@ -129,14 +129,15 @@ export class Frame extends FrameBase { viewController[TRANSITION] = { name: NON_ANIMATED_TRANSITION }; } + if (!navControllerDelegate) { + navControllerDelegate = UINavigationControllerDelegateImpl.new(); + } + + this._ios.controller.delegate = navControllerDelegate; + viewController[DELEGATE] = navControllerDelegate; + const nativeTransition = _getNativeTransition(navigationTransition, true, this.direction); if (!nativeTransition && navigationTransition) { - if (!navControllerDelegate) { - navControllerDelegate = UINavigationControllerAnimatedDelegate.new(); - } - - this._ios.controller.delegate = navControllerDelegate; - viewController[DELEGATE] = navControllerDelegate; if (navigationTransition.instance) { this.transitionId = navigationTransition.instance.id; const transitionState = SharedTransition.getState(this.transitionId); @@ -148,9 +149,6 @@ export class Frame extends FrameBase { }, this); } } - } else { - viewController[DELEGATE] = null; - this._ios.controller.delegate = null; } backstackEntry[NAV_DEPTH] = navDepth; @@ -358,6 +356,17 @@ export class Frame extends FrameBase { public _setNativeViewFrame(nativeView: UIView, frame: CGRect) { // } + + // Emits an event whenever the UINavigationController shows a view controller. + // Consumers can subscribe to 'viewControllerShown' + // to safely interact with the visible controller/navigationItem once it exists. + public _onViewControllerShown(viewController: UIViewController): void { + this.notify({ + eventName: 'viewControllerShown', + object: this, + viewController, + }); + } } const transitionDelegates = new Array(); @@ -410,7 +419,7 @@ class TransitionDelegate extends NSObject { } @NativeClass -class UINavigationControllerAnimatedDelegate extends NSObject implements UINavigationControllerDelegate { +class UINavigationControllerDelegateImpl extends NSObject implements UINavigationControllerDelegate { public static ObjCProtocols = [UINavigationControllerDelegate]; navigationControllerAnimationControllerForOperationFromViewControllerToViewController(navigationController: UINavigationControllerImpl, operation: number, fromVC: UIViewController, toVC: UIViewController): UIViewControllerAnimatedTransitioning { @@ -478,6 +487,16 @@ class UINavigationControllerAnimatedDelegate extends NSObject implements UINavig return null; } + + navigationControllerDidShowViewControllerAnimated(navigationController: UINavigationControllerImpl, viewController: UIViewController, animated: boolean): void { + if (Trace.isEnabled()) { + Trace.write('Frame.navigationController.DID_show(' + navigationController + ', ' + viewController + ', ' + animated + ');', Trace.categories.Debug); + } + const owner = navigationController.owner; + if (owner) { + owner._onViewControllerShown(viewController); + } + } } @NativeClass diff --git a/packages/core/ui/index.ts b/packages/core/ui/index.ts index d64ccaa449..ff1315e83a 100644 --- a/packages/core/ui/index.ts +++ b/packages/core/ui/index.ts @@ -71,6 +71,7 @@ export { SegmentedBar, SegmentedBarItem } from './segmented-bar'; export type { SelectedIndexChangedEventData } from './segmented-bar'; export { Slider } from './slider'; export type { AccessibilityDecrementEventData, AccessibilityIncrementEventData } from './slider'; +export { SplitView } from './split-view'; export { addTaggedAdditionalCSS, removeTaggedAdditionalCSS, resolveFileNameFromUrl } from './styling/style-scope'; export { Background } from './styling/background'; diff --git a/packages/core/ui/list-view/index.d.ts b/packages/core/ui/list-view/index.d.ts index c60d1dbedf..67536ddd8a 100644 --- a/packages/core/ui/list-view/index.d.ts +++ b/packages/core/ui/list-view/index.d.ts @@ -233,6 +233,11 @@ export interface ItemEventData extends EventData { */ index: number; + /** + * When data is sectioned (any platform that supports sections), this is the section index for the item. + */ + section?: number; + /** * The view that is associated to the item, for which the event is raised. */ diff --git a/packages/core/ui/list-view/index.ios.ts b/packages/core/ui/list-view/index.ios.ts index bb609d0e5f..3dff3a77c3 100644 --- a/packages/core/ui/list-view/index.ios.ts +++ b/packages/core/ui/list-view/index.ios.ts @@ -105,6 +105,7 @@ function notifyForItemAtIndex(listView: ListViewBase, cell: any, view: View, eve eventName: eventName, object: listView, index: indexPath.row, + section: indexPath.section, view: view, ios: cell, android: undefined, diff --git a/packages/core/ui/split-view/index.android.ts b/packages/core/ui/split-view/index.android.ts new file mode 100644 index 0000000000..74165742e0 --- /dev/null +++ b/packages/core/ui/split-view/index.android.ts @@ -0,0 +1,7 @@ +import { SplitViewBase } from './split-view-common'; + +export { SplitBehavior, SplitRole, SplitStyle, SplitDisplayMode } from './split-view-common'; + +export class SplitView extends SplitViewBase { + // Android does not have a native SplitViewController equivalent. +} diff --git a/packages/core/ui/split-view/index.d.ts b/packages/core/ui/split-view/index.d.ts new file mode 100644 index 0000000000..25382fbcfa --- /dev/null +++ b/packages/core/ui/split-view/index.d.ts @@ -0,0 +1,11 @@ +import { SplitViewBase } from './split-view-common'; + +export type { SplitBehavior, SplitRole, SplitStyle, SplitDisplayMode } from './split-view-common'; + +/** + * iOS UISplitViewController-backed container. + * On Android, acts as a simple container. + * + * @nsView SplitView + */ +export class SplitView extends SplitViewBase {} diff --git a/packages/core/ui/split-view/index.ios.ts b/packages/core/ui/split-view/index.ios.ts new file mode 100644 index 0000000000..fc46ffd639 --- /dev/null +++ b/packages/core/ui/split-view/index.ios.ts @@ -0,0 +1,420 @@ +import { SplitViewBase, SplitRole, displayModeProperty, splitBehaviorProperty, preferredPrimaryColumnWidthFractionProperty, preferredSupplementaryColumnWidthFractionProperty, preferredInspectorColumnWidthFractionProperty } from './split-view-common'; +import { View } from '../core/view'; +import { layout } from '../../utils'; +import { SDK_VERSION } from '../../utils/constants'; + +@NativeClass +class UISplitViewControllerDelegateImpl extends NSObject implements UISplitViewControllerDelegate { + public static ObjCProtocols = [UISplitViewControllerDelegate]; + static ObjCExposedMethods = { + toggleInspector: { returns: interop.types.void, params: [] }, + }; + private _owner: WeakRef; + + public static initWithOwner(owner: WeakRef): UISplitViewControllerDelegateImpl { + const d = UISplitViewControllerDelegateImpl.new(); + d._owner = owner; + return d; + } + + splitViewControllerCollapseSecondaryViewControllerOntoPrimaryViewController(splitViewController: UISplitViewController, secondaryViewController: UIViewController, primaryViewController: UIViewController): boolean { + const owner = this._owner.deref(); + if (owner) { + // Notify the owner about the collapse action + owner.onSecondaryViewCollapsed(secondaryViewController, primaryViewController); + } + return true; + } + + splitViewControllerDidCollapse(svc: UISplitViewController): void { + // Can be used to notify owner if needed + } + + splitViewControllerDidExpand(svc: UISplitViewController): void { + // Can be used to notify owner if needed + } + + splitViewControllerDidHideColumn(svc: UISplitViewController, column: UISplitViewControllerColumn): void { + // Can be used to notify owner if needed + } + + splitViewControllerDidShowColumn(svc: UISplitViewController, column: UISplitViewControllerColumn): void { + // Can be used to notify owner if needed + } + + splitViewControllerDisplayModeForExpandingToProposedDisplayMode(svc: UISplitViewController, proposedDisplayMode: UISplitViewControllerDisplayMode): UISplitViewControllerDisplayMode { + return UISplitViewControllerDisplayMode.TwoBesideSecondary; + } + + splitViewControllerTopColumnForCollapsingToProposedTopColumn(svc: UISplitViewController, proposedTopColumn: UISplitViewControllerColumn): UISplitViewControllerColumn { + return UISplitViewControllerColumn.Secondary; + } + + toggleInspector(): void { + const owner = this._owner.deref(); + if (owner) { + if (owner.inspectorShowing) { + owner.hideInspector(); + } else { + owner.showInspector(); + } + } + } +} + +export class SplitView extends SplitViewBase { + static instance: SplitView; + static getInstance(): SplitViewBase | null { + return SplitView.instance; + } + + public viewController: UISplitViewController; + private _delegate: UISplitViewControllerDelegateImpl; + // Keep role -> controller + private _controllers = new Map(); + private _children = new Map(); + inspectorShowing = false; + + constructor() { + super(); + this.viewController = UISplitViewController.alloc().initWithStyle(this._getSplitStyle()); + } + + createNativeView() { + SplitView.instance = this; + this._delegate = UISplitViewControllerDelegateImpl.initWithOwner(new WeakRef(this)); + this.viewController.delegate = this._delegate; + this.viewController.presentsWithGesture = true; + + // Apply initial preferences + this._applyPreferences(); + + return this.viewController.view; + } + + disposeNativeView(): void { + super.disposeNativeView(); + this._controllers.clear(); + this._children.clear(); + this.viewController = null; + this._delegate = null; + } + + private _getSplitStyle() { + switch (SplitView.SplitStyle) { + case 'triple': + return UISplitViewControllerStyle.TripleColumn; + default: + // default to double always + return UISplitViewControllerStyle.DoubleColumn; + } + } + + // Controller-backed container: intercept native tree operations + public _addViewToNativeVisualTree(child: SplitViewBase, atIndex: number): boolean { + const role = this._resolveRoleForChild(child, atIndex); + const controller = this._ensureControllerForChild(child); + this._children.set(role, child); + this._controllers.set(role, controller); + this._syncControllers(); + return true; + } + + public _removeViewFromNativeVisualTree(child: View): void { + const role = this._findRoleByChild(child); + if (role) { + this._children.delete(role); + this._controllers.delete(role); + this._syncControllers(); + } + super._removeViewFromNativeVisualTree(child); + } + + public onMeasure(widthMeasureSpec: number, heightMeasureSpec: number): void { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + const width = layout.getMeasureSpecSize(widthMeasureSpec); + const widthMode = layout.getMeasureSpecMode(widthMeasureSpec); + const height = layout.getMeasureSpecSize(heightMeasureSpec); + const heightMode = layout.getMeasureSpecMode(heightMeasureSpec); + + const horizontal = this.effectivePaddingLeft + this.effectivePaddingRight + this.effectiveBorderLeftWidth + this.effectiveBorderRightWidth; + const vertical = this.effectivePaddingTop + this.effectivePaddingBottom + this.effectiveBorderTopWidth + this.effectiveBorderBottomWidth; + + const measuredWidth = Math.max(widthMode === layout.UNSPECIFIED ? 0 : width, this.effectiveMinWidth) + (widthMode === layout.UNSPECIFIED ? horizontal : 0); + const measuredHeight = Math.max(heightMode === layout.UNSPECIFIED ? 0 : height, this.effectiveMinHeight) + (heightMode === layout.UNSPECIFIED ? vertical : 0); + + const widthAndState = View.resolveSizeAndState(measuredWidth, width, widthMode, 0); + const heightAndState = View.resolveSizeAndState(measuredHeight, height, heightMode, 0); + this.setMeasuredDimension(widthAndState, heightAndState); + } + + public onRoleChanged(view: View, oldValue: SplitRole, newValue: SplitRole) { + // Move child mapping to new role and resync + const oldRole = this._findRoleByChild(view); + if (oldRole) { + const controller = this._controllers.get(oldRole); + this._controllers.delete(oldRole); + this._children.delete(oldRole); + if (controller) { + this._controllers.set(newValue, controller); + } + this._children.set(newValue, view); + this._syncControllers(); + } + } + + onSecondaryViewCollapsed(secondaryViewController: UIViewController, primaryViewController: UIViewController): void { + // Default implementation: do nothing. + // Subclasses may override to customize behavior when secondary is collapsed onto primary. + } + + showPrimary(): void { + if (!this.viewController) return; + this.viewController.showColumn(UISplitViewControllerColumn.Primary); + } + + hidePrimary(): void { + if (!this.viewController) return; + this.viewController.hideColumn(UISplitViewControllerColumn.Primary); + } + + showSecondary(): void { + if (!this.viewController) return; + this.viewController.showColumn(UISplitViewControllerColumn.Secondary); + } + + hideSecondary(): void { + if (!this.viewController) return; + this.viewController.hideColumn(UISplitViewControllerColumn.Secondary); + } + + showSupplementary(): void { + if (!this.viewController) return; + this.viewController.showColumn(UISplitViewControllerColumn.Supplementary); + } + + showInspector(): void { + if (!this.viewController) return; + // Guard for older OS versions by feature-detecting inspector-related API + if (this.viewController.preferredInspectorColumnWidthFraction !== undefined) { + this.viewController.showColumn(UISplitViewControllerColumn.Inspector); + this.notifyInspectorChange(true); + } + } + + hideInspector(): void { + if (!this.viewController) return; + if (this.viewController.preferredInspectorColumnWidthFraction !== undefined) { + this.viewController.hideColumn(UISplitViewControllerColumn.Inspector); + this.notifyInspectorChange(false); + } + } + + notifyInspectorChange(showing: boolean): void { + this.inspectorShowing = showing; + this.notify({ + eventName: 'inspectorChange', + object: this, + data: { showing }, + }); + } + + private _resolveRoleForChild(child: SplitViewBase, atIndex: number): SplitRole { + const explicit = SplitViewBase.getRole(child); + if (explicit) { + return explicit; + } + // Fallback by index if no explicit role set + return this._roleByIndex(atIndex) as SplitRole; + } + + private _findRoleByChild(child: View): SplitRole | null { + for (const [role, c] of this._children.entries()) { + if (c === child) { + return role; + } + } + return null; + } + + private _ensureControllerForChild(child: View): UIViewController { + // If child is controller-backed (Page/Frame/etc.), reuse its controller + const vc = (child.ios instanceof UIViewController ? (child.ios as any) : (child as any).viewController) as UIViewController | null; + if (vc) { + return vc; + } + // Fallback: basic wrapper (not expected in current usage where children are Frames/Pages) + const wrapper = UIViewController.new(); + if (!wrapper.view) { + wrapper.view = UIView.new(); + } + if (child.nativeViewProtected) { + wrapper.view.addSubview(child.nativeViewProtected); + } + return wrapper; + } + + private _attachInspectorButton(): void { + const inspector = this._controllers.get('inspector'); + if (!(inspector instanceof UINavigationController)) { + return; + } + + const targetVC = inspector.topViewController; + if (!targetVC) { + // Subscribe to Frame event to know when the top VC is shown, then attach the button. + // Can only attach buttons once VC is available + const frameChild = this._children.get('inspector') as any; + if (frameChild && frameChild.on && !frameChild._inspectorVCShownHandler) { + frameChild._inspectorVCShownHandler = () => { + setTimeout(() => this._attachInspectorButton()); + }; + frameChild.on('viewControllerShown', frameChild._inspectorVCShownHandler.bind(this)); + } + return; + } + + // Avoid duplicates + if (targetVC.navigationItem.rightBarButtonItem) { + return; + } + + // TODO: can provide properties to customize this + const cfg = UIImageSymbolConfiguration.configurationWithPointSizeWeightScale(18, UIImageSymbolWeight.Regular, UIImageSymbolScale.Medium); + const image = UIImage.systemImageNamedWithConfiguration('sidebar.trailing', cfg); + const item = UIBarButtonItem.alloc().initWithImageStyleTargetAction(image, UIBarButtonItemStyle.Plain, this._delegate, 'toggleInspector'); + targetVC.navigationItem.rightBarButtonItem = item; + } + + private _syncControllers(): void { + if (!this.viewController) { + return; + } + // Prefer modern API if present; otherwise fall back to setting viewControllers array + const primary = this._controllers.get('primary'); + const secondary = this._controllers.get('secondary'); + const supplementary = this._controllers.get('supplementary'); + const inspector = this._controllers.get('inspector'); + + if (primary) { + this.viewController.setViewControllerForColumn(primary, UISplitViewControllerColumn.Primary); + } + if (secondary) { + this.viewController.setViewControllerForColumn(secondary, UISplitViewControllerColumn.Secondary); + } + if (supplementary) { + this.viewController.setViewControllerForColumn(supplementary, UISplitViewControllerColumn.Supplementary); + } + if (inspector) { + if (this.viewController.preferredInspectorColumnWidthFraction !== undefined) { + this.viewController.setViewControllerForColumn(inspector, UISplitViewControllerColumn.Inspector); + } + } + + this._applyPreferences(); + } + + private _applyPreferences(): void { + if (!this.viewController) { + return; + } + + // displayMode + let preferredDisplayMode = UISplitViewControllerDisplayMode.Automatic; + switch (this.displayMode) { + case 'secondaryOnly': + preferredDisplayMode = UISplitViewControllerDisplayMode.SecondaryOnly; + break; + case 'oneBesideSecondary': + preferredDisplayMode = UISplitViewControllerDisplayMode.OneBesideSecondary; + break; + case 'oneOverSecondary': + preferredDisplayMode = UISplitViewControllerDisplayMode.OneOverSecondary; + break; + case 'twoBesideSecondary': + preferredDisplayMode = UISplitViewControllerDisplayMode.TwoBesideSecondary; + break; + case 'twoOverSecondary': + preferredDisplayMode = UISplitViewControllerDisplayMode.TwoOverSecondary; + break; + case 'twoDisplaceSecondary': + preferredDisplayMode = UISplitViewControllerDisplayMode.TwoDisplaceSecondary; + break; + } + this.viewController.preferredDisplayMode = preferredDisplayMode; + + // splitBehavior (iOS 14+) + const sb = this.splitBehavior; + let preferredSplitBehavior = UISplitViewControllerSplitBehavior.Automatic; + switch (sb) { + case 'tile': + preferredSplitBehavior = UISplitViewControllerSplitBehavior.Tile; + break; + case 'overlay': + preferredSplitBehavior = UISplitViewControllerSplitBehavior.Overlay ?? UISplitViewControllerSplitBehavior.Automatic; + break; + case 'displace': + preferredSplitBehavior = UISplitViewControllerSplitBehavior.Displace ?? UISplitViewControllerSplitBehavior.Automatic; + break; + } + this.viewController.preferredSplitBehavior = preferredSplitBehavior; + + const primary = this._controllers.get('primary'); + const secondary = this._controllers.get('secondary'); + const supplementary = this._controllers.get('supplementary'); + const inspector = this._controllers.get('inspector'); + if (secondary instanceof UINavigationController && secondary.navigationItem) { + // TODO: can add properties to customize this + secondary.navigationItem.leftBarButtonItem = this.viewController.displayModeButtonItem; + secondary.navigationItem.leftItemsSupplementBackButton = true; + } + if (supplementary) { + this.showSupplementary(); + } + if (inspector) { + this.showInspector(); + // Ensure the inspector column gets its toggle button as soon as the first page is shown + this._attachInspectorButton(); + } + + // Width fractions + if (typeof this.preferredPrimaryColumnWidthFraction === 'number' && !isNaN(this.preferredPrimaryColumnWidthFraction)) { + this.viewController.preferredPrimaryColumnWidthFraction = this.preferredPrimaryColumnWidthFraction; + } + if (SplitView.SplitStyle === 'triple') { + // supplementary applies in triple style + if (typeof this.preferredSupplementaryColumnWidthFraction === 'number' && !isNaN(this.preferredSupplementaryColumnWidthFraction)) { + this.viewController.preferredSupplementaryColumnWidthFraction = this.preferredSupplementaryColumnWidthFraction; + } + } + + if (SDK_VERSION >= 26) { + // Inspector width fraction + const inspectorWidth = this.preferredInspectorColumnWidthFraction; + if (typeof inspectorWidth === 'number' && !isNaN(inspectorWidth)) { + this.viewController.preferredInspectorColumnWidthFraction = inspectorWidth; + } + } + } + + [displayModeProperty.setNative](value: string) { + this._applyPreferences(); + } + + [splitBehaviorProperty.setNative](value: string) { + this._applyPreferences(); + } + + [preferredPrimaryColumnWidthFractionProperty.setNative](value: number) { + this._applyPreferences(); + } + + [preferredSupplementaryColumnWidthFractionProperty.setNative](value: number) { + this._applyPreferences(); + } + + [preferredInspectorColumnWidthFractionProperty.setNative](value: number) { + this._applyPreferences(); + } +} diff --git a/packages/core/ui/split-view/split-view-common.ts b/packages/core/ui/split-view/split-view-common.ts new file mode 100644 index 0000000000..6abef4f7c9 --- /dev/null +++ b/packages/core/ui/split-view/split-view-common.ts @@ -0,0 +1,156 @@ +import { LayoutBase } from '../layouts/layout-base'; +import { View, CSSType } from '../core/view'; +import { Property, makeParser, makeValidator } from '../core/properties'; + +export type SplitRole = 'primary' | 'secondary' | 'supplementary' | 'inspector'; +const splitRoleConverter = makeParser(makeValidator('primary', 'secondary', 'supplementary', 'inspector')); + +// Note: Using 'inspector' splitRole does not (yet) require a distinct style; it's an optional trailing column. +export type SplitStyle = 'automatic' | 'double' | 'triple'; + +export type SplitDisplayMode = 'automatic' | 'secondaryOnly' | 'oneBesideSecondary' | 'oneOverSecondary' | 'twoBesideSecondary' | 'twoOverSecondary' | 'twoDisplaceSecondary'; +const splitDisplayModeConverter = makeParser(makeValidator('automatic', 'secondaryOnly', 'oneBesideSecondary', 'oneOverSecondary', 'twoBesideSecondary', 'twoOverSecondary', 'twoDisplaceSecondary')); + +export type SplitBehavior = 'automatic' | 'tile' | 'overlay' | 'displace'; +const splitBehaviorConverter = makeParser(makeValidator('automatic', 'tile', 'overlay', 'displace')); + +// Default child roles (helps authoring without setting splitRole on children) +const ROLE_ORDER: SplitRole[] = ['primary', 'secondary', 'supplementary', 'inspector']; + +@CSSType('SplitView') +export class SplitViewBase extends LayoutBase { + /** + * The display style for the split view controller. + * Must be set before bootstrapping the app. + */ + static SplitStyle: SplitStyle; + + static getInstance(): SplitViewBase | null { + // Platform-specific implementations may override + return null; + } + + /** Child role (primary, secondary, supplementary, inspector) */ + splitRole: SplitRole; + /** Preferred display mode */ + displayMode: SplitDisplayMode; + /** Preferred split behavior (iOS 14+) */ + splitBehavior: SplitBehavior; + /** Primary column width fraction (0..1) */ + preferredPrimaryColumnWidthFraction: number; + /** Supplementary column width fraction (0..1, iOS 14+ triple) */ + preferredSupplementaryColumnWidthFraction: number; + /** Inspector column width fraction (0..1, iOS 17+/18+ when Inspector column available) */ + preferredInspectorColumnWidthFraction: number; + + /** + * Get child role (primary, secondary, supplementary, inspector) + */ + public static getRole(element: SplitViewBase): SplitRole { + return element.splitRole; + } + + /** + * Set child role (primary, secondary, supplementary, inspector) + */ + public static setRole(element: SplitViewBase, value: SplitRole): void { + element.splitRole = value; + } + + // Called when a child's role changes; platform impls may override + public onRoleChanged(view: View, oldValue: SplitRole, newValue: SplitRole) { + this.requestLayout(); + } + + showPrimary() { + // Platform-specific implementations may override + } + + hidePrimary() { + // Platform-specific implementations may override + } + + showSecondary() { + // Platform-specific implementations may override + } + + hideSecondary() { + // Platform-specific implementations may override + } + + showSupplementary() { + // Platform-specific implementations may override + } + + hideSupplementary() { + // Platform-specific implementations may override + } + + showInspector() { + // Platform-specific implementations may override + } + + hideInspector() { + // Platform-specific implementations may override + } + + // Utility to infer a role by index when none specified + protected _roleByIndex(index: number): SplitRole { + return ROLE_ORDER[Math.max(0, Math.min(index, ROLE_ORDER.length - 1))]; + } +} + +SplitViewBase.prototype.recycleNativeView = 'auto'; + +export const splitRoleProperty = new Property({ + name: 'splitRole', + defaultValue: 'primary', + valueChanged: (target, oldValue, newValue) => { + const parent = target.parent; + if (parent instanceof SplitViewBase) { + parent.onRoleChanged(target, oldValue, newValue); + } + }, + valueConverter: splitRoleConverter, +}); +splitRoleProperty.register(View); + +export const displayModeProperty = new Property({ + name: 'displayMode', + defaultValue: 'automatic', + affectsLayout: __APPLE__, + valueConverter: splitDisplayModeConverter, +}); +displayModeProperty.register(SplitViewBase); + +export const splitBehaviorProperty = new Property({ + name: 'splitBehavior', + defaultValue: 'automatic', + affectsLayout: __APPLE__, + valueConverter: splitBehaviorConverter, +}); +splitBehaviorProperty.register(SplitViewBase); + +export const preferredPrimaryColumnWidthFractionProperty = new Property({ + name: 'preferredPrimaryColumnWidthFraction', + defaultValue: 0, + affectsLayout: __APPLE__, + valueConverter: (v) => Math.max(0, Math.min(1, parseFloat(v))), +}); +preferredPrimaryColumnWidthFractionProperty.register(SplitViewBase); + +export const preferredSupplementaryColumnWidthFractionProperty = new Property({ + name: 'preferredSupplementaryColumnWidthFraction', + defaultValue: 0, + affectsLayout: __APPLE__, + valueConverter: (v) => Math.max(0, Math.min(1, parseFloat(v))), +}); +preferredSupplementaryColumnWidthFractionProperty.register(SplitViewBase); + +export const preferredInspectorColumnWidthFractionProperty = new Property({ + name: 'preferredInspectorColumnWidthFraction', + defaultValue: 0, + affectsLayout: __APPLE__, + valueConverter: (v) => Math.max(0, Math.min(1, parseFloat(v))), +}); +preferredInspectorColumnWidthFractionProperty.register(SplitViewBase);