Skip to content

Commit

Permalink
feat: Add focus prev and next element and clear all cells with meta keys
Browse files Browse the repository at this point in the history
  • Loading branch information
hossein-nas committed May 11, 2024
1 parent 5f48dfb commit 5a8bc98
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 9 deletions.
69 changes: 66 additions & 3 deletions src/pin-input-cell/pin-input-cell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import { property, query } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import {
englishToPersian,
isDeletionKey,
isArrowKeyPressed,
isDeletionKeyPressed,
isDeletionKeyWithCtrlOrMetaPressed,
isValidDigit,
persianToEnglish,
} from './util';
Expand Down Expand Up @@ -75,6 +77,34 @@ export class PinInputCell extends LitElement {
this.dispatchEvent(event);
}

private async emitDeletionWithMetaKeys() {
await this.updateComplete;
const event = new CustomEvent('clear-all', {
bubbles: true,
composed: false,
detail: {
cell: this,
index: this.index,
value: this.value,
} as ValueChangedEventParams,
});
this.dispatchEvent(event);
}

private async emitArrowKeyPressed(key: 'ArrowLeft' | 'ArrowRight') {
await this.updateComplete;
const event = new CustomEvent('arrow-key-pressed', {
bubbles: true,
composed: false,
detail: {
cell: this,
index: this.index,
value: key === 'ArrowLeft' ? 'left' : 'right',
} as ValueChangedEventParams<'left' | 'right'>,
});
this.dispatchEvent(event);
}

private async emitOverflowedValue(value: string) {
await this.updateComplete;
const event = new CustomEvent('overflow-value', {
Expand Down Expand Up @@ -112,13 +142,33 @@ export class PinInputCell extends LitElement {
}

private async validatePressedKey(event: KeyboardEvent) {
if (event.key === 'Backspace' && this.value === '') {
if (
isDeletionKeyWithCtrlOrMetaPressed({
input: event.key,
metaKey: event.metaKey,
ctrlKey: event.ctrlKey,
})
) {
this.value = '';
event.preventDefault();
await this.handleDeletionWithMetaKeys();
return;
}

if (isDeletionKeyPressed(event.key) && this.value === '') {
this.value = '';
event.preventDefault();
await this.handleEmptyCellBackspace();
return;
}

if (isValidDigit(event.key) || isDeletionKey(event.key)) {
if (isArrowKeyPressed(event.key)) {
event.preventDefault();
await this.handleArrowKeyPressed(event.key);
return;
}

if (isValidDigit(event.key) || isDeletionKeyPressed(event.key)) {
return true;
}

Expand Down Expand Up @@ -165,6 +215,13 @@ export class PinInputCell extends LitElement {
await this.emitValueCleared();
}

private async handleDeletionWithMetaKeys() {
await this.emitDeletionWithMetaKeys();
}
private async handleArrowKeyPressed(key: 'ArrowLeft' | 'ArrowRight') {
await this.emitArrowKeyPressed(key);
}

private handleFocus(e: FocusEvent) {
const _target = e.target as HTMLInputElement;
if (_target.value?.length > 0) {
Expand All @@ -181,9 +238,15 @@ export class PinInputCell extends LitElement {
async setValue(value: string) {
if (value.length === 1 && this.value === '') {
await this.updateInputValue(value);
await this.updateComplete;
}
}

async clearValue() {
await this.updateInputValue('');
await this.updateComplete;
}

render() {
return html`
<input
Expand Down
4 changes: 2 additions & 2 deletions src/pin-input-cell/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { PinInputCell } from './pin-input-cell';

export type ValueChangedEventParams = {
export type ValueChangedEventParams<T = string> = {
cell: PinInputCell;
index: number;
value: string;
value: T;
};
25 changes: 22 additions & 3 deletions src/pin-input-cell/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,27 @@ export function isValidDigit(input: string): boolean {
return false;
}

const deletionKeys = ['Meta', 'Delete', 'Backspace'];
export function isArrowKeyPressed(
input: string,
): input is 'ArrowLeft' | 'ArrowRight' {
return ['ArrowLeft', 'ArrowRight'].includes(input);
}

export function isDeletionKeyPressed(input: string): boolean {
return ['Backspace', 'Delete'].includes(input);
}

export function isDeletionKey(key: string): boolean {
return deletionKeys.includes(key);
export function isDeletionKeyWithCtrlOrMetaPressed({
input,
metaKey = false,
ctrlKey = false,
}: {
input: string;
metaKey: boolean;
ctrlKey: boolean;
}) {
if (isDeletionKeyPressed(input) && (metaKey || ctrlKey)) {
return true;
}
return false;
}
49 changes: 48 additions & 1 deletion src/pin-input/pin-input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,50 @@ export class PinInput extends LitElement {

await this.fillCells(overflowedText, cellIndex + 1);
}
private async handleClearPrevCells(
event: CustomEvent<ValueChangedEventParams>,
) {
await this.updateComplete;
const currentIndex = event.detail.index;

const isNotFirstItem =
currentIndex > 0 && this.checkIndexIsInRange(currentIndex);

if (isNotFirstItem) {
await this.clearCellsUntil(currentIndex);
this._cells?.[0].focus();
}
}

private async handleArrowKeyPressed(
event: CustomEvent<ValueChangedEventParams<'left' | 'right'>>,
) {
await this.updateComplete;
const currentIndex = event.detail.index;

const shouldPrevItemFocus =
event.detail.value === 'left' &&
this.checkIndexIsInRange(currentIndex) &&
!this.checkIndexIsFirst(currentIndex);

const shouldNextItemFocus =
event.detail.value === 'right' &&
this.checkIndexIsInRange(currentIndex) &&
!this.checkIndexIsLast(currentIndex);

if (shouldPrevItemFocus) {
this.focusPrevElementByIndex(currentIndex);
} else if (shouldNextItemFocus) {
this.focusNextElementByIndex(currentIndex);
}
}

private async fillCells(value: string, startingAt: number = 0) {
if (startingAt <= this.lastCellIndex) {
let index = 0;
for (const char of value.split('')) {
const pos = index + startingAt;
await this._cells[pos].setValue(char);
await this._cells[pos].updateComplete;
index++;
}
}
Expand Down Expand Up @@ -115,6 +151,12 @@ export class PinInput extends LitElement {
}
}

private async clearCellsUntil(index: number) {
for (let i = 0; i <= index; i++) {
await this._cells?.[i].clearValue();
}
}

private focusPrevElementByIndex(current: number) {
const nextIndex = current - 1;
if (this.checkIndexIsInRange(current) && !this.checkIndexIsFirst(current)) {
Expand Down Expand Up @@ -164,6 +206,11 @@ export class PinInput extends LitElement {
this.handleCellCleared(e)}
@overflow-value=${(e: CustomEvent<ValueChangedEventParams>) =>
this.handleOverflowedCell(e)}
@clear-all=${(e: CustomEvent<ValueChangedEventParams>) =>
this.handleClearPrevCells(e)}
@arrow-key-pressed=${(
e: CustomEvent<ValueChangedEventParams<'left' | 'right'>>,
) => this.handleArrowKeyPressed(e)}
?auto-focus=${this.isFirstCellShouldAutoFocus(index)}
.size=${this.size}
></tap-pin-input-cell>`;
Expand Down

0 comments on commit 5a8bc98

Please sign in to comment.