import { booleanAttribute, ChangeDetectionStrategy, Component, computed, input, model, ViewEncapsulation, } from "@angular/core"; import { setupInvalid } from "../../core/validation/setupInvalid"; import { textfieldTypeHandlers, type SdTextfieldTypes, } from "./sd-textfield-type-handlers"; @Component({ selector: "sd-textfield", changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, standalone: true, imports: [], template: `
@if (controlType() === "password") { **** } @else { @if (controlValue()) {
{{ controlValueText() ? controlValueText() : " " }}
} @else if (placeholder()) { {{ placeholder() }} } @else {   } }
@if (!readonly() && !disabled()) { } `, styles: [ /* language=SCSS */ ` @use "sass:map"; @use "../../../scss/commons/variables"; @use "../../../scss/commons/mixins"; sd-textfield { display: block; position: relative; > input, > ._contents { @include mixins.form-control-base(); overflow: auto; width: 100%; border: 1px solid var(--trans-lighter); border-radius: var(--border-radius-default); background: var(--theme-secondary-lightest); &:focus { outline: none; border-color: var(--theme-secondary-default); } &[type="date"], &[type="month"], &[type="datetime-local"] { padding-top: calc(var(--gap-sm) - 1px); padding-bottom: calc(var(--gap-sm) - 1px); } &::-webkit-scrollbar { display: none; } &::-webkit-input-placeholder { color: var(--text-trans-lighter); } &::-webkit-outer-spin-button, &::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } &::-webkit-calendar-picker-indicator { cursor: pointer; margin: auto; } } > ._contents { display: none; } &[data-sd-type="number"] { > input, > ._contents { text-align: right; } } @each $key, $val in map.get(variables.$vars, theme) { &[data-sd-theme="#{$key}"] { > input, > ._contents { background: var(--theme-#{$key}-lightest); } } } &[data-sd-size="sm"] { > input, > ._contents { padding: var(--gap-xs) var(--gap-sm); &[type="date"], &[type="month"], &[type="datetime-local"], &[type="color"] { padding-top: calc(var(--gap-xs) - 1px); padding-bottom: calc(var(--gap-xs) - 1px); } } } &[data-sd-size="lg"] { > input, > ._contents { padding: var(--gap-default) var(--gap-lg); &[type="date"], &[type="month"], &[type="datetime-local"], &[type="color"] { padding-top: calc(var(--gap-default) - 1px); padding-bottom: calc(var(--gap-default) - 1px); } } } &[data-sd-inline="true"] { display: inline-block; vertical-align: top; > input, > ._contents { width: auto; vertical-align: top; } } &[data-sd-inset="true"] { > ._contents { display: block; } > input { position: absolute; top: 0; left: 0; } > input, > ._contents { width: 100%; border: none; border-radius: 0; } > input:focus { outline: 1px solid var(--theme-primary-default); outline-offset: -1px; } &[data-sd-type="month"] { > input, > ._contents { width: 8.25em; } } &[data-sd-type="date"] { > input, > ._contents { width: 8.25em; } } &[data-sd-type="datetime-local"] { > input, > ._contents { width: 14em; } } &[data-sd-type="year"] { > input, > ._contents { width: 4em; } } &[data-sd-type="color"] { > input, > ._contents { height: calc( var(--font-size-default) * var(--line-height-strip-unit) + var(--gap-sm) * 2 ); } &[data-sd-size="sm"] { > input, > ._contents { height: calc( var(--font-size-default) * var(--line-height-strip-unit) + var(--gap-xs) * 2 ); } } &[data-sd-size="lg"] { > input, > ._contents { height: calc( var(--font-size-default) * var(--line-height-strip-unit) + var(--gap-default) * 2 ); } } } } &[data-sd-disabled="true"] { > ._contents { display: block; background: var(--theme-gray-lightest); color: var(--text-trans-light); } &[data-sd-inset="true"] { > ._contents { background: var(--control-color); color: var(--text-trans-default); } } } &[data-sd-readonly="true"] { > ._contents { display: block; } } } `, ], host: { "[attr.data-sd-type]": "type()", "[attr.data-sd-disabled]": "disabled()", "[attr.data-sd-readonly]": "readonly()", "[attr.data-sd-inline]": "inline()", "[attr.data-sd-inset]": "inset()", "[attr.data-sd-size]": "size()", "[attr.data-sd-theme]": "theme()", }, }) export class SdTextfield { value = model(); type = input.required(); placeholder = input(); title = input(); inputStyle = input(); inputClass = input(); disabled = input(false, { transform: booleanAttribute }); readonly = input(false, { transform: booleanAttribute }); required = input(false, { transform: booleanAttribute }); min = input(); max = input(); minlength = input(); maxlength = input(); pattern = input(); validatorFn = input<(value: SdTextfieldTypes[K] | undefined) => string | undefined>(); format = input(); step = input(); autocomplete = input(); useNumberComma = input(true, { transform: booleanAttribute }); minDigits = input(); inline = input(false, { transform: booleanAttribute }); inset = input(false, { transform: booleanAttribute }); size = input<"sm" | "lg">(); theme = input< "primary" | "secondary" | "info" | "success" | "warning" | "danger" | "gray" | "blue-gray" >(); private readonly _handler = computed(() => textfieldTypeHandlers[this.type()]); controlType = computed(() => this._handler().controlType); controlStep = computed(() => this._handler().getControlStep(this.step())); controlValue = computed(() => { const value = this.value(); if (value == null) return ""; return this._handler().toControlValue(value, { useNumberComma: this.useNumberComma(), format: this.format(), }); }); controlValueText = computed(() => { const value = this.value(); if (value == null) return undefined; return this._handler().toDisplayText(value, { minDigits: this.minDigits(), }) ?? this.controlValue(); }); constructor() { setupInvalid(() => { const value = this.value(); const handlerErrors = this._handler().validate(value, { required: this.required(), min: this.min(), max: this.max(), minlength: this.minlength(), maxlength: this.maxlength(), pattern: this.pattern(), format: this.format(), }); const errorMessages = [...handlerErrors]; if (this.validatorFn()) { const message = this.validatorFn()!(value); if (message != null) { errorMessages.push(message); } } return errorMessages.join("\r\n"); }); } onInput(event: Event): void { const inputEl = event.target as HTMLInputElement; if (inputEl.value === "") { this.value.set(undefined); return; } const parsed = this._handler().parse(inputEl.value, { format: this.format() }); if (parsed == null) { inputEl.value = this.controlValue(); return; } this.value.set(parsed as SdTextfieldTypes[K]); } onInputPaste(event: ClipboardEvent): void { event.preventDefault(); const text = event.clipboardData?.getData("text/plain").trim(); if (text == null || text === "") { this.value.set(undefined); return; } const parsed = this._handler().parse(text, { format: this.format() }); if (parsed == null) { const inputEl = event.target as HTMLInputElement; inputEl.value = this.controlValue(); return; } this.value.set(parsed as SdTextfieldTypes[K] | undefined); } }