2026-05-03 10:11:06 +00:00
|
|
|
import { DeesElement, html, property, customElement, css, type TemplateResult } from '@design.estate/dees-element';
|
|
|
|
|
import { idpElementStyles } from './tokens.js';
|
|
|
|
|
|
|
|
|
|
declare global {
|
|
|
|
|
interface HTMLElementTagNameMap {
|
|
|
|
|
'idp-input': IdpInput;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@customElement('idp-input')
|
|
|
|
|
export class IdpInput extends DeesElement {
|
|
|
|
|
public static demo = () => html`<idp-input label="Email" placeholder="user@example.com"></idp-input>`;
|
|
|
|
|
public static demoGroups = ['idp.global v3 primitives'];
|
|
|
|
|
|
|
|
|
|
@property({ type: String })
|
|
|
|
|
public accessor label = '';
|
|
|
|
|
|
2026-05-07 15:35:37 +00:00
|
|
|
@property({ type: String })
|
|
|
|
|
public accessor name = '';
|
|
|
|
|
|
|
|
|
|
@property({ type: String })
|
|
|
|
|
public accessor key = '';
|
|
|
|
|
|
2026-05-03 10:11:06 +00:00
|
|
|
@property({ type: String })
|
|
|
|
|
public accessor hint = '';
|
|
|
|
|
|
|
|
|
|
@property({ type: String })
|
|
|
|
|
public accessor value = '';
|
|
|
|
|
|
|
|
|
|
@property({ type: String })
|
|
|
|
|
public accessor placeholder = '';
|
|
|
|
|
|
|
|
|
|
@property({ type: String })
|
|
|
|
|
public accessor type = 'text';
|
|
|
|
|
|
2026-05-07 15:35:37 +00:00
|
|
|
@property({ type: String })
|
|
|
|
|
public accessor autocomplete = '';
|
|
|
|
|
|
|
|
|
|
@property({ type: String })
|
|
|
|
|
public accessor error = '';
|
|
|
|
|
|
|
|
|
|
@property({ type: Boolean, reflect: true })
|
|
|
|
|
public accessor required = false;
|
|
|
|
|
|
2026-05-03 10:11:06 +00:00
|
|
|
@property({ type: Boolean, reflect: true })
|
|
|
|
|
public accessor disabled = false;
|
|
|
|
|
|
|
|
|
|
public static styles = [
|
|
|
|
|
...idpElementStyles,
|
|
|
|
|
css`
|
|
|
|
|
:host {
|
|
|
|
|
display: block;
|
|
|
|
|
}
|
|
|
|
|
label {
|
|
|
|
|
display: grid;
|
|
|
|
|
gap: 6px;
|
|
|
|
|
}
|
|
|
|
|
.label {
|
|
|
|
|
color: var(--idp-fg);
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
input {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 36px;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
padding: 0 10px;
|
|
|
|
|
border: 1px solid var(--idp-border);
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
outline: none;
|
|
|
|
|
background: var(--idp-card);
|
|
|
|
|
color: var(--idp-fg);
|
|
|
|
|
font-family: var(--idp-font);
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
transition: border-color 120ms ease, box-shadow 120ms ease;
|
|
|
|
|
}
|
|
|
|
|
input:focus {
|
|
|
|
|
border-color: var(--idp-accent);
|
|
|
|
|
box-shadow: 0 0 0 3px color-mix(in srgb, var(--idp-accent), transparent 86%);
|
|
|
|
|
}
|
|
|
|
|
input:disabled {
|
|
|
|
|
opacity: 0.5;
|
|
|
|
|
}
|
2026-05-07 15:35:37 +00:00
|
|
|
input[aria-invalid='true'] {
|
|
|
|
|
border-color: var(--idp-destructive);
|
|
|
|
|
box-shadow: 0 0 0 3px color-mix(in srgb, var(--idp-destructive), transparent 86%);
|
|
|
|
|
}
|
|
|
|
|
.hint,
|
|
|
|
|
.error {
|
2026-05-03 10:11:06 +00:00
|
|
|
color: var(--idp-muted-fg);
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
line-height: 1.4;
|
|
|
|
|
}
|
2026-05-07 15:35:37 +00:00
|
|
|
.error {
|
|
|
|
|
color: var(--idp-destructive);
|
|
|
|
|
}
|
2026-05-03 10:11:06 +00:00
|
|
|
`,
|
|
|
|
|
];
|
|
|
|
|
|
2026-05-07 15:35:37 +00:00
|
|
|
public focus() {
|
|
|
|
|
this.shadowRoot?.querySelector('input')?.focus();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public validate() {
|
|
|
|
|
const input = this.shadowRoot?.querySelector('input');
|
|
|
|
|
if (this.required && !this.value.trim()) {
|
|
|
|
|
this.error = `${this.label || this.name || 'This field'} is required.`;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (input && !input.checkValidity()) {
|
|
|
|
|
this.error = input.validationMessage;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.error = '';
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-03 10:11:06 +00:00
|
|
|
private handleInput(eventArg: Event) {
|
|
|
|
|
this.value = (eventArg.target as HTMLInputElement).value;
|
2026-05-07 15:35:37 +00:00
|
|
|
if (this.error) {
|
|
|
|
|
this.validate();
|
|
|
|
|
}
|
2026-05-03 10:11:06 +00:00
|
|
|
this.dispatchEvent(new CustomEvent('idp-input-change', {
|
2026-05-07 15:35:37 +00:00
|
|
|
detail: { name: this.name || this.key, key: this.key || this.name, value: this.value },
|
2026-05-03 10:11:06 +00:00
|
|
|
bubbles: true,
|
|
|
|
|
composed: true,
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public render(): TemplateResult {
|
|
|
|
|
return html`
|
|
|
|
|
<label>
|
|
|
|
|
${this.label ? html`<span class="label">${this.label}</span>` : html``}
|
|
|
|
|
<input
|
|
|
|
|
.value=${this.value}
|
2026-05-07 15:35:37 +00:00
|
|
|
name=${this.name || this.key}
|
2026-05-03 10:11:06 +00:00
|
|
|
type=${this.type}
|
|
|
|
|
placeholder=${this.placeholder}
|
2026-05-07 15:35:37 +00:00
|
|
|
autocomplete=${this.autocomplete}
|
|
|
|
|
?required=${this.required}
|
2026-05-03 10:11:06 +00:00
|
|
|
?disabled=${this.disabled}
|
2026-05-07 15:35:37 +00:00
|
|
|
aria-invalid=${this.error ? 'true' : 'false'}
|
2026-05-03 10:11:06 +00:00
|
|
|
@input=${this.handleInput}
|
|
|
|
|
/>
|
2026-05-07 15:35:37 +00:00
|
|
|
${this.error ? html`<span class="error">${this.error}</span>` : this.hint ? html`<span class="hint">${this.hint}</span>` : html``}
|
2026-05-03 10:11:06 +00:00
|
|
|
</label>
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
}
|