<div id="input-container" class="input">
<div class="input__line"> <input class="input__input" id="input" name="input" data-input-type="color" type="color" /></div>
</div>
{% set id = id ??? html_id('input') %}
{% set type = type ?? 'text' %}
{% set dateType = type == 'date' or type == 'datetime-local' or type == 'time' %}
{% set value = value ?? null %}
{% set placeholder = placeholder ?? null %}
{% set autocomplete = autocomplete ?? null %}
{% set required = required ?? false %}
{% set disabled = disabled ?? false %}
{% set invalid = invalid ?? false %}
{% set multiple = multiple ?? false %}
{% set readonly = readonly ?? false %}
{% set name = name ?? null %}
{% set tel = type == 'tel' ? tel|default %}
{% set limitType = limitType ?? null %}
{% set limit = limit ?? null -%}
{% set inputAttributes = {
class: 'input__input',
id: id,
name: name,
autocomplete: autocomplete,
autofocus: autofocus ?? false,
multiple: multiple,
required: required ? true : false,
'aria-describedby': describedBy ?? false,
'aria-invalid': invalid ? 'true' : null,
'aria-required': required ? 'true' : null,
disabled: disabled ? true : false,
readonly: readonly ? true : false,
'data-input-type': type,
} -%}
<div {{ html_attributes({
id: containerId ??? ('container' | namespaceInputId(id)),
class: 'input',
}, attrs ?? {}) }}>
<div class="input__line">
{%- if icon|default %}
<div class="input__icon">
{% include '@icon' with {
icon: icon,
} only %}
</div>
{% endif %}
{%- if prependText|default -%}
<div class="input__text">
{{- prependText -}}
</div>
{%- endif -%}
{% switch type %}
{%- case 'textarea' %}
<textarea {{ html_attributes(inputAttributes | merge({
rows: rows ?? 4,
placeholder: placeholder,
maxlength: maxlength ?? false,
'data-input-limit': limit and limitType ? 'limit' | namespaceInputId(id),
'data-input-limit-count': limit and limitType ? limit,
'data-input-limit-type': limit and limitType ? limitType,
}), inputAttrs ?? {}) }}>{{ value }}</textarea>
{%- case 'select' %}
<select {{ html_attributes(inputAttributes, inputAttrs ?? {}) }}>
{% set hasOptgroups = false %}
{%- if required and selectValue is defined and selectValue %}
<option class="input__option input__option--select" disabled selected value="">
{{- selectValue -}}
</option>
{% endif -%}
{% if resetValue is defined and resetValue %}
<option class="input__option input__option--reset" value="">
{{- resetValue -}}
</option>
{% endif %}
{%- for option in options %}
{% if option.optgroup is defined and option.optgroup %}
{% if hasOptgroups %}
</optgroup>
{% else %}
{% set hasOptgroups = true %}
{% endif %}
<optgroup class="input__option-group" label="{{ option.optgroup }}">
{% else %}
<option {{ html_attributes({
class: 'input__option',
value: option.value,
selected: option.selected ?? value == option.value,
disabled: option.disabled ?? false,
}) }}>
{{- option.label -}}
</option>
{% endif %}
{% endfor -%}
{% if hasOptgroups %}
</optgroup>
{% endif %}
</select>
{%- if not multiple %}
<div class="input__icon input__icon--select">
{% include '@icon' with {
icon: 'arrow-down',
} only %}
</div>
{% endif %}
{%- case 'tel-international' %}
<input {{ html_attributes({
type: 'hidden',
name: 'country' | namespaceInputName(name),
id: 'country' | namespaceInputId(id),
value: country ?? null,
}) }}>
<input {{ html_attributes(inputAttributes | merge({
name: 'phoneNumber' | namespaceInputName(name),
value: phoneNumber ?? value ?? null,
type: 'tel',
placeholder: placeholder,
inputmode: inputmode ?? 'tel',
spellcheck: spellcheck ?? 'false',
'data-input-tel': {
initialCountry: country ?? null,
countryOrder: countryOrder ?? null,
onlyCountries: onlyCountries ?? null,
countryInput: 'country' | namespaceInputId(id),
i18n: {
selectedCountryAriaLabel: 'Selected country' | t('site'),
noCountrySelected: 'No country selected' | t('site'),
countryListAriaLabel: 'List of countries' | t('site'),
searchPlaceholder: 'Search for country' | t('site'),
zeroSearchResults: 'No results found' | t('site'),
oneSearchResult: '1 result found' | t('site'),
multipleSearchResults: '${count} results found' | t('site'),
},
},
}), inputAttrs ?? {}) }} />
{%- default %}
<input {{ html_attributes(inputAttributes | merge({
value: type != 'file' ? value,
type: type,
placeholder: placeholder,
maxlength: maxlength ?? false,
min: min ?? false,
max: max ?? false,
step: step ?? false,
multiple: multiple ?? false,
accept: accept ?? false,
inputmode: inputmode ?? false,
spellcheck: type == 'password' ? 'false' : (spellcheck ?? false),
'data-input-date': dateType,
'data-input-empty': dateType ? not value or value == '',
'data-input-no-spinners': type == 'number' and noSpinners|default,
'data-input-limit': limit and limitType ? 'limit' | namespaceInputId(id),
'data-input-limit-count': limit and limitType ? limit,
'data-input-limit-type': limit and limitType ? limitType,
'data-input-select-on-click': selectOnClick ?? false,
}), inputAttrs ?? {}) }} />
{%- if dateType %}
<button class="input__picker" type="button">
{% include '@icon' with {
icon: 'calendar',
} only %}
</button>
{% endif %}
{%- endswitch -%}
{%- if appendText|default -%}
<div class="input__text">
{{- appendText -}}
</div>
{%- endif -%}
</div>
{% if type == 'file' and files|default %}
<ul class="input__uploads">
{% for file in files %}
<li class="input__pill">{{ file }}</li>
{% endfor %}
</ul>
{% endif %}
{%- if limit and limitType and (type == 'textarea' or type == 'text') -%}
<div class="input__limit">
<span class="input__pill" id="{{ 'limit' | namespaceInputId(id) }}">
{%- if limitType == 'words' -%}
{{ 'Words left: **{words}**' | t('site', {
words: limit - (value | default('') | words | length),
}) | markdown }}
{%- elseif limitType == 'characters' -%}
{{ 'Characters left: **{chars}**' | t('site', {
chars: limit - (value | default('') | length),
}) | markdown }}
{%- endif -%}
</span>
</div>
{%- endif -%}
</div>
{
"id": "input",
"name": "input",
"type": "color"
}
@use 'layers';
@use 'a11y';
.input {
--_input-border-width: var(--input-border-width, 1px);
--_input-min-block-size: var(--input-min-block-size, 4.4rem);
--_input-padding-block: calc((var(--_input-min-block-size) - 1lh) / 2 - var(--_input-border-width));
--_input-padding-inline: var(--input-padding-inline, 2.2rem);
background-color: var(--input-background-color, var(--color-white));
border-color: var(--input-border-color, var(--color-gray-400));
border-radius: var(--input-border-radius, calc(var(--_input-min-block-size) / 2));
border-style: var(--input-border-style, solid);
border-width: var(--_input-border-width);
color: var(--input-color, var(--color-steel-700));
display: flex;
flex-direction: column;
font-size: var(--input-font-size, 1.6rem);
line-height: var(--input-line-height, var(--line-height-regular));
min-inline-size: var(--input-min-inline-size, 20rem);
opacity: var(--input-opacity, 1);
touch-action: manipulation;
transition-property: background-color, border-color, color, opacity;
&:has(.input__input:focus-visible) {
--a11y-outline-offset: -1px;
--a11y-outline-color: var(--color-gray-800);
background-color: var(--input-background-color--engaged, var(--color-white));
border-color: var(--input-border-color--engaged, var(--color-gray-800));
color: var(--input-color--engaged, var(--color-steel-700));
@include a11y.outline();
}
&:has(.input__input[disabled]) {
filter: grayscale(1);
opacity: 0.2;
pointer-events: none;
user-select: none;
}
&:has(.input__input[data-input-type='select'], .input__input[data-input-type='color']) {
cursor: pointer;
}
}
.input__line {
align-items: center;
column-gap: calc(var(--_input-padding-inline) / 2);
display: flex;
padding-inline: var(--_input-padding-inline);
&:has(.iti) {
padding-inline-start: 0;
}
:is(.iti) {
position: relative;
z-index: #{layers.position('dropdown')};
}
}
.input__icon {
--icon-size: var(--input-icon-size, 2rem);
color: var(--input-icon-color, inherit);
display: flex;
flex-shrink: 0;
inline-size: var(--icon-size);
pointer-events: none;
}
.input__text {
color: var(--input-text-color, var(--color-steel-700));
flex-shrink: 0;
pointer-events: none;
touch-action: manipulation;
user-select: none;
white-space: nowrap;
}
.input__input {
appearance: var(--input-appearance, initial);
background-color: transparent;
cursor: var(--input-cursor, default);
display: block;
flex-grow: 1;
inline-size: auto;
inline-size: var(--input-inline-size, 100%);
line-height: inherit;
min-block-size: calc(var(--_input-min-block-size) - var(--_input-border-width) * 2 * 1px);
padding-block: var(--_input-padding-block);
padding-inline: 0;
text-align: var(--input-text-align, left);
&:is([data-input-type='select']) {
--input-color-invalid: var(--input-placeholder-color);
cursor: pointer;
}
&:is([data-input-type='color']) {
cursor: pointer;
}
&:is([data-input-type='tags']) {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
&:is([data-input-no-spinners]) {
appearance: textfield;
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
appearance: none;
margin: 0;
}
}
&:is(:focus) {
outline: 0;
}
&:is(:invalid) {
color: var(--input-color-invalid, inherit);
}
&:is([data-input-empty]) {
color: var(--input-placeholder-color, var(--color-gray-400));
-webkit-text-fill-color: var(--input-placeholder-color, var(--color-gray-400));
}
&::placeholder {
color: var(--input-placeholder-color, var(--color-gray-400));
opacity: 1;
}
&::file-selector-button {
background-color: var(--input-file-selector-button-background-color, var(--color-gray-800));
border-radius: var(--border-radius-small);
border-width: 0;
color: var(--input-file-selector-button-color, var(--color-white));
font-family: var(--font-family-exo);
font-size: 1.4rem;
font-weight: var(--font-weight-semibold);
line-height: var(--line-height-narrow);
padding-block: 0.5em;
padding-inline: 1em;
transition-property: background-color, color;
&:hover {
background-color: var(--input-file-selector-button-background-color--enter, var(--color-gray-950));
color: var(--input-file-selector-button-color--enter, var(--color-white));
}
}
&::-webkit-calendar-picker-indicator {
display: none;
}
&::-webkit-datetime-edit {
block-size: auto;
display: block;
line-height: 1;
padding-block: calc((1em * var(--input-line-height, var(--line-height-regular)) - 1em - var(--_input-border-width)) / 2);
padding-inline: 0;
}
&::-webkit-datetime-edit-ampm-field:focus,
&::-webkit-datetime-edit-day-field:focus,
&::-webkit-datetime-edit-hour-field:focus,
&::-webkit-datetime-edit-millisecond-field:focus,
&::-webkit-datetime-edit-minute-field:focus,
&::-webkit-datetime-edit-month-field:focus,
&::-webkit-datetime-edit-second-field:focus,
&::-webkit-datetime-edit-week-field:focus,
&::-webkit-datetime-edit-year-field:focus {
background-color: var(--tertiary-background-color);
color: var(--primary-form-color);
}
}
.input__limit {
margin-block-end: var(--_input-padding-block);
margin-inline-start: var(--_input-padding-inline);
pointer-events: none;
user-select: none;
}
.input__pill {
background-color: var(--input-limit-background-color, var(--color-gray-100));
border-radius: calc((var(--input-limit-padding-block, 0.5em) + 1lh) / 2);
color: var(--input-limit-color, var(--color-gray-800));
display: inline-block;
font-size: var(--input-limit-font-size, 1.2rem);
font-weight: var(--font-weight-bold);
inline-size: min-content;
line-height: var(--input-limit-line-height, var(--line-height-narrow));
padding-block: var(--input-limit-padding-block, 0.5em);
padding-inline: var(--input-limit-padding-inline, 1em);
white-space: nowrap;
:is(strong) {
font-weight: inherit;
}
}
.input__option {
color: var(--input-color);
font-family: inherit;
font-weight: var(--font-weight-regular);
}
.input__option-group {
color: var(--input-color);
font-family: inherit;
font-weight: var(--font-weight-semibold);
}
.input__option-group .input__option {
padding-inline-start: 1.6rem;
}
.input__option--select {
color: var(--input-placeholder-color);
}
.input__uploads {
display: flex;
flex-wrap: wrap;
gap: var(--input-uploads-gap, 1rem);
margin-block-end: var(--_input-padding-block);
margin-inline-start: var(--_input-padding-inline);
pointer-events: none;
user-select: none;
}
.input__picker {
--icon-size: var(--input-icon-size, 2rem);
block-size: 100%;
color: var(--input-icon-color, inherit);
display: flex;
flex-shrink: 0;
inline-size: var(--icon-size);
// stylelint-disable-next-line at-rule-no-vendor-prefix
@-moz-document url-prefix() {
display: none;
}
}
import { on } from 'delegated-events';
import abort from '../../../javascripts/utils/abort';
import onLoad from '../../../javascripts/utils/onLoad';
const wordsCount = (value: string) => value.split(/\S+/).length - 1;
const charsCount = (value: string) => [...value]
.map((char) => {
const codePoint = char.codePointAt(0);
if (codePoint && codePoint > 127) {
return `&#${codePoint};`;
}
return char;
})
.join('').length;
const updateCount = ($input: HTMLInputElement | HTMLTextAreaElement) => {
const {
inputLimit: limit,
inputLimitType: limitType = 'characters',
inputLimitCount: limitCount = false,
} = $input.dataset;
if (!limit || !limitCount) {
return;
}
requestIdleCallback(() => {
const $limit = document.getElementById(limit) ?? abort();
const $limitDisplay = $limit.querySelector('strong') ?? abort();
const value = $input.isContentEditable ? $input.innerHTML : $input.value;
const count = limitType === 'words' ? wordsCount(value) : charsCount(value);
const left = parseInt(limitCount, 10) - count;
// Update display
$limitDisplay.innerText = left.toString();
});
};
const useInputLimits = ($input: HTMLInputElement | HTMLTextAreaElement) => {
// Update on load
updateCount($input);
// Update on keydown, change and paste
const eventListener = () => updateCount($input);
$input.addEventListener('keydown', eventListener);
$input.addEventListener('change', eventListener);
$input.addEventListener('paste', eventListener);
// Deregister function
return () => {
$input.removeEventListener('keydown', eventListener);
$input.removeEventListener('change', eventListener);
$input.removeEventListener('paste', eventListener);
};
};
on('click', '.input', (event) => {
const { currentTarget: $container } = event;
if (event.target instanceof Element) {
if (event.target.closest('.input__input') || event.target.closest('.input__picker')) {
return;
}
}
const $input = $container.querySelector<HTMLElement>('.input__input');
$input?.focus({
preventScroll: true,
});
if ($input instanceof HTMLSelectElement) {
$input.showPicker();
}
});
on('click', '.input__picker', (event) => {
const { currentTarget: $picker } = event;
if ('showPicker' in HTMLInputElement.prototype) {
try {
const $input = $picker.parentElement?.querySelector<HTMLInputElement>('input.input__input');
$input?.focus({ preventScroll: true });
$input?.showPicker();
} catch {
// Do nothing.
}
}
});
on('click', 'input.input__input[data-input-select-on-click]', (event) => {
const { currentTarget: $input } = event;
$input.select();
$input.setSelectionRange(0, $input.value.length);
});
on('change', 'input.input__input[type^=date], input.input__input[type^=time]', (event) => {
const { currentTarget: $input } = event;
$input.toggleAttribute('data-input-empty', $input.value === '');
});
onLoad<HTMLInputElement>('input.input__input[type^=date], input.input__input[type^=time]', ($input) => {
$input.toggleAttribute('data-input-empty', $input.value === '');
});
onLoad<HTMLInputElement | HTMLTextAreaElement>('.input__input[data-input-limit]', ($input) => {
useInputLimits($input);
});
No notes defined.