From 8eff50f774a4c5fa8af5ff37bb317beedcc2fc9c Mon Sep 17 00:00:00 2001 From: Hossein Nasiri Date: Wed, 24 Apr 2024 09:42:43 +0330 Subject: [PATCH 1/9] feat: develop pin-input --- src/pin-input-cell/index.ts | 14 ++ src/pin-input-cell/pin-input-cell.stories.ts | 24 +++ src/pin-input-cell/pin-input-cell.style.ts | 115 +++++++++++ src/pin-input-cell/pin-input-cell.ts | 186 ++++++++++++++++++ src/pin-input-cell/types.ts | 8 + src/pin-input-cell/util.ts | 52 +++++ src/pin-input/index.ts | 14 ++ src/pin-input/pin-input.stories.ts | 26 +++ src/pin-input/pin-input.style.ts | 90 +++++++++ src/pin-input/pin-input.ts | 189 +++++++++++++++++++ src/pin-input/types.ts | 5 + 11 files changed, 723 insertions(+) create mode 100644 src/pin-input-cell/index.ts create mode 100644 src/pin-input-cell/pin-input-cell.stories.ts create mode 100644 src/pin-input-cell/pin-input-cell.style.ts create mode 100644 src/pin-input-cell/pin-input-cell.ts create mode 100644 src/pin-input-cell/types.ts create mode 100644 src/pin-input-cell/util.ts create mode 100644 src/pin-input/index.ts create mode 100644 src/pin-input/pin-input.stories.ts create mode 100644 src/pin-input/pin-input.style.ts create mode 100644 src/pin-input/pin-input.ts create mode 100644 src/pin-input/types.ts diff --git a/src/pin-input-cell/index.ts b/src/pin-input-cell/index.ts new file mode 100644 index 00000000..5e43ff9c --- /dev/null +++ b/src/pin-input-cell/index.ts @@ -0,0 +1,14 @@ +import { customElement } from "lit/decorators.js"; +import { PinInputCell } from "./pin-input-cell.js"; +import styles from "./pin-input-cell.style.js"; + +@customElement("tap-pin-input-cell") +export class TapPinInputCell extends PinInputCell { + static readonly styles = [styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "tap-pin-input-cell": TapPinInputCell; + } +} diff --git a/src/pin-input-cell/pin-input-cell.stories.ts b/src/pin-input-cell/pin-input-cell.stories.ts new file mode 100644 index 00000000..3f7e3257 --- /dev/null +++ b/src/pin-input-cell/pin-input-cell.stories.ts @@ -0,0 +1,24 @@ +import { html, TemplateResult } from "lit"; +import "./index.js"; + +export default { + title: "PinInput", + component: "tap-pin-input-cell", + argTypes: {}, +}; + +interface Story { + (args: T): TemplateResult; + args?: Partial; + argTypes?: Record; +} + +interface ArgTypes {} + +const Template: Story = ({}: ArgTypes) => html` + +`; + +export const PinInputCell = Template.bind({}); + +PinInputCell.args = {}; diff --git a/src/pin-input-cell/pin-input-cell.style.ts b/src/pin-input-cell/pin-input-cell.style.ts new file mode 100644 index 00000000..00df867d --- /dev/null +++ b/src/pin-input-cell/pin-input-cell.style.ts @@ -0,0 +1,115 @@ +import { css } from "lit"; + +export default css` + :host { + box-sizing: border-box; + --height: var(--tap-pin-input-cell-height, 52px); + --bg-color: var(--tap-pin-input-cell-bg-color, #eaeded); + --error-bg-color: var(--tap-pin-input-cell-error-bg-color, #ffefed); + --disabled-bg-color: var( + --tap-pin-input-cell-disabled-bg-color, + var(--tap-sys-color-surface-disabled) + ); + --color: var(--tap-pin-input-cell-color, #000); + --disabled-color: var( + --tap-pin-input-cell-disabled-color, + var(--tap-sys-color-content-disabled) + ); + --border-radius: var(--tap-pin-input-cell-border-radius, 8px); + --border-width: var(--tap-pin-input-cell-border-width, 2px); + --border-default-color: var( + --tap-pin-input-cell-border-default-color, + transparent + ); + --max-cell-width-ratio: var( + --tap-pin-input-cell-max-cell-width-ratio, + 1.25 + ); + --border-focus-color: var(--tap-pin-input-cell-border-focus-color, #323333); + --border-error-color: var(--tap-pin-input-cell-border-error-color, #f1998e); + --border-disabled-color: var( + --tap-pin-input-cell-border-disabled-color, + transparent + ); + + --font-size-sm: var(--tap-pin-input-cell-font-size-small, 16px); + --font-size-md: var(--tap-pin-input-cell-font-size-medium, 20px); + --font-size-lg: var(--tap-pin-input-cell-font-size-large, 24px); + + --font-weight-sm: var(--tap-pin-input-cell-font-weight-small, 400); + --font-weight-md: var(--tap-pin-input-cell-font-weight-medium, 600); + --font-weight-lg: var(--tap-pin-input-cell-font-weight-large, 600); + + --line-height-sm: var(--tap-pin-input-cell-line-height-small, 26px); + --line-height-md: var(--tap-pin-input-cell-line-height-medium, 32px); + --line-height-lg: var(--tap-pin-input-cell-line-height-large, 36px); + + --padding-sm: var(--tap-pin-input-cell-padding-small, 6px); + --padding-md: var(--tap-pin-input-cell-padding-medium, 8px); + --padding-lg: var(--tap-pin-input-cell-padding-large, 12px); + } + + :host *, + :host *::before, + :host *::after { + box-sizing: inherit; + } + + [hidden] { + display: none !important; + } + + .cell { + border: none; + padding: 0; + margin: 0; + background-color: var(--bg-color); + color: var(--color); + border: var(--border-width) solid var(--border-default-color); + border-radius: var(--border-radius); + outline: none; + min-width: 0; + text-align: center; + font-family: var(--tap-sys-font-family); + min-width: var(--height); + max-width: calc(var(--height) * var(--max-cell-width-ratio)); + } + + .cell[disabled] { + border-color: var(--border-disabled-color); + background-color: var(--disabled-bg-color); + color: var(--disabled-color); + user-select: none; + pointer-events: none; + } + + .cell:focus { + border-color: var(--border-focus-color); + } + + .cell.cell-sm { + padding: var(--padding-sm); + font-size: var(--font-size-sm); + font-weight: var(--font-weight-sm); + line-height: var(--line-height-sm); + } + + .cell.cell-md { + padding: var(--padding-md); + font-size: var(--font-size-md); + font-weight: var(--font-weight-md); + line-height: var(--line-height-md); + } + + .cell.cell-lg { + padding: var(--padding-lg); + font-size: var(--font-size-lg); + font-weight: var(--font-weight-lg); + line-height: var(--line-height-lg); + } + + .cell.has-error { + background-color: var(--error-bg-color); + border-color: var(--border-error-color); + } +`; diff --git a/src/pin-input-cell/pin-input-cell.ts b/src/pin-input-cell/pin-input-cell.ts new file mode 100644 index 00000000..b6cdc8e6 --- /dev/null +++ b/src/pin-input-cell/pin-input-cell.ts @@ -0,0 +1,186 @@ +import {html, LitElement, PropertyValues} from "lit"; +import {property, query} from "lit/decorators.js"; +import {classMap} from "lit/directives/class-map.js"; +import {englishToPersian, isDeletionKey, isValidDigit, persianToEnglish,} from "./util"; +import {ValueChangedEventParams} from "./types"; + +export class PinInputCell extends LitElement { + @query('.cell') _cell!: HTMLInputElement; + + @property({type: Boolean, reflect: true}) disabled: boolean = false; + + @property({attribute: 'auto-focus', type: Boolean}) autoFocus: boolean = false; + + @property({reflect: true, type: Boolean, attribute: "has-error"}) hasError = + false; + + @property({reflect: true, type: String}) value: string = ""!; + + @property() label = ""; + + @property() size: "small" | "medium" | "large" = "medium"; + + @property({type: Number}) index: number = null!; + + protected updated(changed: PropertyValues) { + if (changed.has("value") && !Number.isNaN(this.value)) { + // + } + } + + + private updateInputValue(newValue: string) { + this.value = persianToEnglish(newValue); + (this._cell).value = englishToPersian(newValue); + + if (newValue.length) { + this.emitValueChanged() + } + + if (newValue.length === 0) { + this.emitValueCleared() + } + } + + private async emitValueChanged() { + await this.updateComplete + const event = new CustomEvent('cell-filled', { + bubbles: true, composed: false, detail: { + cell: this, + index: this.index, + value: this.value, + } as ValueChangedEventParams + }) + this.dispatchEvent(event) + } + + private async emitValueCleared() { + await this.updateComplete + const event = new CustomEvent('cell-cleared', { + bubbles: true, composed: false, detail: { + cell: this, + index: this.index, + value: this.value, + } as ValueChangedEventParams + }) + this.dispatchEvent(event) + } + + private async emitOverflowedValue(value: string) { + await this.updateComplete + const event = new CustomEvent('overflow-value', { + bubbles: true, composed: false, detail: { + cell: this, + index: this.index, + value: value, + } as ValueChangedEventParams + }) + this.dispatchEvent(event) + } + + private handleInput(event: InputEvent) { + const inputValue = (event.target as HTMLInputElement).value.replace(/[^\d۰-۹]/g, ''); + + if (typeof inputValue === 'string' && inputValue.length >= 1) { + const lastCharacterIndex = inputValue.length - 1 + this.updateInputValue(inputValue[lastCharacterIndex]); + } else { + this.updateInputValue(''); + } + } + + protected firstUpdated(_changedProperties: PropertyValues) { + super.firstUpdated(_changedProperties); + + if (this.autoFocus) { + this._cell.focus?.() + } + } + + private validatePressedKey(event: KeyboardEvent) { + if (event.key === 'Backspace' && this.value === '') { + event.preventDefault(); + this.handleEmptyCellBackspace() + return; + } + + if (isValidDigit(event.key) || isDeletionKey(event.key)) { + return true; + } + + if ([8, 9, 13, 27, 46, 110, 190].includes(event.keyCode) || + // Allow Ctrl+A, Ctrl+C, Ctrl+V, Ctrl+X + (event.keyCode == 65 && (event.ctrlKey === true || event.metaKey === true)) || + (event.keyCode == 67 && (event.ctrlKey === true || event.metaKey === true)) || + (event.keyCode == 86 && (event.ctrlKey === true || event.metaKey === true)) || + (event.keyCode == 88 && (event.ctrlKey === true || event.metaKey === true))) { + return; + } + + event.preventDefault(); + } + + private handlePaste(e: ClipboardEvent) { + const text: string = e.clipboardData?.getData('text/plain') || '' + if (text && !this.isValidNumericText(text)) { + e.preventDefault(); + } + + if (text?.length > 1) { + this.updateInputValue(text[0]); + this.emitOverflowedValue(text.slice(1)) + e.preventDefault(); + return; + } + } + + private isValidNumericText(input: string) { + if (typeof input === 'string' && input.length > 0) { + const numericRegex = /^[0-9۰-۹]+$/; + return numericRegex.test(input) + } + } + + private handleEmptyCellBackspace() { + this.emitValueCleared(); + } + + private handleFocus(e: FocusEvent) { + const _target = e.target as HTMLInputElement + if (_target.value?.length > 0) { + _target.select(); + } + } + + override focus() { + if (this._cell) { + this._cell.focus?.(); + } + } + + setValue(value: string) { + if (value.length === 1 && this.value === '') { + this.updateInputValue(value) + } + } + + render() { + return html` + + `; + } +} diff --git a/src/pin-input-cell/types.ts b/src/pin-input-cell/types.ts new file mode 100644 index 00000000..9cfc080f --- /dev/null +++ b/src/pin-input-cell/types.ts @@ -0,0 +1,8 @@ +import {PinInputCell} from "./pin-input-cell"; + +export type ValueChangedEventParams = { + cell: PinInputCell, + index: number, + value: string, +} + diff --git a/src/pin-input-cell/util.ts b/src/pin-input-cell/util.ts new file mode 100644 index 00000000..033cdee4 --- /dev/null +++ b/src/pin-input-cell/util.ts @@ -0,0 +1,52 @@ +export function englishToPersian(input: string): string { + const localInput = `${input}` + const englishDigits = "0123456789"; + const persianDigits = "۰۱۲۳۴۵۶۷۸۹"; + let output = ""; + + for (const char of localInput) { + const index = englishDigits.indexOf(char); + if (index !== -1) { + output += persianDigits[index]; + } else { + output += char; + } + } + + return output; +} + +export function persianToEnglish(input: string): string { + const localInput = `${input}`; + const persianDigits = "۰۱۲۳۴۵۶۷۸۹"; + const englishDigits = "0123456789"; + let output = ""; + + for (const char of localInput) { + const index = persianDigits.indexOf(char); + if (index !== -1) { + output += englishDigits[index]; + } else { + output += char; + } + } + + return output; +} + +export function isValidDigit(input: string): boolean { + const englishDigits = "0123456789۰۱۲۳۴۵۶۷۸۹"; + + if (typeof input === "string" && input.length === 1) { + return englishDigits.indexOf(input) !== -1; + } + + return false; +} + +type DeleteKeys = "Meta" | "Delete" | "Backspace"; +const deletionKeys = ["Meta", "Delete", "Backspace"]; + +export function isDeletionKey(key: string): boolean { + return deletionKeys.includes(key); +} diff --git a/src/pin-input/index.ts b/src/pin-input/index.ts new file mode 100644 index 00000000..269ba275 --- /dev/null +++ b/src/pin-input/index.ts @@ -0,0 +1,14 @@ +import { customElement } from "lit/decorators.js"; +import { PinInput } from "./pin-input.js"; +import styles from "./pin-input.style.js"; + +@customElement("tap-pin-input") +export class TapPinInput extends PinInput { + static readonly styles = [styles]; +} + +declare global { + interface HTMLElementTagNameMap { + "tap-pin-input": TapPinInput; + } +} diff --git a/src/pin-input/pin-input.stories.ts b/src/pin-input/pin-input.stories.ts new file mode 100644 index 00000000..eb749f5c --- /dev/null +++ b/src/pin-input/pin-input.stories.ts @@ -0,0 +1,26 @@ +import {html, TemplateResult} from "lit"; +import "./index.js"; + +export default { + title: "PinInput", + component: "tap-pin-input", + argTypes: {}, +}; + +interface Story { + (args: T): TemplateResult; + + args?: Partial; + argTypes?: Record; +} + +interface ArgTypes { +} + +const Template: Story = ({}: ArgTypes) => html` + console.log(e)}> +`; + +export const PinInput = Template.bind({}); + +PinInput.args = {}; diff --git a/src/pin-input/pin-input.style.ts b/src/pin-input/pin-input.style.ts new file mode 100644 index 00000000..6d3df800 --- /dev/null +++ b/src/pin-input/pin-input.style.ts @@ -0,0 +1,90 @@ +import {css} from "lit"; + +export default css` + :host { + box-sizing: border-box; + direction: rtl; + --justify-content: var(--tap-pin-input-justify-content, start); + + --description-font-family: var(--tap-sys-typography-body-sm-font); + --description-font-size: var(--tap-sys-typography-body-sm-size); + --description-line-height: var(--tap-sys-typography-body-sm-height); + --description-font-weight: var(--tap-sys-typography-body-sm-weight); + + --description-font-family: var(--tap-sys-typography-body-sm-font); + --description-font-size: var(--tap-sys-typography-body-sm-size); + --description-line-height: var(--tap-sys-typography-body-sm-height); + --description-font-weight: var(--tap-sys-typography-body-sm-weight); + --description-text-color: var(--tap-sys-color-content-tertiary); + --description-error-text-color: var(--tap-sys-color-content-negative); + --description-disabled-text-color: var(--tap-sys-color-content-disabled); + + --title-font-family: var(--tap-sys-typography-body-md-font); + --title-font-size: var(--tap-sys-typography-body-md-size); + --title-line-height: var(--tap-sys-typography-body-md-height); + --title-font-weight: var(--tap-sys-typography-body-md-weight); + --title-text-color: var(--tap-sys-color-content-primary); + --title-disabled-text-color: var(--tap-sys-color-content-disabled); + + --vertical-gap: .5rem; + --horizontal-cell-gap: var(--tap-pin-input-cell-gap, 1rem); + } + + :host *, + :host *::before, + :host *::after { + box-sizing: inherit; + } + + [hidden] { + display: none !important; + } + + :host([has-error]) .description { + color: var(--description-error-text-color); + } + + :host([disabled]) .title, :host([disabled]) .description { + color: var(--title-disabled-text-color); + } + + :host([disabled]) .description { + color: var(--description-disabled-text-color); + } + + .pin-input { + + } + + .pin-input-wrapper { + display: flex; + row-gap: var(--vertical-gap); + flex-direction: column; + } + + + .title { + flex: 0 1; + font-family: var(--title-font-family); + font-size: var(--title-font-size); + font-weight: var(--title-font-weight); + line-height: var(--title-line-height); + color: var(--title-text-color); + } + + .description { + flex: 0 1; + font-family: var(--description-font-family); + font-size: var(--description-font-size); + font-weight: var(--description-font-weight); + line-height: var(--description-line-height); + color: var(--description-text-color); + } + + .input-cells { + display: flex; + justify-content: var(--justify-content); + flex-direction: row-reverse; + gap: var(--horizontal-cell-gap); + } +`; diff --git a/src/pin-input/pin-input.ts b/src/pin-input/pin-input.ts new file mode 100644 index 00000000..12b9cffd --- /dev/null +++ b/src/pin-input/pin-input.ts @@ -0,0 +1,189 @@ +import {html, LitElement, nothing} from "lit"; +import {property, queryAll} from "lit/decorators.js"; +import {range} from "lit/directives/range.js"; +import {repeat} from "lit/directives/repeat.js"; +import "../pin-input-cell"; +import {PinInputCell} from "../pin-input-cell/pin-input-cell"; +import {englishToPersian} from "../pin-input-cell/util"; +import {InputFilledEventParams} from "./types"; +import {ValueChangedEventParams} from "../pin-input-cell/types"; + +export class PinInput extends LitElement { + @queryAll('.pin-input-cell') _cells!: PinInputCell[] + + @property({type: Boolean, reflect: true}) disabled: boolean = false; + + @property({type: Boolean, reflect: true, attribute: "has-error"}) + hasError = false; + + @property({type: Boolean, attribute: 'auto-focus-first-cell'}) autoFocusFirstCell: boolean = true; + + @property({reflect: true, type: Number}) value = null; + + @property() label = ""; + + @property({reflect: true}) title = ""; + + @property({reflect: true}) description = ""; + + @property({reflect: true, type: Number}) count = 5; + + @property() size: "small" | "medium" | "large" = "medium"; + + connectedCallback(): void { + super.connectedCallback(); + } + + isFirstCellShouldAutoFocus(index: number) { + return this.autoFocusFirstCell && index === 0; + } + + private handleCellFilled(event: CustomEvent) { + this.focusNextElementByIndex(event.detail.index) + this.handleCellsFilled(); + } + + private handleCellCleared(event: CustomEvent) { + this.focusPrevElementByIndex(event.detail.index) + } + + private handleCellsFilled() { + if (this.inputValue) { + this.emitPinInputFilled(); + } + } + + private handleOverflowedCell(event: CustomEvent) { + let overflowedText = event.detail.value; + const cellIndex = event.detail.index; + const isLongerThanRemainingCells = overflowedText.length > this.lastCellIndex - cellIndex; + + if (isLongerThanRemainingCells) { + overflowedText = overflowedText.slice(0, this.lastCellIndex - cellIndex) + } + + this.fillCells(overflowedText, cellIndex + 1) + } + + 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 + this._cells[pos].setValue(char); + await this._cells[pos].updateComplete + index++ + } + } + } + + private emitPinInputFilled() { + + const event = new CustomEvent('filled', { + bubbles: true, + composed: true, + detail: { + value: this.inputValue, + cellCount: this.count, + displayValue: englishToPersian(this.inputValue!) + } as InputFilledEventParams + }) + + this.dispatchEvent(event); + } + + get cellValues() { + return [...this._cells].map((_cell, index) => _cell.value) + } + + get inputValue() { + const result = this.cellValues.join(''); + if (result.length === this.count) { + return result; + } + + return null; + } + + private focusNextElementByIndex(current: number) { + const nextIndex = current + 1; + if (this.checkIndexIsInRange(current) && !this.checkIndexIsLast(current)) { + this._cells[nextIndex].focus() + } + } + + private focusPrevElementByIndex(current: number) { + const nextIndex = current - 1 + if (this.checkIndexIsInRange(current) && !this.checkIndexIsFirst(current)) { + this._cells[nextIndex].focus() + } + } + + private checkIndexIsInRange(index: number) { + return index >= 0 && index < this.count; + } + + private checkIndexIsLast(index: number) { + return index === this.count - 1; + } + + private checkIndexIsFirst(index: number) { + return index === 0; + } + + get lastCellIndex() { + return this.count - 1; + } + + private renderTitle() { + if (typeof this.title === "string" && this.title.length) { + return html` +
${this.title}
`; + } + return nothing; + } + + private renderInputCells() { + + if (typeof this.title === "string" && this.title.length) { + return html` +
+ ${repeat( + range(this.count), + (count) => count, + (_, index) => { + return html` + `; + } + )} +
`; + } + return nothing; + } + + private renderDescription() { + if (typeof this.title === "string" && this.title.length) { + return html` +
${this.description}
`; + } + return nothing; + } + + render() { + return html` +
+ ${this.renderTitle()} ${this.renderInputCells()} + ${this.renderDescription()} +
+ `; + } +} diff --git a/src/pin-input/types.ts b/src/pin-input/types.ts new file mode 100644 index 00000000..ce99f268 --- /dev/null +++ b/src/pin-input/types.ts @@ -0,0 +1,5 @@ +export type InputFilledEventParams = { + cellCount: number, + value: string, + displayValue: string, +} From ce622598c30f71c206faef3967dfe83db20b3e6f Mon Sep 17 00:00:00 2001 From: Hossein Nasiri Date: Sat, 11 May 2024 11:26:12 +0330 Subject: [PATCH 2/9] fix: Fix eslint hint errors --- src/pin-input-cell/index.ts | 10 +- src/pin-input-cell/pin-input-cell.stories.ts | 10 +- src/pin-input-cell/pin-input-cell.style.ts | 2 +- src/pin-input-cell/pin-input-cell.ts | 314 +++++++++-------- src/pin-input-cell/types.ts | 11 +- src/pin-input-cell/util.ts | 21 +- src/pin-input/index.ts | 10 +- src/pin-input/pin-input.stories.ts | 19 +- src/pin-input/pin-input.style.ts | 9 +- src/pin-input/pin-input.ts | 341 ++++++++++--------- 10 files changed, 385 insertions(+), 362 deletions(-) diff --git a/src/pin-input-cell/index.ts b/src/pin-input-cell/index.ts index 5e43ff9c..f459296b 100644 --- a/src/pin-input-cell/index.ts +++ b/src/pin-input-cell/index.ts @@ -1,14 +1,14 @@ -import { customElement } from "lit/decorators.js"; -import { PinInputCell } from "./pin-input-cell.js"; -import styles from "./pin-input-cell.style.js"; +import { customElement } from 'lit/decorators.js'; +import { PinInputCell } from './pin-input-cell.js'; +import styles from './pin-input-cell.style.js'; -@customElement("tap-pin-input-cell") +@customElement('tap-pin-input-cell') export class TapPinInputCell extends PinInputCell { static readonly styles = [styles]; } declare global { interface HTMLElementTagNameMap { - "tap-pin-input-cell": TapPinInputCell; + 'tap-pin-input-cell': TapPinInputCell; } } diff --git a/src/pin-input-cell/pin-input-cell.stories.ts b/src/pin-input-cell/pin-input-cell.stories.ts index 3f7e3257..3046ddd0 100644 --- a/src/pin-input-cell/pin-input-cell.stories.ts +++ b/src/pin-input-cell/pin-input-cell.stories.ts @@ -1,9 +1,9 @@ -import { html, TemplateResult } from "lit"; -import "./index.js"; +import { html, TemplateResult } from 'lit'; +import './index.js'; export default { - title: "PinInput", - component: "tap-pin-input-cell", + title: 'PinInput', + component: 'tap-pin-input-cell', argTypes: {}, }; @@ -15,7 +15,7 @@ interface Story { interface ArgTypes {} -const Template: Story = ({}: ArgTypes) => html` +const Template: Story = (/*{}: ArgTypes*/) => html` `; diff --git a/src/pin-input-cell/pin-input-cell.style.ts b/src/pin-input-cell/pin-input-cell.style.ts index 00df867d..d445cc02 100644 --- a/src/pin-input-cell/pin-input-cell.style.ts +++ b/src/pin-input-cell/pin-input-cell.style.ts @@ -1,4 +1,4 @@ -import { css } from "lit"; +import { css } from 'lit'; export default css` :host { diff --git a/src/pin-input-cell/pin-input-cell.ts b/src/pin-input-cell/pin-input-cell.ts index b6cdc8e6..6babd95c 100644 --- a/src/pin-input-cell/pin-input-cell.ts +++ b/src/pin-input-cell/pin-input-cell.ts @@ -1,186 +1,206 @@ -import {html, LitElement, PropertyValues} from "lit"; -import {property, query} from "lit/decorators.js"; -import {classMap} from "lit/directives/class-map.js"; -import {englishToPersian, isDeletionKey, isValidDigit, persianToEnglish,} from "./util"; -import {ValueChangedEventParams} from "./types"; +import { html, LitElement, PropertyValues } from 'lit'; +import { property, query } from 'lit/decorators.js'; +import { classMap } from 'lit/directives/class-map.js'; +import { + englishToPersian, + isDeletionKey, + isValidDigit, + persianToEnglish, +} from './util'; +import { ValueChangedEventParams } from './types'; export class PinInputCell extends LitElement { - @query('.cell') _cell!: HTMLInputElement; + @query('.cell') _cell!: HTMLInputElement; - @property({type: Boolean, reflect: true}) disabled: boolean = false; + @property({ type: Boolean, reflect: true }) disabled: boolean = false; - @property({attribute: 'auto-focus', type: Boolean}) autoFocus: boolean = false; + @property({ attribute: 'auto-focus', type: Boolean }) autoFocus: boolean = + false; - @property({reflect: true, type: Boolean, attribute: "has-error"}) hasError = - false; + @property({ reflect: true, type: Boolean, attribute: 'has-error' }) hasError = + false; - @property({reflect: true, type: String}) value: string = ""!; + @property({ reflect: true, type: String }) value: string = ''; - @property() label = ""; + @property() label = ''; - @property() size: "small" | "medium" | "large" = "medium"; + @property() size: 'small' | 'medium' | 'large' = 'medium'; - @property({type: Number}) index: number = null!; + @property({ type: Number }) index: number = null!; - protected updated(changed: PropertyValues) { - if (changed.has("value") && !Number.isNaN(this.value)) { - // - } + protected updated(changed: PropertyValues) { + if (changed.has('value') && !Number.isNaN(this.value)) { + // } + } + private async updateInputValue(newValue: string) { + this.value = persianToEnglish(newValue); + this._cell.value = englishToPersian(newValue); - private updateInputValue(newValue: string) { - this.value = persianToEnglish(newValue); - (this._cell).value = englishToPersian(newValue); - - if (newValue.length) { - this.emitValueChanged() - } - - if (newValue.length === 0) { - this.emitValueCleared() - } - } - - private async emitValueChanged() { - await this.updateComplete - const event = new CustomEvent('cell-filled', { - bubbles: true, composed: false, detail: { - cell: this, - index: this.index, - value: this.value, - } as ValueChangedEventParams - }) - this.dispatchEvent(event) + if (newValue.length) { + await this.emitValueChanged(); } - private async emitValueCleared() { - await this.updateComplete - const event = new CustomEvent('cell-cleared', { - bubbles: true, composed: false, detail: { - cell: this, - index: this.index, - value: this.value, - } as ValueChangedEventParams - }) - this.dispatchEvent(event) + if (newValue.length === 0) { + await this.emitValueCleared(); } - - private async emitOverflowedValue(value: string) { - await this.updateComplete - const event = new CustomEvent('overflow-value', { - bubbles: true, composed: false, detail: { - cell: this, - index: this.index, - value: value, - } as ValueChangedEventParams - }) - this.dispatchEvent(event) + } + + private async emitValueChanged() { + await this.updateComplete; + const event = new CustomEvent('cell-filled', { + bubbles: true, + composed: false, + detail: { + cell: this, + index: this.index, + value: this.value, + } as ValueChangedEventParams, + }); + this.dispatchEvent(event); + } + + private async emitValueCleared() { + await this.updateComplete; + const event = new CustomEvent('cell-cleared', { + bubbles: true, + composed: false, + detail: { + cell: this, + index: this.index, + value: this.value, + } as ValueChangedEventParams, + }); + this.dispatchEvent(event); + } + + private async emitOverflowedValue(value: string) { + await this.updateComplete; + const event = new CustomEvent('overflow-value', { + bubbles: true, + composed: false, + detail: { + cell: this, + index: this.index, + value: value, + } as ValueChangedEventParams, + }); + this.dispatchEvent(event); + } + + private async handleInput(event: InputEvent) { + const inputValue = (event.target as HTMLInputElement).value.replace( + /[^\d۰-۹]/g, + '', + ); + + if (typeof inputValue === 'string' && inputValue.length >= 1) { + const lastCharacterIndex = inputValue.length - 1; + await this.updateInputValue(inputValue[lastCharacterIndex]); + } else { + await this.updateInputValue(''); } + } - private handleInput(event: InputEvent) { - const inputValue = (event.target as HTMLInputElement).value.replace(/[^\d۰-۹]/g, ''); + protected firstUpdated(_changedProperties: PropertyValues) { + super.firstUpdated(_changedProperties); - if (typeof inputValue === 'string' && inputValue.length >= 1) { - const lastCharacterIndex = inputValue.length - 1 - this.updateInputValue(inputValue[lastCharacterIndex]); - } else { - this.updateInputValue(''); - } + if (this.autoFocus) { + this._cell.focus?.(); } + } - protected firstUpdated(_changedProperties: PropertyValues) { - super.firstUpdated(_changedProperties); - - if (this.autoFocus) { - this._cell.focus?.() - } + private async validatePressedKey(event: KeyboardEvent) { + if (event.key === 'Backspace' && this.value === '') { + event.preventDefault(); + await this.handleEmptyCellBackspace(); + return; } - private validatePressedKey(event: KeyboardEvent) { - if (event.key === 'Backspace' && this.value === '') { - event.preventDefault(); - this.handleEmptyCellBackspace() - return; - } - - if (isValidDigit(event.key) || isDeletionKey(event.key)) { - return true; - } - - if ([8, 9, 13, 27, 46, 110, 190].includes(event.keyCode) || - // Allow Ctrl+A, Ctrl+C, Ctrl+V, Ctrl+X - (event.keyCode == 65 && (event.ctrlKey === true || event.metaKey === true)) || - (event.keyCode == 67 && (event.ctrlKey === true || event.metaKey === true)) || - (event.keyCode == 86 && (event.ctrlKey === true || event.metaKey === true)) || - (event.keyCode == 88 && (event.ctrlKey === true || event.metaKey === true))) { - return; - } - - event.preventDefault(); + if (isValidDigit(event.key) || isDeletionKey(event.key)) { + return true; } - private handlePaste(e: ClipboardEvent) { - const text: string = e.clipboardData?.getData('text/plain') || '' - if (text && !this.isValidNumericText(text)) { - e.preventDefault(); - } - - if (text?.length > 1) { - this.updateInputValue(text[0]); - this.emitOverflowedValue(text.slice(1)) - e.preventDefault(); - return; - } + if ( + [8, 9, 13, 27, 46, 110, 190].includes(event.keyCode) || + // Allow Ctrl+A, Ctrl+C, Ctrl+V, Ctrl+X + (event.keyCode == 65 && + (event.ctrlKey === true || event.metaKey === true)) || + (event.keyCode == 67 && + (event.ctrlKey === true || event.metaKey === true)) || + (event.keyCode == 86 && + (event.ctrlKey === true || event.metaKey === true)) || + (event.keyCode == 88 && + (event.ctrlKey === true || event.metaKey === true)) + ) { + return; } - private isValidNumericText(input: string) { - if (typeof input === 'string' && input.length > 0) { - const numericRegex = /^[0-9۰-۹]+$/; - return numericRegex.test(input) - } + event.preventDefault(); + } + + private async handlePaste(e: ClipboardEvent) { + const text: string = e.clipboardData?.getData('text/plain') || ''; + if (text && !this.isValidNumericText(text)) { + e.preventDefault(); } - private handleEmptyCellBackspace() { - this.emitValueCleared(); + if (text?.length > 1) { + await this.updateInputValue(text[0]); + await this.emitOverflowedValue(text.slice(1)); + e.preventDefault(); + return; } + } - private handleFocus(e: FocusEvent) { - const _target = e.target as HTMLInputElement - if (_target.value?.length > 0) { - _target.select(); - } + private isValidNumericText(input: string) { + if (typeof input === 'string' && input.length > 0) { + const numericRegex = /^[0-9۰-۹]+$/; + return numericRegex.test(input); } + } + + private async handleEmptyCellBackspace() { + await this.emitValueCleared(); + } - override focus() { - if (this._cell) { - this._cell.focus?.(); - } + private handleFocus(e: FocusEvent) { + const _target = e.target as HTMLInputElement; + if (_target.value?.length > 0) { + _target.select(); } + } - setValue(value: string) { - if (value.length === 1 && this.value === '') { - this.updateInputValue(value) - } + override focus() { + if (this._cell) { + this._cell.focus?.(); } + } - render() { - return html` - - `; + async setValue(value: string) { + if (value.length === 1 && this.value === '') { + await this.updateInputValue(value); } + } + + render() { + return html` + this.handleInput(e)} + @paste=${(e: ClipboardEvent) => this.handlePaste(e)} + @focus=${(e: FocusEvent) => this.handleFocus(e)} + @keydown=${(e: KeyboardEvent) => this.validatePressedKey(e)} + class="cell ${classMap({ + 'cell-sm': this.size === 'small', + 'cell-md': this.size === 'medium', + 'cell-lg': this.size === 'large', + 'has-error': this.hasError, + })}" + /> + `; + } } diff --git a/src/pin-input-cell/types.ts b/src/pin-input-cell/types.ts index 9cfc080f..4b1cae59 100644 --- a/src/pin-input-cell/types.ts +++ b/src/pin-input-cell/types.ts @@ -1,8 +1,7 @@ -import {PinInputCell} from "./pin-input-cell"; +import { PinInputCell } from './pin-input-cell'; export type ValueChangedEventParams = { - cell: PinInputCell, - index: number, - value: string, -} - + cell: PinInputCell; + index: number; + value: string; +}; diff --git a/src/pin-input-cell/util.ts b/src/pin-input-cell/util.ts index 033cdee4..6d0af293 100644 --- a/src/pin-input-cell/util.ts +++ b/src/pin-input-cell/util.ts @@ -1,8 +1,8 @@ export function englishToPersian(input: string): string { - const localInput = `${input}` - const englishDigits = "0123456789"; - const persianDigits = "۰۱۲۳۴۵۶۷۸۹"; - let output = ""; + const localInput = `${input}`; + const englishDigits = '0123456789'; + const persianDigits = '۰۱۲۳۴۵۶۷۸۹'; + let output = ''; for (const char of localInput) { const index = englishDigits.indexOf(char); @@ -18,9 +18,9 @@ export function englishToPersian(input: string): string { export function persianToEnglish(input: string): string { const localInput = `${input}`; - const persianDigits = "۰۱۲۳۴۵۶۷۸۹"; - const englishDigits = "0123456789"; - let output = ""; + const persianDigits = '۰۱۲۳۴۵۶۷۸۹'; + const englishDigits = '0123456789'; + let output = ''; for (const char of localInput) { const index = persianDigits.indexOf(char); @@ -35,17 +35,16 @@ export function persianToEnglish(input: string): string { } export function isValidDigit(input: string): boolean { - const englishDigits = "0123456789۰۱۲۳۴۵۶۷۸۹"; + const englishDigits = '0123456789۰۱۲۳۴۵۶۷۸۹'; - if (typeof input === "string" && input.length === 1) { + if (typeof input === 'string' && input.length === 1) { return englishDigits.indexOf(input) !== -1; } return false; } -type DeleteKeys = "Meta" | "Delete" | "Backspace"; -const deletionKeys = ["Meta", "Delete", "Backspace"]; +const deletionKeys = ['Meta', 'Delete', 'Backspace']; export function isDeletionKey(key: string): boolean { return deletionKeys.includes(key); diff --git a/src/pin-input/index.ts b/src/pin-input/index.ts index 269ba275..e6630a6a 100644 --- a/src/pin-input/index.ts +++ b/src/pin-input/index.ts @@ -1,14 +1,14 @@ -import { customElement } from "lit/decorators.js"; -import { PinInput } from "./pin-input.js"; -import styles from "./pin-input.style.js"; +import { customElement } from 'lit/decorators.js'; +import { PinInput } from './pin-input.js'; +import styles from './pin-input.style.js'; -@customElement("tap-pin-input") +@customElement('tap-pin-input') export class TapPinInput extends PinInput { static readonly styles = [styles]; } declare global { interface HTMLElementTagNameMap { - "tap-pin-input": TapPinInput; + 'tap-pin-input': TapPinInput; } } diff --git a/src/pin-input/pin-input.stories.ts b/src/pin-input/pin-input.stories.ts index eb749f5c..f8058e62 100644 --- a/src/pin-input/pin-input.stories.ts +++ b/src/pin-input/pin-input.stories.ts @@ -1,9 +1,9 @@ -import {html, TemplateResult} from "lit"; -import "./index.js"; +import { html, TemplateResult } from 'lit'; +import './index.js'; export default { - title: "PinInput", - component: "tap-pin-input", + title: 'PinInput', + component: 'tap-pin-input', argTypes: {}, }; @@ -14,11 +14,14 @@ interface Story { argTypes?: Record; } -interface ArgTypes { -} +interface ArgTypes {} -const Template: Story = ({}: ArgTypes) => html` - console.log(e)}> +const Template: Story = (/*{}: ArgTypes*/) => html` + console.log(e)} + > `; export const PinInput = Template.bind({}); diff --git a/src/pin-input/pin-input.style.ts b/src/pin-input/pin-input.style.ts index 6d3df800..2f699e7f 100644 --- a/src/pin-input/pin-input.style.ts +++ b/src/pin-input/pin-input.style.ts @@ -1,4 +1,4 @@ -import {css} from "lit"; +import { css } from 'lit'; export default css` :host { @@ -26,7 +26,7 @@ export default css` --title-text-color: var(--tap-sys-color-content-primary); --title-disabled-text-color: var(--tap-sys-color-content-disabled); - --vertical-gap: .5rem; + --vertical-gap: 0.5rem; --horizontal-cell-gap: var(--tap-pin-input-cell-gap, 1rem); } @@ -44,7 +44,8 @@ export default css` color: var(--description-error-text-color); } - :host([disabled]) .title, :host([disabled]) .description { + :host([disabled]) .title, + :host([disabled]) .description { color: var(--title-disabled-text-color); } @@ -53,7 +54,6 @@ export default css` } .pin-input { - } .pin-input-wrapper { @@ -62,7 +62,6 @@ export default css` flex-direction: column; } - .title { flex: 0 1; font-family: var(--title-font-family); diff --git a/src/pin-input/pin-input.ts b/src/pin-input/pin-input.ts index 12b9cffd..4542e373 100644 --- a/src/pin-input/pin-input.ts +++ b/src/pin-input/pin-input.ts @@ -1,189 +1,192 @@ -import {html, LitElement, nothing} from "lit"; -import {property, queryAll} from "lit/decorators.js"; -import {range} from "lit/directives/range.js"; -import {repeat} from "lit/directives/repeat.js"; -import "../pin-input-cell"; -import {PinInputCell} from "../pin-input-cell/pin-input-cell"; -import {englishToPersian} from "../pin-input-cell/util"; -import {InputFilledEventParams} from "./types"; -import {ValueChangedEventParams} from "../pin-input-cell/types"; +import { html, LitElement, nothing } from 'lit'; +import { property, queryAll } from 'lit/decorators.js'; +import { range } from 'lit/directives/range.js'; +import { repeat } from 'lit/directives/repeat.js'; +import '../pin-input-cell'; +import { PinInputCell } from '../pin-input-cell/pin-input-cell'; +import { englishToPersian } from '../pin-input-cell/util'; +import { InputFilledEventParams } from './types'; +import { ValueChangedEventParams } from '../pin-input-cell/types'; export class PinInput extends LitElement { - @queryAll('.pin-input-cell') _cells!: PinInputCell[] + @queryAll('.pin-input-cell') _cells!: PinInputCell[]; - @property({type: Boolean, reflect: true}) disabled: boolean = false; + @property({ type: Boolean, reflect: true }) disabled: boolean = false; - @property({type: Boolean, reflect: true, attribute: "has-error"}) - hasError = false; + @property({ type: Boolean, reflect: true, attribute: 'has-error' }) + hasError = false; - @property({type: Boolean, attribute: 'auto-focus-first-cell'}) autoFocusFirstCell: boolean = true; + @property({ type: Boolean, attribute: 'auto-focus-first-cell' }) + autoFocusFirstCell: boolean = true; - @property({reflect: true, type: Number}) value = null; + @property({ reflect: true, type: Number }) value = null; - @property() label = ""; + @property() label = ''; - @property({reflect: true}) title = ""; + @property({ reflect: true }) title = ''; - @property({reflect: true}) description = ""; + @property({ reflect: true }) description = ''; - @property({reflect: true, type: Number}) count = 5; + @property({ reflect: true, type: Number }) count = 5; - @property() size: "small" | "medium" | "large" = "medium"; + @property() size: 'small' | 'medium' | 'large' = 'medium'; - connectedCallback(): void { - super.connectedCallback(); - } - - isFirstCellShouldAutoFocus(index: number) { - return this.autoFocusFirstCell && index === 0; - } - - private handleCellFilled(event: CustomEvent) { - this.focusNextElementByIndex(event.detail.index) - this.handleCellsFilled(); - } + connectedCallback(): void { + super.connectedCallback(); + } - private handleCellCleared(event: CustomEvent) { - this.focusPrevElementByIndex(event.detail.index) - } - - private handleCellsFilled() { - if (this.inputValue) { - this.emitPinInputFilled(); - } - } + isFirstCellShouldAutoFocus(index: number) { + return this.autoFocusFirstCell && index === 0; + } - private handleOverflowedCell(event: CustomEvent) { - let overflowedText = event.detail.value; - const cellIndex = event.detail.index; - const isLongerThanRemainingCells = overflowedText.length > this.lastCellIndex - cellIndex; + private handleCellFilled(event: CustomEvent) { + this.focusNextElementByIndex(event.detail.index); + this.handleCellsFilled(); + } - if (isLongerThanRemainingCells) { - overflowedText = overflowedText.slice(0, this.lastCellIndex - cellIndex) - } - - this.fillCells(overflowedText, cellIndex + 1) - } + private handleCellCleared(event: CustomEvent) { + this.focusPrevElementByIndex(event.detail.index); + } - 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 - this._cells[pos].setValue(char); - await this._cells[pos].updateComplete - index++ - } - } + private handleCellsFilled() { + if (this.inputValue) { + this.emitPinInputFilled(); } + } - private emitPinInputFilled() { + private async handleOverflowedCell( + event: CustomEvent, + ) { + let overflowedText = event.detail.value; + const cellIndex = event.detail.index; + const isLongerThanRemainingCells = + overflowedText.length > this.lastCellIndex - cellIndex; - const event = new CustomEvent('filled', { - bubbles: true, - composed: true, - detail: { - value: this.inputValue, - cellCount: this.count, - displayValue: englishToPersian(this.inputValue!) - } as InputFilledEventParams - }) - - this.dispatchEvent(event); - } - - get cellValues() { - return [...this._cells].map((_cell, index) => _cell.value) - } - - get inputValue() { - const result = this.cellValues.join(''); - if (result.length === this.count) { - return result; - } - - return null; - } - - private focusNextElementByIndex(current: number) { - const nextIndex = current + 1; - if (this.checkIndexIsInRange(current) && !this.checkIndexIsLast(current)) { - this._cells[nextIndex].focus() - } - } - - private focusPrevElementByIndex(current: number) { - const nextIndex = current - 1 - if (this.checkIndexIsInRange(current) && !this.checkIndexIsFirst(current)) { - this._cells[nextIndex].focus() - } - } - - private checkIndexIsInRange(index: number) { - return index >= 0 && index < this.count; - } - - private checkIndexIsLast(index: number) { - return index === this.count - 1; - } - - private checkIndexIsFirst(index: number) { - return index === 0; - } - - get lastCellIndex() { - return this.count - 1; - } - - private renderTitle() { - if (typeof this.title === "string" && this.title.length) { - return html` -
${this.title}
`; - } - return nothing; - } - - private renderInputCells() { - - if (typeof this.title === "string" && this.title.length) { - return html` -
- ${repeat( - range(this.count), - (count) => count, - (_, index) => { - return html` - `; - } - )} -
`; - } - return nothing; - } - - private renderDescription() { - if (typeof this.title === "string" && this.title.length) { - return html` -
${this.description}
`; - } - return nothing; - } - - render() { - return html` -
- ${this.renderTitle()} ${this.renderInputCells()} - ${this.renderDescription()} -
- `; - } + if (isLongerThanRemainingCells) { + overflowedText = overflowedText.slice(0, this.lastCellIndex - cellIndex); + } + + await this.fillCells(overflowedText, cellIndex + 1); + } + + 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++; + } + } + } + + private emitPinInputFilled() { + const event = new CustomEvent('filled', { + bubbles: true, + composed: true, + detail: { + value: this.inputValue, + cellCount: this.count, + displayValue: englishToPersian(this.inputValue!), + } as InputFilledEventParams, + }); + + this.dispatchEvent(event); + } + + get cellValues() { + return [...this._cells].map((_cell) => _cell.value); + } + + get inputValue() { + const result = this.cellValues.join(''); + if (result.length === this.count) { + return result; + } + + return null; + } + + private focusNextElementByIndex(current: number) { + const nextIndex = current + 1; + if (this.checkIndexIsInRange(current) && !this.checkIndexIsLast(current)) { + this._cells[nextIndex].focus(); + } + } + + private focusPrevElementByIndex(current: number) { + const nextIndex = current - 1; + if (this.checkIndexIsInRange(current) && !this.checkIndexIsFirst(current)) { + this._cells[nextIndex].focus(); + } + } + + private checkIndexIsInRange(index: number) { + return index >= 0 && index < this.count; + } + + private checkIndexIsLast(index: number) { + return index === this.count - 1; + } + + private checkIndexIsFirst(index: number) { + return index === 0; + } + + get lastCellIndex() { + return this.count - 1; + } + + private renderTitle() { + if (typeof this.title === 'string' && this.title.length) { + return html`
${this.title}
`; + } + return nothing; + } + + private renderInputCells() { + if (typeof this.title === 'string' && this.title.length) { + return html` +
+ ${repeat( + range(this.count), + (count) => count, + (_, index) => { + return html` ) => + this.handleCellFilled(e)} + @cell-cleared=${(e: CustomEvent) => + this.handleCellCleared(e)} + @overflow-value=${(e: CustomEvent) => + this.handleOverflowedCell(e)} + ?auto-focus=${this.isFirstCellShouldAutoFocus(index)} + >`; + }, + )} +
+ `; + } + return nothing; + } + + private renderDescription() { + if (typeof this.title === 'string' && this.title.length) { + return html`
${this.description}
`; + } + return nothing; + } + + render() { + return html` +
+ ${this.renderTitle()} ${this.renderInputCells()} + ${this.renderDescription()} +
+ `; + } } From 7ce78062545851bdfcb6bfedaf297d442102deb6 Mon Sep 17 00:00:00 2001 From: Hossein Nasiri Date: Sat, 11 May 2024 11:36:35 +0330 Subject: [PATCH 3/9] fix: Pass down `size` prop --- src/pin-input/pin-input.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pin-input/pin-input.ts b/src/pin-input/pin-input.ts index 4542e373..f2a97908 100644 --- a/src/pin-input/pin-input.ts +++ b/src/pin-input/pin-input.ts @@ -165,6 +165,7 @@ export class PinInput extends LitElement { @overflow-value=${(e: CustomEvent) => this.handleOverflowedCell(e)} ?auto-focus=${this.isFirstCellShouldAutoFocus(index)} + .size=${this.size} >`; }, )} From 0a44b925f5bd858a69558c851107f9c75e8caed0 Mon Sep 17 00:00:00 2001 From: Hossein Nasiri Date: Sat, 11 May 2024 12:17:08 +0330 Subject: [PATCH 4/9] feat: Add focus prev and next element and clear all cells with meta keys --- src/pin-input-cell/pin-input-cell.ts | 69 ++++++++++++++++++++++++++-- src/pin-input-cell/types.ts | 4 +- src/pin-input-cell/util.ts | 25 ++++++++-- src/pin-input/pin-input.ts | 49 +++++++++++++++++++- 4 files changed, 138 insertions(+), 9 deletions(-) diff --git a/src/pin-input-cell/pin-input-cell.ts b/src/pin-input-cell/pin-input-cell.ts index 6babd95c..0351d466 100644 --- a/src/pin-input-cell/pin-input-cell.ts +++ b/src/pin-input-cell/pin-input-cell.ts @@ -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'; @@ -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', { @@ -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; } @@ -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) { @@ -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` = { cell: PinInputCell; index: number; - value: string; + value: T; }; diff --git a/src/pin-input-cell/util.ts b/src/pin-input-cell/util.ts index 6d0af293..ff883953 100644 --- a/src/pin-input-cell/util.ts +++ b/src/pin-input-cell/util.ts @@ -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; } diff --git a/src/pin-input/pin-input.ts b/src/pin-input/pin-input.ts index f2a97908..770245b5 100644 --- a/src/pin-input/pin-input.ts +++ b/src/pin-input/pin-input.ts @@ -68,6 +68,43 @@ export class PinInput extends LitElement { await this.fillCells(overflowedText, cellIndex + 1); } + private async handleClearPrevCells( + event: CustomEvent, + ) { + 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>, + ) { + 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) { @@ -75,7 +112,6 @@ export class PinInput extends LitElement { for (const char of value.split('')) { const pos = index + startingAt; await this._cells[pos].setValue(char); - await this._cells[pos].updateComplete; index++; } } @@ -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)) { @@ -164,6 +206,11 @@ export class PinInput extends LitElement { this.handleCellCleared(e)} @overflow-value=${(e: CustomEvent) => this.handleOverflowedCell(e)} + @clear-all=${(e: CustomEvent) => + this.handleClearPrevCells(e)} + @arrow-key-pressed=${( + e: CustomEvent>, + ) => this.handleArrowKeyPressed(e)} ?auto-focus=${this.isFirstCellShouldAutoFocus(index)} .size=${this.size} >`; From fe449023b5e959c328770bfc675deca3f6f0ab31 Mon Sep 17 00:00:00 2001 From: Hossein Nasiri Date: Mon, 8 Jul 2024 07:39:47 +0330 Subject: [PATCH 5/9] fix: utilize tap system color tokens --- src/pin-input-cell/pin-input-cell.style.ts | 127 ++++++++++----------- src/pin-input-cell/pin-input-cell.ts | 22 ++-- src/pin-input/pin-input.style.ts | 81 ++++++------- 3 files changed, 111 insertions(+), 119 deletions(-) diff --git a/src/pin-input-cell/pin-input-cell.style.ts b/src/pin-input-cell/pin-input-cell.style.ts index d445cc02..3bb00936 100644 --- a/src/pin-input-cell/pin-input-cell.style.ts +++ b/src/pin-input-cell/pin-input-cell.style.ts @@ -1,58 +1,46 @@ -import { css } from 'lit'; +import {css} from 'lit'; export default css` :host { - box-sizing: border-box; - --height: var(--tap-pin-input-cell-height, 52px); - --bg-color: var(--tap-pin-input-cell-bg-color, #eaeded); - --error-bg-color: var(--tap-pin-input-cell-error-bg-color, #ffefed); - --disabled-bg-color: var( - --tap-pin-input-cell-disabled-bg-color, - var(--tap-sys-color-surface-disabled) - ); - --color: var(--tap-pin-input-cell-color, #000); - --disabled-color: var( - --tap-pin-input-cell-disabled-color, - var(--tap-sys-color-content-disabled) - ); - --border-radius: var(--tap-pin-input-cell-border-radius, 8px); - --border-width: var(--tap-pin-input-cell-border-width, 2px); - --border-default-color: var( - --tap-pin-input-cell-border-default-color, - transparent - ); - --max-cell-width-ratio: var( - --tap-pin-input-cell-max-cell-width-ratio, - 1.25 - ); - --border-focus-color: var(--tap-pin-input-cell-border-focus-color, #323333); - --border-error-color: var(--tap-pin-input-cell-border-error-color, #f1998e); - --border-disabled-color: var( - --tap-pin-input-cell-border-disabled-color, - transparent - ); + --tap-pin-input-cell-box-sizing: border-box; + --tap-pin-input-cell-height: 52px; + --tap-pin-input-cell-max-cell-width-ratio: 1.25; + + --tap-pin-input-cell-font-family: var(--tap-sys-font-family); + --tap-pin-input-cell-font-size-small: var(--tap-sys-typography-headline-xs-size, 16px); + --tap-pin-input-cell-font-size-medium: var(--tap-sys-typography-headline-sm-size, 20px); + --tap-pin-input-cell-font-size-large: var(--tap-sys-typography-headline-md-size, 24px); - --font-size-sm: var(--tap-pin-input-cell-font-size-small, 16px); - --font-size-md: var(--tap-pin-input-cell-font-size-medium, 20px); - --font-size-lg: var(--tap-pin-input-cell-font-size-large, 24px); + --tap-pin-input-cell-font-weight-small: var(--tap-sys-typography-headline-xs-weight, 600); + --tap-pin-input-cell-font-weight-medium: var(--tap-sys-typography-headline-sm-weight, 600); + --tap-pin-input-cell-font-weight-large: var(--tap-sys-typography-headline-md-weight, 600); - --font-weight-sm: var(--tap-pin-input-cell-font-weight-small, 400); - --font-weight-md: var(--tap-pin-input-cell-font-weight-medium, 600); - --font-weight-lg: var(--tap-pin-input-cell-font-weight-large, 600); + --tap-pin-input-cell-line-height-small: var(--tap-sys-typography-headline-xs-height, 26px); + --tap-pin-input-cell-line-height-medium: var(--tap-sys-typography-headline-sm-height, 32px); + --tap-pin-input-cell-line-height-large: var(--tap-sys-typography-headline-md-height, 36px); - --line-height-sm: var(--tap-pin-input-cell-line-height-small, 26px); - --line-height-md: var(--tap-pin-input-cell-line-height-medium, 32px); - --line-height-lg: var(--tap-pin-input-cell-line-height-large, 36px); + --tap-pin-input-cell-padding-small: var(--tap-sys-spacing-3, 6px); + --tap-pin-input-cell-padding-medium: var(--tap-sys-spacing-4, 8px); + --tap-pin-input-cell-padding-large: var(--tap-sys-spacing-5, 12px); - --padding-sm: var(--tap-pin-input-cell-padding-small, 6px); - --padding-md: var(--tap-pin-input-cell-padding-medium, 8px); - --padding-lg: var(--tap-pin-input-cell-padding-large, 12px); + --tap-pin-input-cell-bg-color: var(--tap-palette-gray-100); + --tap-pin-input-cell-error-bg-color: var(--tap-palette-red-50); + --tap-pin-input-cell-disabled-bg-color: var(--tap-sys-color-surface-disabled); + --tap-pin-input-cell-color: var(--tap-palette-black); + --tap-pin-input-cell-disabled-color: var(--tap-sys-color-content-disabled); + + --tap-pin-input-cell-border-focus-color: var(--tap-sys-color-border-focus); + --tap-pin-input-cell-border-error-color: var(--tap-sys-color-border-negative); + --tap-pin-input-cell-border-disabled-color: transparent; + --tap-pin-input-cell-border-radius: var(--tap-sys-radius-3); + --tap-pin-input-cell-border-width: var(--tap-sys-stroke-4); + --tap-pin-input-cell-border-default-color: transparent; } :host *, :host *::before, :host *::after { - box-sizing: inherit; + box-sizing: var(--tap-pin-input-cell-box-sizing, inherit); } [hidden] { @@ -63,53 +51,56 @@ export default css` border: none; padding: 0; margin: 0; - background-color: var(--bg-color); - color: var(--color); - border: var(--border-width) solid var(--border-default-color); - border-radius: var(--border-radius); + background-color: var(--tap-pin-input-cell-bg-color); + color: var(--tap-pin-input-cell-color); + border: var(--tap-pin-input-cell-border-width) solid var(--tap-pin-input-cell-border-default-color); + border-radius: var(--tap-pin-input-cell-border-radius); outline: none; min-width: 0; text-align: center; - font-family: var(--tap-sys-font-family); - min-width: var(--height); - max-width: calc(var(--height) * var(--max-cell-width-ratio)); + font-family: var(--tap-pin-input-cell-font-family); + min-width: var(--tap-pin-input-cell-height); + max-width: calc( + var(--tap-pin-input-cell-height) * + var(--tap-pin-input-cell-max-cell-width-ratio) + ); } .cell[disabled] { - border-color: var(--border-disabled-color); - background-color: var(--disabled-bg-color); - color: var(--disabled-color); + border-color: var(--tap-pin-input-cell-border-disabled-color); + background-color: var(--tap-pin-input-cell-disabled-bg-color); + color: var(--tap-pin-input-cell-border-disabled-color); user-select: none; pointer-events: none; } .cell:focus { - border-color: var(--border-focus-color); + border-color: var(--tap-pin-input-cell-border-focus-color); } .cell.cell-sm { - padding: var(--padding-sm); - font-size: var(--font-size-sm); - font-weight: var(--font-weight-sm); - line-height: var(--line-height-sm); + padding: var(--tap-pin-input-cell-padding-small); + font-size: var(--tap-pin-input-cell-font-size-small); + font-weight: var(--tap-pin-input-cell-font-weight-small); + line-height: var(--tap-pin-input-cell-line-height-small); } .cell.cell-md { - padding: var(--padding-md); - font-size: var(--font-size-md); - font-weight: var(--font-weight-md); - line-height: var(--line-height-md); + padding: var(--tap-pin-input-cell-padding-medium); + font-size: var(--tap-pin-input-cell-font-size-medium); + font-weight: var(--tap-pin-input-cell-font-weight-medium); + line-height: var(--tap-pin-input-cell-line-height-medium); } .cell.cell-lg { - padding: var(--padding-lg); - font-size: var(--font-size-lg); - font-weight: var(--font-weight-lg); - line-height: var(--line-height-lg); + padding: var(--tap-pin-input-cell-padding-large); + font-size: var(--tap-pin-input-cell-font-size-large); + font-weight: var(--tap-pin-input-cell-font-weight-large); + line-height: var(--tap-pin-input-cell-line-height-large); } .cell.has-error { - background-color: var(--error-bg-color); - border-color: var(--border-error-color); + background-color: var(--tap-pin-input-cell-error-bg-color); + border-color: var(--tap-pin-input-cell-error-bg-color); } `; diff --git a/src/pin-input-cell/pin-input-cell.ts b/src/pin-input-cell/pin-input-cell.ts index 0351d466..9045ab76 100644 --- a/src/pin-input-cell/pin-input-cell.ts +++ b/src/pin-input-cell/pin-input-cell.ts @@ -1,34 +1,33 @@ -import { html, LitElement, PropertyValues } from 'lit'; -import { property, query } from 'lit/decorators.js'; -import { classMap } from 'lit/directives/class-map.js'; +import {html, LitElement, PropertyValues} from 'lit'; +import {property, query} from 'lit/decorators.js'; +import {classMap} from 'lit/directives/class-map.js'; import { - englishToPersian, isArrowKeyPressed, isDeletionKeyPressed, isDeletionKeyWithCtrlOrMetaPressed, isValidDigit, persianToEnglish, } from './util'; -import { ValueChangedEventParams } from './types'; +import {ValueChangedEventParams} from './types'; export class PinInputCell extends LitElement { @query('.cell') _cell!: HTMLInputElement; - @property({ type: Boolean, reflect: true }) disabled: boolean = false; + @property({type: Boolean, reflect: true}) disabled: boolean = false; - @property({ attribute: 'auto-focus', type: Boolean }) autoFocus: boolean = + @property({attribute: 'auto-focus', type: Boolean}) autoFocus: boolean = false; - @property({ reflect: true, type: Boolean, attribute: 'has-error' }) hasError = + @property({reflect: true, type: Boolean, attribute: 'has-error'}) hasError = false; - @property({ reflect: true, type: String }) value: string = ''; + @property({reflect: true, type: String}) value: string = ''; @property() label = ''; @property() size: 'small' | 'medium' | 'large' = 'medium'; - @property({ type: Number }) index: number = null!; + @property({type: Number}) index: number = null!; protected updated(changed: PropertyValues) { if (changed.has('value') && !Number.isNaN(this.value)) { @@ -38,7 +37,7 @@ export class PinInputCell extends LitElement { private async updateInputValue(newValue: string) { this.value = persianToEnglish(newValue); - this._cell.value = englishToPersian(newValue); + this._cell.value = (newValue); if (newValue.length) { await this.emitValueChanged(); @@ -218,6 +217,7 @@ export class PinInputCell extends LitElement { private async handleDeletionWithMetaKeys() { await this.emitDeletionWithMetaKeys(); } + private async handleArrowKeyPressed(key: 'ArrowLeft' | 'ArrowRight') { await this.emitArrowKeyPressed(key); } diff --git a/src/pin-input/pin-input.style.ts b/src/pin-input/pin-input.style.ts index 2f699e7f..c4d7c01e 100644 --- a/src/pin-input/pin-input.style.ts +++ b/src/pin-input/pin-input.style.ts @@ -1,39 +1,40 @@ -import { css } from 'lit'; +import {css} from 'lit'; export default css` :host { - box-sizing: border-box; - direction: rtl; - --justify-content: var(--tap-pin-input-justify-content, start); + --tap-pin-input-box-sizing: border-box; + --tap-pin-input-direction: rtl; - --description-font-family: var(--tap-sys-typography-body-sm-font); - --description-font-size: var(--tap-sys-typography-body-sm-size); - --description-line-height: var(--tap-sys-typography-body-sm-height); - --description-font-weight: var(--tap-sys-typography-body-sm-weight); + --tap-pin-input-description-font-family: var(--tap-sys-typography-body-sm-font); + --tap-pin-input-description-font-size: var(--tap-sys-typography-body-sm-size); + --tap-pin-input-description-line-height: var(--tap-sys-typography-body-sm-height); + --tap-pin-input-description-font-weight: var(--tap-sys-typography-body-sm-weight); - --description-font-family: var(--tap-sys-typography-body-sm-font); - --description-font-size: var(--tap-sys-typography-body-sm-size); - --description-line-height: var(--tap-sys-typography-body-sm-height); - --description-font-weight: var(--tap-sys-typography-body-sm-weight); - --description-text-color: var(--tap-sys-color-content-tertiary); - --description-error-text-color: var(--tap-sys-color-content-negative); - --description-disabled-text-color: var(--tap-sys-color-content-disabled); + --tap-pin-input-description-font-family: var(--tap-sys-typography-body-sm-font); + --tap-pin-input-description-font-size: var(--tap-sys-typography-body-sm-size); + --tap-pin-input-description-line-height: var(--tap-sys-typography-body-sm-height); + --tap-pin-input-description-font-weight: var(--tap-sys-typography-body-sm-weight); + --tap-pin-input-description-text-color: var(--tap-sys-color-content-tertiary); + --tap-pin-input-description-error-text-color: var(--tap-sys-color-content-negative); + --tap-pin-input-description-disabled-text-color: var(--tap-sys-color-content-disabled); - --title-font-family: var(--tap-sys-typography-body-md-font); - --title-font-size: var(--tap-sys-typography-body-md-size); - --title-line-height: var(--tap-sys-typography-body-md-height); - --title-font-weight: var(--tap-sys-typography-body-md-weight); - --title-text-color: var(--tap-sys-color-content-primary); - --title-disabled-text-color: var(--tap-sys-color-content-disabled); + --tap-inpu-input-title-font-family: var(--tap-sys-typography-body-md-font); + --tap-inpu-input-title-font-size: var(--tap-sys-typography-body-md-size); + --tap-inpu-input-title-line-height: var(--tap-sys-typography-body-md-height); + --tap-inpu-input-title-font-weight: var(--tap-sys-typography-body-md-weight); + --tap-inpu-input-title-text-color: var(--tap-sys-color-content-primary); + --tap-inpu-input-title-disabled-text-color: var(--tap-sys-color-content-disabled); - --vertical-gap: 0.5rem; - --horizontal-cell-gap: var(--tap-pin-input-cell-gap, 1rem); + --tap-pin-input-justify-content: start; + --tap-input-input-vertical-gap: 0.5rem; + --tap-pin-input-horizontal-cell-gap: 1rem; } :host *, :host *::before, :host *::after { - box-sizing: inherit; + box-sizing: var(--tap-pin-input-box-sizing); + direction: var(--tap-pin-input-direction); } [hidden] { @@ -41,16 +42,16 @@ export default css` } :host([has-error]) .description { - color: var(--description-error-text-color); + color: var(--tap-pin-input-description-error-text-color); } :host([disabled]) .title, :host([disabled]) .description { - color: var(--title-disabled-text-color); + color: var(--tap-inpu-input-title-disabled-text-color); } :host([disabled]) .description { - color: var(--description-disabled-text-color); + color: var(--tap-pin-input-description-disabled-text-color); } .pin-input { @@ -58,32 +59,32 @@ export default css` .pin-input-wrapper { display: flex; - row-gap: var(--vertical-gap); + row-gap: var(--tap-input-input-vertical-gap); flex-direction: column; } .title { flex: 0 1; - font-family: var(--title-font-family); - font-size: var(--title-font-size); - font-weight: var(--title-font-weight); - line-height: var(--title-line-height); - color: var(--title-text-color); + font-family: var(--tap-inpu-input-title-font-family); + font-size: var(--tap-inpu-input-title-font-size); + font-weight: var(--tap-inpu-input-title-font-weight); + line-height: var(--tap-inpu-input-title-line-height); + color: var(--tap-inpu-input-title-text-color); } .description { flex: 0 1; - font-family: var(--description-font-family); - font-size: var(--description-font-size); - font-weight: var(--description-font-weight); - line-height: var(--description-line-height); - color: var(--description-text-color); + font-family: var(--tap-pin-input-description-font-family); + font-size: var(--tap-pin-input-description-font-size); + font-weight: var(--tap-pin-input-description-font-weight); + line-height: var(--tap-pin-input-description-line-height); + color: var(--tap-pin-input-description-text-color); } .input-cells { display: flex; - justify-content: var(--justify-content); + justify-content: var(--tap-pin-input-justify-content); flex-direction: row-reverse; - gap: var(--horizontal-cell-gap); + gap: var(--tap-pin-input-horizontal-cell-gap); } `; From a41d0806eef6d211b5d64d2a596a48dff95c55c5 Mon Sep 17 00:00:00 2001 From: Hossein Nasiri Date: Mon, 8 Jul 2024 11:49:41 +0330 Subject: [PATCH 6/9] fix: convert events to concrete event classes --- src/pin-input-cell/constants.ts | 5 + src/pin-input-cell/events.ts | 122 +++++++++++++++++++++ src/pin-input-cell/index.ts | 2 + src/pin-input-cell/pin-input-cell.style.ts | 60 +++++++--- src/pin-input-cell/pin-input-cell.ts | 90 ++++++++------- src/pin-input/constants.ts | 1 + src/pin-input/events.ts | 25 +++++ src/pin-input/index.ts | 2 + src/pin-input/pin-input.stories.ts | 2 +- src/pin-input/pin-input.style.ts | 58 +++++++--- src/pin-input/pin-input.ts | 69 ++++++------ src/pin-input/types.ts | 8 +- 12 files changed, 327 insertions(+), 117 deletions(-) create mode 100644 src/pin-input-cell/constants.ts create mode 100644 src/pin-input-cell/events.ts create mode 100644 src/pin-input/constants.ts create mode 100644 src/pin-input/events.ts diff --git a/src/pin-input-cell/constants.ts b/src/pin-input-cell/constants.ts new file mode 100644 index 00000000..76476c91 --- /dev/null +++ b/src/pin-input-cell/constants.ts @@ -0,0 +1,5 @@ +export const PIN_INPUT_CELL_FILLED_TYPE = 'cell-filled'; +export const PIN_INPUT_CELL_CLEARED_TYPE = 'cell-cleared'; +export const PIN_INPUT_CELL_CLEARED_ALL_TYPE = 'cell-cleared-all-with-meta-key'; +export const PIN_INPUT_CELL_ARROW_KEY_PRESSED_TYPE = 'arrow-key-pressed'; +export const PIN_INPUT_CELL_OVERFLOW_VALUE_TYPE = 'overflow-value'; diff --git a/src/pin-input-cell/events.ts b/src/pin-input-cell/events.ts new file mode 100644 index 00000000..63144258 --- /dev/null +++ b/src/pin-input-cell/events.ts @@ -0,0 +1,122 @@ +import { + PIN_INPUT_CELL_ARROW_KEY_PRESSED_TYPE, + PIN_INPUT_CELL_CLEARED_ALL_TYPE, + PIN_INPUT_CELL_CLEARED_TYPE, + PIN_INPUT_CELL_FILLED_TYPE, + PIN_INPUT_CELL_OVERFLOW_VALUE_TYPE, +} from './constants'; +import { ValueChangedEventParams } from './types'; + +export class PinInputCellFilled extends Event { + public message: string; + public details: ValueChangedEventParams; + + constructor( + message: string, + details: ValueChangedEventParams, + eventInitDict: EventInit = {}, + ) { + const _eventInitDict = { + bubbles: true, + composed: false, + ...eventInitDict, + }; + const type = PIN_INPUT_CELL_FILLED_TYPE; + super(type, _eventInitDict); + + this.details = details; + this.message = message; + } +} + +export class PinInputCellCleared extends Event { + public message: string; + public details: ValueChangedEventParams; + + constructor( + message: string, + details: ValueChangedEventParams, + eventInitDict: EventInit = {}, + ) { + const _eventInitDict = { + bubbles: true, + composed: false, + ...eventInitDict, + }; + + const type = PIN_INPUT_CELL_CLEARED_TYPE; + super(type, _eventInitDict); + + this.details = details; + this.message = message; + } +} + +export class PinInputCellClearedAll extends Event { + public message: string; + public details: ValueChangedEventParams; + + constructor( + message: string, + details: ValueChangedEventParams, + eventInitDict: EventInit = {}, + ) { + const _eventInitDict = { + bubbles: true, + composed: false, + ...eventInitDict, + }; + + const type = PIN_INPUT_CELL_CLEARED_ALL_TYPE; + super(type, _eventInitDict); + + this.details = details; + this.message = message; + } +} + +export class PinInputCellArrowKeyPressed extends Event { + public message: string; + public details: ValueChangedEventParams; + + constructor( + message: string, + details: ValueChangedEventParams, + eventInitDict: EventInit = {}, + ) { + const _eventInitDict = { + bubbles: true, + composed: false, + ...eventInitDict, + }; + + const type = PIN_INPUT_CELL_ARROW_KEY_PRESSED_TYPE; + super(type, _eventInitDict); + + this.details = details; + this.message = message; + } +} + +export class PinInputCellOverflowValue extends Event { + public message: string; + public details: ValueChangedEventParams; + + constructor( + message: string, + details: ValueChangedEventParams, + eventInitDict: EventInit = {}, + ) { + const _eventInitDict = { + bubbles: true, + composed: false, + ...eventInitDict, + }; + + const type = PIN_INPUT_CELL_OVERFLOW_VALUE_TYPE; + super(type, _eventInitDict); + + this.details = details; + this.message = message; + } +} diff --git a/src/pin-input-cell/index.ts b/src/pin-input-cell/index.ts index f459296b..c7556922 100644 --- a/src/pin-input-cell/index.ts +++ b/src/pin-input-cell/index.ts @@ -1,6 +1,8 @@ import { customElement } from 'lit/decorators.js'; import { PinInputCell } from './pin-input-cell.js'; import styles from './pin-input-cell.style.js'; +export * from './events.js'; +export * from './constants.js'; @customElement('tap-pin-input-cell') export class TapPinInputCell extends PinInputCell { diff --git a/src/pin-input-cell/pin-input-cell.style.ts b/src/pin-input-cell/pin-input-cell.style.ts index 3bb00936..acce4dc1 100644 --- a/src/pin-input-cell/pin-input-cell.style.ts +++ b/src/pin-input-cell/pin-input-cell.style.ts @@ -1,4 +1,4 @@ -import {css} from 'lit'; +import { css } from 'lit'; export default css` :host { @@ -7,17 +7,44 @@ export default css` --tap-pin-input-cell-max-cell-width-ratio: 1.25; --tap-pin-input-cell-font-family: var(--tap-sys-font-family); - --tap-pin-input-cell-font-size-small: var(--tap-sys-typography-headline-xs-size, 16px); - --tap-pin-input-cell-font-size-medium: var(--tap-sys-typography-headline-sm-size, 20px); - --tap-pin-input-cell-font-size-large: var(--tap-sys-typography-headline-md-size, 24px); + --tap-pin-input-cell-font-size-small: var( + --tap-sys-typography-headline-xs-size, + 16px + ); + --tap-pin-input-cell-font-size-medium: var( + --tap-sys-typography-headline-sm-size, + 20px + ); + --tap-pin-input-cell-font-size-large: var( + --tap-sys-typography-headline-md-size, + 24px + ); - --tap-pin-input-cell-font-weight-small: var(--tap-sys-typography-headline-xs-weight, 600); - --tap-pin-input-cell-font-weight-medium: var(--tap-sys-typography-headline-sm-weight, 600); - --tap-pin-input-cell-font-weight-large: var(--tap-sys-typography-headline-md-weight, 600); + --tap-pin-input-cell-font-weight-small: var( + --tap-sys-typography-headline-xs-weight, + 600 + ); + --tap-pin-input-cell-font-weight-medium: var( + --tap-sys-typography-headline-sm-weight, + 600 + ); + --tap-pin-input-cell-font-weight-large: var( + --tap-sys-typography-headline-md-weight, + 600 + ); - --tap-pin-input-cell-line-height-small: var(--tap-sys-typography-headline-xs-height, 26px); - --tap-pin-input-cell-line-height-medium: var(--tap-sys-typography-headline-sm-height, 32px); - --tap-pin-input-cell-line-height-large: var(--tap-sys-typography-headline-md-height, 36px); + --tap-pin-input-cell-line-height-small: var( + --tap-sys-typography-headline-xs-height, + 26px + ); + --tap-pin-input-cell-line-height-medium: var( + --tap-sys-typography-headline-sm-height, + 32px + ); + --tap-pin-input-cell-line-height-large: var( + --tap-sys-typography-headline-md-height, + 36px + ); --tap-pin-input-cell-padding-small: var(--tap-sys-spacing-3, 6px); --tap-pin-input-cell-padding-medium: var(--tap-sys-spacing-4, 8px); @@ -25,12 +52,16 @@ export default css` --tap-pin-input-cell-bg-color: var(--tap-palette-gray-100); --tap-pin-input-cell-error-bg-color: var(--tap-palette-red-50); - --tap-pin-input-cell-disabled-bg-color: var(--tap-sys-color-surface-disabled); + --tap-pin-input-cell-disabled-bg-color: var( + --tap-sys-color-surface-disabled + ); --tap-pin-input-cell-color: var(--tap-palette-black); --tap-pin-input-cell-disabled-color: var(--tap-sys-color-content-disabled); --tap-pin-input-cell-border-focus-color: var(--tap-sys-color-border-focus); - --tap-pin-input-cell-border-error-color: var(--tap-sys-color-border-negative); + --tap-pin-input-cell-border-error-color: var( + --tap-sys-color-border-negative + ); --tap-pin-input-cell-border-disabled-color: transparent; --tap-pin-input-cell-border-radius: var(--tap-sys-radius-3); --tap-pin-input-cell-border-width: var(--tap-sys-stroke-4); @@ -53,7 +84,8 @@ export default css` margin: 0; background-color: var(--tap-pin-input-cell-bg-color); color: var(--tap-pin-input-cell-color); - border: var(--tap-pin-input-cell-border-width) solid var(--tap-pin-input-cell-border-default-color); + border: var(--tap-pin-input-cell-border-width) solid + var(--tap-pin-input-cell-border-default-color); border-radius: var(--tap-pin-input-cell-border-radius); outline: none; min-width: 0; @@ -62,7 +94,7 @@ export default css` min-width: var(--tap-pin-input-cell-height); max-width: calc( var(--tap-pin-input-cell-height) * - var(--tap-pin-input-cell-max-cell-width-ratio) + var(--tap-pin-input-cell-max-cell-width-ratio) ); } diff --git a/src/pin-input-cell/pin-input-cell.ts b/src/pin-input-cell/pin-input-cell.ts index 9045ab76..93307077 100644 --- a/src/pin-input-cell/pin-input-cell.ts +++ b/src/pin-input-cell/pin-input-cell.ts @@ -1,6 +1,6 @@ -import {html, LitElement, PropertyValues} from 'lit'; -import {property, query} from 'lit/decorators.js'; -import {classMap} from 'lit/directives/class-map.js'; +import { html, LitElement, PropertyValues } from 'lit'; +import { property, query } from 'lit/decorators.js'; +import { classMap } from 'lit/directives/class-map.js'; import { isArrowKeyPressed, isDeletionKeyPressed, @@ -8,26 +8,32 @@ import { isValidDigit, persianToEnglish, } from './util'; -import {ValueChangedEventParams} from './types'; +import { + PinInputCellArrowKeyPressed, + PinInputCellCleared, + PinInputCellClearedAll, + PinInputCellFilled, + PinInputCellOverflowValue, +} from './events'; export class PinInputCell extends LitElement { @query('.cell') _cell!: HTMLInputElement; - @property({type: Boolean, reflect: true}) disabled: boolean = false; + @property({ type: Boolean, reflect: true }) disabled: boolean = false; - @property({attribute: 'auto-focus', type: Boolean}) autoFocus: boolean = + @property({ attribute: 'auto-focus', type: Boolean }) autoFocus: boolean = false; - @property({reflect: true, type: Boolean, attribute: 'has-error'}) hasError = + @property({ reflect: true, type: Boolean, attribute: 'has-error' }) hasError = false; - @property({reflect: true, type: String}) value: string = ''; + @property({ reflect: true, type: String }) value: string = ''; @property() label = ''; @property() size: 'small' | 'medium' | 'large' = 'medium'; - @property({type: Number}) index: number = null!; + @property({ type: Number }) index: number = null!; protected updated(changed: PropertyValues) { if (changed.has('value') && !Number.isNaN(this.value)) { @@ -37,7 +43,7 @@ export class PinInputCell extends LitElement { private async updateInputValue(newValue: string) { this.value = persianToEnglish(newValue); - this._cell.value = (newValue); + this._cell.value = newValue; if (newValue.length) { await this.emitValueChanged(); @@ -50,71 +56,63 @@ export class PinInputCell extends LitElement { private async emitValueChanged() { await this.updateComplete; - const event = new CustomEvent('cell-filled', { - bubbles: true, - composed: false, - detail: { - cell: this, - index: this.index, - value: this.value, - } as ValueChangedEventParams, + const event = new PinInputCellFilled('PinInputCell filled', { + cell: this, + index: this.index, + value: this.value, }); + this.dispatchEvent(event); } private async emitValueCleared() { await this.updateComplete; - const event = new CustomEvent('cell-cleared', { - bubbles: true, - composed: false, - detail: { - cell: this, - index: this.index, - value: this.value, - } as ValueChangedEventParams, + const event = new PinInputCellCleared('PinInputCell cleared', { + cell: this, + index: this.index, + value: this.value, }); + 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, + const event = new PinInputCellClearedAll('PinInputCell cleared all', { + cell: this, + index: this.index, + value: this.value, }); + this.dispatchEvent(event); } private async emitArrowKeyPressed(key: 'ArrowLeft' | 'ArrowRight') { await this.updateComplete; - const event = new CustomEvent('arrow-key-pressed', { - bubbles: true, - composed: false, - detail: { + + const event = new PinInputCellArrowKeyPressed<'left' | 'right'>( + 'PinInputCell arrow key pressed', + { 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', { - bubbles: true, - composed: false, - detail: { + const event = new PinInputCellOverflowValue( + 'PinInputCell Overflowed value', + { cell: this, index: this.index, value: value, - } as ValueChangedEventParams, - }); + }, + ); + this.dispatchEvent(event); } diff --git a/src/pin-input/constants.ts b/src/pin-input/constants.ts new file mode 100644 index 00000000..6a3fd641 --- /dev/null +++ b/src/pin-input/constants.ts @@ -0,0 +1 @@ +export const PIN_INPUT_FILLED_TYPE = 'input-filled'; diff --git a/src/pin-input/events.ts b/src/pin-input/events.ts new file mode 100644 index 00000000..9c608c45 --- /dev/null +++ b/src/pin-input/events.ts @@ -0,0 +1,25 @@ +import { PIN_INPUT_FILLED_TYPE } from './constants'; +import { InputFilledEventParams } from './types'; + +export class PinInputFilled extends Event { + public message: string; + public details: InputFilledEventParams; + + constructor( + message: string, + details: InputFilledEventParams, + eventInitDict: EventInit = {}, + ) { + const _eventInitDict = { + bubbles: true, + composed: false, + ...eventInitDict, + }; + + const type = PIN_INPUT_FILLED_TYPE; + super(type, _eventInitDict); + + this.details = details; + this.message = message; + } +} diff --git a/src/pin-input/index.ts b/src/pin-input/index.ts index e6630a6a..d82b9389 100644 --- a/src/pin-input/index.ts +++ b/src/pin-input/index.ts @@ -1,6 +1,8 @@ import { customElement } from 'lit/decorators.js'; import { PinInput } from './pin-input.js'; import styles from './pin-input.style.js'; +export * from './events.js'; +export * from './constants.js'; @customElement('tap-pin-input') export class TapPinInput extends PinInput { diff --git a/src/pin-input/pin-input.stories.ts b/src/pin-input/pin-input.stories.ts index f8058e62..7cdb834c 100644 --- a/src/pin-input/pin-input.stories.ts +++ b/src/pin-input/pin-input.stories.ts @@ -20,7 +20,7 @@ const Template: Story = (/*{}: ArgTypes*/) => html` console.log(e)} + @input-filled=${(e: Error) => console.log(e)} > `; diff --git a/src/pin-input/pin-input.style.ts b/src/pin-input/pin-input.style.ts index c4d7c01e..622d7f0c 100644 --- a/src/pin-input/pin-input.style.ts +++ b/src/pin-input/pin-input.style.ts @@ -1,29 +1,57 @@ -import {css} from 'lit'; +import { css } from 'lit'; export default css` :host { --tap-pin-input-box-sizing: border-box; --tap-pin-input-direction: rtl; - --tap-pin-input-description-font-family: var(--tap-sys-typography-body-sm-font); - --tap-pin-input-description-font-size: var(--tap-sys-typography-body-sm-size); - --tap-pin-input-description-line-height: var(--tap-sys-typography-body-sm-height); - --tap-pin-input-description-font-weight: var(--tap-sys-typography-body-sm-weight); + --tap-pin-input-description-font-family: var( + --tap-sys-typography-body-sm-font + ); + --tap-pin-input-description-font-size: var( + --tap-sys-typography-body-sm-size + ); + --tap-pin-input-description-line-height: var( + --tap-sys-typography-body-sm-height + ); + --tap-pin-input-description-font-weight: var( + --tap-sys-typography-body-sm-weight + ); - --tap-pin-input-description-font-family: var(--tap-sys-typography-body-sm-font); - --tap-pin-input-description-font-size: var(--tap-sys-typography-body-sm-size); - --tap-pin-input-description-line-height: var(--tap-sys-typography-body-sm-height); - --tap-pin-input-description-font-weight: var(--tap-sys-typography-body-sm-weight); - --tap-pin-input-description-text-color: var(--tap-sys-color-content-tertiary); - --tap-pin-input-description-error-text-color: var(--tap-sys-color-content-negative); - --tap-pin-input-description-disabled-text-color: var(--tap-sys-color-content-disabled); + --tap-pin-input-description-font-family: var( + --tap-sys-typography-body-sm-font + ); + --tap-pin-input-description-font-size: var( + --tap-sys-typography-body-sm-size + ); + --tap-pin-input-description-line-height: var( + --tap-sys-typography-body-sm-height + ); + --tap-pin-input-description-font-weight: var( + --tap-sys-typography-body-sm-weight + ); + --tap-pin-input-description-text-color: var( + --tap-sys-color-content-tertiary + ); + --tap-pin-input-description-error-text-color: var( + --tap-sys-color-content-negative + ); + --tap-pin-input-description-disabled-text-color: var( + --tap-sys-color-content-disabled + ); --tap-inpu-input-title-font-family: var(--tap-sys-typography-body-md-font); --tap-inpu-input-title-font-size: var(--tap-sys-typography-body-md-size); - --tap-inpu-input-title-line-height: var(--tap-sys-typography-body-md-height); - --tap-inpu-input-title-font-weight: var(--tap-sys-typography-body-md-weight); + --tap-inpu-input-title-line-height: var( + --tap-sys-typography-body-md-height + ); + --tap-inpu-input-title-font-weight: var( + --tap-sys-typography-body-md-weight + ); --tap-inpu-input-title-text-color: var(--tap-sys-color-content-primary); - --tap-inpu-input-title-disabled-text-color: var(--tap-sys-color-content-disabled); + --tap-inpu-input-title-disabled-text-color: var( + --tap-sys-color-content-disabled + ); --tap-pin-input-justify-content: start; --tap-input-input-vertical-gap: 0.5rem; diff --git a/src/pin-input/pin-input.ts b/src/pin-input/pin-input.ts index 770245b5..2471e63a 100644 --- a/src/pin-input/pin-input.ts +++ b/src/pin-input/pin-input.ts @@ -4,9 +4,14 @@ import { range } from 'lit/directives/range.js'; import { repeat } from 'lit/directives/repeat.js'; import '../pin-input-cell'; import { PinInputCell } from '../pin-input-cell/pin-input-cell'; -import { englishToPersian } from '../pin-input-cell/util'; -import { InputFilledEventParams } from './types'; -import { ValueChangedEventParams } from '../pin-input-cell/types'; +import { + PinInputCellArrowKeyPressed, + PinInputCellCleared, + PinInputCellClearedAll, + PinInputCellFilled, + PinInputCellOverflowValue, +} from '../pin-input-cell/events'; +import { PinInputFilled } from './events'; export class PinInput extends LitElement { @queryAll('.pin-input-cell') _cells!: PinInputCell[]; @@ -39,13 +44,13 @@ export class PinInput extends LitElement { return this.autoFocusFirstCell && index === 0; } - private handleCellFilled(event: CustomEvent) { - this.focusNextElementByIndex(event.detail.index); + private handleCellFilled(event: PinInputCellFilled) { + this.focusNextElementByIndex(event.details.index); this.handleCellsFilled(); } - private handleCellCleared(event: CustomEvent) { - this.focusPrevElementByIndex(event.detail.index); + private handleCellCleared(event: PinInputCellCleared) { + this.focusPrevElementByIndex(event.details.index); } private handleCellsFilled() { @@ -54,11 +59,9 @@ export class PinInput extends LitElement { } } - private async handleOverflowedCell( - event: CustomEvent, - ) { - let overflowedText = event.detail.value; - const cellIndex = event.detail.index; + private async handleOverflowedCell(event: PinInputCellOverflowValue) { + let overflowedText = event.details.value; + const cellIndex = event.details.index; const isLongerThanRemainingCells = overflowedText.length > this.lastCellIndex - cellIndex; @@ -68,11 +71,10 @@ export class PinInput extends LitElement { await this.fillCells(overflowedText, cellIndex + 1); } - private async handleClearPrevCells( - event: CustomEvent, - ) { + + private async handleClearPrevCells(event: PinInputCellClearedAll) { await this.updateComplete; - const currentIndex = event.detail.index; + const currentIndex = event.details.index; const isNotFirstItem = currentIndex > 0 && this.checkIndexIsInRange(currentIndex); @@ -83,19 +85,17 @@ export class PinInput extends LitElement { } } - private async handleArrowKeyPressed( - event: CustomEvent>, - ) { + private async handleArrowKeyPressed(event: PinInputCellArrowKeyPressed) { await this.updateComplete; - const currentIndex = event.detail.index; + const currentIndex = event.details.index; const shouldPrevItemFocus = - event.detail.value === 'left' && + event.details.value === 'left' && this.checkIndexIsInRange(currentIndex) && !this.checkIndexIsFirst(currentIndex); const shouldNextItemFocus = - event.detail.value === 'right' && + event.details.value === 'right' && this.checkIndexIsInRange(currentIndex) && !this.checkIndexIsLast(currentIndex); @@ -118,14 +118,10 @@ export class PinInput extends LitElement { } private emitPinInputFilled() { - const event = new CustomEvent('filled', { - bubbles: true, - composed: true, - detail: { - value: this.inputValue, - cellCount: this.count, - displayValue: englishToPersian(this.inputValue!), - } as InputFilledEventParams, + const event = new PinInputFilled('PinInput Filled.', { + value: this.inputValue!, + cellCount: this.count, + displayValue: this.inputValue!, }); this.dispatchEvent(event); @@ -200,17 +196,16 @@ export class PinInput extends LitElement { index=${index} ?disabled=${this.disabled} ?has-error=${this.hasError} - @cell-filled=${(e: CustomEvent) => + @cell-filled=${(e: PinInputCellFilled) => this.handleCellFilled(e)} - @cell-cleared=${(e: CustomEvent) => + @cell-cleared=${(e: PinInputCellCleared) => this.handleCellCleared(e)} - @overflow-value=${(e: CustomEvent) => + @overflow-value=${(e: PinInputCellOverflowValue) => this.handleOverflowedCell(e)} - @clear-all=${(e: CustomEvent) => + @cell-cleared-all-with-meta-key=${(e: PinInputCellClearedAll) => this.handleClearPrevCells(e)} - @arrow-key-pressed=${( - e: CustomEvent>, - ) => this.handleArrowKeyPressed(e)} + @arrow-key-pressed=${(e: PinInputCellArrowKeyPressed) => + this.handleArrowKeyPressed(e)} ?auto-focus=${this.isFirstCellShouldAutoFocus(index)} .size=${this.size} >`; diff --git a/src/pin-input/types.ts b/src/pin-input/types.ts index ce99f268..a173751e 100644 --- a/src/pin-input/types.ts +++ b/src/pin-input/types.ts @@ -1,5 +1,5 @@ export type InputFilledEventParams = { - cellCount: number, - value: string, - displayValue: string, -} + cellCount: number; + value: string; + displayValue: string; +}; From 26b7a3337a3a2e2b6e5a5eb140ab0ae64251615c Mon Sep 17 00:00:00 2001 From: Hossein Nasiri Date: Mon, 8 Jul 2024 21:20:38 +0330 Subject: [PATCH 7/9] feat: add form ElementInternals to pin input --- docs/components/custom-elements.json | 4614 ++++++++++++++++++++++++++ src/pin-input/pin-input.stories.ts | 21 + src/pin-input/pin-input.ts | 87 +- 3 files changed, 4714 insertions(+), 8 deletions(-) create mode 100644 docs/components/custom-elements.json diff --git a/docs/components/custom-elements.json b/docs/components/custom-elements.json new file mode 100644 index 00000000..c7759cc2 --- /dev/null +++ b/docs/components/custom-elements.json @@ -0,0 +1,4614 @@ +{ + "schemaVersion": "1.0.0", + "readme": "", + "modules": [ + { + "kind": "javascript-module", + "path": "src/avatar/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapAvatar", + "cssProperties": [ + { + "name": "--tap-avatar-background-color", + "default": "--tap-sys-color-surface-secondary" + }, + { + "name": "--tap-avatar-border-color", + "default": "--tap-sys-color-border-primary" + }, + { + "name": "--tap-avatar-border-radius", + "default": "--tap-sys-radius-full" + }, + { + "name": "--tap-avatar-width-xxSmall", + "default": "--tap-sys-spacing-8" + }, + { + "name": "--tap-avatar-height-xxSmall", + "default": "--tap-sys-spacing-8" + }, + { + "name": "--tap-avatar-width-xSmall", + "default": "--tap-sys-spacing-9" + }, + { + "name": "--tap-avatar-height-xSmall", + "default": "--tap-sys-spacing-9" + }, + { + "name": "--tap-avatar-width-small", + "default": "--tap-sys-spacing-10" + }, + { + "name": "--tap-avatar-height-small", + "default": "--tap-sys-spacing-10" + }, + { + "name": "--tap-avatar-width-medium", + "default": "--tap-sys-spacing-11" + }, + { + "name": "--tap-avatar-height-medium", + "default": "--tap-sys-spacing-11" + }, + { + "name": "--tap-avatar-width-large", + "default": "56px" + }, + { + "name": "--tap-avatar-height-large", + "default": "56px" + }, + { + "name": "--tap-avatar-width-xLarge", + "default": "72px" + }, + { + "name": "--tap-avatar-height-xLarge", + "default": "72px" + } + ], + "cssParts": [ + { + "description": "The container that wraps the avatar component.", + "name": "avatar" + }, + { + "description": "The container that wraps the avatar's placeholder.", + "name": "placeholder" + }, + { + "description": "The avatar image. Only shown when the image is present.", + "name": "image" + } + ], + "slots": [ + { + "description": "The default slot to use when image is not present.", + "name": "" + } + ], + "members": [ + { + "type": { + "text": "string" + }, + "description": "A label to use to describe the avatar to assistive devices.", + "name": "label", + "default": "''", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "The image source to use for the avatar.", + "name": "image", + "default": "''", + "kind": "field" + }, + { + "type": { + "text": "'eager' | 'lazy'" + }, + "description": " Indicates how the browser should load the image.", + "name": "loading", + "default": "'eager'", + "kind": "field" + }, + { + "type": { + "text": "'xSmall' | 'small' | 'medium' | 'large' | 'xLarge'" + }, + "description": "The size of the avatar.", + "name": "size", + "default": "'medium'", + "kind": "field" + } + ], + "superclass": { + "name": "Avatar", + "module": "/src/avatar/avatar.js" + }, + "summary": "Display user profile image, initials or fallback icon", + "tagName": "tap-avatar", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapAvatar", + "declaration": { + "name": "TapAvatar", + "module": "src/avatar/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-avatar", + "declaration": { + "name": "TapAvatar", + "module": "src/avatar/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/badge-wrapper/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapBadgeWrapper", + "cssParts": [ + { + "description": "The container that wraps the main content and the badge.", + "name": "wrapper" + }, + { + "description": "The container that positions the badge.", + "name": "badge" + } + ], + "slots": [ + { + "description": "The default slot for the main content.", + "name": "" + }, + { + "description": "The slot for the badge to be positioned.", + "name": "badge" + } + ], + "members": [], + "superclass": { + "name": "BadgeWrapper", + "module": "/src/badge-wrapper/badge-wrapper" + }, + "summary": "A wrapper component to position a badge relative to its content.", + "tagName": "tap-badge-wrapper", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapBadgeWrapper", + "declaration": { + "name": "TapBadgeWrapper", + "module": "src/badge-wrapper/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-badge-wrapper", + "declaration": { + "name": "TapBadgeWrapper", + "module": "src/badge-wrapper/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/badge/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapBadge", + "cssProperties": [ + { + "description": "Font family of the badge.", + "name": "--tap-font-family", + "default": "--tap-sys-font-family" + }, + { + "description": "Line height of the badge text.", + "name": "--tap-badge-line-height", + "default": "--tap-sys-typography-body-xs-height" + }, + { + "description": "Font size of the badge text.", + "name": "--tap-badge-font-size", + "default": "--tap-sys-typography-body-xs-size" + }, + { + "description": "Font weight of the badge text.", + "name": "--tap-badge-font-weight", + "default": "--tap-sys-typography-body-xs-weight" + }, + { + "description": "Text color of the badge.", + "name": "--tap-badge-color", + "default": "--tap-sys-color-content-primary" + }, + { + "description": "Border radius of the badge.", + "name": "--tap-badge-border-radius", + "default": "--tap-sys-radius-4" + }, + { + "description": "Margin around the icon.", + "name": "--tap-badge-icon-margin", + "default": "--tap-sys-spacing-2" + }, + { + "description": "Border radius for pill and numeral types.", + "name": "--tap-badge-pill-numeral-border-radius", + "default": "--tap-sys-radius-4" + }, + { + "description": "Vertical padding for pill and numeral types.", + "name": "--tap-badge-pill-numeral-vertical-padding", + "default": "--tap-sys-spacing-2" + }, + { + "description": "Horizontal padding for pill and numeral types.", + "name": "--tap-badge-pill-numeral-horizontal-padding", + "default": "--tap-sys-spacing-4" + }, + { + "description": "Width of the dot type badge.", + "name": "--tap-badge-dot-width", + "default": "6px" + }, + { + "description": "Height of the dot type badge.", + "name": "--tap-badge-dot-height", + "default": "6px" + }, + { + "description": "Margin around the dot type badge.", + "name": "--tap-badge-dot-margin", + "default": "3px" + }, + { + "description": "Background color for info variant.", + "name": "--tap-badge-info-background-color", + "default": "--tap-sys-color-surface-accent" + }, + { + "description": "Text color for info variant.", + "name": "--tap-badge-info-color", + "default": "--tap-sys-color-content-on-negative" + }, + { + "description": "Background color for success variant.", + "name": "--tap-badge-success-background-color", + "default": "--tap-sys-color-surface-positive" + }, + { + "description": "Text color for success variant.", + "name": "--tap-badge-success-color", + "default": "--tap-sys-color-content-on-negative" + }, + { + "description": "Background color for error variant.", + "name": "--tap-badge-error-background-color", + "default": "--tap-sys-color-surface-negative" + }, + { + "description": "Text color for error variant.", + "name": "--tap-badge-error-color", + "default": "--tap-sys-color-content-on-negative" + }, + { + "description": "Background color for warning variant.", + "name": "--tap-badge-warning-background-color", + "default": "--tap-sys-color-surface-warning" + }, + { + "description": "Text color for warning variant.", + "name": "--tap-badge-warning-color", + "default": "--tap-sys-color-content-on-warning" + }, + { + "description": "Background color for inverse variant.", + "name": "--tap-badge-inverse-background-color", + "default": "--tap-sys-color-surface-disabled" + }, + { + "description": "Text color for inverse variant.", + "name": "--tap-badge-inverse-color", + "default": "--tap-sys-color-content-tertiary" + }, + { + "description": "Background color for low priority info variant.", + "name": "--tap-badge-info-low-background-color", + "default": "--tap-sys-color-surface-accent-light" + }, + { + "description": "Text color for low priority info variant.", + "name": "--tap-badge-info-low-color", + "default": "--tap-sys-color-content-accent" + }, + { + "description": "Background color for low priority success variant.", + "name": "--tap-badge-success-low-background-color", + "default": "--tap-sys-color-surface-positive-light" + }, + { + "description": "Text color for low priority success variant.", + "name": "--tap-badge-success-low-color", + "default": "--tap-sys-color-content-positive" + }, + { + "description": "Background color for low priority error variant.", + "name": "--tap-badge-error-low-background-color", + "default": "--tap-sys-color-surface-negative-light" + }, + { + "description": "Text color for low priority error variant.", + "name": "--tap-badge-error-low-color", + "default": "--tap-sys-color-content-negative" + }, + { + "description": "Background color for low priority warning variant.", + "name": "--tap-badge-warning-low-background-color", + "default": "--tap-sys-color-surface-warning-light" + }, + { + "description": "Text color for low priority warning variant.", + "name": "--tap-badge-warning-low-color", + "default": "--tap-sys-color-content-warning" + } + ], + "cssParts": [ + { + "description": "The main badge element.", + "name": "badge" + }, + { + "description": "The icon inside the badge.", + "name": "icon" + } + ], + "members": [ + { + "type": { + "text": "string|number" + }, + "description": "The value to display inside the badge.", + "name": "value", + "default": "''", + "kind": "field" + }, + { + "type": { + "text": "'pill' | 'numeral' | 'dot'" + }, + "description": "The type of the badge.", + "name": "type", + "default": "'pill'", + "kind": "field" + }, + { + "type": { + "text": "'success' | 'error' | 'info' | 'warning' | 'inverse'" + }, + "description": "The variant style of the badge.", + "name": "variant", + "default": "'inverse'", + "kind": "field" + }, + { + "type": { + "text": "'high' | 'low'" + }, + "description": "The priority level of the badge.", + "name": "priority", + "default": "'high'", + "kind": "field" + }, + { + "type": { + "text": "boolean" + }, + "description": "Whether to show an icon before the badge value.", + "name": "leadingIcon", + "default": "false", + "kind": "field" + } + ], + "superclass": { + "name": "Badge", + "module": "/src/badge/badge" + }, + "summary": "Display badge component with different styles and types.", + "tagName": "tap-badge", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapBadge", + "declaration": { + "name": "TapBadge", + "module": "src/badge/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-badge", + "declaration": { + "name": "TapBadge", + "module": "src/badge/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/banner/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapBanner", + "cssProperties": [ + { + "description": "Spacing around the banner content.", + "name": "--tap-sys-spacing-6" + }, + { + "description": "Background color of the banner.", + "name": "--tap-banner-color-surface" + }, + { + "description": "Border radius of the banner.", + "name": "--tap-sys-radius-4" + }, + { + "description": "Background image of the banner.", + "name": "--tap-banner-background-image" + }, + { + "description": "Margin around the banner.", + "name": "--tap-sys-spacing-4" + }, + { + "description": "Text color of the banner.", + "name": "--tap-banner-color-content" + }, + { + "description": "Font family for the heading text.", + "name": "--tap-sys-typography-headline-xs-font", + "default": "--tap-sys-font-family" + }, + { + "description": "Line height for the heading text.", + "name": "--tap-sys-typography-headline-xs-height" + }, + { + "description": "Font size for the heading text.", + "name": "--tap-sys-typography-headline-xs-size" + }, + { + "description": "Font weight for the heading text.", + "name": "--tap-sys-typography-headline-xs-weight" + }, + { + "description": "Margin below the heading text.", + "name": "--tap-sys-spacing-3" + }, + { + "description": "Font family for the description text.", + "name": "--tap-sys-typography-body-xs-font" + }, + { + "description": "Line height for the description text.", + "name": "--tap-sys-typography-body-xs-height" + }, + { + "description": "Font size for the description text.", + "name": "--tap-sys-typography-body-xs-size" + }, + { + "description": "Font weight for the description text.", + "name": "--tap-sys-typography-body-xs-weight" + }, + { + "description": "Margin above the action slot.", + "name": "--tap-sys-spacing-5" + }, + { + "description": "Minimum height of the extra slot in the hero variant.", + "name": "--tap-sys-spacing-8" + } + ], + "cssParts": [ + { + "description": "The main banner element.", + "name": "banner" + }, + { + "description": "The content container inside the banner.", + "name": "content" + }, + { + "description": "The action container inside the banner.", + "name": "action" + }, + { + "description": "The extra slot container inside the hero variant.", + "name": "extra" + } + ], + "members": [ + { + "type": { + "text": "string" + }, + "description": "The heading text to display in the banner.", + "name": "heading", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "The description text to display in the banner.", + "name": "description", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "The URL of the background image to display in the banner.", + "name": "image", + "kind": "field" + }, + { + "type": { + "text": "boolean" + }, + "description": "Whether the banner should take the full width of its container.", + "name": "full-width", + "default": "false", + "kind": "field" + }, + { + "type": { + "text": "'default' | 'hero'" + }, + "description": "The variant style of the banner.", + "name": "variant", + "default": "'default'", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "The background color of the banner.", + "name": "background-color", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "The text color of the banner.", + "name": "textColor", + "kind": "field" + } + ], + "superclass": { + "name": "Banner", + "module": "/src/banner/banner.js" + }, + "summary": "Display a banner with optional heading, description, and action slot.", + "tagName": "tap-banner", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapBanner", + "declaration": { + "name": "TapBanner", + "module": "src/banner/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-banner", + "declaration": { + "name": "TapBanner", + "module": "src/banner/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/base-button/index.ts", + "declarations": [], + "exports": [ + { + "kind": "js", + "name": "baseButtonStyles", + "declaration": { + "name": "default", + "module": "./base-button.style" + } + }, + { + "kind": "js", + "name": "BaseButton", + "declaration": { + "name": "BaseButton", + "module": "./base-button" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/bottom-navigation/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapBottomNavigation", + "cssProperties": [ + { + "description": "The background color of the navigation bar.", + "name": "--tap-bottom-navigation-background", + "default": "--tap-sys-color-surface-secondary" + }, + { + "description": "The width of the top border of the navigation bar.", + "name": "--tap-bottom-navigation-border-top-width", + "default": "--tap-sys-stroke-1" + }, + { + "description": "The color of the top border of the navigation bar.", + "name": "--tap-bottom-navigation-border-top-color", + "default": "--tap-sys-color-border-primary" + } + ], + "cssParts": [ + { + "description": "The main container for the bottom navigation bar.", + "name": "bottom-navigation" + } + ], + "slots": [ + { + "description": "The default slot for adding `tap-bottom-navigation-item` elements.", + "name": "" + } + ], + "members": [], + "superclass": { + "name": "BottomNavigation", + "module": "/src/bottom-navigation/bottom-navigation.js" + }, + "summary": "A bottom navigation bar that contains multiple navigation items.", + "tagName": "tap-bottom-navigation", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapBottomNavigation", + "declaration": { + "name": "TapBottomNavigation", + "module": "src/bottom-navigation/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-bottom-navigation", + "declaration": { + "name": "TapBottomNavigation", + "module": "src/bottom-navigation/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/bottom-navigation-item/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapBottomNavigationItem", + "cssProperties": [ + { + "description": "The font family for the navigation item.", + "name": "--tap-font-family", + "default": "--tap-sys-font-family" + }, + { + "description": "The text color when the item is inactive.", + "name": "--tap-bottom-navigation-item-color", + "default": "--tap-sys-color-content-tertiary" + }, + { + "description": "The text color when the item is active.", + "name": "--tap-bottom-navigation-active-color", + "default": "--tap-sys-color-content-primary" + }, + { + "description": "The line height for the label.", + "name": "--tap-bottom-navigation-item-line-height", + "default": "--tap-sys-typography-label-xs-height" + }, + { + "description": "The font size for the label.", + "name": "--tap-bottom-navigation-item-font-size", + "default": "--tap-sys-typography-label-xs-size" + }, + { + "description": "The font weight for the label.", + "name": "--tap-bottom-navigation-item-font-weight", + "default": "--tap-sys-typography-label-xs-weight" + } + ], + "cssParts": [ + { + "description": "The main container for the bottom navigation item.", + "name": "bottom-navigation-item" + } + ], + "slots": [ + { + "description": "Slot for the icon when the item is inactive.", + "name": "icon" + }, + { + "description": "Slot for the icon when the item is active.", + "name": "active-icon" + }, + { + "description": "The default slot for the item label or content.", + "name": "" + } + ], + "members": [ + { + "type": { + "text": "boolean" + }, + "description": "Indicates whether the navigation item is active.", + "name": "active", + "default": "false", + "kind": "field" + } + ], + "superclass": { + "name": "BottomNavigationItem", + "module": "/src/bottom-navigation-item/bottom-navigation-item.js" + }, + "summary": "Represents a single item in a bottom navigation bar.", + "tagName": "tap-bottom-navigation-item", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapBottomNavigationItem", + "declaration": { + "name": "TapBottomNavigationItem", + "module": "src/bottom-navigation-item/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-bottom-navigation-item", + "declaration": { + "name": "TapBottomNavigationItem", + "module": "src/bottom-navigation-item/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/bottom-sheet/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapBottomSheet", + "cssProperties": [ + { + "name": "--tap-bottom-sheet-bottom", + "default": "0" + }, + { + "name": "--tap-bottom-sheet-header-padding", + "default": "0" + }, + { + "name": "--tap-bottom-sheet-header-padding", + "default": "12px" + }, + { + "name": "--tap-bottom-sheet-background", + "default": "--tap-sys-color-surface-primary" + }, + { + "name": "--tap-bottom-sheet-content-overflow-y", + "default": "scroll" + } + ], + "cssParts": [ + { + "description": "The dimmer element darkens the background and is clickable to close the bottom sheet.", + "name": "dimmer" + }, + { + "description": "The header of the bottom sheet component, containing the title and dismiss button.", + "name": "header" + }, + { + "description": "The container that wraps the bottom sheet's content.", + "name": "body" + } + ], + "slots": [ + { + "description": "The content of the bottom sheet's body.", + "name": "bottom-sheet-body" + }, + { + "description": "The bottom sheet's header part.", + "name": "bottom-sheet-header" + } + ], + "members": [ + { + "type": { + "text": "boolean" + }, + "description": "Controls the visibility of the bottom sheet. If true, the bottom sheet is visible.", + "name": "open", + "default": "false", + "kind": "field" + }, + { + "type": { + "text": "boolean" + }, + "description": "Determines whether the bottom sheet can be dismissed by the user. If true, a close button is displayed, allowing the bottom sheet to be closed.", + "name": "dismissible", + "default": "true", + "kind": "field" + }, + { + "type": { + "text": "boolean" + }, + "description": " Controls the presence of a dimmer overlay.", + "name": "hasDimmer", + "default": "false", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "Specifies the title displayed in the header of the bottom sheet.", + "name": "title", + "default": "''", + "kind": "field" + }, + { + "type": { + "text": "boolean" + }, + "description": " If true, the bottom sheet expands to 90% of the viewport height (90vh).", + "name": "expanded", + "default": "false", + "kind": "field" + }, + { + "type": { + "text": "boolean" + }, + "description": " Controls the visibility of the grabber element.", + "name": "showGrabber", + "default": "true", + "kind": "field" + } + ], + "superclass": { + "name": "BottomSheet", + "module": "/src/bottom-sheet/bottom-sheet" + }, + "summary": "Bottom sheet Element", + "tagName": "tap-bottom-sheet", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapBottomSheet", + "declaration": { + "name": "TapBottomSheet", + "module": "src/bottom-sheet/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-bottom-sheet", + "declaration": { + "name": "TapBottomSheet", + "module": "src/bottom-sheet/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/button/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapButton", + "cssProperties": [ + { + "description": "The font family for the button.", + "name": "--tap-font-family", + "default": "--tap-sys-font-family" + }, + { + "description": "The border radius for the button.", + "name": "--tap-sys-radius-full" + }, + { + "description": "The surface cover color for the button.", + "name": "--tap-button-color-surface-cover", + "default": "--tap-sys-color-surface-overlay-light" + }, + { + "description": "The overlay light color for the button.", + "name": "--tap-sys-color-surface-overlay-light" + }, + { + "description": "The disabled surface color for the button.", + "name": "--tap-sys-color-surface-disabled" + }, + { + "description": "The disabled surface color for the button.", + "name": "--tap-button-color-surface-disabled", + "default": "--tap-sys-color-surface-disabled" + }, + { + "description": "The spacing for the small button height.", + "name": "--tap-sys-spacing-9" + }, + { + "description": "The spacing for the medium button height.", + "name": "--tap-sys-spacing-10" + }, + { + "description": "The spacing for the large button height.", + "name": "--tap-sys-spacing-11" + }, + { + "description": "The spacing for the small button padding.", + "name": "--tap-sys-spacing-5" + }, + { + "description": "The spacing for the medium button padding.", + "name": "--tap-sys-spacing-6" + }, + { + "description": "The spacing for the large button padding.", + "name": "--tap-sys-spacing-8" + }, + { + "description": "The line height for the small button label.", + "name": "--tap-button-typography-label-sm-height", + "default": "--tap-sys-typography-label-sm-height" + }, + { + "description": "The font size for the small button label.", + "name": "--tap-button-typography-label-sm-size", + "default": "--tap-sys-typography-label-sm-size" + }, + { + "description": "The font weight for the small button label.", + "name": "--tap-button-typography-label-sm-weight", + "default": "--tap-sys-typography-label-sm-weight" + }, + { + "description": "The line height for the medium button label.", + "name": "--tap-button-typography-label-md-height", + "default": "--tap-sys-typography-label-sm-height" + }, + { + "description": "The font size for the medium button label.", + "name": "--tap-button-typography-label-md-size", + "default": "--tap-sys-typography-label-sm-size" + }, + { + "description": "The font weight for the medium button label.", + "name": "--tap-button-typography-label-md-weight", + "default": "--tap-sys-typography-label-sm-weight" + }, + { + "description": "The line height for the large button label.", + "name": "--tap-button-typography-label-lg-height", + "default": "--tap-sys-typography-label-lg-height" + }, + { + "description": "The font size for the large button label.", + "name": "--tap-button-typography-label-lg-size", + "default": "--tap-sys-typography-label-lg-size" + }, + { + "description": "The font weight for the large button label.", + "name": "--tap-button-typography-label-lg-weight", + "default": "--tap-sys-typography-label-lg-weight" + }, + { + "description": "The surface color for the primary variant.", + "name": "--tap-button-color-surface-inverse-primary", + "default": "--tap-sys-color-surface-inverse-primary" + }, + { + "description": "The surface color for the primary variant.", + "name": "--tap-sys-color-surface-inverse-primary" + }, + { + "description": "The content color on inverse surface.", + "name": "--tap-button-color-content-on-inverse", + "default": "--tap-sys-color-content-on-inverse" + }, + { + "description": "The content color on inverse surface.", + "name": "--tap-sys-color-content-on-inverse" + }, + { + "description": "The surface color for the ghost variant.", + "name": "--tap-button-color-surface-tertiary", + "default": "--tap-sys-color-surface-tertiary" + }, + { + "description": "The surface color for the ghost variant.", + "name": "--tap-sys-color-surface-tertiary" + }, + { + "description": "The content color for the primary variant.", + "name": "--tap-button-color-content-primary", + "default": "--tap-sys-color-content-primary" + }, + { + "description": "The content color for the primary variant.", + "name": "--tap-sys-color-content-primary" + }, + { + "description": "The surface color for the elevated variant.", + "name": "--tap-button-color-surface-primary", + "default": "--tap-sys-color-surface-primary" + }, + { + "description": "The surface color for the elevated variant.", + "name": "--tap-sys-color-surface-primary" + }, + { + "description": "The surface color for the destructive variant.", + "name": "--tap-button-color-surface-destructive", + "default": "--tap-sys-color-surface-negative-light" + }, + { + "description": "The surface color for the destructive variant.", + "name": "--tap-sys-color-surface-negative-light" + }, + { + "description": "The content color for the destructive variant.", + "name": "--tap-button-color-content-destructive", + "default": "--tap-sys-color-content-negative" + }, + { + "description": "The content color for the destructive variant.", + "name": "--tap-sys-color-content-negative" + }, + { + "description": "The gradient color for the brand variant.", + "name": "--tap-button-color-gradient-brand", + "default": "--tap-sys-color-gradient-brand" + }, + { + "description": "The gradient color for the brand variant.", + "name": "--tap-sys-color-gradient-brand" + } + ], + "cssParts": [ + { + "description": "The button element.", + "name": "button" + } + ], + "slots": [ + { + "description": "buttons content", + "name": "" + } + ], + "members": [ + { + "type": { + "text": "boolean" + }, + "description": "Whether the button is disabled.", + "name": "disabled", + "default": "false", + "kind": "field" + }, + { + "type": { + "text": "'button' | 'submit' | 'reset'" + }, + "description": "The type of the button.", + "name": "type", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "The value associated with the button.", + "name": "value", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "The name associated with the button.", + "name": "name", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "The accessible label for the button.", + "name": "label", + "kind": "field" + }, + { + "type": { + "text": "boolean" + }, + "description": "Whether the button is in a loading state.", + "name": "loading", + "default": "false", + "kind": "field" + }, + { + "type": { + "text": "'small' | 'medium' | 'large'" + }, + "description": "The size of the button.", + "name": "size", + "default": "'medium'", + "kind": "field" + }, + { + "type": { + "text": "'primary' | 'ghost' | 'naked' | 'elevated' | 'destructive' | 'brand'" + }, + "description": "The variant style of the button.", + "name": "variant", + "default": "'primary'", + "kind": "field" + } + ], + "superclass": { + "name": "Button", + "module": "/src/button/button" + }, + "summary": "A customizable button component with various styles and states.", + "tagName": "tap-button", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapButton", + "declaration": { + "name": "TapButton", + "module": "src/button/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-button", + "declaration": { + "name": "TapButton", + "module": "src/button/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/checkbox/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapCheckbox", + "cssProperties": [ + { + "description": "The border radius of the checkbox.", + "name": "--tap-checkbox-border-radius", + "default": "--tap-sys-radius-1" + }, + { + "description": "The height of the checkbox.", + "name": "--tap-checkbox-height", + "default": "--tap-sys-spacing-7" + }, + { + "description": "The width of the checkbox.", + "name": "--tap-checkbox-width", + "default": "--tap-sys-spacing-7" + }, + { + "description": "The height of the checkbox input.", + "name": "--tap-checkbox-input-height", + "default": "--tap-sys-spacing-7" + }, + { + "description": "The width of the checkbox input.", + "name": "--tap-checkbox-input-width", + "default": "--tap-sys-spacing-7" + }, + { + "description": "The background color of the checkbox.", + "name": "--tap-checkbox-background-color", + "default": "--tap-sys-color-surface-primary" + }, + { + "description": "The border color of the checkbox.", + "name": "--tap-checkbox-border", + "default": "--tap-sys-color-border-inverse-primary" + }, + { + "description": "The background color of the checked or indeterminate checkbox.", + "name": "--tap-checkbox-checked-background-color", + "default": "--tap-sys-color-surface-inverse-primary" + }, + { + "description": "The color of the content inside the checked or indeterminate checkbox.", + "name": "--tap-checkbox-checked-color", + "default": "--tap-sys-color-content-on-inverse" + }, + { + "description": "The background color of the disabled checkbox.", + "name": "--tap-checkbox-checked-color", + "default": "--tap-sys-color-surface-disabled" + }, + { + "description": "The border color of the disabled checkbox.", + "name": "--tap-checkbox-disabled-border-color", + "default": "--tap-sys-color-surface-disabled" + }, + { + "description": "The color of the content inside the disabled checkbox.", + "name": "--tap-checkbox-disabled-color", + "default": "--tap-sys-color-content-disabled" + } + ], + "cssParts": [ + { + "description": "The main container for the checkbox.", + "name": "checkbox" + } + ], + "members": [ + { + "type": { + "text": "boolean" + }, + "description": "Indicates whether the checkbox is checked.", + "name": "checked", + "default": "false", + "kind": "field" + }, + { + "type": { + "text": "boolean" + }, + "description": "Indicates whether the checkbox is in an indeterminate state.", + "name": "indeterminate", + "default": "false", + "kind": "field" + }, + { + "type": { + "text": "boolean" + }, + "description": "Indicates whether the checkbox is disabled.", + "name": "disabled", + "default": "false", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "The value of the checkbox when it is checked.", + "name": "value", + "default": "'on'", + "kind": "field" + } + ], + "superclass": { + "name": "Checkbox", + "module": "/src/checkbox/checkbox" + }, + "summary": "A checkbox component with support for checked and indeterminate states.", + "tagName": "tap-checkbox", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapCheckbox", + "declaration": { + "name": "TapCheckbox", + "module": "src/checkbox/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-checkbox", + "declaration": { + "name": "TapCheckbox", + "module": "src/checkbox/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/empty-state/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapEmptyState", + "cssProperties": [ + { + "description": "The font family used in the empty state.", + "name": "--tap-font-family", + "default": "--tap-sys-font-family" + }, + { + "description": "The horizontal margin for the icon slot.", + "name": "--tap-empty-state-icon-horizontal-margin" + }, + { + "description": "The vertical margin for the icon slot.", + "name": "--tap-empty-state-icon-vertical-margin", + "default": "--tap-sys-spacing-6" + }, + { + "description": "The horizontal margin for the actions slot.", + "name": "--tap-empty-state-actions-horizontal-margin" + }, + { + "description": "The vertical margin for the actions slot.", + "name": "--tap-empty-state-actions-vertical-margin", + "default": "--tap-sys-spacing-6" + }, + { + "description": "The horizontal padding for the content slot.", + "name": "--tap-empty-state-content-horizontal-padding", + "default": "--tap-sys-spacing-6" + }, + { + "description": "The vertical padding for the content slot.", + "name": "--tap-empty-state-content-vertical-padding", + "default": "--tap-sys-spacing-4" + }, + { + "description": "The font size used for the title in the empty state.", + "name": "--tap-empty-state-title-font-size", + "default": "--tap-sys-typography-headline-sm-size" + }, + { + "description": "The font weight used for the title in the empty state.", + "name": "--tap-empty-state-title-font-weight", + "default": "--tap-sys-typography-headline-sm-weight" + }, + { + "description": "The line height used for the title in the empty state.", + "name": "--tap-empty-state-title-line-height", + "default": "--tap-sys-typography-headline-sm-height" + }, + { + "description": "The font size used for the description in the empty state.", + "name": "--tap-empty-state-description-font-size", + "default": "--tap-sys-typography-headline-sm-size" + }, + { + "description": "The font weight used for the description in the empty state.", + "name": "--tap-empty-state-description-font-weight", + "default": "--tap-sys-typography-headline-sm-size" + }, + { + "description": "The line height used for the description in the empty state.", + "name": "--tap-empty-state-description-line-height", + "default": "--tap-sys-typography-headline-sm-size" + }, + { + "description": "The color used for the description in the empty state.", + "name": "--tap-empty-state-description-color", + "default": "--tap-sys-typography-headline-sm-size" + }, + { + "description": "The top margin used for the description in the empty state.", + "name": "--tap-empty-state-description-top-margin", + "default": "--tap-sys-spacing-4" + } + ], + "cssParts": [ + { + "description": "The root container of the empty state component a `div`.", + "name": "container" + }, + { + "description": "The container of the icon slot, a `span`.", + "name": "icon" + }, + { + "description": "The wrapper around heading and description, a `div`.", + "name": "content" + }, + { + "description": "The heading element, a `span`.", + "name": "heading" + }, + { + "description": "The description element, a `p`.", + "name": "description" + }, + { + "description": "The container of the actions slot, a `div`.", + "name": "actions" + } + ], + "slots": [ + { + "description": "the icon slot of the empty state.", + "name": "icon" + }, + { + "description": "the actions slot of the empty state.", + "name": "actions" + } + ], + "members": [ + { + "type": { + "text": "string" + }, + "description": "The title of the empty state.", + "name": "title", + "default": "''", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "The description for the empty state.", + "name": "description", + "default": "''", + "kind": "field" + } + ], + "superclass": { + "name": "EmptyState", + "module": "/src/empty-state/empty-state" + }, + "summary": "An empty state component with icon and actions slots.", + "tagName": "tap-empty-state", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapEmptyState", + "declaration": { + "name": "TapEmptyState", + "module": "src/empty-state/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-empty-state", + "declaration": { + "name": "TapEmptyState", + "module": "src/empty-state/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/icon-button/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapIconButton", + "cssProperties": [ + { + "description": "The font family for the button.", + "name": "--tap-font-family", + "default": "--tap-sys-font-family" + }, + { + "description": "The border radius for the button.", + "name": "--tap-sys-radius-full" + }, + { + "description": "The surface cover color for the button.", + "name": "--tap-button-color-surface-cover", + "default": "--tap-sys-color-surface-overlay-light" + }, + { + "description": "The overlay light color for the button.", + "name": "--tap-sys-color-surface-overlay-light" + }, + { + "description": "The disabled surface color for the button.", + "name": "--tap-sys-color-surface-disabled" + }, + { + "description": "The disabled surface color for the button.", + "name": "--tap-button-color-surface-disabled", + "default": "--tap-sys-color-surface-disabled" + }, + { + "description": "The spacing for the small button height.", + "name": "--tap-sys-spacing-9" + }, + { + "description": "The spacing for the medium button height.", + "name": "--tap-sys-spacing-10" + }, + { + "description": "The spacing for the large button height.", + "name": "--tap-sys-spacing-11" + }, + { + "description": "The spacing for the small button padding.", + "name": "--tap-sys-spacing-5" + }, + { + "description": "The spacing for the medium button padding.", + "name": "--tap-sys-spacing-6" + }, + { + "description": "The spacing for the large button padding.", + "name": "--tap-sys-spacing-8" + }, + { + "description": "The line height for the small button label.", + "name": "--tap-button-typography-label-sm-height", + "default": "--tap-sys-typography-label-sm-height" + }, + { + "description": "The font size for the small button label.", + "name": "--tap-button-typography-label-sm-size", + "default": "--tap-sys-typography-label-sm-size" + }, + { + "description": "The font weight for the small button label.", + "name": "--tap-button-typography-label-sm-weight", + "default": "--tap-sys-typography-label-sm-weight" + }, + { + "description": "The line height for the medium button label.", + "name": "--tap-button-typography-label-md-height", + "default": "--tap-sys-typography-label-sm-height" + }, + { + "description": "The font size for the medium button label.", + "name": "--tap-button-typography-label-md-size", + "default": "--tap-sys-typography-label-sm-size" + }, + { + "description": "The font weight for the medium button label.", + "name": "--tap-button-typography-label-md-weight", + "default": "--tap-sys-typography-label-sm-weight" + }, + { + "description": "The line height for the large button label.", + "name": "--tap-button-typography-label-lg-height", + "default": "--tap-sys-typography-label-lg-height" + }, + { + "description": "The font size for the large button label.", + "name": "--tap-button-typography-label-lg-size", + "default": "--tap-sys-typography-label-lg-size" + }, + { + "description": "The font weight for the large button label.", + "name": "--tap-button-typography-label-lg-weight", + "default": "--tap-sys-typography-label-lg-weight" + }, + { + "description": "The surface color for the primary variant.", + "name": "--tap-button-color-surface-inverse-primary", + "default": "--tap-sys-color-surface-inverse-primary" + }, + { + "description": "The surface color for the primary variant.", + "name": "--tap-sys-color-surface-inverse-primary" + }, + { + "description": "The content color on inverse surface.", + "name": "--tap-button-color-content-on-inverse", + "default": "--tap-sys-color-content-on-inverse" + }, + { + "description": "The content color on inverse surface.", + "name": "--tap-sys-color-content-on-inverse" + }, + { + "description": "The surface color for the ghost variant.", + "name": "--tap-button-color-surface-tertiary", + "default": "--tap-sys-color-surface-tertiary" + }, + { + "description": "The surface color for the ghost variant.", + "name": "--tap-sys-color-surface-tertiary" + }, + { + "description": "The content color for the primary variant.", + "name": "--tap-button-color-content-primary", + "default": "--tap-sys-color-content-primary" + }, + { + "description": "The content color for the primary variant.", + "name": "--tap-sys-color-content-primary" + }, + { + "description": "The surface color for the elevated variant.", + "name": "--tap-button-color-surface-primary", + "default": "--tap-sys-color-surface-primary" + }, + { + "description": "The surface color for the elevated variant.", + "name": "--tap-sys-color-surface-primary" + }, + { + "description": "The surface color for the destructive variant.", + "name": "--tap-button-color-surface-destructive", + "default": "--tap-sys-color-surface-negative-light" + }, + { + "description": "The surface color for the destructive variant.", + "name": "--tap-sys-color-surface-negative-light" + }, + { + "description": "The content color for the destructive variant.", + "name": "--tap-button-color-content-destructive", + "default": "--tap-sys-color-content-negative" + }, + { + "description": "The content color for the destructive variant.", + "name": "--tap-sys-color-content-negative" + }, + { + "description": "The gradient color for the brand variant.", + "name": "--tap-button-color-gradient-brand", + "default": "--tap-sys-color-gradient-brand" + }, + { + "description": "The gradient color for the brand variant.", + "name": "--tap-sys-color-gradient-brand" + } + ], + "cssParts": [ + { + "description": "The button element.", + "name": "button" + } + ], + "slots": [ + { + "description": "Icon button's content.", + "name": "" + } + ], + "members": [ + { + "type": { + "text": "boolean" + }, + "description": "Whether the icon button is disabled.", + "name": "disabled", + "default": "false", + "kind": "field" + }, + { + "type": { + "text": "'small' | 'medium' | 'large'" + }, + "description": "The size of the icon button.", + "name": "size", + "default": "'medium'", + "kind": "field" + }, + { + "type": { + "text": "'primary' | 'ghost' | 'naked' | 'elevated' | 'destructive' | 'brand'" + }, + "description": "The variant style of the icon button.", + "name": "variant", + "default": "'primary'", + "kind": "field" + } + ], + "superclass": { + "name": "IconButton", + "module": "/src/icon-button/icon-button" + }, + "summary": "A customizable icon button component with various styles and states.", + "tagName": "tap-icon-button", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapIconButton", + "declaration": { + "name": "TapIconButton", + "module": "src/icon-button/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-icon-button", + "declaration": { + "name": "TapIconButton", + "module": "src/icon-button/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/input/index.ts", + "declarations": [], + "exports": [ + { + "kind": "js", + "name": "inputStyles", + "declaration": { + "name": "default", + "module": "./input.style" + } + }, + { + "kind": "js", + "name": "Input", + "declaration": { + "name": "Input", + "module": "./input" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/divider/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapDivider", + "cssProperties": [ + { + "description": "The background color of the divider.", + "name": "--tap-divider-background-color", + "default": "--tap-sys-color-border-primary" + }, + { + "description": "The margin around the divider.", + "name": "--tap-divider-margin", + "default": "--tap-sys-spacing-4" + }, + { + "description": "The height of the thin divider.", + "name": "--tap-divider-thin-height", + "default": "--tap-sys-spacing-1" + }, + { + "description": "The height of the medium divider.", + "name": "--tap-divider-medium-height", + "default": "--tap-sys-spacing-2" + }, + { + "description": "The background color of the bold divider.", + "name": "--tap-divider-bold-background-color", + "default": "--tap-sys-color-surface-secondary" + }, + { + "description": "The height of the bold divider.", + "name": "--tap-divider-bold-height", + "default": "--tap-sys-spacing-4" + } + ], + "cssParts": [ + { + "description": "The main container for the divider.", + "name": "divider" + } + ], + "members": [ + { + "type": { + "text": "'thin' | 'medium' | 'bold'" + }, + "description": "The thickness of the divider.", + "name": "type", + "default": "'medium'", + "kind": "field" + } + ], + "superclass": { + "name": "Divider", + "module": "/src/divider/divider.js" + }, + "summary": "A divider component used to separate content.", + "tagName": "tap-divider", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapDivider", + "declaration": { + "name": "TapDivider", + "module": "src/divider/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-divider", + "declaration": { + "name": "TapDivider", + "module": "src/divider/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/modal/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapModal", + "cssProperties": [ + { + "description": "The background color of the overlay.", + "name": "--tap-dialog-color-surface-overlay", + "default": "--tap-sys-color-surface-overlay-dark" + }, + { + "description": "The background color of the modal dialog.", + "name": "--tap-dialog-color-surface-primary", + "default": "--tap-sys-color-surface-primary" + }, + { + "description": "The border radius of the modal dialog.", + "name": "--tap-dialog-radius", + "default": "--tap-sys-radius-6" + }, + { + "description": "The top margin of the icon.", + "name": "--tap-dialog-icon-top-margin", + "default": "--tap-sys-spacing-8" + }, + { + "description": "The background color of the image container.", + "name": "--tap-dialog-image-container-background-color", + "default": "--tap-palette-gray-100" + }, + { + "description": "The vertical padding of the content.", + "name": "--tap-dialog-content-vertical-padding", + "default": "--tap-sys-spacing-4" + }, + { + "description": "The horizontal padding of the content.", + "name": "--tap-dialog-content-horizontal-padding", + "default": "--tap-sys-spacing-6" + }, + { + "description": "The vertical margin of the content.", + "name": "--tap-dialog-content-vertical-margin", + "default": "--tap-sys-spacing-6" + }, + { + "description": "The horizontal margin of the content.", + "name": "--tap-dialog-content-horizontal-margin" + }, + { + "description": "The font size of the title in the dialog modal.", + "name": "--tap-dialog-title-font-size", + "default": "--tap-sys-typography-headline-sm-size" + }, + { + "description": "The font weight of title in the dialog modal.", + "name": "--tap-dialog-title-font-weight", + "default": "--tap-sys-typography-headline-sm-weight" + }, + { + "description": "The line height of title in the dialog modal.", + "name": "--tap-dialog-title-line-height", + "default": "--tap-sys-typography-headline-sm-height" + }, + { + "description": "The margin of the description in the dialog modal.", + "name": "--tap-dialog-description-margin" + }, + { + "description": "The top padding of the description in the dialog modal.", + "name": "--tap-dialog-description-padding-top", + "default": "--tap-sys-spacing-4" + }, + { + "description": "The font size of the description in the dialog modal.", + "name": "--tap-dialog-description-font-size", + "default": "--tap-sys-typography-body-md-size" + }, + { + "description": "The font weight of the description in the dialog modal.", + "name": "--tap-dialog-description-font-weight", + "default": "--tap-sys-typography-body-md-weight" + }, + { + "description": "The line height of the description in the dialog modal.", + "name": "--tap-dialog-description-line-height", + "default": "--tap-sys-typography-body-md-height" + }, + { + "description": "The color of the description in the dialog modal.", + "name": "--tap-dialog-description-color", + "default": "--tap-palette-gray-500" + }, + { + "description": "The padding of the actions in the dialog modal.", + "name": "--tap-dialog-actions-padding", + "default": "--tap-sys-spacing-6" + } + ], + "cssParts": [ + { + "description": "The overlay background when the modal is open.", + "name": "overlay" + }, + { + "description": "The main container for the modal dialog.", + "name": "dialog" + }, + { + "description": "The container of the image element, a `div`.", + "name": "image-container" + }, + { + "description": "The image element, a `img`.", + "name": "image" + }, + { + "description": "The container of the icon slot, a `div`.", + "name": "icon" + }, + { + "description": "The wrapper around title and description, a `div`.", + "name": "content" + }, + { + "description": "The title element, a `span`.", + "name": "title" + }, + { + "description": "The description element, a `p`.", + "name": "description" + }, + { + "description": " The container of the actions slot, a `div`.", + "name": "actions" + } + ], + "slots": [ + { + "description": "the icon slot of the modal dialog.", + "name": "icon" + }, + { + "description": "the actions slot of the modal dialog.", + "name": "actions" + } + ], + "members": [ + { + "type": { + "text": "boolean" + }, + "description": "Indicates whether the modal is open.", + "name": "open", + "default": "false", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "Sets the title of the modal. If not provided, no title is displayed.", + "name": "title", + "default": "''", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "Provides a description or content text for the modal. If empty, no description is shown.", + "name": "description", + "default": "''", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "Determines the alignment of the modal's content. Can be 'right', or 'center'.", + "name": "alignment", + "default": "'right'", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "URL of an image to display within the modal. If specified, the image is shown above the content, if not pass the icon slot.", + "name": "image", + "kind": "field" + } + ], + "superclass": { + "name": "Modal", + "module": "/src/modal/modal" + }, + "summary": "A modal dialog component.", + "tagName": "tap-modal", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapModal", + "declaration": { + "name": "TapModal", + "module": "src/modal/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-modal", + "declaration": { + "name": "TapModal", + "module": "src/modal/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/notice/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapNotice", + "cssProperties": [ + { + "description": "The width of the notice component.", + "name": "--tap-notice-width", + "default": "100%" + }, + { + "description": "The height of the notice component.", + "name": "--tap-notice-height", + "default": "auto" + }, + { + "description": "The default gap between horizontal items of the notice.", + "name": "--tap-notice-gap", + "default": "--tap-sys-spacing-5" + }, + { + "description": "The default border radius of the notice.", + "name": "--tap-notice-radius", + "default": "--tap-sys-radius-3" + }, + { + "description": "The default vertical padding of the notice.", + "name": "--tap-notice-vertical-padding", + "default": "--tap-sys-spacing-6" + }, + { + "description": "The default horizontal padding of the notice.", + "name": "--tap-notice-horizontal-padding", + "default": "--tap-sys-spacing-5" + }, + { + "description": "The default font used for tapsi web components.", + "name": "--tap-font-family", + "default": "--tap-sys-font-family" + }, + { + "description": "The space between message and action buttons if there are any.", + "name": "--tap-notice-actions-margin-top", + "default": "--tap-sys-spacing-4" + }, + { + "name": "--tap-notice-title-font-size", + "default": "--tap-sys-typography-label-md-size" + }, + { + "name": "--tap-notice-title-line-height", + "default": "--tap-sys-typography-label-md-height" + }, + { + "name": "--tap-notice-title-font-weight", + "default": "--tap-sys-typography-label-md-weight" + }, + { + "name": "--tap-notice-message-font-size", + "default": "--tap-sys-typography-label-sm-size" + }, + { + "name": "--tap-notice-message-line-height", + "default": "--tap-sys-typography-label-sm-height" + }, + { + "name": "--tap-notice-message-font-weight", + "default": "--tap-sys-typography-label-sm-weight" + }, + { + "name": "--tap-notice-message-font-weight", + "default": "--tap-sys-typography-label-sm-weight" + }, + { + "description": "The text and icon color in the specified variant and high priority. The icon color in the specified variant and low priority.", + "name": "--tap-notice-[variant]-[priority]-color" + }, + { + "description": "The background color in the specified variant and priority.", + "name": "--tap-notice-[variant]-[priority]-bg-color" + }, + { + "description": "The color of the title in the low priority mode (overrides --tap-notice-[variant]-[priority]-color).", + "name": "--tap-notice-title-low-color", + "default": "--tap-sys-color-content-primary" + }, + { + "description": "The color of the message in the low priority mode (overrides --tap-notice-[variant]-[priority]-color).", + "name": "--tap-notice-message-low-color", + "default": "--tap-sys-color-content-secondary" + }, + { + "description": "The color of the dismiss button in the low priority mode (overrides --tap-notice-[variant]-[priority]-color).", + "name": "--tap-notice-dismiss-low-color", + "default": "--tap-sys-color-content-secondary" + } + ], + "cssParts": [ + { + "description": "The root container of the notice component a `div`.", + "name": "notice" + }, + { + "description": "The container of the icon, a `span`.", + "name": "icon" + }, + { + "description": "The wrapper around title, message and actions, a `div`.", + "name": "content-root" + }, + { + "description": "The message element, a `p`.", + "name": "message" + }, + { + "description": "The title element, a `p`.", + "name": "title" + }, + { + "description": "The actions slot, a `slot`.", + "name": "actions" + }, + { + "description": "The dismiss button, a `button`.", + "name": "dismiss" + } + ], + "slots": [ + { + "description": "notice text", + "name": "" + }, + { + "description": "slot actions", + "name": "actions" + } + ], + "members": [ + { + "type": { + "text": "'success' | 'error' | 'info' | 'warning' | 'inverse'" + }, + "description": "The variant of the notice. Defaults to `inverse`.", + "name": "variant", + "default": "'inverse'", + "kind": "field" + }, + { + "type": { + "text": "'high' | 'low'" + }, + "description": "The priority of the notice. Defaults to `high`. Bolder colors are used with `high` priority whereas lighter colors are used for 'low' priority notices.", + "name": "priority", + "default": "'high'", + "kind": "field" + }, + { + "type": { + "text": "boolean" + }, + "description": "Indicates whether the notice can be dismissed or not. When dismissable, the dismiss button is rendered and emits 'dismiss' event upon click.", + "name": "dismissable", + "default": "false", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "kind": "field" + } + ], + "events": [ + { + "description": "Fires when the dismiss button is clicked (if dismissable).", + "name": "dismiss" + } + ], + "superclass": { + "name": "Notice", + "module": "/src/notice/notice" + }, + "summary": "A notice/alert component is a user interface element that displays important messages or alerts to inform or warn users about specific events or actions.", + "tagName": "tap-notice", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapNotice", + "declaration": { + "name": "TapNotice", + "module": "src/notice/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-notice", + "declaration": { + "name": "TapNotice", + "module": "src/notice/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/pinwheel/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapPinwheel", + "cssProperties": [ + { + "name": "--tap-pinwheel-vertical-padding", + "default": "--tap-sys-spacing-0" + }, + { + "name": "--tap-pinwheel-horizontal-padding", + "default": "--tap-sys-spacing-6" + }, + { + "name": "--tap-pinwheel-item-height", + "default": "--tap-sys-spacing-11" + }, + { + "name": "--tap-font-family", + "default": "--tap-sys-font-family" + }, + { + "name": "--tap-pinwheel-typography-body-md-height", + "default": "--tap-sys-typography-body-md-height" + }, + { + "name": "--tap-pinwheel-typography-body-md-size", + "default": "--tap-sys-typography-body-md-size" + }, + { + "name": "--tap-pinwheel-typography-body-md-weight", + "default": "--tap-sys-typography-body-md-weight" + }, + { + "name": "--tap-pinwheel-item-color", + "default": "--tap-sys-color-content-tertiary" + }, + { + "name": "--tap-pinwheel-typography-label-md-height", + "default": "--tap-sys-typography-label-md-height" + }, + { + "name": "--tap-pinwheel-typography-label-md-size", + "default": "--tap-sys-typography-label-md-size" + }, + { + "name": "--tap-pinwheel-typography-label-md-weight", + "default": "--tap-sys-typography-label-md-weight" + }, + { + "name": "--tap-pinwheel-active-item-color", + "default": "--tap-sys-color-content-primary" + } + ], + "cssParts": [ + { + "description": "The container that wraps the pinwheel component.", + "name": "pinwheel" + }, + { + "description": "The item that rendered in pinwheel.", + "name": "pinwheel-item" + } + ], + "members": [ + { + "type": { + "text": "string[]" + }, + "description": "An array of strings that contains pinwheel items.", + "name": "items", + "default": "[]", + "kind": "field" + } + ], + "events": [ + { + "description": "Fires when the pinwheel changes", + "name": "pinwheel-change" + } + ], + "superclass": { + "name": "Pinwheel", + "module": "/src/pinwheel/pinwheel" + }, + "summary": "A pinwheel component for selecting items by scrolling through a list.", + "tagName": "tap-pinwheel", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapPinwheel", + "declaration": { + "name": "TapPinwheel", + "module": "src/pinwheel/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-pinwheel", + "declaration": { + "name": "TapPinwheel", + "module": "src/pinwheel/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/pinwheel-group/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapPinwheelGroup", + "cssProperties": [ + { + "name": "--tap-pinwheel-group-selector-radius", + "default": "--tap-sys-spacing-4" + }, + { + "name": "--tap-pinwheel-group-selector-background-color", + "default": "--tap-sys-color-surface-tertiary" + } + ], + "cssParts": [ + { + "description": "The container that wraps the pinwheels.", + "name": "pinwheel-group" + }, + { + "description": "The selector indicator that wraps selected row.", + "name": "pinwheel-selector-indicator" + } + ], + "slots": [ + { + "description": "The default slot to get pinwheels.", + "name": "" + } + ], + "members": [], + "superclass": { + "name": "PinwheelGroup", + "module": "/src/pinwheel-group/pinwheel-group" + }, + "summary": "Grouping pinwheel components and attach selector indicator to them.", + "tagName": "tap-pinwheel-group", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapPinwheelGroup", + "declaration": { + "name": "TapPinwheelGroup", + "module": "src/pinwheel-group/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-pinwheel-group", + "declaration": { + "name": "TapPinwheelGroup", + "module": "src/pinwheel-group/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/progress-indicator/index.ts", + "declarations": [ + { + "kind": "class", + "description": "### Example", + "name": "TapProgressIndicator", + "cssProperties": [ + { + "description": "The gap between steps.", + "name": "--tap-progress-indicator-progressbar-color", + "default": "--tap-sys-spacing-3" + }, + { + "description": "The height of each step.", + "name": "--tap-progress-indicator-step-height", + "default": "--tap-sys-spacing-3" + }, + { + "description": "The background color of each step.", + "name": "--tap-progress-indicator-step-background-color", + "default": "--tap-sys-color-surface-tertiary" + }, + { + "description": "The background color of active steps.", + "name": "--tap-progress-indicator-active-step-background-color", + "default": "--tap-sys-color-border-inverse-primary" + } + ], + "cssParts": [ + { + "description": "The container for the progress bar.", + "name": "progressbar" + }, + { + "description": "Each individual step in the progress bar.", + "name": "step" + } + ], + "members": [ + { + "type": { + "text": "number" + }, + "description": "The total number of steps.", + "name": "max", + "default": "2", + "kind": "field" + }, + { + "type": { + "text": "number" + }, + "description": "The current step index.", + "name": "current", + "default": "0", + "kind": "field" + } + ], + "superclass": { + "name": "ProgressIndicator", + "module": "/src/progress-indicator/progress-indicator.js" + }, + "summary": "A progress indicator component displaying multiple steps.", + "tagName": "tap-progress-indicator", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapProgressIndicator", + "declaration": { + "name": "TapProgressIndicator", + "module": "src/progress-indicator/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-progress-indicator", + "declaration": { + "name": "TapProgressIndicator", + "module": "src/progress-indicator/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/radio/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapRadio", + "cssProperties": [ + { + "description": "The border radius of the radio button.", + "name": "--tap-radio-border-radius", + "default": "--tap-sys-radius-full" + }, + { + "description": "The height of the radio button.", + "name": "--tap-radio-height", + "default": "--tap-sys-spacing-7" + }, + { + "description": "The width of the radio button.", + "name": "--tap-radio-width", + "default": "--tap-sys-spacing-7" + }, + { + "description": "The background color of the radio button.", + "name": "--tap-radio-background-color", + "default": "--tap-sys-color-surface-primary" + }, + { + "description": "The border color of the radio button.", + "name": "--tap-radio-border", + "default": "--tap-sys-color-border-inverse-primary" + }, + { + "description": "The background color of the checked radio button.", + "name": "--tap-radio-checked-background-color", + "default": "--tap-sys-color-surface-inverse-primary" + }, + { + "description": "The color of the content inside the checked radio button.", + "name": "--tap-radio-checked-color", + "default": "--tap-sys-color-content-on-inverse" + }, + { + "description": "The background color of the disabled radio button.", + "name": "--tap-radio-disabled-background-color", + "default": "--tap-sys-color-surface-disabled" + }, + { + "description": "The border color of the disabled radio button.", + "name": "--tap-radio-disabled-border-color", + "default": "--tap-sys-color-surface-disabled" + }, + { + "description": "The color of the content inside the disabled radio button.", + "name": "--tap-radio-disabled-color", + "default": "--tap-sys-color-content-disabled" + }, + { + "description": "The height of the radio button input.", + "name": "--tap-radio-input-height", + "default": "--tap-sys-spacing-7" + }, + { + "description": "The width of the radio button input.", + "name": "--tap-radio-input-width", + "default": "--tap-sys-spacing-7" + } + ], + "cssParts": [ + { + "description": "The main container for the radio button.", + "name": "radio" + } + ], + "members": [ + { + "type": { + "text": "boolean" + }, + "description": "Indicates whether the radio button is checked.", + "name": "checked", + "default": "false", + "kind": "field" + }, + { + "type": { + "text": "boolean" + }, + "description": "Indicates whether the radio button is disabled.", + "name": "disabled", + "default": "false", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "The value of the radio button.", + "name": "value", + "default": "''", + "kind": "field" + } + ], + "events": [ + { + "description": "Fires when a radio option is selected", + "name": "radio-input-change" + } + ], + "superclass": { + "name": "Radio", + "module": "/src/radio/radio" + }, + "summary": "A radio button component.", + "tagName": "tap-radio", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapRadio", + "declaration": { + "name": "TapRadio", + "module": "src/radio/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-radio", + "declaration": { + "name": "TapRadio", + "module": "src/radio/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/radio-group/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapRadioGroup", + "cssProperties": [ + { + "description": "The padding around the radio group.", + "name": "--tap-radio-group-padding", + "default": "--tap-sys-spacing-3" + }, + { + "description": "The gap between radio buttons.", + "name": "--tap-radio-group-gap", + "default": "--tap-sys-spacing-3" + } + ], + "cssParts": [ + { + "description": "The main container for the radio group.", + "name": "radio-group" + } + ], + "members": [ + { + "type": { + "text": "'horizontal' | 'vertical'" + }, + "description": "The direction in which the radio buttons are laid out. Defaults to `vertical`.", + "name": "direction", + "default": "'vertical'", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "The value of the selected radio button.", + "name": "value", + "default": "''", + "kind": "field" + } + ], + "events": [ + { + "description": "Dispatched when the selected radio button changes.", + "name": "radio-group-change" + } + ], + "superclass": { + "name": "RadioGroup", + "module": "/src/radio-group/radio-group" + }, + "summary": "A group of radio buttons.", + "tagName": "tap-radio-group", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapRadioGroup", + "declaration": { + "name": "TapRadioGroup", + "module": "src/radio-group/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-radio-group", + "declaration": { + "name": "TapRadioGroup", + "module": "src/radio-group/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/route/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapRoute", + "cssProperties": [ + { + "description": "The font family used in the row.", + "name": "--tap-font-family", + "default": "--tap-sys-font-family" + }, + { + "description": "The background color of the row.", + "name": "--tap-row-background-color", + "default": "--tap-palette-white" + }, + { + "description": "The vertical padding for the leading slot.", + "name": "--tap-row-leading-vertical-padding" + }, + { + "description": "The horizontal padding for the leading slot.", + "name": "--tap-row-leading-horizontal-padding", + "default": "--tap-sys-spacing-4" + }, + { + "description": "The padding for the content slot.", + "name": "--tap-row-content-padding", + "default": "--tap-sys-spacing-4" + }, + { + "description": "The vertical padding for the trailing slot.", + "name": "--tap-row-trailing-vertical-padding" + }, + { + "description": "The horizontal padding for the trailing slot.", + "name": "--tap-row-trailing-horizontal-padding", + "default": "--tap-sys-spacing-4" + }, + { + "description": "The height of the standard size row.", + "name": "--tap-row-standard-height", + "default": "--tap-sys-spacing-13" + }, + { + "description": "The height of the compact size row.", + "name": "--tap-row-compact-height", + "default": "--tap-sys-spacing-12" + } + ], + "slots": [ + { + "description": "the content slot of the row", + "name": "content" + } + ], + "members": [ + { + "type": { + "text": "boolean" + }, + "description": "The size of the row. Defaults to `standard`.", + "name": "editable", + "default": "false", + "kind": "field" + }, + { + "type": { + "text": "'circle'|'square'|'plus'" + }, + "description": "Indicates whether the row is navigable (clickable).", + "name": "leading-icon", + "default": "'circle'", + "kind": "field" + }, + { + "type": { + "text": "'first'|'middle'|'last'" + }, + "description": "Indicates whether the row is navigable (clickable).", + "name": "ordinal", + "default": "'first'", + "kind": "field" + } + ], + "events": [ + { + "description": "Dispatched when the content of any slot changes.", + "name": "slotchange" + } + ], + "superclass": { + "name": "Route", + "module": "/src/route/route" + }, + "summary": "A route component.", + "tagName": "tap-route", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapRoute", + "declaration": { + "name": "TapRoute", + "module": "src/route/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-route", + "declaration": { + "name": "TapRoute", + "module": "src/route/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/row/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapRow", + "cssProperties": [ + { + "description": "The font family used in the row.", + "name": "--tap-font-family", + "default": "--tap-sys-font-family" + }, + { + "description": "The background color of the row.", + "name": "--tap-row-background-color", + "default": "--tap-palette-white" + }, + { + "description": "The vertical padding for the leading slot.", + "name": "--tap-row-leading-vertical-padding" + }, + { + "description": "The horizontal padding for the leading slot.", + "name": "--tap-row-leading-horizontal-padding", + "default": "--tap-sys-spacing-4" + }, + { + "description": "The padding for the content slot.", + "name": "--tap-row-content-padding", + "default": "--tap-sys-spacing-4" + }, + { + "description": "The vertical padding for the trailing slot.", + "name": "--tap-row-trailing-vertical-padding" + }, + { + "description": "The horizontal padding for the trailing slot.", + "name": "--tap-row-trailing-horizontal-padding", + "default": "--tap-sys-spacing-4" + }, + { + "description": "The height of the standard size row.", + "name": "--tap-row-standard-height", + "default": "--tap-sys-spacing-13" + }, + { + "description": "The height of the compact size row.", + "name": "--tap-row-compact-height", + "default": "--tap-sys-spacing-12" + } + ], + "cssParts": [ + { + "description": "The main container for the row.", + "name": "row" + }, + { + "description": "The container for the leading slot.", + "name": "leading" + }, + { + "description": "The container for the content slot.", + "name": "content" + }, + { + "description": "The container for the trailing slot.", + "name": "trailing" + }, + { + "description": "The container for the navigable icon.", + "name": "navigable" + } + ], + "slots": [ + { + "description": "the leading slot of the row", + "name": "leading" + }, + { + "description": "the content slot of the row", + "name": "content" + }, + { + "description": "the trailing slot of the row", + "name": "trailing" + } + ], + "members": [ + { + "type": { + "text": "'standard' | 'compact'" + }, + "description": "The size of the row. Defaults to `standard`.", + "name": "size", + "default": "'compact'", + "kind": "field" + }, + { + "type": { + "text": "boolean" + }, + "description": "Indicates whether the row is navigable (clickable).", + "name": "navigable", + "default": "false", + "kind": "field" + } + ], + "events": [ + { + "description": "Dispatched when the content of any slot changes.", + "name": "slotchange" + } + ], + "superclass": { + "name": "Row", + "module": "/src/row/row" + }, + "summary": "A flexible row component with leading, content, and trailing slots.", + "tagName": "tap-row", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapRow", + "declaration": { + "name": "TapRow", + "module": "src/row/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-row", + "declaration": { + "name": "TapRow", + "module": "src/row/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/segmented-button/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapSegmentedButton", + "cssProperties": [ + { + "description": "The font family used in the button.", + "name": "--tap-font-family", + "default": "--tap-sys-font-family" + }, + { + "description": "The border radius of the button.", + "name": "--tap-segmented-button-border-radius", + "default": "--tap-sys-radius-full" + }, + { + "description": "The text color of the button.", + "name": "--tap-segmented-button-color", + "default": "--tap-sys-color-content-primary" + }, + { + "description": "The line height of the button text.", + "name": "--tap-segmented-button-line-height", + "default": "--tap-sys-typography-label-sm-height" + }, + { + "description": "The font size of the button text.", + "name": "--tap-segmented-button-font-size", + "default": "--tap-sys-typography-label-sm-size" + }, + { + "description": "The font weight of the button text.", + "name": "--tap-segmented-button-font-weight", + "default": "--tap-sys-typography-label-sm-weight" + }, + { + "description": "The background color of the selected button.", + "name": "--tap-segmented-button-selected-background-color", + "default": "--tap-sys-color-surface-primary" + } + ], + "cssParts": [ + { + "description": "The main container for the button.", + "name": "button" + } + ], + "slots": [ + { + "description": "segmented button content", + "name": "" + } + ], + "members": [ + { + "type": { + "text": "boolean" + }, + "description": "Indicates whether the button is selected.", + "name": "selected", + "default": "false", + "kind": "field" + }, + { + "type": { + "text": "boolean" + }, + "description": "Indicates whether the button is disabled.", + "name": "disabled", + "default": "false", + "kind": "field" + } + ], + "superclass": { + "name": "SegmentedButton", + "module": "/src/segmented-button/segmented-button" + }, + "summary": "A segmented button component.", + "tagName": "tap-segmented-button", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapSegmentedButton", + "declaration": { + "name": "TapSegmentedButton", + "module": "src/segmented-button/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-segmented-button", + "declaration": { + "name": "TapSegmentedButton", + "module": "src/segmented-button/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/segmented-button-group/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapSegmentedButtonGroup", + "cssProperties": [ + { + "description": "The background color of the button group.", + "name": "--tap-segmented-button-group-color-surface", + "default": "--tap-sys-color-surface-secondary" + }, + { + "description": "The border radius of the button group.", + "name": "--tap-segmented-button-group-border-radius", + "default": "--tap-sys-radius-full" + }, + { + "description": "The padding inside the button group.", + "name": "--tap-segmented-button-group-padding", + "default": "--tap-sys-spacing-3" + }, + { + "description": "The height of the small size button group.", + "name": "--tap-segmented-button-group-sm-height", + "default": "--tap-sys-spacing-10" + }, + { + "description": "The height of the medium size button group.", + "name": "--tap-segmented-button-group-md-height", + "default": "--tap-sys-spacing-11" + } + ], + "cssParts": [ + { + "description": "The main container for the button group.", + "name": "button-group" + } + ], + "slots": [ + { + "description": "segmented button group contents, you should use some `tap-segmented-button` components here", + "name": "" + } + ], + "members": [ + { + "type": { + "text": "'sm' | 'md'" + }, + "description": "The size of the segmented button group.", + "name": "size", + "default": "'md'", + "kind": "field" + } + ], + "events": [ + { + "description": "Fires when the button group is changed", + "name": "segmented-button-group-change" + } + ], + "superclass": { + "name": "SegmentedButtonGroup", + "module": "/src/segmented-button-group/segmented-button-group" + }, + "summary": "A group of segmented buttons.", + "tagName": "tap-segmented-button-group", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapSegmentedButtonGroup", + "declaration": { + "name": "TapSegmentedButtonGroup", + "module": "src/segmented-button-group/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-segmented-button-group", + "declaration": { + "name": "TapSegmentedButtonGroup", + "module": "src/segmented-button-group/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/skeleton/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapSkeleton", + "cssProperties": [ + { + "description": "Background color of the skeleton", + "name": "--tap-skeleton-background", + "default": "--tap-sys-color-surface-tertiary" + }, + { + "description": "Border radius of the skeleton", + "name": "--tap-skeleton-radius", + "default": "--tap-sys-radius-2" + }, + { + "description": "Width of the skeleton", + "name": "--tap-skeleton-width", + "default": "100%" + }, + { + "description": "Height of the skeleton", + "name": "--tap-skeleton-height", + "default": "--tap-sys-spacing-8" + }, + { + "description": "Border radius of the skeleton variant rect", + "name": "--tap-skeleton-rect-radius", + "default": "--tap-sys-spacing-0" + }, + { + "description": "Border radius of the skeleton variant circle", + "name": "--tap-skeleton-circle-radius", + "default": "--tap-sys-radius-full" + }, + { + "description": "Width of the skeleton variant circle", + "name": "--tap-skeleton-circle-width", + "default": "--tap-sys-spacing-10" + }, + { + "description": "Height of the skeleton variant circle", + "name": "--tap-skeleton-circle-height", + "default": "--tap-sys-spacing-10" + } + ], + "cssParts": [ + { + "description": "The skeleton element", + "name": "skeleton" + } + ], + "members": [ + { + "type": { + "text": "\"line\" | \"rect\" | \"circle\"" + }, + "description": "The variant of the skeleton.", + "name": "variant", + "default": "\"line\"", + "kind": "field" + }, + { + "type": { + "text": "\"progress\" | \"pulse\" | \"none\"" + }, + "description": "The animation mode of the skeleton.", + "name": "animation-mode", + "default": "\"progress\"", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "The width value of the skeleton.", + "name": "width", + "default": "\"100%\"", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "The height value of the skeleton.", + "name": "height", + "default": "\"20px\"", + "kind": "field" + } + ], + "superclass": { + "name": "Skeleton", + "module": "/src/skeleton/skeleton" + }, + "summary": "Display Skeleton component with different styles and types.", + "tagName": "tap-skeleton", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapSkeleton", + "declaration": { + "name": "TapSkeleton", + "module": "src/skeleton/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-skeleton", + "declaration": { + "name": "TapSkeleton", + "module": "src/skeleton/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/spinner/index.ts", + "declarations": [ + { + "kind": "class", + "description": "### Example\n\n\n##### Simple\n\n```html\n\n```\n\n##### Variant\n\n```html\n\n```", + "name": "TapSpinner", + "cssProperties": [ + { + "name": "`--tap-spinner-color-primary`", + "default": "`--tap-sys-color-surface-black`" + }, + { + "name": "`--tap-spinner-color-inverse`", + "default": "`--tap-sys-color-surface-white`" + }, + { + "name": "`--tap-spinner-sm-size`", + "default": "`--tap-sys-spacing-7`" + }, + { + "name": "`--tap-spinner-md-size`", + "default": "`--tap-sys-spacing-8`" + }, + { + "name": "`--tap-spinner-lg-size`", + "default": "`--tap-sys-spacing-10`" + }, + { + "name": "`--tap-spinner-padding`", + "default": "`--tap-sys-spacing-2`" + } + ], + "cssParts": [ + { + "description": "The svg tag that represents spinner component.", + "name": "`svg`" + } + ], + "members": [ + { + "type": { + "text": "`'default'` \\| `'primary'` \\| `'inverse'`" + }, + "description": "Specifies the spinner color. Acceptable values are `primary` and `inverse`.", + "name": "`variant`", + "default": "`'primary'`", + "kind": "field" + } + ], + "superclass": { + "name": "Spinner", + "module": "/src/spinner/spinner" + }, + "summary": "Display spinner.", + "tagName": "tap-spinner", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapSpinner", + "declaration": { + "name": "TapSpinner", + "module": "src/spinner/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-spinner", + "declaration": { + "name": "TapSpinner", + "module": "src/spinner/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/step-indicator/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapStepIndicator", + "cssProperties": [ + { + "description": "The size of each step.", + "name": "--tap-step-indicator-step-size", + "default": "--tap-sys-spacing-5" + }, + { + "description": "The border radius of each step.", + "name": "--tap-step-indicator-step-border-radius", + "default": "--tap-sys-radius-full" + }, + { + "description": "The border color of each step.", + "name": "--tap-step-indicator-step-border-color", + "default": "--tap-sys-color-surface-primary" + }, + { + "description": "The background color of each step.", + "name": "--tap-step-indicator-step-background-color", + "default": "--tap-sys-color-border-primary" + } + ], + "cssParts": [ + { + "description": "The container for the steps.", + "name": "steps" + }, + { + "description": "Each individual step.", + "name": "step" + } + ], + "members": [ + { + "type": { + "text": "number" + }, + "description": "The total number of steps. Defaults to 2.", + "name": "steps", + "default": "2", + "kind": "field" + }, + { + "type": { + "text": "number" + }, + "description": "The current step index. Defaults to 0.", + "name": "current", + "default": "0", + "kind": "field" + } + ], + "events": [ + { + "description": "Step indicator change event", + "name": "tap-step-indicator-change" + } + ], + "superclass": { + "name": "StepIndicator", + "module": "/src/step-indicator/step-indicator.js" + }, + "summary": "A step indicator component.", + "tagName": "tap-step-indicator", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapStepIndicator", + "declaration": { + "name": "TapStepIndicator", + "module": "src/step-indicator/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-step-indicator", + "declaration": { + "name": "TapStepIndicator", + "module": "src/step-indicator/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/stepper/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapStepper", + "cssProperties": [ + { + "name": "--tap-font-family", + "default": "--tap-sys-font-family" + }, + { + "name": "--tap-stepper-typography-label-sm-size", + "default": "--tap-sys-typography-label-md-size" + }, + { + "name": "--tap-stepper-typography-label-md-size", + "default": "--tap-sys-typography-label-lg-size" + } + ], + "cssParts": [ + { + "description": "The container that wraps the stepper component.", + "name": "stepper" + }, + { + "description": "The button that contains minus icon and decrease the value.", + "name": "decrease-button" + }, + { + "description": "The button that contains plus icon and increases the value.", + "name": "increase-button" + }, + { + "description": "The `p` tag that shows the `value`.", + "name": "value" + }, + { + "description": "The `p` tag that shows the `unit` and will be hidden if `unit` not exists.", + "name": "unit" + } + ], + "members": [ + { + "type": { + "text": "string" + }, + "description": "A single quantity of the value", + "name": "unit", + "default": "''", + "kind": "field" + }, + { + "type": { + "text": "boolean" + }, + "description": "If `true`, the component is disabled.", + "name": "disabled", + "default": "false", + "kind": "field" + }, + { + "type": { + "text": "number" + }, + "description": "The number to which the current value is increased or decreased. It can be an integer or decimal", + "name": "step", + "default": "1", + "kind": "field" + }, + { + "type": { + "text": "'small' | 'medium'" + }, + "description": "The size of icons and font.", + "name": "size", + "default": "'medium'", + "kind": "field" + }, + { + "type": { + "text": "number" + }, + "description": "The min value.", + "name": "min", + "default": "-Infinity", + "kind": "field" + }, + { + "type": { + "text": "number" + }, + "description": "The max value.", + "name": "max", + "default": "Infinity", + "kind": "field" + }, + { + "type": { + "text": "number" + }, + "description": "If `true` the component will fill the parent.", + "name": "fullWidth", + "default": "false", + "kind": "field" + }, + { + "type": { + "text": "number" + }, + "description": "The current value.", + "name": "value", + "default": "0", + "kind": "field" + } + ], + "events": [ + { + "description": "Fired when the value of the stepper changes.", + "name": "stepper-change" + } + ], + "superclass": { + "name": "Stepper", + "module": "/src/stepper/stepper.js" + }, + "summary": "A form item that shows a value and can be increased or decreased by tw buttons", + "tagName": "tap-stepper", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapStepper", + "declaration": { + "name": "TapStepper", + "module": "src/stepper/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-stepper", + "declaration": { + "name": "TapStepper", + "module": "src/stepper/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/text-field/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapTextField", + "cssProperties": [ + { + "description": "The font family used in the text field.", + "name": "--tap-font-family", + "default": "--tap-sys-font-family" + }, + { + "description": "The gap between the elements in the text field.", + "name": "--tap-input-field-gap", + "default": "--tap-sys-spacing-4" + }, + { + "description": "The color of the label.", + "name": "--tap-input-label-color", + "default": "--tap-sys-color-content-primary" + }, + { + "description": "The line height of the label.", + "name": "--tap-input-label-line-height", + "default": "--tap-sys-typography-label-sm-height" + }, + { + "description": "The font size of the label.", + "name": "--tap-input-label-font-size", + "default": "--tap-sys-typography-label-sm-size" + }, + { + "description": "The font weight of the label.", + "name": "--tap-input-label-font-weight", + "default": "--tap-sys-typography-label-sm-weight" + }, + { + "description": "The color of the caption.", + "name": "--tap-input-caption-color", + "default": "--tap-sys-color-content-tertiary" + }, + { + "description": "The line height of the caption.", + "name": "--tap-input-caption-line-height", + "default": "--tap-sys-typography-body-sm-height" + }, + { + "description": "The font size of the caption.", + "name": "--tap-input-caption-font-size", + "default": "--tap-sys-typography-body-sm-size" + }, + { + "description": "The font weight of the caption.", + "name": "--tap-input-caption-font-weight", + "default": "--tap-sys-typography-body-sm-weight" + }, + { + "description": "The height of the container.", + "name": "--tap-input-container-height", + "default": "--tap-sys-spacing-11" + }, + { + "description": "The padding inside the container.", + "name": "--tap-input-container-padding", + "default": "--tap-sys-spacing-6" + }, + { + "description": "The gap between elements in the container.", + "name": "--tap-input-container-gap", + "default": "--tap-sys-spacing-4" + }, + { + "description": "The background color of the container.", + "name": "--tap-input-container-background-color", + "default": "--tap-sys-color-surface-tertiary" + }, + { + "description": "The border radius of the container.", + "name": "--tap-input-container-border-radius", + "default": "--tap-sys-radius-3" + }, + { + "description": "The background color of the container when focused.", + "name": "--tap-input-focus-background-color", + "default": "--tap-sys-color-surface-secondary" + }, + { + "description": "The border width of the container when focused.", + "name": "--tap-input-focus-border-width", + "default": "--tap-sys-color-border-inverse-primary" + }, + { + "description": "The color of the input text.", + "name": "--tap-text-field-input-color", + "default": "--tap-sys-color-content-primary" + }, + { + "description": "The line height of the input text.", + "name": "--tap-text-field-input-line-height", + "default": "--tap-sys-typography-body-md-height" + }, + { + "description": "The font size of the input text.", + "name": "--tap-text-field-input-font-size", + "default": "--tap-sys-typography-body-md-size" + }, + { + "description": "The font weight of the input text.", + "name": "--tap-text-field-input-font-weight", + "default": "--tap-sys-typography-body-md-weight" + }, + { + "description": "The color of the placeholder text.", + "name": "--tap-text-field-input-placeholder-color", + "default": "--tap-sys-color-content-tertiary" + }, + { + "description": "The line height of the placeholder text.", + "name": "--tap-text-field-input-placeholder-line-height", + "default": "--tap-sys-typography-body-md-height" + }, + { + "description": "The font size of the placeholder text.", + "name": "--tap-text-field-input-placeholder-font-size", + "default": "--tap-sys-typography-body-md-size" + }, + { + "description": "The font weight of the placeholder text.", + "name": "--tap-text-field-input-placeholder-font-weight", + "default": "--tap-sys-typography-body-md-weight" + }, + { + "description": "The color of the caption when there is an error.", + "name": "--tap-input-error-caption-color", + "default": "--tap-sys-color-content-negative" + }, + { + "description": "The background color of the container when there is an error.", + "name": "--tap-input-error-container-background-color", + "default": "--tap-sys-color-surface-negative-light" + }, + { + "description": "The border color of the container when there is an error.", + "name": "--tap-input-error-container-border-color", + "default": "--tap-sys-color-border-negative" + }, + { + "description": "The background color of the container when disabled.", + "name": "--tap-input-disabled-container-background-color", + "default": "--tap-sys-color-surface-disabled" + }, + { + "description": "The color of the text and elements when disabled.", + "name": "--tap-input-disabled-container-color", + "default": "--tap-sys-color-content-disabled" + }, + { + "description": "The color of the input caret.", + "name": "--tap-input-caret-color", + "default": "--tap-sys-color-surface-accent" + } + ], + "cssParts": [ + { + "description": "The main container for the text field.", + "name": "field" + }, + { + "description": "The label for the text field.", + "name": "label" + }, + { + "description": "The container for the input and any leading/trailing elements.", + "name": "container" + }, + { + "description": "The input element.", + "name": "input" + }, + { + "description": "The caption for the text field.", + "name": "caption" + } + ], + "slots": [ + { + "description": "the leading slot of the text-field", + "name": "leading" + }, + { + "description": "the trailing slot of the text-field", + "name": "trailing" + } + ], + "members": [ + { + "type": { + "text": "string" + }, + "description": "The value of the text field.", + "name": "value", + "default": "''", + "kind": "field" + }, + { + "type": { + "text": "boolean" + }, + "description": "Indicates whether the text field is disabled.", + "name": "disabled", + "default": "false", + "kind": "field" + }, + { + "type": { + "text": "boolean" + }, + "description": "Indicates whether the text field has an error.", + "name": "error", + "default": "false", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "The caption text for the text field.", + "name": "caption", + "default": "''", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "The label for the text field.", + "name": "label", + "default": "''", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "The name of the text field.", + "name": "name", + "default": "''", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "The placeholder text for the text field.", + "name": "placeholder", + "default": "''", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "The input mode for the text field.", + "name": "inputmode", + "default": "''", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "The type of the text field.", + "name": "type", + "default": "''", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "The autocomplete attribute for the text field.", + "name": "autocomplete", + "default": "''", + "kind": "field" + } + ], + "superclass": { + "name": "TextField", + "module": "/src/text-field/text-field" + }, + "summary": "A text field component.", + "tagName": "tap-text-field", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapTextField", + "declaration": { + "name": "TapTextField", + "module": "src/text-field/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-text-field", + "declaration": { + "name": "TapTextField", + "module": "src/text-field/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/textarea/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapTextarea", + "cssProperties": [ + { + "description": "The font family used in the input.", + "name": "--tap-font-family", + "default": "--tap-sys-font-family" + }, + { + "description": "The gap between the elements in the input.", + "name": "--tap-input-field-gap", + "default": "--tap-sys-spacing-4" + }, + { + "description": "The color of the label.", + "name": "--tap-input-label-color", + "default": "--tap-sys-color-content-primary" + }, + { + "description": "The line height of the label.", + "name": "--tap-input-label-line-height", + "default": "--tap-sys-typography-label-sm-height" + }, + { + "description": "The font size of the label.", + "name": "--tap-input-label-font-size", + "default": "--tap-sys-typography-label-sm-size" + }, + { + "description": "The font weight of the label.", + "name": "--tap-input-label-font-weight", + "default": "--tap-sys-typography-label-sm-weight" + }, + { + "description": "The color of the caption.", + "name": "--tap-input-caption-color", + "default": "--tap-sys-color-content-tertiary" + }, + { + "description": "The line height of the caption.", + "name": "--tap-input-caption-line-height", + "default": "--tap-sys-typography-body-sm-height" + }, + { + "description": "The font size of the caption.", + "name": "--tap-input-caption-font-size", + "default": "--tap-sys-typography-body-sm-size" + }, + { + "description": "The font weight of the caption.", + "name": "--tap-input-caption-font-weight", + "default": "--tap-sys-typography-body-sm-weight" + }, + { + "description": "The height of the container.", + "name": "--tap-input-container-height", + "default": "--tap-sys-spacing-11" + }, + { + "description": "The padding inside the container.", + "name": "--tap-input-container-padding", + "default": "--tap-sys-spacing-6" + }, + { + "description": "The gap between elements in the container.", + "name": "--tap-input-container-gap", + "default": "--tap-sys-spacing-4" + }, + { + "description": "The background color of the container.", + "name": "--tap-input-container-background-color", + "default": "--tap-sys-color-surface-tertiary" + }, + { + "description": "The border radius of the container.", + "name": "--tap-input-container-border-radius", + "default": "--tap-sys-radius-3" + }, + { + "description": "The background color of the container when focused.", + "name": "--tap-input-focus-background-color", + "default": "--tap-sys-color-surface-secondary" + }, + { + "description": "The border width of the container when focused.", + "name": "--tap-input-focus-border-width", + "default": "--tap-sys-color-border-inverse-primary" + }, + { + "description": "The color of the input text.", + "name": "--tap-textarea-input-color", + "default": "--tap-sys-color-content-primary" + }, + { + "description": "The line height of the input text.", + "name": "--tap-textarea-input-line-height", + "default": "--tap-sys-typography-body-md-height" + }, + { + "description": "The font size of the input text.", + "name": "--tap-textarea-input-font-size", + "default": "--tap-sys-typography-body-md-size" + }, + { + "description": "The font weight of the input text.", + "name": "--tap-textarea-input-font-weight", + "default": "--tap-sys-typography-body-md-weight" + }, + { + "description": "The color of the placeholder text.", + "name": "--tap-textarea-input-placeholder-color", + "default": "--tap-sys-color-content-tertiary" + }, + { + "description": "The line height of the placeholder text.", + "name": "--tap-textarea-input-placeholder-line-height", + "default": "--tap-sys-typography-body-md-height" + }, + { + "description": "The font size of the placeholder text.", + "name": "--tap-textarea-input-placeholder-font-size", + "default": "--tap-sys-typography-body-md-size" + }, + { + "description": "The font weight of the placeholder text.", + "name": "--tap-textarea-input-placeholder-font-weight", + "default": "--tap-sys-typography-body-md-weight" + }, + { + "description": "The color of the caption when there is an error.", + "name": "--tap-input-error-caption-color", + "default": "--tap-sys-color-content-negative" + }, + { + "description": "The background color of the container when there is an error.", + "name": "--tap-input-error-container-background-color", + "default": "--tap-sys-color-surface-negative-light" + }, + { + "description": "The border color of the container when there is an error.", + "name": "--tap-input-error-container-border-color", + "default": "--tap-sys-color-border-negative" + }, + { + "description": "The background color of the container when disabled.", + "name": "--tap-input-disabled-container-background-color", + "default": "--tap-sys-color-surface-disabled" + }, + { + "description": "The color of the text and elements when disabled.", + "name": "--tap-input-disabled-container-color", + "default": "--tap-sys-color-content-disabled" + }, + { + "description": "The color of the input caret.", + "name": "--tap-input-caret-color", + "default": "--tap-sys-color-surface-accent" + } + ], + "cssParts": [ + { + "description": "The main container for the textarea.", + "name": "field" + }, + { + "description": "The label for the input.", + "name": "label" + }, + { + "description": "The container for the input.", + "name": "container" + }, + { + "description": "The input element.", + "name": "input" + }, + { + "description": "The caption for the textarea.", + "name": "caption" + } + ], + "members": [ + { + "type": { + "text": "string" + }, + "description": "The value of the textarea.", + "name": "value", + "default": "''", + "kind": "field" + }, + { + "type": { + "text": "boolean" + }, + "description": "Indicates whether the textarea is disabled.", + "name": "disabled", + "default": "false", + "kind": "field" + }, + { + "type": { + "text": "boolean" + }, + "description": "Indicates whether the textarea has an error.", + "name": "error", + "default": "false", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "The caption text for the textarea.", + "name": "caption", + "default": "''", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "The label for the textarea.", + "name": "label", + "default": "''", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "The name of the textarea.", + "name": "name", + "default": "''", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "The placeholder text for the textarea.", + "name": "placeholder", + "default": "''", + "kind": "field" + } + ], + "superclass": { + "name": "Textarea", + "module": "/src/textarea/textarea" + }, + "summary": "A textarea component.", + "tagName": "tap-textarea", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapTextarea", + "declaration": { + "name": "TapTextarea", + "module": "src/textarea/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-textarea", + "declaration": { + "name": "TapTextarea", + "module": "src/textarea/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/toast/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapToast", + "cssProperties": [ + { + "description": "The vertical padding inside the toast.", + "name": "--tap-toast-vertical-padding", + "default": "--tap-sys-spacing-5" + }, + { + "description": "The horizontal padding inside the toast.", + "name": "--tap-toast-horizontal-padding", + "default": "--tap-sys-spacing-5" + }, + { + "description": "The default background color of the toast.", + "name": "--tap-toast-background-color-default", + "default": "--tap-sys-color-surface-inverse-secondary" + }, + { + "description": "The default text color of the toast.", + "name": "--tap-toast-color-default", + "default": "--tap-sys-color-content-on-inverse" + }, + { + "description": "The background color of the success variant.", + "name": "--tap-toast-background-color-success", + "default": "--tap-sys-color-surface-positive" + }, + { + "description": "The text color of the success variant.", + "name": "--tap-toast-color-success", + "default": "--tap-sys-color-content-on-inverse" + }, + { + "description": "The background color of the error variant.", + "name": "--tap-toast-background-color-error", + "default": "--tap-sys-color-surface-negative" + }, + { + "description": "The text color of the error variant.", + "name": "--tap-toast-color-error", + "default": "--tap-sys-color-content-on-inverse" + }, + { + "description": "The background color of the info variant.", + "name": "--tap-toast-background-color-info", + "default": "--tap-sys-color-surface-accent" + }, + { + "description": "The text color of the info variant.", + "name": "--tap-toast-color-info", + "default": "--tap-sys-color-content-on-inverse" + }, + { + "description": "The background color of the warning variant.", + "name": "--tap-toast-background-color-warning", + "default": "--tap-sys-color-surface-warning" + }, + { + "description": "The text color of the warning variant.", + "name": "--tap-toast-color-warning", + "default": "--tap-sys-color-content-primary" + }, + { + "description": "The background color of the inverse variant.", + "name": "--tap-toast-background-color-inverse", + "default": "--tap-sys-color-surface-inverse-secondary" + }, + { + "description": "The text color of the inverse variant.", + "name": "--tap-toast-color-inverse", + "default": "--tap-sys-color-content-on-inverse" + }, + { + "description": "The color of the dismiss button.", + "name": "--tap-toast-dismiss-color", + "default": "'inherit'" + }, + { + "description": "The background color of the dismiss button.", + "name": "--tap-toast-dismiss-background-color", + "default": "transparent" + } + ], + "cssParts": [ + { + "description": "The main container for the toast notification.", + "name": "toast" + }, + { + "description": "The container for the icon.", + "name": "icon" + }, + { + "description": "The container for the message.", + "name": "message" + }, + { + "description": "The container for the dismiss button.", + "name": "dismiss" + } + ], + "members": [ + { + "type": { + "text": "boolean" + }, + "description": "Indicates whether to show the dismiss button.", + "name": "show-dismiss-button", + "default": "false", + "kind": "field" + }, + { + "type": { + "text": "'success' | 'error' | 'info' | 'warning' | 'inverse'" + }, + "description": "The variant of the toast notification. Defaults to `inverse`.", + "name": "variant", + "default": "'inverse'", + "kind": "field" + } + ], + "events": [ + { + "description": "Fires when the toast dismiss button is clicked", + "name": "dismiss" + } + ], + "superclass": { + "name": "Toast", + "module": "/src/toast/toast" + }, + "summary": "A toast notification component.", + "tagName": "tap-toast", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapToast", + "declaration": { + "name": "TapToast", + "module": "src/toast/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-toast", + "declaration": { + "name": "TapToast", + "module": "src/toast/index.ts" + } + } + ] + }, + { + "kind": "javascript-module", + "path": "src/tooltip/index.ts", + "declarations": [ + { + "kind": "class", + "description": "", + "name": "TapTooltip", + "slots": [ + { + "type": { + "text": "label" + }, + "description": "the label of the tooltip", + "name": "" + }, + { + "type": { + "text": "target-element" + }, + "description": "the tooltip's target element", + "name": "" + } + ], + "members": [ + { + "type": { + "text": "'top'| 'top-start'| 'top-end'| 'right'| 'right-start'| 'right-end'| 'bottom'| 'bottom-start'| 'bottom-end'| 'left'| 'left-start'| 'left-end'" + }, + "description": "the position of the tooltip", + "name": "placement", + "default": "'top'", + "kind": "field" + }, + { + "type": { + "text": "boolean" + }, + "description": "is the tooltip dismissible?", + "name": "dismissible", + "default": "true", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "tooltip's width", + "name": "width", + "default": "'0'", + "kind": "field" + }, + { + "type": { + "text": "string" + }, + "description": "tooltip's arrow offset", + "name": "arrow-offset", + "default": "''", + "kind": "field" + } + ], + "events": [ + { + "description": "Fires when the toast dismiss button is clicked", + "name": "dismiss" + } + ], + "superclass": { + "name": "Tooltip", + "module": "/src/tooltip/tooltip" + }, + "summary": "A tooltip component for showing more info", + "tagName": "tap-tooltip", + "customElement": true + } + ], + "exports": [ + { + "kind": "js", + "name": "TapTooltip", + "declaration": { + "name": "TapTooltip", + "module": "src/tooltip/index.ts" + } + }, + { + "kind": "custom-element-definition", + "name": "tap-tooltip", + "declaration": { + "name": "TapTooltip", + "module": "src/tooltip/index.ts" + } + } + ] + } + ] +} diff --git a/src/pin-input/pin-input.stories.ts b/src/pin-input/pin-input.stories.ts index 7cdb834c..f23bba99 100644 --- a/src/pin-input/pin-input.stories.ts +++ b/src/pin-input/pin-input.stories.ts @@ -27,3 +27,24 @@ const Template: Story = (/*{}: ArgTypes*/) => html` export const PinInput = Template.bind({}); PinInput.args = {}; +const TemplateWithForm: Story = (/*{}: ArgTypes*/) => html` +
{ + console.log({ e, b }); + e.preventDefault(); + }} + @formdata=${(e, b) => { + console.log({ e, b }); + e.preventDefault(); + }} + > + console.log(e)} + > + +
+`; + +export const PinInputWithForm = TemplateWithForm.bind({}); diff --git a/src/pin-input/pin-input.ts b/src/pin-input/pin-input.ts index 2471e63a..0da43fe4 100644 --- a/src/pin-input/pin-input.ts +++ b/src/pin-input/pin-input.ts @@ -14,6 +14,14 @@ import { import { PinInputFilled } from './events'; export class PinInput extends LitElement { + private internals_: ElementInternals; + static formAssociated = true; + + constructor() { + super(); + this.internals_ = this.attachInternals(); + } + @queryAll('.pin-input-cell') _cells!: PinInputCell[]; @property({ type: Boolean, reflect: true }) disabled: boolean = false; @@ -21,16 +29,16 @@ export class PinInput extends LitElement { @property({ type: Boolean, reflect: true, attribute: 'has-error' }) hasError = false; - @property({ type: Boolean, attribute: 'auto-focus-first-cell' }) - autoFocusFirstCell: boolean = true; + @property({ type: Boolean, attribute: 'auto-focus' }) + autoFocus: boolean = true; - @property({ reflect: true, type: Number }) value = null; + @property({ reflect: true, type: String }) _value = ''; @property() label = ''; - @property({ reflect: true }) title = ''; + @property() title = ''; - @property({ reflect: true }) description = ''; + @property() description = ''; @property({ reflect: true, type: Number }) count = 5; @@ -41,7 +49,7 @@ export class PinInput extends LitElement { } isFirstCellShouldAutoFocus(index: number) { - return this.autoFocusFirstCell && index === 0; + return this.autoFocus && index === 0; } private handleCellFilled(event: PinInputCellFilled) { @@ -118,21 +126,37 @@ export class PinInput extends LitElement { } private emitPinInputFilled() { + const value = this.inputValue!; const event = new PinInputFilled('PinInput Filled.', { - value: this.inputValue!, + value: value, cellCount: this.count, - displayValue: this.inputValue!, + displayValue: value, }); this.dispatchEvent(event); } + private handleFormValidity(value: string) { + if (typeof value === 'string' && value.length < this.count) { + this.internals_.setValidity( + { customError: true }, + 'Value is less than required', + ); + } else { + this.internals_.setValidity({}); + } + } + get cellValues() { return [...this._cells].map((_cell) => _cell.value); } get inputValue() { const result = this.cellValues.join(''); + this.internals_.setFormValue(result); + this._value = result; + this.handleFormValidity(result); + if (result.length === this.count) { return result; } @@ -140,6 +164,53 @@ export class PinInput extends LitElement { return null; } + set value(value: string) { + if (value) { + void this.fillCells(value); + } + } + + async formResetCallback() { + await this.fillCells(''); + void this.emitPinInputFilled(); + } + + formStateRestoreCallback(state: string) { + this.internals_.setFormValue(state); + } + + get value() { + return this._value; + } + + get form() { + return this.internals_.form; + } + + get type() { + return 'pin-input'; + } + + get validity() { + return this.internals_.validity; + } + + get validationMessage() { + return this.internals_.validationMessage; + } + + get willValidate() { + return this.internals_.willValidate; + } + + checkValidity() { + return this.internals_.checkValidity(); + } + + reportValidity() { + return this.internals_.reportValidity(); + } + private focusNextElementByIndex(current: number) { const nextIndex = current + 1; if (this.checkIndexIsInRange(current) && !this.checkIndexIsLast(current)) { From ed2bc2d7ef42ccb531ed4471933568e0b3e03c88 Mon Sep 17 00:00:00 2001 From: Hossein Nasiri Date: Mon, 14 Oct 2024 19:45:45 +0330 Subject: [PATCH 8/9] feat: add jsdoc --- docs/dev/components/[component].md | 2 ++ docs/dev/components/elements-config.json | 8 ++++++ src/pin-input-cell/index.ts | 2 +- src/pin-input-cell/pin-input-cell.ts | 14 ++++++----- src/pin-input/index.ts | 22 +++++++++++++++-- src/pin-input/pin-input.style.ts | 2 +- src/pin-input/pin-input.ts | 31 ++++++++++++------------ 7 files changed, 56 insertions(+), 25 deletions(-) diff --git a/docs/dev/components/[component].md b/docs/dev/components/[component].md index 75c93e38..897ff8b0 100644 --- a/docs/dev/components/[component].md +++ b/docs/dev/components/[component].md @@ -44,6 +44,8 @@ import '../../../src/toast'; import '../../../src/tooltip'; import '../../../src/empty-state'; import '../../../src/button'; +import '../../../src/pin-input'; +import '../../../src/pin-input-cell'; import '../../../styles/theme.css'; import '@tapsioss/icons/dist/icons'; import "cemnama" diff --git a/docs/dev/components/elements-config.json b/docs/dev/components/elements-config.json index 11ed36dd..7689206f 100644 --- a/docs/dev/components/elements-config.json +++ b/docs/dev/components/elements-config.json @@ -34,6 +34,14 @@ }, "slotKnobs": {} }, + "tap-pin-input": { + "propertyDefaultValues": { + "title": "عنوان", + "description": "توضیحات", + "count": 5 + }, + "slotKnobs": {} + }, "tap-toast": { "propertyDefaultValues": { "show-dismiss-button": false, diff --git a/src/pin-input-cell/index.ts b/src/pin-input-cell/index.ts index c7556922..56533a0e 100644 --- a/src/pin-input-cell/index.ts +++ b/src/pin-input-cell/index.ts @@ -6,7 +6,7 @@ export * from './constants.js'; @customElement('tap-pin-input-cell') export class TapPinInputCell extends PinInputCell { - static readonly styles = [styles]; + static override readonly styles = [styles]; } declare global { diff --git a/src/pin-input-cell/pin-input-cell.ts b/src/pin-input-cell/pin-input-cell.ts index 93307077..8abcd46d 100644 --- a/src/pin-input-cell/pin-input-cell.ts +++ b/src/pin-input-cell/pin-input-cell.ts @@ -35,7 +35,7 @@ export class PinInputCell extends LitElement { @property({ type: Number }) index: number = null!; - protected updated(changed: PropertyValues) { + protected override updated(changed: PropertyValues) { if (changed.has('value') && !Number.isNaN(this.value)) { // } @@ -124,13 +124,15 @@ export class PinInputCell extends LitElement { if (typeof inputValue === 'string' && inputValue.length >= 1) { const lastCharacterIndex = inputValue.length - 1; + if( inputValue[lastCharacterIndex]){ await this.updateInputValue(inputValue[lastCharacterIndex]); + } } else { await this.updateInputValue(''); } } - protected firstUpdated(_changedProperties: PropertyValues) { + protected override firstUpdated(_changedProperties: PropertyValues) { super.firstUpdated(_changedProperties); if (this.autoFocus) { @@ -193,7 +195,7 @@ export class PinInputCell extends LitElement { e.preventDefault(); } - if (text?.length > 1) { + if (text?.length > 1 && text[0]) { await this.updateInputValue(text[0]); await this.emitOverflowedValue(text.slice(1)); e.preventDefault(); @@ -245,17 +247,17 @@ export class PinInputCell extends LitElement { await this.updateComplete; } - render() { + override render() { return html` this.handleInput(e)} @paste=${(e: ClipboardEvent) => this.handlePaste(e)} @focus=${(e: FocusEvent) => this.handleFocus(e)} @keydown=${(e: KeyboardEvent) => this.validatePressedKey(e)} - class="cell ${classMap({ + part="pin-input-cell" + class="pin-input-cell cell ${classMap({ 'cell-sm': this.size === 'small', 'cell-md': this.size === 'medium', 'cell-lg': this.size === 'large', diff --git a/src/pin-input/index.ts b/src/pin-input/index.ts index d82b9389..07fcdd50 100644 --- a/src/pin-input/index.ts +++ b/src/pin-input/index.ts @@ -1,12 +1,30 @@ import { customElement } from 'lit/decorators.js'; import { PinInput } from './pin-input.js'; import styles from './pin-input.style.js'; -export * from './events.js'; export * from './constants.js'; +export * from './events.js'; +/** + * @summary A flexible Pin Input component used for entering multi-digit codes. + * + * @prop {boolean} [disabled=false] - When true, disables the component, rendering all elements (title, description, and input cells) in light gray and preventing user interaction. + * @prop {boolean} [has-error=false] - When true, adds a red border around the input cells and changes the description text to red, indicating an error. + * @prop {boolean} [auto-focus=false] - Automatically focuses on the first input cell when the component is loaded. + * @prop {string} [label=""] - A label that provides additional context, typically used for accessibility. + * @prop {string} [title=""] - Optional text displayed above the input cells in solid black to describe the input. + * @prop {string} [description=""] - Optional helper text displayed below the input cells in gray, turning red if `has-error` is true. + * @prop {number} [count=5] - Specifies the number of input cells for the pin code. + * @prop {'small' | 'medium' | 'large'} [size='medium'] - Defines the size of the input cells, adjusting the overall look and feel of the component. + * + * @csspart [pin-input] - The container element that wraps the entire pin input component. + * @csspart [pin-input-cell] - Each individual input cell where users enter digits. + * @csspart [title] - The element responsible for rendering the title above the input cells. + * @csspart [description] - The element that renders the description below the input cells. + * @csspart [input-cells] - The container that holds all the input cells together. + */ @customElement('tap-pin-input') export class TapPinInput extends PinInput { - static readonly styles = [styles]; + static override readonly styles = [styles]; } declare global { diff --git a/src/pin-input/pin-input.style.ts b/src/pin-input/pin-input.style.ts index 622d7f0c..4e32c1cb 100644 --- a/src/pin-input/pin-input.style.ts +++ b/src/pin-input/pin-input.style.ts @@ -85,7 +85,7 @@ export default css` .pin-input { } - .pin-input-wrapper { + .pin-input { display: flex; row-gap: var(--tap-input-input-vertical-gap); flex-direction: column; diff --git a/src/pin-input/pin-input.ts b/src/pin-input/pin-input.ts index 0da43fe4..50311e4d 100644 --- a/src/pin-input/pin-input.ts +++ b/src/pin-input/pin-input.ts @@ -1,9 +1,8 @@ import { html, LitElement, nothing } from 'lit'; -import { property, queryAll } from 'lit/decorators.js'; +import { property, queryAll, state } from 'lit/decorators.js'; import { range } from 'lit/directives/range.js'; import { repeat } from 'lit/directives/repeat.js'; import '../pin-input-cell'; -import { PinInputCell } from '../pin-input-cell/pin-input-cell'; import { PinInputCellArrowKeyPressed, PinInputCellCleared, @@ -11,6 +10,7 @@ import { PinInputCellFilled, PinInputCellOverflowValue, } from '../pin-input-cell/events'; +import { PinInputCell } from '../pin-input-cell/pin-input-cell'; import { PinInputFilled } from './events'; export class PinInput extends LitElement { @@ -32,11 +32,11 @@ export class PinInput extends LitElement { @property({ type: Boolean, attribute: 'auto-focus' }) autoFocus: boolean = true; - @property({ reflect: true, type: String }) _value = ''; + @state() private _value = ''; @property() label = ''; - @property() title = ''; + @property() override title = ''; @property() description = ''; @@ -44,7 +44,7 @@ export class PinInput extends LitElement { @property() size: 'small' | 'medium' | 'large' = 'medium'; - connectedCallback(): void { + override connectedCallback(): void { super.connectedCallback(); } @@ -89,7 +89,7 @@ export class PinInput extends LitElement { if (isNotFirstItem) { await this.clearCellsUntil(currentIndex); - this._cells?.[0].focus(); + this._cells?.[0]?.focus(); } } @@ -119,7 +119,7 @@ export class PinInput extends LitElement { let index = 0; for (const char of value.split('')) { const pos = index + startingAt; - await this._cells[pos].setValue(char); + await this._cells[pos]?.setValue(char); index++; } } @@ -214,20 +214,20 @@ export class PinInput extends LitElement { private focusNextElementByIndex(current: number) { const nextIndex = current + 1; if (this.checkIndexIsInRange(current) && !this.checkIndexIsLast(current)) { - this._cells[nextIndex].focus(); + this._cells[nextIndex]?.focus(); } } private async clearCellsUntil(index: number) { for (let i = 0; i <= index; i++) { - await this._cells?.[i].clearValue(); + await this._cells?.[i]?.clearValue(); } } private focusPrevElementByIndex(current: number) { const nextIndex = current - 1; if (this.checkIndexIsInRange(current) && !this.checkIndexIsFirst(current)) { - this._cells[nextIndex].focus(); + this._cells[nextIndex]?.focus(); } } @@ -249,7 +249,7 @@ export class PinInput extends LitElement { private renderTitle() { if (typeof this.title === 'string' && this.title.length) { - return html`
${this.title}
`; + return html`
${this.title}
`; } return nothing; } @@ -257,13 +257,14 @@ export class PinInput extends LitElement { private renderInputCells() { if (typeof this.title === 'string' && this.title.length) { return html` -
+
${repeat( range(this.count), (count) => count, (_, index) => { return html` ${this.description}
`; + return html`
${this.description}
`; } return nothing; } - render() { + override render() { return html` -
+
${this.renderTitle()} ${this.renderInputCells()} ${this.renderDescription()}
From f60343364ea8ec8118a0f68cb6c11cada816c5b0 Mon Sep 17 00:00:00 2001 From: Hossein Nasiri Date: Wed, 30 Oct 2024 19:31:30 +0330 Subject: [PATCH 9/9] fix: resolve issues in MR --- src/pin-input-cell/events.ts | 103 +++----------- src/pin-input-cell/pin-input-cell.ts | 23 ++- src/pin-input/constants.ts | 2 +- src/pin-input/events.ts | 25 +--- src/pin-input/pin-input.stories.ts | 4 +- src/pin-input/pin-input.ts | 203 ++++++++++++++------------- 6 files changed, 144 insertions(+), 216 deletions(-) diff --git a/src/pin-input-cell/events.ts b/src/pin-input-cell/events.ts index 63144258..9f6212ba 100644 --- a/src/pin-input-cell/events.ts +++ b/src/pin-input-cell/events.ts @@ -1,3 +1,4 @@ +import { BaseEvent } from '../utils'; import { PIN_INPUT_CELL_ARROW_KEY_PRESSED_TYPE, PIN_INPUT_CELL_CLEARED_ALL_TYPE, @@ -7,116 +8,46 @@ import { } from './constants'; import { ValueChangedEventParams } from './types'; -export class PinInputCellFilled extends Event { - public message: string; - public details: ValueChangedEventParams; - - constructor( - message: string, - details: ValueChangedEventParams, - eventInitDict: EventInit = {}, - ) { - const _eventInitDict = { - bubbles: true, - composed: false, - ...eventInitDict, - }; - const type = PIN_INPUT_CELL_FILLED_TYPE; - super(type, _eventInitDict); - - this.details = details; - this.message = message; +export class CellFilled extends BaseEvent { + static type = PIN_INPUT_CELL_FILLED_TYPE; + constructor(details: ValueChangedEventParams){ + super(CellFilled.type, {details}) } } -export class PinInputCellCleared extends Event { - public message: string; - public details: ValueChangedEventParams; - +export class CellCleared extends BaseEvent { + static type = PIN_INPUT_CELL_CLEARED_TYPE; constructor( - message: string, details: ValueChangedEventParams, - eventInitDict: EventInit = {}, ) { - const _eventInitDict = { - bubbles: true, - composed: false, - ...eventInitDict, - }; - - const type = PIN_INPUT_CELL_CLEARED_TYPE; - super(type, _eventInitDict); - - this.details = details; - this.message = message; + super(CellCleared.type, {details}); } } -export class PinInputCellClearedAll extends Event { - public message: string; - public details: ValueChangedEventParams; - +export class CellClearedAll extends BaseEvent { + static type = PIN_INPUT_CELL_CLEARED_ALL_TYPE; constructor( - message: string, details: ValueChangedEventParams, - eventInitDict: EventInit = {}, ) { - const _eventInitDict = { - bubbles: true, - composed: false, - ...eventInitDict, - }; - - const type = PIN_INPUT_CELL_CLEARED_ALL_TYPE; - super(type, _eventInitDict); - this.details = details; - this.message = message; + super(CellClearedAll.type, {details}); } } -export class PinInputCellArrowKeyPressed extends Event { - public message: string; - public details: ValueChangedEventParams; - +export class CellArrowKeyPressed extends BaseEvent> { + static type = PIN_INPUT_CELL_ARROW_KEY_PRESSED_TYPE; constructor( - message: string, details: ValueChangedEventParams, - eventInitDict: EventInit = {}, ) { - const _eventInitDict = { - bubbles: true, - composed: false, - ...eventInitDict, - }; - - const type = PIN_INPUT_CELL_ARROW_KEY_PRESSED_TYPE; - super(type, _eventInitDict); - - this.details = details; - this.message = message; + super(CellArrowKeyPressed.type, {details}); } } -export class PinInputCellOverflowValue extends Event { - public message: string; - public details: ValueChangedEventParams; - +export class CellOverflowValue extends BaseEvent { + static type = PIN_INPUT_CELL_OVERFLOW_VALUE_TYPE; constructor( - message: string, details: ValueChangedEventParams, - eventInitDict: EventInit = {}, ) { - const _eventInitDict = { - bubbles: true, - composed: false, - ...eventInitDict, - }; - - const type = PIN_INPUT_CELL_OVERFLOW_VALUE_TYPE; - super(type, _eventInitDict); - - this.details = details; - this.message = message; + super(CellOverflowValue.type, {details}); } } diff --git a/src/pin-input-cell/pin-input-cell.ts b/src/pin-input-cell/pin-input-cell.ts index 8abcd46d..4e4c9dd5 100644 --- a/src/pin-input-cell/pin-input-cell.ts +++ b/src/pin-input-cell/pin-input-cell.ts @@ -9,11 +9,11 @@ import { persianToEnglish, } from './util'; import { - PinInputCellArrowKeyPressed, - PinInputCellCleared, - PinInputCellClearedAll, - PinInputCellFilled, - PinInputCellOverflowValue, + CellArrowKeyPressed, + CellCleared, + CellClearedAll, + CellFilled, + CellOverflowValue, } from './events'; export class PinInputCell extends LitElement { @@ -56,7 +56,7 @@ export class PinInputCell extends LitElement { private async emitValueChanged() { await this.updateComplete; - const event = new PinInputCellFilled('PinInputCell filled', { + const event = new CellFilled({ cell: this, index: this.index, value: this.value, @@ -67,7 +67,7 @@ export class PinInputCell extends LitElement { private async emitValueCleared() { await this.updateComplete; - const event = new PinInputCellCleared('PinInputCell cleared', { + const event = new CellCleared({ cell: this, index: this.index, value: this.value, @@ -78,7 +78,7 @@ export class PinInputCell extends LitElement { private async emitDeletionWithMetaKeys() { await this.updateComplete; - const event = new PinInputCellClearedAll('PinInputCell cleared all', { + const event = new CellClearedAll({ cell: this, index: this.index, value: this.value, @@ -90,8 +90,7 @@ export class PinInputCell extends LitElement { private async emitArrowKeyPressed(key: 'ArrowLeft' | 'ArrowRight') { await this.updateComplete; - const event = new PinInputCellArrowKeyPressed<'left' | 'right'>( - 'PinInputCell arrow key pressed', + const event = new CellArrowKeyPressed( { cell: this, index: this.index, @@ -104,9 +103,7 @@ export class PinInputCell extends LitElement { private async emitOverflowedValue(value: string) { await this.updateComplete; - const event = new PinInputCellOverflowValue( - 'PinInputCell Overflowed value', - { + const event = new CellOverflowValue({ cell: this, index: this.index, value: value, diff --git a/src/pin-input/constants.ts b/src/pin-input/constants.ts index 6a3fd641..f782613c 100644 --- a/src/pin-input/constants.ts +++ b/src/pin-input/constants.ts @@ -1 +1 @@ -export const PIN_INPUT_FILLED_TYPE = 'input-filled'; +export const PIN_INPUT_FILLED_TYPE = 'filled'; diff --git a/src/pin-input/events.ts b/src/pin-input/events.ts index 9c608c45..07bc81b3 100644 --- a/src/pin-input/events.ts +++ b/src/pin-input/events.ts @@ -1,25 +1,10 @@ +import { BaseEvent } from '../utils'; import { PIN_INPUT_FILLED_TYPE } from './constants'; import { InputFilledEventParams } from './types'; -export class PinInputFilled extends Event { - public message: string; - public details: InputFilledEventParams; - - constructor( - message: string, - details: InputFilledEventParams, - eventInitDict: EventInit = {}, - ) { - const _eventInitDict = { - bubbles: true, - composed: false, - ...eventInitDict, - }; - - const type = PIN_INPUT_FILLED_TYPE; - super(type, _eventInitDict); - - this.details = details; - this.message = message; +export class Filled extends BaseEvent { + static type = PIN_INPUT_FILLED_TYPE; + constructor(details: InputFilledEventParams){ + super(Filled.type, {details}) } } diff --git a/src/pin-input/pin-input.stories.ts b/src/pin-input/pin-input.stories.ts index f23bba99..f494faed 100644 --- a/src/pin-input/pin-input.stories.ts +++ b/src/pin-input/pin-input.stories.ts @@ -20,7 +20,7 @@ const Template: Story = (/*{}: ArgTypes*/) => html` console.log(e)} + @filled=${(e: Error) => console.log(e)} > `; @@ -41,7 +41,7 @@ const TemplateWithForm: Story = (/*{}: ArgTypes*/) => html` console.log(e)} + @filled=${(e: Error) => console.log(e)} > diff --git a/src/pin-input/pin-input.ts b/src/pin-input/pin-input.ts index 50311e4d..d9dad178 100644 --- a/src/pin-input/pin-input.ts +++ b/src/pin-input/pin-input.ts @@ -1,48 +1,40 @@ -import { html, LitElement, nothing } from 'lit'; -import { property, queryAll, state } from 'lit/decorators.js'; -import { range } from 'lit/directives/range.js'; -import { repeat } from 'lit/directives/repeat.js'; -import '../pin-input-cell'; +import { html, LitElement, nothing } from "lit"; +import { property, queryAll } from "lit/decorators.js"; +import { range } from "lit/directives/range.js"; +import { repeat } from "lit/directives/repeat.js"; +import "../pin-input-cell"; import { - PinInputCellArrowKeyPressed, - PinInputCellCleared, - PinInputCellClearedAll, - PinInputCellFilled, - PinInputCellOverflowValue, -} from '../pin-input-cell/events'; -import { PinInputCell } from '../pin-input-cell/pin-input-cell'; -import { PinInputFilled } from './events'; - -export class PinInput extends LitElement { - private internals_: ElementInternals; - static formAssociated = true; - - constructor() { - super(); - this.internals_ = this.attachInternals(); - } - - @queryAll('.pin-input-cell') _cells!: PinInputCell[]; - - @property({ type: Boolean, reflect: true }) disabled: boolean = false; - - @property({ type: Boolean, reflect: true, attribute: 'has-error' }) + type CellArrowKeyPressed, + type CellCleared, + type CellClearedAll, + type CellFilled, + type CellOverflowValue, +} from "../pin-input-cell/events"; +import { type PinInputCell } from "../pin-input-cell/pin-input-cell"; +import { withElementInternals, withFormAssociated } from "../utils/index.js"; +import { internals } from "../utils/mixins/with-element-internals"; +import { getFormValue } from "../utils/mixins/with-form-associated"; +import { Filled } from "./events"; + +const BaseClass = withFormAssociated(withElementInternals(LitElement)); + +export class PinInput extends BaseClass { + @queryAll(".pin-input-cell") _cells!: PinInputCell[]; + + @property({ type: Boolean, reflect: true }) override disabled: boolean = + false; + + @property({ type: Boolean, reflect: true, attribute: "has-error" }) hasError = false; - @property({ type: Boolean, attribute: 'auto-focus' }) + @property({ type: Boolean, attribute: "auto-focus" }) autoFocus: boolean = true; - @state() private _value = ''; - - @property() label = ''; - - @property() override title = ''; - - @property() description = ''; + @property() description = ""; @property({ reflect: true, type: Number }) count = 5; - @property() size: 'small' | 'medium' | 'large' = 'medium'; + @property() size: "small" | "medium" | "large" = "medium"; override connectedCallback(): void { super.connectedCallback(); @@ -52,22 +44,23 @@ export class PinInput extends LitElement { return this.autoFocus && index === 0; } - private handleCellFilled(event: PinInputCellFilled) { + private handleCellFilled(event: CellFilled) { this.focusNextElementByIndex(event.details.index); this.handleCellsFilled(); } - private handleCellCleared(event: PinInputCellCleared) { + private handleCellCleared(event: CellCleared) { this.focusPrevElementByIndex(event.details.index); } private handleCellsFilled() { - if (this.inputValue) { + if (this.value) { + this.handleFormValidity(this.value); this.emitPinInputFilled(); } } - private async handleOverflowedCell(event: PinInputCellOverflowValue) { + private async handleOverflowedCell(event: CellOverflowValue) { let overflowedText = event.details.value; const cellIndex = event.details.index; const isLongerThanRemainingCells = @@ -80,7 +73,7 @@ export class PinInput extends LitElement { await this.fillCells(overflowedText, cellIndex + 1); } - private async handleClearPrevCells(event: PinInputCellClearedAll) { + private async handleClearPrevCells(event: CellClearedAll) { await this.updateComplete; const currentIndex = event.details.index; @@ -93,17 +86,17 @@ export class PinInput extends LitElement { } } - private async handleArrowKeyPressed(event: PinInputCellArrowKeyPressed) { + private async handleArrowKeyPressed(event: CellArrowKeyPressed) { await this.updateComplete; const currentIndex = event.details.index; const shouldPrevItemFocus = - event.details.value === 'left' && + event.details.value === "left" && this.checkIndexIsInRange(currentIndex) && !this.checkIndexIsFirst(currentIndex); const shouldNextItemFocus = - event.details.value === 'right' && + event.details.value === "right" && this.checkIndexIsInRange(currentIndex) && !this.checkIndexIsLast(currentIndex); @@ -117,8 +110,10 @@ export class PinInput extends LitElement { private async fillCells(value: string, startingAt: number = 0) { if (startingAt <= this.lastCellIndex) { let index = 0; - for (const char of value.split('')) { + + for (const char of value.split("")) { const pos = index + startingAt; + await this._cells[pos]?.setValue(char); index++; } @@ -126,9 +121,9 @@ export class PinInput extends LitElement { } private emitPinInputFilled() { - const value = this.inputValue!; - const event = new PinInputFilled('PinInput Filled.', { - value: value, + const value = this.value; + const event = new Filled({ + value, cellCount: this.count, displayValue: value, }); @@ -137,31 +132,34 @@ export class PinInput extends LitElement { } private handleFormValidity(value: string) { - if (typeof value === 'string' && value.length < this.count) { - this.internals_.setValidity( + if (typeof value === "string" && value.length < this.count) { + this[internals].setValidity( { customError: true }, - 'Value is less than required', + "Value is less than required", ); } else { - this.internals_.setValidity({}); + this[internals].setValidity({}); } } get cellValues() { - return [...this._cells].map((_cell) => _cell.value); + return [...this._cells].map(_cell => _cell.value); } - get inputValue() { - const result = this.cellValues.join(''); - this.internals_.setFormValue(result); - this._value = result; - this.handleFormValidity(result); + get value() { + const result = this.cellValues.join(""); + + this[internals].setFormValue(result); if (result.length === this.count) { return result; } - return null; + return ""; + } + + override [getFormValue]() { + return this.value; } set value(value: string) { @@ -170,49 +168,44 @@ export class PinInput extends LitElement { } } - async formResetCallback() { - await this.fillCells(''); - void this.emitPinInputFilled(); - } - - formStateRestoreCallback(state: string) { - this.internals_.setFormValue(state); - } - - get value() { - return this._value; + override formResetCallback() { + void (async () => { + await this.fillCells(""); + this.emitPinInputFilled(); + })(); } - get form() { - return this.internals_.form; + override formStateRestoreCallback(state: string) { + this[internals].setFormValue(state); } get type() { - return 'pin-input'; + return "pin-input"; } get validity() { - return this.internals_.validity; + return this[internals].validity; } get validationMessage() { - return this.internals_.validationMessage; + return this[internals].validationMessage; } get willValidate() { - return this.internals_.willValidate; + return this[internals].willValidate; } checkValidity() { - return this.internals_.checkValidity(); + return this[internals].checkValidity(); } reportValidity() { - return this.internals_.reportValidity(); + return this[internals].reportValidity(); } private focusNextElementByIndex(current: number) { const nextIndex = current + 1; + if (this.checkIndexIsInRange(current) && !this.checkIndexIsLast(current)) { this._cells[nextIndex]?.focus(); } @@ -226,6 +219,7 @@ export class PinInput extends LitElement { private focusPrevElementByIndex(current: number) { const nextIndex = current - 1; + if (this.checkIndexIsInRange(current) && !this.checkIndexIsFirst(current)) { this._cells[nextIndex]?.focus(); } @@ -248,19 +242,30 @@ export class PinInput extends LitElement { } private renderTitle() { - if (typeof this.title === 'string' && this.title.length) { - return html`
${this.title}
`; + if (typeof this.title === "string" && this.title.length) { + return html` +
+ ${this.title} +
+ `; } + return nothing; } private renderInputCells() { - if (typeof this.title === 'string' && this.title.length) { + if (typeof this.title === "string" && this.title.length) { return html` -
+
${repeat( range(this.count), - (count) => count, + count => count, (_, index) => { return html` - this.handleCellFilled(e)} - @cell-cleared=${(e: PinInputCellCleared) => - this.handleCellCleared(e)} - @overflow-value=${(e: PinInputCellOverflowValue) => + @cell-filled=${(e: CellFilled) => this.handleCellFilled(e)} + @cell-cleared=${(e: CellCleared) => this.handleCellCleared(e)} + @overflow-value=${(e: CellOverflowValue) => this.handleOverflowedCell(e)} - @cell-cleared-all-with-meta-key=${(e: PinInputCellClearedAll) => + @cell-cleared-all-with-meta-key=${(e: CellClearedAll) => this.handleClearPrevCells(e)} - @arrow-key-pressed=${(e: PinInputCellArrowKeyPressed) => + @arrow-key-pressed=${(e: CellArrowKeyPressed) => this.handleArrowKeyPressed(e)} ?auto-focus=${this.isFirstCellShouldAutoFocus(index)} .size=${this.size} @@ -286,19 +289,31 @@ export class PinInput extends LitElement {
`; } + return nothing; } private renderDescription() { - if (typeof this.title === 'string' && this.title.length) { - return html`
${this.description}
`; + if (typeof this.title === "string" && this.title.length) { + return html` +
+ ${this.description} +
+ `; } + return nothing; } override render() { return html` -
+
${this.renderTitle()} ${this.renderInputCells()} ${this.renderDescription()}