mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78740d0dfc | ||
|
|
94518c9387 | ||
|
|
31f45cdcc9 | ||
|
|
61cf0c534e | ||
|
|
8d5ed47a28 | ||
|
|
a94e2a87fb | ||
|
|
818e387fe8 | ||
|
|
ff39d40255 | ||
|
|
91aaaab71a | ||
|
|
01afdc42e5 | ||
|
|
36939e10ae | ||
|
|
5ed73cdf4d | ||
|
|
0be79fe82b | ||
|
|
216f51b12a | ||
|
|
dc9faa6a0f | ||
|
|
4f4f31b65e | ||
|
|
9d04c127e8 | ||
|
|
181fc59ab7 | ||
|
|
53ec9cff5e | ||
|
|
d61456c46d | ||
|
|
07868354aa | ||
|
|
5275332e43 | ||
|
|
ea52db66f0 | ||
|
|
c45c8d5564 | ||
|
|
afcc46e1cc | ||
|
|
4e23aad3d9 | ||
|
|
a664ccbde9 | ||
|
|
8002114e72 | ||
|
|
04b874e32a | ||
|
|
c727419350 | ||
|
|
e1e23bc1a2 | ||
|
|
cdc2fb652f | ||
|
|
bb519b8724 | ||
|
|
1956f98968 | ||
|
|
3ed44993e1 | ||
|
|
f4ecc32c14 | ||
|
|
d5eb3a44e6 | ||
|
|
33768e1d0c | ||
|
|
ce4a381d4f | ||
|
|
2d878fc4f6 | ||
|
|
65bc99577c | ||
|
|
abad12fbdb | ||
|
|
d77a9d57ec | ||
|
|
813611a61b | ||
|
|
96d6012071 | ||
|
|
7214a8401b | ||
|
|
bf3f3bb3dc | ||
|
|
3a6fcc7d8b | ||
|
|
62dd16a5ee | ||
|
|
0956f8bc55 | ||
|
|
a4a64530ff | ||
|
|
e17c822bfb | ||
|
|
f5b0299729 | ||
|
|
1267945480 | ||
|
|
907cc7b159 | ||
|
|
e76f79d054 | ||
|
|
f130fb2b17 | ||
|
|
6b817f26b0 | ||
|
|
c356526520 | ||
|
|
caa3afa613 | ||
|
|
0a0cbd8f2a | ||
|
|
639314ab21 | ||
|
|
7512c90241 |
@@ -185,7 +185,7 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- run:
|
||||
command: npm install --legacy-peer-deps
|
||||
command: npm install
|
||||
working_directory: /tmp/workspace/packages/vue
|
||||
- run:
|
||||
command: sudo npm link
|
||||
@@ -208,7 +208,7 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: /tmp/workspace
|
||||
- run:
|
||||
command: npm install --legacy-peer-deps
|
||||
command: npm install
|
||||
working_directory: /tmp/workspace/packages/vue-router
|
||||
- run:
|
||||
command: sudo npm link
|
||||
@@ -442,8 +442,11 @@ workflows:
|
||||
requires: [puppeteer-dependencies, build-core]
|
||||
- test-core-spec:
|
||||
requires: [build-core]
|
||||
- test-core-treeshake:
|
||||
requires: [build-core]
|
||||
# Adam requested we skip this test for now
|
||||
# since it is failing on ES5 code which
|
||||
# will be removed in Ionic Framework v6
|
||||
#- test-core-treeshake:
|
||||
# requires: [build-core]
|
||||
- test-core-screenshot:
|
||||
requires: [build-core]
|
||||
filters:
|
||||
|
||||
127
.github/COMPONENT-GUIDE.md
vendored
127
.github/COMPONENT-GUIDE.md
vendored
@@ -9,6 +9,8 @@
|
||||
* [Ripple Effect](#ripple-effect)
|
||||
* [Example Components](#example-components)
|
||||
* [References](#references)
|
||||
- [Accessibility](#accessibility)
|
||||
* [Checkbox](#checkbox)
|
||||
- [Rendering Anchor or Button](#rendering-anchor-or-button)
|
||||
* [Example Components](#example-components-1)
|
||||
* [Component Structure](#component-structure-1)
|
||||
@@ -362,6 +364,131 @@ ion-ripple-effect {
|
||||
- [iOS Buttons](https://developer.apple.com/design/human-interface-guidelines/ios/controls/buttons/)
|
||||
|
||||
|
||||
## Accessibility
|
||||
|
||||
### Checkbox
|
||||
|
||||
#### Example Components
|
||||
|
||||
- [ion-checkbox](https://github.com/ionic-team/ionic/tree/master/core/src/components/checkbox)
|
||||
- [ion-toggle](https://github.com/ionic-team/ionic/tree/master/core/src/components/toggle)
|
||||
|
||||
#### VoiceOver
|
||||
|
||||
In order for VoiceOver to work properly with a checkbox component there must be a native `input` with `type="checkbox"`, and `aria-checked` and `role="checkbox"` **must** be on the host element. The `aria-hidden` attribute needs to be added if the checkbox is disabled, preventing iOS users from selecting it:
|
||||
|
||||
```tsx
|
||||
render() {
|
||||
const { checked, disabled } = this;
|
||||
|
||||
return (
|
||||
<Host
|
||||
aria-checked={`${checked}`}
|
||||
aria-hidden={disabled ? 'true' : null}
|
||||
role="checkbox"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
/>
|
||||
...
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### NVDA
|
||||
|
||||
It is required to have `aria-checked` on the native input for checked to read properly and `disabled` to prevent tabbing to the input:
|
||||
|
||||
```tsx
|
||||
render() {
|
||||
const { checked, disabled } = this;
|
||||
|
||||
return (
|
||||
<Host
|
||||
aria-checked={`${checked}`}
|
||||
aria-hidden={disabled ? 'true' : null}
|
||||
role="checkbox"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
aria-checked={`${checked}`}
|
||||
disabled={disabled}
|
||||
/>
|
||||
...
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### Labels
|
||||
|
||||
A helper function has been created to get the proper `aria-label` for the checkbox. This can be imported as `getAriaLabel` like the following:
|
||||
|
||||
```tsx
|
||||
const { label, labelId, labelText } = getAriaLabel(el, inputId);
|
||||
```
|
||||
|
||||
where `el` and `inputId` are the following:
|
||||
|
||||
```tsx
|
||||
private inputId = `ion-cb-${checkboxIds++}`;
|
||||
|
||||
@Element() el!: HTMLElement;
|
||||
```
|
||||
|
||||
This can then be added to the `Host` like the following:
|
||||
|
||||
```tsx
|
||||
<Host
|
||||
aria-labelledby={label ? labelId : null}
|
||||
aria-checked={`${checked}`}
|
||||
aria-hidden={disabled ? 'true' : null}
|
||||
role="checkbox"
|
||||
>
|
||||
```
|
||||
|
||||
In addition to that, the checkbox should have a label added:
|
||||
|
||||
```tsx
|
||||
<Host
|
||||
aria-labelledby={label ? labelId : null}
|
||||
aria-checked={`${checked}`}
|
||||
aria-hidden={disabled ? 'true' : null}
|
||||
role="checkbox"
|
||||
>
|
||||
<label htmlFor={inputId}>
|
||||
{labelText}
|
||||
</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
aria-checked={`${checked}`}
|
||||
disabled={disabled}
|
||||
id={inputId}
|
||||
/>
|
||||
```
|
||||
|
||||
#### Hidden Input
|
||||
|
||||
A helper function to render a hidden input has been added, it can be added in the `render`:
|
||||
|
||||
```tsx
|
||||
renderHiddenInput(true, el, name, (checked ? value : ''), disabled);
|
||||
```
|
||||
|
||||
> This is required for the checkbox to work with forms.
|
||||
|
||||
#### Known Issues
|
||||
|
||||
When using VoiceOver on macOS, Chrome will announce the following when you are focused on a checkbox:
|
||||
|
||||
```
|
||||
currently on a checkbox inside of a checkbox
|
||||
```
|
||||
|
||||
This is a compromise we have to make in order for it to work with the other screen readers & Safari.
|
||||
|
||||
|
||||
## Rendering Anchor or Button
|
||||
|
||||
Certain components can render an `<a>` or a `<button>` depending on the presence of an `href` attribute.
|
||||
|
||||
5
.github/ionic-issue-bot.yml
vendored
5
.github/ionic-issue-bot.yml
vendored
@@ -27,7 +27,10 @@ comment:
|
||||
is added to issues that need a code reproduction.
|
||||
|
||||
|
||||
Please provide a reproduction with the minimum amount of code required to reproduce the issue. Without a reliable code reproduction, it is unlikely we will be able to resolve the issue, leading to it being closed.
|
||||
Please reproduce this issue in an Ionic starter application and provide a way for us to access it (GitHub repo, StackBlitz, etc). Without a reliable code reproduction, it is unlikely we will be able to resolve the issue, leading to it being closed.
|
||||
|
||||
|
||||
If you have already provided a code snippet and are seeing this message, it is likely that the code snippet was not enough for our team to reproduce the issue.
|
||||
|
||||
|
||||
For a guide on how to create a good reproduction, see our [Contributing Guide](https://ionicframework.com/docs/contributing/how-to-contribute#creating-a-good-code-reproduction).
|
||||
|
||||
@@ -63,8 +63,9 @@ async function main() {
|
||||
function checkProductionRelease() {
|
||||
const corePath = common.projectPath('core');
|
||||
const hasEsm = fs.existsSync(path.join(corePath, 'dist', 'esm'));
|
||||
const hasEsmEs5 = fs.existsSync(path.join(corePath, 'dist', 'esm-es5'));
|
||||
const hasCjs = fs.existsSync(path.join(corePath, 'dist', 'cjs'));
|
||||
if (!hasEsm || !hasCjs) {
|
||||
if (!hasEsm || !hasEsmEs5 || !hasCjs) {
|
||||
throw new Error('core build is not a production build');
|
||||
}
|
||||
}
|
||||
|
||||
104
CHANGELOG.md
104
CHANGELOG.md
@@ -1,3 +1,107 @@
|
||||
## [5.5.2](https://github.com/ionic-team/ionic/compare/v5.5.1...v5.5.2) (2020-12-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **android:** setting hardwareBackButton: false in config now disables default webview behavior ([#22555](https://github.com/ionic-team/ionic/issues/22555)) ([dc9faa6](https://github.com/ionic-team/ionic/commit/dc9faa6a0fbebb64c83c107c79cfd486cc0c096a)), closes [#18237](https://github.com/ionic-team/ionic/issues/18237)
|
||||
* **button:** allow aria-label to be inherited on inner button ([#22632](https://github.com/ionic-team/ionic/issues/22632)) ([818e387](https://github.com/ionic-team/ionic/commit/818e387fe81ac7026fb374d8865116dadd433c87)), closes [#22629](https://github.com/ionic-team/ionic/issues/22629)
|
||||
* **react:** hardware back button now navigates correctly ([36939e1](https://github.com/ionic-team/ionic/commit/36939e10ae0b8ac9a9275ee06d8e0d345de7c64f))
|
||||
* **react:** setting a ref now allows other props to be passed in ([31f45cd](https://github.com/ionic-team/ionic/commit/31f45cdcc953b08749d9db08321fa5ec6cbe2532)), closes [#22609](https://github.com/ionic-team/ionic/issues/22609)
|
||||
* **refresher:** clean up old css if calling refresh method before native refresher is setup ([#22640](https://github.com/ionic-team/ionic/issues/22640)) ([8d5ed47](https://github.com/ionic-team/ionic/commit/8d5ed47a282f92a60a2c4126a673cc2a5733067e)), closes [#22636](https://github.com/ionic-team/ionic/issues/22636)
|
||||
* **refresher:** refresher correctly detects native refresher when shown asynchronously ([#22623](https://github.com/ionic-team/ionic/issues/22623)) ([5ed73cd](https://github.com/ionic-team/ionic/commit/5ed73cdf4d63eeee25ef28d9676fcaa4f8e07b47)), closes [#22616](https://github.com/ionic-team/ionic/issues/22616)
|
||||
* **vue:** adding non tab button elements inside ion-tab-bar no longer causes errors ([#22643](https://github.com/ionic-team/ionic/issues/22643)) ([61cf0c5](https://github.com/ionic-team/ionic/commit/61cf0c534e45ce09410be6bfb50bdc27b657d1bc)), closes [#22642](https://github.com/ionic-team/ionic/issues/22642)
|
||||
* **vue:** correctly handle navigation failures ([#22621](https://github.com/ionic-team/ionic/issues/22621)) ([216f51b](https://github.com/ionic-team/ionic/commit/216f51b12a8c4ae7b410f47ce3d350ea513b68a1)), closes [#22591](https://github.com/ionic-team/ionic/issues/22591)
|
||||
* **vue:** correctly remove old view when replacing route ([#22566](https://github.com/ionic-team/ionic/issues/22566)) ([4f4f31b](https://github.com/ionic-team/ionic/commit/4f4f31b65e48294c3130ff24ae00b1a2aa1f9d31)), closes [#22492](https://github.com/ionic-team/ionic/issues/22492)
|
||||
* **vue:** pass in correct route to props function ([#22605](https://github.com/ionic-team/ionic/issues/22605)) ([01afdc4](https://github.com/ionic-team/ionic/commit/01afdc42e5b1598d4d15cb51761bbb3eb5d13893)), closes [#22602](https://github.com/ionic-team/ionic/issues/22602)
|
||||
* **vue:** query strings are now correctly handled when navigating back ([#22615](https://github.com/ionic-team/ionic/issues/22615)) ([a94e2a8](https://github.com/ionic-team/ionic/commit/a94e2a87fb759e7b7daed2d0304c1199dbc7afd1)), closes [#22517](https://github.com/ionic-team/ionic/issues/22517)
|
||||
* **vue:** swipe back gesture is properly disabled when swipeBackEnabled config is false ([#22568](https://github.com/ionic-team/ionic/issues/22568)) ([9d04c12](https://github.com/ionic-team/ionic/commit/9d04c127e817676983940b034a4c7efc92fdfbc6)), closes [#22567](https://github.com/ionic-team/ionic/issues/22567)
|
||||
|
||||
### For Ionic Vue Developers
|
||||
|
||||
Vue Router 4 has been released! Be sure to update from the release candidate to the latest stable version of Vue Router.
|
||||
|
||||
For more information on the changes in Vue Router 4, see https://github.com/vuejs/vue-router-next/releases/tag/v4.0.0.
|
||||
|
||||
```
|
||||
npm install vue-router@4
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.5.1](https://github.com/ionic-team/ionic/compare/v5.5.0...v5.5.1) (2020-11-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **checkbox:** click handler now fires properly ([#22573](https://github.com/ionic-team/ionic/issues/22573)) ([0786835](https://github.com/ionic-team/ionic/commit/07868354aaf88deebf7472a5bf0f34d7c823de17)), closes [#22557](https://github.com/ionic-team/ionic/issues/22557)
|
||||
* **radio:** properly announce radios on screen readers and resolve axe errors ([#22507](https://github.com/ionic-team/ionic/issues/22507)) ([afcc46e](https://github.com/ionic-team/ionic/commit/afcc46e1cc4d7f6e9d1a50f8b367da4b1d0c3143))
|
||||
* **react:** eliminate use of deprecated `findDOMNode`, resolves [#20972](https://github.com/ionic-team/ionic/issues/20972) ([5275332](https://github.com/ionic-team/ionic/commit/5275332e43694f3ee8738a1726c0d202b16c3052))
|
||||
* **router:** navigation guards now fire when navigating to a page with params ([#22521](https://github.com/ionic-team/ionic/issues/22521)) ([1956f98](https://github.com/ionic-team/ionic/commit/1956f9896883dc4687488e5418e50ce0f6cbe6c9)), closes [#22516](https://github.com/ionic-team/ionic/issues/22516)
|
||||
* **select:** fix a11y issues with axe and screen readers ([#22494](https://github.com/ionic-team/ionic/issues/22494)) ([04b874e](https://github.com/ionic-team/ionic/commit/04b874e32a65588ca79eda9399ab7e9d86a3cb77)), closes [#21552](https://github.com/ionic-team/ionic/issues/21552) [#21548](https://github.com/ionic-team/ionic/issues/21548)
|
||||
* **select:** improvements for announcing placeholder and value on screenreaders ([#22556](https://github.com/ionic-team/ionic/issues/22556)) ([ea52db6](https://github.com/ionic-team/ionic/commit/ea52db66f05a185fed6b2e849734a7ffa1c6c6ea))
|
||||
* **vue:** onBeforeRouteLeave and onBeforeRouteUpdate hooks now fire properly ([#22542](https://github.com/ionic-team/ionic/issues/22542)) ([8002114](https://github.com/ionic-team/ionic/commit/8002114e720361e60d7a7fe2d15ab88b49a72e1b)), closes [#22540](https://github.com/ionic-team/ionic/issues/22540)
|
||||
* **vue:** tabs now correctly fire lifecycle events ([#22479](https://github.com/ionic-team/ionic/issues/22479)) ([cdc2fb6](https://github.com/ionic-team/ionic/commit/cdc2fb652fe5aa149eaa751a77fb506ac1f64195)), closes [#22466](https://github.com/ionic-team/ionic/issues/22466)
|
||||
* **vue:** unit testing a routerLink-capable component no longer warns of missing router dependency ([#22532](https://github.com/ionic-team/ionic/issues/22532)) ([4e23aad](https://github.com/ionic-team/ionic/commit/4e23aad3d911188e4a2706545463a81332c00ce9)), closes [#22506](https://github.com/ionic-team/ionic/issues/22506)
|
||||
|
||||
### For Ionic Vue Developers
|
||||
|
||||
When updating to Ionic Vue v5.5.1 make sure you are on the latest version of `vue-router@next` to take advantage of the bug fixes in this release:
|
||||
|
||||
```
|
||||
npm install vue-router@next
|
||||
```
|
||||
|
||||
|
||||
|
||||
# [5.5.0 Chlorine](https://github.com/ionic-team/ionic/compare/v5.4.4...v5.5.0) (2020-11-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **backdrop:** nvda no longer incorrectly announces backdrop ([#22481](https://github.com/ionic-team/ionic/issues/22481)) ([2d878fc](https://github.com/ionic-team/ionic/commit/2d878fc4f6c7a710dbfb722e188e3e402e1672f9)), closes [#22102](https://github.com/ionic-team/ionic/issues/22102)
|
||||
* **checkbox:** use a native input to fix a11y issues with axe and screen readers ([#22402](https://github.com/ionic-team/ionic/issues/22402)) ([7214a84](https://github.com/ionic-team/ionic/commit/7214a8401b709e1353155304cf6e9f97b2b4d294)), closes [#21644](https://github.com/ionic-team/ionic/issues/21644) [#20517](https://github.com/ionic-team/ionic/issues/20517) [#17796](https://github.com/ionic-team/ionic/issues/17796)
|
||||
* **input:** title attribute is now automatically inherited ([#22493](https://github.com/ionic-team/ionic/issues/22493)) ([abad12f](https://github.com/ionic-team/ionic/commit/abad12fbdb1378066282fe8e9b7761747951b685)), closes [#22055](https://github.com/ionic-team/ionic/issues/22055)
|
||||
* **refresher:** ios native refresher now works in side menu ([#22449](https://github.com/ionic-team/ionic/issues/22449)) ([a4a6453](https://github.com/ionic-team/ionic/commit/a4a64530ff083b83187b293dfdacb0fa45ad9f51))
|
||||
* **refresher:** md native refresher now works in side menu ([#22446](https://github.com/ionic-team/ionic/issues/22446)) ([6b817f2](https://github.com/ionic-team/ionic/commit/6b817f26b08d01d8367d16308db775b6192e7628)), closes [#20832](https://github.com/ionic-team/ionic/issues/20832)
|
||||
* **toggle:** use a native input to fix a11y issues with axe and screen readers ([#22477](https://github.com/ionic-team/ionic/issues/22477)) ([813611a](https://github.com/ionic-team/ionic/commit/813611a61b664c9827760ccaa889d0e2fcae7d94)), closes [#22011](https://github.com/ionic-team/ionic/issues/22011) [#21552](https://github.com/ionic-team/ionic/issues/21552)
|
||||
* **vue:** correctly pass route props to components ([#22476](https://github.com/ionic-team/ionic/issues/22476)) ([0956f8b](https://github.com/ionic-team/ionic/commit/0956f8bc5588836996c8c74f98166c347414a312)), closes [#22472](https://github.com/ionic-team/ionic/issues/22472)
|
||||
* **vue:** tab bar now works with slot="top" ([#22461](https://github.com/ionic-team/ionic/issues/22461)) ([e17c822](https://github.com/ionic-team/ionic/commit/e17c822bfbc2a876226738b77a4c95c02e0b5953)), closes [#22456](https://github.com/ionic-team/ionic/issues/22456)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **chip:** add disabled property ([#20658](https://github.com/ionic-team/ionic/issues/20658)) ([0a0cbd8](https://github.com/ionic-team/ionic/commit/0a0cbd8f2a505ad2b3d8afb60cb1e940ced52e0d)), closes [#19510](https://github.com/ionic-team/ionic/issues/19510)
|
||||
* **segment:** add swipeGesture property to allow for disabling of the swipe gesture ([#22087](https://github.com/ionic-team/ionic/issues/22087)) ([65bc995](https://github.com/ionic-team/ionic/commit/65bc99577c44cce653dafd9937c4d8f9c45fff61)), closes [#22048](https://github.com/ionic-team/ionic/issues/22048)
|
||||
* **vue:** composition api lifecycle methods ([#22241](https://github.com/ionic-team/ionic/issues/22241)) ([f5b0299](https://github.com/ionic-team/ionic/commit/f5b0299729c2c639e432612e62fb7eaa189ca969))
|
||||
* **vue:** vetur support ([#22403](https://github.com/ionic-team/ionic/issues/22403)) ([e76f79d](https://github.com/ionic-team/ionic/commit/e76f79d0548c97edd193808f5e0a19889cffae5b))
|
||||
* **vue:** web-types support ([#22428](https://github.com/ionic-team/ionic/issues/22428)) ([639314a](https://github.com/ionic-team/ionic/commit/639314ab218b65a9a2de6040417b0e1b363e47ef)), closes [#19522](https://github.com/ionic-team/ionic/issues/19522)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **ios:** move content to stacking context while preserving position: fixed behavior ([#22489](https://github.com/ionic-team/ionic/issues/22489)) ([d77a9d5](https://github.com/ionic-team/ionic/commit/d77a9d57ec02c69df43ec2a286eea674a85cae36)), closes [#22473](https://github.com/ionic-team/ionic/issues/22473)
|
||||
|
||||
|
||||
|
||||
## [5.4.4](https://github.com/ionic-team/ionic/compare/v5.4.3...v5.4.4) (2020-11-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **build:** add missing es5 output ([228d349](https://github.com/ionic-team/ionic/commit/228d349c6e29b62cbfee5d5502883682cfa5032f))
|
||||
|
||||
|
||||
|
||||
## [5.4.3](https://github.com/ionic-team/ionic/compare/v5.4.2...v5.4.3) (2020-11-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **all** add missing vendor prefixes to css ([0989ea5](https://github.com/ionic-team/ionic/commit/0989ea5ac897f528e8fce5434861ca080b9b4a56))
|
||||
|
||||
|
||||
|
||||
## [5.4.2](https://github.com/ionic-team/ionic/compare/v5.4.1...v5.4.2) (2020-11-05)
|
||||
|
||||
|
||||
|
||||
10
angular/package-lock.json
generated
10
angular/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@ionic/angular",
|
||||
"version": "5.4.2",
|
||||
"version": "5.5.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/angular",
|
||||
"version": "5.4.2",
|
||||
"version": "5.5.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ionic/core": "file:../core",
|
||||
@@ -43,7 +43,7 @@
|
||||
}
|
||||
},
|
||||
"../core": {
|
||||
"version": "5.4.1",
|
||||
"version": "5.5.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ionicons": "^5.1.2",
|
||||
@@ -54,7 +54,7 @@
|
||||
"@rollup/plugin-virtual": "^2.0.3",
|
||||
"@stencil/core": "2.1.2",
|
||||
"@stencil/sass": "1.3.2",
|
||||
"@stencil/vue-output-target": "0.1.8",
|
||||
"@stencil/vue-output-target": "0.2.2",
|
||||
"@types/jest": "^26.0.10",
|
||||
"@types/node": "^14.6.0",
|
||||
"@types/puppeteer": "3.0.1",
|
||||
@@ -5179,7 +5179,7 @@
|
||||
"@rollup/plugin-virtual": "^2.0.3",
|
||||
"@stencil/core": "2.1.2",
|
||||
"@stencil/sass": "1.3.2",
|
||||
"@stencil/vue-output-target": "0.1.8",
|
||||
"@stencil/vue-output-target": "0.2.2",
|
||||
"@types/jest": "^26.0.10",
|
||||
"@types/node": "^14.6.0",
|
||||
"@types/puppeteer": "3.0.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/angular",
|
||||
"version": "5.4.2",
|
||||
"version": "5.5.2",
|
||||
"description": "Angular specific wrappers for @ionic/core",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
@@ -42,7 +42,7 @@
|
||||
"validate": "npm i && npm run lint && npm run test && npm run build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ionic/core": "5.4.2",
|
||||
"@ionic/core": "5.5.2",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -156,8 +156,8 @@ export class IonCheckbox {
|
||||
}
|
||||
export declare interface IonChip extends Components.IonChip {
|
||||
}
|
||||
@ProxyCmp({ inputs: ["color", "mode", "outline"] })
|
||||
@Component({ selector: "ion-chip", changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>", inputs: ["color", "mode", "outline"] })
|
||||
@ProxyCmp({ inputs: ["color", "disabled", "mode", "outline"] })
|
||||
@Component({ selector: "ion-chip", changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>", inputs: ["color", "disabled", "mode", "outline"] })
|
||||
export class IonChip {
|
||||
protected el: HTMLElement;
|
||||
constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) {
|
||||
@@ -661,8 +661,8 @@ export class IonSearchbar {
|
||||
}
|
||||
export declare interface IonSegment extends Components.IonSegment {
|
||||
}
|
||||
@ProxyCmp({ inputs: ["color", "disabled", "mode", "scrollable", "value"] })
|
||||
@Component({ selector: "ion-segment", changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>", inputs: ["color", "disabled", "mode", "scrollable", "value"] })
|
||||
@ProxyCmp({ inputs: ["color", "disabled", "mode", "scrollable", "swipeGesture", "value"] })
|
||||
@Component({ selector: "ion-segment", changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-content></ng-content>", inputs: ["color", "disabled", "mode", "scrollable", "swipeGesture", "value"] })
|
||||
export class IonSegment {
|
||||
ionChange!: EventEmitter<CustomEvent>;
|
||||
protected el: HTMLElement;
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"sync:build": "sh scripts/build-ionic.sh",
|
||||
"sync": "sh scripts/sync.sh",
|
||||
"build": "npm run sync && ng build --prod --no-progress",
|
||||
"pretest": "webdriver-manager update --versions.chrome 85.0.4183.87",
|
||||
"pretest": "webdriver-manager update --versions.chrome 87.0.4280.20",
|
||||
"test": "ng e2e --prod --webdriver-update=false",
|
||||
"test.dev": "npm run sync && ng e2e",
|
||||
"lint": "ng lint",
|
||||
|
||||
@@ -248,6 +248,7 @@ ion-checkbox,part,mark
|
||||
|
||||
ion-chip,shadow
|
||||
ion-chip,prop,color,string | undefined,undefined,false,false
|
||||
ion-chip,prop,disabled,boolean,false,false,false
|
||||
ion-chip,prop,mode,"ios" | "md",undefined,false,false
|
||||
ion-chip,prop,outline,boolean,false,false,false
|
||||
ion-chip,css-prop,--background
|
||||
@@ -1014,6 +1015,7 @@ ion-segment,prop,color,string | undefined,undefined,false,false
|
||||
ion-segment,prop,disabled,boolean,false,false,false
|
||||
ion-segment,prop,mode,"ios" | "md",undefined,false,false
|
||||
ion-segment,prop,scrollable,boolean,false,false,false
|
||||
ion-segment,prop,swipeGesture,boolean,true,false,false
|
||||
ion-segment,prop,value,null | string | undefined,undefined,false,false
|
||||
ion-segment,event,ionChange,SegmentChangeEventDetail,true
|
||||
ion-segment,css-prop,--background
|
||||
|
||||
18
core/package-lock.json
generated
18
core/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "5.4.2",
|
||||
"version": "5.5.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/core",
|
||||
"version": "5.4.2",
|
||||
"version": "5.5.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ionicons": "^5.1.2",
|
||||
@@ -17,7 +17,7 @@
|
||||
"@rollup/plugin-virtual": "^2.0.3",
|
||||
"@stencil/core": "2.1.2",
|
||||
"@stencil/sass": "1.3.2",
|
||||
"@stencil/vue-output-target": "0.1.8",
|
||||
"@stencil/vue-output-target": "0.2.2",
|
||||
"@types/jest": "^26.0.10",
|
||||
"@types/node": "^14.6.0",
|
||||
"@types/puppeteer": "3.0.1",
|
||||
@@ -1657,9 +1657,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@stencil/vue-output-target": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.1.8.tgz",
|
||||
"integrity": "sha512-ruZmJuv3T0i1ZHgpOn72k4xj0WVpaerSQvjovCEgDMQ6uZewcWxc41jI0GcxAt/dn2g/feZCsKdz8M8yB9RtfA==",
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.2.2.tgz",
|
||||
"integrity": "sha512-WBnN/8ggIVYgKbJOML1DVzjFnKv7RaQpTa+XNeY0r/EG6MbXlUjUC/4Cpkg5wQ94WNuPpphAR4oRWGsmiQDxPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@stylelint/postcss-css-in-js": {
|
||||
@@ -16387,9 +16387,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@stencil/vue-output-target": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.1.8.tgz",
|
||||
"integrity": "sha512-ruZmJuv3T0i1ZHgpOn72k4xj0WVpaerSQvjovCEgDMQ6uZewcWxc41jI0GcxAt/dn2g/feZCsKdz8M8yB9RtfA==",
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@stencil/vue-output-target/-/vue-output-target-0.2.2.tgz",
|
||||
"integrity": "sha512-WBnN/8ggIVYgKbJOML1DVzjFnKv7RaQpTa+XNeY0r/EG6MbXlUjUC/4Cpkg5wQ94WNuPpphAR4oRWGsmiQDxPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@stylelint/postcss-css-in-js": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "5.4.2",
|
||||
"version": "5.5.2",
|
||||
"description": "Base components for Ionic",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
@@ -38,7 +38,7 @@
|
||||
"@rollup/plugin-virtual": "^2.0.3",
|
||||
"@stencil/core": "2.1.2",
|
||||
"@stencil/sass": "1.3.2",
|
||||
"@stencil/vue-output-target": "0.1.8",
|
||||
"@stencil/vue-output-target": "0.2.2",
|
||||
"@types/jest": "^26.0.10",
|
||||
"@types/node": "^14.6.0",
|
||||
"@types/puppeteer": "3.0.1",
|
||||
@@ -75,7 +75,7 @@
|
||||
"css.sass": "sass src/css:./css",
|
||||
"lint": "npm run lint.ts && npm run lint.sass",
|
||||
"lint.fix": "npm run lint.ts.fix && npm run lint.sass.fix",
|
||||
"lint.sass": "stylelint 'src/**/*.scss'",
|
||||
"lint.sass": "stylelint \"src/**/*.scss\"",
|
||||
"lint.sass.fix": "npm run lint.sass -- --fix",
|
||||
"lint.ts": "tslint --project .",
|
||||
"lint.ts.fix": "tslint --project . --fix",
|
||||
|
||||
26
core/src/components.d.ts
vendored
26
core/src/components.d.ts
vendored
@@ -394,7 +394,7 @@ export namespace Components {
|
||||
*/
|
||||
"name": string;
|
||||
/**
|
||||
* The value of the toggle does not mean if it's checked or not, use the `checked` property for that. The value of a toggle is analogous to the value of a `<input type="checkbox">`, it's only used when the toggle participates in a native `<form>`.
|
||||
* The value of the checkbox does not mean if it's checked or not, use the `checked` property for that. The value of a checkbox is analogous to the value of an `<input type="checkbox">`, it's only used when the checkbox participates in a native `<form>`.
|
||||
*/
|
||||
"value": string;
|
||||
}
|
||||
@@ -403,6 +403,10 @@ export namespace Components {
|
||||
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
|
||||
*/
|
||||
"color"?: Color;
|
||||
/**
|
||||
* If `true`, the user cannot interact with the chip.
|
||||
*/
|
||||
"disabled": boolean;
|
||||
/**
|
||||
* The mode determines which platform styles to use.
|
||||
*/
|
||||
@@ -1704,7 +1708,7 @@ export namespace Components {
|
||||
*/
|
||||
"name": string;
|
||||
"setButtonTabindex": (value: number) => Promise<void>;
|
||||
"setFocus": () => Promise<void>;
|
||||
"setFocus": (ev: any) => Promise<void>;
|
||||
/**
|
||||
* the value of the radio.
|
||||
*/
|
||||
@@ -2060,6 +2064,10 @@ export namespace Components {
|
||||
* If `true`, the segment buttons will overflow and the user can swipe to see them. In addition, this will disable the gesture to drag the indicator between the buttons in order to swipe to see hidden buttons.
|
||||
*/
|
||||
"scrollable": boolean;
|
||||
/**
|
||||
* If `true`, users will be able to swipe between segment buttons to activate them.
|
||||
*/
|
||||
"swipeGesture": boolean;
|
||||
/**
|
||||
* the value of the segment.
|
||||
*/
|
||||
@@ -3701,7 +3709,7 @@ declare namespace LocalJSX {
|
||||
*/
|
||||
"name"?: string;
|
||||
/**
|
||||
* Emitted when the toggle loses focus.
|
||||
* Emitted when the checkbox loses focus.
|
||||
*/
|
||||
"onIonBlur"?: (event: CustomEvent<void>) => void;
|
||||
/**
|
||||
@@ -3709,7 +3717,7 @@ declare namespace LocalJSX {
|
||||
*/
|
||||
"onIonChange"?: (event: CustomEvent<CheckboxChangeEventDetail>) => void;
|
||||
/**
|
||||
* Emitted when the toggle has focus.
|
||||
* Emitted when the checkbox has focus.
|
||||
*/
|
||||
"onIonFocus"?: (event: CustomEvent<void>) => void;
|
||||
/**
|
||||
@@ -3717,7 +3725,7 @@ declare namespace LocalJSX {
|
||||
*/
|
||||
"onIonStyle"?: (event: CustomEvent<StyleEventDetail>) => void;
|
||||
/**
|
||||
* The value of the toggle does not mean if it's checked or not, use the `checked` property for that. The value of a toggle is analogous to the value of a `<input type="checkbox">`, it's only used when the toggle participates in a native `<form>`.
|
||||
* The value of the checkbox does not mean if it's checked or not, use the `checked` property for that. The value of a checkbox is analogous to the value of an `<input type="checkbox">`, it's only used when the checkbox participates in a native `<form>`.
|
||||
*/
|
||||
"value"?: string;
|
||||
}
|
||||
@@ -3726,6 +3734,10 @@ declare namespace LocalJSX {
|
||||
* The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics).
|
||||
*/
|
||||
"color"?: Color;
|
||||
/**
|
||||
* If `true`, the user cannot interact with the chip.
|
||||
*/
|
||||
"disabled"?: boolean;
|
||||
/**
|
||||
* The mode determines which platform styles to use.
|
||||
*/
|
||||
@@ -5366,6 +5378,10 @@ declare namespace LocalJSX {
|
||||
* If `true`, the segment buttons will overflow and the user can swipe to see them. In addition, this will disable the gesture to drag the indicator between the buttons in order to swipe to see hidden buttons.
|
||||
*/
|
||||
"scrollable"?: boolean;
|
||||
/**
|
||||
* If `true`, users will be able to swipe between segment buttons to activate them.
|
||||
*/
|
||||
"swipeGesture"?: boolean;
|
||||
/**
|
||||
* the value of the segment.
|
||||
*/
|
||||
|
||||
@@ -158,7 +158,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
||||
|
||||
// If hitting arrow down or arrow right, move to the next radio
|
||||
// If we're on the last radio, move to the first radio
|
||||
if (['ArrowDown', 'ArrowRight'].includes(ev.key)) {
|
||||
if (['ArrowDown', 'ArrowRight'].includes(ev.code)) {
|
||||
nextEl = (index === radios.length - 1)
|
||||
? radios[0]
|
||||
: radios[index + 1];
|
||||
@@ -166,7 +166,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
||||
|
||||
// If hitting arrow up or arrow left, move to the previous radio
|
||||
// If we're on the first radio, move to the last radio
|
||||
if (['ArrowUp', 'ArrowLeft'].includes(ev.key)) {
|
||||
if (['ArrowUp', 'ArrowLeft'].includes(ev.code)) {
|
||||
nextEl = (index === 0)
|
||||
? radios[radios.length - 1]
|
||||
: radios[index - 1];
|
||||
@@ -373,22 +373,24 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
||||
return values;
|
||||
}
|
||||
|
||||
private renderAlertInputs(labelledBy: string | undefined) {
|
||||
private renderAlertInputs() {
|
||||
switch (this.inputType) {
|
||||
case 'checkbox': return this.renderCheckbox(labelledBy);
|
||||
case 'radio': return this.renderRadio(labelledBy);
|
||||
default: return this.renderInput(labelledBy);
|
||||
case 'checkbox': return this.renderCheckbox();
|
||||
case 'radio': return this.renderRadio();
|
||||
default: return this.renderInput();
|
||||
}
|
||||
}
|
||||
|
||||
private renderCheckbox(labelledby: string | undefined) {
|
||||
private renderCheckbox() {
|
||||
const inputs = this.processedInputs;
|
||||
const mode = getIonMode(this);
|
||||
|
||||
if (inputs.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="alert-checkbox-group" aria-labelledby={labelledby}>
|
||||
<div class="alert-checkbox-group">
|
||||
{ inputs.map(i => (
|
||||
<button
|
||||
type="button"
|
||||
@@ -422,13 +424,15 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
||||
);
|
||||
}
|
||||
|
||||
private renderRadio(labelledby: string | undefined) {
|
||||
private renderRadio() {
|
||||
const inputs = this.processedInputs;
|
||||
|
||||
if (inputs.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="alert-radio-group" role="radiogroup" aria-labelledby={labelledby} aria-activedescendant={this.activeId}>
|
||||
<div class="alert-radio-group" role="radiogroup" aria-activedescendant={this.activeId}>
|
||||
{ inputs.map(i => (
|
||||
<button
|
||||
type="button"
|
||||
@@ -459,13 +463,13 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
||||
);
|
||||
}
|
||||
|
||||
private renderInput(labelledby: string | undefined) {
|
||||
private renderInput() {
|
||||
const inputs = this.processedInputs;
|
||||
if (inputs.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div class="alert-input-group" aria-labelledby={labelledby}>
|
||||
<div class="alert-input-group">
|
||||
{ inputs.map(i => {
|
||||
if (i.type === 'textarea') {
|
||||
return (
|
||||
@@ -552,13 +556,6 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
||||
const subHdrId = `alert-${overlayIndex}-sub-hdr`;
|
||||
const msgId = `alert-${overlayIndex}-msg`;
|
||||
|
||||
let labelledById: string | undefined;
|
||||
if (header !== undefined) {
|
||||
labelledById = hdrId;
|
||||
} else if (subHeader !== undefined) {
|
||||
labelledById = subHdrId;
|
||||
}
|
||||
|
||||
return (
|
||||
<Host
|
||||
role="dialog"
|
||||
@@ -589,7 +586,7 @@ export class Alert implements ComponentInterface, OverlayInterface {
|
||||
|
||||
<div id={msgId} class="alert-message" innerHTML={sanitizeDOMString(this.message)}></div>
|
||||
|
||||
{this.renderAlertInputs(labelledById)}
|
||||
{this.renderAlertInputs()}
|
||||
{this.renderAlertButtons()}
|
||||
|
||||
</div>
|
||||
|
||||
@@ -13,7 +13,7 @@ export class App implements ComponentInterface {
|
||||
|
||||
componentDidLoad() {
|
||||
if (Build.isBrowser) {
|
||||
rIC(() => {
|
||||
rIC(async () => {
|
||||
const isHybrid = isPlatform(window, 'hybrid');
|
||||
if (!config.getBoolean('_testing')) {
|
||||
import('../../utils/tap-click').then(module => module.startTapClick(config));
|
||||
@@ -24,8 +24,11 @@ export class App implements ComponentInterface {
|
||||
if (config.getBoolean('inputShims', needInputShims())) {
|
||||
import('../../utils/input-shims/input-shims').then(module => module.startInputShims(config));
|
||||
}
|
||||
const hardwareBackButtonModule = await import('../../utils/hardware-back-button');
|
||||
if (config.getBoolean('hardwareBackButton', isHybrid)) {
|
||||
import('../../utils/hardware-back-button').then(module => module.startHardwareBackButton());
|
||||
hardwareBackButtonModule.startHardwareBackButton();
|
||||
} else {
|
||||
hardwareBackButtonModule.blockHardwareBackButton();
|
||||
}
|
||||
if (typeof (window as any) !== 'undefined') {
|
||||
import('../../utils/keyboard/keyboard').then(module => module.startKeyboardAssist(window));
|
||||
|
||||
@@ -67,6 +67,7 @@ export class Backdrop implements ComponentInterface {
|
||||
return (
|
||||
<Host
|
||||
tabindex="-1"
|
||||
aria-hidden="true"
|
||||
class={{
|
||||
[mode]: true,
|
||||
'backdrop-hide': !this.visible,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Prop
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { AnimationBuilder, Color, RouterDirection } from '../../interface';
|
||||
import { AnchorInterface, ButtonInterface } from '../../utils/element-interface';
|
||||
import { hasShadowDom } from '../../utils/helpers';
|
||||
import { hasShadowDom, inheritAttributes } from '../../utils/helpers';
|
||||
import { createColorClasses, hostContext, openURL } from '../../utils/theme';
|
||||
|
||||
/**
|
||||
@@ -28,6 +28,7 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
|
||||
private inItem = false;
|
||||
private inListHeader = false;
|
||||
private inToolbar = false;
|
||||
private inheritedAttributes: { [k: string]: any } = {};
|
||||
|
||||
@Element() el!: HTMLElement;
|
||||
|
||||
@@ -134,6 +135,7 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
|
||||
this.inToolbar = !!this.el.closest('ion-buttons');
|
||||
this.inListHeader = !!this.el.closest('ion-list-header');
|
||||
this.inItem = !!this.el.closest('ion-item') || !!this.el.closest('ion-item-divider');
|
||||
this.inheritedAttributes = inheritAttributes(this.el, ['aria-label']);
|
||||
}
|
||||
|
||||
private get hasIconOnly() {
|
||||
@@ -184,7 +186,7 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
|
||||
|
||||
render() {
|
||||
const mode = getIonMode(this);
|
||||
const { buttonType, type, disabled, rel, target, size, href, color, expand, hasIconOnly, shape, strong } = this;
|
||||
const { buttonType, type, disabled, rel, target, size, href, color, expand, hasIconOnly, shape, strong, inheritedAttributes } = this;
|
||||
const finalSize = size === undefined && this.inItem ? 'small' : size;
|
||||
const TagType = href === undefined ? 'button' : 'a' as any;
|
||||
const attrs = (TagType === 'button')
|
||||
@@ -227,6 +229,7 @@ export class Button implements ComponentInterface, AnchorInterface, ButtonInterf
|
||||
disabled={disabled}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
{...inheritedAttributes}
|
||||
>
|
||||
<span class="button-inner">
|
||||
<slot name="icon-only"></slot>
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
<ion-button class="wide">wide</ion-button>
|
||||
<ion-button class="large">large</ion-button>
|
||||
<ion-button class="round">rounded</ion-button>
|
||||
<ion-button aria-label="this is my custom label">custom aria-label</ion-button>
|
||||
|
||||
<!-- Custom Colors -->
|
||||
<ion-button class="custom">custom</ion-button>
|
||||
|
||||
@@ -40,8 +40,18 @@
|
||||
--checkmark-color: #{current-color(contrast)};
|
||||
}
|
||||
|
||||
button {
|
||||
label {
|
||||
@include input-cover();
|
||||
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
input {
|
||||
@include visually-hidden();
|
||||
}
|
||||
|
||||
.checkbox-icon {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Prop
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { CheckboxChangeEventDetail, Color, StyleEventDetail } from '../../interface';
|
||||
import { findItemLabel, renderHiddenInput } from '../../utils/helpers';
|
||||
import { getAriaLabel, renderHiddenInput } from '../../utils/helpers';
|
||||
import { createColorClasses, hostContext } from '../../utils/theme';
|
||||
|
||||
/**
|
||||
@@ -22,7 +22,7 @@ import { createColorClasses, hostContext } from '../../utils/theme';
|
||||
export class Checkbox implements ComponentInterface {
|
||||
|
||||
private inputId = `ion-cb-${checkboxIds++}`;
|
||||
private buttonEl?: HTMLElement;
|
||||
private focusEl?: HTMLElement;
|
||||
|
||||
@Element() el!: HTMLElement;
|
||||
|
||||
@@ -54,11 +54,11 @@ export class Checkbox implements ComponentInterface {
|
||||
@Prop() disabled = false;
|
||||
|
||||
/**
|
||||
* The value of the toggle does not mean if it's checked or not, use the `checked`
|
||||
* The value of the checkbox does not mean if it's checked or not, use the `checked`
|
||||
* property for that.
|
||||
*
|
||||
* The value of a toggle is analogous to the value of a `<input type="checkbox">`,
|
||||
* it's only used when the toggle participates in a native `<form>`.
|
||||
* The value of a checkbox is analogous to the value of an `<input type="checkbox">`,
|
||||
* it's only used when the checkbox participates in a native `<form>`.
|
||||
*/
|
||||
@Prop() value = 'on';
|
||||
|
||||
@@ -68,12 +68,12 @@ export class Checkbox implements ComponentInterface {
|
||||
@Event() ionChange!: EventEmitter<CheckboxChangeEventDetail>;
|
||||
|
||||
/**
|
||||
* Emitted when the toggle has focus.
|
||||
* Emitted when the checkbox has focus.
|
||||
*/
|
||||
@Event() ionFocus!: EventEmitter<void>;
|
||||
|
||||
/**
|
||||
* Emitted when the toggle loses focus.
|
||||
* Emitted when the checkbox loses focus.
|
||||
*/
|
||||
@Event() ionBlur!: EventEmitter<void>;
|
||||
|
||||
@@ -109,12 +109,14 @@ export class Checkbox implements ComponentInterface {
|
||||
}
|
||||
|
||||
private setFocus() {
|
||||
if (this.buttonEl) {
|
||||
this.buttonEl.focus();
|
||||
if (this.focusEl) {
|
||||
this.focusEl.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private onClick = () => {
|
||||
private onClick = (ev: any) => {
|
||||
ev.preventDefault();
|
||||
|
||||
this.setFocus();
|
||||
this.checked = !this.checked;
|
||||
this.indeterminate = false;
|
||||
@@ -129,14 +131,11 @@ export class Checkbox implements ComponentInterface {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { inputId, indeterminate, disabled, checked, value, color, el } = this;
|
||||
const labelId = inputId + '-lbl';
|
||||
const { color, checked, disabled, el, indeterminate, inputId, name, value } = this;
|
||||
const mode = getIonMode(this);
|
||||
const label = findItemLabel(el);
|
||||
if (label) {
|
||||
label.id = labelId;
|
||||
}
|
||||
renderHiddenInput(true, el, this.name, (checked ? value : ''), disabled);
|
||||
const { label, labelId, labelText } = getAriaLabel(el, inputId);
|
||||
|
||||
renderHiddenInput(true, el, name, (checked ? value : ''), disabled);
|
||||
|
||||
let path = indeterminate
|
||||
? <path d="M6 12L18 12" part="mark" />
|
||||
@@ -151,10 +150,10 @@ export class Checkbox implements ComponentInterface {
|
||||
return (
|
||||
<Host
|
||||
onClick={this.onClick}
|
||||
role="checkbox"
|
||||
aria-disabled={disabled ? 'true' : null}
|
||||
aria-labelledby={label ? labelId : null}
|
||||
aria-checked={`${checked}`}
|
||||
aria-labelledby={labelId}
|
||||
aria-hidden={disabled ? 'true' : null}
|
||||
role="checkbox"
|
||||
class={createColorClasses(color, {
|
||||
[mode]: true,
|
||||
'in-item': hostContext('ion-item', el),
|
||||
@@ -167,14 +166,18 @@ export class Checkbox implements ComponentInterface {
|
||||
<svg class="checkbox-icon" viewBox="0 0 24 24" part="container">
|
||||
{path}
|
||||
</svg>
|
||||
<button
|
||||
type="button"
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
disabled={this.disabled}
|
||||
ref={btnEl => this.buttonEl = btnEl}
|
||||
>
|
||||
</button>
|
||||
<label htmlFor={inputId}>
|
||||
{labelText}
|
||||
</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
aria-checked={`${checked}`}
|
||||
disabled={disabled}
|
||||
id={inputId}
|
||||
onFocus={() => this.onFocus()}
|
||||
onBlur={() => this.onBlur()}
|
||||
ref={focusEl => this.focusEl = focusEl}
|
||||
/>
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -228,8 +228,8 @@ export class CheckboxExample {
|
||||
<ion-label>{{entry.val}}</ion-label>
|
||||
<ion-checkbox
|
||||
slot="end"
|
||||
@input="entry.checked = $event.target.value"
|
||||
:value="entry.isChecked">
|
||||
@update:modelValue="entry.isChecked = $event"
|
||||
:modelValue="entry.isChecked">
|
||||
</ion-checkbox>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
@@ -266,16 +266,16 @@ export default defineComponent({
|
||||
| `indeterminate` | `indeterminate` | If `true`, the checkbox will visually appear as indeterminate. | `boolean` | `false` |
|
||||
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
|
||||
| `name` | `name` | The name of the control, which is submitted with the form data. | `string` | `this.inputId` |
|
||||
| `value` | `value` | The value of the toggle does not mean if it's checked or not, use the `checked` property for that. The value of a toggle is analogous to the value of a `<input type="checkbox">`, it's only used when the toggle participates in a native `<form>`. | `string` | `'on'` |
|
||||
| `value` | `value` | The value of the checkbox does not mean if it's checked or not, use the `checked` property for that. The value of a checkbox is analogous to the value of an `<input type="checkbox">`, it's only used when the checkbox participates in a native `<form>`. | `string` | `'on'` |
|
||||
|
||||
|
||||
## Events
|
||||
|
||||
| Event | Description | Type |
|
||||
| ----------- | ---------------------------------------------- | ---------------------------------------- |
|
||||
| `ionBlur` | Emitted when the toggle loses focus. | `CustomEvent<void>` |
|
||||
| `ionBlur` | Emitted when the checkbox loses focus. | `CustomEvent<void>` |
|
||||
| `ionChange` | Emitted when the checked property has changed. | `CustomEvent<CheckboxChangeEventDetail>` |
|
||||
| `ionFocus` | Emitted when the toggle has focus. | `CustomEvent<void>` |
|
||||
| `ionFocus` | Emitted when the checkbox has focus. | `CustomEvent<void>` |
|
||||
|
||||
|
||||
## Shadow Parts
|
||||
|
||||
@@ -26,6 +26,11 @@
|
||||
</ion-header>
|
||||
|
||||
<ion-content id="content">
|
||||
<ion-item onClick="clickItem()">
|
||||
<ion-label>Clickable Item</ion-label>
|
||||
<ion-checkbox></ion-checkbox>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Default</ion-label>
|
||||
<ion-checkbox checked></ion-checkbox>
|
||||
@@ -38,7 +43,7 @@
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Secondary</ion-label>
|
||||
<ion-checkbox checked color="secondary"></ion-checkbox>
|
||||
<ion-checkbox disabled checked color="secondary"></ion-checkbox>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
@@ -103,6 +108,35 @@
|
||||
</ion-content>
|
||||
|
||||
</ion-app>
|
||||
|
||||
<script>
|
||||
const inputs = document.querySelectorAll('ion-checkbox');
|
||||
|
||||
for (var i = 0; i < inputs.length; i++) {
|
||||
const input = inputs[i];
|
||||
|
||||
input.addEventListener('ionBlur', function() {
|
||||
console.log('Listen ionBlur: fired');
|
||||
});
|
||||
|
||||
input.addEventListener('ionFocus', function() {
|
||||
console.log('Listen ionFocus: fired');
|
||||
});
|
||||
|
||||
input.addEventListener('ionChange', function(ev) {
|
||||
console.log('Listen ionChange: fired', ev.detail);
|
||||
});
|
||||
|
||||
input.addEventListener('click', function() {
|
||||
console.log('Listen click: fired');
|
||||
});
|
||||
}
|
||||
|
||||
const clickItem = () => {
|
||||
console.log('Item click: fired');
|
||||
}
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -31,22 +31,22 @@
|
||||
<div class="ion-padding-start">
|
||||
<!-- Default to unchecked -->
|
||||
<label for="unchecked">Unchecked</label>
|
||||
<input name="unchecked" type="checkbox">
|
||||
<input name="unchecked" id="unchecked" type="checkbox">
|
||||
<br>
|
||||
|
||||
<!-- Default to checked -->
|
||||
<label for="checked">Checked</label>
|
||||
<input name="checked" type="checkbox" checked />
|
||||
<input name="checked" id="checked" type="checkbox" checked />
|
||||
<br>
|
||||
|
||||
<!-- Default to indeterminate -->
|
||||
<label for="indeterminate">Indeterminate</label>
|
||||
<input name="indeterminate" type="checkbox" class="indeterminate">
|
||||
<input name="indeterminate" id="indeterminate" type="checkbox" class="indeterminate">
|
||||
<br>
|
||||
|
||||
<!-- Default to checked / indeterminate -->
|
||||
<label for="both">Checked / Indeterminate</label>
|
||||
<input name="both" type="checkbox" checked class="indeterminate">
|
||||
<input name="both" id="both" type="checkbox" checked class="indeterminate">
|
||||
<br>
|
||||
</div>
|
||||
|
||||
@@ -81,15 +81,15 @@
|
||||
</ion-label>
|
||||
</ion-list-header>
|
||||
<div class="ion-padding-start">
|
||||
<ion-checkbox indeterminate></ion-checkbox>
|
||||
<ion-checkbox indeterminate color="secondary"></ion-checkbox>
|
||||
<ion-checkbox indeterminate color="tertiary"></ion-checkbox>
|
||||
<ion-checkbox indeterminate color="success"></ion-checkbox>
|
||||
<ion-checkbox indeterminate color="warning"></ion-checkbox>
|
||||
<ion-checkbox indeterminate color="danger"></ion-checkbox>
|
||||
<ion-checkbox indeterminate color="dark"></ion-checkbox>
|
||||
<ion-checkbox indeterminate color="medium"></ion-checkbox>
|
||||
<ion-checkbox indeterminate color="light"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Default Indeterminate" indeterminate></ion-checkbox>
|
||||
<ion-checkbox aria-label="Secondary Indeterminate" indeterminate color="secondary"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Tertiary Indeterminate" indeterminate color="tertiary"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Success Indeterminate" indeterminate color="success"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Warning Indeterminate" indeterminate color="warning"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Danger Indeterminate" indeterminate color="danger"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Dark Indeterminate" indeterminate color="dark"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Medium Indeterminate" indeterminate color="medium"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Light Indeterminate" indeterminate color="light"></ion-checkbox>
|
||||
</div>
|
||||
|
||||
<ion-list-header>
|
||||
@@ -100,20 +100,20 @@
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<ion-checkbox name="tall" id="tall" indeterminate></ion-checkbox>
|
||||
<label for="tall">Tall Things</label>
|
||||
<ion-checkbox aria-labelledby="tall-label-0" indeterminate></ion-checkbox>
|
||||
<label id="tall-label-0">Tall Things</label>
|
||||
<ul>
|
||||
<li>
|
||||
<ion-checkbox name="tall-1" id="tall-1" checked></ion-checkbox>
|
||||
<label for="tall-1">Skyscrapers</label>
|
||||
<ion-checkbox aria-labelledby="tall-label-1" checked></ion-checkbox>
|
||||
<label id="tall-label-1">Skyscrapers</label>
|
||||
</li>
|
||||
<li>
|
||||
<ion-checkbox name="tall-2" id="tall-2"></ion-checkbox>
|
||||
<label for="tall-2">Trees</label>
|
||||
<ion-checkbox aria-labelledby="tall-label-2"></ion-checkbox>
|
||||
<label id="tall-label-2">Trees</label>
|
||||
</li>
|
||||
<li>
|
||||
<ion-checkbox name="tall-2" id="tall-2"></ion-checkbox>
|
||||
<label for="tall-2">Giants</label>
|
||||
<ion-checkbox aria-labelledby="tall-label-3"></ion-checkbox>
|
||||
<label id="tall-label-3">Giants</label>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
@@ -13,67 +13,67 @@
|
||||
|
||||
<body class="ion-padding">
|
||||
<h1>Default</h1>
|
||||
<ion-checkbox></ion-checkbox>
|
||||
<ion-checkbox checked></ion-checkbox>
|
||||
<ion-checkbox disabled></ion-checkbox>
|
||||
<ion-checkbox disabled checked></ion-checkbox>
|
||||
<ion-checkbox aria-label="Default Checkbox"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Default Checkbox" checked></ion-checkbox>
|
||||
<ion-checkbox aria-label="Default Checkbox" disabled></ion-checkbox>
|
||||
<ion-checkbox aria-label="Default Checkbox" disabled checked></ion-checkbox>
|
||||
|
||||
<h1>Colors</h1>
|
||||
<ion-checkbox color="primary"></ion-checkbox>
|
||||
<ion-checkbox color="secondary"></ion-checkbox>
|
||||
<ion-checkbox color="tertiary"></ion-checkbox>
|
||||
<ion-checkbox color="success"></ion-checkbox>
|
||||
<ion-checkbox color="warning"></ion-checkbox>
|
||||
<ion-checkbox color="danger"></ion-checkbox>
|
||||
<ion-checkbox color="light"></ion-checkbox>
|
||||
<ion-checkbox color="medium"></ion-checkbox>
|
||||
<ion-checkbox color="dark"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Primary" color="primary"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Secondary" color="secondary"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Tertiary" color="tertiary"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Success" color="success"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Warning" color="warning"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Danger" color="danger"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Light" color="light"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Medium" color="medium"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Dark" color="dark"></ion-checkbox>
|
||||
|
||||
<hr>
|
||||
|
||||
<ion-checkbox checked color="primary"></ion-checkbox>
|
||||
<ion-checkbox checked color="secondary"></ion-checkbox>
|
||||
<ion-checkbox checked color="tertiary"></ion-checkbox>
|
||||
<ion-checkbox checked color="success"></ion-checkbox>
|
||||
<ion-checkbox checked color="warning"></ion-checkbox>
|
||||
<ion-checkbox checked color="danger"></ion-checkbox>
|
||||
<ion-checkbox checked color="light"></ion-checkbox>
|
||||
<ion-checkbox checked color="medium"></ion-checkbox>
|
||||
<ion-checkbox checked color="dark"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Primary" checked color="primary"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Secondary" checked color="secondary"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Tertiary" checked color="tertiary"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Success" checked color="success"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Warning" checked color="warning"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Danger" checked color="danger"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Light" checked color="light"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Medium" checked color="medium"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Dark" checked color="dark"></ion-checkbox>
|
||||
|
||||
<hr>
|
||||
|
||||
<ion-checkbox checked disabled color="primary"></ion-checkbox>
|
||||
<ion-checkbox checked disabled color="secondary"></ion-checkbox>
|
||||
<ion-checkbox checked disabled color="tertiary"></ion-checkbox>
|
||||
<ion-checkbox checked disabled color="success"></ion-checkbox>
|
||||
<ion-checkbox checked disabled color="warning"></ion-checkbox>
|
||||
<ion-checkbox checked disabled color="danger"></ion-checkbox>
|
||||
<ion-checkbox checked disabled color="light"></ion-checkbox>
|
||||
<ion-checkbox checked disabled color="medium"></ion-checkbox>
|
||||
<ion-checkbox checked disabled color="dark"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Primary" checked disabled color="primary"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Secondary" checked disabled color="secondary"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Tertiary" checked disabled color="tertiary"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Success" checked disabled color="success"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Warning" checked disabled color="warning"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Danger" checked disabled color="danger"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Light" checked disabled color="light"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Medium" checked disabled color="medium"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Dark" checked disabled color="dark"></ion-checkbox>
|
||||
|
||||
<h1>Custom</h1>
|
||||
<ion-checkbox class="custom"></ion-checkbox>
|
||||
<ion-checkbox class="custom" checked></ion-checkbox>
|
||||
<ion-checkbox class="custom" disabled></ion-checkbox>
|
||||
<ion-checkbox class="custom" disabled checked></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Custom" class="custom"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Custom" class="custom" checked></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Custom" class="custom" disabled></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Custom" class="custom" disabled checked></ion-checkbox>
|
||||
|
||||
<h1>Custom: checked</h1>
|
||||
<ion-checkbox class="custom-checked"></ion-checkbox>
|
||||
<ion-checkbox class="custom-checked" checked></ion-checkbox>
|
||||
<ion-checkbox class="custom-checked" disabled></ion-checkbox>
|
||||
<ion-checkbox class="custom-checked" disabled checked></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Custom" class="custom-checked"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Custom" class="custom-checked" checked></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Custom" class="custom-checked" disabled></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Custom" class="custom-checked" disabled checked></ion-checkbox>
|
||||
|
||||
<h1>Custom: light</h1>
|
||||
<ion-checkbox class="custom-light"></ion-checkbox>
|
||||
<ion-checkbox class="custom-light" checked></ion-checkbox>
|
||||
<ion-checkbox class="custom-light" disabled></ion-checkbox>
|
||||
<ion-checkbox class="custom-light" disabled checked></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Custom Light" class="custom-light"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Custom Light" class="custom-light" checked></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Custom Light" class="custom-light" disabled></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Custom Light" class="custom-light" disabled checked></ion-checkbox>
|
||||
|
||||
<h1>Custom: transition</h1>
|
||||
<ion-checkbox class="custom-transition"></ion-checkbox>
|
||||
<ion-checkbox class="custom-transition" checked></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Custom Transition" class="custom-transition"></ion-checkbox>
|
||||
<ion-checkbox aria-label="Checkbox Custom Transition" class="custom-transition" checked></ion-checkbox>
|
||||
|
||||
<style>
|
||||
.custom {
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
<ion-label>{{entry.val}}</ion-label>
|
||||
<ion-checkbox
|
||||
slot="end"
|
||||
@input="entry.checked = $event.target.value"
|
||||
:value="entry.isChecked">
|
||||
@update:modelValue="entry.isChecked = $event"
|
||||
:modelValue="entry.isChecked">
|
||||
</ion-checkbox>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
@@ -37,6 +37,11 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:host(.chip-disabled) {
|
||||
cursor: default;
|
||||
opacity: .4;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
// Chip Colors
|
||||
// ---------------------------------------------
|
||||
|
||||
@@ -28,14 +28,21 @@ export class Chip implements ComponentInterface {
|
||||
*/
|
||||
@Prop() outline = false;
|
||||
|
||||
/**
|
||||
* If `true`, the user cannot interact with the chip.
|
||||
*/
|
||||
@Prop() disabled = false;
|
||||
|
||||
render() {
|
||||
const mode = getIonMode(this);
|
||||
|
||||
return (
|
||||
<Host
|
||||
aria-disabled={this.disabled ? 'true' : null}
|
||||
class={createColorClasses(this.color, {
|
||||
[mode]: true,
|
||||
'chip-outline': this.outline,
|
||||
'chip-disabled': this.disabled,
|
||||
'ion-activatable': true,
|
||||
})}
|
||||
>
|
||||
|
||||
@@ -7,7 +7,7 @@ Chips represent complex entities in small blocks, such as a contact. A chip can
|
||||
|
||||
## Usage
|
||||
|
||||
### Angular / javascript
|
||||
### Angular
|
||||
|
||||
```html
|
||||
<ion-chip>
|
||||
@@ -22,6 +22,60 @@ Chips represent complex entities in small blocks, such as a contact. A chip can
|
||||
<ion-label color="dark">Secondary w/ Dark label</ion-label>
|
||||
</ion-chip>
|
||||
|
||||
<ion-chip [disabled]="true">
|
||||
<ion-label>Disabled Chip</ion-label>
|
||||
</ion-chip>
|
||||
|
||||
<ion-chip>
|
||||
<ion-icon name="pin"></ion-icon>
|
||||
<ion-label>Default</ion-label>
|
||||
</ion-chip>
|
||||
|
||||
<ion-chip>
|
||||
<ion-icon name="heart" color="dark"></ion-icon>
|
||||
<ion-label>Default</ion-label>
|
||||
</ion-chip>
|
||||
|
||||
<ion-chip>
|
||||
<ion-label>Button Chip</ion-label>
|
||||
<ion-icon name="close-circle"></ion-icon>
|
||||
</ion-chip>
|
||||
|
||||
<ion-chip>
|
||||
<ion-icon name="pin" color="primary"></ion-icon>
|
||||
<ion-label>Icon Chip</ion-label>
|
||||
<ion-icon name="close"></ion-icon>
|
||||
</ion-chip>
|
||||
|
||||
<ion-chip>
|
||||
<ion-avatar>
|
||||
<img src="https://gravatar.com/avatar/dba6bae8c566f9d4041fb9cd9ada7741?d=identicon&f=y">
|
||||
</ion-avatar>
|
||||
<ion-label>Avatar Chip</ion-label>
|
||||
<ion-icon name="close-circle"></ion-icon>
|
||||
</ion-chip>
|
||||
```
|
||||
|
||||
|
||||
### Javascript
|
||||
|
||||
```html
|
||||
<ion-chip>
|
||||
<ion-label>Default</ion-label>
|
||||
</ion-chip>
|
||||
|
||||
<ion-chip>
|
||||
<ion-label color="secondary">Secondary Label</ion-label>
|
||||
</ion-chip>
|
||||
|
||||
<ion-chip color="secondary">
|
||||
<ion-label color="dark">Secondary w/ Dark label</ion-label>
|
||||
</ion-chip>
|
||||
|
||||
<ion-chip disabled="true">
|
||||
<ion-label>Disabled Chip</ion-label>
|
||||
</ion-chip>
|
||||
|
||||
<ion-chip>
|
||||
<ion-icon name="pin"></ion-icon>
|
||||
<ion-label>Default</ion-label>
|
||||
@@ -81,6 +135,10 @@ export const ChipExamples: React.FC = () => {
|
||||
<IonLabel color="dark">Secondary w/ Dark label</IonLabel>
|
||||
</IonChip>
|
||||
|
||||
<IonChip disabled={true}>
|
||||
<IonLabel>Disabled Chip</IonLabel>
|
||||
</IonChip>
|
||||
|
||||
<IonChip>
|
||||
<IonIcon icon={pin} />
|
||||
<IonLabel>Default</IonLabel>
|
||||
@@ -191,6 +249,10 @@ export class ChipExample {
|
||||
<ion-label color="dark">Secondary w/ Dark label</ion-label>
|
||||
</ion-chip>
|
||||
|
||||
<ion-chip :disabled="true">
|
||||
<ion-label>Disabled Chip</ion-label>
|
||||
</ion-chip>
|
||||
|
||||
<ion-chip>
|
||||
<ion-icon :icon="pin"></ion-icon>
|
||||
<ion-label>Default</ion-label>
|
||||
@@ -240,11 +302,12 @@ export default defineComponent({
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Attribute | Description | Type | Default |
|
||||
| --------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | ----------- |
|
||||
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
|
||||
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
|
||||
| `outline` | `outline` | Display an outline style button. | `boolean` | `false` |
|
||||
| Property | Attribute | Description | Type | Default |
|
||||
| ---------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | ----------- |
|
||||
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
|
||||
| `disabled` | `disabled` | If `true`, the user cannot interact with the chip. | `boolean` | `false` |
|
||||
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
|
||||
| `outline` | `outline` | Display an outline style button. | `boolean` | `false` |
|
||||
|
||||
|
||||
## CSS Custom Properties
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<ion-app>
|
||||
<ion-content>
|
||||
<h3>Default</h3>
|
||||
|
||||
<p>
|
||||
<ion-chip>
|
||||
<ion-label>Default</ion-label>
|
||||
@@ -195,6 +196,26 @@
|
||||
</ion-chip>
|
||||
</p>
|
||||
|
||||
<h3>Disabled</h3>
|
||||
|
||||
<p>
|
||||
<ion-chip disabled>
|
||||
<ion-label>Disabled</ion-label>
|
||||
</ion-chip>
|
||||
<ion-chip outline color="danger" class="ion-focused" disabled>
|
||||
<ion-label>Disabled Outline</ion-label>
|
||||
</ion-chip>
|
||||
<ion-chip color="secondary" class="ion-focused" disabled>
|
||||
<ion-icon name="checkmark-circle"></ion-icon>
|
||||
<ion-label>Disabled Secondary with Icon</ion-label>
|
||||
</ion-chip>
|
||||
<ion-chip outline class="ion-focused" disabled>
|
||||
<ion-icon name="git-pull-request"></ion-icon>
|
||||
<ion-label>Disabled Outline with Icon and Avatar</ion-label>
|
||||
<ion-icon name="close-circle"></ion-icon>
|
||||
</ion-chip>
|
||||
</p>
|
||||
|
||||
<h3>Custom</h3>
|
||||
|
||||
<!-- Custom Font -->
|
||||
@@ -246,11 +267,6 @@
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
ion-chip {
|
||||
display: inline-block !important;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.wide {
|
||||
--background: #d1f3ff;
|
||||
--background-hover: #add8e6;
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
<ion-label color="dark">Secondary w/ Dark label</ion-label>
|
||||
</ion-chip>
|
||||
|
||||
<ion-chip [disabled]="true">
|
||||
<ion-label>Disabled Chip</ion-label>
|
||||
</ion-chip>
|
||||
|
||||
<ion-chip>
|
||||
<ion-icon name="pin"></ion-icon>
|
||||
<ion-label>Default</ion-label>
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
<ion-label color="dark">Secondary w/ Dark label</ion-label>
|
||||
</ion-chip>
|
||||
|
||||
<ion-chip disabled="true">
|
||||
<ion-label>Disabled Chip</ion-label>
|
||||
</ion-chip>
|
||||
|
||||
<ion-chip>
|
||||
<ion-icon name="pin"></ion-icon>
|
||||
<ion-label>Default</ion-label>
|
||||
|
||||
@@ -24,6 +24,10 @@ export const ChipExamples: React.FC = () => {
|
||||
<IonLabel color="dark">Secondary w/ Dark label</IonLabel>
|
||||
</IonChip>
|
||||
|
||||
<IonChip disabled={true}>
|
||||
<IonLabel>Disabled Chip</IonLabel>
|
||||
</IonChip>
|
||||
|
||||
<IonChip>
|
||||
<IonIcon icon={pin} />
|
||||
<IonLabel>Default</IonLabel>
|
||||
@@ -57,4 +61,4 @@ export const ChipExamples: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
```
|
||||
```
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
<ion-label color="dark">Secondary w/ Dark label</ion-label>
|
||||
</ion-chip>
|
||||
|
||||
<ion-chip :disabled="true">
|
||||
<ion-label>Disabled Chip</ion-label>
|
||||
</ion-chip>
|
||||
|
||||
<ion-chip>
|
||||
<ion-icon :icon="pin"></ion-icon>
|
||||
<ion-label>Default</ion-label>
|
||||
|
||||
@@ -100,7 +100,9 @@
|
||||
*
|
||||
* See: https://bugs.webkit.org/show_bug.cgi?id=216701
|
||||
*/
|
||||
will-change: scroll-position, transform;
|
||||
z-index: 0;
|
||||
|
||||
will-change: scroll-position;
|
||||
}
|
||||
|
||||
.scroll-y {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Build, Component, ComponentInterface, Element, Event, EventEmitter, Hos
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { AutocompleteTypes, Color, InputChangeEventDetail, StyleEventDetail, TextFieldTypes } from '../../interface';
|
||||
import { debounceEvent, findItemLabel } from '../../utils/helpers';
|
||||
import { debounceEvent, findItemLabel, inheritAttributes } from '../../utils/helpers';
|
||||
import { createColorClasses } from '../../utils/theme';
|
||||
|
||||
/**
|
||||
@@ -21,7 +21,7 @@ export class Input implements ComponentInterface {
|
||||
private nativeInput?: HTMLInputElement;
|
||||
private inputId = `ion-input-${inputIds++}`;
|
||||
private didBlurAfterEdit = false;
|
||||
private tabindex?: string | number;
|
||||
private inheritedAttributes: { [k: string]: any } = {};
|
||||
|
||||
/**
|
||||
* This is required for a WebKit bug which requires us to
|
||||
@@ -225,14 +225,7 @@ export class Input implements ComponentInterface {
|
||||
@Event() ionStyle!: EventEmitter<StyleEventDetail>;
|
||||
|
||||
componentWillLoad() {
|
||||
// If the ion-input has a tabindex attribute we get the value
|
||||
// and pass it down to the native input, then remove it from the
|
||||
// ion-input to avoid causing tabbing twice on the same element
|
||||
if (this.el.hasAttribute('tabindex')) {
|
||||
const tabindex = this.el.getAttribute('tabindex');
|
||||
this.tabindex = tabindex !== null ? tabindex : undefined;
|
||||
this.el.removeAttribute('tabindex');
|
||||
}
|
||||
this.inheritedAttributes = inheritAttributes(this.el, ['tabindex', 'title']);
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
@@ -428,13 +421,13 @@ export class Input implements ComponentInterface {
|
||||
spellcheck={this.spellcheck}
|
||||
step={this.step}
|
||||
size={this.size}
|
||||
tabindex={this.tabindex}
|
||||
type={this.type}
|
||||
value={value}
|
||||
onInput={this.onInput}
|
||||
onBlur={this.onBlur}
|
||||
onFocus={this.onFocus}
|
||||
onKeyDown={this.onKeydown}
|
||||
{...this.inheritedAttributes}
|
||||
/>
|
||||
{(this.clearInput && !this.readonly && !this.disabled) && <button
|
||||
aria-label="reset"
|
||||
|
||||
@@ -423,7 +423,7 @@ export class MenuExample {
|
||||
</ion-content>
|
||||
</ion-menu>
|
||||
|
||||
<ion-router-outlet main></ion-router-outlet>
|
||||
<ion-router-outlet id="main"></ion-router-outlet>
|
||||
</template>
|
||||
<style>
|
||||
.my-custom-menu {
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
</ion-content>
|
||||
</ion-menu>
|
||||
|
||||
<ion-router-outlet main></ion-router-outlet>
|
||||
<ion-router-outlet id="main"></ion-router-outlet>
|
||||
</template>
|
||||
<style>
|
||||
.my-custom-menu {
|
||||
|
||||
@@ -79,6 +79,8 @@ export class ModalPage {
|
||||
}
|
||||
```
|
||||
|
||||
> If you need a wrapper element inside of your modal component, we recommend using a `<div class="ion-page">` so that the component dimensions are still computed properly.
|
||||
|
||||
### Passing Data
|
||||
|
||||
During creation of a modal, data can be passed in through the `componentProps`.
|
||||
@@ -251,6 +253,8 @@ function presentModal() {
|
||||
}
|
||||
```
|
||||
|
||||
> If you need a wrapper element inside of your modal component, we recommend using a `<div class="ion-page">` so that the component dimensions are still computed properly.
|
||||
|
||||
### Passing Data
|
||||
|
||||
During creation of a modal, data can be passed in through the `componentProps`. The previous example can be written to include data:
|
||||
@@ -346,6 +350,8 @@ export const ModalExample: React.FC = () => {
|
||||
};
|
||||
```
|
||||
|
||||
> If you need a wrapper element inside of your modal component, we recommend using an `<IonPage>` so that the component dimensions are still computed properly.
|
||||
|
||||
### Swipeable Modals
|
||||
|
||||
Modals in iOS mode have the ability to be presented in a card-style and swiped to close. The card-style presentation and swipe to close gesture are not mutually exclusive, meaning you can pick and choose which features you want to use. For example, you can have a card-style modal that cannot be swiped or a full sized modal that can be swiped.
|
||||
@@ -467,6 +473,8 @@ export class PageModal {
|
||||
}
|
||||
```
|
||||
|
||||
> If you need a wrapper element inside of your modal component, we recommend using a `<div class="ion-page">` so that the component dimensions are still computed properly.
|
||||
|
||||
### Passing Data
|
||||
|
||||
During creation of a modal, data can be passed in through the `componentProps`.
|
||||
@@ -578,16 +586,14 @@ async presentModal() {
|
||||
|
||||
```html
|
||||
<template>
|
||||
<div>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>{{ title }}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
{{ content }}
|
||||
</ion-content>
|
||||
</div>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>{{ title }}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
{{ content }}
|
||||
</ion-content>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -672,6 +678,8 @@ export default defineComponent({
|
||||
</script>
|
||||
```
|
||||
|
||||
> If you need a wrapper element inside of your modal component, we recommend using an `<ion-page>` so that the component dimensions are still computed properly.
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
@@ -36,6 +36,8 @@ export class ModalPage {
|
||||
}
|
||||
```
|
||||
|
||||
> If you need a wrapper element inside of your modal component, we recommend using a `<div class="ion-page">` so that the component dimensions are still computed properly.
|
||||
|
||||
### Passing Data
|
||||
|
||||
During creation of a modal, data can be passed in through the `componentProps`.
|
||||
|
||||
@@ -31,6 +31,8 @@ function presentModal() {
|
||||
}
|
||||
```
|
||||
|
||||
> If you need a wrapper element inside of your modal component, we recommend using a `<div class="ion-page">` so that the component dimensions are still computed properly.
|
||||
|
||||
### Passing Data
|
||||
|
||||
During creation of a modal, data can be passed in through the `componentProps`. The previous example can be written to include data:
|
||||
|
||||
@@ -17,6 +17,8 @@ export const ModalExample: React.FC = () => {
|
||||
};
|
||||
```
|
||||
|
||||
> If you need a wrapper element inside of your modal component, we recommend using an `<IonPage>` so that the component dimensions are still computed properly.
|
||||
|
||||
### Swipeable Modals
|
||||
|
||||
Modals in iOS mode have the ability to be presented in a card-style and swiped to close. The card-style presentation and swipe to close gesture are not mutually exclusive, meaning you can pick and choose which features you want to use. For example, you can have a card-style modal that cannot be swiped or a full sized modal that can be swiped.
|
||||
|
||||
@@ -44,6 +44,8 @@ export class PageModal {
|
||||
}
|
||||
```
|
||||
|
||||
> If you need a wrapper element inside of your modal component, we recommend using a `<div class="ion-page">` so that the component dimensions are still computed properly.
|
||||
|
||||
### Passing Data
|
||||
|
||||
During creation of a modal, data can be passed in through the `componentProps`.
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
```html
|
||||
<template>
|
||||
<div>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>{{ title }}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
{{ content }}
|
||||
</ion-content>
|
||||
</div>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>{{ title }}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-padding">
|
||||
{{ content }}
|
||||
</ion-content>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -93,3 +91,5 @@ export default defineComponent({
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
> If you need a wrapper element inside of your modal component, we recommend using an `<ion-page>` so that the component dimensions are still computed properly.
|
||||
|
||||
@@ -10,6 +10,7 @@ export class RadioGroup implements ComponentInterface {
|
||||
|
||||
private inputId = `ion-rg-${radioGroupIds++}`;
|
||||
private labelId = `${this.inputId}-lbl`;
|
||||
private label?: HTMLIonLabelElement | null;
|
||||
|
||||
@Element() el!: HTMLElement;
|
||||
|
||||
@@ -68,10 +69,9 @@ export class RadioGroup implements ComponentInterface {
|
||||
async connectedCallback() {
|
||||
// Get the list header if it exists and set the id
|
||||
// this is used to set aria-labelledby
|
||||
const el = this.el;
|
||||
const header = el.querySelector('ion-list-header') || el.querySelector('ion-item-divider');
|
||||
const header = this.el.querySelector('ion-list-header') || this.el.querySelector('ion-item-divider');
|
||||
if (header) {
|
||||
const label = header.querySelector('ion-label');
|
||||
const label = this.label = header.querySelector('ion-label');
|
||||
if (label) {
|
||||
this.labelId = label.id = this.name + '-lbl';
|
||||
}
|
||||
@@ -83,6 +83,8 @@ export class RadioGroup implements ComponentInterface {
|
||||
}
|
||||
|
||||
private onClick = (ev: Event) => {
|
||||
ev.preventDefault();
|
||||
|
||||
const selectedRadio = ev.target && (ev.target as HTMLElement).closest('ion-radio');
|
||||
if (selectedRadio) {
|
||||
const currentValue = this.value;
|
||||
@@ -110,12 +112,13 @@ export class RadioGroup implements ComponentInterface {
|
||||
// Only move the radio if the current focus is in the radio group
|
||||
if (ev.target && radios.includes(ev.target)) {
|
||||
const index = radios.findIndex(radio => radio === ev.target);
|
||||
const current = radios[index];
|
||||
|
||||
let next;
|
||||
|
||||
// If hitting arrow down or arrow right, move to the next radio
|
||||
// If we're on the last radio, move to the first radio
|
||||
if (['ArrowDown', 'ArrowRight'].includes(ev.key)) {
|
||||
if (['ArrowDown', 'ArrowRight'].includes(ev.code)) {
|
||||
next = (index === radios.length - 1)
|
||||
? radios[0]
|
||||
: radios[index + 1];
|
||||
@@ -123,29 +126,39 @@ export class RadioGroup implements ComponentInterface {
|
||||
|
||||
// If hitting arrow up or arrow left, move to the previous radio
|
||||
// If we're on the first radio, move to the last radio
|
||||
if (['ArrowUp', 'ArrowLeft'].includes(ev.key)) {
|
||||
if (['ArrowUp', 'ArrowLeft'].includes(ev.code)) {
|
||||
next = (index === 0)
|
||||
? radios[radios.length - 1]
|
||||
: radios[index - 1];
|
||||
}
|
||||
|
||||
if (next && radios.includes(next)) {
|
||||
next.setFocus();
|
||||
next.setFocus(ev);
|
||||
|
||||
if (!inSelectPopover) {
|
||||
this.value = next.value;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the radio group value when a user presses the
|
||||
// space bar on top of a selected radio (only applies
|
||||
// to radios in a select popover)
|
||||
if (['Space'].includes(ev.code)) {
|
||||
this.value = current.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { label, labelId } = this;
|
||||
const mode = getIonMode(this);
|
||||
|
||||
return (
|
||||
<Host
|
||||
role="radiogroup"
|
||||
aria-labelledby={this.labelId}
|
||||
aria-labelledby={label ? labelId : null}
|
||||
onClick={this.onClick}
|
||||
class={getIonMode(this)}
|
||||
class={mode}
|
||||
>
|
||||
</Host>
|
||||
);
|
||||
|
||||
@@ -20,85 +20,156 @@
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="outer-content">
|
||||
<ion-radio-group id="dynamicDisabled" disabled name="tannen" id="group" value="biff">
|
||||
<ion-list-header>
|
||||
<ion-label>Luckiest Man On Earth <span id="group-value"></span></ion-label>
|
||||
</ion-list-header>
|
||||
<p class="ion-text-center">
|
||||
<ion-button onClick="addRadio()">Add Radio</ion-button>
|
||||
<ion-button onClick="addChecked()">Add Checked</ion-button>
|
||||
<ion-button id="removeButton" onClick="removeRadio()">Remove Radio</ion-button>
|
||||
</p>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Biff
|
||||
<span id="biff"></span>
|
||||
</ion-label>
|
||||
<ion-radio value="biff" slot="start"></ion-radio>
|
||||
</ion-item>
|
||||
<ion-list>
|
||||
<ion-radio-group id="dynamicDisabled" disabled name="tannen" id="group" value="biff">
|
||||
<ion-list-header>
|
||||
<ion-label>Luckiest Man On Earth <span id="group-value"></span></ion-label>
|
||||
</ion-list-header>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Griff
|
||||
<span id="griff"></span>
|
||||
</ion-label>
|
||||
<ion-radio value="griff" slot="start"></ion-radio>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Biff
|
||||
<span id="biff"></span>
|
||||
</ion-label>
|
||||
<ion-radio value="biff" slot="start"></ion-radio>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Buford
|
||||
<span id="buford"></span>
|
||||
</ion-label>
|
||||
<ion-radio value="buford" slot="start"></ion-radio>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Griff
|
||||
<span id="griff"></span>
|
||||
</ion-label>
|
||||
<ion-radio value="griff" slot="start"></ion-radio>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>George</ion-label>
|
||||
<ion-radio value="george" slot="start"></ion-radio>
|
||||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label>Buford
|
||||
<span id="buford"></span>
|
||||
</ion-label>
|
||||
<ion-radio value="buford" slot="start"></ion-radio>
|
||||
</ion-item>
|
||||
|
||||
</ion-radio-group>
|
||||
<ion-item>
|
||||
<ion-label>George</ion-label>
|
||||
<ion-radio value="george" slot="start"></ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
</ion-list>
|
||||
|
||||
<ion-button onClick="addSelect()">Add Select</ion-button>
|
||||
<ion-button onClick="addCheckedSelect()">Add Checked Select</ion-button>
|
||||
<ion-button onClick="removeSelect()">Remove Select</ion-button>
|
||||
<ion-list>
|
||||
<ion-radio-group value="huey">
|
||||
<ion-item>
|
||||
<ion-label>Huey</ion-label>
|
||||
<ion-radio slot="start" value="huey"></ion-radio>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Dewey</ion-label>
|
||||
<ion-radio slot="start" value="dewey"></ion-radio>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Louie</ion-label>
|
||||
<ion-radio slot="start" value="louie"></ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
</ion-list>
|
||||
|
||||
<ion-list>
|
||||
<ion-radio-group value="huey">
|
||||
<ion-item-divider>
|
||||
<ion-label>
|
||||
Maintenance Drone
|
||||
</ion-label>
|
||||
</ion-item-divider>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Huey</ion-label>
|
||||
<ion-radio slot="start" value="huey"></ion-radio>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Dewey</ion-label>
|
||||
<ion-radio slot="start" value="dewey"></ion-radio>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Louie</ion-label>
|
||||
<ion-radio slot="start" value="louie"></ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
|
||||
<style>
|
||||
.outer-content {
|
||||
--background: #f2f2f2;
|
||||
}
|
||||
|
||||
ion-list {
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
let count = 0;
|
||||
const removeButton = document.querySelector('#removeButton');
|
||||
|
||||
const valueEl = document.querySelector('#group-value');
|
||||
const group = document.querySelector('ion-radio-group');
|
||||
|
||||
group.addEventListener('ionChange', (ev) => {
|
||||
valueEl.textContent = group.value;
|
||||
});
|
||||
|
||||
customElements.whenDefined('ion-radio-group')
|
||||
.then(() => group.componentOnReady())
|
||||
.then(() => {
|
||||
valueEl.textContent = group.value;
|
||||
});
|
||||
|
||||
function addSelect() {
|
||||
function addRadio() {
|
||||
const item = document.createElement('ion-item');
|
||||
|
||||
item.innerHTML = `
|
||||
<ion-label>Item ${count}</ion-label>
|
||||
<ion-radio value="item-${count}" slot="start"></ion-radio>
|
||||
`;
|
||||
group.appendChild(item);
|
||||
count++;
|
||||
|
||||
removeButton.disabled = false;
|
||||
}
|
||||
function addCheckedSelect() {
|
||||
|
||||
function addChecked() {
|
||||
const item = document.createElement('ion-item');
|
||||
item.innerHTML = `
|
||||
<ion-label>Item ${count}</ion-label>
|
||||
<ion-radio value="item-${count}" slot="start"></ion-radio>
|
||||
`;
|
||||
group.appendChild(item);
|
||||
|
||||
|
||||
group.value = `item-${count}`;
|
||||
count++;
|
||||
|
||||
removeButton.disabled = false;
|
||||
}
|
||||
function removeSelect() {
|
||||
group.children[group.children.length - 1].remove();
|
||||
|
||||
function removeRadio() {
|
||||
const removeEl = group.children[group.children.length - 1];
|
||||
|
||||
if (removeEl && removeEl.tagName === 'ION-ITEM') {
|
||||
removeEl.remove();
|
||||
|
||||
// No more radios to remove, disable button
|
||||
if (!group.querySelector('ion-item')) {
|
||||
removeButton.disabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</ion-app>
|
||||
|
||||
@@ -13,28 +13,28 @@
|
||||
|
||||
<body>
|
||||
<ion-radio-group value="danger">
|
||||
<ion-radio></ion-radio>
|
||||
<ion-radio aria-label="Default"></ion-radio>
|
||||
|
||||
<ion-radio color="primary"></ion-radio>
|
||||
<ion-radio color="secondary"></ion-radio>
|
||||
<ion-radio color="tertiary"></ion-radio>
|
||||
<ion-radio color="success"></ion-radio>
|
||||
<ion-radio color="warning"></ion-radio>
|
||||
<ion-radio color="danger" value="danger"></ion-radio>
|
||||
<ion-radio color="light"></ion-radio>
|
||||
<ion-radio color="medium"></ion-radio>
|
||||
<ion-radio color="dark"></ion-radio>
|
||||
<ion-radio aria-label="Primary" color="primary"></ion-radio>
|
||||
<ion-radio aria-label="Secondary" color="secondary"></ion-radio>
|
||||
<ion-radio aria-label="Tertiary" color="tertiary"></ion-radio>
|
||||
<ion-radio aria-label="Success" color="success"></ion-radio>
|
||||
<ion-radio aria-label="Warning" color="warning"></ion-radio>
|
||||
<ion-radio aria-label="Danger" color="danger" value="danger"></ion-radio>
|
||||
<ion-radio aria-label="Light" color="light"></ion-radio>
|
||||
<ion-radio aria-label="Medium" color="medium"></ion-radio>
|
||||
<ion-radio aria-label="Dark" color="dark"></ion-radio>
|
||||
|
||||
<ion-radio disabled></ion-radio>
|
||||
<ion-radio color="secondary" disabled></ion-radio>
|
||||
<ion-radio aria-label="Default" disabled></ion-radio>
|
||||
<ion-radio aria-label="Secondary" color="secondary" disabled></ion-radio>
|
||||
</ion-radio-group>
|
||||
|
||||
<p>
|
||||
allow-empty-selection="true":
|
||||
<ion-radio-group allow-empty-selection="true">
|
||||
<ion-radio color="primary" value="1"></ion-radio>
|
||||
<ion-radio color="secondary" value="2"></ion-radio>
|
||||
<ion-radio color="tertiary" value="3"></ion-radio>
|
||||
<ion-radio aria-label="Primary" color="primary" value="1"></ion-radio>
|
||||
<ion-radio aria-label="Secondary" color="secondary" value="2"></ion-radio>
|
||||
<ion-radio aria-label="Tertiary" color="tertiary" value="3"></ion-radio>
|
||||
</ion-radio-group>
|
||||
</p>
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
.radio-icon {
|
||||
display: flex;
|
||||
|
||||
@@ -38,11 +37,25 @@
|
||||
contain: layout size style;
|
||||
}
|
||||
|
||||
button {
|
||||
@include input-cover();
|
||||
}
|
||||
|
||||
.radio-icon,
|
||||
.radio-inner {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
label {
|
||||
@include input-cover();
|
||||
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
input {
|
||||
@include visually-hidden();
|
||||
}
|
||||
|
||||
:host(:focus) {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Meth
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { Color, StyleEventDetail } from '../../interface';
|
||||
import { addEventListener, findItemLabel, removeEventListener } from '../../utils/helpers';
|
||||
import { addEventListener, getAriaLabel, removeEventListener } from '../../utils/helpers';
|
||||
import { createColorClasses, hostContext } from '../../utils/theme';
|
||||
|
||||
/**
|
||||
@@ -20,11 +20,10 @@ import { createColorClasses, hostContext } from '../../utils/theme';
|
||||
shadow: true
|
||||
})
|
||||
export class Radio implements ComponentInterface {
|
||||
private buttonEl?: HTMLButtonElement;
|
||||
private inputId = `ion-rb-${radioButtonIds++}`;
|
||||
private radioGroup: HTMLIonRadioGroupElement | null = null;
|
||||
|
||||
@Element() el!: HTMLElement;
|
||||
@Element() el!: HTMLIonRadioElement;
|
||||
|
||||
/**
|
||||
* If `true`, the radio is selected.
|
||||
@@ -77,10 +76,11 @@ export class Radio implements ComponentInterface {
|
||||
|
||||
/** @internal */
|
||||
@Method()
|
||||
async setFocus() {
|
||||
if (this.buttonEl) {
|
||||
this.buttonEl.focus();
|
||||
}
|
||||
async setFocus(ev: any) {
|
||||
ev.stopPropagation();
|
||||
ev.preventDefault();
|
||||
|
||||
this.el.focus();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@@ -139,17 +139,17 @@ export class Radio implements ComponentInterface {
|
||||
render() {
|
||||
const { inputId, disabled, checked, color, el, buttonTabindex } = this;
|
||||
const mode = getIonMode(this);
|
||||
const labelId = inputId + '-lbl';
|
||||
const label = findItemLabel(el);
|
||||
if (label) {
|
||||
label.id = labelId;
|
||||
}
|
||||
const { label, labelId, labelText } = getAriaLabel(el, inputId);
|
||||
|
||||
return (
|
||||
<Host
|
||||
role="radio"
|
||||
aria-disabled={disabled ? 'true' : null}
|
||||
aria-checked={`${checked}`}
|
||||
aria-labelledby={labelId}
|
||||
aria-hidden={disabled ? 'true' : null}
|
||||
aria-labelledby={label ? labelId : null}
|
||||
role="radio"
|
||||
tabindex={buttonTabindex}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
class={createColorClasses(color, {
|
||||
[mode]: true,
|
||||
'in-item': hostContext('ion-item', el),
|
||||
@@ -160,16 +160,18 @@ export class Radio implements ComponentInterface {
|
||||
>
|
||||
<div class="radio-icon" part="container">
|
||||
<div class="radio-inner" part="mark" />
|
||||
<div class="radio-ripple"></div>
|
||||
</div>
|
||||
<button
|
||||
ref={btnEl => this.buttonEl = btnEl}
|
||||
type="button"
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
<label htmlFor={inputId}>
|
||||
{labelText}
|
||||
</label>
|
||||
<input
|
||||
type="radio"
|
||||
checked={checked}
|
||||
disabled={disabled}
|
||||
tabindex={buttonTabindex}
|
||||
>
|
||||
</button>
|
||||
tabindex="-1"
|
||||
id={inputId}
|
||||
/>
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
|
||||
19
core/src/components/radio/test/a11y/e2e.ts
Normal file
19
core/src/components/radio/test/a11y/e2e.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { newE2EPage } from '@stencil/core/testing';
|
||||
|
||||
test('radio: a11y', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/radio/test/a11y?ionic:_testing=true'
|
||||
});
|
||||
|
||||
const compare = await page.compareScreenshot();
|
||||
expect(compare).toMatchScreenshot();
|
||||
});
|
||||
|
||||
test('radio:rtl: a11y', async () => {
|
||||
const page = await newE2EPage({
|
||||
url: '/src/components/radio/test/a11y?ionic:_testing=true&rtl=true'
|
||||
});
|
||||
|
||||
const compare = await page.compareScreenshot();
|
||||
expect(compare).toMatchScreenshot();
|
||||
});
|
||||
166
core/src/components/radio/test/a11y/index.html
Normal file
166
core/src/components/radio/test/a11y/index.html
Normal file
@@ -0,0 +1,166 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Radio - a11y</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
|
||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script></head>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Radio - a11y</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content id="content" class="outer-content">
|
||||
<div class="native-radio-group">
|
||||
<p>Select a maintenance drone (native):</p>
|
||||
|
||||
<div>
|
||||
<input type="radio" id="huey" name="drone" value="huey" checked>
|
||||
<label for="huey">Huey</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input type="radio" id="dewey" name="drone" value="dewey">
|
||||
<label for="dewey">Dewey</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input type="radio" id="fooey" value="fooey" disabled/>
|
||||
<label for="fooey">Fooey</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input type="radio" id="louie" name="drone" value="louie">
|
||||
<label for="louie">Louie</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ion-list>
|
||||
<ion-list-header>
|
||||
<ion-label>
|
||||
Select a maintenance drone:
|
||||
</ion-label>
|
||||
</ion-list-header>
|
||||
<ion-radio-group value="huey">
|
||||
<ion-item>
|
||||
<ion-label>Huey</ion-label>
|
||||
<ion-radio slot="start" value="huey"></ion-radio>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Dewey</ion-label>
|
||||
<ion-radio slot="start" value="dewey"></ion-radio>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Fooey</ion-label>
|
||||
<ion-radio slot="start" value="fooey" color="secondary" disabled></ion-radio>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Louie</ion-label>
|
||||
<ion-radio slot="start" value="louie"></ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
</ion-list>
|
||||
|
||||
<ion-list>
|
||||
<ion-radio-group value="huey">
|
||||
<ion-item>
|
||||
<ion-label>Huey</ion-label>
|
||||
<ion-radio slot="start" value="huey" color="danger"></ion-radio>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Dewey</ion-label>
|
||||
<ion-radio slot="start" value="dewey" color="secondary"></ion-radio>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Fooey</ion-label>
|
||||
<ion-radio slot="start" value="fooey" color="secondary" disabled></ion-radio>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Louie</ion-label>
|
||||
<ion-radio slot="start" value="louie" color="tertiary"></ion-radio>
|
||||
</ion-item>
|
||||
</ion-radio-group>
|
||||
</ion-list>
|
||||
|
||||
<div style="padding: 10px 16px">
|
||||
<ion-radio-group value="louie">
|
||||
<h5>Custom Labels</h5>
|
||||
<div>
|
||||
<ion-radio id="custom-huey" value="huey"></ion-radio>
|
||||
<label for="custom-huey">Huey</label>
|
||||
</div>
|
||||
<div>
|
||||
<ion-radio id="custom-dewey" value="dewey"></ion-radio>
|
||||
<label for="custom-dewey">Dewey</label>
|
||||
</div>
|
||||
<div>
|
||||
<ion-radio id="custom-fooey" value="fooey" disabled></ion-radio>
|
||||
<label for="custom-fooey">Fooey</label>
|
||||
</div>
|
||||
<div>
|
||||
<ion-radio id="custom-louie" value="louie"></ion-radio>
|
||||
<label for="custom-louie">Louie</label>
|
||||
</div>
|
||||
</ion-radio-group>
|
||||
</div>
|
||||
</ion-content>
|
||||
|
||||
</ion-app>
|
||||
</body>
|
||||
|
||||
<style>
|
||||
ion-list {
|
||||
margin-bottom: 10px !important;
|
||||
}
|
||||
|
||||
.native-radio-group {
|
||||
background: white;
|
||||
padding: 10px 16px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.native-radio-group div {
|
||||
margin: 10px 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const inputs = document.querySelectorAll('ion-radio');
|
||||
|
||||
for (var i = 0; i < inputs.length; i++) {
|
||||
const input = inputs[i];
|
||||
|
||||
input.addEventListener('ionBlur', function() {
|
||||
console.log('Listen ionBlur: fired');
|
||||
});
|
||||
|
||||
input.addEventListener('ionFocus', function() {
|
||||
console.log('Listen ionFocus: fired');
|
||||
});
|
||||
|
||||
input.addEventListener('ionChange', function(ev) {
|
||||
console.log('Listen ionChange: fired', ev.detail);
|
||||
});
|
||||
|
||||
input.addEventListener('click', function() {
|
||||
console.log('Listen click: fired');
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
</html>
|
||||
@@ -14,49 +14,49 @@
|
||||
<body>
|
||||
<h1>Default</h1>
|
||||
<ion-radio-group value="radio">
|
||||
<ion-radio></ion-radio>
|
||||
<ion-radio value="radio"></ion-radio>
|
||||
<ion-radio aria-label="Default"></ion-radio>
|
||||
<ion-radio aria-label="Default" value="radio"></ion-radio>
|
||||
</ion-radio-group>
|
||||
|
||||
<h1>Colors: Unchecked</h1>
|
||||
<ion-radio color="primary"></ion-radio>
|
||||
<ion-radio color="secondary"></ion-radio>
|
||||
<ion-radio color="tertiary"></ion-radio>
|
||||
<ion-radio color="success"></ion-radio>
|
||||
<ion-radio color="warning"></ion-radio>
|
||||
<ion-radio color="danger"></ion-radio>
|
||||
<ion-radio color="light"></ion-radio>
|
||||
<ion-radio color="medium"></ion-radio>
|
||||
<ion-radio color="dark"></ion-radio>
|
||||
<ion-radio aria-label="Primary" color="primary"></ion-radio>
|
||||
<ion-radio aria-label="Secondary" color="secondary"></ion-radio>
|
||||
<ion-radio aria-label="Tertiary" color="tertiary"></ion-radio>
|
||||
<ion-radio aria-label="Success" color="success"></ion-radio>
|
||||
<ion-radio aria-label="Warning" color="warning"></ion-radio>
|
||||
<ion-radio aria-label="Danger" color="danger"></ion-radio>
|
||||
<ion-radio aria-label="Light" color="light"></ion-radio>
|
||||
<ion-radio aria-label="Medium" color="medium"></ion-radio>
|
||||
<ion-radio aria-label="Dark" color="dark"></ion-radio>
|
||||
|
||||
<h1>Colors: Checked</h1>
|
||||
<ion-radio-group value="radio">
|
||||
<ion-radio color="primary" value="radio"></ion-radio>
|
||||
<ion-radio color="secondary" value="radio"></ion-radio>
|
||||
<ion-radio color="tertiary" value="radio"></ion-radio>
|
||||
<ion-radio color="success" value="radio"></ion-radio>
|
||||
<ion-radio color="warning" value="radio"></ion-radio>
|
||||
<ion-radio color="danger" value="radio"></ion-radio>
|
||||
<ion-radio color="light" value="radio"></ion-radio>
|
||||
<ion-radio color="medium" value="radio"></ion-radio>
|
||||
<ion-radio color="dark" value="radio"></ion-radio>
|
||||
<ion-radio aria-label="Primary" color="primary" value="radio"></ion-radio>
|
||||
<ion-radio aria-label="Secondary" color="secondary" value="radio"></ion-radio>
|
||||
<ion-radio aria-label="Tertiary" color="tertiary" value="radio"></ion-radio>
|
||||
<ion-radio aria-label="Success" color="success" value="radio"></ion-radio>
|
||||
<ion-radio aria-label="Warning" color="warning" value="radio"></ion-radio>
|
||||
<ion-radio aria-label="Danger" color="danger" value="radio"></ion-radio>
|
||||
<ion-radio aria-label="Light" color="light" value="radio"></ion-radio>
|
||||
<ion-radio aria-label="Medium" color="medium" value="radio"></ion-radio>
|
||||
<ion-radio aria-label="Dark" color="dark" value="radio"></ion-radio>
|
||||
</ion-radio-group>
|
||||
|
||||
<h1>Disabled</h1>
|
||||
<ion-radio-group value="radio">
|
||||
<ion-radio disabled></ion-radio>
|
||||
<ion-radio color="secondary" disabled></ion-radio>
|
||||
<ion-radio disabled value="radio"></ion-radio>
|
||||
<ion-radio color="secondary" disabled value="radio"></ion-radio>
|
||||
<ion-radio aria-label="Default" disabled></ion-radio>
|
||||
<ion-radio aria-label="Secondary" color="secondary" disabled></ion-radio>
|
||||
<ion-radio aria-label="Default" disabled value="radio"></ion-radio>
|
||||
<ion-radio aria-label="Secondary" color="secondary" disabled value="radio"></ion-radio>
|
||||
</ion-radio-group>
|
||||
|
||||
<h1>Custom</h1>
|
||||
|
||||
<ion-radio-group value="radio">
|
||||
<ion-radio class="custom"></ion-radio>
|
||||
<ion-radio class="custom" value="radio"></ion-radio>
|
||||
<ion-radio class="custom" color="tertiary" value="radio"></ion-radio>
|
||||
<ion-radio class="custom-size" color="danger" value="radio"></ion-radio>
|
||||
<ion-radio aria-label="Custom" class="custom"></ion-radio>
|
||||
<ion-radio aria-label="Custom" class="custom" value="radio"></ion-radio>
|
||||
<ion-radio aria-label="Custom Tertiary" class="custom" color="tertiary" value="radio"></ion-radio>
|
||||
<ion-radio aria-label="Custom Size" class="custom-size" color="danger" value="radio"></ion-radio>
|
||||
</ion-radio-group>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -124,8 +124,8 @@ export class Refresher implements ComponentInterface {
|
||||
*/
|
||||
@Event() ionStart!: EventEmitter<void>;
|
||||
|
||||
private checkNativeRefresher() {
|
||||
const useNativeRefresher = shouldUseNativeRefresher(this.el, getIonMode(this));
|
||||
private async checkNativeRefresher() {
|
||||
const useNativeRefresher = await shouldUseNativeRefresher(this.el, getIonMode(this));
|
||||
if (useNativeRefresher && !this.nativeRefresher) {
|
||||
const contentEl = this.el.closest('ion-content');
|
||||
this.setupNativeRefresher(contentEl);
|
||||
@@ -165,7 +165,7 @@ export class Refresher implements ComponentInterface {
|
||||
private async setupiOSNativeRefresher(pullingSpinner: HTMLIonSpinnerElement, refreshingSpinner: HTMLIonSpinnerElement) {
|
||||
this.elementToTransform = this.scrollEl!;
|
||||
const ticks = pullingSpinner.shadowRoot!.querySelectorAll('svg');
|
||||
const MAX_PULL = this.scrollEl!.clientHeight * 0.16;
|
||||
let MAX_PULL = this.scrollEl!.clientHeight * 0.16;
|
||||
const NUM_TICKS = ticks.length;
|
||||
|
||||
writeTask(() => ticks.forEach(el => el.style.setProperty('animation', 'none')));
|
||||
@@ -242,7 +242,7 @@ export class Refresher implements ComponentInterface {
|
||||
this.gesture = (await import('../../utils/gesture')).createGesture({
|
||||
el: this.scrollEl!,
|
||||
gestureName: 'refresher',
|
||||
gesturePriority: 10,
|
||||
gesturePriority: 31,
|
||||
direction: 'y',
|
||||
threshold: 5,
|
||||
onStart: () => {
|
||||
@@ -251,6 +251,18 @@ export class Refresher implements ComponentInterface {
|
||||
if (!this.didRefresh) {
|
||||
translateElement(this.elementToTransform, '0px');
|
||||
}
|
||||
|
||||
/**
|
||||
* If the content had `display: none` when
|
||||
* the refresher was initialized, its clientHeight
|
||||
* will be 0. When the gesture starts, the content
|
||||
* will be visible, so try to get the correct
|
||||
* client height again. This is most common when
|
||||
* using the refresher in an ion-menu.
|
||||
*/
|
||||
if (MAX_PULL === 0) {
|
||||
MAX_PULL = this.scrollEl!.clientHeight * 0.16;
|
||||
}
|
||||
},
|
||||
onMove: ev => {
|
||||
this.lastVelocityY = ev.velocityY;
|
||||
@@ -289,7 +301,7 @@ export class Refresher implements ComponentInterface {
|
||||
this.gesture = (await import('../../utils/gesture')).createGesture({
|
||||
el: this.scrollEl!,
|
||||
gestureName: 'refresher',
|
||||
gesturePriority: 10,
|
||||
gesturePriority: 31,
|
||||
direction: 'y',
|
||||
threshold: 5,
|
||||
canStart: () => this.state !== RefresherState.Refreshing && this.state !== RefresherState.Completing && this.scrollEl!.scrollTop === 0,
|
||||
@@ -366,6 +378,15 @@ export class Refresher implements ComponentInterface {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* If using non-native refresher before make sure
|
||||
* we clean up any old CSS. This can happen when
|
||||
* a user manually calls the refresh method in a
|
||||
* component create callback before the native
|
||||
* refresher is setup.
|
||||
*/
|
||||
this.setCss(0, '', false, '');
|
||||
|
||||
this.nativeRefresher = true;
|
||||
|
||||
const pullingSpinner = this.el.querySelector('ion-refresher-content .refresher-pulling ion-spinner') as HTMLIonSpinnerElement;
|
||||
@@ -399,13 +420,13 @@ export class Refresher implements ComponentInterface {
|
||||
this.scrollEl = await contentEl.getScrollElement();
|
||||
this.backgroundContentEl = getElementRoot(contentEl).querySelector('#background-content') as HTMLElement;
|
||||
|
||||
if (shouldUseNativeRefresher(this.el, getIonMode(this))) {
|
||||
if (await shouldUseNativeRefresher(this.el, getIonMode(this))) {
|
||||
this.setupNativeRefresher(contentEl);
|
||||
} else {
|
||||
this.gesture = (await import('../../utils/gesture')).createGesture({
|
||||
el: contentEl,
|
||||
gestureName: 'refresher',
|
||||
gesturePriority: 10,
|
||||
gesturePriority: 31,
|
||||
direction: 'y',
|
||||
threshold: 20,
|
||||
passive: false,
|
||||
|
||||
@@ -166,9 +166,14 @@ export const translateElement = (el?: HTMLElement, value?: string) => {
|
||||
// Utils
|
||||
// -----------------------------
|
||||
|
||||
export const shouldUseNativeRefresher = (referenceEl: HTMLIonRefresherElement, mode: string) => {
|
||||
const pullingSpinner = referenceEl.querySelector('ion-refresher-content .refresher-pulling ion-spinner');
|
||||
const refreshingSpinner = referenceEl.querySelector('ion-refresher-content .refresher-refreshing ion-spinner');
|
||||
export const shouldUseNativeRefresher = async (referenceEl: HTMLIonRefresherElement, mode: string) => {
|
||||
const refresherContent = referenceEl.querySelector('ion-refresher-content');
|
||||
if (!refresherContent) { return Promise.resolve(false); }
|
||||
|
||||
await refresherContent.componentOnReady();
|
||||
|
||||
const pullingSpinner = refresherContent.querySelector('.refresher-pulling ion-spinner');
|
||||
const refreshingSpinner = refresherContent.querySelector('.refresher-refreshing ion-spinner');
|
||||
|
||||
return (
|
||||
pullingSpinner !== null &&
|
||||
|
||||
@@ -33,17 +33,17 @@
|
||||
<ion-content class="ion-padding">
|
||||
<ion-list>
|
||||
<ion-button id="router-push">router.push</ion-button><br>
|
||||
<ion-router-link href="/child">
|
||||
<ion-router-link href="/child/1">
|
||||
<ion-button id="router-link">ion-router-link</ion-button><br>
|
||||
</ion-router-link>
|
||||
<ion-button href="/child" id="href">href</ion-button>
|
||||
<ion-button href="/child/1" id="href">href</ion-button>
|
||||
</ion-list>
|
||||
</ion-content>`;
|
||||
|
||||
const childButton = this.querySelector('#router-push');
|
||||
childButton.addEventListener('click', () => {
|
||||
const r = document.querySelector('ion-router');
|
||||
r.push('/child');
|
||||
r.push('/child/1');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -145,7 +145,7 @@
|
||||
<ion-route-redirect from="/" to="/home"></ion-route-redirect>
|
||||
<ion-route url="/home" component="home-page"></ion-route>
|
||||
<ion-route url="/test" component="test-page"></ion-route>
|
||||
<ion-route url="/child" component="child-page"></ion-route>
|
||||
<ion-route url="/child/:id" component="child-page"></ion-route>
|
||||
</ion-router>
|
||||
|
||||
<ion-nav></ion-nav>
|
||||
|
||||
@@ -13,7 +13,7 @@ test('router: guards - router-link - allow/allow', async () => {
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
await checkUrl(page, '#/child');
|
||||
await checkUrl(page, '#/child/1');
|
||||
|
||||
const backButton = await page.$('ion-back-button');
|
||||
await backButton.click();
|
||||
@@ -78,14 +78,14 @@ test('router: guards - router-link - allow/block', async () => {
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
await checkUrl(page, '#/child');
|
||||
await checkUrl(page, '#/child/1');
|
||||
|
||||
const backButton = await page.$('ion-back-button');
|
||||
await backButton.click();
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
await checkUrl(page, '#/child');
|
||||
await checkUrl(page, '#/child/1');
|
||||
});
|
||||
|
||||
// TODO this is an actual bug in the code.
|
||||
@@ -102,7 +102,7 @@ test('router: guards - router-link - allow/redirect', async () => {
|
||||
|
||||
await page.waitForChanges();
|
||||
|
||||
await checkUrl(page, '#/child');
|
||||
await checkUrl(page, '#/child/1');
|
||||
|
||||
const backButton = await page.$('ion-back-button');
|
||||
await backButton.click();
|
||||
|
||||
@@ -74,7 +74,9 @@ export const matchesPath = (inputPath: string[], chain: RouteChain): RouteChain
|
||||
return chain.map((route, i) => ({
|
||||
id: route.id,
|
||||
path: route.path,
|
||||
params: mergeParams(route.params, allparams![i])
|
||||
params: mergeParams(route.params, allparams![i]),
|
||||
beforeEnter: route.beforeEnter,
|
||||
beforeLeave: route.beforeLeave
|
||||
}));
|
||||
}
|
||||
return chain;
|
||||
|
||||
@@ -566,13 +566,14 @@ export default defineComponent({
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Attribute | Description | Type | Default |
|
||||
| ------------ | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | ----------- |
|
||||
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
|
||||
| `disabled` | `disabled` | If `true`, the user cannot interact with the segment. | `boolean` | `false` |
|
||||
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
|
||||
| `scrollable` | `scrollable` | If `true`, the segment buttons will overflow and the user can swipe to see them. In addition, this will disable the gesture to drag the indicator between the buttons in order to swipe to see hidden buttons. | `boolean` | `false` |
|
||||
| `value` | `value` | the value of the segment. | `null \| string \| undefined` | `undefined` |
|
||||
| Property | Attribute | Description | Type | Default |
|
||||
| -------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | ----------- |
|
||||
| `color` | `color` | The color to use from your application's color palette. Default options are: `"primary"`, `"secondary"`, `"tertiary"`, `"success"`, `"warning"`, `"danger"`, `"light"`, `"medium"`, and `"dark"`. For more information on colors, see [theming](/docs/theming/basics). | `string \| undefined` | `undefined` |
|
||||
| `disabled` | `disabled` | If `true`, the user cannot interact with the segment. | `boolean` | `false` |
|
||||
| `mode` | `mode` | The mode determines which platform styles to use. | `"ios" \| "md"` | `undefined` |
|
||||
| `scrollable` | `scrollable` | If `true`, the segment buttons will overflow and the user can swipe to see them. In addition, this will disable the gesture to drag the indicator between the buttons in order to swipe to see hidden buttons. | `boolean` | `false` |
|
||||
| `swipeGesture` | `swipe-gesture` | If `true`, users will be able to swipe between segment buttons to activate them. | `boolean` | `true` |
|
||||
| `value` | `value` | the value of the segment. | `null \| string \| undefined` | `undefined` |
|
||||
|
||||
|
||||
## Events
|
||||
|
||||
@@ -65,6 +65,16 @@ export class Segment implements ComponentInterface {
|
||||
*/
|
||||
@Prop() scrollable = false;
|
||||
|
||||
/**
|
||||
* If `true`, users will be able to swipe between segment buttons to activate them.
|
||||
*/
|
||||
@Prop() swipeGesture = true;
|
||||
|
||||
@Watch('swipeGesture')
|
||||
swipeGestureChanged() {
|
||||
this.gestureChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* the value of the segment.
|
||||
*/
|
||||
@@ -111,8 +121,8 @@ export class Segment implements ComponentInterface {
|
||||
}
|
||||
|
||||
private gestureChanged() {
|
||||
if (this.gesture && !this.scrollable) {
|
||||
this.gesture.enable(!this.disabled);
|
||||
if (this.gesture) {
|
||||
this.gesture.enable(!this.scrollable && !this.disabled && this.swipeGesture);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +147,6 @@ export class Segment implements ComponentInterface {
|
||||
onMove: ev => this.onMove(ev),
|
||||
onEnd: ev => this.onEnd(ev),
|
||||
});
|
||||
this.gesture.enable(!this.scrollable);
|
||||
this.gestureChanged();
|
||||
|
||||
if (this.disabled) {
|
||||
@@ -390,9 +399,18 @@ export class Segment implements ComponentInterface {
|
||||
private onClick = (ev: Event) => {
|
||||
const current = ev.target as HTMLIonSegmentButtonElement;
|
||||
const previous = this.checked;
|
||||
|
||||
// If the current element is a segment then that means
|
||||
// the user tried to swipe to a segment button and
|
||||
// click a segment button at the same time so we should
|
||||
// not update the checked segment button
|
||||
if (current.tagName === 'ION-SEGMENT') {
|
||||
return;
|
||||
}
|
||||
|
||||
this.value = current.value;
|
||||
|
||||
if (this.scrollable) {
|
||||
if (this.scrollable || !this.swipeGesture) {
|
||||
if (previous) {
|
||||
this.checkButton(previous, current);
|
||||
} else {
|
||||
|
||||
@@ -198,6 +198,9 @@
|
||||
<div class="ion-padding">
|
||||
<ion-button expand="block" color="secondary" onClick="toggleValue()">Toggle Value</ion-button>
|
||||
</div>
|
||||
<div class="ion-padding-horizontal">
|
||||
<ion-button expand="block" color="tertiary" onClick="toggleSwipeGesture()">Toggle Swipe Gesture</ion-button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var dynamicAttrDisable = document.getElementsByName('dynamicAttrDisable');
|
||||
@@ -229,6 +232,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSwipeGesture() {
|
||||
const ionSegmentElement = document.querySelectorAll('ion-segment');
|
||||
for (var i = 0; i < ionSegmentElement.length; i++) {
|
||||
ionSegmentElement[i].swipeGesture = !ionSegmentElement[i].swipeGesture;
|
||||
}
|
||||
}
|
||||
|
||||
async function listenForEvent() {
|
||||
const ionSegmentElement = document.querySelector('ion-segment.event-tester');
|
||||
ionSegmentElement.addEventListener('ionChange', (event) => {
|
||||
|
||||
@@ -118,7 +118,7 @@ Customizing the interface dialog should be done by following the Customization s
|
||||
- [Action Sheet Customization](../action-sheet#customization)
|
||||
- [Popover Customization](../popover#customization)
|
||||
|
||||
However, the Select Option does set a class for easier styling and allows for the ability to pass a class to the overlay option, see the [Select Options documentation](./select-option) for usage examples of customizing options.
|
||||
However, the Select Option does set a class for easier styling and allows for the ability to pass a class to the overlay option, see the [Select Options documentation](../select-option) for usage examples of customizing options.
|
||||
|
||||
<!-- Auto Generated Below -->
|
||||
|
||||
|
||||
@@ -50,8 +50,18 @@
|
||||
opacity: var(--placeholder-opacity);
|
||||
}
|
||||
|
||||
button {
|
||||
label {
|
||||
@include input-cover();
|
||||
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
@include visually-hidden();
|
||||
}
|
||||
|
||||
.select-icon {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Meth
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { ActionSheetButton, ActionSheetOptions, AlertInput, AlertOptions, CssClassMap, OverlaySelect, PopoverOptions, SelectChangeEventDetail, SelectInterface, SelectPopoverOption, StyleEventDetail } from '../../interface';
|
||||
import { findItemLabel, renderHiddenInput } from '../../utils/helpers';
|
||||
import { findItemLabel, getAriaLabel, renderHiddenInput } from '../../utils/helpers';
|
||||
import { actionSheetController, alertController, popoverController } from '../../utils/overlays';
|
||||
import { hostContext } from '../../utils/theme';
|
||||
import { watchForOptions } from '../../utils/watch-options';
|
||||
@@ -29,7 +29,7 @@ export class Select implements ComponentInterface {
|
||||
private inputId = `ion-sel-${selectIds++}`;
|
||||
private overlay?: OverlaySelect;
|
||||
private didInit = false;
|
||||
private buttonEl?: HTMLButtonElement;
|
||||
private focusEl?: HTMLButtonElement;
|
||||
private mutationO?: MutationObserver;
|
||||
|
||||
@Element() el!: HTMLIonSelectElement;
|
||||
@@ -403,8 +403,8 @@ export class Select implements ComponentInterface {
|
||||
}
|
||||
|
||||
private setFocus() {
|
||||
if (this.buttonEl) {
|
||||
this.buttonEl.focus();
|
||||
if (this.focusEl) {
|
||||
this.focusEl.focus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,23 +432,21 @@ export class Select implements ComponentInterface {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { placeholder, name, disabled, isExpanded, value, el } = this;
|
||||
const { disabled, el, inputId, isExpanded, name, placeholder, value } = this;
|
||||
const mode = getIonMode(this);
|
||||
const labelId = this.inputId + '-lbl';
|
||||
const label = findItemLabel(el);
|
||||
if (label) {
|
||||
label.id = labelId;
|
||||
}
|
||||
const { labelText, labelId } = getAriaLabel(el, inputId);
|
||||
|
||||
renderHiddenInput(true, el, name, parseValue(value), disabled);
|
||||
|
||||
const displayValue = this.getText();
|
||||
|
||||
let addPlaceholderClass = false;
|
||||
let selectText = this.getText();
|
||||
let selectText = displayValue;
|
||||
if (selectText === '' && placeholder != null) {
|
||||
selectText = placeholder;
|
||||
addPlaceholderClass = true;
|
||||
}
|
||||
|
||||
renderHiddenInput(true, el, name, parseValue(value), disabled);
|
||||
|
||||
const selectTextClasses: CssClassMap = {
|
||||
'select-text': true,
|
||||
'select-placeholder': addPlaceholderClass
|
||||
@@ -456,34 +454,47 @@ export class Select implements ComponentInterface {
|
||||
|
||||
const textPart = addPlaceholderClass ? 'placeholder' : 'text';
|
||||
|
||||
// If there is a label then we need to concatenate it with the
|
||||
// current value (or placeholder) and a comma so it separates
|
||||
// nicely when the screen reader announces it, otherwise just
|
||||
// announce the value / placeholder
|
||||
const displayLabel = labelText !== undefined
|
||||
? (selectText !== '' ? `${selectText}, ${labelText}` : labelText)
|
||||
: selectText;
|
||||
|
||||
return (
|
||||
<Host
|
||||
onClick={this.onClick}
|
||||
role="listbox"
|
||||
aria-haspopup="dialog"
|
||||
role="button"
|
||||
aria-haspopup="listbox"
|
||||
aria-disabled={disabled ? 'true' : null}
|
||||
aria-expanded={`${isExpanded}`}
|
||||
aria-labelledby={labelId}
|
||||
aria-label={displayLabel}
|
||||
class={{
|
||||
[mode]: true,
|
||||
'in-item': hostContext('ion-item', el),
|
||||
'select-disabled': disabled,
|
||||
}}
|
||||
>
|
||||
<div class={selectTextClasses} part={textPart}>
|
||||
<div aria-hidden="true" class={selectTextClasses} part={textPart}>
|
||||
{selectText}
|
||||
</div>
|
||||
<div class="select-icon" role="presentation" part="icon">
|
||||
<div class="select-icon-inner"></div>
|
||||
</div>
|
||||
<label id={labelId}>
|
||||
{displayLabel}
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
disabled={disabled}
|
||||
id={inputId}
|
||||
aria-labelledby={labelId}
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded={`${isExpanded}`}
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
disabled={disabled}
|
||||
ref={(btnEl => this.buttonEl = btnEl)}
|
||||
>
|
||||
</button>
|
||||
ref={(focusEl => this.focusEl = focusEl)}
|
||||
></button>
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
|
||||
178
core/src/components/select/test/a11y/index.html
Normal file
178
core/src/components/select/test/a11y/index.html
Normal file
@@ -0,0 +1,178 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" dir="ltr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Select - a11y</title>
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet">
|
||||
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet">
|
||||
<script src="../../../../../scripts/testing/scripts.js"></script>
|
||||
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
|
||||
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ion-app>
|
||||
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Select - a11y</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="outer-content">
|
||||
<ion-list>
|
||||
<ion-list-header>
|
||||
<ion-label>
|
||||
Native Select
|
||||
</ion-label>
|
||||
</ion-list-header>
|
||||
|
||||
<div class="native-select-wrapper">
|
||||
<label for="pet-select">Favorite Pet</label>
|
||||
|
||||
<select name="pets" id="pet-select">
|
||||
<option value="dog">Dog</option>
|
||||
<option value="cat">Cat</option>
|
||||
<option value="hamster">Hamster</option>
|
||||
<option value="parrot">Parrot</option>
|
||||
<option value="spider">Spider</option>
|
||||
<option value="goldfish">Goldfish</option>
|
||||
</select>
|
||||
</div>
|
||||
</ion-list>
|
||||
|
||||
<ion-list>
|
||||
<ion-list-header>
|
||||
<ion-label>
|
||||
Custom Label Ionic Select
|
||||
</ion-label>
|
||||
</ion-list-header>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Favorite Pet</ion-label>
|
||||
|
||||
<ion-select>
|
||||
<ion-select-option value="dog">Dog</ion-select-option>
|
||||
<ion-select-option value="cat">Cat</ion-select-option>
|
||||
<ion-select-option value="hamster">Hamster</ion-select-option>
|
||||
<ion-select-option value="parrot">Parrot</ion-select-option>
|
||||
<ion-select-option value="spider">Spider</ion-select-option>
|
||||
<ion-select-option value="goldfish">Goldfish</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Favorite Pet</ion-label>
|
||||
|
||||
<ion-select placeholder="Select a Pet">
|
||||
<ion-select-option value="dog">Dog</ion-select-option>
|
||||
<ion-select-option value="cat">Cat</ion-select-option>
|
||||
<ion-select-option value="hamster">Hamster</ion-select-option>
|
||||
<ion-select-option value="parrot">Parrot</ion-select-option>
|
||||
<ion-select-option value="spider">Spider</ion-select-option>
|
||||
<ion-select-option value="goldfish">Goldfish</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Favorite Pet</ion-label>
|
||||
|
||||
<ion-select value="spider">
|
||||
<ion-select-option value="dog">Dog</ion-select-option>
|
||||
<ion-select-option value="cat">Cat</ion-select-option>
|
||||
<ion-select-option value="hamster">Hamster</ion-select-option>
|
||||
<ion-select-option value="parrot">Parrot</ion-select-option>
|
||||
<ion-select-option value="spider">Spider</ion-select-option>
|
||||
<ion-select-option value="goldfish">Goldfish</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
<ion-list>
|
||||
<ion-list-header>
|
||||
<ion-label>
|
||||
Alert Ionic Select
|
||||
</ion-label>
|
||||
</ion-list-header>
|
||||
|
||||
<ion-item>
|
||||
<label for="ionic-select">Favorite Pet</label>
|
||||
|
||||
<ion-select id="ionic-select">
|
||||
<ion-select-option value="dog">Dog</ion-select-option>
|
||||
<ion-select-option value="cat">Cat</ion-select-option>
|
||||
<ion-select-option value="hamster">Hamster</ion-select-option>
|
||||
<ion-select-option value="parrot">Parrot</ion-select-option>
|
||||
<ion-select-option value="spider">Spider</ion-select-option>
|
||||
<ion-select-option value="goldfish">Goldfish</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
<ion-list>
|
||||
<ion-list-header>
|
||||
<ion-label>
|
||||
Popover Ionic Select
|
||||
</ion-label>
|
||||
</ion-list-header>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Favorite Pet</ion-label>
|
||||
|
||||
<ion-select interface="popover">
|
||||
<ion-select-option value="dog">Dog</ion-select-option>
|
||||
<ion-select-option value="cat">Cat</ion-select-option>
|
||||
<ion-select-option value="hamster">Hamster</ion-select-option>
|
||||
<ion-select-option value="parrot">Parrot</ion-select-option>
|
||||
<ion-select-option value="spider">Spider</ion-select-option>
|
||||
<ion-select-option value="goldfish">Goldfish</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
<ion-list>
|
||||
<ion-list-header>
|
||||
<ion-label>
|
||||
Action Sheet Ionic Select
|
||||
</ion-label>
|
||||
</ion-list-header>
|
||||
|
||||
<ion-item>
|
||||
<ion-label>Favorite Pet</ion-label>
|
||||
|
||||
<ion-select interface="action-sheet">
|
||||
<ion-select-option value="dog">Dog</ion-select-option>
|
||||
<ion-select-option value="cat">Cat</ion-select-option>
|
||||
<ion-select-option value="hamster">Hamster</ion-select-option>
|
||||
<ion-select-option value="parrot">Parrot</ion-select-option>
|
||||
<ion-select-option value="spider">Spider</ion-select-option>
|
||||
<ion-select-option value="goldfish">Goldfish</ion-select-option>
|
||||
</ion-select>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
|
||||
|
||||
</ion-content>
|
||||
|
||||
<style>
|
||||
.native-select-wrapper {
|
||||
display: flex;
|
||||
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
label {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
||||
</script>
|
||||
</ion-app>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -736,7 +736,7 @@ export class SkeletonTextExample {
|
||||
</div>
|
||||
|
||||
<!-- Skeleton screen -->
|
||||
<div *ngIf="!data">
|
||||
<div v-if="!data">
|
||||
<div class="ion-padding custom-skeleton">
|
||||
<ion-skeleton-text animated style="width: 60%"></ion-skeleton-text>
|
||||
<ion-skeleton-text animated></ion-skeleton-text>
|
||||
@@ -819,12 +819,12 @@ import {
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonList,
|
||||
IonListheader,
|
||||
IonListHeader,
|
||||
IonSkeletonText,
|
||||
IonThumbnail
|
||||
} from '@ionic/vue';
|
||||
import { call } from 'ionicons/icons';
|
||||
import { defineComponent } from 'vue';
|
||||
import { defineComponent, ref } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -833,7 +833,7 @@ export default defineComponent({
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonList,
|
||||
IonListheader,
|
||||
IonListHeader,
|
||||
IonSkeletonText,
|
||||
IonThumbnail
|
||||
},
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Skeleton screen -->
|
||||
<div *ngIf="!data">
|
||||
<div v-if="!data">
|
||||
<div class="ion-padding custom-skeleton">
|
||||
<ion-skeleton-text animated style="width: 60%"></ion-skeleton-text>
|
||||
<ion-skeleton-text animated></ion-skeleton-text>
|
||||
@@ -145,12 +145,12 @@ import {
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonList,
|
||||
IonListheader,
|
||||
IonListHeader,
|
||||
IonSkeletonText,
|
||||
IonThumbnail
|
||||
} from '@ionic/vue';
|
||||
import { call } from 'ionicons/icons';
|
||||
import { defineComponent } from 'vue';
|
||||
import { defineComponent, ref } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@@ -159,7 +159,7 @@ export default defineComponent({
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonList,
|
||||
IonListheader,
|
||||
IonListHeader,
|
||||
IonSkeletonText,
|
||||
IonThumbnail
|
||||
},
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
The tab component is a child component of [tabs](../tabs). Each tab can contain a top level navigation stack for an app or a single view. An app can have many tabs, all with their own independent navigation.
|
||||
|
||||
See the [tabs documentation](../tabs/) for more details on configuring tabs.
|
||||
> Note: This component should only be used with vanilla or Stencil JavaScript projects. For Angular, React, and Vue apps you do not need to use `ion-tab` to declare your tab components.
|
||||
|
||||
See the [tabs documentation](../tabs/) for more details on configuring tabs.
|
||||
|
||||
<!-- Auto Generated Below -->
|
||||
|
||||
|
||||
@@ -307,59 +307,117 @@ will match the following tab:
|
||||
|
||||
### Vue
|
||||
|
||||
**Tabs.vue**
|
||||
```html
|
||||
<template>
|
||||
<!-- Listen to before and after tab change events -->
|
||||
<ion-tabs @IonTabsWillChange="beforeTabChange" @IonTabsDidChange="afterTabChange">
|
||||
<ion-tab tab="schedule">
|
||||
<Schedule />
|
||||
</ion-tab>
|
||||
|
||||
<!-- Match by "app.speakers" route name -->
|
||||
<ion-tab tab="speakers" :routes="'app.speakers'">
|
||||
<Speakers />
|
||||
</ion-tab>
|
||||
|
||||
<!-- Match by an array of route names -->
|
||||
<ion-tab tab="map" :routes="['app.map', 'app.other.route']">
|
||||
<Map />
|
||||
</ion-tab>
|
||||
|
||||
<!-- Get matched routes with a helper method -->
|
||||
<ion-tab tab="about" :routes="getMatchedRoutes">
|
||||
<About />
|
||||
</ion-tab>
|
||||
|
||||
<!-- Use v-slot:bottom with Vue ^2.6.0 -->
|
||||
<template slot="bottom">
|
||||
<ion-tab-bar>
|
||||
<ion-tab-button tab="schedule">
|
||||
<ion-icon name="calendar"></ion-icon>
|
||||
<ion-page>
|
||||
<ion-tabs @ionTabsWillChange="beforeTabChange" @ionTabsDidChange="afterTabChange">
|
||||
<ion-tab-bar slot="bottom">
|
||||
<ion-tab-button tab="schedule" href="/tabs/schedule">
|
||||
<ion-icon :icon="calendar"></ion-icon>
|
||||
<ion-label>Schedule</ion-label>
|
||||
<ion-badge>6</ion-badge>
|
||||
</ion-tab-button>
|
||||
|
||||
<!-- Provide a custom route to navigate to -->
|
||||
<ion-tab-button tab="speakers" :to="{ name: 'app.speakers' }">
|
||||
<ion-icon name="person-circle"></ion-icon>
|
||||
|
||||
<ion-tab-button tab="speakers" href="/tabs/speakers">
|
||||
<ion-icon :icon="personCircle"></ion-icon>
|
||||
<ion-label>Speakers</ion-label>
|
||||
</ion-tab-button>
|
||||
|
||||
<!-- Provide extra data to route -->
|
||||
<ion-tab-button tab="map" :to="{ name: 'app.map', params: { mode: 'dark' } }">
|
||||
<ion-icon name="map"></ion-icon>
|
||||
<ion-label>Map</ion-label>
|
||||
</ion-tab-button>
|
||||
|
||||
<!-- Provide custom click handler -->
|
||||
<ion-tab-button tab="about" @click="goToAboutTab">
|
||||
<ion-icon name="information-circle"></ion-icon>
|
||||
<ion-label>About</ion-label>
|
||||
</ion-tab-button>
|
||||
</ion-tab-bar>
|
||||
</template>
|
||||
</ion-tabs>
|
||||
</ion-tabs>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from 'vue';
|
||||
import {
|
||||
IonIcon,
|
||||
IonLabel,
|
||||
IonPage,
|
||||
IonTabBar,
|
||||
IonTabButton,
|
||||
IonTabs
|
||||
} from '@ionic/vue';
|
||||
import { calendar, personCircle } from 'ionicons/icons';
|
||||
|
||||
export default defineComponent({
|
||||
components: { IonIcon, IonLabel, IonPage, IonTabBar, IonTabButton, IonTabs },
|
||||
setup() {
|
||||
const beforeTabChange = () => {
|
||||
// do something before tab change
|
||||
}
|
||||
const afterTabChange = () => {
|
||||
// do something after tab change
|
||||
}
|
||||
return {
|
||||
calendar,
|
||||
personCircle,
|
||||
beforeTabChange,
|
||||
afterTabChange
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
**Schedule.vue**
|
||||
```html
|
||||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Schedule</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">Schedule Tab</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from 'vue';
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar
|
||||
} from '@ionic/vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: { IonContent, IonHeader, IonPage, IonTitle, IonToolbar }
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
**Speakers.vue**
|
||||
```html
|
||||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Speakers</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">Speakers Tab</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from 'vue';
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar
|
||||
} from '@ionic/vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: { IonContent, IonHeader, IonPage, IonTitle, IonToolbar }
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -1,54 +1,112 @@
|
||||
**Tabs.vue**
|
||||
```html
|
||||
<template>
|
||||
<!-- Listen to before and after tab change events -->
|
||||
<ion-tabs @IonTabsWillChange="beforeTabChange" @IonTabsDidChange="afterTabChange">
|
||||
<ion-tab tab="schedule">
|
||||
<Schedule />
|
||||
</ion-tab>
|
||||
|
||||
<!-- Match by "app.speakers" route name -->
|
||||
<ion-tab tab="speakers" :routes="'app.speakers'">
|
||||
<Speakers />
|
||||
</ion-tab>
|
||||
|
||||
<!-- Match by an array of route names -->
|
||||
<ion-tab tab="map" :routes="['app.map', 'app.other.route']">
|
||||
<Map />
|
||||
</ion-tab>
|
||||
|
||||
<!-- Get matched routes with a helper method -->
|
||||
<ion-tab tab="about" :routes="getMatchedRoutes">
|
||||
<About />
|
||||
</ion-tab>
|
||||
|
||||
<!-- Use v-slot:bottom with Vue ^2.6.0 -->
|
||||
<template slot="bottom">
|
||||
<ion-tab-bar>
|
||||
<ion-tab-button tab="schedule">
|
||||
<ion-icon name="calendar"></ion-icon>
|
||||
<ion-page>
|
||||
<ion-tabs @ionTabsWillChange="beforeTabChange" @ionTabsDidChange="afterTabChange">
|
||||
<ion-tab-bar slot="bottom">
|
||||
<ion-tab-button tab="schedule" href="/tabs/schedule">
|
||||
<ion-icon :icon="calendar"></ion-icon>
|
||||
<ion-label>Schedule</ion-label>
|
||||
<ion-badge>6</ion-badge>
|
||||
</ion-tab-button>
|
||||
|
||||
<!-- Provide a custom route to navigate to -->
|
||||
<ion-tab-button tab="speakers" :to="{ name: 'app.speakers' }">
|
||||
<ion-icon name="person-circle"></ion-icon>
|
||||
|
||||
<ion-tab-button tab="speakers" href="/tabs/speakers">
|
||||
<ion-icon :icon="personCircle"></ion-icon>
|
||||
<ion-label>Speakers</ion-label>
|
||||
</ion-tab-button>
|
||||
|
||||
<!-- Provide extra data to route -->
|
||||
<ion-tab-button tab="map" :to="{ name: 'app.map', params: { mode: 'dark' } }">
|
||||
<ion-icon name="map"></ion-icon>
|
||||
<ion-label>Map</ion-label>
|
||||
</ion-tab-button>
|
||||
|
||||
<!-- Provide custom click handler -->
|
||||
<ion-tab-button tab="about" @click="goToAboutTab">
|
||||
<ion-icon name="information-circle"></ion-icon>
|
||||
<ion-label>About</ion-label>
|
||||
</ion-tab-button>
|
||||
</ion-tab-bar>
|
||||
</template>
|
||||
</ion-tabs>
|
||||
</ion-tabs>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from 'vue';
|
||||
import {
|
||||
IonIcon,
|
||||
IonLabel,
|
||||
IonPage,
|
||||
IonTabBar,
|
||||
IonTabButton,
|
||||
IonTabs
|
||||
} from '@ionic/vue';
|
||||
import { calendar, personCircle } from 'ionicons/icons';
|
||||
|
||||
export default defineComponent({
|
||||
components: { IonIcon, IonLabel, IonPage, IonTabBar, IonTabButton, IonTabs },
|
||||
setup() {
|
||||
const beforeTabChange = () => {
|
||||
// do something before tab change
|
||||
}
|
||||
const afterTabChange = () => {
|
||||
// do something after tab change
|
||||
}
|
||||
return {
|
||||
calendar,
|
||||
personCircle,
|
||||
beforeTabChange,
|
||||
afterTabChange
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
**Schedule.vue**
|
||||
```html
|
||||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Schedule</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">Schedule Tab</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from 'vue';
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar
|
||||
} from '@ionic/vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: { IonContent, IonHeader, IonPage, IonTitle, IonToolbar }
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
**Speakers.vue**
|
||||
```html
|
||||
<template>
|
||||
<ion-page>
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title>Speakers</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding">Speakers Tab</ion-content>
|
||||
</ion-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from 'vue';
|
||||
import {
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonPage,
|
||||
IonTitle,
|
||||
IonToolbar
|
||||
} from '@ionic/vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: { IonContent, IonHeader, IonPage, IonTitle, IonToolbar }
|
||||
});
|
||||
</script>
|
||||
```
|
||||
@@ -253,11 +253,11 @@ export class TextareaExample {
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { IonItem, IonLabe, IonTextarea } from '@ionic/vue';
|
||||
import { IonItem, IonLabel, IonTextarea } from '@ionic/vue';
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: { IonItem, IonLabe, IonTextarea }
|
||||
components: { IonItem, IonLabel, IonTextarea }
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Build, Component, ComponentInterface, Element, Event, EventEmitter, Hos
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { Color, StyleEventDetail, TextareaChangeEventDetail } from '../../interface';
|
||||
import { debounceEvent, findItemLabel, raf } from '../../utils/helpers';
|
||||
import { debounceEvent, findItemLabel, inheritAttributes, raf } from '../../utils/helpers';
|
||||
import { createColorClasses } from '../../utils/theme';
|
||||
|
||||
/**
|
||||
@@ -22,6 +22,7 @@ export class Textarea implements ComponentInterface {
|
||||
private inputId = `ion-textarea-${textareaIds++}`;
|
||||
private didBlurAfterEdit = false;
|
||||
private textareaWrapper?: HTMLElement;
|
||||
private inheritedAttributes: { [k: string]: any } = {};
|
||||
|
||||
/**
|
||||
* This is required for a WebKit bug which requires us to
|
||||
@@ -212,6 +213,10 @@ export class Textarea implements ComponentInterface {
|
||||
}
|
||||
}
|
||||
|
||||
componentWillLoad() {
|
||||
this.inheritedAttributes = inheritAttributes(this.el, ['title']);
|
||||
}
|
||||
|
||||
componentDidLoad() {
|
||||
raf(() => this.runAutoGrow());
|
||||
}
|
||||
@@ -379,6 +384,7 @@ export class Textarea implements ComponentInterface {
|
||||
onBlur={this.onBlur}
|
||||
onFocus={this.onFocus}
|
||||
onKeyDown={this.onKeyDown}
|
||||
{...this.inheritedAttributes}
|
||||
>
|
||||
{value}
|
||||
</textarea>
|
||||
|
||||
@@ -38,11 +38,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { IonItem, IonLabe, IonTextarea } from '@ionic/vue';
|
||||
import { IonItem, IonLabel, IonTextarea } from '@ionic/vue';
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: { IonItem, IonLabe, IonTextarea }
|
||||
components: { IonItem, IonLabel, IonTextarea }
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<ion-toolbar>
|
||||
<ion-title>Toggle - Basic</ion-title>
|
||||
<ion-buttons slot="primary">
|
||||
<ion-toggle></ion-toggle>
|
||||
<ion-toggle aria-label="Toggle"></ion-toggle>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
@@ -86,7 +86,7 @@
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<ion-toggle id="standAloneChecked"></ion-toggle>
|
||||
<ion-toggle aria-label="Stand-alone toggle" id="standAloneChecked"></ion-toggle>
|
||||
Stand-alone toggle:
|
||||
<span id="standAloneCheckedSpan"></span>
|
||||
</p>
|
||||
|
||||
@@ -25,53 +25,53 @@
|
||||
|
||||
<ion-content class="ion-padding-horizontal">
|
||||
<h1>Default</h1>
|
||||
<ion-toggle></ion-toggle>
|
||||
<ion-toggle checked></ion-toggle>
|
||||
<ion-toggle color="danger"></ion-toggle>
|
||||
<ion-toggle color="danger" checked></ion-toggle>
|
||||
<ion-toggle color="tertiary" class="toggle-activated"></ion-toggle>
|
||||
<ion-toggle color="tertiary" checked class="toggle-activated"></ion-toggle>
|
||||
<ion-toggle aria-label="Default"></ion-toggle>
|
||||
<ion-toggle aria-label="Default" checked></ion-toggle>
|
||||
<ion-toggle aria-label="Default Danger" color="danger"></ion-toggle>
|
||||
<ion-toggle aria-label="Default Danger" color="danger" checked></ion-toggle>
|
||||
<ion-toggle aria-label="Default Tertiary" color="tertiary" class="toggle-activated"></ion-toggle>
|
||||
<ion-toggle aria-label="Default Tertiary Activated" color="tertiary" checked class="toggle-activated"></ion-toggle>
|
||||
|
||||
<h1>Custom Widths</h1>
|
||||
<ion-toggle color="secondary" class="width-small"></ion-toggle>
|
||||
<ion-toggle color="secondary" checked class="width-small"></ion-toggle>
|
||||
<ion-toggle color="secondary" class="width-large"></ion-toggle>
|
||||
<ion-toggle color="secondary" checked class="width-large"></ion-toggle>
|
||||
<ion-toggle color="tertiary" class="width-large toggle-activated"></ion-toggle>
|
||||
<ion-toggle color="tertiary" checked class="width-large toggle-activated"></ion-toggle>
|
||||
<ion-toggle aria-label="Secondary Small Width" color="secondary" class="width-small"></ion-toggle>
|
||||
<ion-toggle aria-label="Secondary Small Width" color="secondary" checked class="width-small"></ion-toggle>
|
||||
<ion-toggle aria-label="Secondary Large Width" color="secondary" class="width-large"></ion-toggle>
|
||||
<ion-toggle aria-label="Secondary Large Width" color="secondary" checked class="width-large"></ion-toggle>
|
||||
<ion-toggle aria-label="Tertiary Large Width Activated" color="tertiary" class="width-large toggle-activated"></ion-toggle>
|
||||
<ion-toggle aria-label="Tertiary Large Width Activated" color="tertiary" checked class="width-large toggle-activated"></ion-toggle>
|
||||
|
||||
<h1>Custom Heights</h1>
|
||||
<div style="display: flex; flex-flow: column; float: left;">
|
||||
<ion-toggle class="height-small"></ion-toggle>
|
||||
<ion-toggle checked class="height-small"></ion-toggle>
|
||||
<ion-toggle aria-label="Small Height" class="height-small"></ion-toggle>
|
||||
<ion-toggle aria-label="Small Height" checked class="height-small"></ion-toggle>
|
||||
</div>
|
||||
<ion-toggle class="height-large"></ion-toggle>
|
||||
<ion-toggle checked class="height-large"></ion-toggle>
|
||||
<ion-toggle class="handle-height-large"></ion-toggle>
|
||||
<ion-toggle checked class="handle-height-large"></ion-toggle>
|
||||
<ion-toggle checked class="handle-height-large toggle-activated"></ion-toggle>
|
||||
<ion-toggle aria-label="Large Height" class="height-large"></ion-toggle>
|
||||
<ion-toggle aria-label="Large Height" checked class="height-large"></ion-toggle>
|
||||
<ion-toggle aria-label="Large Height" class="handle-height-large"></ion-toggle>
|
||||
<ion-toggle aria-label="Large Height" checked class="handle-height-large"></ion-toggle>
|
||||
<ion-toggle aria-label="Large Height Activated" checked class="handle-height-large toggle-activated"></ion-toggle>
|
||||
|
||||
<h1>Dynamic Sizes</h1>
|
||||
<ion-toggle color="tertiary" class="dynamic-small width-small height-small"></ion-toggle>
|
||||
<ion-toggle color="tertiary" checked class="dynamic-small width-small height-small"></ion-toggle>
|
||||
<ion-toggle color="tertiary" class="dynamic-large width-large height-large"></ion-toggle>
|
||||
<ion-toggle color="tertiary" checked class="dynamic-large width-large height-large"></ion-toggle>
|
||||
<ion-toggle aria-label="Tertiary Small Width Small Height" color="tertiary" class="dynamic-small width-small height-small"></ion-toggle>
|
||||
<ion-toggle aria-label="Tertiary Small Width Small Height" color="tertiary" checked class="dynamic-small width-small height-small"></ion-toggle>
|
||||
<ion-toggle aria-label="Tertiary Large Width Large Height" color="tertiary" class="dynamic-large width-large height-large"></ion-toggle>
|
||||
<ion-toggle aria-label="Tertiary Large Width Large Height" color="tertiary" checked class="dynamic-large width-large height-large"></ion-toggle>
|
||||
|
||||
<h1>Complex Custom Toggles</h1>
|
||||
<ion-toggle mode="ios" class="all-custom"></ion-toggle>
|
||||
<ion-toggle mode="ios" checked class="all-custom"></ion-toggle>
|
||||
<ion-toggle aria-label="Custom" mode="ios" class="all-custom"></ion-toggle>
|
||||
<ion-toggle aria-label="Custom" mode="ios" checked class="all-custom"></ion-toggle>
|
||||
|
||||
<ion-toggle class="custom-overflow"></ion-toggle>
|
||||
<ion-toggle checked class="custom-overflow"></ion-toggle>
|
||||
<ion-toggle aria-label="Custom Overflow" class="custom-overflow"></ion-toggle>
|
||||
<ion-toggle aria-label="Custom Overflow" checked class="custom-overflow"></ion-toggle>
|
||||
|
||||
<ion-toggle mode="ios" color="dark" class="custom-spacing"></ion-toggle>
|
||||
<ion-toggle mode="ios" color="dark" checked class="custom-spacing"></ion-toggle>
|
||||
<ion-toggle aria-label="Custom Spacing iOS" mode="ios" color="dark" class="custom-spacing"></ion-toggle>
|
||||
<ion-toggle aria-label="Custom Spacing iOS" mode="ios" color="dark" checked class="custom-spacing"></ion-toggle>
|
||||
|
||||
<ion-toggle mode="md" color="dark" class="custom-spacing"></ion-toggle>
|
||||
<ion-toggle mode="md" color="dark" checked class="custom-spacing"></ion-toggle>
|
||||
<ion-toggle aria-label="Custom Spacing MD" mode="md" color="dark" class="custom-spacing"></ion-toggle>
|
||||
<ion-toggle aria-label="Custom Spacing MD" mode="md" color="dark" checked class="custom-spacing"></ion-toggle>
|
||||
|
||||
<ion-toggle mode="ios" class="icon-custom"></ion-toggle>
|
||||
<ion-toggle mode="ios" checked class="icon-custom"></ion-toggle>
|
||||
<ion-toggle aria-label="Custom Icon iOS" mode="ios" class="icon-custom"></ion-toggle>
|
||||
<ion-toggle aria-label="Custom Icon iOS" mode="ios" checked class="icon-custom"></ion-toggle>
|
||||
</ion-content>
|
||||
</ion-app>
|
||||
|
||||
|
||||
@@ -13,23 +13,23 @@
|
||||
|
||||
<body>
|
||||
<!-- Default -->
|
||||
<ion-toggle></ion-toggle>
|
||||
<ion-toggle aria-label="Default Toggle"></ion-toggle>
|
||||
|
||||
<!-- Colors -->
|
||||
<ion-toggle checked color="primary"></ion-toggle>
|
||||
<ion-toggle checked color="secondary"></ion-toggle>
|
||||
<ion-toggle checked color="tertiary"></ion-toggle>
|
||||
<ion-toggle checked color="success"></ion-toggle>
|
||||
<ion-toggle checked color="warning"></ion-toggle>
|
||||
<ion-toggle checked color="danger"></ion-toggle>
|
||||
<ion-toggle checked color="light"></ion-toggle>
|
||||
<ion-toggle checked color="medium"></ion-toggle>
|
||||
<ion-toggle checked color="dark"></ion-toggle>
|
||||
<ion-toggle checked class="custom"></ion-toggle>
|
||||
<ion-toggle aria-label="Primary Toggle" checked color="primary"></ion-toggle>
|
||||
<ion-toggle aria-label="Secondary Toggle" checked color="secondary"></ion-toggle>
|
||||
<ion-toggle aria-label="Tertiary Toggle" checked color="tertiary"></ion-toggle>
|
||||
<ion-toggle aria-label="Success Toggle" checked color="success"></ion-toggle>
|
||||
<ion-toggle aria-label="Warning Toggle" checked color="warning"></ion-toggle>
|
||||
<ion-toggle aria-label="Danger Toggle" checked color="danger"></ion-toggle>
|
||||
<ion-toggle aria-label="Light Toggle" checked color="light"></ion-toggle>
|
||||
<ion-toggle aria-label="Medium Toggle" checked color="medium"></ion-toggle>
|
||||
<ion-toggle aria-label="Dark Toggle" checked color="dark"></ion-toggle>
|
||||
<ion-toggle aria-label="Custom Toggle" checked class="custom"></ion-toggle>
|
||||
|
||||
<!-- Disabled -->
|
||||
<ion-toggle checked disabled></ion-toggle>
|
||||
<ion-toggle checked disabled color="secondary"></ion-toggle>
|
||||
<ion-toggle aria-label="Disabled Default Toggle" checked disabled></ion-toggle>
|
||||
<ion-toggle aria-label="Disabled Secondary Toggle" checked disabled color="secondary"></ion-toggle>
|
||||
|
||||
<style>
|
||||
.custom {
|
||||
|
||||
@@ -45,8 +45,18 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
button {
|
||||
label {
|
||||
@include input-cover();
|
||||
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
input {
|
||||
@include visually-hidden();
|
||||
}
|
||||
|
||||
// Toggle Background Track: Unchecked
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Component, ComponentInterface, Element, Event, EventEmitter, Host, Prop
|
||||
|
||||
import { getIonMode } from '../../global/ionic-global';
|
||||
import { Color, Gesture, GestureDetail, StyleEventDetail, ToggleChangeEventDetail } from '../../interface';
|
||||
import { findItemLabel, renderHiddenInput } from '../../utils/helpers';
|
||||
import { getAriaLabel, renderHiddenInput } from '../../utils/helpers';
|
||||
import { hapticSelection } from '../../utils/native/haptic';
|
||||
import { createColorClasses, hostContext } from '../../utils/theme';
|
||||
|
||||
@@ -24,7 +24,7 @@ export class Toggle implements ComponentInterface {
|
||||
|
||||
private inputId = `ion-tg-${toggleIds++}`;
|
||||
private gesture?: Gesture;
|
||||
private buttonEl?: HTMLElement;
|
||||
private focusEl?: HTMLElement;
|
||||
private lastDrag = 0;
|
||||
|
||||
@Element() el!: HTMLElement;
|
||||
@@ -156,12 +156,14 @@ export class Toggle implements ComponentInterface {
|
||||
}
|
||||
|
||||
private setFocus() {
|
||||
if (this.buttonEl) {
|
||||
this.buttonEl.focus();
|
||||
if (this.focusEl) {
|
||||
this.focusEl.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private onClick = () => {
|
||||
private onClick = (ev: Event) => {
|
||||
ev.preventDefault();
|
||||
|
||||
if (this.lastDrag + 300 < Date.now()) {
|
||||
this.checked = !this.checked;
|
||||
}
|
||||
@@ -176,23 +178,20 @@ export class Toggle implements ComponentInterface {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { inputId, disabled, checked, activated, color, el } = this;
|
||||
const { activated, color, checked, disabled, el, inputId, name } = this;
|
||||
const mode = getIonMode(this);
|
||||
const labelId = inputId + '-lbl';
|
||||
const label = findItemLabel(el);
|
||||
const { label, labelId, labelText } = getAriaLabel(el, inputId);
|
||||
const value = this.getValue();
|
||||
if (label) {
|
||||
label.id = labelId;
|
||||
}
|
||||
renderHiddenInput(true, el, this.name, (checked ? value : ''), disabled);
|
||||
|
||||
renderHiddenInput(true, el, name, (checked ? value : ''), disabled);
|
||||
|
||||
return (
|
||||
<Host
|
||||
onClick={this.onClick}
|
||||
role="checkbox"
|
||||
aria-disabled={disabled ? 'true' : null}
|
||||
aria-labelledby={label ? labelId : null}
|
||||
aria-checked={`${checked}`}
|
||||
aria-labelledby={labelId}
|
||||
aria-hidden={disabled ? 'true' : null}
|
||||
role="switch"
|
||||
class={createColorClasses(color, {
|
||||
[mode]: true,
|
||||
'in-item': hostContext('ion-item', el),
|
||||
@@ -207,15 +206,19 @@ export class Toggle implements ComponentInterface {
|
||||
<div class="toggle-inner" part="handle" />
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onFocus={this.onFocus}
|
||||
onBlur={this.onBlur}
|
||||
<label htmlFor={inputId}>
|
||||
{labelText}
|
||||
</label>
|
||||
<input
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
aria-checked={`${checked}`}
|
||||
disabled={disabled}
|
||||
ref={btnEl => this.buttonEl = btnEl}
|
||||
aria-hidden="true"
|
||||
>
|
||||
</button>
|
||||
id={inputId}
|
||||
onFocus={() => this.onFocus()}
|
||||
onBlur={() => this.onBlur()}
|
||||
ref={focusEl => this.focusEl = focusEl}
|
||||
/>
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,31 @@
|
||||
}
|
||||
}
|
||||
|
||||
@mixin visually-hidden() {
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
border: 0;
|
||||
outline: 0;
|
||||
clip: rect(0 0 0 0);
|
||||
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
}
|
||||
|
||||
@mixin text-inherit() {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
@@ -509,4 +534,4 @@
|
||||
transform: $rtl-translate $extra;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,20 @@ interface HandlerRegister {
|
||||
id: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* When hardwareBackButton: false in config,
|
||||
* we need to make sure we also block the default
|
||||
* webview behavior. If we don't then it will be
|
||||
* possible for users to navigate backward while
|
||||
* an overlay is still open. Additionally, it will
|
||||
* give the appearance that the hardwareBackButton
|
||||
* config is not working as the page transition
|
||||
* will still happen.
|
||||
*/
|
||||
export const blockHardwareBackButton = () => {
|
||||
document.addEventListener('backbutton', () => {}); // tslint:disable-line
|
||||
}
|
||||
|
||||
export const startHardwareBackButton = () => {
|
||||
const doc = document;
|
||||
|
||||
|
||||
@@ -5,6 +5,32 @@ import { Side } from '../interface';
|
||||
declare const __zone_symbol__requestAnimationFrame: any;
|
||||
declare const requestAnimationFrame: any;
|
||||
|
||||
/**
|
||||
* Elements inside of web components sometimes need to inherit global attributes
|
||||
* set on the host. For example, the inner input in `ion-input` should inherit
|
||||
* the `title` attribute that developers set directly on `ion-input`. This
|
||||
* helper function should be called in componentWillLoad and assigned to a variable
|
||||
* that is later used in the render function.
|
||||
*
|
||||
* This does not need to be reactive as changing attributes on the host element
|
||||
* does not trigger a re-render.
|
||||
*/
|
||||
export const inheritAttributes = (el: HTMLElement, attributes: string[] = []) => {
|
||||
const attributeObject: { [k: string]: any } = {};
|
||||
|
||||
attributes.forEach(attr => {
|
||||
if (el.hasAttribute(attr)) {
|
||||
const value = el.getAttribute(attr);
|
||||
if (value !== null) {
|
||||
attributeObject[attr] = el.getAttribute(attr);
|
||||
}
|
||||
el.removeAttribute(attr);
|
||||
}
|
||||
});
|
||||
|
||||
return attributeObject;
|
||||
}
|
||||
|
||||
export const addEventListener = (el: any, eventName: string, callback: any, opts?: any) => {
|
||||
if (typeof (window as any) !== 'undefined') {
|
||||
const win = window as any;
|
||||
@@ -70,7 +96,7 @@ export const hasShadowDom = (el: HTMLElement) => {
|
||||
return !!el.shadowRoot && !!(el as any).attachShadow;
|
||||
};
|
||||
|
||||
export const findItemLabel = (componentEl: HTMLElement) => {
|
||||
export const findItemLabel = (componentEl: HTMLElement): HTMLIonLabelElement | null => {
|
||||
const itemEl = componentEl.closest('ion-item');
|
||||
if (itemEl) {
|
||||
return itemEl.querySelector('ion-label');
|
||||
@@ -78,6 +104,72 @@ export const findItemLabel = (componentEl: HTMLElement) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* This method is used for Ionic's input components that use Shadow DOM. In
|
||||
* order to properly label the inputs to work with screen readers, we need
|
||||
* to get the text content of the label outside of the shadow root and pass
|
||||
* it to the input inside of the shadow root.
|
||||
*
|
||||
* Referencing label elements by id from outside of the component is
|
||||
* impossible due to the shadow boundary, read more here:
|
||||
* https://developer.salesforce.com/blogs/2020/01/accessibility-for-web-components.html
|
||||
*
|
||||
* @param componentEl The shadow element that needs the aria label
|
||||
* @param inputId The unique identifier for the input
|
||||
*/
|
||||
export const getAriaLabel = (componentEl: HTMLElement, inputId: string): { label: Element | null, labelId: string, labelText: string | null | undefined } => {
|
||||
let labelText;
|
||||
|
||||
// If the user provides their own label via the aria-labelledby attr
|
||||
// we should use that instead of looking for an ion-label
|
||||
const labelledBy = componentEl.getAttribute('aria-labelledby');
|
||||
|
||||
// Grab the id off of the component in case they are using
|
||||
// a custom label using the label element
|
||||
const componentId = componentEl.id;
|
||||
|
||||
let labelId = labelledBy !== null && labelledBy.trim() !== ''
|
||||
? labelledBy
|
||||
: inputId + '-lbl';
|
||||
|
||||
let label = labelledBy !== null && labelledBy.trim() !== ''
|
||||
? document.querySelector(`#${labelledBy}`)
|
||||
: findItemLabel(componentEl);
|
||||
|
||||
if (label) {
|
||||
if (labelledBy === null) {
|
||||
label.id = labelId;
|
||||
}
|
||||
|
||||
labelText = label.textContent;
|
||||
label.setAttribute('aria-hidden', 'true');
|
||||
|
||||
// if there is no label, check to see if the user has provided
|
||||
// one by setting an id on the component and using the label element
|
||||
} else if (componentId.trim() !== '') {
|
||||
label = document.querySelector(`label[for=${componentId}]`);
|
||||
|
||||
if (label) {
|
||||
label.id = labelId = `${componentId}-lbl`;
|
||||
labelText = label.textContent;
|
||||
}
|
||||
}
|
||||
|
||||
return { label, labelId, labelText };
|
||||
};
|
||||
|
||||
/**
|
||||
* This method is used to add a hidden input to a host element that contains
|
||||
* a Shadow DOM. It does not add the input inside of the Shadow root which
|
||||
* allows it to be picked up inside of forms. It should contain the same
|
||||
* values as the host element.
|
||||
*
|
||||
* @param always Add a hidden input even if the container does not use Shadow
|
||||
* @param container The element where the input will be added
|
||||
* @param name The name of the input
|
||||
* @param value The value of the input
|
||||
* @param disabled If true, the input is disabled
|
||||
*/
|
||||
export const renderHiddenInput = (always: boolean, container: HTMLElement, name: string, value: string | undefined | null, disabled: boolean) => {
|
||||
if (always || hasShadowDom(container)) {
|
||||
let input = container.querySelector('input.aux-input') as HTMLInputElement | null;
|
||||
|
||||
39
core/src/utils/test/attributes.spec.ts
Normal file
39
core/src/utils/test/attributes.spec.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { inheritAttributes } from '../helpers';
|
||||
|
||||
describe('inheritAttributes()', () => {
|
||||
it('should create an attribute inheritance object', () => {
|
||||
const el = document.createElement('div');
|
||||
el.setAttribute('tabindex', '20');
|
||||
el.setAttribute('title', 'myTitle');
|
||||
|
||||
const attributeObject = inheritAttributes(el, ['tabindex', 'title']);
|
||||
|
||||
expect(attributeObject).toEqual({
|
||||
tabindex: '20',
|
||||
title: 'myTitle'
|
||||
});
|
||||
});
|
||||
|
||||
it('should not inherit attributes that are not defined on the element', () => {
|
||||
const el = document.createElement('div');
|
||||
el.setAttribute('tabindex', '20');
|
||||
|
||||
const attributeObject = inheritAttributes(el, ['tabindex', 'title']);
|
||||
|
||||
expect(attributeObject).toEqual({
|
||||
tabindex: '20'
|
||||
});
|
||||
});
|
||||
|
||||
it('should not inherit attributes that are not defined on the input array', () => {
|
||||
const el = document.createElement('div');
|
||||
el.setAttribute('tabindex', '20');
|
||||
el.setAttribute('title', 'myTitle');
|
||||
|
||||
const attributeObject = inheritAttributes(el, ['title']);
|
||||
|
||||
expect(attributeObject).toEqual({
|
||||
title: 'myTitle'
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -6,6 +6,7 @@ import { vueOutputTarget } from '@stencil/vue-output-target';
|
||||
import { apiSpecGenerator } from './scripts/api-spec-generator';
|
||||
|
||||
export const config: Config = {
|
||||
autoprefixCss: true,
|
||||
namespace: 'Ionic',
|
||||
bundles: [
|
||||
{ components: ['ion-action-sheet'] },
|
||||
@@ -170,6 +171,7 @@ export const config: Config = {
|
||||
]
|
||||
}
|
||||
],
|
||||
buildEs5: 'prod',
|
||||
extras: {
|
||||
cssVarsShim: true,
|
||||
dynamicImportShim: true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/docs",
|
||||
"version": "5.4.2",
|
||||
"version": "5.5.2",
|
||||
"description": "Pre-packaged API documentation for the Ionic docs.",
|
||||
"main": "core.json",
|
||||
"types": "core.d.ts",
|
||||
|
||||
10
packages/angular-server/package-lock.json
generated
10
packages/angular-server/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@ionic/angular-server",
|
||||
"version": "5.4.2",
|
||||
"version": "5.5.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@ionic/angular-server",
|
||||
"version": "5.4.2",
|
||||
"version": "5.5.2",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@angular/animations": "8.2.13",
|
||||
@@ -32,7 +32,7 @@
|
||||
}
|
||||
},
|
||||
"../../core": {
|
||||
"version": "5.4.1",
|
||||
"version": "5.5.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -44,7 +44,7 @@
|
||||
"@rollup/plugin-virtual": "^2.0.3",
|
||||
"@stencil/core": "2.1.2",
|
||||
"@stencil/sass": "1.3.2",
|
||||
"@stencil/vue-output-target": "0.1.8",
|
||||
"@stencil/vue-output-target": "0.2.2",
|
||||
"@types/jest": "^26.0.10",
|
||||
"@types/node": "^14.6.0",
|
||||
"@types/puppeteer": "3.0.1",
|
||||
@@ -5440,7 +5440,7 @@
|
||||
"@rollup/plugin-virtual": "^2.0.3",
|
||||
"@stencil/core": "2.1.2",
|
||||
"@stencil/sass": "1.3.2",
|
||||
"@stencil/vue-output-target": "0.1.8",
|
||||
"@stencil/vue-output-target": "0.2.2",
|
||||
"@types/jest": "^26.0.10",
|
||||
"@types/node": "^14.6.0",
|
||||
"@types/puppeteer": "3.0.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/angular-server",
|
||||
"version": "5.4.2",
|
||||
"version": "5.5.2",
|
||||
"description": "Angular SSR Module for Ionic",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
@@ -49,7 +49,7 @@
|
||||
"@angular/core": "8.2.13",
|
||||
"@angular/platform-browser": "8.2.13",
|
||||
"@angular/platform-server": "8.2.13",
|
||||
"@ionic/core": "5.4.2",
|
||||
"@ionic/core": "5.5.2",
|
||||
"ng-packagr": "5.7.1",
|
||||
"tslint": "^5.12.1",
|
||||
"tslint-ionic-rules": "0.0.21",
|
||||
|
||||
4
packages/react-router/.prettierignore
Normal file
4
packages/react-router/.prettierignore
Normal file
@@ -0,0 +1,4 @@
|
||||
dist
|
||||
dist-transpiled
|
||||
*.md
|
||||
build
|
||||
8
packages/react-router/.prettierrc.json
Normal file
8
packages/react-router/.prettierrc.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"jsxSingleQuote": false,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
2
packages/react-router/empty-module.js
vendored
2
packages/react-router/empty-module.js
vendored
@@ -1 +1 @@
|
||||
module.exports = ''
|
||||
module.exports = '';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/react-router",
|
||||
"version": "5.4.2",
|
||||
"version": "5.5.2",
|
||||
"description": "React Router wrapper for @ionic/react",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
@@ -39,28 +39,29 @@
|
||||
"tslib": "*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ionic/core": "5.4.2",
|
||||
"@ionic/react": "5.4.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"@ionic/core": "5.5.2",
|
||||
"@ionic/react": "5.5.2",
|
||||
"react": ">=16.8.6",
|
||||
"react-dom": ">=16.8.6",
|
||||
"react-router": "^5.0.1",
|
||||
"react-router-dom": "^5.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ionic/core": "5.4.2",
|
||||
"@ionic/react": "5.4.2",
|
||||
"@ionic/core": "5.5.2",
|
||||
"@ionic/react": "5.5.2",
|
||||
"@rollup/plugin-node-resolve": "^8.1.0",
|
||||
"@testing-library/jest-dom": "^5.11.0",
|
||||
"@testing-library/react": "^10.4.9",
|
||||
"@testing-library/jest-dom": "^5.11.6",
|
||||
"@testing-library/react": "^11.2.2",
|
||||
"@testing-library/user-event": "^12.0.11",
|
||||
"@types/jest": "^26.0.3",
|
||||
"@types/jest": "^26.0.15",
|
||||
"@types/node": "^14.0.14",
|
||||
"@types/react": "^16.9.2",
|
||||
"@types/react": "16.14.0",
|
||||
"@types/react-dom": "^16.9.0",
|
||||
"@types/react-router": "^5.0.3",
|
||||
"@types/react-router-dom": "^5.1.5",
|
||||
"jest": "^26.4.1",
|
||||
"jest": "^26.6.3",
|
||||
"np": "^6.4.0",
|
||||
"prettier": "^2.2.0",
|
||||
"react": "^16.9.0",
|
||||
"react-dom": "^16.9.0",
|
||||
"react-router": "^5.0.1",
|
||||
@@ -68,7 +69,7 @@
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^2.26.4",
|
||||
"rollup-plugin-sourcemaps": "^0.6.2",
|
||||
"ts-jest": "^26.1.1",
|
||||
"ts-jest": "^26.4.4",
|
||||
"tslint": "^6.1.2",
|
||||
"tslint-ionic-rules": "0.0.21",
|
||||
"tslint-react": "^5.0.0",
|
||||
|
||||
@@ -7,17 +7,14 @@ export default {
|
||||
{
|
||||
file: 'dist/index.esm.js',
|
||||
format: 'es',
|
||||
sourcemap: true
|
||||
sourcemap: true,
|
||||
},
|
||||
{
|
||||
file: 'dist/index.js',
|
||||
format: 'commonjs',
|
||||
sourcemap: true
|
||||
}
|
||||
sourcemap: true,
|
||||
},
|
||||
],
|
||||
external: (id) => !/^(\.|\/)/.test(id),
|
||||
plugins: [
|
||||
resolve(),
|
||||
sourcemaps(),
|
||||
]
|
||||
plugins: [resolve(), sourcemaps()],
|
||||
};
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
|
||||
window.matchMedia = window.matchMedia || function() {
|
||||
return {
|
||||
matches: false,
|
||||
addListener() { },
|
||||
removeListener() { }
|
||||
};
|
||||
} as any;
|
||||
window.matchMedia =
|
||||
window.matchMedia ||
|
||||
(function () {
|
||||
return {
|
||||
matches: false,
|
||||
addListener() {},
|
||||
removeListener() {},
|
||||
};
|
||||
} as any);
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { Action as HistoryAction, History, Location as HistoryLocation, createHashHistory as createHistory } from 'history';
|
||||
import {
|
||||
Action as HistoryAction,
|
||||
History,
|
||||
Location as HistoryLocation,
|
||||
createHashHistory as createHistory,
|
||||
} from 'history';
|
||||
import React from 'react';
|
||||
import { BrowserRouterProps, Router } from 'react-router-dom';
|
||||
|
||||
@@ -10,7 +15,7 @@ interface IonReactHashRouterProps extends BrowserRouterProps {
|
||||
|
||||
export class IonReactHashRouter extends React.Component<IonReactHashRouterProps> {
|
||||
history: History;
|
||||
historyListenHandler?: ((location: HistoryLocation, action: HistoryAction) => void);
|
||||
historyListenHandler?: (location: HistoryLocation, action: HistoryAction) => void;
|
||||
|
||||
constructor(props: IonReactHashRouterProps) {
|
||||
super(props);
|
||||
|
||||
@@ -10,7 +10,7 @@ interface IonReactMemoryRouterProps extends MemoryRouterProps {
|
||||
|
||||
export class IonReactMemoryRouter extends React.Component<IonReactMemoryRouterProps> {
|
||||
history: MemoryHistory;
|
||||
historyListenHandler?: ((location: HistoryLocation, action: HistoryAction) => void);
|
||||
historyListenHandler?: (location: HistoryLocation, action: HistoryAction) => void;
|
||||
|
||||
constructor(props: IonReactMemoryRouterProps) {
|
||||
super(props);
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { Action as HistoryAction, History, Location as HistoryLocation, createBrowserHistory as createHistory } from 'history';
|
||||
import {
|
||||
Action as HistoryAction,
|
||||
History,
|
||||
Location as HistoryLocation,
|
||||
createBrowserHistory as createHistory,
|
||||
} from 'history';
|
||||
import React from 'react';
|
||||
import { BrowserRouterProps, Router } from 'react-router-dom';
|
||||
|
||||
@@ -9,8 +14,7 @@ interface IonReactRouterProps extends BrowserRouterProps {
|
||||
}
|
||||
|
||||
export class IonReactRouter extends React.Component<IonReactRouterProps> {
|
||||
|
||||
historyListenHandler?: ((location: HistoryLocation, action: HistoryAction) => void);
|
||||
historyListenHandler?: (location: HistoryLocation, action: HistoryAction) => void;
|
||||
history: History;
|
||||
|
||||
constructor(props: IonReactRouterProps) {
|
||||
|
||||
@@ -5,7 +5,12 @@ import { Route } from 'react-router';
|
||||
export class IonRouteInner extends React.PureComponent<IonRouteProps> {
|
||||
render() {
|
||||
return (
|
||||
<Route path={this.props.path} exact={this.props.exact} render={this.props.render} computedMatch={(this.props as any).computedMatch} />
|
||||
<Route
|
||||
path={this.props.path}
|
||||
exact={this.props.exact}
|
||||
render={this.props.render}
|
||||
computedMatch={(this.props as any).computedMatch}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
RouterDirection,
|
||||
ViewItem,
|
||||
generateId,
|
||||
getConfig
|
||||
getConfig,
|
||||
} from '@ionic/react';
|
||||
import { Action as HistoryAction, Location as HistoryLocation } from 'history';
|
||||
import React from 'react';
|
||||
@@ -21,11 +21,13 @@ import StackManager from './StackManager';
|
||||
|
||||
export interface LocationState {
|
||||
direction?: RouterDirection;
|
||||
routerOptions?: { as?: string, unmount?: boolean; };
|
||||
routerOptions?: { as?: string; unmount?: boolean };
|
||||
}
|
||||
|
||||
interface IonRouteProps extends RouteComponentProps<{}, {}, LocationState> {
|
||||
registerHistoryListener: (cb: (location: HistoryLocation<any>, action: HistoryAction) => void) => void;
|
||||
registerHistoryListener: (
|
||||
cb: (location: HistoryLocation<any>, action: HistoryAction) => void
|
||||
) => void;
|
||||
}
|
||||
|
||||
interface IonRouteState {
|
||||
@@ -48,7 +50,7 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
|
||||
findViewItemByRouteInfo: this.viewStack.findViewItemByRouteInfo,
|
||||
findLeavingViewItemByRouteInfo: this.viewStack.findLeavingViewItemByRouteInfo,
|
||||
addViewItem: this.viewStack.add,
|
||||
unMountViewItem: this.viewStack.remove
|
||||
unMountViewItem: this.viewStack.remove,
|
||||
};
|
||||
|
||||
constructor(props: IonRouteProps) {
|
||||
@@ -57,19 +59,20 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
|
||||
const routeInfo = {
|
||||
id: generateId('routeInfo'),
|
||||
pathname: this.props.location.pathname,
|
||||
search: this.props.location.search
|
||||
search: this.props.location.search,
|
||||
};
|
||||
|
||||
this.locationHistory.add(routeInfo);
|
||||
this.handleChangeTab = this.handleChangeTab.bind(this);
|
||||
this.handleResetTab = this.handleResetTab.bind(this);
|
||||
this.handleNativeBack = this.handleNativeBack.bind(this);
|
||||
this.handleNavigate = this.handleNavigate.bind(this);
|
||||
this.handleNavigateBack = this.handleNavigateBack.bind(this);
|
||||
this.props.registerHistoryListener(this.handleHistoryChange.bind(this));
|
||||
this.handleSetCurrentTab = this.handleSetCurrentTab.bind(this);
|
||||
|
||||
this.state = {
|
||||
routeInfo
|
||||
routeInfo,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -111,7 +114,7 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
|
||||
this.incomingRouteParams = {
|
||||
routeAction: 'replace',
|
||||
routeDirection: 'none',
|
||||
tab: this.currentTab // TODO this isn't legit if replacing to a page that is not in the tabs
|
||||
tab: this.currentTab, // TODO this isn't legit if replacing to a page that is not in the tabs
|
||||
};
|
||||
}
|
||||
if (action === 'POP') {
|
||||
@@ -124,7 +127,7 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
|
||||
this.incomingRouteParams = {
|
||||
routeAction: 'pop',
|
||||
routeDirection: direction,
|
||||
tab: this.currentTab
|
||||
tab: this.currentTab,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -133,7 +136,7 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
|
||||
routeAction: 'push',
|
||||
routeDirection: location.state?.direction || 'forward',
|
||||
routeOptions: location.state?.routerOptions,
|
||||
tab: this.currentTab
|
||||
tab: this.currentTab,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -142,12 +145,14 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
|
||||
|
||||
if (this.incomingRouteParams?.id) {
|
||||
routeInfo = {
|
||||
...this.incomingRouteParams as RouteInfo,
|
||||
lastPathname: leavingLocationInfo.pathname
|
||||
...(this.incomingRouteParams as RouteInfo),
|
||||
lastPathname: leavingLocationInfo.pathname,
|
||||
};
|
||||
this.locationHistory.add(routeInfo);
|
||||
} else {
|
||||
const isPushed = (this.incomingRouteParams.routeAction === 'push' && this.incomingRouteParams.routeDirection === 'forward');
|
||||
const isPushed =
|
||||
this.incomingRouteParams.routeAction === 'push' &&
|
||||
this.incomingRouteParams.routeDirection === 'forward';
|
||||
routeInfo = {
|
||||
id: generateId('routeInfo'),
|
||||
...this.incomingRouteParams,
|
||||
@@ -155,7 +160,7 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
|
||||
pathname: location.pathname,
|
||||
search: location.search,
|
||||
params: this.props.match.params,
|
||||
prevRouteLastPathname: leavingLocationInfo.lastPathname
|
||||
prevRouteLastPathname: leavingLocationInfo.lastPathname,
|
||||
};
|
||||
if (isPushed) {
|
||||
routeInfo.tab = leavingLocationInfo.tab;
|
||||
@@ -181,20 +186,31 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
|
||||
}
|
||||
|
||||
this.setState({
|
||||
routeInfo
|
||||
routeInfo,
|
||||
});
|
||||
}
|
||||
|
||||
this.incomingRouteParams = undefined;
|
||||
}
|
||||
|
||||
handleNavigate(path: string, routeAction: RouteAction, routeDirection?: RouterDirection, routeAnimation?: AnimationBuilder, routeOptions?: any, tab?: string) {
|
||||
handleNativeBack() {
|
||||
this.props.history.goBack();
|
||||
}
|
||||
|
||||
handleNavigate(
|
||||
path: string,
|
||||
routeAction: RouteAction,
|
||||
routeDirection?: RouterDirection,
|
||||
routeAnimation?: AnimationBuilder,
|
||||
routeOptions?: any,
|
||||
tab?: string
|
||||
) {
|
||||
this.incomingRouteParams = Object.assign(this.incomingRouteParams || {}, {
|
||||
routeAction,
|
||||
routeDirection,
|
||||
routeOptions,
|
||||
routeAnimation,
|
||||
tab
|
||||
tab,
|
||||
});
|
||||
|
||||
if (routeAction === 'push') {
|
||||
@@ -211,7 +227,12 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
|
||||
if (routeInfo && routeInfo.pushedByRoute) {
|
||||
const prevInfo = this.locationHistory.findLastLocation(routeInfo);
|
||||
if (prevInfo) {
|
||||
this.incomingRouteParams = { ...prevInfo, routeAction: 'pop', routeDirection: 'back', routeAnimation: routeAnimation || routeInfo.routeAnimation };
|
||||
this.incomingRouteParams = {
|
||||
...prevInfo,
|
||||
routeAction: 'pop',
|
||||
routeDirection: 'back',
|
||||
routeAnimation: routeAnimation || routeInfo.routeAnimation,
|
||||
};
|
||||
if (routeInfo.lastPathname === routeInfo.pushedByRoute) {
|
||||
this.props.history.goBack();
|
||||
} else {
|
||||
@@ -247,14 +268,13 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<RouteManagerContext.Provider
|
||||
value={this.routeMangerContextState}
|
||||
>
|
||||
<RouteManagerContext.Provider value={this.routeMangerContextState}>
|
||||
<NavManager
|
||||
ionRoute={IonRouteInner}
|
||||
ionRedirect={{}}
|
||||
stackManager={StackManager}
|
||||
routeInfo={this.state.routeInfo!}
|
||||
onNativeBack={this.handleNativeBack}
|
||||
onNavigateBack={this.handleNavigateBack}
|
||||
onNavigate={this.handleNavigate}
|
||||
onSetCurrentTab={this.handleSetCurrentTab}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import { IonRoute, RouteInfo, ViewItem, ViewLifeCycleManager, ViewStacks, generateId } from '@ionic/react';
|
||||
import {
|
||||
IonRoute,
|
||||
RouteInfo,
|
||||
ViewItem,
|
||||
ViewLifeCycleManager,
|
||||
ViewStacks,
|
||||
generateId,
|
||||
} from '@ionic/react';
|
||||
import React from 'react';
|
||||
import { matchPath } from 'react-router';
|
||||
|
||||
export class ReactRouterViewStack extends ViewStacks {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.createViewItem = this.createViewItem.bind(this);
|
||||
@@ -13,20 +19,25 @@ export class ReactRouterViewStack extends ViewStacks {
|
||||
this.findViewItemByPathname = this.findViewItemByPathname.bind(this);
|
||||
}
|
||||
|
||||
createViewItem(outletId: string, reactElement: React.ReactElement, routeInfo: RouteInfo, page?: HTMLElement) {
|
||||
createViewItem(
|
||||
outletId: string,
|
||||
reactElement: React.ReactElement,
|
||||
routeInfo: RouteInfo,
|
||||
page?: HTMLElement
|
||||
) {
|
||||
const viewItem: ViewItem = {
|
||||
id: generateId('viewItem'),
|
||||
outletId,
|
||||
ionPageElement: page,
|
||||
reactElement,
|
||||
mount: true,
|
||||
ionRoute: false
|
||||
ionRoute: false,
|
||||
};
|
||||
|
||||
const matchProps = {
|
||||
exact: reactElement.props.exact,
|
||||
path: reactElement.props.path || reactElement.props.from,
|
||||
component: reactElement.props.component
|
||||
component: reactElement.props.component,
|
||||
};
|
||||
|
||||
const match = matchPath(routeInfo.pathname, matchProps);
|
||||
@@ -38,7 +49,7 @@ export class ReactRouterViewStack extends ViewStacks {
|
||||
|
||||
viewItem.routeData = {
|
||||
match,
|
||||
childProps: reactElement.props
|
||||
childProps: reactElement.props,
|
||||
};
|
||||
|
||||
return viewItem;
|
||||
@@ -49,7 +60,7 @@ export class ReactRouterViewStack extends ViewStacks {
|
||||
|
||||
// Sync latest routes with viewItems
|
||||
React.Children.forEach(ionRouterOutlet.props.children, (child: React.ReactElement) => {
|
||||
const viewItem = viewItems.find(v => {
|
||||
const viewItem = viewItems.find((v) => {
|
||||
return matchComponent(child, v.routeData.childProps.path || v.routeData.childProps.from);
|
||||
});
|
||||
if (viewItem) {
|
||||
@@ -57,23 +68,30 @@ export class ReactRouterViewStack extends ViewStacks {
|
||||
}
|
||||
});
|
||||
|
||||
const children = viewItems.map(viewItem => {
|
||||
|
||||
const children = viewItems.map((viewItem) => {
|
||||
let clonedChild;
|
||||
if (viewItem.ionRoute && !viewItem.disableIonPageManagement) {
|
||||
clonedChild = (
|
||||
<ViewLifeCycleManager key={`view-${viewItem.id}`} mount={viewItem.mount} removeView={() => this.remove(viewItem)}>
|
||||
<ViewLifeCycleManager
|
||||
key={`view-${viewItem.id}`}
|
||||
mount={viewItem.mount}
|
||||
removeView={() => this.remove(viewItem)}
|
||||
>
|
||||
{React.cloneElement(viewItem.reactElement, {
|
||||
computedMatch: viewItem.routeData.match
|
||||
computedMatch: viewItem.routeData.match,
|
||||
})}
|
||||
</ViewLifeCycleManager>
|
||||
);
|
||||
} else {
|
||||
const match = matchComponent(viewItem.reactElement, routeInfo.pathname);
|
||||
clonedChild = (
|
||||
<ViewLifeCycleManager key={`view-${viewItem.id}`} mount={viewItem.mount} removeView={() => this.remove(viewItem)}>
|
||||
<ViewLifeCycleManager
|
||||
key={`view-${viewItem.id}`}
|
||||
mount={viewItem.mount}
|
||||
removeView={() => this.remove(viewItem)}
|
||||
>
|
||||
{React.cloneElement(viewItem.reactElement, {
|
||||
computedMatch: viewItem.routeData.match
|
||||
computedMatch: viewItem.routeData.match,
|
||||
})}
|
||||
</ViewLifeCycleManager>
|
||||
);
|
||||
@@ -98,7 +116,12 @@ export class ReactRouterViewStack extends ViewStacks {
|
||||
}
|
||||
|
||||
findLeavingViewItemByRouteInfo(routeInfo: RouteInfo, outletId?: string, mustBeIonRoute = true) {
|
||||
const { viewItem } = this.findViewItemByPath(routeInfo.lastPathname!, outletId, false, mustBeIonRoute);
|
||||
const { viewItem } = this.findViewItemByPath(
|
||||
routeInfo.lastPathname!,
|
||||
outletId,
|
||||
false,
|
||||
mustBeIonRoute
|
||||
);
|
||||
return viewItem;
|
||||
}
|
||||
|
||||
@@ -107,8 +130,12 @@ export class ReactRouterViewStack extends ViewStacks {
|
||||
return viewItem;
|
||||
}
|
||||
|
||||
private findViewItemByPath(pathname: string, outletId?: string, forceExact?: boolean, mustBeIonRoute?: boolean) {
|
||||
|
||||
private findViewItemByPath(
|
||||
pathname: string,
|
||||
outletId?: string,
|
||||
forceExact?: boolean,
|
||||
mustBeIonRoute?: boolean
|
||||
) {
|
||||
let viewItem: ViewItem | undefined;
|
||||
let match: ReturnType<typeof matchPath> | undefined;
|
||||
let viewStack: ViewItem[];
|
||||
@@ -136,7 +163,7 @@ export class ReactRouterViewStack extends ViewStacks {
|
||||
const matchProps = {
|
||||
exact: forceExact ? true : v.routeData.childProps.exact,
|
||||
path: v.routeData.childProps.path || v.routeData.childProps.from,
|
||||
component: v.routeData.childProps.component
|
||||
component: v.routeData.childProps.component,
|
||||
};
|
||||
const myMatch = matchPath(pathname, matchProps);
|
||||
if (myMatch) {
|
||||
@@ -154,23 +181,21 @@ export class ReactRouterViewStack extends ViewStacks {
|
||||
path: pathname,
|
||||
url: pathname,
|
||||
isExact: true,
|
||||
params: {}
|
||||
params: {},
|
||||
};
|
||||
viewItem = v;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function matchComponent(node: React.ReactElement, pathname: string, forceExact?: boolean) {
|
||||
const matchProps = {
|
||||
exact: forceExact ? true : node.props.exact,
|
||||
path: node.props.path || node.props.from,
|
||||
component: node.props.component
|
||||
component: node.props.component,
|
||||
};
|
||||
const match = matchPath(pathname, matchProps);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
StackContextState,
|
||||
ViewItem,
|
||||
generateId,
|
||||
getConfig
|
||||
getConfig,
|
||||
} from '@ionic/react';
|
||||
import React from 'react';
|
||||
import { matchPath } from 'react-router-dom';
|
||||
@@ -16,7 +16,7 @@ interface StackManagerProps {
|
||||
routeInfo: RouteInfo;
|
||||
}
|
||||
|
||||
interface StackManagerState { }
|
||||
interface StackManagerState {}
|
||||
|
||||
export class StackManager extends React.PureComponent<StackManagerProps, StackManagerState> {
|
||||
id: string;
|
||||
@@ -26,7 +26,7 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
||||
|
||||
stackContextValue: StackContextState = {
|
||||
registerIonPage: this.registerIonPage.bind(this),
|
||||
isInOutlet: () => true
|
||||
isInOutlet: () => true,
|
||||
};
|
||||
|
||||
constructor(props: StackManagerProps) {
|
||||
@@ -57,7 +57,6 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
||||
}
|
||||
|
||||
async handlePageTransition(routeInfo: RouteInfo) {
|
||||
|
||||
// If routerOutlet isn't quite ready, give it another try in a moment
|
||||
if (!this.routerOutletElement || !this.routerOutletElement.commit) {
|
||||
setTimeout(() => this.handlePageTransition(routeInfo), 10);
|
||||
@@ -66,7 +65,10 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
||||
let leavingViewItem = this.context.findLeavingViewItemByRouteInfo(routeInfo, this.id);
|
||||
|
||||
if (!leavingViewItem && routeInfo.prevRouteLastPathname) {
|
||||
leavingViewItem = this.context.findViewItemByPathname(routeInfo.prevRouteLastPathname, this.id);
|
||||
leavingViewItem = this.context.findViewItemByPathname(
|
||||
routeInfo.prevRouteLastPathname,
|
||||
this.id
|
||||
);
|
||||
}
|
||||
|
||||
// Check if leavingViewItem should be unmounted
|
||||
@@ -74,7 +76,7 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
||||
if (routeInfo.routeAction === 'replace') {
|
||||
leavingViewItem.mount = false;
|
||||
} else if (!(routeInfo.routeAction === 'push' && routeInfo.routeDirection === 'forward')) {
|
||||
if (routeInfo.routeDirection !== 'none' && (enteringViewItem !== leavingViewItem)) {
|
||||
if (routeInfo.routeDirection !== 'none' && enteringViewItem !== leavingViewItem) {
|
||||
leavingViewItem.mount = false;
|
||||
}
|
||||
} else if (routeInfo.routeOptions?.unmount) {
|
||||
@@ -82,7 +84,10 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
||||
}
|
||||
}
|
||||
|
||||
const enteringRoute = matchRoute(this.ionRouterOutlet?.props.children, routeInfo) as React.ReactElement;
|
||||
const enteringRoute = matchRoute(
|
||||
this.ionRouterOutlet?.props.children,
|
||||
routeInfo
|
||||
) as React.ReactElement;
|
||||
if (enteringViewItem) {
|
||||
enteringViewItem.reactElement = enteringRoute;
|
||||
}
|
||||
@@ -120,7 +125,6 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
||||
}
|
||||
|
||||
async setupRouterOutlet(routerOutlet: HTMLIonRouterOutletElement) {
|
||||
|
||||
const canStart = () => {
|
||||
const config = getConfig();
|
||||
const swipeEnabled = config && config.get('swipeBackEnabled', routerOutlet.mode === 'ios');
|
||||
@@ -137,20 +141,28 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
||||
routerOutlet.swipeHandler = {
|
||||
canStart,
|
||||
onStart,
|
||||
onEnd: _shouldContinue => true
|
||||
onEnd: (_shouldContinue) => true,
|
||||
};
|
||||
}
|
||||
|
||||
async transitionPage(routeInfo: RouteInfo, enteringViewItem: ViewItem, leavingViewItem?: ViewItem) {
|
||||
|
||||
async transitionPage(
|
||||
routeInfo: RouteInfo,
|
||||
enteringViewItem: ViewItem,
|
||||
leavingViewItem?: ViewItem
|
||||
) {
|
||||
const routerOutlet = this.routerOutletElement!;
|
||||
|
||||
const direction = (routeInfo.routeDirection === 'none' || routeInfo.routeDirection === 'root')
|
||||
? undefined
|
||||
: routeInfo.routeDirection;
|
||||
const direction =
|
||||
routeInfo.routeDirection === 'none' || routeInfo.routeDirection === 'root'
|
||||
? undefined
|
||||
: routeInfo.routeDirection;
|
||||
|
||||
if (enteringViewItem && enteringViewItem.ionPageElement && this.routerOutletElement) {
|
||||
if (leavingViewItem && leavingViewItem.ionPageElement && (enteringViewItem === leavingViewItem)) {
|
||||
if (
|
||||
leavingViewItem &&
|
||||
leavingViewItem.ionPageElement &&
|
||||
enteringViewItem === leavingViewItem
|
||||
) {
|
||||
// If a page is transitioning to another version of itself
|
||||
// we clone it so we can have an animation to show
|
||||
|
||||
@@ -184,7 +196,7 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
||||
direction: direction as any,
|
||||
showGoBack: direction === 'forward',
|
||||
progressAnimation: false,
|
||||
animationBuilder: routeInfo.routeAnimation
|
||||
animationBuilder: routeInfo.routeAnimation,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -200,25 +212,28 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
||||
this.props.routeInfo,
|
||||
() => {
|
||||
this.forceUpdate();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<StackContext.Provider value={this.stackContextValue}>
|
||||
{React.cloneElement(ionRouterOutlet as any, {
|
||||
ref: (node: HTMLIonRouterOutletElement) => {
|
||||
if (ionRouterOutlet.props.setRef) {
|
||||
ionRouterOutlet.props.setRef(node);
|
||||
}
|
||||
if (ionRouterOutlet.props.forwardedRef) {
|
||||
ionRouterOutlet.props.forwardedRef.current = node;
|
||||
}
|
||||
this.routerOutletElement = node;
|
||||
const { ref } = ionRouterOutlet as any;
|
||||
if (typeof ref === 'function') {
|
||||
ref(node);
|
||||
}
|
||||
}
|
||||
},
|
||||
{React.cloneElement(
|
||||
ionRouterOutlet as any,
|
||||
{
|
||||
ref: (node: HTMLIonRouterOutletElement) => {
|
||||
if (ionRouterOutlet.props.setRef) {
|
||||
ionRouterOutlet.props.setRef(node);
|
||||
}
|
||||
if (ionRouterOutlet.props.forwardedRef) {
|
||||
ionRouterOutlet.props.forwardedRef.current = node;
|
||||
}
|
||||
this.routerOutletElement = node;
|
||||
const { ref } = ionRouterOutlet as any;
|
||||
if (typeof ref === 'function') {
|
||||
ref(node);
|
||||
}
|
||||
},
|
||||
},
|
||||
components
|
||||
)}
|
||||
</StackContext.Provider>
|
||||
@@ -238,7 +253,7 @@ function matchRoute(node: React.ReactNode, routeInfo: RouteInfo) {
|
||||
const matchProps = {
|
||||
exact: child.props.exact,
|
||||
path: child.props.path || child.props.from,
|
||||
component: child.props.component
|
||||
component: child.props.component,
|
||||
};
|
||||
const match = matchPath(routeInfo.pathname, matchProps);
|
||||
if (match) {
|
||||
@@ -264,7 +279,7 @@ function matchComponent(node: React.ReactElement, pathname: string, forceExact?:
|
||||
const matchProps = {
|
||||
exact: forceExact ? true : node.props.exact,
|
||||
path: node.props.path || node.props.from,
|
||||
component: node.props.component
|
||||
component: node.props.component,
|
||||
};
|
||||
const match = matchPath(pathname, matchProps);
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import { Route } from 'react-router';
|
||||
import { MemoryHistory, createMemoryHistory } from 'history';
|
||||
|
||||
describe('Router', () => {
|
||||
|
||||
// This test fails because the ion-router-outlet web component (from core)
|
||||
// isn't supported in JSDOM, so it never registers
|
||||
// TODO: figure out why they are failing on new router code when they worked before
|
||||
@@ -23,7 +22,7 @@ describe('Router', () => {
|
||||
<IonApp>
|
||||
<IonReactMemoryRouter history={history}>
|
||||
{/* <IonRouterOutlet> */}
|
||||
<Route path="/" component={Page} exact />
|
||||
<Route path="/" component={Page} exact />
|
||||
{/* </IonRouterOutlet> */}
|
||||
</IonReactMemoryRouter>
|
||||
</IonApp>
|
||||
@@ -32,7 +31,6 @@ describe('Router', () => {
|
||||
});
|
||||
|
||||
it.skip('should be visible', () => {
|
||||
|
||||
const MyPage = () => {
|
||||
return (
|
||||
<IonPage className="ion-page-invisible">
|
||||
@@ -42,11 +40,10 @@ describe('Router', () => {
|
||||
};
|
||||
|
||||
const { container } = render(<IonTestApp Page={MyPage} />);
|
||||
console.log(container.outerHTML)
|
||||
console.log(container.outerHTML);
|
||||
const page = container.getElementsByClassName('ion-page')[0];
|
||||
expect(page).not.toHaveClass('ion-page-invisible');
|
||||
expect(page).toHaveStyle('z-index: 101');
|
||||
|
||||
});
|
||||
|
||||
it.skip('should fire initial lifecycle events', async () => {
|
||||
@@ -68,14 +65,9 @@ describe('Router', () => {
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
render(<IonTestApp Page={MyPage} />);
|
||||
expect(ionViewWillEnterListener).toHaveBeenCalledTimes(1);
|
||||
expect(ionViewDidEnterListener).toHaveBeenCalledTimes(1);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
});;
|
||||
});
|
||||
|
||||
12
packages/react-router/test-app/config/env.js
vendored
12
packages/react-router/test-app/config/env.js
vendored
@@ -9,9 +9,7 @@ delete require.cache[require.resolve('./paths')];
|
||||
|
||||
const NODE_ENV = process.env.NODE_ENV;
|
||||
if (!NODE_ENV) {
|
||||
throw new Error(
|
||||
'The NODE_ENV environment variable is required but was not specified.'
|
||||
);
|
||||
throw new Error('The NODE_ENV environment variable is required but was not specified.');
|
||||
}
|
||||
|
||||
// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use
|
||||
@@ -30,7 +28,7 @@ const dotenvFiles = [
|
||||
// that have already been set. Variable expansion is supported in .env files.
|
||||
// https://github.com/motdotla/dotenv
|
||||
// https://github.com/motdotla/dotenv-expand
|
||||
dotenvFiles.forEach(dotenvFile => {
|
||||
dotenvFiles.forEach((dotenvFile) => {
|
||||
if (fs.existsSync(dotenvFile)) {
|
||||
require('dotenv-expand')(
|
||||
require('dotenv').config({
|
||||
@@ -52,8 +50,8 @@ dotenvFiles.forEach(dotenvFile => {
|
||||
const appDirectory = fs.realpathSync(process.cwd());
|
||||
process.env.NODE_PATH = (process.env.NODE_PATH || '')
|
||||
.split(path.delimiter)
|
||||
.filter(folder => folder && !path.isAbsolute(folder))
|
||||
.map(folder => path.resolve(appDirectory, folder))
|
||||
.filter((folder) => folder && !path.isAbsolute(folder))
|
||||
.map((folder) => path.resolve(appDirectory, folder))
|
||||
.join(path.delimiter);
|
||||
|
||||
// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be
|
||||
@@ -62,7 +60,7 @@ const REACT_APP = /^REACT_APP_/i;
|
||||
|
||||
function getClientEnvironment(publicUrl) {
|
||||
const raw = Object.keys(process.env)
|
||||
.filter(key => REACT_APP.test(key))
|
||||
.filter((key) => REACT_APP.test(key))
|
||||
.reduce(
|
||||
(env, key) => {
|
||||
env[key] = process.env[key];
|
||||
|
||||
@@ -14,20 +14,14 @@ function validateKeyAndCerts({ cert, key, keyFile, crtFile }) {
|
||||
// publicEncrypt will throw an error with an invalid cert
|
||||
encrypted = crypto.publicEncrypt(cert, Buffer.from('test'));
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}`
|
||||
);
|
||||
throw new Error(`The certificate "${chalk.yellow(crtFile)}" is invalid.\n${err.message}`);
|
||||
}
|
||||
|
||||
try {
|
||||
// privateDecrypt will throw an error with an invalid key
|
||||
crypto.privateDecrypt(key, encrypted);
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
`The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${
|
||||
err.message
|
||||
}`
|
||||
);
|
||||
throw new Error(`The certificate key "${chalk.yellow(keyFile)}" is invalid.\n${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,9 +29,9 @@ function validateKeyAndCerts({ cert, key, keyFile, crtFile }) {
|
||||
function readEnvFile(file, type) {
|
||||
if (!fs.existsSync(file)) {
|
||||
throw new Error(
|
||||
`You specified ${chalk.cyan(
|
||||
type
|
||||
)} in your env, but the file "${chalk.yellow(file)}" can't be found.`
|
||||
`You specified ${chalk.cyan(type)} in your env, but the file "${chalk.yellow(
|
||||
file
|
||||
)}" can't be found.`
|
||||
);
|
||||
}
|
||||
return fs.readFileSync(file);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user