-
Notifications
You must be signed in to change notification settings - Fork 13.4k
feat(input): add input-password-toggle component #29175
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 41 commits
Commits
Show all changes
47 commits
Select commit
Hold shift + click to select a range
8635650
fix(input, textarea, select): account for multiple start/end slot ele…
liamdebeasi 5ac8d29
chore(): add updated snapshots
Ionitron de678b5
fix selector
liamdebeasi fc3837f
Add component
liamdebeasi d9a2468
test(input-password-toggle): add failing tests
liamdebeasi d3cef3d
add implementation
liamdebeasi 4ae25c2
Merge branch 'feature-8.0' into ld/input-slot-margin
liamdebeasi 38e66cf
Revert "fix selector"
liamdebeasi a7f006b
chore: clean up
liamdebeasi 94a8ac3
Merge remote-tracking branch 'origin/ld/input-slot-margin' into ld/pa…
liamdebeasi 2efcddc
chore: build and lint
liamdebeasi fba9b5f
fix(input-password-toggle): hide when input is disabled or readonly
liamdebeasi 33076f6
chore: remove todo
liamdebeasi 4587cc2
chore: improve docs explanation
liamdebeasi 00960a2
refactor: warn about unsupported input types
liamdebeasi d570514
add more tests
liamdebeasi 78e4fc2
Merge remote-tracking branch 'origin/feature-8.0' into ld/password-to…
liamdebeasi a9c72f4
fix typo
liamdebeasi 28abf98
fix(input-password-toggle): re-render when input type changes
liamdebeasi 3273a1d
run build
liamdebeasi 6238237
lint
liamdebeasi a08df08
div-be-gone
liamdebeasi 7e66c47
test(input): add screenshot test
liamdebeasi b87c6e9
lint
liamdebeasi 37aa257
use attribute
liamdebeasi e22fc9c
Merge branch 'feature-8.0' into ld/password-toggle
liamdebeasi 4cc81ac
chore: add ground truths
liamdebeasi a2e25f8
fix test
liamdebeasi c61aa1f
test: add a11y test
liamdebeasi a36134d
clean up
liamdebeasi 90e62e3
refactor: rename to showIcon and hideIcon
liamdebeasi b8c22c2
typo
liamdebeasi 4bcc59f
refactor: input sets password toggle type
liamdebeasi f145bc8
clarify message
liamdebeasi ecefeba
refactor: leverage existing styles
liamdebeasi 243fb2b
build and lint
liamdebeasi 7025105
Merge branch 'feature-8.0' into ld/password-toggle
liamdebeasi 60e61ec
re-add per-mode stylesheet for now
liamdebeasi 83d2423
Merge branch 'feature-8.0' into ld/password-toggle
liamdebeasi ab75160
Merge remote-tracking branch 'origin/feature-8.0' into ld/password-to…
liamdebeasi f0aa12b
Merge branch 'feature-8.0' into ld/password-toggle
liamdebeasi 14a33bb
refactor: add comment, use single file
liamdebeasi e7248cf
update template
liamdebeasi 6352186
refactor: use shape=“round” instead
liamdebeasi 6d1a328
chore: update screenshots
liamdebeasi c4e094d
Merge branch 'feature-8.0' into ld/password-toggle
liamdebeasi 2ec9bd0
chore(): add updated snapshots
Ionitron File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
Empty file.
151 changes: 151 additions & 0 deletions
151
core/src/components/input-password-toggle/input-password-toggle.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
import type { ComponentInterface } from '@stencil/core'; | ||
import { Component, Element, Host, Prop, h, Watch } from '@stencil/core'; | ||
import { printIonWarning } from '@utils/logging'; | ||
import { createColorClasses } from '@utils/theme'; | ||
import { eyeOff, eye } from 'ionicons/icons'; | ||
|
||
import { getIonMode } from '../../global/ionic-global'; | ||
import type { Color, TextFieldTypes } from '../../interface'; | ||
|
||
/** | ||
* @virtualProp {"ios" | "md"} mode - The mode determines which platform styles to use. | ||
*/ | ||
@Component({ | ||
tag: 'ion-input-password-toggle', | ||
styleUrls: { | ||
ios: 'input-password-toggle.ios.scss', | ||
md: 'input-password-toggle.md.scss', | ||
}, | ||
liamdebeasi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
shadow: true, | ||
}) | ||
export class InputPasswordToggle implements ComponentInterface { | ||
private inputElRef!: HTMLIonInputElement | null; | ||
|
||
@Element() el!: HTMLIonInputElement; | ||
|
||
/** | ||
* 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). | ||
*/ | ||
@Prop({ reflect: true }) color?: Color; | ||
|
||
/** | ||
* The icon that can be used to represent showing a password. If not set, the "eye" Ionicon will be used. | ||
*/ | ||
@Prop() showIcon?: string; | ||
|
||
/** | ||
* The icon that can be used to represent hiding a password. If not set, the "eyeOff" Ionicon will be used. | ||
*/ | ||
@Prop() hideIcon?: string; | ||
|
||
/** | ||
* @internal | ||
*/ | ||
@Prop({ mutable: true }) type: TextFieldTypes = 'password'; | ||
|
||
/** | ||
* Whenever the input type changes we need to re-run validation to ensure the password | ||
* toggle is being used with the correct input type. If the application changes the type | ||
* outside of this component we also need to re-render so the correct icon is shown. | ||
*/ | ||
@Watch('type') | ||
onTypeChange(newValue: TextFieldTypes) { | ||
if (newValue !== 'text' && newValue !== 'password') { | ||
printIonWarning( | ||
`ion-input-password-toggle only supports inputs of type "text" or "password". Input of type "${newValue}" is not compatible.`, | ||
liamdebeasi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
this.el | ||
); | ||
|
||
return; | ||
} | ||
} | ||
|
||
connectedCallback() { | ||
const { el } = this; | ||
|
||
const inputElRef = (this.inputElRef = el.closest('ion-input')); | ||
|
||
if (!inputElRef) { | ||
printIonWarning( | ||
'No ancestor ion-input found for ion-input-password-toggle. This component must be slotted inside of an ion-input.', | ||
el | ||
); | ||
|
||
return; | ||
} | ||
|
||
/** | ||
* Important: Set the type in connectedCallback because the default value | ||
* of this.type may not always be accurate. Usually inputs have the "password" type | ||
* but it is possible to have the input to initially have the "text" type. In that scenario | ||
* the wrong icon will show briefly before switching to the correct icon. Setting the | ||
* type here allows us to avoid that flicker. | ||
*/ | ||
this.type = inputElRef.type; | ||
} | ||
|
||
disconnectedCallback() { | ||
this.inputElRef = null; | ||
} | ||
|
||
private togglePasswordVisibility = () => { | ||
const { inputElRef } = this; | ||
|
||
if (!inputElRef) { | ||
return; | ||
} | ||
|
||
inputElRef.type = inputElRef.type === 'text' ? 'password' : 'text'; | ||
}; | ||
|
||
render() { | ||
const { color, type } = this; | ||
|
||
const mode = getIonMode(this); | ||
|
||
const showPasswordIcon = this.showIcon ?? eye; | ||
const hidePasswordIcon = this.hideIcon ?? eyeOff; | ||
|
||
const isPasswordVisible = type === 'text'; | ||
|
||
return ( | ||
<Host | ||
class={createColorClasses(color, { | ||
[mode]: true, | ||
})} | ||
> | ||
{/* | ||
This part is intentionally undocumented. It only exists so that Input | ||
can style the button when InputPasswordToggle is slotted inside of the Input. | ||
*/} | ||
<ion-button | ||
liamdebeasi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
part="button" | ||
mode={mode} | ||
color={color} | ||
fill="clear" | ||
aria-checked={isPasswordVisible ? 'true' : 'false'} | ||
aria-label="show password" | ||
role="switch" | ||
type="button" | ||
onPointerDown={(ev) => { | ||
/** | ||
* This prevents mobile browsers from | ||
* blurring the input when the password toggle | ||
* button is activated. | ||
*/ | ||
ev.preventDefault(); | ||
}} | ||
onClick={this.togglePasswordVisibility} | ||
> | ||
<ion-icon | ||
slot="icon-only" | ||
aria-hidden="true" | ||
icon={isPasswordVisible ? hidePasswordIcon : showPasswordIcon} | ||
></ion-icon> | ||
</ion-button> | ||
</Host> | ||
); | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
core/src/components/input-password-toggle/test/a11y/input-password-toggle.e2e.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import AxeBuilder from '@axe-core/playwright'; | ||
import { expect } from '@playwright/test'; | ||
import { configs, test } from '@utils/test/playwright'; | ||
|
||
configs({ directions: ['ltr'] }).forEach(({ title, config }) => { | ||
test.describe(title('input password toggle: a11y'), () => { | ||
test('should not have accessibility violations', async ({ page }) => { | ||
await page.setContent( | ||
` | ||
<main> | ||
<ion-input label="input" type="password"> | ||
<ion-input-password-toggle slot="end"></ion-input-password-toggle> | ||
</ion-input> | ||
</main> | ||
`, | ||
config | ||
); | ||
|
||
const results = await new AxeBuilder({ page }).analyze(); | ||
expect(results.violations).toEqual([]); | ||
}); | ||
}); | ||
}); |
76 changes: 76 additions & 0 deletions
76
core/src/components/input-password-toggle/test/basic/index.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
<!DOCTYPE html> | ||
<html lang="en" dir="ltr"> | ||
<head> | ||
<meta charset="UTF-8" /> | ||
<title>Input - Toggle Password</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> | ||
<style> | ||
.grid { | ||
display: grid; | ||
grid-template-columns: repeat(5, minmax(250px, 1fr)); | ||
grid-row-gap: 20px; | ||
grid-column-gap: 20px; | ||
} | ||
h2 { | ||
font-size: 12px; | ||
font-weight: normal; | ||
|
||
color: #6f7378; | ||
|
||
margin-top: 10px; | ||
} | ||
@media screen and (max-width: 800px) { | ||
.grid { | ||
grid-template-columns: 1fr; | ||
padding: 0; | ||
} | ||
} | ||
</style> | ||
</head> | ||
|
||
<body> | ||
<ion-app> | ||
<ion-header> | ||
<ion-toolbar> | ||
<ion-title>Input - Basic</ion-title> | ||
</ion-toolbar> | ||
</ion-header> | ||
|
||
<ion-content id="content" class="ion-padding"> | ||
<div class="grid"> | ||
<div class="grid-item"> | ||
<h2>Default</h2> | ||
<ion-input type="password" value="supersecurepassword" label="Password"> | ||
<ion-input-password-toggle mode="ios" slot="end"></ion-input-password-toggle> | ||
</ion-input> | ||
</div> | ||
<div class="grid-item"> | ||
<h2>Custom Icon</h2> | ||
<ion-input type="password" value="supersecurepassword" label="Password"> | ||
<ion-input-password-toggle show-icon="trash" slot="end"></ion-input-password-toggle> | ||
</ion-input> | ||
</div> | ||
<div class="grid-item"> | ||
<h2>Custom Mode/Color</h2> | ||
<ion-input type="password" value="supersecurepassword" label="Password"> | ||
<ion-input-password-toggle | ||
color="danger" | ||
mode="ios" | ||
show-icon="trash" | ||
slot="end" | ||
></ion-input-password-toggle> | ||
</ion-input> | ||
</div> | ||
</div> | ||
</ion-content> | ||
</ion-app> | ||
</body> | ||
</html> |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.