Files
dees-catalog-mobile/ts_web/elements/00group-input/dees-mobile-input/dees-mobile-input.ts

229 lines
5.3 KiB
TypeScript
Raw Normal View History

2025-12-22 10:53:15 +00:00
import {
DeesElement,
css,
cssManager,
customElement,
html,
property,
type TemplateResult,
} from '@design.estate/dees-element';
import { mobileComponentStyles } from '../../00componentstyles.js';
import { demoFunc } from './dees-mobile-input.demo.js';
export type InputType = 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' | 'search';
declare global {
interface HTMLElementTagNameMap {
'dees-mobile-input': DeesMobileInput;
}
}
@customElement('dees-mobile-input')
export class DeesMobileInput extends DeesElement {
public static demo = demoFunc;
@property({ type: String })
accessor type: InputType = 'text';
@property({ type: String })
accessor placeholder: string = '';
@property({ type: String })
accessor value: string = '';
@property({ type: Boolean })
accessor disabled: boolean = false;
@property({ type: String })
accessor id: string = '';
@property({ type: String })
accessor name: string = '';
@property({ type: String })
accessor label: string = '';
@property({ type: String })
accessor error: string = '';
@property({ type: Boolean })
accessor required: boolean = false;
@property({ type: String })
accessor autocomplete: string = '';
public static styles = [
cssManager.defaultStyles,
mobileComponentStyles,
css`
:host {
display: block;
}
.input-wrapper {
display: flex;
flex-direction: column;
gap: 0.375rem;
}
label {
font-size: 0.875rem;
font-weight: 500;
color: var(--dees-foreground);
}
label .required {
color: var(--dees-danger);
margin-left: 0.25rem;
}
input {
width: 100%;
height: 2.5rem;
padding: 0 0.75rem;
/* 16px minimum to prevent iOS zoom */
font-size: 1rem;
line-height: 1.25rem;
color: var(--dees-foreground);
background: var(--dees-background);
border: 1px solid var(--dees-input);
border-radius: calc(var(--dees-radius) - 2px);
outline: none;
transition: all var(--dees-transition-fast);
box-sizing: border-box;
-webkit-appearance: none;
font-family: inherit;
}
input:focus {
outline: 2px solid transparent;
outline-offset: 2px;
border-color: var(--dees-ring);
box-shadow: 0 0 0 2px var(--dees-background), 0 0 0 4px var(--dees-ring);
}
input:disabled {
opacity: 0.5;
cursor: not-allowed;
background: var(--dees-muted);
}
input::placeholder {
color: var(--dees-muted-foreground);
}
/* Remove number input spinners */
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type=number] {
-moz-appearance: textfield;
}
/* Error state */
:host([error]) input,
input.error {
border-color: var(--dees-danger);
}
:host([error]) input:focus,
input.error:focus {
box-shadow: 0 0 0 2px var(--dees-background), 0 0 0 4px var(--dees-danger);
}
.error-message {
font-size: 0.75rem;
color: var(--dees-danger);
margin-top: 0.25rem;
}
`,
];
private handleInput(e: Event) {
const input = e.target as HTMLInputElement;
this.value = input.value;
this.dispatchEvent(new CustomEvent('input', {
detail: { value: input.value },
bubbles: true,
composed: true
}));
}
private handleChange(e: Event) {
const input = e.target as HTMLInputElement;
this.value = input.value;
this.dispatchEvent(new CustomEvent('change', {
detail: { value: input.value },
bubbles: true,
composed: true
}));
}
private handleFocus() {
// Emit input-focus for keyboard detection
this.dispatchEvent(new CustomEvent('input-focus', {
bubbles: true,
composed: true
}));
}
private handleBlur() {
// Emit input-blur for keyboard detection
this.dispatchEvent(new CustomEvent('input-blur', {
bubbles: true,
composed: true
}));
}
public render(): TemplateResult {
return html`
<div class="input-wrapper">
${this.label ? html`
<label for=${this.id || 'input'}>
${this.label}
${this.required ? html`<span class="required">*</span>` : ''}
</label>
` : ''}
<input
.type=${this.type}
.placeholder=${this.placeholder}
.value=${this.value}
?disabled=${this.disabled}
?required=${this.required}
.id=${this.id || 'input'}
.name=${this.name}
.autocomplete=${this.autocomplete}
class=${this.error ? 'error' : ''}
@input=${this.handleInput}
@change=${this.handleChange}
@focus=${this.handleFocus}
@blur=${this.handleBlur}
/>
${this.error ? html`
<div class="error-message">${this.error}</div>
` : ''}
</div>
`;
}
/**
* Focus the input programmatically
*/
public focus() {
const input = this.shadowRoot?.querySelector('input');
input?.focus();
}
/**
* Blur the input programmatically
*/
public blur() {
const input = this.shadowRoot?.querySelector('input');
input?.blur();
}
}