├── .editorconfig
├── .eslintrc
├── .github
└── issue_template.md
├── .gitignore
├── .npsrc
├── .prettierignore
├── .prettierrc
├── .prettierrc.json
├── .travis.yml
├── .vscode
├── launch.json
└── settings.json
├── LICENSE
├── Notes.md
├── README.md
├── apps
├── demo-angular
│ ├── .gitignore
│ ├── nativescript.config.ts
│ ├── package.json
│ ├── references.d.ts
│ ├── src
│ │ ├── app-routing.module.ts
│ │ ├── app.component.ts
│ │ ├── app.css
│ │ ├── app.module.ts
│ │ ├── home.component.html
│ │ ├── home.component.ts
│ │ ├── main.ts
│ │ └── plugin-demos
│ │ │ ├── .gitkeep
│ │ │ ├── nativescript-accessibility-ext.component.html
│ │ │ ├── nativescript-accessibility-ext.component.ts
│ │ │ └── nativescript-accessibility-ext.module.ts
│ ├── tsconfig.json
│ └── webpack.config.js
└── demo
│ ├── .gitignore
│ ├── nativescript.config.ts
│ ├── package.json
│ ├── references.d.ts
│ ├── src
│ ├── app-root.xml
│ ├── app.css
│ ├── app.ts
│ ├── main-page.ts
│ ├── main-page.xml
│ ├── main-view-model.ts
│ └── plugin-demos
│ │ ├── .gitkeep
│ │ ├── nativescript-accessibility-ext.ts
│ │ └── nativescript-accessibility-ext.xml
│ └── tsconfig.json
├── jest.config.js
├── nx.json
├── package-lock.json
├── package.json
├── packages
├── .gitkeep
└── nativescript-accessibility-ext
│ ├── Angular.md
│ ├── FontScaling.md
│ ├── Notes.md
│ ├── README.md
│ ├── angular
│ ├── data
│ │ ├── a11y-font-scaling.ts
│ │ └── a11y-service-enabled.ts
│ ├── directives
│ │ └── a11y-grid-layout.directive.ts
│ ├── index.ts
│ ├── package.json
│ ├── pipes
│ │ └── a11y-font-scaling.pipe.ts
│ ├── services
│ │ └── base.service.ts
│ └── tsconfig.angular.json
│ ├── index.ts
│ ├── package.json
│ ├── references.d.ts
│ ├── scss
│ ├── _fontscales.scss
│ ├── a11y-helpers.scss
│ ├── a11y.compat.scss
│ ├── a11y.scss
│ └── core
│ │ ├── _controls.scss
│ │ ├── _headings.scss
│ │ ├── _index.scss
│ │ ├── _utilities.scss
│ │ └── utilities
│ │ ├── _index.scss
│ │ └── _text.scss
│ ├── trace.ts
│ ├── tsconfig.json
│ ├── ui
│ ├── action-bar
│ │ ├── action-bar.android.ts
│ │ ├── action-bar.d.ts
│ │ └── action-bar.ios.ts
│ ├── core
│ │ ├── view-common.ts
│ │ ├── view.android.ts
│ │ ├── view.d.ts
│ │ └── view.ios.ts
│ ├── css-classes-helper.ts
│ ├── index.ts
│ ├── page
│ │ ├── page-common.ts
│ │ ├── page.android.ts
│ │ ├── page.d.ts
│ │ └── page.ios.ts
│ └── slider
│ │ ├── slider-common.ts
│ │ ├── slider.android.ts
│ │ ├── slider.d.ts
│ │ └── slider.ios.ts
│ └── utils
│ ├── accessibility-helper.android.ts
│ ├── accessibility-helper.d.ts
│ ├── accessibility-helper.ios.ts
│ ├── fontscale-observable.android.ts
│ ├── fontscale-observable.d.ts
│ ├── fontscale-observable.ios.ts
│ ├── global-events.ts
│ ├── helpers.ts
│ ├── index.ts
│ ├── utils-common.ts
│ ├── utils.android.ts
│ ├── utils.d.ts
│ └── utils.ios.ts
├── references.d.ts
├── tools
├── assets
│ ├── App_Resources
│ │ ├── Android
│ │ │ ├── app.gradle
│ │ │ └── src
│ │ │ │ └── main
│ │ │ │ ├── AndroidManifest.xml
│ │ │ │ └── res
│ │ │ │ ├── drawable-hdpi
│ │ │ │ ├── background.png
│ │ │ │ ├── icon.png
│ │ │ │ └── logo.png
│ │ │ │ ├── drawable-ldpi
│ │ │ │ ├── background.png
│ │ │ │ ├── icon.png
│ │ │ │ └── logo.png
│ │ │ │ ├── drawable-mdpi
│ │ │ │ ├── background.png
│ │ │ │ ├── icon.png
│ │ │ │ └── logo.png
│ │ │ │ ├── drawable-nodpi
│ │ │ │ └── splash_screen.xml
│ │ │ │ ├── drawable-xhdpi
│ │ │ │ ├── background.png
│ │ │ │ ├── icon.png
│ │ │ │ └── logo.png
│ │ │ │ ├── drawable-xxhdpi
│ │ │ │ ├── background.png
│ │ │ │ ├── icon.png
│ │ │ │ └── logo.png
│ │ │ │ ├── drawable-xxxhdpi
│ │ │ │ ├── background.png
│ │ │ │ ├── icon.png
│ │ │ │ └── logo.png
│ │ │ │ ├── values-v21
│ │ │ │ ├── colors.xml
│ │ │ │ └── styles.xml
│ │ │ │ └── values
│ │ │ │ ├── colors.xml
│ │ │ │ └── styles.xml
│ │ └── iOS
│ │ │ ├── Assets.xcassets
│ │ │ ├── AppIcon.appiconset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── icon-1024.png
│ │ │ │ ├── icon-20.png
│ │ │ │ ├── icon-20@2x.png
│ │ │ │ ├── icon-20@3x.png
│ │ │ │ ├── icon-29.png
│ │ │ │ ├── icon-29@2x.png
│ │ │ │ ├── icon-29@3x.png
│ │ │ │ ├── icon-40.png
│ │ │ │ ├── icon-40@2x.png
│ │ │ │ ├── icon-40@3x.png
│ │ │ │ ├── icon-60@2x.png
│ │ │ │ ├── icon-60@3x.png
│ │ │ │ ├── icon-76.png
│ │ │ │ ├── icon-76@2x.png
│ │ │ │ └── icon-83.5@2x.png
│ │ │ ├── Contents.json
│ │ │ ├── LaunchScreen.AspectFill.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── LaunchScreen-AspectFill.png
│ │ │ │ ├── LaunchScreen-AspectFill@2x.png
│ │ │ │ └── LaunchScreen-AspectFill@3x.png
│ │ │ └── LaunchScreen.Center.imageset
│ │ │ │ ├── Contents.json
│ │ │ │ ├── LaunchScreen-Center.png
│ │ │ │ ├── LaunchScreen-Center@2x.png
│ │ │ │ └── LaunchScreen-Center@3x.png
│ │ │ ├── Info.plist
│ │ │ ├── LaunchScreen.storyboard
│ │ │ ├── build.xcconfig
│ │ │ ├── icon.png
│ │ │ └── icon@2x.png
│ ├── README.md
│ └── publishing
│ │ └── .npmignore
├── demo
│ ├── index.ts
│ ├── nativescript-accessibility-ext
│ │ └── index.ts
│ ├── references.d.ts
│ ├── tsconfig.json
│ └── utils
│ │ ├── demo-base.ts
│ │ └── index.ts
├── schematics
│ └── .gitkeep
├── scripts
│ └── build-finish.ts
├── tsconfig.tools.json
└── workspace-scripts.js
├── tsconfig.base.json
└── workspace.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parser": "@typescript-eslint/parser",
4 | "parserOptions": {
5 | "ecmaVersion": 2018,
6 | "sourceType": "module",
7 | "project": "./tsconfig.base.json"
8 | },
9 | "ignorePatterns": ["**/*"],
10 | "plugins": ["@typescript-eslint", "@nrwl/nx"],
11 | "extends": [
12 | "eslint:recommended",
13 | "plugin:@typescript-eslint/eslint-recommended",
14 | "plugin:@typescript-eslint/recommended",
15 | "prettier",
16 | "prettier/@typescript-eslint"
17 | ],
18 | "rules": {
19 | "@typescript-eslint/explicit-member-accessibility": "off",
20 | "@typescript-eslint/explicit-function-return-type": "off",
21 | "@typescript-eslint/no-parameter-properties": "off",
22 | "@nrwl/nx/enforce-module-boundaries": [
23 | "error",
24 | {
25 | "enforceBuildableLibDependency": true,
26 | "allow": [],
27 | "depConstraints": [
28 | { "sourceTag": "*", "onlyDependOnLibsWithTags": ["*"] }
29 | ]
30 | }
31 | ]
32 | },
33 | "overrides": [
34 | {
35 | "files": ["*.tsx"],
36 | "rules": {
37 | "@typescript-eslint/no-unused-vars": "off"
38 | }
39 | }
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------
/.github/issue_template.md:
--------------------------------------------------------------------------------
1 | ### Make sure to check the demo app(s) for sample usage
2 |
3 | ### Make sure to check the existing issues in this repository
4 |
5 | ### If the demo apps cannot help and there is no issue for your problem, tell us about it
6 | Please, ensure your title is less than 63 characters long and starts with a capital
7 | letter.
8 |
9 | ### Which platform(s) does your issue occur on?
10 | - iOS/Android/Both
11 | - iOS/Android versions
12 | - emulator or device. What type of device?
13 |
14 | ### Please, provide the following version numbers that your issue occurs with:
15 |
16 | - CLI: (run `tns --version` to fetch it)
17 | - Cross-platform modules: (check the 'version' attribute in the
18 | `node_modules/tns-core-modules/package.json` file in your project)
19 | - Runtime(s): (look for the `"tns-android"` and `"tns-ios"` properties in the `package.json` file of your project)
20 | - Plugin(s): (look for the version numbers in the `package.json` file of your
21 | project and paste your dependencies and devDependencies here)
22 |
23 | ### Please, tell us how to recreate the issue in as much detail as possible.
24 | Describe the steps to reproduce it.
25 |
26 | ### Is there any code involved?
27 | - provide a code example to recreate the problem
28 | - (EVEN BETTER) provide a .zip with application or refer to a repository with application where the problem is reproducible.
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 |
8 | # dependencies
9 | node_modules
10 | package-lock.json
11 | yarn.lock
12 |
13 | # IDEs and editors
14 | /.idea
15 | .project
16 | .classpath
17 | .c9/
18 | *.launch
19 | .settings/
20 | *.sublime-workspace
21 |
22 | # IDE - VSCode
23 | .vscode/*
24 | !.vscode/settings.json
25 | !.vscode/tasks.json
26 | !.vscode/launch.json
27 | !.vscode/extensions.json
28 |
29 | # misc
30 | /.sass-cache
31 | /connect.lock
32 | /coverage
33 | /libpeerconnection.log
34 | npm-debug.log
35 | yarn-error.log
36 | testem.log
37 | /typings
38 |
39 | # System Files
40 | .DS_Store
41 | Thumbs.db
42 |
43 | *.tgz
44 | packages/**/angular/dist
45 |
--------------------------------------------------------------------------------
/.npsrc:
--------------------------------------------------------------------------------
1 | {
2 | "config": "./tools/workspace-scripts.js"
3 | }
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Add files here to ignore them from prettier formatting
2 |
3 | /dist
4 | /coverage
5 | native-src
6 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "always",
3 | "bracketSpacing": true,
4 | "printWidth": 160,
5 | "semi": true,
6 | "singleQuote": true,
7 | "tabWidth": 2,
8 | "trailingComma": "all"
9 | }
10 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "always",
3 | "bracketSpacing": true,
4 | "printWidth": 160,
5 | "semi": true,
6 | "singleQuote": true,
7 | "tabWidth": 2,
8 | "trailingComma": "all"
9 | }
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "node"
4 | script:
5 | - npm run setup
6 | - npm start @nota.build-all
7 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Launch on iOS",
9 | "type": "nativescript",
10 | "request": "launch",
11 | "platform": "ios",
12 | "appRoot": "${workspaceRoot}/demo",
13 | "sourceMaps": true,
14 | "watch": true
15 | },
16 | {
17 | "name": "Test on iOS",
18 | "type": "nativescript",
19 | "request": "launch",
20 | "platform": "ios",
21 | "appRoot": "${workspaceRoot}/demo",
22 | "sourceMaps": true,
23 | "watch": false,
24 | "stopOnEntry": true,
25 | "launchTests": true,
26 | "tnsArgs": [
27 | "--justlaunch"
28 | ]
29 | },
30 | {
31 | "name": "Attach on iOS",
32 | "type": "nativescript",
33 | "request": "attach",
34 | "platform": "ios",
35 | "appRoot": "${workspaceRoot}/demo",
36 | "sourceMaps": true,
37 | "watch": false
38 | },
39 | {
40 | "name": "Launch on Android",
41 | "type": "nativescript",
42 | "request": "launch",
43 | "platform": "android",
44 | "appRoot": "${workspaceRoot}/demo",
45 | "sourceMaps": true,
46 | "watch": true
47 | },
48 | {
49 | "name": "Test on Android",
50 | "type": "nativescript",
51 | "request": "launch",
52 | "platform": "android",
53 | "appRoot": "${workspaceRoot}/demo",
54 | "sourceMaps": true,
55 | "watch": false,
56 | "stopOnEntry": true,
57 | "launchTests": true,
58 | "tnsArgs": [
59 | "--justlaunch"
60 | ]
61 | },
62 | {
63 | "name": "Attach on Android",
64 | "type": "nativescript",
65 | "request": "attach",
66 | "platform": "android",
67 | "appRoot": "${workspaceRoot}/demo",
68 | "sourceMaps": true,
69 | "watch": false
70 | }
71 | ]
72 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "[typescript]":{
3 | "editor.formatOnSave": true
4 | },
5 | "files.exclude": {
6 | "**/platforms": true,
7 | "**/node_modules": true,
8 | "**/.git": true,
9 | "**/.svn": true,
10 | "**/.hg": true,
11 | "**/CVS": true,
12 | "**/.DS_Store": true,
13 | "**/*.js": {
14 | "when": "$(basename).ts"
15 | },
16 | "**/*.css": {
17 | "when": "$(basename).scss"
18 | },
19 | "src/css": true
20 | },
21 | "search.exclude": {
22 | "{demo-ng,demo}/platforms/**": true,
23 | "{demo-ng,demo}/platforms/**/*": true,
24 | "**/node_modules": true,
25 | "**/bower_components": true
26 | },
27 | "files.watcherExclude": {
28 | "**/.git/objects/**": true,
29 | "**/.git/subtree-cache/**": true,
30 | "{demo-ng,demo}/platforms/**": true,
31 | "{demo-ng,demo}/platforms/**/*": true,
32 | "**/node_modules/**": true,
33 | "**/node_modules/**/*": true
34 | },
35 | "cSpell.words": [
36 | "Alcácer",
37 | "Aleix",
38 | "Arda",
39 | "Bitmask",
40 | "Cillessen",
41 | "Codegen",
42 | "Combobox",
43 | "Digne",
44 | "Flexbox",
45 | "Focusable",
46 | "Freiling",
47 | "Gomes",
48 | "Iniesta",
49 | "Jordi",
50 | "Mascherano",
51 | "Masip",
52 | "Mathieu",
53 | "Messi",
54 | "Morten",
55 | "Newable",
56 | "Neymar",
57 | "Paco",
58 | "Rafinha",
59 | "Rakitic",
60 | "Recycler",
61 | "Sergi",
62 | "Sjøgren",
63 | "Stegen",
64 | "Umtiti",
65 | "Unscoped",
66 | "androidview",
67 | "androidx",
68 | "appcompat",
69 | "armeabi",
70 | "assistive",
71 | "ayfs",
72 | "bootstrapper",
73 | "codelyzer",
74 | "compat",
75 | "ddfreiling",
76 | "devkit",
77 | "devtool",
78 | "downlevel",
79 | "fontscale",
80 | "fontscales",
81 | "globalevents",
82 | "gmail",
83 | "inputmethodservice",
84 | "mabs",
85 | "moduleid",
86 | "nativescript",
87 | "ngfactory",
88 | "ngmodule",
89 | "ngstyle",
90 | "ngtools",
91 | "nospace",
92 | "nsconfig",
93 | "oraclejdk",
94 | "pathinfo",
95 | "prettierrc",
96 | "quotemark",
97 | "radiobtn",
98 | "radiobutton",
99 | "ruleset",
100 | "sasswatch",
101 | "selectable",
102 | "sidedrawer",
103 | "softnav",
104 | "toplevel",
105 | "tscwatch",
106 | "uglifyjs",
107 | "weblink",
108 | "xcode",
109 | "ydemo"
110 | ]
111 | }
112 |
--------------------------------------------------------------------------------
/Notes.md:
--------------------------------------------------------------------------------
1 | # Implementing iOS accessibility features
2 |
3 | On iOS certain accessibility features requires us to define functions to the Native-objects.
4 |
5 | ## Functions:
6 |
7 | ### accessibilityActivate()
8 | Tells the element to activate itself and report the success or failure of the operation.
9 |
10 | ### accessibilityIncrement()
11 | Tells the accessibility element to increment the value of its content.
12 |
13 | User action: A one-finger swipe up that increments a value in an element.
14 |
15 | ### accessibilityDecrement()
16 | Tells the accessibility element to decrement the value of its content.
17 |
18 | User action: A one-finger swipe down that decrements a value in an element.
19 |
20 | ### accessibilityScroll(UIAccessibilityScrollDirection)
21 | Scrolls screen content in an application-specific way and returns the success or failure of the action.
22 |
23 | User action: A three-finger swipe that scrolls content vertically or horizontally.
24 |
25 | ### accessibilityPerformEscape()
26 | Dismisses a modal view and returns the success or failure of the action.
27 |
28 | User action: A two-finger Z-shaped gesture that dismisses a modal dialog, or goes back one level in a navigation hierarchy.
29 | Returns true, if we're handling the escape.
30 | Dev note: Unlike **accessibilityPerformMagicTap()** this cannot be added to the AppDelegate in NativeScript.
31 |
32 | ### accessibilityPerformMagicTap()
33 | Performs a salient action.
34 |
35 | User action: A two-finger double-tap that performs the most-intended action.
36 | This could be play/pause in an audio-player or pick/hang up in a phone-app.
37 | Dev note: Can be added to the AppDelegate in NativeScript.
38 |
39 | ## Links
40 | https://developer.apple.com/documentation/uikit/accessibility/uiaccessibilityaction
41 | https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/SupportingAccessibility.html
42 |
--------------------------------------------------------------------------------
/apps/demo-angular/.gitignore:
--------------------------------------------------------------------------------
1 | hooks
2 | platforms
--------------------------------------------------------------------------------
/apps/demo-angular/nativescript.config.ts:
--------------------------------------------------------------------------------
1 | import { NativeScriptConfig } from '@nativescript/core';
2 |
3 | export default {
4 | id: 'org.nativescript.plugindemoangular',
5 | appResourcesPath: '../../tools/assets/App_Resources',
6 | android: {
7 | v8Flags: '--expose_gc',
8 | markingMode: 'none',
9 | },
10 | appPath: 'src',
11 | } as NativeScriptConfig;
12 |
--------------------------------------------------------------------------------
/apps/demo-angular/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "main.js",
3 | "dependencies": {
4 | "@angular/animations": "file:../../node_modules/@angular/animations",
5 | "@angular/common": "file:../../node_modules/@angular/common",
6 | "@angular/compiler": "file:../../node_modules/@angular/compiler",
7 | "@angular/core": "file:../../node_modules/@angular/core",
8 | "@angular/forms": "file:../../node_modules/@angular/forms",
9 | "@angular/platform-browser": "file:../../node_modules/@angular/platform-browser",
10 | "@angular/platform-browser-dynamic": "file:../../node_modules/@angular/platform-browser-dynamic",
11 | "@angular/router": "file:../../node_modules/@angular/router",
12 | "@nativescript/angular": "file:../../node_modules/@nativescript/angular",
13 | "@nativescript/core": "file:../../node_modules/@nativescript/core",
14 | "nativescript-theme-core": "file:../../node_modules/nativescript-theme-core",
15 | "reflect-metadata": "file:../../node_modules/reflect-metadata",
16 | "rxjs": "file:../../node_modules/rxjs",
17 | "zone.js": "file:../../node_modules/zone.js",
18 | "@nota/nativescript-accessibility-ext": "file:../../dist/packages/nativescript-accessibility-ext"
19 | },
20 | "devDependencies": {
21 | "@angular/compiler-cli": "file:../../node_modules/@angular/compiler-cli",
22 | "@nativescript/android": "~7.0.0",
23 | "@nativescript/ios": "7.2.0",
24 | "@nativescript/webpack": "~4.1.0",
25 | "@ngtools/webpack": "file:../../node_modules/@ngtools/webpack",
26 | "typescript": "file:../../node_modules/typescript"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/apps/demo-angular/references.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/apps/demo-angular/src/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes } from '@angular/router';
3 | import { NativeScriptRouterModule } from '@nativescript/angular';
4 |
5 | import { HomeComponent } from './home.component';
6 |
7 | const routes: Routes = [
8 | { path: '', redirectTo: '/home', pathMatch: 'full' },
9 | { path: 'home', component: HomeComponent },
10 | {
11 | path: 'nativescript-accessibility-ext',
12 | loadChildren: () => import('./plugin-demos/nativescript-accessibility-ext.module').then((m) => m.NativescriptAccessibilityExtModule),
13 | },
14 | ];
15 |
16 | @NgModule({
17 | imports: [NativeScriptRouterModule.forRoot(routes)],
18 | exports: [NativeScriptRouterModule],
19 | })
20 | export class AppRoutingModule {}
21 |
--------------------------------------------------------------------------------
/apps/demo-angular/src/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'demo-app',
5 | template: `
6 |
7 | `,
8 | })
9 | export class AppComponent {}
10 |
--------------------------------------------------------------------------------
/apps/demo-angular/src/app.css:
--------------------------------------------------------------------------------
1 | @import '~nativescript-theme-core/css/core.light.css';
2 | @import url('~@nota/nativescript-accessibility-ext/css/a11y.css');
3 |
4 | button,
5 | label,
6 | stack-layout {
7 | horizontal-align: center;
8 | }
9 |
10 | button {
11 | font-size: 36;
12 | }
13 |
14 | .title {
15 | font-size: 30;
16 | margin: 20;
17 | }
18 |
19 | .message {
20 | font-size: 20;
21 | color: #284848;
22 | text-align: center;
23 | margin: 0 20;
24 | }
25 |
--------------------------------------------------------------------------------
/apps/demo-angular/src/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
2 | import { Trace } from '@nativescript/core';
3 | import { NativeScriptModule } from '@nativescript/angular';
4 | import { NotaAccessibilityExtModule } from '@nota/nativescript-accessibility-ext/angular';
5 | import { categories } from '@nota/nativescript-accessibility-ext/trace';
6 |
7 | import { AppComponent } from './app.component';
8 | import { AppRoutingModule } from './app-routing.module';
9 | import { HomeComponent } from './home.component';
10 |
11 | Trace.setCategories(categories.FontScale);
12 | Trace.enable();
13 |
14 | @NgModule({
15 | schemas: [NO_ERRORS_SCHEMA],
16 | declarations: [AppComponent, HomeComponent],
17 | bootstrap: [AppComponent],
18 | imports: [NativeScriptModule, AppRoutingModule, NotaAccessibilityExtModule],
19 | })
20 | export class AppModule {}
21 |
--------------------------------------------------------------------------------
/apps/demo-angular/src/home.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/apps/demo-angular/src/home.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'demo-home',
5 | templateUrl: 'home.component.html',
6 | })
7 | export class HomeComponent {
8 | demos = [
9 | {
10 | name: 'nativescript-accessibility-ext',
11 | },
12 | ];
13 | }
14 |
--------------------------------------------------------------------------------
/apps/demo-angular/src/main.ts:
--------------------------------------------------------------------------------
1 | import { platformNativeScriptDynamic } from '@nativescript/angular';
2 | import { AppModule } from './app.module';
3 |
4 | platformNativeScriptDynamic().bootstrapModule(AppModule);
5 |
--------------------------------------------------------------------------------
/apps/demo-angular/src/plugin-demos/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/apps/demo-angular/src/plugin-demos/.gitkeep
--------------------------------------------------------------------------------
/apps/demo-angular/src/plugin-demos/nativescript-accessibility-ext.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
32 |
33 |
34 |
35 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/apps/demo-angular/src/plugin-demos/nativescript-accessibility-ext.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, NgZone } from '@angular/core';
2 | import { DemoSharedNativescriptAccessibilityExt } from '@demo/shared';
3 | import {} from '@nota/nativescript-accessibility-ext';
4 |
5 | @Component({
6 | selector: 'demo-nativescript-accessibility-ext',
7 | templateUrl: 'nativescript-accessibility-ext.component.html',
8 | styles: [
9 | `
10 | .screen-reader {
11 | border-width: 1;
12 | border-radius: 10;
13 | padding: 10;
14 | margin: 0 10;
15 |
16 | Label {
17 | a11y-role: header;
18 | }
19 | }
20 |
21 | ListView {
22 | .list-view-item {
23 | orientation: horizontal;
24 | a11y-enabled: true;
25 | a11y-role: button;
26 |
27 | &.odd {
28 | a11y-role: switch;
29 | a11y-state: checked;
30 | }
31 | }
32 | }
33 | `,
34 | ],
35 | })
36 | export class NativescriptAccessibilityExtComponent {
37 | demoShared: DemoSharedNativescriptAccessibilityExt;
38 |
39 | constructor(private _ngZone: NgZone) {
40 | this.demoShared = new DemoSharedNativescriptAccessibilityExt();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/apps/demo-angular/src/plugin-demos/nativescript-accessibility-ext.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
2 | import { NativeScriptCommonModule, NativeScriptRouterModule } from '@nativescript/angular';
3 | import { NativescriptAccessibilityExtComponent } from './nativescript-accessibility-ext.component';
4 |
5 | @NgModule({
6 | imports: [NativeScriptCommonModule, NativeScriptRouterModule.forChild([{ path: '', component: NativescriptAccessibilityExtComponent }])],
7 | declarations: [NativescriptAccessibilityExtComponent],
8 | schemas: [NO_ERRORS_SCHEMA],
9 | })
10 | export class NativescriptAccessibilityExtModule {}
11 |
--------------------------------------------------------------------------------
/apps/demo-angular/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "rootDirs": [".", "../.."],
5 | "baseUrl": ".",
6 | "paths": {
7 | "~/*": ["src/*"],
8 | "@nota/*": ["../../dist/packages/*"],
9 | "@demo/shared": ["../../tools/demo/index.ts"]
10 | }
11 | },
12 | "files": ["./references.d.ts", "./src/main.ts"]
13 | }
14 |
--------------------------------------------------------------------------------
/apps/demo/.gitignore:
--------------------------------------------------------------------------------
1 | # NativeScript
2 | hooks/
3 | node_modules/
4 | platforms/
5 |
6 | # NativeScript Template
7 | *.js.map
8 | *.js
9 |
10 | # Logs
11 | logs
12 | *.log
13 | npm-debug.log*
14 | yarn-debug.log*
15 | yarn-error.log*
16 |
17 | # General
18 | .DS_Store
19 | .AppleDouble
20 | .LSOverride
21 | .idea
22 | .cloud
23 | .project
24 | tmp/
25 | typings/
26 |
27 | # misc
28 | npm-debug.log
29 |
30 | # app
31 | !*.d.ts
32 | !src/assets/fontawesome.min.css
33 | /report/
34 | .nsbuildinfo
35 | /temp/
36 | /src/tns_modules/
37 |
38 | # app uses platform specific scss which can inadvertently get renamed which will cause problems
39 | app/app.scss
40 |
41 | package-lock.json
42 |
--------------------------------------------------------------------------------
/apps/demo/nativescript.config.ts:
--------------------------------------------------------------------------------
1 | import { NativeScriptConfig } from '@nativescript/core';
2 |
3 | export default {
4 | id: 'org.nativescript.plugindemo',
5 | appResourcesPath: '../../tools/assets/App_Resources',
6 | android: {
7 | v8Flags: '--expose_gc',
8 | markingMode: 'none',
9 | },
10 | appPath: 'src',
11 | } as NativeScriptConfig;
12 |
--------------------------------------------------------------------------------
/apps/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "app.js",
3 | "description": "NativeScript Application",
4 | "license": "SEE LICENSE IN ",
5 | "repository": "",
6 | "dependencies": {
7 | "nativescript-theme-core": "file:../../node_modules/nativescript-theme-core",
8 | "@nativescript/core": "file:../../node_modules/@nativescript/core",
9 | "@nota/nativescript-accessibility-ext": "file:../../packages/nativescript-accessibility-ext"
10 | },
11 | "devDependencies": {
12 | "@nativescript/android": "~7.0.0",
13 | "@nativescript/ios": "7.2.0",
14 | "@nativescript/webpack": "~4.1.0",
15 | "typescript": "file:../../node_modules/typescript"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/apps/demo/references.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/apps/demo/src/app-root.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/apps/demo/src/app.css:
--------------------------------------------------------------------------------
1 | @import '~nativescript-theme-core/css/core.light.css';
2 |
--------------------------------------------------------------------------------
/apps/demo/src/app.ts:
--------------------------------------------------------------------------------
1 | import { Application } from '@nativescript/core';
2 |
3 | Application.run({ moduleName: 'app-root' });
4 |
--------------------------------------------------------------------------------
/apps/demo/src/main-page.ts:
--------------------------------------------------------------------------------
1 | import { EventData, Page } from '@nativescript/core';
2 | import { MainViewModel } from './main-view-model';
3 |
4 | export function navigatingTo(args: EventData) {
5 | const page = args.object;
6 | page.bindingContext = new MainViewModel();
7 | }
8 |
--------------------------------------------------------------------------------
/apps/demo/src/main-page.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/apps/demo/src/main-view-model.ts:
--------------------------------------------------------------------------------
1 | import { Observable, Frame } from '@nativescript/core';
2 |
3 | export class MainViewModel extends Observable {
4 | viewDemo(args) {
5 | Frame.topmost().navigate({
6 | moduleName: `plugin-demos/${args.object.text}`,
7 | });
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/apps/demo/src/plugin-demos/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/apps/demo/src/plugin-demos/.gitkeep
--------------------------------------------------------------------------------
/apps/demo/src/plugin-demos/nativescript-accessibility-ext.ts:
--------------------------------------------------------------------------------
1 | import { Observable, EventData, Page } from '@nativescript/core';
2 | import { DemoSharedNativescriptAccessibilityExt } from '@demo/shared';
3 | import {} from '@nota/nativescript-accessibility-ext';
4 |
5 | export function navigatingTo(args: EventData) {
6 | const page = args.object;
7 | page.bindingContext = new DemoModel();
8 | }
9 |
10 | export class DemoModel extends DemoSharedNativescriptAccessibilityExt {}
11 |
--------------------------------------------------------------------------------
/apps/demo/src/plugin-demos/nativescript-accessibility-ext.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/apps/demo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "rootDirs": [".", "../.."],
5 | "baseUrl": ".",
6 | "paths": {
7 | "~/*": ["src/*"],
8 | "@nota/*": ["../../packages/*"],
9 | "@demo/shared": ["../../tools/demo/index.ts"]
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | testMatch: ['**/+(*.)+(spec|test).+(ts|js)?(x)'],
3 | transform: {
4 | '^.+\\.(ts|js|html)$': 'ts-jest',
5 | },
6 | resolver: '@nrwl/jest/plugins/resolver',
7 | moduleFileExtensions: ['ts', 'js', 'html'],
8 | coverageReporters: ['html'],
9 | };
10 |
--------------------------------------------------------------------------------
/nx.json:
--------------------------------------------------------------------------------
1 | {
2 | "npmScope": "nota",
3 | "affected": {
4 | "defaultBase": "master"
5 | },
6 | "implicitDependencies": {
7 | "workspace.json": "*",
8 | "package.json": {
9 | "dependencies": "*",
10 | "devDependencies": "*"
11 | },
12 | "tsconfig.base.json": "*",
13 | "tslint.json": "*",
14 | "nx.json": "*"
15 | },
16 | "projects": {
17 | "demo": {
18 | "tags": []
19 | },
20 | "demo-angular": {
21 | "tags": []
22 | },
23 | "all": {
24 | "tags": []
25 | },
26 | "nativescript-accessibility-ext": {
27 | "tags": []
28 | }
29 | },
30 | "workspaceLayout": {
31 | "appsDir": "apps",
32 | "libsDir": "packages"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "plugins",
3 | "version": "0.0.0",
4 | "license": "MIT",
5 | "scripts": {
6 | "setup": "npx rimraf node_modules package-lock.json dist tmp && npm i && ts-patch install",
7 | "start": "nps",
8 | "add": "nx g @nativescript/plugin-tools:add-package",
9 | "add-angular": "nx g @nativescript/plugin-tools:add-angular",
10 | "config": "nx g @nativescript/plugin-tools:config",
11 | "publish-packages": "nx g @nativescript/plugin-tools:publish",
12 | "sync-packages-with-demos": "nx g @nativescript/plugin-tools:sync-packages-with-demos",
13 | "postinstall": "node ./node_modules/husky/lib/installer/bin install"
14 | },
15 | "private": true,
16 | "devDependencies": {
17 | "@angular/animations": "~11.2.2",
18 | "@angular/common": "~11.2.2",
19 | "@angular/compiler": "~11.2.2",
20 | "@angular/compiler-cli": "~11.2.2",
21 | "@angular/core": "~11.2.2",
22 | "@angular/forms": "~11.2.2",
23 | "@angular/platform-browser": "~11.2.2",
24 | "@angular/platform-browser-dynamic": "~11.2.2",
25 | "@angular/router": "~11.2.2",
26 | "@nativescript/angular": "~11.0.1",
27 | "@nativescript/core": "~7.2.1",
28 | "@nativescript/plugin-tools": "^1.1.0",
29 | "@nativescript/theme": "^2.5.0",
30 | "@nativescript/types": "~7.2.0",
31 | "@nativescript/webpack": "~4.1.0",
32 | "@ngtools/webpack": "~11.2.1",
33 | "@nrwl/eslint-plugin-nx": "^11.3.1",
34 | "@nrwl/jest": "^11.3.1",
35 | "@nrwl/node": "^11.3.1",
36 | "@nrwl/workspace": "^11.3.1",
37 | "@nstudio/focus": "^11.1.0",
38 | "@nstudio/nps-i": "~1.1.1",
39 | "@types/jest": "~26.0.20",
40 | "@types/node": "~14.14.31",
41 | "@typescript-eslint/eslint-plugin": "^4.15.2",
42 | "@typescript-eslint/parser": "^4.15.2",
43 | "autoprefixer": "^10.2.4",
44 | "dart-sass": "^1.25.0",
45 | "dotenv": "~8.2.0",
46 | "eslint": "~7.20.0",
47 | "eslint-config-prettier": "~8.0.0",
48 | "husky": "^4.3.0",
49 | "jest": "~26.4.1",
50 | "lint-staged": "^10.5.4",
51 | "nativescript-permissions": "^1.3.11",
52 | "nativescript-theme-core": "~1.0.4",
53 | "nativescript-vue": "~2.8.3",
54 | "nativescript-vue-template-compiler": "~2.8.3",
55 | "ng-packagr": "~11.2.4",
56 | "node-sass": "^7.0.0",
57 | "postcss": "^8.4.31",
58 | "postcss-url": "^10.1.1",
59 | "prettier": "^2.2.1",
60 | "pretty-data": "^0.40.0",
61 | "reflect-metadata": "~0.1.13",
62 | "rimraf": "^3.0.2",
63 | "rxjs": "^6.6.3",
64 | "strip-json-comments": "^3.1.1",
65 | "ts-jest": "^26.5.1",
66 | "ts-node": "~9.1.1",
67 | "ts-patch": "^1.3.2",
68 | "tslint": "^6.1.3",
69 | "typescript": "~4.1.5",
70 | "xml2js": "^0.5.0",
71 | "zone.js": "^0.11.4"
72 | },
73 | "husky": {
74 | "hooks": {
75 | "pre-commit": "lint-staged"
76 | }
77 | },
78 | "lint-staged": {
79 | "**/*": [
80 | "nx format:write --files"
81 | ]
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/packages/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/packages/.gitkeep
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/Angular.md:
--------------------------------------------------------------------------------
1 | # Using @nota/nativescript-accessibility-ext with angular
2 |
3 | ## Setup
4 |
5 | Modify your `app.module.ts` by importing `NotaAccessibilityExtModule` into your `NgModule`
6 |
7 | ```typescript
8 | import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
9 | import { NativeScriptModule } from "nativescript-angular/nativescript.module";
10 | import { NotaAccessibilityExtModule } '@nota/nativescript-accessibility-ext/angular'; // <-- Add this line
11 |
12 | import { AppRoutingModule } from "./app-routing.module";
13 | import { AppComponent } from "./app.component";
14 | import { ItemsComponent } from "./item/items.component";
15 | import { ItemDetailComponent } from "./item/item-detail.component";
16 |
17 | @NgModule({
18 | bootstrap: [
19 | AppComponent
20 | ],
21 | imports: [
22 | NativeScriptModule,
23 | AppRoutingModule,
24 | NotaAccessibilityExtModule, /// <-- Add this line
25 | ],
26 | declarations: [
27 | AppComponent,
28 | ItemsComponent,
29 | ItemDetailComponent
30 | ],
31 | providers: [],
32 | schemas: [
33 | NO_ERRORS_SCHEMA
34 | ]
35 | })
36 | export class AppModule { }
37 | ```
38 |
39 | ## Directives
40 |
41 | ### GridLayout[a11yRows], GridLayout[a11yColumns]
42 |
43 | It can be a good idea to scale your `rows` and `columns` to according to the accessibility font-scale setting. This can be done via the `GridLayout[a11yRows], GridLayout[a11yColumns]` directive.
44 |
45 | ```html
46 |
47 |
48 |
49 | ```
50 |
51 | At fontScale 1.3/130% this `GridLayout` will have `rows="39, auto"` and `columns="65, *"`, at 4.0/400% it will have `rows="120, auto"` and `columns="325, *"`
52 |
53 | ## Pipes
54 |
55 | ### a11yFontScale
56 |
57 | Scale number values according to the accessibility font-scale setting in template
58 | ```html
59 |
60 | ```
61 |
62 | ## A11Y observables
63 |
64 | ### A11YFontScalingObservable
65 |
66 | Inject an Observable with the current accessibility font-scale setting.
67 |
68 | ```typescript
69 | @Component({...})
70 | export class MyComponent {
71 | constructor(private fontScaling$: A11yFontScalingObservable) {}
72 | }
73 | ```
74 |
75 | ### A11YIsServiceEnabledObservable
76 |
77 | Is VoiceOver/TalkBack enabled?
78 |
79 | ```typescript
80 | @Component({...})
81 | export class MyComponent {
82 | constructor(private isA11yServiceEnabled$: A11yServiceEnabledObservable) {}
83 | }
84 | ```
85 |
86 | ## Component CSS
87 |
88 | ### Font Scaling
89 |
90 | ```scss
91 | $my-custom-font-size: 16;
92 | $my-custom-height: 40;
93 | $my-custom-margin: 2;
94 | .my-class {
95 | height: $my-custom-height;
96 | margin: $my-custom-margin;
97 | font-size: $my-custom-font-size;
98 |
99 | &[ios] {
100 | // Only applies on iOS
101 | font-size: calc(#{$my-custom-font-size} * var(--a11y-fontscale-factor));
102 | }
103 |
104 | &[android] {
105 | // Font sizes auto-scale on android but margins don't
106 | margin: calc(#{$my-custom-margin} * var(--a11y-fontscale-factor));
107 | }
108 | }
109 | ```
110 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/FontScaling.md:
--------------------------------------------------------------------------------
1 | # Scaling fonts in your application
2 |
3 | Problem:
4 | You need to support users with a visual impairment that requires larger fonts.
5 |
6 | `@nota/nativescript-accessibility-ext` extends the nativescript theme (requires `nativescript-theme-core@^2.0.19`) to provide support for font-scaling on `iOS`.
7 | Support is built-in on `android` but you might need to handle the layout changes.
8 |
9 | ## Installing the theme extension:
10 |
11 | You need to add a single line to your `app.scss`:
12 |
13 | ```scss
14 | @import '~@nativescript/theme/core.compat';
15 | @import '~@nativescript/theme/scss/index';
16 | @import url('~@nota/nativescript-accessibility-ext/a11y.css'); // <-- add this line
17 | ```
18 |
19 | For compat mode:
20 | ```scss
21 | @import '~@nativescript/theme/core.compat';
22 | @import '~@nativescript/theme/scss/index';
23 | @import url('~@nota/nativescript-accessibility-ext/a11y.compat.css'); // <-- add this line
24 | ```
25 |
26 | If you use the theme-classes font-scaling will be enabled on both platforms.
27 |
28 | ## Writing your own font-scale styles:
29 |
30 | You can write your own font-scale styles using `css-variables` and `css-calc`.
31 |
32 | | variable | description |
33 | | -- | -- |
34 | | --a11y-fontscale-factor | Current fontscale factor |
35 | | --a11y-font-size | Default font-size, scaled version of --const-font-size |
36 | | --a11y-btn-font-size | Button font-size, scaled version of --const-btn-font-size |
37 | | --a11y-icon-font-size-lg | Icon font-size, scaled version of --const-icon-font-size-lg |
38 | | --a11y-drawer-header-font-size | Sidebar header font-size |
39 | | --const-segmented-bar-font-size | Base font size used by the segmented bar |
40 | | --const-segmented-bar-height | Base height of the segmented bar |
41 | | --a11y-segmented-bar-font-size | SegmentedBar font-size, scaled version of --const-segmented-bar-font-size |
42 | | --a11y-segmented-bar-height | SegmentedBar height, scaled version of --const-segmented-bar-height |
43 | | --const-drawer-header-font-size | Base SideDrawer header font-size |
44 | | --a11y-drawer-header-font-size | SideDrawer header font-size, scaled version of --const-drawer-header-font-size |
45 | | --a11y-{text-class-name}-size | Scaled font-size variable for the text CSS-classes `t-10`...`t-36`, `h1`...`h6`, `body`, `body2` and `footnote`.|
46 |
47 | ```scss
48 | .my-class {
49 | height: calc(40 * var(--a11y-fontscale-factor));
50 | margin: calc(2 * var(--a11y-fontscale-factor));
51 | font-size: calc(16 * var(--a11y-fontscale-factor));
52 | }
53 | ```
54 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/Notes.md:
--------------------------------------------------------------------------------
1 | # Implementing iOS accessibility features
2 |
3 | On iOS certain accessibility features requires us to define functions to the Native-objects.
4 |
5 | ## Functions:
6 |
7 | ### accessibilityActivate()
8 | Tells the element to activate itself and report the success or failure of the operation.
9 |
10 | ### accessibilityIncrement()
11 | Tells the accessibility element to increment the value of its content.
12 |
13 | User action: A one-finger swipe up that increments a value in an element.
14 |
15 | ### accessibilityDecrement()
16 | Tells the accessibility element to decrement the value of its content.
17 |
18 | User action: A one-finger swipe down that decrements a value in an element.
19 |
20 | ### accessibilityScroll(UIAccessibilityScrollDirection)
21 | Scrolls screen content in an application-specific way and returns the success or failure of the action.
22 |
23 | User action: A three-finger swipe that scrolls content vertically or horizontally.
24 |
25 | ### accessibilityPerformEscape()
26 | Dismisses a modal view and returns the success or failure of the action.
27 |
28 | User action: A two-finger Z-shaped gesture that dismisses a modal dialog, or goes back one level in a navigation hierarchy.
29 | Returns true, if we're handling the escape.
30 | Dev note: Unlike **accessibilityPerformMagicTap()** this cannot be added to the AppDelegate in NativeScript.
31 |
32 | ### accessibilityPerformMagicTap()
33 | Performs a salient action.
34 |
35 | User action: A two-finger double-tap that performs the most-intended action.
36 | This could be play/pause in an audio-player or pick/hang up in a phone-app.
37 | Dev note: Can be added to the AppDelegate in NativeScript.
38 |
39 | ## Links
40 | https://developer.apple.com/documentation/uikit/accessibility/uiaccessibilityaction
41 | https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/SupportingAccessibility.html
42 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/README.md:
--------------------------------------------------------------------------------
1 | # Implementing iOS accessibility features
2 |
3 | On iOS certain accessibility features requires us to define functions to the Native-objects.
4 |
5 | ## Functions:
6 |
7 | ### accessibilityActivate()
8 | Tells the element to activate itself and report the success or failure of the operation.
9 |
10 | ### accessibilityIncrement()
11 | Tells the accessibility element to increment the value of its content.
12 |
13 | User action: A one-finger swipe up that increments a value in an element.
14 |
15 | ### accessibilityDecrement()
16 | Tells the accessibility element to decrement the value of its content.
17 |
18 | User action: A one-finger swipe down that decrements a value in an element.
19 |
20 | ### accessibilityScroll(UIAccessibilityScrollDirection)
21 | Scrolls screen content in an application-specific way and returns the success or failure of the action.
22 |
23 | User action: A three-finger swipe that scrolls content vertically or horizontally.
24 |
25 | ### accessibilityPerformEscape()
26 | Dismisses a modal view and returns the success or failure of the action.
27 |
28 | User action: A two-finger Z-shaped gesture that dismisses a modal dialog, or goes back one level in a navigation hierarchy.
29 | Returns true, if we're handling the escape.
30 | Dev note: Unlike **accessibilityPerformMagicTap()** this cannot be added to the AppDelegate in NativeScript.
31 |
32 | ### accessibilityPerformMagicTap()
33 | Performs a salient action.
34 |
35 | User action: A two-finger double-tap that performs the most-intended action.
36 | This could be play/pause in an audio-player or pick/hang up in a phone-app.
37 | Dev note: Can be added to the AppDelegate in NativeScript.
38 |
39 | ## Links
40 | https://developer.apple.com/documentation/uikit/accessibility/uiaccessibilityaction
41 | https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/SupportingAccessibility.html
42 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/angular/data/a11y-font-scaling.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, OnDestroy } from '@angular/core';
2 | import { FontScaleObservable } from '@nota/nativescript-accessibility-ext';
3 | import { BehaviorSubject } from 'rxjs';
4 |
5 | @Injectable({ providedIn: 'root' })
6 | export class A11yFontScalingObservable extends BehaviorSubject implements OnDestroy {
7 | private tnsObs = new FontScaleObservable();
8 |
9 | constructor() {
10 | super(1);
11 |
12 | this.tnsObs.on(FontScaleObservable.propertyChangeEvent, this.updateFontScalingValue, this);
13 | this.updateFontScalingValue();
14 | }
15 |
16 | public ngOnDestroy() {
17 | this.tnsObs.off(FontScaleObservable.propertyChangeEvent, this.updateFontScalingValue, this);
18 | this.tnsObs = null;
19 | }
20 |
21 | private updateFontScalingValue() {
22 | const fontScale = this.tnsObs.fontScale;
23 | if (typeof fontScale === 'number' && !isNaN(fontScale)) {
24 | this.next(fontScale);
25 | } else {
26 | this.next(1);
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/angular/data/a11y-service-enabled.ts:
--------------------------------------------------------------------------------
1 | import { Injectable, OnDestroy } from '@angular/core';
2 | import { Application as nsApp, PropertyChangeData } from '@nativescript/core';
3 | import { BehaviorSubject } from 'rxjs';
4 | import { AccessibilityServiceEnabledObservable, isAccessibilityServiceEnabled } from '@nota/nativescript-accessibility-ext';
5 |
6 | @Injectable({ providedIn: 'root' })
7 | export class A11yServiceEnabledObservable extends BehaviorSubject implements OnDestroy {
8 | private tnsObs = new AccessibilityServiceEnabledObservable();
9 |
10 | constructor() {
11 | super(isAccessibilityServiceEnabled());
12 |
13 | nsApp.on(nsApp.resumeEvent, this.resumeEvent, this);
14 |
15 | this.tnsObs.on(AccessibilityServiceEnabledObservable.propertyChangeEvent, this.tnsPropertyValueChanged, this);
16 | }
17 |
18 | public ngOnDestroy() {
19 | nsApp.off(nsApp.resumeEvent, this.resumeEvent, this);
20 | this.tnsObs.off(AccessibilityServiceEnabledObservable.propertyChangeEvent, this.tnsPropertyValueChanged, this);
21 | this.resumeEvent = null;
22 | this.tnsObs = null;
23 | }
24 |
25 | private resumeEvent() {
26 | this.next(isAccessibilityServiceEnabled());
27 | }
28 |
29 | private tnsPropertyValueChanged(evt: PropertyChangeData) {
30 | this.next(!!this.tnsObs.accessibilityServiceEnabled);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/angular/directives/a11y-grid-layout.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, ElementRef, Input, OnInit } from '@angular/core';
2 | import { GridLayout } from '@nativescript/core';
3 | import { BehaviorSubject, combineLatest } from 'rxjs';
4 | import { filter, map } from 'rxjs/operators';
5 | import { A11yFontScalingObservable } from '../data/a11y-font-scaling';
6 | import { BaseService } from '../services/base.service';
7 |
8 | @Directive({
9 | selector: 'GridLayout[a11yRows], GridLayout[a11yColumns]',
10 | })
11 | export class A11YGridLayoutDirective extends BaseService implements OnInit {
12 | private readonly rows$ = new BehaviorSubject(null);
13 | @Input('a11yRows')
14 | public set rows(a11yRows: string) {
15 | this.rows$.next(`${a11yRows}`);
16 | }
17 | public get rows() {
18 | return this.rows$.value;
19 | }
20 |
21 | private readonly columns$ = new BehaviorSubject(null);
22 | @Input('a11yColumns')
23 | public set columns(a11yColumns: string) {
24 | this.columns$.next(`${a11yColumns}`);
25 | }
26 | public get columns() {
27 | return this.columns$.value;
28 | }
29 |
30 | constructor(private readonly el: ElementRef, private readonly fontScaling$: A11yFontScalingObservable) {
31 | super();
32 | }
33 |
34 | public ngOnInit() {
35 | combineLatest(this.rows$, this.fontScaling$)
36 | .pipe(
37 | map(([rows, fontScale]) => this.fixValue(rows, fontScale)),
38 | filter((rows) => !!rows),
39 | this.takeUntilDestroy(),
40 | )
41 | .subscribe((rows) => (this.el.nativeElement['rows'] = rows));
42 |
43 | combineLatest(this.columns$, this.fontScaling$)
44 | .pipe(
45 | map(([columns, fontScale]) => this.fixValue(columns, fontScale)),
46 | filter((columns) => !!columns),
47 | this.takeUntilDestroy(),
48 | )
49 | .subscribe((columns) => (this.el.nativeElement['columns'] = columns));
50 | }
51 |
52 | private fixValue(str: string, fontScale: number) {
53 | if (!str) {
54 | return null;
55 | }
56 |
57 | return str
58 | .split(',')
59 | .map((part) => `${part}`.trim().toLowerCase())
60 | .filter((part) => !!part)
61 | .map((part) => {
62 | switch (part) {
63 | case '*':
64 | case 'auto': {
65 | return part;
66 | }
67 | default: {
68 | return Number(part) * fontScale;
69 | }
70 | }
71 | })
72 | .join(', ');
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/angular/index.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import '@nota/nativescript-accessibility-ext';
3 | import { A11yFontScalingObservable } from './data/a11y-font-scaling';
4 | import { A11yServiceEnabledObservable } from './data/a11y-service-enabled';
5 | import { A11YGridLayoutDirective } from './directives/a11y-grid-layout.directive';
6 | import { A11YFontScalePipe } from './pipes/a11y-font-scaling.pipe';
7 | export * from '@nota/nativescript-accessibility-ext';
8 | export { A11YFontScalePipe, A11yFontScalingObservable, A11YGridLayoutDirective, A11yServiceEnabledObservable };
9 |
10 | @NgModule({
11 | providers: [A11YFontScalePipe],
12 | declarations: [A11YGridLayoutDirective, A11YFontScalePipe],
13 | exports: [A11YGridLayoutDirective, A11YFontScalePipe],
14 | })
15 | export class NotaAccessibilityExtModule {}
16 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/angular/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nativescript-nativescript-accessibility-ext-angular",
3 | "ngPackage": {
4 | "lib": {
5 | "entryFile": "index.ts",
6 | "umdModuleIds": {
7 | "@nativescript/core": "ns-core",
8 | "@nativescript/angular": "ns-angular",
9 | "@nota/nativescript-accessibility-ext": "ns-nativescript-accessibility-ext"
10 | }
11 | },
12 | "whitelistedNonPeerDependencies": [
13 | "."
14 | ]
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/angular/pipes/a11y-font-scaling.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 | import { map } from 'rxjs/operators';
3 | import { A11yFontScalingObservable } from '../data/a11y-font-scaling';
4 |
5 | @Pipe({ name: 'a11yFontScale' })
6 | export class A11YFontScalePipe implements PipeTransform {
7 | constructor(private readonly fontScaling$: A11yFontScalingObservable) {}
8 |
9 | public transform(input: number | string) {
10 | return this.fontScaling$.pipe(map((factor) => Number(input) * factor));
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/angular/services/base.service.ts:
--------------------------------------------------------------------------------
1 | import { ElementRef, Injectable, OnDestroy } from '@angular/core';
2 | import { Observable, ReplaySubject, Subscription, timer } from 'rxjs';
3 |
4 | @Injectable()
5 | export class BaseService implements OnDestroy {
6 | protected isDeleted = false;
7 |
8 | public readonly destroy$ = new ReplaySubject(1);
9 |
10 | public ngOnDestroy() {
11 | this.destroy$.next(true);
12 | this.isDeleted = true;
13 |
14 | // Deference member variables to avoid leaks
15 | for (const [key, value] of Object.entries(this)) {
16 | if (!value) {
17 | continue;
18 | }
19 |
20 | if (value instanceof ElementRef) {
21 | delete this[key];
22 | continue;
23 | }
24 |
25 | if (value instanceof Subscription) {
26 | try {
27 | value.unsubscribe();
28 | } catch {
29 | // ignore
30 | }
31 |
32 | delete this[key];
33 | continue;
34 | }
35 |
36 | if (value instanceof Observable) {
37 | // Remove observables. This should help clear up 'this' references on operators.
38 | if (key === 'destroy$') {
39 | continue;
40 | }
41 |
42 | delete this[key];
43 | continue;
44 | }
45 | }
46 | }
47 |
48 | public takeUntilDestroy() {
49 | const destroy$ = this.destroy$;
50 |
51 | // This should have been a `takeUntil(this.destroy$)` but it kept emitting after destroy...
52 | return function (source: Observable) {
53 | return new Observable(function (subscriber) {
54 | const sub = source.subscribe(subscriber);
55 |
56 | const destroySub = destroy$.subscribe(function () {
57 | // tslint:disable:no-console
58 | // Extra check here because HMR-mode logged errors about them missing
59 | if (sub) {
60 | sub.unsubscribe();
61 | }
62 |
63 | if (subscriber) {
64 | subscriber.complete();
65 | }
66 |
67 | if (destroySub) {
68 | destroySub.unsubscribe();
69 | }
70 | });
71 |
72 | return () => {
73 | sub.unsubscribe();
74 | destroySub.unsubscribe();
75 | };
76 | });
77 | };
78 | }
79 |
80 | /**
81 | * Replacement for setTimeout(...), that won't trigger after the component have been destroyed.
82 | */
83 | protected timerUnlessDestroyed(cb: () => void, delay = 0) {
84 | return timer(delay)
85 | .pipe(this.takeUntilDestroy())
86 | .subscribe(() => {
87 | try {
88 | cb();
89 | } catch {
90 | // ignore
91 | }
92 | });
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/angular/tsconfig.angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../../node_modules/ng-packagr/lib/ts/conf/tsconfig.ngc.json",
3 | "compilerOptions": {
4 | "types": ["node"],
5 | "baseUrl": ".",
6 | "paths": {
7 | "@nota/nativescript-accessibility-ext": ["../../../dist/packages/nativescript-accessibility-ext"],
8 | "@nota/nativescript-accessibility-ext/*": ["../../../dist/packages/nativescript-accessibility-ext/*"]
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ui';
2 | export * from './utils';
3 |
4 | import './ui';
5 | import './utils';
6 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@nota/nativescript-accessibility-ext",
3 | "version": "7.0.3",
4 | "description": "Add support for 'VoiceOver' and 'TalkBack' in NativeScript",
5 | "main": "index",
6 | "typings": "index.d.ts",
7 | "nativescript": {
8 | "platforms": {
9 | "android": "7.0.0",
10 | "ios": "7.0.0"
11 | },
12 | "plugin": {
13 | "nan": true,
14 | "pan": true,
15 | "core3": true,
16 | "category": "Utilities"
17 | }
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "https://github.com/Notalib/nativescript-accessibility-ext.git"
22 | },
23 | "keywords": [
24 | "NativeScript",
25 | "JavaScript",
26 | "Accessibility",
27 | "angular",
28 | "Android",
29 | "TalkBack",
30 | "VoiceOver",
31 | "iOS"
32 | ],
33 | "author": {
34 | "name": "Nota",
35 | "email": "app@nota.dk",
36 | "url": "https://nota.dk"
37 | },
38 | "contributors": [
39 | {
40 | "name": "Morten Anton Bach Sjøgren",
41 | "url": "http://mabs.dk",
42 | "email": "m_abs@mabs.dk"
43 | },
44 | {
45 | "name": "Daniel Freiling",
46 | "email": "ddfreiling@gmail.com"
47 | }
48 | ],
49 | "bugs": {
50 | "url": "https://github.com/Notalib/nativescript-accessibility-ext/issues"
51 | },
52 | "license": "Apache-2.0",
53 | "homepage": "https://github.com/Notalib/nativescript-accessibility-ext/#readme",
54 | "readmeFilename": "README.md"
55 | }
56 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/references.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/scss/_fontscales.scss:
--------------------------------------------------------------------------------
1 | $a11y-font-scales: (
2 | 50: (
3 | factor: 0.5,
4 | extraSmall: true,
5 | ios: true,
6 | ),
7 | 70: (
8 | factor: 0.7,
9 | extraSmall: true,
10 | ios: true,
11 | ),
12 | 85: (
13 | factor: 0.85,
14 | ios: true,
15 | android: true,
16 | ),
17 | 100: (
18 | factor: 1,
19 | ios: true,
20 | android: true,
21 | ),
22 | 115: (
23 | factor: 1.15,
24 | ios: true,
25 | android: true,
26 | ),
27 | 130: (
28 | factor: 1.3,
29 | ios: true,
30 | android: true,
31 | ),
32 | 150: (
33 | factor: 1.5,
34 | ios: true,
35 | ),
36 | 200: (
37 | factor: 2,
38 | extraLarge: true,
39 | ios: true,
40 | ),
41 | 250: (
42 | factor: 2.5,
43 | extraLarge: true,
44 | ios: true,
45 | ),
46 | 300: (
47 | factor: 3,
48 | extraLarge: true,
49 | ios: true,
50 | ),
51 | 350: (
52 | factor: 3.5,
53 | extraLarge: true,
54 | ios: true,
55 | ),
56 | 400: (
57 | factor: 4,
58 | extraLarge: true,
59 | ios: true,
60 | ),
61 | ) !default;
62 |
63 | $a11y-font-size: 10 !default;
64 | $a11y-font-sizes: (
65 | t-10: (
66 | font-size: $a11y-font-size,
67 | ),
68 | t-12: (
69 | font-size: $a11y-font-size + 2,
70 | ),
71 | t-14: (
72 | font-size: $a11y-font-size + 4,
73 | ),
74 | t-15: (
75 | font-size: $a11y-font-size + 5,
76 | ),
77 | t-16: (
78 | font-size: $a11y-font-size + 6,
79 | ),
80 | t-17: (
81 | font-size: $a11y-font-size + 7,
82 | ),
83 | t-18: (
84 | font-size: $a11y-font-size + 8,
85 | ),
86 | t-19: (
87 | font-size: $a11y-font-size + 9,
88 | ),
89 | t-20: (
90 | font-size: $a11y-font-size + 10,
91 | ),
92 | t-25: (
93 | font-size: $a11y-font-size + 15,
94 | ),
95 | t-30: (
96 | font-size: $a11y-font-size + 20,
97 | ),
98 | t-36: (
99 | font-size: $a11y-font-size + 26,
100 | ),
101 | ) !default;
102 |
103 | $a11y-label-classes: (
104 | h1: (
105 | font-size: 32,
106 | ),
107 | h2: (
108 | font-size: 22,
109 | ),
110 | h3: (
111 | font-size: 15,
112 | ),
113 | h4: (
114 | font-size: 12,
115 | ),
116 | h5: (
117 | font-size: 11,
118 | ),
119 | h6: (
120 | font-size: 10,
121 | ),
122 | body: (
123 | font-size: 14,
124 | ),
125 | body2: (
126 | font-size: 17,
127 | ),
128 | footnote: (
129 | font-size: 13,
130 | ),
131 | ) !default;
132 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/scss/a11y-helpers.scss:
--------------------------------------------------------------------------------
1 | @import './fontscales';
2 |
3 | .ns-root,
4 | .ns-modal {
5 | &.a11y-service-disabled {
6 | .a11y-service-enabled-visible,
7 | .a11y-service-disabled-hidden {
8 | visibility: collapse;
9 | }
10 | }
11 |
12 | &.a11y-service-enabled {
13 | .a11y-service-enabled-hidden,
14 | .a11y-service-disabled-hidden {
15 | visibility: collapse;
16 | }
17 | }
18 |
19 | &.a11y-fontscale-xs {
20 | .a11y-fontscale-xs-hidden,
21 | .a11y-fontscale-xl-visible {
22 | visibility: collapse;
23 | }
24 | }
25 |
26 | &.a11y-fontscale-m {
27 | .a11y-fontscale-m-hidden,
28 | .a11y-fontscale-xs-visible,
29 | .a11y-fontscale-xl-visible {
30 | visibility: collapse;
31 | }
32 | }
33 |
34 | &.a11y-fontscale-xl {
35 | .a11y-fontscale-xl-hidden,
36 | .a11y-fontscale-m-visible,
37 | .a11y-fontscale-xs-visible {
38 | visibility: collapse;
39 | }
40 | }
41 |
42 | --a11y-fontscale-factor: 1;
43 | @each $scaleName, $params in $a11y-font-scales {
44 | $a11y-fontscale-factor: map-get($params, factor); // scaling factor 0.8, 1.0., 1.3 etc
45 |
46 | &.a11y-fontscale-#{$scaleName} {
47 | --a11y-fontscale-factor: #{$a11y-fontscale-factor};
48 | }
49 | }
50 |
51 | --const-segmented-bar-font-size: 15;
52 | --const-drawer-header-font-size: 40;
53 |
54 | --const-segmented-bar-height: 56;
55 |
56 | --a11y-font-size: var(--const-font-size);
57 | --const-font-size-2: calc(var(--const-font-size) - 2);
58 | --a11y-font-size-2: var(----const-font-size-2);
59 | --a11y-btn-font-size: var(--const-btn-font-size);
60 | --a11y-icon-font-size-lg: var(--const-icon-font-size-lg);
61 | --a11y-segmented-bar-font-size: var(--const-segmented-bar-font-size);
62 | --a11y-segmented-bar-height: var(--const-segmented-bar-height);
63 | --a11y-drawer-header-font-size: var(--const-drawer-header-font-size);
64 |
65 | @each $text-class, $values in $a11y-font-sizes {
66 | $a11y-font-size: map-get($values, font-size);
67 |
68 | --a11y-#{$text-class}-size: #{$a11y-font-size};
69 | }
70 |
71 | @each $label-class, $values in $a11y-label-classes {
72 | $a11y-font-size: map-get($values, font-size);
73 |
74 | --a11y-#{$label-class}-size: #{$a11y-font-size};
75 | }
76 |
77 | &.ns-a11y {
78 | font-size: var(--a11y-font-size);
79 | }
80 | }
81 |
82 | .ns-ios,
83 | .ns-modal[ios] {
84 | --const-segmented-bar-height: 30;
85 |
86 | --a11y-font-size: calc(var(--const-font-size) * var(--a11y-fontscale-factor));
87 | --a11y-font-size-2: calc(var(--const-font-size-2) * var(--a11y-fontscale-factor));
88 | --a11y-btn-font-size: calc(var(--const-btn-font-size) * var(--a11y-fontscale-factor));
89 | --a11y-icon-font-size-lg: calc(var(--const-icon-font-size-lg) * var(--a11y-fontscale-factor));
90 | --a11y-segmented-bar-font-size: calc(var(--const-segmented-bar-font-size) * var(--a11y-fontscale-factor));
91 | --a11y-segmented-bar-height: calc(var(--const-segmented-bar-height) * var(--a11y-fontscale-factor));
92 | --a11y-drawer-header-font-size: calc(var(--const-drawer-header-font-size) * var(--a11y-fontscale-factor));
93 |
94 | @each $text-class, $values in $a11y-font-sizes {
95 | $a11y-font-size: map-get($values, font-size);
96 |
97 | --a11y-#{$text-class}-size: calc(#{$a11y-font-size} * var(--a11y-fontscale-factor));
98 | }
99 |
100 | @each $label-class, $values in $a11y-label-classes {
101 | $a11y-font-size: map-get($values, font-size);
102 |
103 | --a11y-#{$label-class}-size: calc(#{$a11y-font-size} * var(--a11y-fontscale-factor));
104 | }
105 | }
106 |
107 | .a11y-enabled {
108 | a11y-enabled: true;
109 | }
110 |
111 | Slider {
112 | @extend .a11y-enabled;
113 | a11y-role: adjustable;
114 | }
115 |
116 | .h1,
117 | .h2,
118 | .h3,
119 | .h4,
120 | .h5,
121 | .h6,
122 | .a11y-header {
123 | @extend .a11y-enabled;
124 | a11y-role: header;
125 | }
126 |
127 | .a11y-hidden {
128 | a11y-hidden: true;
129 | }
130 |
131 | .a11y-btn,
132 | button {
133 | @extend .a11y-enabled;
134 | a11y-role: button;
135 | > * {
136 | @extend .a11y-hidden;
137 | }
138 | }
139 |
140 | %a11y-checkable {
141 | @extend .a11y-enabled;
142 |
143 | &.a11y-checked {
144 | a11y-state: checked;
145 | }
146 | }
147 |
148 | .a11y-radiobtn {
149 | @extend %a11y-checkable;
150 | a11y-role: radiobutton;
151 | }
152 | .a11y-checkbox {
153 | @extend %a11y-checkable;
154 | a11y-role: checkbox;
155 | }
156 | .a11y-switch {
157 | @extend %a11y-checkable;
158 | a11y-role: switch;
159 | }
160 | .a11y-disabled {
161 | a11y-state: disabled;
162 | }
163 |
164 | .a11y-selected {
165 | a11y-state: selected;
166 | }
167 |
168 | .a11y-weblink {
169 | @extend .a11y-enabled;
170 | a11y-role: link;
171 | }
172 |
173 | .a11y-start-media {
174 | @extend .a11y-enabled;
175 | a11y-role: button;
176 | a11y-media-session: true;
177 | }
178 |
179 | .a11y-image {
180 | @extend .a11y-enabled;
181 | a11y-role: image;
182 | }
183 |
184 | .a11y-search-btn {
185 | @extend .a11y-enabled;
186 | a11y-role: search;
187 | }
188 |
189 | .a11y-updates-often {
190 | @extend .a11y-enabled;
191 | a11y-live-region: assertive;
192 | }
193 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/scss/a11y.compat.scss:
--------------------------------------------------------------------------------
1 | $compat: true;
2 |
3 | @import './a11y';
4 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/scss/a11y.scss:
--------------------------------------------------------------------------------
1 | $compat: false !default;
2 |
3 | @import 'fontscales';
4 | @import 'a11y-helpers';
5 |
6 | .ns-a11y {
7 | @import 'core/index';
8 | }
9 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/scss/core/_controls.scss:
--------------------------------------------------------------------------------
1 | @if not $compat {
2 | button,
3 | .nt-button {
4 | font-size: var(--a11y-btn-font-size);
5 | a11y-enabled: true;
6 | a11y-role: button;
7 | }
8 |
9 | SegmentedBar,
10 | .nt-segmented-bar {
11 | font-size: var(--a11y-segmented-bar-font-size);
12 | height: var(--a11y-segmented-bar-height);
13 | }
14 |
15 | TabView,
16 | .nt-tab-view {
17 | tab-text-font-size: var(--a11y-font-size);
18 | }
19 |
20 | BottomNavigation,
21 | .nt-bottom-navigation {
22 | font-size: var(--a11y-font-size-2);
23 | }
24 |
25 | ListView,
26 | RadListView,
27 | .nt-list-view {
28 | NTIcon,
29 | .nt-icon {
30 | font-size: var(--a11y-icon-font-size-lg);
31 | }
32 | }
33 |
34 | RadSideDrawer,
35 | .nt-drawer {
36 | .sidedrawer-list-item {
37 | .sidedrawer-list-item-icon {
38 | font-size: var(--a11y-icon-font-size-lg);
39 | }
40 | }
41 |
42 | .nt-drawer__header {
43 | > label {
44 | font-size: var(--a11y-drawer-header-font-size);
45 | }
46 |
47 | &-brand {
48 | font-size: var(--a11y-font-size);
49 | }
50 | }
51 | }
52 |
53 | form,
54 | .nt-form {
55 | .nt-form__title {
56 | font-size: var(--a11y-btn-font-size);
57 | }
58 | }
59 |
60 | // Form fields
61 | TextView,
62 | TextField,
63 | PickerField,
64 | DatePickerField,
65 | TimePickerField,
66 | DateTimePickerFields,
67 | DataFormEditorCore {
68 | font-size: var(--a11y-font-size);
69 | }
70 |
71 | NTInput,
72 | .nt-input {
73 | > label {
74 | font-size: var(--a11y-font-size);
75 | }
76 |
77 | & > NTIcon,
78 | & > .nt-icon {
79 | font-size: var(--a11y-icon-font-size-lg);
80 | }
81 | }
82 |
83 | ActionBar,
84 | NTActionBar,
85 | .nt-action-bar {
86 | font-size: var(--a11y-btn-font-size);
87 |
88 | NTIcon,
89 | label,
90 | button,
91 | .nt-action-bar__item {
92 | font-size: var(--a11y-font-size);
93 | }
94 |
95 | & > label {
96 | font-size: var(--a11y-btn-font-size);
97 | }
98 |
99 | > label,
100 | > GridLayout label {
101 | font-size: var(--a11y-btn-font-size);
102 | }
103 | }
104 | } @else {
105 | .btn {
106 | font-size: var(--a11y-btn-font-size);
107 | a11y-enabled: true;
108 | a11y-role: button;
109 | }
110 |
111 | SegmentedBar {
112 | font-size: var(--a11y-segmented-bar-font-size);
113 | height: var(--a11y-segmented-bar-height);
114 | }
115 |
116 | .tab-view {
117 | tab-text-font-size: var(--a11y-font-size);
118 | }
119 |
120 | .list-group {
121 | .list-group-item-text {
122 | font-size: var(--a11y-font-size-2);
123 | }
124 | }
125 |
126 | .side-drawer {
127 | .sidedrawer-header {
128 | > label {
129 | font-size: var(--a11y-drawer-header-font-size);
130 | }
131 |
132 | &-brand {
133 | font-size: var(--a11y-font-size);
134 | }
135 | }
136 | }
137 |
138 | .form {
139 | .nt-form__title {
140 | font-size: var(--a11y-btn-font-size);
141 | }
142 | }
143 |
144 | .input {
145 | .nt-form__title {
146 | font-size: var(--a11y-font-size);
147 | }
148 | }
149 |
150 | .input-field {
151 | > .label {
152 | font-size: var(--a11y-font-size);
153 | }
154 | }
155 |
156 | .action-bar {
157 | font-size: var(--a11y-btn-font-size);
158 | .action-item {
159 | font-size: var(--a11y-font-size);
160 | }
161 |
162 | & > label {
163 | font-size: var(--a11y-btn-font-size);
164 | }
165 |
166 | > label,
167 | > GridLayout label {
168 | font-size: var(--a11y-btn-font-size);
169 | }
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/scss/core/_headings.scss:
--------------------------------------------------------------------------------
1 | @each $label-class in map-keys($a11y-label-classes) {
2 | .#{$label-class} {
3 | font-size: var(--a11y-#{$label-class}-size);
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/scss/core/_index.scss:
--------------------------------------------------------------------------------
1 | // Utilities
2 | @import './utilities/index';
3 |
4 | @import './headings';
5 | @import './utilities';
6 | @import './controls';
7 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/scss/core/_utilities.scss:
--------------------------------------------------------------------------------
1 | NTIcon,
2 | .nt-icon {
3 | font-size: var(--a11y-btn-font-size);
4 | }
5 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/scss/core/utilities/_index.scss:
--------------------------------------------------------------------------------
1 | @import 'text';
2 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/scss/core/utilities/_text.scss:
--------------------------------------------------------------------------------
1 | @each $text-class in map-keys($a11y-font-sizes) {
2 | .#{$text-class} {
3 | font-size: var(--a11y-#{$text-class}-size);
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/trace.ts:
--------------------------------------------------------------------------------
1 | import { Trace } from '@nativescript/core';
2 | export namespace categories {
3 | export const A11Y = 'A11Y';
4 | export const GlobalEvents = `${A11Y}-GlobalEvents`;
5 | export const FontScale = `${A11Y}-FontScale`;
6 | export const AndroidHelper = `${A11Y}-AndroidHelper`;
7 |
8 | export const separator = ',';
9 | export const All = [A11Y, GlobalEvents, FontScale, AndroidHelper];
10 | }
11 |
12 | export function isTraceEnabled() {
13 | return Trace.isEnabled();
14 | }
15 |
16 | /**
17 | * Write to NativeScript's trace.
18 | */
19 | export function writeTrace(message: string, type = Trace.messageType.info, category = categories.A11Y) {
20 | if (isTraceEnabled()) {
21 | Trace.write(message, category, type);
22 | }
23 | }
24 |
25 | export function writeFontScaleTrace(message: string, type = Trace.messageType.info) {
26 | writeTrace(message, type, categories.FontScale);
27 | }
28 |
29 | export function writeGlobalEventsTrace(message: string, type = Trace.messageType.info) {
30 | writeTrace(message, type, categories.GlobalEvents);
31 | }
32 |
33 | export function writeErrorTrace(message) {
34 | Trace.write(message, categories.A11Y, Trace.messageType.error);
35 | }
36 |
37 | export function writeWarnTrace(message) {
38 | writeTrace(message, Trace.messageType.warn);
39 | }
40 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc",
5 | "rootDir": "."
6 | },
7 | "exclude": ["**/*.spec.ts", "angular"],
8 | "include": ["**/*.ts", "**/*.d.ts", "references.d.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/ui/action-bar/action-bar.android.ts:
--------------------------------------------------------------------------------
1 | export * from '@nativescript/core/ui/action-bar';
2 | import { ActionBar } from '@nativescript/core';
3 | import { setViewFunction, wrapFunction } from '../../utils';
4 | import { AccessibilityHelper, getAndroidView } from '../../utils/accessibility-helper';
5 | import { commonFunctions } from '../core/view-common';
6 |
7 | setViewFunction(ActionBar, commonFunctions.accessibilityScreenChanged, function accessibilityScreenChanged(this: ActionBar) {
8 | const nativeView = getAndroidView(this);
9 | if (!nativeView) {
10 | return;
11 | }
12 |
13 | const wasFocusable = android.os.Build.VERSION.SDK_INT >= 26 && nativeView.getFocusable();
14 | const hasHeading = android.os.Build.VERSION.SDK_INT >= 28 && nativeView.isAccessibilityHeading();
15 | const importantForA11Y = nativeView.getImportantForAccessibility();
16 |
17 | try {
18 | nativeView.setFocusable(false);
19 | nativeView.setImportantForAccessibility(android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO);
20 |
21 | let announceView: android.view.View | null = null;
22 |
23 | const numChildren = nativeView.getChildCount();
24 | for (let i = 0; i < numChildren; i += 1) {
25 | const childView = nativeView.getChildAt(i);
26 | if (!childView) {
27 | continue;
28 | }
29 |
30 | childView.setFocusable(true);
31 | if (childView instanceof androidx.appcompat.widget.AppCompatTextView) {
32 | announceView = childView;
33 | if (android.os.Build.VERSION.SDK_INT >= 28) {
34 | announceView.setAccessibilityHeading(true);
35 | }
36 | }
37 | }
38 |
39 | if (!announceView) {
40 | announceView = nativeView;
41 | }
42 |
43 | announceView.setFocusable(true);
44 | announceView.setImportantForAccessibility(android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES);
45 |
46 | announceView.sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED);
47 | announceView.sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
48 | } catch {
49 | // ignore
50 | } finally {
51 | setTimeout(() => {
52 | const localAndroidView = getAndroidView(this);
53 | if (!localAndroidView) {
54 | return;
55 | }
56 |
57 | if (android.os.Build.VERSION.SDK_INT >= 28) {
58 | nativeView.setAccessibilityHeading(hasHeading);
59 | }
60 |
61 | if (android.os.Build.VERSION.SDK_INT >= 26) {
62 | localAndroidView.setFocusable(wasFocusable);
63 | }
64 | localAndroidView.setImportantForAccessibility(importantForA11Y);
65 | });
66 | }
67 | });
68 |
69 | for (const fnName of ['update', '_onTitlePropertyChanged']) {
70 | wrapFunction(
71 | ActionBar.prototype,
72 | fnName,
73 | function (this: ActionBar) {
74 | AccessibilityHelper.updateContentDescription(this, true);
75 | },
76 | 'ActionBar',
77 | );
78 | }
79 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/ui/action-bar/action-bar.d.ts:
--------------------------------------------------------------------------------
1 | export * from '@nativescript/core/ui/action-bar/action-bar';
2 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/ui/action-bar/action-bar.ios.ts:
--------------------------------------------------------------------------------
1 | export * from '@nativescript/core/ui/action-bar';
2 | import { ActionBar } from '@nativescript/core';
3 | import { isTraceEnabled, writeTrace } from '../../trace';
4 | import { wrapFunction } from '../../utils';
5 | import { getUIView } from '../../utils/accessibility-helper';
6 | import { accessibilityHintProperty, accessibilityLabelProperty, accessibilityLanguageProperty, accessibilityValueProperty } from '../core/view-common';
7 |
8 | function updateA11YProperty(tnsView: ActionBar, propName: string, value: string | null | undefined) {
9 | value = value != null ? `${value}` : null;
10 | const cls = `ActionBar<${tnsView}.ios>.${propName} = ${value}`;
11 | const uiView = getUIView(tnsView);
12 | if (!uiView) {
13 | if (isTraceEnabled()) {
14 | writeTrace(`${cls} - no nativeView`);
15 | }
16 |
17 | return;
18 | }
19 |
20 | if (isTraceEnabled()) {
21 | writeTrace(`${cls}`);
22 | }
23 |
24 | uiView[propName] = value;
25 | if (!tnsView.page) {
26 | return;
27 | }
28 |
29 | const pageNativeView = tnsView.page.ios as UIViewController;
30 | if (!pageNativeView || !pageNativeView.navigationItem) {
31 | if (isTraceEnabled()) {
32 | writeTrace(`${cls} - no page nativeView`);
33 | }
34 |
35 | return;
36 | }
37 |
38 | pageNativeView.navigationItem[propName] = value;
39 | }
40 |
41 | ActionBar.prototype[accessibilityLabelProperty.setNative] = function accessibilityLabelSetNative(this: ActionBar, label: string | null) {
42 | if (this.title && label != null) {
43 | label = `${this.title}. ${label}`;
44 | }
45 |
46 | updateA11YProperty(this, 'accessibilityLabel', label);
47 | };
48 |
49 | ActionBar.prototype[accessibilityValueProperty.setNative] = function accessibilityValueSetNative(this: ActionBar, value: string) {
50 | updateA11YProperty(this, 'accessibilityValue', value);
51 | };
52 |
53 | ActionBar.prototype[accessibilityHintProperty.setNative] = function accessibilityHintSetNative(this: ActionBar, hint: string) {
54 | updateA11YProperty(this, 'accessibilityValue', hint);
55 | };
56 |
57 | ActionBar.prototype[accessibilityLanguageProperty.setNative] = function accessibilityLanguageSetNative(this: ActionBar, lang: string) {
58 | updateA11YProperty(this, 'accessibilityLanguage', lang);
59 | };
60 |
61 | wrapFunction(
62 | ActionBar.prototype,
63 | 'update',
64 | function customUpdate() {
65 | for (const propName of ['accessibilityLabel', 'accessibilityValue', 'accessibilityLanguage', 'accessibilityHint']) {
66 | updateA11YProperty(this, propName, this[propName]);
67 | }
68 | },
69 | 'ActionBar',
70 | );
71 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/ui/core/view-common.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import { isIOS, View } from '@nativescript/core';
4 | import { addBooleanCssPropertyToView, addCssPropertyToView, addPropertyToView, makePropertyEnumConverter, setViewFunction } from '../../utils/helpers';
5 |
6 | export enum AccessibilityTrait {
7 | /**
8 | * The element has no traits.
9 | */
10 | None = 'none',
11 |
12 | /**
13 | * The element should be treated as a button.
14 | */
15 | Button = 'button',
16 |
17 | /**
18 | * The element should be treated as a link.
19 | */
20 | Link = 'link',
21 |
22 | /**
23 | * The element should be treated as a search field.
24 | */
25 | SearchField = 'search',
26 |
27 | /**
28 | * The element should be treated as an image.
29 | */
30 | Image = 'image',
31 |
32 | /**
33 | * The element is currently selected.
34 | */
35 | Selected = 'selected',
36 |
37 | /**
38 | * The element plays its own sound when activated.
39 | */
40 | PlaysSound = 'plays',
41 |
42 | /**
43 | * The element behaves as a keyboard key.
44 | */
45 | KeyboardKey = 'key',
46 |
47 | /**
48 | * The element should be treated as static text that cannot change.
49 | */
50 | StaticText = 'text',
51 |
52 | /**
53 | * The element provides summary information when the application starts.
54 | */
55 | SummaryElement = 'summary',
56 |
57 | /**
58 | * The element is not enabled and does not respond to user interaction.
59 | */
60 | NotEnabled = 'disabled',
61 |
62 | /**
63 | * The element frequently updates its label or value.
64 | */
65 | UpdatesFrequently = 'frequentUpdates',
66 |
67 | /**
68 | * The element starts a media session when it is activated.
69 | */
70 | StartsMediaSession = 'startsMedia',
71 |
72 | /**
73 | * The element allows continuous adjustment through a range of values.
74 | */
75 | Adjustable = 'adjustable',
76 |
77 | /**
78 | * The element allows direct touch interaction for VoiceOver users.
79 | */
80 | AllowsDirectInteraction = 'allowsDirectInteraction',
81 |
82 | /**
83 | * The element should cause an automatic page turn when VoiceOver finishes reading the text within it.
84 | * Note: Requires custom view with accessibilityScroll(...)
85 | */
86 | CausesPageTurn = 'pageTurn',
87 |
88 | /**
89 | * The element is a header that divides content into sections, such as the title of a navigation bar.
90 | */
91 | Header = 'header',
92 | }
93 |
94 | export enum AccessibilityRole {
95 | /**
96 | * The element has no traits.
97 | */
98 | None = 'none',
99 |
100 | /**
101 | * The element should be treated as a button.
102 | */
103 | Button = 'button',
104 |
105 | /**
106 | * The element should be treated as a link.
107 | */
108 | Link = 'link',
109 |
110 | /**
111 | * The element should be treated as a search field.
112 | */
113 | Search = 'search',
114 |
115 | /**
116 | * The element should be treated as an image.
117 | */
118 | Image = 'image',
119 |
120 | /**
121 | * The element should be treated as a image button.
122 | */
123 | ImageButton = 'image_button',
124 |
125 | /**
126 | * The element behaves as a keyboard key.
127 | */
128 | KeyboardKey = 'keyboard_key',
129 |
130 | /**
131 | * The element should be treated as static text that cannot change.
132 | */
133 | StaticText = 'text_field',
134 |
135 | /**
136 | * The element allows continuous adjustment through a range of values.
137 | */
138 | Adjustable = 'adjustable',
139 |
140 | /**
141 | * The element provides summary information when the application starts.
142 | */
143 | Summary = 'summery',
144 |
145 | /**
146 | * The element is a header that divides content into sections, such as the title of a navigation bar.
147 | */
148 | Header = 'header',
149 | Checkbox = 'checkbox',
150 | ProgressBar = 'progress_bar',
151 | RadioButton = 'radiobutton',
152 | SpinButton = 'spin_button',
153 | Switch = 'switch',
154 | }
155 |
156 | export enum AccessibilityState {
157 | Selected = 'selected',
158 | Checked = 'checked',
159 | Unchecked = 'unchecked',
160 | Disabled = 'disabled',
161 | }
162 |
163 | export enum AccessibilityLiveRegion {
164 | None = 'none',
165 | Polite = 'polite',
166 | Assertive = 'assertive',
167 | }
168 |
169 | export const commonFunctions = {
170 | accessibilityAnnouncement: 'accessibilityAnnouncement',
171 | accessibilityScreenChanged: 'accessibilityScreenChanged',
172 | };
173 |
174 | export const iosFunctions = {
175 | iosPostAccessibilityNotification: 'iosPostAccessibilityNotification',
176 | };
177 | export const androidFunctions = {
178 | androidSendAccessibilityEvent: 'androidSendAccessibilityEvent',
179 | };
180 | export const allFunctions = {
181 | ...commonFunctions,
182 | ...iosFunctions,
183 | ...androidFunctions,
184 | };
185 |
186 | for (const fnName of Object.keys(allFunctions)) {
187 | setViewFunction(View, fnName);
188 | }
189 |
190 | const accessiblePropertyName = 'accessible';
191 | const accessibleCssName = 'a11y-enabled';
192 | const accessibilityHiddenPropertyName = 'accessibilityHidden';
193 | const accessibilityHiddenCssName = 'a11y-hidden';
194 | const accessibilityIdPropertyName = 'accessibilityIdentifier';
195 | const accessibilityRolePropertyName = 'accessibilityRole';
196 | const accessibilityRoleCssName = 'a11y-role';
197 | const accessibilityStatePropertyName = 'accessibilityState';
198 | const accessibilityStateCssName = 'a11y-state';
199 | const accessibilityLabelPropertyName = 'accessibilityLabel';
200 | const accessibilityValuePropertyName = 'accessibilityValue';
201 | const accessibilityHintPropertyName = 'accessibilityHint';
202 | const accessibilityLiveRegionPropertyName = 'accessibilityLiveRegion';
203 | const accessibilityLiveRegionCssName = 'a11y-live-region';
204 | const accessibilityTraitsPropertyName = 'accessibilityTraits';
205 | const accessibilityLanguagePropertyName = 'accessibilityLanguage';
206 | const accessibilityLanguageCssName = 'a11y-lang';
207 | const accessibilityMediaSessionPropertyName = 'accessibilityMediaSession';
208 | const accessibilityMediaSessionCssName = 'a11y-media-session';
209 |
210 | // Common properties
211 | export const accessibleCssProperty = addBooleanCssPropertyToView(View, accessiblePropertyName, accessibleCssName);
212 | export const accessibilityIdProperty = addPropertyToView(View, accessibilityIdPropertyName);
213 | export const accessibilityRoleCssProperty = addCssPropertyToView(
214 | View,
215 | accessibilityRolePropertyName,
216 | accessibilityRoleCssName,
217 | false,
218 | undefined,
219 | makePropertyEnumConverter(AccessibilityRole),
220 | );
221 | export const accessibilityStateCssProperty = addCssPropertyToView(
222 | View,
223 | accessibilityStatePropertyName,
224 | accessibilityStateCssName,
225 | false,
226 | undefined,
227 | makePropertyEnumConverter(AccessibilityState),
228 | );
229 | export const accessibilityLabelProperty = addPropertyToView(View, accessibilityLabelPropertyName);
230 | export const accessibilityValueProperty = addPropertyToView(View, accessibilityValuePropertyName);
231 | export const accessibilityHintProperty = addPropertyToView(View, accessibilityHintPropertyName);
232 | export const accessibilityHiddenCssProperty = addBooleanCssPropertyToView(View, accessibilityHiddenPropertyName, accessibilityHiddenCssName, !!isIOS);
233 | export const accessibilityLiveRegionCssProperty = addCssPropertyToView<'none' | 'polite' | 'assertive'>(
234 | View,
235 | accessibilityLiveRegionPropertyName,
236 | accessibilityLiveRegionCssName,
237 | false,
238 | 'none',
239 | (value: string): 'none' | 'polite' | 'assertive' => {
240 | switch (`${value}`.toLowerCase()) {
241 | case 'none': {
242 | return 'none';
243 | }
244 | case 'polite': {
245 | return 'polite';
246 | }
247 | case 'assertive': {
248 | return 'assertive';
249 | }
250 | }
251 |
252 | return 'none';
253 | },
254 | );
255 |
256 | // iOS properties:
257 | export const accessibilityTraitsProperty = addPropertyToView(View, accessibilityTraitsPropertyName);
258 | export const accessibilityLanguageProperty = addCssPropertyToView(View, accessibilityLanguagePropertyName, accessibilityLanguageCssName);
259 | export const accessibilityMediaSessionCssProperty = addBooleanCssPropertyToView(
260 | View,
261 | accessibilityMediaSessionPropertyName,
262 | accessibilityMediaSessionCssName,
263 | false,
264 | );
265 |
266 | Object.defineProperties(View, {
267 | accessibilityFocusEvent: {
268 | configurable: true,
269 | get() {
270 | return 'accessibilityFocus';
271 | },
272 | },
273 | accessibilityBlurEvent: {
274 | configurable: true,
275 | get() {
276 | return 'accessibilityBlur';
277 | },
278 | },
279 | accessibilityFocusChangedEvent: {
280 | configurable: true,
281 | get() {
282 | return 'accessibilityFocusChanged';
283 | },
284 | },
285 | AccessibilityTrait: {
286 | configurable: true,
287 | get() {
288 | return AccessibilityTrait;
289 | },
290 | },
291 | AccessibilityComponentType: {
292 | configurable: true,
293 | get() {
294 | return AccessibilityRole;
295 | },
296 | },
297 | AccessibilityState: {
298 | configurable: true,
299 | get() {
300 | return AccessibilityState;
301 | },
302 | },
303 | });
304 |
305 | Object.defineProperties(View.prototype, {
306 | importantForAccessibility: {
307 | configurable: true,
308 | get() {
309 | return null;
310 | },
311 | set(this: View, value) {
312 | console.warn(`DEPRECATED: ${this}.importantForAccessibility = "${value}" is no longer supported. Please use "${accessibilityHiddenPropertyName}"`);
313 | if (value && value !== 'yes') {
314 | this[accessibilityHiddenPropertyName] = true;
315 | }
316 | },
317 | },
318 | accessibilityElementsHidden: {
319 | configurable: true,
320 | get() {
321 | return null;
322 | },
323 | set(this: View, value) {
324 | console.warn(`DEPRECATED: ${this}.accessibilityElementsHidden = "${value}" is no longer supported. Please use "${accessibilityHiddenPropertyName}"`);
325 |
326 | this[accessibilityHiddenPropertyName] = !!value;
327 | },
328 | },
329 | accessibilityComponentType: {
330 | configurable: true,
331 | get(this: View) {
332 | return this[accessibilityRolePropertyName];
333 | },
334 | set(this: View, value) {
335 | console.warn(`DEPRECATED: ${this}.accessibilityComponentType = "${value}" is no longer supported. Please use "${accessibilityRolePropertyName}"`);
336 |
337 | if (value === 'radiobutton_checked') {
338 | this[accessibilityRolePropertyName] = AccessibilityRole.RadioButton;
339 | this[accessibilityStatePropertyName] = AccessibilityState.Checked;
340 | } else if (value === 'radiobutton_unchecked') {
341 | this[accessibilityRolePropertyName] = AccessibilityRole.RadioButton;
342 | this[accessibilityStatePropertyName] = AccessibilityState.Unchecked;
343 | } else {
344 | this[accessibilityRolePropertyName] = value;
345 | }
346 | },
347 | },
348 | });
349 |
350 | export { View };
351 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/ui/core/view.android.ts:
--------------------------------------------------------------------------------
1 | import { View } from '@nativescript/core';
2 | import { isTraceEnabled, writeTrace } from '../../trace';
3 | import { AccessibilityHelper, getAndroidView } from '../../utils/accessibility-helper';
4 | import { setViewFunction } from '../../utils/helpers';
5 | import {
6 | accessibilityHiddenCssProperty,
7 | accessibilityHintProperty,
8 | accessibilityLabelProperty,
9 | accessibilityLiveRegionCssProperty,
10 | AccessibilityRole,
11 | accessibilityRoleCssProperty,
12 | accessibilityStateCssProperty,
13 | accessibilityValueProperty,
14 | accessibleCssProperty,
15 | androidFunctions,
16 | commonFunctions,
17 | } from './view-common';
18 |
19 | View.prototype[accessibilityHiddenCssProperty.setNative] = function accessibilityHiddenSetNative(this: View, value: boolean) {
20 | const androidView = getAndroidView(this);
21 | if (!androidView) {
22 | return;
23 | }
24 |
25 | if (value) {
26 | if (isTraceEnabled()) {
27 | writeTrace(`View<${this}.android>.accessibilityHidden - hide element`);
28 | }
29 |
30 | androidView.setImportantForAccessibility(android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
31 | } else {
32 | if (isTraceEnabled()) {
33 | writeTrace(`View<${this}.android>.accessibilityHidden - show element`);
34 | }
35 |
36 | androidView.setImportantForAccessibility(android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES);
37 | }
38 | };
39 |
40 | View.prototype[accessibilityRoleCssProperty.setNative] = function accessibilityComponentTypeSetNative(this: View, value: string) {
41 | const androidView = getAndroidView(this);
42 | if (!androidView) {
43 | return;
44 | }
45 |
46 | AccessibilityHelper.updateAccessibilityProperties(this);
47 |
48 | if (android.os.Build.VERSION.SDK_INT >= 28) {
49 | androidView.setAccessibilityHeading(value === AccessibilityRole.Header);
50 | }
51 | };
52 |
53 | View.prototype[accessibilityStateCssProperty.setNative] = function accessibilityStateSetNative(this: View) {
54 | AccessibilityHelper.updateAccessibilityProperties(this);
55 | };
56 |
57 | View.prototype[accessibilityLiveRegionCssProperty.setNative] = function accessibilityLiveRegionSetNative(this: View, value: string) {
58 | const androidView = getAndroidView(this);
59 | if (!androidView) {
60 | return;
61 | }
62 |
63 | switch (value.toLowerCase()) {
64 | case 'assertive': {
65 | androidView.setAccessibilityLiveRegion(android.view.View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE);
66 | if (isTraceEnabled()) {
67 | writeTrace(`View<${this}.android>.accessibilityLiveRegion - value: ${value}. Sets to ACCESSIBILITY_LIVE_REGION_ASSERTIVE`);
68 | }
69 | break;
70 | }
71 | case 'polite': {
72 | androidView.setAccessibilityLiveRegion(android.view.View.ACCESSIBILITY_LIVE_REGION_POLITE);
73 | if (isTraceEnabled()) {
74 | writeTrace(`View<${this}.android>.accessibilityLiveRegion - value: ${value}. Sets to ACCESSIBILITY_LIVE_REGION_POLITE`);
75 | }
76 | break;
77 | }
78 | default: {
79 | androidView.setAccessibilityLiveRegion(android.view.View.ACCESSIBILITY_LIVE_REGION_NONE);
80 | if (isTraceEnabled()) {
81 | writeTrace(`View<${this}.android>.accessibilityLiveRegion - value: ${value}. Sets to ACCESSIBILITY_LIVE_REGION_NONE`);
82 | }
83 | break;
84 | }
85 | }
86 | };
87 |
88 | View.prototype[accessibleCssProperty.setNative] = function accessibleSetNative(this: View, isAccessible: boolean) {
89 | const androidView = getAndroidView(this);
90 | if (!androidView) {
91 | return;
92 | }
93 |
94 | androidView.setFocusable(!!isAccessible);
95 |
96 | if (isTraceEnabled()) {
97 | writeTrace(`View<${this}.android>.accessible = ${isAccessible}`);
98 | }
99 |
100 | AccessibilityHelper.updateAccessibilityProperties(this);
101 | };
102 |
103 | setViewFunction(View, androidFunctions.androidSendAccessibilityEvent, function sendAccessibilityEvent(this: View, eventName: string, msg?: string) {
104 | const cls = `View<${this}.android>.sendAccessibilityEvent(${eventName} -> ${msg})`;
105 |
106 | let androidView = getAndroidView(this);
107 | if (androidView) {
108 | if (isTraceEnabled()) {
109 | writeTrace(`${cls}`);
110 | }
111 | AccessibilityHelper.sendAccessibilityEvent(this, eventName, msg);
112 |
113 | return;
114 | }
115 |
116 | androidView = null;
117 |
118 | if (isTraceEnabled()) {
119 | writeTrace(`${cls} -> waiting for view to be loaded`);
120 | }
121 |
122 | this.once(View.loadedEvent, (args) => {
123 | androidView = getAndroidView(args.object as View);
124 | if (!androidView) {
125 | if (isTraceEnabled()) {
126 | writeTrace(`${cls} -> view not loaded -> ${eventName} -> ${msg}`);
127 | }
128 |
129 | return;
130 | }
131 |
132 | if (isTraceEnabled()) {
133 | writeTrace(`${cls} -> view loaded -> ${eventName} -> ${msg}`);
134 | }
135 | AccessibilityHelper.sendAccessibilityEvent(this, eventName, msg);
136 | });
137 | });
138 |
139 | setViewFunction(View, commonFunctions.accessibilityAnnouncement, function accessibilityAnnouncement(this: View, msg?: string) {
140 | const cls = `View<${this}.android>.accessibilityAnnouncement(${JSON.stringify(msg)})`;
141 |
142 | if (isTraceEnabled()) {
143 | writeTrace(cls);
144 | }
145 |
146 | if (!msg) {
147 | msg = this.accessibilityLabel;
148 |
149 | if (isTraceEnabled()) {
150 | writeTrace(`${cls} - no msg sending accessibilityLabel = ${JSON.stringify(this.accessibilityLabel)} instead`);
151 | }
152 | }
153 |
154 | this.androidSendAccessibilityEvent('announcement', msg);
155 | });
156 |
157 | View.prototype[accessibilityLabelProperty.setNative] = function accessibilityLabelSetNative(this: View, label: string) {
158 | this._androidContentDescriptionUpdated = true;
159 | const newValue = AccessibilityHelper.updateContentDescription(this);
160 | if (isTraceEnabled()) {
161 | writeTrace(`View<${this}.android>.accessibilityLabel = "${label}" - contentDesc = "${newValue}"`);
162 | }
163 | };
164 |
165 | View.prototype[accessibilityValueProperty.setNative] = function accessibilityLabelSetNative(this: View, value: string) {
166 | this._androidContentDescriptionUpdated = true;
167 | const newValue = AccessibilityHelper.updateContentDescription(this);
168 | if (isTraceEnabled()) {
169 | writeTrace(`View<${this}.android>.accessibilityValue = "${value}" - contentDesc = "${newValue}"`);
170 | }
171 | };
172 |
173 | View.prototype[accessibilityHintProperty.setNative] = function accessibilityLabelSetNative(this: View, hint: string) {
174 | this._androidContentDescriptionUpdated = true;
175 | const newValue = AccessibilityHelper.updateContentDescription(this);
176 | if (isTraceEnabled()) {
177 | writeTrace(`View<${this}.android>.accessibilityHint = "${hint}" - contentDesc = "${newValue}"`);
178 | }
179 | };
180 |
181 | setViewFunction(View, commonFunctions.accessibilityScreenChanged, function accessibilityScreenChanged(this: View) {
182 | this.androidSendAccessibilityEvent('window_state_changed');
183 | });
184 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/ui/core/view.d.ts:
--------------------------------------------------------------------------------
1 | import { EventData } from '@nativescript/core';
2 | import { FontScaleObservable } from '../../utils/FontScaleObservable';
3 | import {
4 | AccessibilityLiveRegion as _AccessibilityLiveRegion,
5 | AccessibilityRole as _AccessibilityRole,
6 | AccessibilityState as _AccessibilityState,
7 | AccessibilityTrait as _AccessibilityTrait,
8 | } from './view-common';
9 |
10 | declare module '@nativescript/core/ui/core/view' {
11 | // @ts-ignore
12 | type PostAccessibilityNotificationType = 'announcement' | 'screen' | 'layout';
13 |
14 | interface View {
15 | fontScaleObservable?: FontScaleObservable | void;
16 | // Common for both platforms
17 |
18 | /**
19 | * If `true` the element is an accessibility element and all the children will be treated as a single selectable component.
20 | */
21 | accessible?: boolean;
22 |
23 | /**
24 | * Make an announcement to the screen reader.
25 | */
26 | accessibilityAnnouncement(msg?: string): void;
27 |
28 | /**
29 | * Announce screen changed
30 | */
31 | accessibilityScreenChanged(): void;
32 |
33 | /**
34 | * Short description of the element, ideally one word.
35 | */
36 | accessibilityLabel?: string;
37 |
38 | /**
39 | * Current value of the element in a localized string.
40 | */
41 | accessibilityValue?: string;
42 |
43 | /**
44 | * A hint describes the elements behavior. Example: 'Tap change playback speed'
45 | */
46 | accessibilityHint?: string;
47 |
48 | /**
49 | * Set the elements unique accessibilityIdentifier.
50 | */
51 | accessibilityIdentifier?: string;
52 |
53 | /**
54 | * Hide the element from the a11y service
55 | */
56 | accessibilityHidden?: boolean;
57 |
58 | accessibilityRole?: _AccessibilityRole | string;
59 | accessibilityState?: _AccessibilityState | string;
60 |
61 | accessibilityLiveRegion?: _AccessibilityLiveRegion | string;
62 | androidSendAccessibilityEvent(eventName: string, text?: string);
63 |
64 | // iOS Specific
65 | accessibilityTraits?: _AccessibilityTrait | _AccessibilityTrait[] | string | string[];
66 |
67 | /**
68 | * This view starts a media session. Equivalent to trait = startsMedia
69 | */
70 | accessibilityMediaSession?: boolean;
71 |
72 | /**
73 | * Sets the language in which to speak the element's label and value.
74 | * Accepts language ID tags that follows the "BCP 47" specification.
75 | */
76 | accessibilityLanguage?: string;
77 |
78 | /**
79 | * iOS: post accessibility notification.
80 | * type = 'announcement' will announce `args` via VoiceOver. If no args element will be announced instead.
81 | * type = 'layout' used when the layout of a screen changes.
82 | * type = 'screen' large change made to the screen.
83 | */
84 | iosPostAccessibilityNotification(type: PostAccessibilityNotificationType, args?: string);
85 |
86 | /**
87 | * Internal use only. This is used to limit the number of updates to android.view.View.setContentDescription()
88 | */
89 | _androidContentDescriptionUpdated?: boolean;
90 | }
91 |
92 | // Adding static properties
93 | namespace View {
94 | /**
95 | * Event triggered than the view receives the accessibility focus
96 | */
97 | let accessibilityFocusEvent: string;
98 |
99 | /**
100 | * Event triggered than the view looses the accessibility focus
101 | */
102 | let accessibilityBlurEvent: string;
103 |
104 | /**
105 | * Event triggered than the view looses or receives the accessibility focus
106 | */
107 | let accessibilityFocusChangedEvent: string;
108 |
109 | function on(event: string, callback: (data: EventData) => void, thisArg?: any);
110 | function once(event: string, callback: (data: EventData) => void, thisArg?: any);
111 | function off(eventNames: string, callback?: any, thisArg?: any);
112 | function addEventListener(eventNames: string, callback: (data: EventData) => void, thisArg?: any);
113 | function removeEventListener(eventNames: string, callback?: any, thisArg?: any);
114 |
115 | const AccessibilityTrait: typeof _AccessibilityTrait;
116 | const AccessibilityState: typeof _AccessibilityState;
117 | const AccessibilityComponentType: typeof _AccessibilityRole;
118 | }
119 |
120 | interface AccessibilityFocusEventData extends EventData {
121 | object: View;
122 | }
123 |
124 | interface AccessibilityBlurEventData extends AccessibilityFocusEventData {}
125 |
126 | interface AccessibilityFocusChangedEventData extends AccessibilityFocusEventData {
127 | value: boolean;
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/ui/core/view.ios.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import { View } from '@nativescript/core';
4 | import { PostAccessibilityNotificationType } from '@nativescript/core/ui/core/view';
5 | import { isTraceEnabled, writeTrace } from '../../trace';
6 | import { AccessibilityHelper, getUIView } from '../../utils/accessibility-helper';
7 | import { setViewFunction } from '../../utils/helpers';
8 | import {
9 | accessibilityHiddenCssProperty,
10 | accessibilityHintProperty,
11 | accessibilityIdProperty,
12 | accessibilityLabelProperty,
13 | accessibilityLanguageProperty,
14 | accessibilityLiveRegionCssProperty,
15 | accessibilityMediaSessionCssProperty,
16 | accessibilityRoleCssProperty,
17 | accessibilityStateCssProperty,
18 | accessibilityTraitsProperty,
19 | accessibilityValueProperty,
20 | accessibleCssProperty,
21 | commonFunctions,
22 | iosFunctions,
23 | } from './view-common';
24 |
25 | function updateA11YProperty(tnsView: View, propName: string, value: string | null) {
26 | const cls = `View<${tnsView}.ios>.${propName} = ${value}`;
27 | const uiView = getUIView(tnsView);
28 | if (!uiView) {
29 | if (isTraceEnabled()) {
30 | writeTrace(`${cls} - no nativeView`);
31 | }
32 |
33 | return;
34 | }
35 |
36 | value = value != null ? `${value}` : null;
37 | if (isTraceEnabled()) {
38 | writeTrace(`${cls}`);
39 | }
40 |
41 | uiView[propName] = value;
42 | }
43 |
44 | View.prototype[accessibleCssProperty.setNative] = function accessibleSetNative(this: View, isAccessible: boolean) {
45 | const uiView = getUIView(this);
46 | if (!uiView) {
47 | return;
48 | }
49 |
50 | uiView.isAccessibilityElement = !!isAccessible;
51 |
52 | if (isTraceEnabled()) {
53 | writeTrace(`View<${this}.ios>.accessible = ${uiView.isAccessibilityElement}`);
54 | }
55 |
56 | AccessibilityHelper.updateAccessibilityProperties(this);
57 | };
58 |
59 | View.prototype[accessibilityRoleCssProperty.setNative] = function accessibilityComponentTypeSetNative(this: View) {
60 | AccessibilityHelper.updateAccessibilityProperties(this);
61 | };
62 |
63 | View.prototype[accessibilityTraitsProperty.setNative] = function accessibilityTraitsSetNative(this: View) {
64 | AccessibilityHelper.updateAccessibilityProperties(this);
65 | };
66 |
67 | View.prototype[accessibilityValueProperty.setNative] = function accessibilityValueSetNative(this: View, value: string) {
68 | updateA11YProperty(this, 'accessibilityValue', value);
69 | };
70 |
71 | View.prototype[accessibilityHiddenCssProperty.setNative] = function accessibilityElementsHiddenSetNative(this: View, isHidden: boolean) {
72 | const uiView = getUIView(this);
73 | if (!uiView) {
74 | return;
75 | }
76 |
77 | uiView.accessibilityElementsHidden = !!isHidden;
78 | if (isTraceEnabled()) {
79 | writeTrace(`View<${this}.ios>.accessibilityElementsHidden - ${!!isHidden}`);
80 | }
81 |
82 | AccessibilityHelper.updateAccessibilityProperties(this);
83 | };
84 |
85 | View.prototype[accessibilityLiveRegionCssProperty.setNative] = function accessibilityLiveRegionSetNative(this: View) {
86 | AccessibilityHelper.updateAccessibilityProperties(this);
87 | };
88 |
89 | View.prototype[accessibilityStateCssProperty.setNative] = function accessibilityStateSetNative(this: View) {
90 | AccessibilityHelper.updateAccessibilityProperties(this);
91 | };
92 |
93 | setViewFunction(View, iosFunctions.iosPostAccessibilityNotification, function postAccessibilityNotification(
94 | this: View,
95 | notificationType: PostAccessibilityNotificationType,
96 | msg?: string,
97 | ) {
98 | const cls = `View<${this}.ios>.postAccessibilityNotification("${notificationType}", "${msg}")`;
99 | if (!notificationType) {
100 | if (isTraceEnabled()) {
101 | writeTrace(`${cls} - falsy notificationType`);
102 | }
103 |
104 | return;
105 | }
106 |
107 | let notification: number;
108 | let args: string | UIView | null = getUIView(this);
109 | if (typeof msg === 'string' && msg) {
110 | args = msg;
111 | }
112 |
113 | switch (notificationType.toLowerCase()) {
114 | case 'announcement': {
115 | notification = UIAccessibilityAnnouncementNotification;
116 | break;
117 | }
118 | case 'layout': {
119 | notification = UIAccessibilityLayoutChangedNotification;
120 | break;
121 | }
122 | case 'screen': {
123 | notification = UIAccessibilityScreenChangedNotification;
124 | break;
125 | }
126 | default: {
127 | if (isTraceEnabled()) {
128 | writeTrace(`${cls} - unknown notificationType`);
129 | }
130 |
131 | return;
132 | }
133 | }
134 |
135 | if (isTraceEnabled()) {
136 | writeTrace(`${cls} - send ${notification} with ${args || null}`);
137 | }
138 |
139 | UIAccessibilityPostNotification(notification, args || null);
140 | });
141 |
142 | setViewFunction(View, commonFunctions.accessibilityAnnouncement, function accessibilityAnnouncement(this: View, msg?: string) {
143 | const cls = `View<${this}.ios>.accessibilityAnnouncement("${msg}")`;
144 | if (!msg) {
145 | if (isTraceEnabled()) {
146 | writeTrace(`${cls} - no msg, sending view.accessibilityLabel = ${this.accessibilityLabel} instead`);
147 | }
148 |
149 | msg = this.accessibilityLabel;
150 | }
151 |
152 | if (isTraceEnabled()) {
153 | writeTrace(`${cls} - sending ${msg}`);
154 | }
155 |
156 | this.iosPostAccessibilityNotification('announcement', msg);
157 | });
158 |
159 | View.prototype[accessibilityLabelProperty.setNative] = function accessibilityLabelSetNative(this: View, label: string) {
160 | updateA11YProperty(this, 'accessibilityLabel', label);
161 | };
162 |
163 | View.prototype[accessibilityIdProperty.setNative] = function accessibilityIdentifierSetNative(this: View, identifier: string) {
164 | updateA11YProperty(this, 'accessibilityIdentifier', identifier);
165 | };
166 |
167 | View.prototype[accessibilityLanguageProperty.setNative] = function accessibilityLanguageSetNative(this: View, lang: string) {
168 | updateA11YProperty(this, 'accessibilityLanguage', lang);
169 | };
170 |
171 | View.prototype[accessibilityHintProperty.setNative] = function accessibilityHintSetNative(this: View, hint: string) {
172 | updateA11YProperty(this, 'accessibilityHint', hint);
173 | };
174 |
175 | View.prototype[accessibilityMediaSessionCssProperty.setNative] = function accessibilityMediaSessionSetNative(this: View) {
176 | AccessibilityHelper.updateAccessibilityProperties(this);
177 | };
178 |
179 | setViewFunction(View, commonFunctions.accessibilityScreenChanged, function accessibilityScreenChanged(this: View) {
180 | this.iosPostAccessibilityNotification('screen');
181 | });
182 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/ui/css-classes-helper.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import { Application, CSSUtils, isAndroid, PropertyChangeData, View } from '@nativescript/core';
4 | import { isTraceEnabled, writeFontScaleTrace, writeTrace } from '../trace';
5 | import { FontScaleObservable } from '../utils/fontscale-observable';
6 | import '../utils/global-events';
7 | import { hmrSafeEvents } from '../utils/helpers';
8 | import { AccessibilityServiceEnabledObservable } from '../utils/utils';
9 |
10 | // CSS-classes
11 | const fontExtraSmallClass = `a11y-fontscale-xs`;
12 | const fontExtraMediumClass = `a11y-fontscale-m`;
13 | const fontExtraLargeClass = `a11y-fontscale-xl`;
14 | const a11yServiceEnabledClass = `a11y-service-enabled`;
15 | const a11yServiceDisabledClass = `a11y-service-disabled`;
16 | const rootA11YClass = 'ns-a11y';
17 |
18 | class A11YCssClassHelper {
19 | private readonly cls = `CssClassesHelper`;
20 |
21 | private readonly fontScaleCssClasses = new Map(FontScaleObservable.VALID_FONT_SCALES.map((fs) => [fs, `a11y-fontscale-${Number(fs * 100).toFixed(0)}`]));
22 |
23 | private currentFontScaleClass = '';
24 | private currentFontScaleCategory = '';
25 | private currentA11YStatusClass = '';
26 | private currentRootA11YClass = '';
27 |
28 | private readonly fontScaleObservable = new FontScaleObservable();
29 | private readonly a11yServiceObservable = new AccessibilityServiceEnabledObservable();
30 |
31 | /**
32 | * Keep a list of WeakRefs to loaded modal views.
33 | *
34 | * This is needed to trigger UI updates if the fontscale or a11y-service status changes
35 | **/
36 | private loadedModalViewRefs = new Map>();
37 |
38 | constructor() {
39 | this.fontScaleObservable.on(FontScaleObservable.propertyChangeEvent, this.fontScaleChanged, this);
40 | this.a11yServiceObservable.on(AccessibilityServiceEnabledObservable.propertyChangeEvent, this.a11yServiceChanged, this);
41 |
42 | // Override global events
43 | hmrSafeEvents(`${this.cls}.updateRootViews`, [Application.displayedEvent, Application.resumeEvent], Application, (evt) => {
44 | this.updateRootViews(evt);
45 | });
46 |
47 | hmrSafeEvents(`${this.cls}.modalViewShowing`, [View.shownModallyEvent], View, (evt) => {
48 | this.addModalViewRef(evt.object);
49 | });
50 | }
51 |
52 | /**
53 | * Update css-helper classes on root and modal-views
54 | */
55 |
56 | private updateRootViews(evt?: any) {
57 | evt = { ...evt };
58 |
59 | const cls = `${this.cls}.updateRootViews({eventName: ${evt.eventName}, object: ${evt.object}})`;
60 | if (!this.updateCurrentHelperClasses()) {
61 | if (isTraceEnabled()) {
62 | writeTrace(`${cls} - no changes`);
63 | }
64 |
65 | return;
66 | }
67 |
68 | for (const view of [Application.getRootView(), ...this.getModalViews()]) {
69 | if (!view) {
70 | continue;
71 | }
72 |
73 | if (isTraceEnabled()) {
74 | writeTrace(`${cls} - update css state on ${view}`);
75 | }
76 |
77 | view._onCssStateChange();
78 | }
79 | }
80 |
81 | /**
82 | * Get loaded modal views
83 | */
84 |
85 | private getModalViews() {
86 | const views = [] as View[];
87 | for (const [id, viewRef] of this.loadedModalViewRefs) {
88 | const view = viewRef.get();
89 | if (!view) {
90 | // This view doesn't exists anymore, remove the WeakRef from the set.
91 | this.loadedModalViewRefs.delete(id);
92 | continue;
93 | }
94 |
95 | views.push(view);
96 | }
97 |
98 | return views;
99 | }
100 |
101 | /**
102 | * Add modal view to list loaded modals.
103 | *
104 | * These are used to the UI if fontscale or the a11y-service status changes while the modal is active.
105 | */
106 |
107 | private addModalViewRef(modalView: View) {
108 | for (const [id, viewRef] of this.loadedModalViewRefs) {
109 | const otherView = viewRef.get();
110 | if (!otherView) {
111 | // This view doesn't exists anymore, remove the WeakRef from the set.
112 | this.loadedModalViewRefs.delete(id);
113 | continue;
114 | }
115 | }
116 |
117 | this.loadedModalViewRefs.set(`${modalView}`, new WeakRef(modalView));
118 | }
119 |
120 | private removeCssClass(cssClass: string) {
121 | CSSUtils.removeSystemCssClass(cssClass);
122 |
123 | [Application.getRootView(), ...this.getModalViews()].forEach((view) => {
124 | if (view) {
125 | view.cssClasses.delete(cssClass);
126 | }
127 | });
128 | }
129 |
130 | private addCssClass(cssClass: string) {
131 | CSSUtils.pushToRootViewCssClasses(cssClass);
132 |
133 | [Application.getRootView(), ...this.getModalViews()].forEach((view) => {
134 | if (view) {
135 | view.cssClasses.add(cssClass);
136 | }
137 | });
138 | }
139 |
140 | /**
141 | * Update the helper CSS-classes.
142 | * Return true is any changes.
143 | */
144 |
145 | private updateCurrentHelperClasses(): boolean {
146 | const { fontScale, isExtraSmall, isExtraLarge } = this.fontScaleObservable;
147 |
148 | let changed = false;
149 |
150 | const oldFontScaleClass = this.currentFontScaleClass;
151 | if (this.fontScaleCssClasses.has(fontScale)) {
152 | this.currentFontScaleClass = this.fontScaleCssClasses.get(fontScale);
153 | } else {
154 | this.currentFontScaleClass = this.fontScaleCssClasses.get(1);
155 | }
156 |
157 | if (oldFontScaleClass !== this.currentFontScaleClass) {
158 | this.removeCssClass(oldFontScaleClass);
159 | this.addCssClass(this.currentFontScaleClass);
160 |
161 | changed = true;
162 | }
163 |
164 | let oldActiveFontScaleCategory = this.currentFontScaleCategory;
165 | if (isAndroid) {
166 | this.currentFontScaleCategory = fontExtraMediumClass;
167 | } else {
168 | if (isExtraSmall) {
169 | this.currentFontScaleCategory = fontExtraSmallClass;
170 | } else if (isExtraLarge) {
171 | this.currentFontScaleCategory = fontExtraLargeClass;
172 | } else {
173 | this.currentFontScaleCategory = fontExtraMediumClass;
174 | }
175 | }
176 |
177 | if (oldActiveFontScaleCategory !== this.currentFontScaleCategory) {
178 | this.removeCssClass(oldActiveFontScaleCategory);
179 | this.addCssClass(this.currentFontScaleCategory);
180 |
181 | changed = true;
182 | }
183 |
184 | const oldA11YStatusClass = this.currentA11YStatusClass;
185 | if (this.a11yServiceObservable.accessibilityServiceEnabled) {
186 | this.currentA11YStatusClass = a11yServiceEnabledClass;
187 | } else {
188 | this.currentA11YStatusClass = a11yServiceDisabledClass;
189 | }
190 |
191 | if (oldA11YStatusClass !== this.currentA11YStatusClass) {
192 | this.removeCssClass(oldA11YStatusClass);
193 | this.addCssClass(this.currentA11YStatusClass);
194 |
195 | changed = true;
196 | }
197 |
198 | if (this.currentRootA11YClass !== rootA11YClass) {
199 | this.addCssClass(rootA11YClass);
200 | this.currentRootA11YClass = rootA11YClass;
201 |
202 | changed = true;
203 | }
204 |
205 | return changed;
206 | }
207 |
208 | private fontScaleChanged(event: PropertyChangeData) {
209 | const { fontScale } = this.fontScaleObservable;
210 | if (isTraceEnabled()) {
211 | writeFontScaleTrace(`${this.cls}.fontScaleChanged(): ${FontScaleObservable.FONT_SCALE} changed to ${fontScale}`);
212 | }
213 |
214 | this.updateRootViews(event);
215 | }
216 |
217 | private a11yServiceChanged(event: PropertyChangeData) {
218 | if (isTraceEnabled()) {
219 | writeFontScaleTrace(`${this.cls}.a11yServiceChanged(): to ${this.a11yServiceObservable.accessibilityServiceEnabled}`);
220 | }
221 |
222 | this.updateRootViews(event);
223 | }
224 | }
225 |
226 | export const cssClassHelper = new A11YCssClassHelper();
227 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/ui/index.ts:
--------------------------------------------------------------------------------
1 | export * from './css-classes-helper';
2 | export * from './action-bar/action-bar';
3 | export * from './core/view';
4 | export * from './page/page';
5 | export * from './slider/slider';
6 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/ui/page/page-common.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import { Page, PageNavigatedData } from '@nativescript/core/ui/page';
4 | import { isTraceEnabled, writeTrace } from '../../trace';
5 | import '../../utils/global-events';
6 | import { hmrSafeEvents } from '../../utils/helpers';
7 |
8 | hmrSafeEvents('PageAnnounce', [Page.navigatedToEvent], Page, (args: PageNavigatedData) => {
9 | const page = args.object;
10 | if (!page) {
11 | return;
12 | }
13 |
14 | const cls = `${page} - PageAnnounce`;
15 | if (Page.disableAnnouncePage) {
16 | if (isTraceEnabled()) {
17 | writeTrace(`${cls} - disabled globally`);
18 | }
19 |
20 | return;
21 | }
22 |
23 | if (page.disableAnnouncePage) {
24 | if (isTraceEnabled()) {
25 | writeTrace(`${cls} - disabled for ${page}`);
26 | }
27 |
28 | return;
29 | }
30 |
31 | page.accessibilityScreenChanged(!!args.isBackNavigation);
32 | });
33 |
34 | export { Page };
35 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/ui/page/page.android.ts:
--------------------------------------------------------------------------------
1 | export * from './page-common';
2 | import { Page } from '@nativescript/core';
3 | import { isTraceEnabled, writeTrace } from '../../trace';
4 | import { getLastFocusedViewOnPage, setViewFunction } from '../../utils';
5 | import { getAndroidView } from '../../utils/accessibility-helper';
6 | import { commonFunctions } from '../core/view-common';
7 |
8 | setViewFunction(Page, commonFunctions.accessibilityScreenChanged, function accessibilityScreenChanged(this: Page, refocus = false) {
9 | const cls = `${this}.${commonFunctions.accessibilityScreenChanged}(${refocus})`;
10 |
11 | if (refocus) {
12 | const lastFocusedView = getLastFocusedViewOnPage(this);
13 | if (lastFocusedView) {
14 | if (isTraceEnabled()) {
15 | writeTrace(`${cls} - refocus on ${lastFocusedView}`);
16 | }
17 |
18 | const announceView = getAndroidView(lastFocusedView);
19 | if (announceView) {
20 | announceView.sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED);
21 | announceView.sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
22 |
23 | return;
24 | }
25 | }
26 | }
27 |
28 | if (this.actionBarHidden) {
29 | if (isTraceEnabled()) {
30 | writeTrace(`${cls} - action-bar hidden`);
31 | }
32 |
33 | this.androidSendAccessibilityEvent('window_state_changed');
34 |
35 | return;
36 | }
37 |
38 | if (this.accessibilityLabel) {
39 | if (isTraceEnabled()) {
40 | writeTrace(`${cls} - page has an accessibilityLabel: ${this.accessibilityLabel}`);
41 | }
42 |
43 | this.androidSendAccessibilityEvent('window_state_changed');
44 |
45 | return;
46 | }
47 |
48 | if (this.actionBar.accessibilityLabel || this.actionBar.title) {
49 | if (isTraceEnabled()) {
50 | writeTrace(`${cls} action-bar has an accessibilityLabel="${this.actionBar.accessibilityLabel}" or a title="${this.actionBar.title}"`);
51 | }
52 |
53 | this.actionBar.accessibilityScreenChanged();
54 |
55 | return;
56 | }
57 |
58 | if (isTraceEnabled()) {
59 | writeTrace(`${cls} - action-bar doesn't have an accessibilityLabel or a title`);
60 | }
61 |
62 | this.androidSendAccessibilityEvent('window_state_changed');
63 | });
64 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/ui/page/page.d.ts:
--------------------------------------------------------------------------------
1 | import { Page, EventData } from '@nativescript/core';
2 | import { FontScaleObservable } from '../../utils/FontScaleObservable';
3 |
4 | declare module '@nativescript/core/ui/page' {
5 | interface PageEventData extends EventData {
6 | object: Page;
7 | }
8 |
9 | interface PageNavigatedData extends PageEventData {
10 | /**
11 | * The navigation context (optional, may be undefined) passed to the page navigation events method.
12 | */
13 | context: any;
14 |
15 | /**
16 | * Represents if a navigation is forward or backward.
17 | */
18 | isBackNavigation: boolean;
19 | }
20 |
21 | interface Page {
22 | /**
23 | * Disable announce page on `navigatedTo`
24 | */
25 | disableAnnouncePage?: boolean;
26 |
27 | /**
28 | * Announce screen changed
29 | *
30 | * @param refocus Attempt to set focus to the last focused view on back navigation.
31 | */
32 | accessibilityScreenChanged(refocus?: boolean): void;
33 | }
34 |
35 | namespace Page {
36 | /**
37 | * Disable announce page on `navigatedTo`
38 | */
39 | var disableAnnouncePage: boolean | void;
40 | }
41 | }
42 |
43 | export { Page };
44 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/ui/page/page.ios.ts:
--------------------------------------------------------------------------------
1 | export * from './page-common';
2 |
3 | import { Page } from '@nativescript/core';
4 | import { isTraceEnabled, writeTrace } from '../../trace';
5 | import { getLastFocusedViewOnPage, setViewFunction } from '../../utils';
6 | import { getUIView } from '../../utils/accessibility-helper';
7 | import { commonFunctions } from '../core/view-common';
8 |
9 | setViewFunction(Page, commonFunctions.accessibilityScreenChanged, function accessibilityScreenChanged(this: Page, refocus = false) {
10 | const cls = `${this}.${commonFunctions.accessibilityScreenChanged}`;
11 |
12 | if (refocus) {
13 | const lastFocusedView = getLastFocusedViewOnPage(this);
14 | if (lastFocusedView) {
15 | if (isTraceEnabled()) {
16 | writeTrace(`${cls} - refocus on ${lastFocusedView}`);
17 | }
18 |
19 | const uiView = getUIView(lastFocusedView);
20 | if (uiView) {
21 | UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, uiView);
22 |
23 | return;
24 | }
25 | }
26 | }
27 |
28 | if (this.actionBarHidden) {
29 | if (isTraceEnabled()) {
30 | writeTrace(`${cls} - action-bar hidden`);
31 | }
32 |
33 | this.androidSendAccessibilityEvent('window_state_changed');
34 |
35 | return;
36 | }
37 |
38 | if (this.actionBarHidden) {
39 | if (isTraceEnabled()) {
40 | writeTrace(`${cls} - action-bar hidden`);
41 | }
42 |
43 | UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, getUIView(this));
44 |
45 | return;
46 | }
47 |
48 | if (this.accessibilityLabel) {
49 | if (isTraceEnabled()) {
50 | writeTrace(`${cls} - page has an accessibilityLabel: ${this.accessibilityLabel}`);
51 | }
52 |
53 | UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, getUIView(this));
54 |
55 | return;
56 | }
57 |
58 | if (this.actionBar.accessibilityLabel || this.actionBar.title) {
59 | if (isTraceEnabled()) {
60 | writeTrace(`${cls} action-bar has an accessibilityLabel="${this.actionBar.accessibilityLabel}" or a title="${this.actionBar.title}"`);
61 | }
62 |
63 | UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, this.actionBar.nativeView);
64 |
65 | return;
66 | }
67 |
68 | if (isTraceEnabled()) {
69 | writeTrace(`${cls} - action-bar doesn't have an accessibilityLabel or a title`);
70 | }
71 |
72 | UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, getUIView(this));
73 | });
74 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/ui/slider/slider-common.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import { Slider } from '@nativescript/core';
4 | import { AccessibilityDecrementEventData, AccessibilityIncrementEventData } from '@nativescript/core/ui/slider';
5 | import { addCssPropertyToView, setViewFunction } from '../../utils/helpers';
6 |
7 | Slider.accessibilityDecrementEvent = 'accessibilityDecrement';
8 | Slider.accessibilityIncrementEvent = 'accessibilityIncrement';
9 |
10 | const accessibilityStepsPropertyName = 'accessibilityStep';
11 | const accessibilityStepsCssName = 'a11y-steps';
12 |
13 | export const accessibilityStepsCssProperty = addCssPropertyToView(Slider, accessibilityStepsPropertyName, accessibilityStepsCssName, false, 10, parseInt);
14 |
15 | setViewFunction(Slider, '_handlerAccessibilityIncrementEvent', function _handlerAccessibilityIncrementEvent(this: Slider) {
16 | const args: AccessibilityIncrementEventData = {
17 | object: this,
18 | eventName: Slider.accessibilityIncrementEvent,
19 | value: this.value + (this.accessibilityStep || 10),
20 | };
21 |
22 | this.notify(args);
23 |
24 | return args.value;
25 | });
26 |
27 | setViewFunction(Slider, '_handlerAccessibilityDecrementEvent', function _handlerAccessibilityDecrementEvent(this: Slider) {
28 | const args: AccessibilityDecrementEventData = {
29 | object: this,
30 | eventName: Slider.accessibilityIncrementEvent,
31 | value: this.value - (this.accessibilityStep || 10),
32 | };
33 |
34 | this.notify(args);
35 |
36 | return args.value;
37 | });
38 |
39 | export { Slider };
40 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/ui/slider/slider.android.ts:
--------------------------------------------------------------------------------
1 | export const dummy = '';
2 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/ui/slider/slider.d.ts:
--------------------------------------------------------------------------------
1 | import '@nativescript/core/ui/slider';
2 | import { EventData } from '@nativescript/core';
3 |
4 | declare module '@nativescript/core/ui/slider' {
5 | interface Slider {
6 | accessibilityStep: number;
7 | _handlerAccessibilityIncrementEvent(): number;
8 | _handlerAccessibilityDecrementEvent(): number;
9 | }
10 |
11 | namespace Slider {
12 | let accessibilityIncrementEvent: string;
13 | let accessibilityDecrementEvent: string;
14 | }
15 |
16 | interface AccessibilityIncrementEventData extends EventData {
17 | object: Slider;
18 | value?: number;
19 | }
20 |
21 | interface AccessibilityDecrementEventData extends EventData {
22 | object: Slider;
23 | value?: number;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/ui/slider/slider.ios.ts:
--------------------------------------------------------------------------------
1 | import { setViewFunction } from '../../utils/helpers';
2 | import { Slider } from './slider-common';
3 |
4 | @NativeClass()
5 | class NotaUISlider extends UISlider {
6 | public owner: WeakRef;
7 |
8 | public static initWithOwner(owner: WeakRef) {
9 | const slider = NotaUISlider.new() as NotaUISlider;
10 | slider.owner = owner;
11 |
12 | return slider;
13 | }
14 |
15 | public accessibilityIncrement() {
16 | const owner = this.owner.get();
17 | if (!owner) {
18 | this.value += 10;
19 | } else {
20 | this.value = owner._handlerAccessibilityIncrementEvent();
21 | }
22 |
23 | this.sendActionsForControlEvents(UIControlEvents.ValueChanged);
24 | }
25 |
26 | public accessibilityDecrement() {
27 | const owner = this.owner.get();
28 | if (!owner) {
29 | this.value += 10;
30 | } else {
31 | this.value = owner._handlerAccessibilityDecrementEvent();
32 | }
33 |
34 | this.sendActionsForControlEvents(UIControlEvents.ValueChanged);
35 | }
36 | }
37 |
38 | setViewFunction(Slider, 'createNativeView', function sliderCreateNativeView(this: Slider) {
39 | return NotaUISlider.initWithOwner(new WeakRef(this));
40 | });
41 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/utils/accessibility-helper.d.ts:
--------------------------------------------------------------------------------
1 | import { View as TNSView } from '@nativescript/core';
2 |
3 | export declare class AccessibilityHelper {
4 | public static updateAccessibilityProperties(tnsView: TNSView): void;
5 | public static sendAccessibilityEvent(tnsView: TNSView, eventName: string, text?: string): void;
6 | public static updateContentDescription(tnsView: TNSView, forceUpdate?: boolean): string;
7 | }
8 |
9 | export function getAndroidView(tnsView: TNSView): T;
10 | export function getUIView(view: TNSView): T;
11 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/utils/accessibility-helper.ios.ts:
--------------------------------------------------------------------------------
1 | import '@nativescript/core';
2 | import { Application, ProxyViewContainer, View as TNSView } from '@nativescript/core';
3 | import { AccessibilityLiveRegion, AccessibilityRole, AccessibilityState, AccessibilityTrait } from '../ui/core/view-common';
4 | import { hmrSafeEvents, inputArrayToBitMask, notifyAccessibilityFocusState } from './helpers';
5 |
6 | export function getAndroidView(tnsView: TNSView): T {
7 | throw new Error(`getAndroidView(${tnsView}) - should never be called on iOS`);
8 | }
9 |
10 | export function getUIView(view: TNSView): T {
11 | return view.ios;
12 | }
13 |
14 | let AccessibilityTraitsMap: Map;
15 | let RoleTypeMap: Map;
16 |
17 | let nativeFocusedNotificationObserver: any;
18 | const uiViewToTnsView = new WeakMap>();
19 | let lastFocusedView: WeakRef;
20 | function ensureNativeClasses() {
21 | if (AccessibilityTraitsMap && nativeFocusedNotificationObserver) {
22 | return;
23 | }
24 |
25 | AccessibilityTraitsMap = new Map([
26 | [AccessibilityTrait.None, UIAccessibilityTraitNone],
27 | [AccessibilityTrait.Button, UIAccessibilityTraitButton],
28 | [AccessibilityTrait.Link, UIAccessibilityTraitLink],
29 | [AccessibilityTrait.SearchField, UIAccessibilityTraitSearchField],
30 | [AccessibilityTrait.Image, UIAccessibilityTraitImage],
31 | [AccessibilityTrait.Selected, UIAccessibilityTraitSelected],
32 | [AccessibilityTrait.PlaysSound, UIAccessibilityTraitPlaysSound],
33 | [AccessibilityTrait.StaticText, UIAccessibilityTraitStaticText],
34 | [AccessibilityTrait.SummaryElement, UIAccessibilityTraitSummaryElement],
35 | [AccessibilityTrait.NotEnabled, UIAccessibilityTraitNotEnabled],
36 | [AccessibilityTrait.UpdatesFrequently, UIAccessibilityTraitUpdatesFrequently],
37 | [AccessibilityTrait.StartsMediaSession, UIAccessibilityTraitStartsMediaSession],
38 | [AccessibilityTrait.Adjustable, UIAccessibilityTraitAdjustable],
39 | [AccessibilityTrait.AllowsDirectInteraction, UIAccessibilityTraitAllowsDirectInteraction],
40 | [AccessibilityTrait.CausesPageTurn, UIAccessibilityTraitCausesPageTurn],
41 | [AccessibilityTrait.Header, UIAccessibilityTraitHeader],
42 | ]);
43 |
44 | RoleTypeMap = new Map([
45 | [AccessibilityRole.Button, UIAccessibilityTraitButton],
46 | [AccessibilityRole.Header, UIAccessibilityTraitHeader],
47 | [AccessibilityRole.Link, UIAccessibilityTraitLink],
48 | [AccessibilityRole.Search, UIAccessibilityTraitSearchField],
49 | [AccessibilityRole.Image, UIAccessibilityTraitImage],
50 | [AccessibilityRole.ImageButton, UIAccessibilityTraitImage | UIAccessibilityTraitButton],
51 | [AccessibilityRole.KeyboardKey, UIAccessibilityTraitKeyboardKey],
52 | [AccessibilityRole.StaticText, UIAccessibilityTraitStaticText],
53 | [AccessibilityRole.Summary, UIAccessibilityTraitSummaryElement],
54 | [AccessibilityRole.Adjustable, UIAccessibilityTraitAdjustable],
55 | [AccessibilityRole.Checkbox, UIAccessibilityTraitButton],
56 | [AccessibilityRole.Switch, UIAccessibilityTraitButton],
57 | [AccessibilityRole.RadioButton, UIAccessibilityTraitButton],
58 | ]);
59 |
60 | nativeFocusedNotificationObserver = Application.ios.addNotificationObserver(UIAccessibilityElementFocusedNotification, (args: NSNotification) => {
61 | const uiView = args.userInfo.objectForKey(UIAccessibilityFocusedElementKey) as UIView;
62 |
63 | const tnsView = uiViewToTnsView.has(uiView) ? uiViewToTnsView.get(uiView).get() : null;
64 | if (!tnsView) {
65 | return;
66 | }
67 |
68 | const lastView = lastFocusedView && lastFocusedView.get();
69 | if (lastView && tnsView !== lastView) {
70 | const lastFocusedUIView = getUIView(lastView);
71 | if (lastFocusedUIView) {
72 | lastFocusedView = null;
73 |
74 | notifyAccessibilityFocusState(lastView, false, true);
75 | }
76 | }
77 |
78 | lastFocusedView = new WeakRef(tnsView);
79 |
80 | notifyAccessibilityFocusState(tnsView, true, false);
81 | });
82 |
83 | Application.on(Application.exitEvent, () => {
84 | if (nativeFocusedNotificationObserver) {
85 | Application.ios.removeNotificationObserver(nativeFocusedNotificationObserver, UIAccessibilityElementFocusedNotification);
86 | }
87 |
88 | nativeFocusedNotificationObserver = null;
89 | });
90 | }
91 |
92 | export class AccessibilityHelper {
93 | public static updateAccessibilityProperties(tnsView: TNSView) {
94 | if (tnsView instanceof ProxyViewContainer) {
95 | return;
96 | }
97 |
98 | const uiView = getUIView(tnsView);
99 | if (!uiView) {
100 | return;
101 | }
102 |
103 | ensureNativeClasses();
104 |
105 | const accessibilityRole = tnsView.accessibilityRole as AccessibilityRole;
106 | const accessibilityState = tnsView.accessibilityState as AccessibilityState;
107 |
108 | if (!tnsView.accessible || tnsView.accessibilityHidden) {
109 | uiView.accessibilityTraits = UIAccessibilityTraitNone;
110 |
111 | return;
112 | }
113 |
114 | let a11yTraits = UIAccessibilityTraitNone;
115 | if (RoleTypeMap.has(accessibilityRole)) {
116 | a11yTraits |= RoleTypeMap.get(accessibilityRole);
117 | }
118 |
119 | switch (accessibilityRole) {
120 | case AccessibilityRole.Checkbox:
121 | case AccessibilityRole.RadioButton:
122 | case AccessibilityRole.Switch: {
123 | if (accessibilityState === AccessibilityState.Checked) {
124 | a11yTraits |= AccessibilityTraitsMap.get(AccessibilityTrait.Selected);
125 | }
126 | break;
127 | }
128 | default: {
129 | if (accessibilityState === AccessibilityState.Selected) {
130 | a11yTraits |= AccessibilityTraitsMap.get(AccessibilityTrait.Selected);
131 | }
132 | if (accessibilityState === AccessibilityState.Disabled) {
133 | a11yTraits |= AccessibilityTraitsMap.get(AccessibilityTrait.NotEnabled);
134 | }
135 | break;
136 | }
137 | }
138 |
139 | const UpdatesFrequentlyTrait = AccessibilityTraitsMap.get(AccessibilityTrait.UpdatesFrequently);
140 |
141 | switch (tnsView.accessibilityLiveRegion) {
142 | case AccessibilityLiveRegion.Polite:
143 | case AccessibilityLiveRegion.Assertive: {
144 | a11yTraits |= UpdatesFrequentlyTrait;
145 | break;
146 | }
147 | default: {
148 | a11yTraits &= ~UpdatesFrequentlyTrait;
149 | break;
150 | }
151 | }
152 |
153 | if (tnsView.accessibilityMediaSession) {
154 | a11yTraits |= AccessibilityTraitsMap.get(AccessibilityTrait.StartsMediaSession);
155 | }
156 |
157 | if (tnsView.accessibilityTraits) {
158 | a11yTraits |= inputArrayToBitMask(tnsView.accessibilityTraits, AccessibilityTraitsMap);
159 | }
160 |
161 | uiView.accessibilityTraits = a11yTraits;
162 | }
163 |
164 | public static sendAccessibilityEvent(tnsView: TNSView, eventName: string, text?: string): void {
165 | throw new Error('AccessibilityHelper.sendAccessibilityEvent() - Should never be called on iOS');
166 | }
167 |
168 | public static updateContentDescription(tnsView: TNSView, forceUpdate?: boolean): string {
169 | throw new Error('AccessibilityHelper.updateContentDescription() . Should never be called on iOS');
170 | }
171 | }
172 |
173 | hmrSafeEvents('A11YHelper:loadedEvent', [TNSView.loadedEvent], TNSView, function (this: null, evt) {
174 | const tnsView = evt.object;
175 | if (!tnsView) {
176 | return;
177 | }
178 |
179 | if (tnsView instanceof ProxyViewContainer) {
180 | return;
181 | }
182 |
183 | const uiView = getUIView(tnsView);
184 | if (!uiView) {
185 | return;
186 | }
187 |
188 | uiViewToTnsView.set(uiView, new WeakRef(tnsView));
189 | });
190 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/utils/fontscale-observable.android.ts:
--------------------------------------------------------------------------------
1 | import { Application, Observable, PropertyChangeData } from '@nativescript/core';
2 | import { isTraceEnabled, writeFontScaleTrace } from '../trace';
3 |
4 | function getClosestValidFontScale(fontScale: number) {
5 | fontScale = Number(fontScale) || 1;
6 |
7 | return FontScaleObservable.VALID_FONT_SCALES.sort((a, b) => Math.abs(fontScale - a) - Math.abs(fontScale - b)).shift();
8 | }
9 |
10 | let internalObservable: Observable;
11 | function fontScaleChanged(origFontScale: number) {
12 | const fontScale = getClosestValidFontScale(origFontScale);
13 |
14 | const cls = `fontScaleChanged(${fontScale}) - was = ${origFontScale}`;
15 | if (isTraceEnabled()) {
16 | writeFontScaleTrace(`${cls}`);
17 | }
18 |
19 | internalObservable.set(FontScaleObservable.FONT_SCALE, fontScale);
20 | }
21 |
22 | function useAndroidFontScale() {
23 | fontScaleChanged(Number(Application.android.context.getResources().getConfiguration().fontScale));
24 | }
25 |
26 | function setupConfigListener() {
27 | Application.off(Application.launchEvent, setupConfigListener);
28 |
29 | const context = Application.android && (Application.android.context as android.content.Context);
30 |
31 | if (!context) {
32 | Application.on(Application.launchEvent, setupConfigListener);
33 |
34 | return;
35 | }
36 |
37 | useAndroidFontScale();
38 |
39 | let configChangedCallback = new android.content.ComponentCallbacks2({
40 | onLowMemory() {
41 | // Dummy
42 | },
43 | onTrimMemory() {
44 | // Dummy
45 | },
46 | onConfigurationChanged(newConfig: android.content.res.Configuration) {
47 | fontScaleChanged(Number(newConfig.fontScale));
48 | },
49 | });
50 |
51 | context.registerComponentCallbacks(configChangedCallback);
52 | Application.on(Application.resumeEvent, useAndroidFontScale);
53 | }
54 |
55 | function ensureObservable() {
56 | if (internalObservable) {
57 | return;
58 | }
59 |
60 | internalObservable = new Observable();
61 | setupConfigListener();
62 | }
63 |
64 | export class FontScaleObservable extends Observable {
65 | public static readonly FONT_SCALE = 'fontScale';
66 | public static readonly IS_EXTRA_SMALL = 'isExtraSmall';
67 | public static readonly IS_EXTRA_LARGE = 'isExtraSmall';
68 |
69 | public static get VALID_FONT_SCALES() {
70 | return [0.85, 1, 1.15, 1.3];
71 | }
72 |
73 | public readonly fontScale = 1;
74 | public readonly isExtraSmall = false;
75 | public readonly isExtraLarge = false;
76 |
77 | constructor() {
78 | super();
79 |
80 | ensureObservable();
81 |
82 | const selfRef = new WeakRef(this);
83 | function callback(args: PropertyChangeData) {
84 | const self = selfRef.get();
85 | if (self) {
86 | self.set(args.propertyName, args.value);
87 |
88 | return;
89 | }
90 |
91 | internalObservable.off(Observable.propertyChangeEvent, callback);
92 | }
93 |
94 | internalObservable.on(Observable.propertyChangeEvent, callback);
95 | this.set(FontScaleObservable.FONT_SCALE, internalObservable.get(FontScaleObservable.FONT_SCALE));
96 | this.set(FontScaleObservable.IS_EXTRA_SMALL, false);
97 | this.set(FontScaleObservable.IS_EXTRA_LARGE, false);
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/utils/fontscale-observable.d.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from '@nativescript/core';
2 |
3 | export declare class FontScaleObservable extends Observable {
4 | public static readonly FONT_SCALE: 'fontScale';
5 | public static readonly IS_EXTRA_SMALL: 'isExtraSmall';
6 | public static readonly IS_EXTRA_LARGE: 'isExtraSmall';
7 |
8 | public static readonly VALID_FONT_SCALES: number[];
9 |
10 | public readonly fontScale: number;
11 | public readonly isExtraSmall: boolean;
12 | public readonly isExtraLarge: boolean;
13 | }
14 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/utils/fontscale-observable.ios.ts:
--------------------------------------------------------------------------------
1 | import { Application, Observable, PropertyChangeData } from '@nativescript/core';
2 | import { isTraceEnabled, writeErrorTrace, writeFontScaleTrace } from '../trace';
3 |
4 | const getClosestValidFontScale = function getClosestValidFontScaleImpl(fontScale: number) {
5 | fontScale = Number(fontScale) || 1;
6 |
7 | return FontScaleObservable.VALID_FONT_SCALES.sort((a, b) => Math.abs(fontScale - a) - Math.abs(fontScale - b)).shift();
8 | };
9 |
10 | let internalObservable: Observable;
11 | function fontScaleChanged(origFontScale: number) {
12 | const cls = `fontScaleChanged(${origFontScale})`;
13 |
14 | if (isTraceEnabled()) {
15 | writeFontScaleTrace(`${cls}`);
16 | }
17 |
18 | const fontScale = getClosestValidFontScale(origFontScale);
19 |
20 | if (isTraceEnabled()) {
21 | writeFontScaleTrace(`${cls} - settings closest valid value: ${fontScale}`);
22 | }
23 |
24 | internalObservable.set(FontScaleObservable.FONT_SCALE, fontScale);
25 | internalObservable.set(FontScaleObservable.IS_EXTRA_SMALL, fontScale < 0.85);
26 | internalObservable.set(FontScaleObservable.IS_EXTRA_LARGE, fontScale > 1.5);
27 | }
28 |
29 | const sizeMap = new Map([
30 | [UIContentSizeCategoryExtraSmall, 0.5],
31 | [UIContentSizeCategorySmall, 0.7],
32 | [UIContentSizeCategoryMedium, 0.85],
33 | [UIContentSizeCategoryLarge, 1],
34 | [UIContentSizeCategoryExtraLarge, 1.15],
35 | [UIContentSizeCategoryExtraExtraLarge, 1.3],
36 | [UIContentSizeCategoryExtraExtraExtraLarge, 1.5],
37 | [UIContentSizeCategoryAccessibilityMedium, 2],
38 | [UIContentSizeCategoryAccessibilityLarge, 2.5],
39 | [UIContentSizeCategoryAccessibilityExtraLarge, 3],
40 | [UIContentSizeCategoryAccessibilityExtraExtraLarge, 3.5],
41 | [UIContentSizeCategoryAccessibilityExtraExtraExtraLarge, 4],
42 | ]);
43 |
44 | function contentSizeUpdated(fontSize: string) {
45 | if (sizeMap.has(fontSize)) {
46 | fontScaleChanged(sizeMap.get(fontSize));
47 |
48 | return;
49 | }
50 |
51 | if (isTraceEnabled()) {
52 | writeFontScaleTrace(`fontSize: ${fontSize} is unknown`);
53 | }
54 |
55 | fontScaleChanged(1);
56 | }
57 |
58 | function useIOSFontScale() {
59 | if (Application.ios.nativeApp) {
60 | contentSizeUpdated(Application.ios.nativeApp.preferredContentSizeCategory);
61 | } else {
62 | fontScaleChanged(1);
63 | }
64 | }
65 |
66 | function setupConfigListener(attempt = 0) {
67 | if (!Application.ios.nativeApp) {
68 | if (attempt > 100) {
69 | if (isTraceEnabled()) {
70 | writeErrorTrace(`App didn't become active couldn't enable font scaling`);
71 | }
72 |
73 | fontScaleChanged(1);
74 |
75 | return;
76 | }
77 |
78 | // Couldn't get launchEvent to trigger.
79 | setTimeout(() => setupConfigListener(attempt + 1), 1);
80 |
81 | return;
82 | }
83 |
84 | const fontSizeObserver = Application.ios.addNotificationObserver(UIContentSizeCategoryDidChangeNotification, (args) => {
85 | const fontSize = args.userInfo.valueForKey(UIContentSizeCategoryNewValueKey);
86 | contentSizeUpdated(fontSize);
87 | });
88 |
89 | Application.on(Application.exitEvent, () => {
90 | Application.ios.removeNotificationObserver(fontSizeObserver, UIContentSizeCategoryDidChangeNotification);
91 | internalObservable = null;
92 |
93 | Application.off(Application.resumeEvent, useIOSFontScale);
94 | });
95 |
96 | Application.on(Application.resumeEvent, useIOSFontScale);
97 |
98 | useIOSFontScale();
99 | }
100 |
101 | function ensureObservable() {
102 | if (internalObservable) {
103 | return;
104 | }
105 |
106 | internalObservable = new Observable();
107 | setupConfigListener();
108 | }
109 |
110 | export class FontScaleObservable extends Observable {
111 | public static readonly FONT_SCALE = 'fontScale';
112 | public static readonly IS_EXTRA_SMALL = 'isExtraSmall';
113 | public static readonly IS_EXTRA_LARGE = 'isExtraSmall';
114 |
115 | public static get VALID_FONT_SCALES() {
116 | // iOS supports a wider number of font scales than Android does.
117 | return [0.5, 0.7, 0.85, 1, 1.15, 1.3, 1.5, 2, 2.5, 3, 3.5, 4];
118 | }
119 |
120 | public readonly fontScale = 1;
121 | public readonly isExtraSmall = false;
122 | public readonly isExtraLarge = false;
123 |
124 | constructor() {
125 | super();
126 |
127 | ensureObservable();
128 |
129 | const selfRef = new WeakRef(this);
130 | function callback(args: PropertyChangeData) {
131 | const self = selfRef.get();
132 | if (self) {
133 | self.set(args.propertyName, args.value);
134 |
135 | return;
136 | }
137 |
138 | internalObservable.off(Observable.propertyChangeEvent, callback);
139 | }
140 |
141 | internalObservable.on(Observable.propertyChangeEvent, callback);
142 |
143 | const fontScale = internalObservable.get(FontScaleObservable.FONT_SCALE);
144 | this.set(FontScaleObservable.IS_EXTRA_SMALL, fontScale < 0.85);
145 | this.set(FontScaleObservable.IS_EXTRA_LARGE, fontScale > 1.5);
146 | this.set(FontScaleObservable.FONT_SCALE, fontScale);
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/utils/global-events.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import {
4 | AbsoluteLayout,
5 | ActionBar,
6 | ActivityIndicator,
7 | Button,
8 | ContainerView,
9 | DatePicker,
10 | DockLayout,
11 | EditableTextBase,
12 | EventData,
13 | FlexboxLayout,
14 | Frame,
15 | GridLayout,
16 | HtmlView,
17 | Image,
18 | Label,
19 | LayoutBase,
20 | ListPicker,
21 | ListView,
22 | Observable,
23 | Page,
24 | Placeholder,
25 | Progress,
26 | Repeater,
27 | ScrollView,
28 | SearchBar,
29 | SegmentedBar,
30 | Slider,
31 | StackLayout,
32 | Switch,
33 | TabView,
34 | TextBase,
35 | TextField,
36 | TextView,
37 | TimePicker,
38 | View,
39 | WebView,
40 | WrapLayout,
41 | } from '@nativescript/core';
42 | import { CustomLayoutView } from '@nativescript/core/ui/core/view';
43 | import { isTraceEnabled, writeGlobalEventsTrace } from '../trace';
44 | import { unwrapFunction, wrapFunction } from './helpers';
45 |
46 | export function setupGlobalEventsOnViewClass(ViewClass: any, viewName: string) {
47 | const obsKeyName = `__a11y_globalEvent_${viewName}_observable`;
48 |
49 | if (ViewClass[obsKeyName]) {
50 | if (isTraceEnabled()) {
51 | writeGlobalEventsTrace(`"${viewName}" already overridden`);
52 | }
53 |
54 | return;
55 | }
56 |
57 | if (isTraceEnabled()) {
58 | writeGlobalEventsTrace(`Adding to "${viewName}"`);
59 | }
60 |
61 | ViewClass[obsKeyName] = new Observable();
62 |
63 | unwrapFunction(ViewClass.prototype, 'notify', viewName);
64 |
65 | wrapFunction(
66 | ViewClass.prototype,
67 | 'notify',
68 | function customNotify(arg: EventData) {
69 | if (!ViewClass[obsKeyName].hasListeners(arg.eventName)) {
70 | return;
71 | }
72 |
73 | if (isTraceEnabled()) {
74 | writeGlobalEventsTrace(`Notify "${arg.eventName}" to all "${viewName}" from ${arg.object}`);
75 | }
76 |
77 | ViewClass[obsKeyName].notify(arg);
78 | },
79 | viewName,
80 | );
81 |
82 | ViewClass.on = ViewClass.addEventListener = function customAddEventListener(eventNames: string, callback: (data: EventData) => void, thisArg?: any) {
83 | if (isTraceEnabled()) {
84 | writeGlobalEventsTrace(`On: "${eventNames}" thisArg:${thisArg} to "${viewName}"`);
85 | }
86 |
87 | ViewClass[obsKeyName].on(eventNames, callback, thisArg);
88 | };
89 |
90 | ViewClass.once = function customAddOnceEventListener(eventNames: string, callback: (data: EventData) => void, thisArg?: any) {
91 | if (isTraceEnabled()) {
92 | writeGlobalEventsTrace(`Once: "${eventNames}" thisArg:${thisArg} to "${viewName}"`);
93 | }
94 |
95 | ViewClass[obsKeyName].once(eventNames, callback, thisArg);
96 | };
97 |
98 | ViewClass.off = ViewClass.removeEventListener = function customRemoveEventListener(eventNames: string, callback?: any, thisArg?: any) {
99 | if (isTraceEnabled()) {
100 | writeGlobalEventsTrace(`Remove: "${eventNames}" this:${thisArg} from "${viewName}"`);
101 | }
102 |
103 | ViewClass[obsKeyName].off(eventNames, callback, thisArg);
104 | };
105 | }
106 |
107 | // Add the global events to the View-class before adding it to the sub-classes.
108 | setupGlobalEventsOnViewClass(View, 'View');
109 | setupGlobalEventsOnViewClass(TextBase, 'TextBase');
110 | setupGlobalEventsOnViewClass(ContainerView, 'ContainerView');
111 | setupGlobalEventsOnViewClass(LayoutBase, 'LayoutBase');
112 |
113 | for (const { viewClass, viewName } of [
114 | { viewClass: AbsoluteLayout, viewName: 'AbsoluteLayout' },
115 | { viewClass: ActionBar, viewName: 'ActionBar' },
116 | { viewClass: ActivityIndicator, viewName: 'ActivityIndicator' },
117 | { viewClass: Button, viewName: 'Button' },
118 | { viewClass: CustomLayoutView, viewName: 'CustomLayoutView' },
119 | { viewClass: DatePicker, viewName: 'DatePicker' },
120 | { viewClass: DockLayout, viewName: 'DockLayout' },
121 | { viewClass: EditableTextBase, viewName: 'EditableTextBase' },
122 | { viewClass: FlexboxLayout, viewName: 'FlexboxLayout' },
123 | { viewClass: Frame, viewName: 'Frame' },
124 | { viewClass: GridLayout, viewName: 'GridLayout' },
125 | { viewClass: HtmlView, viewName: 'HtmlView' },
126 | { viewClass: Image, viewName: 'Image' },
127 | { viewClass: Label, viewName: 'Label' },
128 | { viewClass: ListPicker, viewName: 'ListPicker' },
129 | { viewClass: ListView, viewName: 'ListView' },
130 | { viewClass: Page, viewName: 'Page' },
131 | { viewClass: Placeholder, viewName: 'Placeholder' },
132 | { viewClass: Progress, viewName: 'Progress' },
133 | { viewClass: Repeater, viewName: 'Repeater' },
134 | { viewClass: ScrollView, viewName: 'ScrollView' },
135 | { viewClass: SearchBar, viewName: 'SearchBar' },
136 | { viewClass: SegmentedBar, viewName: 'SegmentedBar' },
137 | { viewClass: Slider, viewName: 'Slider' },
138 | { viewClass: StackLayout, viewName: 'StackLayout' },
139 | { viewClass: Switch, viewName: 'Switch' },
140 | { viewClass: TabView, viewName: 'TabView' },
141 | { viewClass: TextField, viewName: 'TextField' },
142 | { viewClass: TextView, viewName: 'TextView' },
143 | { viewClass: TimePicker, viewName: 'TimePicker' },
144 | { viewClass: WebView, viewName: 'WebView' },
145 | { viewClass: WrapLayout, viewName: 'WrapLayout' },
146 | ]) {
147 | setupGlobalEventsOnViewClass(viewClass, viewName);
148 | }
149 |
--------------------------------------------------------------------------------
/packages/nativescript-accessibility-ext/utils/helpers.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | import { booleanConverter, CssProperty, InheritedCssProperty, Page, Property, Style, View } from '@nativescript/core';
4 | import { AccessibilityBlurEventData, AccessibilityFocusChangedEventData, AccessibilityFocusEventData } from '@nativescript/core/ui/core/view';
5 | import { isTraceEnabled, writeErrorTrace, writeTrace } from '../trace';
6 |
7 | const lastFocusedViewOnPageKeyName = '__lastFocusedViewOnPage';
8 |
9 | export function getLastFocusedViewOnPage(page: Page): View | null {
10 | try {
11 | const lastFocusedViewRef = page[lastFocusedViewOnPageKeyName] as WeakRef;
12 | if (!lastFocusedViewRef) {
13 | return null;
14 | }
15 |
16 | const lastFocusedView = lastFocusedViewRef.get();
17 | if (!lastFocusedView) {
18 | return null;
19 | }
20 |
21 | if (!lastFocusedView.parent || lastFocusedView.page !== page) {
22 | return null;
23 | }
24 |
25 | return lastFocusedView;
26 | } catch {
27 | // ignore
28 | } finally {
29 | delete page[lastFocusedViewOnPageKeyName];
30 | }
31 |
32 | return null;
33 | }
34 |
35 | /**
36 | * Dummy function that does nothing.
37 | */
38 | export function noop() {
39 | // ignore
40 | }
41 |
42 | export function makePropertyEnumConverter(enumValues: any) {
43 | return (value: string): T | null => {
44 | if (!value) {
45 | return null;
46 | }
47 |
48 | for (const [key, v] of Object.entries(enumValues)) {
49 | if (key === value || `${v}` === `${value}`.toLowerCase()) {
50 | return v;
51 | }
52 | }
53 |
54 | return null;
55 | };
56 | }
57 |
58 | /**
59 | * Add a new function to a View-class
60 | */
61 | export function setViewFunction(viewClass: any, fnName: string, fn?: Function) {
62 | viewClass.prototype[fnName] = fn || noop;
63 | }
64 |
65 | function getOriginalWrappedFnName(viewName: string, fnName: string) {
66 | return `___a11y_${viewName}_${fnName}`;
67 | }
68 |
69 | /**
70 | * Wrap a function on an object.
71 | * The original function will be called before the func.
72 | */
73 | export function wrapFunction(obj: any, fnName: string, func: Function, objName: string) {
74 | const origFNName = getOriginalWrappedFnName(objName, fnName);
75 |
76 | obj[origFNName] = (obj[origFNName] || obj[fnName]) as Function;
77 |
78 | obj[fnName] = function (...args: any[]) {
79 | let origFN = obj[origFNName];
80 | if (!origFN) {
81 | writeErrorTrace(`wrapFunction(${obj}) don't have an original function for ${fnName}`);
82 |
83 | origFN = noop;
84 | }
85 |
86 | const res = origFN.call(this, ...args);
87 |
88 | func.call(this, ...args);
89 |
90 | return res;
91 | };
92 | }
93 |
94 | /**
95 | * Unwrap a function on a class wrapped by wrapFunction.
96 | */
97 | export function unwrapFunction(obj: any, fnName: string, viewName: string) {
98 | const origFNName = getOriginalWrappedFnName(viewName, fnName);
99 | if (!obj[origFNName]) {
100 | return;
101 | }
102 |
103 | obj[fnName] = obj[origFNName];
104 | delete obj[origFNName];
105 | }
106 |
107 | export function enforceArray(val: string | string[]): string[] {
108 | if (Array.isArray(val)) {
109 | return val;
110 | }
111 |
112 | if (typeof val === 'string') {
113 | return val.split(/[, ]/g).filter((v: string) => !!v);
114 | }
115 |
116 | if (isTraceEnabled()) {
117 | writeTrace(`enforceArray: val is of unsupported type: ${val} -> ${typeof val}`);
118 | }
119 |
120 | return [];
121 | }
122 |
123 | /**
124 | * Convert array of values into a bitmask.
125 | *
126 | * @param values string values
127 | * @param map map lower-case name to integer value.
128 | */
129 | export function inputArrayToBitMask(values: string | string[], map: Map): number {
130 | return (
131 | enforceArray(values)
132 | .filter((value) => !!value)
133 | .map((value) => `${value}`.toLocaleLowerCase())
134 | .filter((value) => map.has(value))
135 | .reduce((res, value) => res | map.get(value), 0) || 0
136 | );
137 | }
138 |
139 | /**
140 | * Extend NativeScript View with a new property.
141 | */
142 | export function addPropertyToView(viewClass: typeof View, name: string, defaultValue?: T, valueConverter?: (value: string) => T): Property {
143 | const property = new Property({
144 | name,
145 | defaultValue,
146 | valueConverter,
147 | });
148 | property.register(viewClass);
149 |
150 | return property;
151 | }
152 |
153 | export function addBooleanPropertyToView(viewClass: typeof View, name: string, defaultValue?: boolean): Property {
154 | return addPropertyToView(viewClass, name, defaultValue, booleanConverter);
155 | }
156 |
157 | export function addCssPropertyToView(
158 | viewClass: typeof View,
159 | name: string,
160 | cssName: string,
161 | inherited = false,
162 | defaultValue?: T,
163 | valueConverter?: (value: string) => T,
164 | ): CssProperty
9 |
10 |
11 |
14 |
15 |
16 |
19 |
20 |
23 |
--------------------------------------------------------------------------------
/tools/assets/App_Resources/Android/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #F5F5F5
4 | #757575
5 | #33B5E5
6 | #272734
7 |
--------------------------------------------------------------------------------
/tools/assets/App_Resources/Android/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
18 |
19 |
21 |
22 |
23 |
31 |
32 |
34 |
35 |
36 |
42 |
43 |
45 |
46 |
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images": [
3 | {
4 | "size": "20x20",
5 | "idiom": "iphone",
6 | "filename": "icon-20@2x.png",
7 | "scale": "2x"
8 | },
9 | {
10 | "size": "20x20",
11 | "idiom": "iphone",
12 | "filename": "icon-20@3x.png",
13 | "scale": "3x"
14 | },
15 | {
16 | "size": "29x29",
17 | "idiom": "iphone",
18 | "filename": "icon-29.png",
19 | "scale": "1x"
20 | },
21 | {
22 | "size": "29x29",
23 | "idiom": "iphone",
24 | "filename": "icon-29@2x.png",
25 | "scale": "2x"
26 | },
27 | {
28 | "size": "29x29",
29 | "idiom": "iphone",
30 | "filename": "icon-29@3x.png",
31 | "scale": "3x"
32 | },
33 | {
34 | "size": "40x40",
35 | "idiom": "iphone",
36 | "filename": "icon-40@2x.png",
37 | "scale": "2x"
38 | },
39 | {
40 | "size": "40x40",
41 | "idiom": "iphone",
42 | "filename": "icon-40@3x.png",
43 | "scale": "3x"
44 | },
45 | {
46 | "size": "60x60",
47 | "idiom": "iphone",
48 | "filename": "icon-60@2x.png",
49 | "scale": "2x"
50 | },
51 | {
52 | "size": "60x60",
53 | "idiom": "iphone",
54 | "filename": "icon-60@3x.png",
55 | "scale": "3x"
56 | },
57 | {
58 | "size": "20x20",
59 | "idiom": "ipad",
60 | "filename": "icon-20.png",
61 | "scale": "1x"
62 | },
63 | {
64 | "size": "20x20",
65 | "idiom": "ipad",
66 | "filename": "icon-20@2x.png",
67 | "scale": "2x"
68 | },
69 | {
70 | "size": "29x29",
71 | "idiom": "ipad",
72 | "filename": "icon-29.png",
73 | "scale": "1x"
74 | },
75 | {
76 | "size": "29x29",
77 | "idiom": "ipad",
78 | "filename": "icon-29@2x.png",
79 | "scale": "2x"
80 | },
81 | {
82 | "size": "40x40",
83 | "idiom": "ipad",
84 | "filename": "icon-40.png",
85 | "scale": "1x"
86 | },
87 | {
88 | "size": "40x40",
89 | "idiom": "ipad",
90 | "filename": "icon-40@2x.png",
91 | "scale": "2x"
92 | },
93 | {
94 | "size": "76x76",
95 | "idiom": "ipad",
96 | "filename": "icon-76.png",
97 | "scale": "1x"
98 | },
99 | {
100 | "size": "76x76",
101 | "idiom": "ipad",
102 | "filename": "icon-76@2x.png",
103 | "scale": "2x"
104 | },
105 | {
106 | "size": "83.5x83.5",
107 | "idiom": "ipad",
108 | "filename": "icon-83.5@2x.png",
109 | "scale": "2x"
110 | },
111 | {
112 | "size": "1024x1024",
113 | "idiom": "ios-marketing",
114 | "filename": "icon-1024.png",
115 | "scale": "1x"
116 | }
117 | ],
118 | "info": {
119 | "version": 1,
120 | "author": "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-1024.png
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20.png
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29.png
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40.png
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76.png
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/tools/assets/App_Resources/iOS/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info": {
3 | "version": 1,
4 | "author": "xcode"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images": [
3 | {
4 | "idiom": "universal",
5 | "filename": "LaunchScreen-AspectFill.png",
6 | "scale": "1x"
7 | },
8 | {
9 | "idiom": "universal",
10 | "filename": "LaunchScreen-AspectFill@2x.png",
11 | "scale": "2x"
12 | },
13 | {
14 | "idiom": "universal",
15 | "filename": "LaunchScreen-AspectFill@3x.png",
16 | "scale": "3x"
17 | }
18 | ],
19 | "info": {
20 | "version": 1,
21 | "author": "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/tools/assets/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill.png
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/tools/assets/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@2x.png
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/tools/assets/App_Resources/iOS/Assets.xcassets/LaunchScreen.AspectFill.imageset/LaunchScreen-AspectFill@3x.png
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images": [
3 | {
4 | "idiom": "universal",
5 | "filename": "LaunchScreen-Center.png",
6 | "scale": "1x"
7 | },
8 | {
9 | "idiom": "universal",
10 | "filename": "LaunchScreen-Center@2x.png",
11 | "scale": "2x"
12 | },
13 | {
14 | "idiom": "universal",
15 | "filename": "LaunchScreen-Center@3x.png",
16 | "scale": "3x"
17 | }
18 | ],
19 | "info": {
20 | "version": 1,
21 | "author": "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/tools/assets/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center.png
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/tools/assets/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@2x.png
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/tools/assets/App_Resources/iOS/Assets.xcassets/LaunchScreen.Center.imageset/LaunchScreen-Center@3x.png
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | ${PRODUCT_NAME}
9 | CFBundleExecutable
10 | ${EXECUTABLE_NAME}
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | ${PRODUCT_NAME}
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIRequiresFullScreen
28 |
29 | UIRequiredDeviceCapabilities
30 |
31 | armv7
32 |
33 | UISupportedInterfaceOrientations
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationLandscapeLeft
37 | UIInterfaceOrientationLandscapeRight
38 |
39 | UISupportedInterfaceOrientations~ipad
40 |
41 | UIInterfaceOrientationPortrait
42 | UIInterfaceOrientationPortraitUpsideDown
43 | UIInterfaceOrientationLandscapeLeft
44 | UIInterfaceOrientationLandscapeRight
45 |
46 | ITSAppUsesNonExemptEncryption
47 |
48 | NSAppTransportSecurity
49 |
50 | NSAllowsArbitraryLoads
51 |
52 | NSAllowsArbitraryLoadsForMedia
53 |
54 | NSAllowsArbitraryLoadsInWebContent
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/build.xcconfig:
--------------------------------------------------------------------------------
1 | // You can add custom settings here
2 | // for example you can uncomment the following line to force distribution code signing
3 | // CODE_SIGN_IDENTITY = iPhone Distribution
4 | // To build for device with Xcode 8 you need to specify your development team. More info: https://developer.apple.com/library/prerelease/content/releasenotes/DeveloperTools/RN-Xcode/Introduction.html
5 | // DEVELOPMENT_TEAM = YOUR_TEAM_ID;
6 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
7 |
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/tools/assets/App_Resources/iOS/icon.png
--------------------------------------------------------------------------------
/tools/assets/App_Resources/iOS/icon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/tools/assets/App_Resources/iOS/icon@2x.png
--------------------------------------------------------------------------------
/tools/assets/README.md:
--------------------------------------------------------------------------------
1 | Assets shared across multiple targets in the workspace to reduce filesize of the repo as well as reduce maintenance costs of duplicate assets.
2 |
3 | - `App_Resources`: All `e2e` app harnesses share the same App_Resources from here.
4 |
--------------------------------------------------------------------------------
/tools/assets/publishing/.npmignore:
--------------------------------------------------------------------------------
1 | __ivy_ngcc__
2 | *.__ivy_ngcc_bak
3 | *.tgz
--------------------------------------------------------------------------------
/tools/demo/index.ts:
--------------------------------------------------------------------------------
1 | export * from './utils';
2 | export * from './nativescript-accessibility-ext';
3 |
--------------------------------------------------------------------------------
/tools/demo/nativescript-accessibility-ext/index.ts:
--------------------------------------------------------------------------------
1 | import { LoadEventData, ObservableArray } from '@nativescript/core';
2 | import type { AccessibilityFocusEventData } from '@nativescript/core/ui/core/view';
3 | import {} from '@nota/nativescript-accessibility-ext';
4 | import { DemoSharedBase } from '../utils';
5 |
6 | export interface ListItem {
7 | id: number;
8 | name: string;
9 | role: string;
10 | }
11 |
12 | export class DemoSharedNativescriptAccessibilityExt extends DemoSharedBase {
13 | readonly source = new ObservableArray([
14 | { id: 1, name: 'Ter Stegen', role: 'Goalkeeper' },
15 | { id: 3, name: 'Piqué', role: 'Defender' },
16 | { id: 4, name: 'I. Rakitic', role: 'Midfielder' },
17 | { id: 5, name: 'Sergio', role: 'Midfielder' },
18 | { id: 6, name: 'Denis Suárez', role: 'Midfielder' },
19 | { id: 7, name: 'Arda', role: 'Midfielder' },
20 | { id: 8, name: 'A. Iniesta', role: 'Midfielder' },
21 | { id: 9, name: 'Suárez', role: 'Forward' },
22 | { id: 10, name: 'Messi', role: 'Forward' },
23 | { id: 11, name: 'Neymar', role: 'Forward' },
24 | { id: 12, name: 'Rafinha', role: 'Midfielder' },
25 | { id: 13, name: 'Cillessen', role: 'Goalkeeper' },
26 | { id: 14, name: 'Mascherano', role: 'Defender' },
27 | { id: 17, name: 'Paco Alcácer', role: 'Forward' },
28 | { id: 18, name: 'Jordi Alba', role: 'Defender' },
29 | { id: 19, name: 'Digne', role: 'Defender' },
30 | { id: 20, name: 'Sergi Roberto', role: 'Midfielder' },
31 | { id: 21, name: 'André Gomes', role: 'Midfielder' },
32 | { id: 22, name: 'Aleix Vidal', role: 'Midfielder' },
33 | { id: 23, name: 'Umtiti', role: 'Defender' },
34 | { id: 24, name: 'Mathieu', role: 'Defender' },
35 | { id: 25, name: 'Masip', role: 'Goalkeeper' },
36 | ]);
37 |
38 | onLoaded(event: LoadEventData) {
39 | const cls = `onLoaded`;
40 |
41 | for (const [key, value] of Object.entries(event)) {
42 | console.log(`${cls} ${key}=${value}`);
43 | }
44 | }
45 |
46 | onItemLoading(event) {
47 | const cls = `onItemLoading`;
48 |
49 | for (const [key, value] of Object.entries(event)) {
50 | console.log(`${cls} ${key}=${value}`);
51 | }
52 | }
53 |
54 | onItemTap(event) {
55 | const cls = `onItemTap`;
56 |
57 | for (const [key, value] of Object.entries(event)) {
58 | console.log(`${cls} ${key}=${value}`);
59 | }
60 | }
61 |
62 | onAccessibilityFocus(event: AccessibilityFocusEventData, index?: number) {
63 | const cls = `onAccessibilityFocus: index=${index}`;
64 |
65 | for (const [key, value] of Object.entries(event)) {
66 | console.log(`${cls} ${key}=${value}`);
67 | }
68 | }
69 |
70 | itemTap(item: ListItem) {
71 | alert({
72 | title: item.name,
73 | message: item.role,
74 | okButtonText: 'Ok',
75 | });
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/tools/demo/references.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/tools/demo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "compilerOptions": {
4 | "baseUrl": ".",
5 | "paths": {
6 | "@nota/*": ["../../packages/*"]
7 | }
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/tools/demo/utils/demo-base.ts:
--------------------------------------------------------------------------------
1 | import { Observable } from '@nativescript/core';
2 |
3 | export class DemoSharedBase extends Observable {
4 | // in case you want to globally control how your shared demo code works across whole workspace
5 | }
6 |
--------------------------------------------------------------------------------
/tools/demo/utils/index.ts:
--------------------------------------------------------------------------------
1 | export * from './demo-base';
2 |
--------------------------------------------------------------------------------
/tools/schematics/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Notalib/nativescript-accessibility-ext/889a90c4b6295f13c594b949f4357e38a385dfdb/tools/schematics/.gitkeep
--------------------------------------------------------------------------------
/tools/scripts/build-finish.ts:
--------------------------------------------------------------------------------
1 | const ngPackage = require('ng-packagr');
2 | const path = require('path');
3 | const fs = require('fs-extra');
4 |
5 | const rootDir = path.resolve(path.join(__dirname, '..', '..'));
6 | const nxConfigPath = path.resolve(path.join(rootDir, 'nx.json'));
7 | const nxConfig = JSON.parse(fs.readFileSync(nxConfigPath));
8 | const npmScope = nxConfig.npmScope;
9 |
10 | const cmdArgs = process.argv.slice(2);
11 | const packageName = cmdArgs[0];
12 | const publish = cmdArgs[1] === 'publish';
13 |
14 | console.log(`Building ${npmScope}/${packageName}...${publish ? 'and publishing.' : ''}`);
15 |
16 | // build angular package
17 | function buildAngular() {
18 | ngPackage
19 | .ngPackagr()
20 | .forProject(`packages/${packageName}/angular/package.json`)
21 | .withTsConfig(`packages/${packageName}/angular/tsconfig.angular.json`)
22 | .build()
23 | .then(() => {
24 | copyAngularDist();
25 | })
26 | .catch((error) => {
27 | console.error(error);
28 | process.exit(1);
29 | });
30 | }
31 |
32 | // copy angular ng-packagr output to dist/packages/{name}
33 | function copyAngularDist() {
34 | fs.copy(path.join('packages', packageName, 'angular', 'dist'), path.join('dist', 'packages', packageName, 'angular'))
35 | .then(() => {
36 | console.log(`${packageName} angular built successfully.`);
37 | finishPreparation();
38 | })
39 | .catch((err) => console.error(err));
40 | }
41 |
42 | function finishPreparation() {
43 | fs.copy(path.join('tools', 'assets', 'publishing'), path.join('dist', 'packages', packageName))
44 | .then(() => console.log(`${npmScope}/${packageName} ready to publish.`))
45 | .catch((err) => console.error(err));
46 | }
47 |
48 | if (fs.existsSync(path.join(rootDir, 'packages', packageName, 'angular'))) {
49 | // package has angular specific src, build it first
50 | buildAngular();
51 | } else {
52 | finishPreparation();
53 | }
54 |
--------------------------------------------------------------------------------
/tools/tsconfig.tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.base.json",
3 | "compilerOptions": {
4 | "outDir": "../dist/out-tsc/tools",
5 | "rootDir": ".",
6 | "module": "commonjs",
7 | "target": "es5",
8 | "types": ["node"]
9 | },
10 | "include": ["**/*.ts"],
11 | "exclude": ["demo"]
12 | }
13 |
--------------------------------------------------------------------------------
/tools/workspace-scripts.js:
--------------------------------------------------------------------------------
1 | const npsUtils = require('nps-utils');
2 |
3 | module.exports = {
4 | message: 'NativeScript Plugins ~ made with ❤️ Choose a command to start...',
5 | pageSize: 32,
6 | scripts: {
7 | default: 'nps-i',
8 | nx: {
9 | script: 'nx',
10 | description: 'Execute any command with the @nrwl/cli',
11 | },
12 | format: {
13 | script: 'nx format:write',
14 | description: 'Format source code of the entire workspace (auto-run on precommit hook)',
15 | },
16 | '🔧': {
17 | script: `npx cowsay "NativeScript plugin demos make developers 😊"`,
18 | description: '_____________ Apps to demo plugins with _____________',
19 | },
20 | // demos
21 | apps: {
22 | '...Vanilla...': {
23 | script: `npx cowsay "Nothing wrong with vanilla 🍦"`,
24 | description: ` 🔻 Vanilla`,
25 | },
26 | demo: {
27 | clean: {
28 | script: 'nx run demo:clean',
29 | description: '⚆ Clean 🧹',
30 | },
31 | ios: {
32 | script: 'nx run demo:ios',
33 | description: '⚆ Run iOS ',
34 | },
35 | android: {
36 | script: 'nx run demo:android',
37 | description: '⚆ Run Android 🤖',
38 | },
39 | },
40 | '...Angular...': {
41 | script: `npx cowsay "Test all the Angles!"`,
42 | description: ` 🔻 Angular`,
43 | },
44 | 'demo-angular': {
45 | clean: {
46 | script: 'nx run demo-angular:clean',
47 | description: '⚆ Clean 🧹',
48 | },
49 | ios: {
50 | script: 'nx run demo-angular:ios',
51 | description: '⚆ Run iOS ',
52 | },
53 | android: {
54 | script: 'nx run demo-angular:android',
55 | description: '⚆ Run Android 🤖',
56 | },
57 | },
58 | },
59 | '⚙️': {
60 | script: `npx cowsay "@nota/* packages will keep your ⚙️ cranking"`,
61 | description: '_____________ @nota/* _____________',
62 | },
63 | // packages
64 | // build output is always in dist/packages
65 | '@nota': {
66 | // @nota/nativescript-accessibility-ext
67 | 'nativescript-accessibility-ext': {
68 | build: {
69 | script: 'nx run nativescript-accessibility-ext:build.all',
70 | description: '@nota/nativescript-accessibility-ext: Build',
71 | },
72 | },
73 | 'build-all': {
74 | script: 'nx run all:build',
75 | description: 'Build all packages',
76 | },
77 | },
78 | '⚡': {
79 | script: `npx cowsay "Focus only on source you care about for efficiency ⚡"`,
80 | description: '_____________ Focus (VS Code supported) _____________',
81 | },
82 | focus: {
83 | 'nativescript-accessibility-ext': {
84 | script: 'nx run nativescript-accessibility-ext:focus',
85 | description: 'Focus on @nota/nativescript-accessibility-ext',
86 | },
87 | reset: {
88 | script: 'nx run all:focus',
89 | description: 'Reset Focus',
90 | },
91 | },
92 | '.....................': {
93 | script: `npx cowsay "That's all for now folks ~"`,
94 | description: '.....................',
95 | },
96 | },
97 | };
98 |
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "rootDir": ".",
4 | "sourceMap": true,
5 | "declaration": true,
6 | "moduleResolution": "node",
7 | "emitDecoratorMetadata": true,
8 | "experimentalDecorators": true,
9 | "noEmitHelpers": true,
10 | "target": "es2017",
11 | "module": "esnext",
12 | "lib": ["es2017", "dom"],
13 | "skipLibCheck": true,
14 | "skipDefaultLibCheck": true,
15 | "baseUrl": ".",
16 | "plugins": [
17 | {
18 | "transform": "@nativescript/webpack/transformers/ns-transform-native-classes",
19 | "type": "raw"
20 | }
21 | ],
22 | "paths": {
23 | "@nota/*": ["packages/*"],
24 | "@demo/shared": ["tools/demo/index.ts"]
25 | }
26 | },
27 | "exclude": ["node_modules", "tmp"]
28 | }
29 |
--------------------------------------------------------------------------------
/workspace.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "projects": {
4 | "demo": {
5 | "root": "apps/demo/",
6 | "sourceRoot": "apps/demo/src",
7 | "projectType": "application",
8 | "prefix": "demo",
9 | "architect": {
10 | "ios": {
11 | "builder": "@nrwl/workspace:run-commands",
12 | "options": {
13 | "commands": ["ns debug ios --no-hmr --emulator --env.testing"],
14 | "cwd": "apps/demo",
15 | "parallel": false
16 | }
17 | },
18 | "android": {
19 | "builder": "@nrwl/workspace:run-commands",
20 | "options": {
21 | "commands": ["ns debug android --no-hmr --emulator --env.testing"],
22 | "cwd": "apps/demo",
23 | "parallel": false
24 | }
25 | },
26 | "clean": {
27 | "builder": "@nrwl/workspace:run-commands",
28 | "options": {
29 | "commands": ["ns clean", "npm i"],
30 | "cwd": "apps/demo",
31 | "parallel": false
32 | }
33 | }
34 | }
35 | },
36 | "demo-angular": {
37 | "root": "apps/demo-angular/",
38 | "sourceRoot": "apps/demo-angular/src",
39 | "projectType": "application",
40 | "prefix": "demo",
41 | "architect": {
42 | "ios": {
43 | "builder": "@nrwl/workspace:run-commands",
44 | "options": {
45 | "commands": ["ns debug ios --no-hmr --emulator --env.testing"],
46 | "cwd": "apps/demo-angular",
47 | "parallel": false
48 | }
49 | },
50 | "android": {
51 | "builder": "@nrwl/workspace:run-commands",
52 | "options": {
53 | "commands": ["ns debug android --no-hmr --emulator --env.testing"],
54 | "cwd": "apps/demo-angular",
55 | "parallel": false
56 | }
57 | },
58 | "clean": {
59 | "builder": "@nrwl/workspace:run-commands",
60 | "options": {
61 | "commands": ["ns clean", "npm i"],
62 | "cwd": "apps/demo-angular",
63 | "parallel": false
64 | }
65 | }
66 | }
67 | },
68 | "all": {
69 | "root": "packages",
70 | "projectType": "library",
71 | "architect": {
72 | "build": {
73 | "builder": "@nrwl/workspace:run-commands",
74 | "outputs": ["dist/packages"],
75 | "options": {
76 | "commands": ["nx run nativescript-accessibility-ext:build.all"],
77 | "parallel": false
78 | }
79 | },
80 | "focus": {
81 | "builder": "@nrwl/workspace:run-commands",
82 | "outputs": ["dist/packages"],
83 | "options": {
84 | "commands": ["nx g @nativescript/plugin-tools:focus-packages"],
85 | "parallel": false
86 | }
87 | }
88 | }
89 | },
90 | "nativescript-accessibility-ext": {
91 | "root": "packages/nativescript-accessibility-ext",
92 | "sourceRoot": "packages/nativescript-accessibility-ext",
93 | "projectType": "library",
94 | "schematics": {},
95 | "architect": {
96 | "build": {
97 | "builder": "@nrwl/node:package",
98 | "options": {
99 | "outputPath": "dist/packages/nativescript-accessibility-ext",
100 | "tsConfig": "packages/nativescript-accessibility-ext/tsconfig.json",
101 | "packageJson": "packages/nativescript-accessibility-ext/package.json",
102 | "main": "packages/nativescript-accessibility-ext/index.ts",
103 | "assets": [
104 | "packages/nativescript-accessibility-ext/*.md",
105 | "packages/nativescript-accessibility-ext/index.d.ts",
106 | "LICENSE",
107 | {
108 | "glob": "**/*",
109 | "input": "packages/nativescript-accessibility-ext/scss/",
110 | "output": "./scss/"
111 | },
112 | {
113 | "glob": "**/*.d.ts",
114 | "input": "packages/nativescript-accessibility-ext/ui/",
115 | "output": "./ui/"
116 | },
117 | {
118 | "glob": "**/*.d.ts",
119 | "input": "packages/nativescript-accessibility-ext/utils/",
120 | "output": "./utils/"
121 | },
122 | {
123 | "glob": "**/*",
124 | "input": "packages/nativescript-accessibility-ext/platforms/",
125 | "output": "./platforms/"
126 | }
127 | ]
128 | }
129 | },
130 | "build.all": {
131 | "builder": "@nrwl/workspace:run-commands",
132 | "outputs": ["dist/packages"],
133 | "options": {
134 | "commands": [
135 | "nx run nativescript-accessibility-ext:build",
136 | "cp -v packages/nativescript-accessibility-ext/utils/*.d.ts dist/packages/nativescript-accessibility-ext/utils/",
137 | "cp -v packages/nativescript-accessibility-ext/ui/core/*.d.ts dist/packages/nativescript-accessibility-ext/ui/core/",
138 | "cp -v packages/nativescript-accessibility-ext/ui/page/*.d.ts dist/packages/nativescript-accessibility-ext/ui/page/",
139 | "cp -v packages/nativescript-accessibility-ext/ui/action-bar/*.d.ts dist/packages/nativescript-accessibility-ext/ui/action-bar/",
140 | "cp -v packages/nativescript-accessibility-ext/ui/slider/*.d.ts dist/packages/nativescript-accessibility-ext/ui/slider/",
141 | "rimraf dist/packages/nativescript-accessibility-ext/scss dist/packages/nativescript-accessibility-ext/css && rimraf dist/packages/nativescript-accessibility-ext/css",
142 | "mkdir -p dist/packages/nativescript-accessibility-ext/css",
143 | "cp -a packages/nativescript-accessibility-ext/scss dist/packages/nativescript-accessibility-ext",
144 | "cd dist/packages/nativescript-accessibility-ext && npx dart-sass --style compressed --load-path node_modules --source-map scss/a11y-helpers.scss:css/a11y-helpers.css scss/a11y.scss:css/a11y.css scss/a11y.compat.scss:css/a11y.compat.css css",
145 | "node tools/scripts/build-finish.ts nativescript-accessibility-ext"
146 | ],
147 | "parallel": false
148 | }
149 | },
150 | "focus": {
151 | "builder": "@nrwl/workspace:run-commands",
152 | "outputs": ["dist/packages"],
153 | "options": {
154 | "commands": ["nx g @nativescript/plugin-tools:focus-packages nativescript-accessibility-ext"],
155 | "parallel": false
156 | }
157 | }
158 | }
159 | }
160 | },
161 | "cli": {
162 | "defaultCollection": "@nrwl/workspace"
163 | }
164 | }
165 |
--------------------------------------------------------------------------------