<div class="input">
<div class="input__inner">
<input class="input__input input__input--password" id="input" name="input" type="password" spellcheck="false" />
</div>
</div>
{% set type = type ?? 'text' %}
{% 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 limitType = limit.type ?? 'characters' %}
{% set limit = limit.count ?? limit ?? false -%}
{% set inputAttributes = {
class: {
'input__input': true,
("input__input--#{type}"): true,
},
id: id,
name: name ?? false,
autocomplete: autocomplete,
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,
} -%}
<div {{ html_attributes({
class: {
'input': true,
},
}, attrs ?? {}) }}>
<div class="input__inner">
{%- if icon|default %}
{% include '@icon' with {
icon: icon,
class: 'input__icon',
} only %}
{% endif %}
{% switch type %}
{%- case 'textarea' %}
<textarea {{ html_attributes(inputAttributes | merge({
rows: rows ?? 4,
placeholder: placeholder,
maxlength: maxlength ?? false,
'data-limit': limit ? 'limit' | namespaceInputId(id),
'data-limit-count': limit,
'data-limit-type': limit ? limitType,
}), inputAttrs ?? {}) }}>{{ value }}</textarea>
{%- case 'richtext' %}
<input {{ html_attributes(inputAttributes | merge({
type: 'hidden',
value: value,
'data-richtext': 'richtext' | namespaceInputId(id),
}), inputAttrs ?? {}) }}>
<div class="input__input input__input--richtext" id="{{ 'richtext' | namespaceInputId(id) }}">
<div class="input__richtext-toolbar">
{% set richtextActions = {
bold: {
title: 'Bold' | t('site'),
icon: 'bold',
},
italic: {
title: 'Italic' | t('site'),
icon: 'italic',
},
underline: {
title: 'Underline' | t('site'),
icon: 'underline',
},
strikethrough: {
title: 'Strikethrough' | t('site'),
icon: 'strikethrough',
},
heading1: {
title: 'Format as headline level 1' | t('site'),
icon: 'heading-1',
},
heading2: {
title: 'Format as headline level 2' | t('site'),
icon: 'heading-2',
},
paragraph: {
title: 'Format as paragraph' | t('site'),
icon: 'paragraph',
},
quote: {
title: 'Format as quote' | t('site'),
icon: 'blockquote',
},
ulist: {
title: 'Unordered list' | t('site'),
icon: 'unordered-list',
},
olist: {
title: 'Ordered list' | t('site'),
icon: 'ordered-list',
},
code: {
title: 'Format as code' | t('site'),
icon: 'code',
},
line: {
title: 'Horizontal line' | t('site'),
icon: 'line',
},
link: {
title: 'Link' | t('site'),
icon: 'link',
},
image: {
title: 'Image' | t('site'),
icon: 'image',
},
clear: {
title: 'Clear formatting' | t('site'),
icon: 'clear',
},
alignleft: {
title: 'Align to the left' | t('site'),
icon: 'align-left',
},
alignright: {
title: 'Align to the right' | t('site'),
icon: 'align-right',
},
aligncenter: {
title: 'Align centered' | t('site'),
icon: 'align-center',
},
} %}
{% for button in buttons %}
{% if button in richtextActions|keys %}
<button {{ html_attributes({
class: 'input__richtext-toolbar-button',
type: 'button',
title: richtextActions[button].title,
'data-exec': button,
}) }}>
{% include '@icon' with {
icon: richtextActions[button].icon,
class: 'input__richtext-toolbar-button-icon',
} only %}
</button>
{% endif %}
{% endfor %}
</div>
<div class="input__richtext-content"> </div>
</div>
{%- 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 %}
{% 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 ?? false,
disabled: option.disabled ?? false,
}) }}>
{{- option.label -}}
</option>
{% endif %}
{% endfor -%}
{% if hasOptgroups %}
</optgroup>
{% endif %}
</select>
{%- if not multiple %}
{% include '@icon' with {
icon: 'caret-down',
class: 'input__icon input__icon--select',
} only %}
{% endif %}
{%- case 'digit-code' %}
<div class="input__digit-code">
<div class="input__digit-code-input"{% if length|default %} style="--input-digit-code-length: {{ length }}"{% endif %}>
<input {{ html_attributes(inputAttributes | merge({
value: value,
type: 'text',
placeholder: placeholder,
maxlength: length ?? null,
inputmode: 'decimal',
pattern: '[0-9]*',
}), inputAttrs ?? {}) }} />
</div>
</div>
{%- 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-limit': limit ? 'limit' | namespaceInputId(id),
'data-limit-count': limit,
'data-limit-type': limit ? limitType,
'data-tags': tags|default ? tags|json_encode,
'data-tags-remove-label': tags|default ? 'Remove Tag' | t('site'),
'data-select-on-click': selectOnClick ?? false,
'data-tel': type == 'tel' and countries|default ? countries | merge({
i18n: {
selectedCountryAriaLabel: 'Selected country' | t('site'),
noCountrySelected: 'No country selected' | t('site'),
countryListAriaLabel: 'List of countries' | t('site'),
searchPlaceholder: 'Search' | t('site'),
zeroSearchResults: 'No results found' | t('site'),
oneSearchResult: '1 result found' | t('site'),
multipleSearchResults: '${count} results found' | t('site'),
},
}),
'data-typeahead': typeahead|default ? {
id: 'typeahead' | namespaceInputId(id),
url: typeahead.url,
minLength: typeahead.minLength ?? 0,
},
autocomplete: typeahead|default ? 'off' : autocomplete,
}), inputAttrs ?? {}) }} />
{% if type == 'file' %}
<ul class="input__uploads">
{% for file in value %}
<li class="input__upload">{{ file }}</li>
{% endfor %}
</ul>
{% endif %}
{%- endswitch -%}
{%- if limit and (type == 'textarea' or type == 'text') -%}
<span class="input__limit" id="{{ 'limit' | namespaceInputId(id) }}">
{%- if limitType == 'words' -%}
{{ 'Words left: <strong>{words}</strong>' | t('site', {
words: limit - value|default('')|words_count,
}) | raw }}
{%- elseif limitType == 'characters' -%}
{{ 'Characters left: <strong>{chars}</strong>' | t('site', {
chars: limit - value|default('')|chars_count,
}) | raw }}
{%- endif -%}
</span>
{%- endif -%}
</div>
</div>
{
"id": "input",
"name": "input",
"type": "password"
}
:root {
--input-background-color: var(--color-white);
--input-border-color-invalid: var(--error-color);
--input-border-color: var(--form-color);
--input-border-radius: var(--form-border-radius);
--input-border-width: 2px;
--input-color: var(--text-color);
--input-dropdown-background-selected: var(--color-grey-light);
--input-dropdown-background: var(--color-white);
--input-dropdown-border-color: var(--form-color);
--input-dropdown-color: var(--text-color);
--input-dropdown-padding-block: 1.2rem;
--input-focus-outline-color: var(--form-focus-color);
--input-focus-outline-width: 3px;
--input-font-size: 1.8rem;
--input-icon-color: var(--form-color);
--input-icon-gap: 0.6rem;
--input-icon-size: 1.6rem;
--input-limit-background-over-limit: var(--error-color);
--input-limit-background: var(--form-color);
--input-limit-color-over-limit: var(--color-white);
--input-limit-color: var(--color-white);
--input-limit-size: 1.3rem;
--input-line-height: 2.4rem;
--input-padding-block: 1.2rem;
--input-padding-inline: 2rem;
--input-placeholder-color: var(--form-placeholder-color);
--input-richtext-button-background-color-engaged: var(--form-color);
--input-richtext-button-background-color-selected: var(--form-color);
--input-richtext-button-background-color: var(--color-white);
--input-richtext-button-border-color-engaged: var(--form-color);
--input-richtext-button-border-color-selected: var(--form-color);
--input-richtext-button-border-color: var(--form-color);
--input-richtext-button-color-engaged: var(--color-white);
--input-richtext-button-color-selected: var(--color-white);
--input-richtext-button-color: var(--form-color);
--input-richtext-button-size: 2.6rem;
--input-tag-background: var(--form-color);
--input-tag-color: var(--color-white);
--input-tag-remove-background: var(--color-white);
--input-tag-remove-color: var(--form-color);
--input-uploads-background-color: var(--background-color);
--input-uploads-color: var(--text-color);
--input-uploads-gap: 1.5rem;
--input-uploads-margin-block: 1.2rem;
--input-z-index: #{z-index('default')};
}
.input {
display: flex;
flex-direction: column;
}
.input__inner {
position: relative;
z-index: var(--input-z-index);
}
.input__input {
--focus-outline-offset: 0;
--focus-outline-width: var(--input-focus-outline-width);
--focus-outline-color: var(--input-focus-outline-color);
--input-padding-inline-start-icon: calc(var(--input-padding-inline) + var(--input-icon-gap) + var(--input-icon-size));
--selection-background-color: var(--input-color);
--selection-foreground-color: var(--input-background-color);
background-color: var(--input-background-color);
border: var(--input-border-width) solid var(--input-border-color);
border-radius: var(--input-border-radius);
color: var(--input-color);
display: block;
font-size: var(--input-font-size);
inline-size: 100%;
line-height: var(--input-line-height);
min-block-size: calc(var(--input-line-height) + (var(--input-padding-block) + var(--input-border-width)) * 2);
min-inline-size: 100%;
padding-block: var(--input-padding-block);
padding-inline: var(--input-padding-inline);
position: relative;
text-align: start;
transition-property: border-color;
z-index: 2;
&:where(:not(:first-child)) {
padding-inline-start: var(--input-padding-inline-start-icon);
}
&:where(:not(:last-child)) {
padding-inline-end: var(--input-padding-inline-start-icon);
}
&:is([aria-invalid='true']) {
border-color: var(--input-border-color-invalid);
}
&:is(:focus, :focus-within) {
border-color: var(--input-border-color);
@include use-focus-outline();
}
&[disabled] {
opacity: 0.5;
user-select: none;
}
&::placeholder {
color: var(--input-placeholder-color);
opacity: 1;
}
}
.input__input--typeahead {
background-color: transparent;
z-index: 2;
}
.input__input--richtext {
padding-inline: var(--input-padding-inline);
&:focus-within {
@include use-focus-outline();
}
}
.input__input--color {
block-size: var(--input-line-height);
cursor: pointer;
line-height: 0;
&::-webkit-color-swatch,
&::-moz-color-swatch {
border: 0;
}
}
.input__input--select {
cursor: pointer;
&:invalid {
color: var(--input-placeholder-color);
}
}
.input__digit-code {
--focus-outline-offset: 0;
--focus-outline-width: var(--input-focus-outline-width);
--focus-outline-color: var(--input-focus-outline-color);
--input-digit-code-letter-spacing: 2em;
background-color: var(--input-background-color);
border: var(--input-border-width) solid var(--input-border-color);
border-radius: var(--input-border-radius);
container-name: input-digit-code;
container-type: inline-size;
&:has([aria-invalid='true']) {
border-color: var(--input-border-color-invalid);
}
&:has(:focus-visible) {
@include use-focus-outline();
}
}
.input__digit-code-input {
--input-line-height: 1.33;
block-size: calc(var(--input-line-height) * var(--input-font-size) + var(--input-padding-block) * 2);
font-size: var(--input-font-size);
line-height: var(--input-line-height);
overflow: hidden;
position: relative;
&:is([style]) {
--input-line-height: 1.2;
max-inline-size: calc((0.97ch + var(--input-digit-code-letter-spacing)) * var(--input-digit-code-length) - 1px);
@supports (width: 1cqi) {
--input-font-size: calc(38cqi / var(--input-digit-code-length));
}
}
@supports (height: 1lh) {
block-size: calc(1lh + var(--input-padding-block) * 2);
}
}
.input__input--digit-code {
--focus-outline-color: transparent;
--focus-outline-width: 0;
--input-border-width: 0;
background-color: transparent;
background-image: linear-gradient(to right, var(--color-grey-light) 1px, transparent 1px);
background-origin: content-box;
background-position-x: calc((var(--input-digit-code-letter-spacing) / 2) + 0.97ch);
background-position-y: center;
background-repeat: repeat-x;
background-size: calc(0.97ch + var(--input-digit-code-letter-spacing)) 1.3em;
font-variant-numeric: tabular-nums slashed-zero;
inline-size: auto;
inset-block: 0;
inset-inline-end: -5ch;
inset-inline-start: -1px;
letter-spacing: var(--input-digit-code-letter-spacing);
max-inline-size: none;
padding-inline: calc((var(--input-digit-code-letter-spacing) / 2));
position: absolute;
}
.input__input--tags {
display: flex;
flex-wrap: wrap;
gap: 1rem;
padding-inline: var(--input-padding-inline);
&:focus-within {
@include use-focus-outline();
}
}
.input__richtext-toolbar {
display: flex;
gap: 0.5rem;
user-select: none;
}
.input__richtext-toolbar-button {
background-color: var(--input-richtext-button-background-color);
block-size: var(--input-richtext-button-size);
border: 1px solid var(--input-richtext-button-border-color);
border-radius: 0.5rem;
color: var(--input-richtext-button-color);
font-size: calc(var(--input-richtext-button-size) * 0.5);
inline-size: var(--input-richtext-button-size);
line-height: 0;
transition-property: background-color, border-color, color;
&:is(:hover, :focus-visible) {
background-color: var(--input-richtext-button-background-color-engaged);
border-color: var(--input-richtext-button-border-color-engaged);
color: var(--input-richtext-button-color-engaged);
}
}
.input__richtext-toolbar-button--selected {
background-color: var(--input-richtext-button-background-color-selected);
border-color: var(--input-richtext-button-border-color-selected);
color: var(--input-richtext-button-color-selected);
}
.input__richtext-toolbar-button-icon {
pointer-events: none;
}
.input__richtext-content {
--focus-outline-width: 0;
color: var(--text-color);
line-height: var(--line-height-body);
margin-block-start: 1rem;
> * + * {
margin-block-start: 0.5rem;
}
:is(h1) {
font-size: 1.3em;
font-weight: bold;
}
:is(h2) {
font-size: 1.1em;
font-weight: bold;
}
:is(blockquote) {
border-inline-start: 0.5rem solid var(--color-gray-500);
padding-inline-start: 1.5rem;
}
:is(ul, ol) {
padding-inline-start: 2rem;
}
:is(pre) {
background-color: var(--light-background-color);
font-family: monospace;
white-space: nowrap;
}
:is(hr) {
border-block-start: 1px solid var(--text-color);
}
:is(a) {
color: var(--link-color);
text-decoration-color: var(--link-color);
text-decoration-line: underline;
text-decoration-style: solid;
text-decoration-thickness: 1px;
}
}
.input__option,
.input__option-group {
color: var(--input-color);
font-family: inherit;
}
.input__option--select {
color: var(--input-placeholder-color);
}
.input__icon {
color: var(--input-icon-color);
font-size: var(--input-icon-size);
inset-block-start: 50%;
inset-inline-start: var(--input-padding-inline);
line-height: 0;
pointer-events: none;
position: absolute;
transform: translateY(-50%);
transition-property: color;
z-index: 3;
}
.input__icon--select {
inset-inline: auto var(--input-padding-inline);
}
.input__limit {
--input-limit-start-offset: calc(var(--input-border-radius) + 0.4em);
align-self: flex-end;
background-color: var(--input-limit-background);
color: var(--input-limit-color);
display: inline-block;
font-size: var(--input-limit-size);
line-height: 1;
margin-block-start: 0.4rem;
padding-block: 0.4em;
padding-inline: 0.8em;
position: relative;
transition-property: background-color, color;
z-index: 1;
.input__input[aria-invalid='true'] + & {
background-color: var(--input-limit-background-over-limit);
color: var(--input-limit-color-over-limit);
}
}
.input__tags-input {
--focus-outline-width: 0;
display: inline-block;
flex-grow: 1;
min-inline-size: 11rem;
white-space: pre-wrap;
}
.input__tag {
align-items: center;
background-color: var(--input-tag-background);
border-radius: 0.5rem;
color: var(--input-tag-color);
display: flex;
font-size: 1.4rem;
gap: 0.3rem;
line-height: 2.4rem;
padding-inline: 0.5rem;
}
.input__tag-remove {
block-size: 2rem;
cursor: pointer;
display: block;
inline-size: 2rem;
order: 2;
position: relative;
transition-property: scale;
&:is(:hover, :focus) {
scale: 1.3;
}
&::after {
background-color: var(--input-tag-remove-background);
border-radius: 50%;
color: var(--input-tag-remove-color);
content: 'X';
font-size: 1rem;
font-weight: bold;
inset: 0.4rem;
line-height: 1.2rem;
position: absolute;
text-align: center;
}
}
.input__dropdown {
--input-tags-arrow-size: 1.2rem;
--input-tags-arrow-offset: calc(var(--input-padding-inline) - var(--input-tags-arrow-size) / 2);
animation-duration: var(--duration-default);
animation-name: opacity;
background-color: var(--input-dropdown-background);
border-color: var(--input-dropdown-border-color);
border-radius: var(--input-border-radius);
border-style: solid;
border-width: 1px;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.05);
color: var(--input-dropdown-color);
font-size: 1.4rem;
inline-size: 100% !important;
line-height: var(--line-height-small);
position: absolute;
text-align: start;
transform: translateY(calc(var(--input-tags-arrow-size) / 2));
z-index: #{z-index('dropdown')};
&::before {
block-size: 0;
border: var(--input-tags-arrow-size) solid transparent;
border-block-end-color: var(--input-dropdown-border-color);
content: '';
inline-size: 0;
inset-block-start: 0;
inset-inline-start: 0;
position: absolute;
transform: translate(var(--input-tags-arrow-offset), -100%);
z-index: 2;
}
}
.input__dropdown--hide {
display: none;
}
.input__dropdown-item {
padding-block: var(--input-dropdown-padding-block);
padding-inline: var(--input-padding-inline);
position: relative;
transition-property: background-color, font-weight;
& + & {
border-block-start: 1px solid var(--input-dropdown-border-color);
}
&:first-child {
border-start-end-radius: var(--input-border-radius);
border-start-start-radius: var(--input-border-radius);
}
&:first-child::before {
block-size: 0;
border: calc(var(--input-tags-arrow-size) - 0.15rem) solid transparent;
border-block-end-color: var(--input-dropdown-background);
content: '';
inline-size: 0;
inset-block-start: 0;
inset-inline-start: 0;
position: absolute;
transform: translate(calc(var(--input-tags-arrow-offset) + 0.15rem), -100%);
transition-property: border-color;
z-index: 4;
}
&:last-child {
border-end-end-radius: var(--input-border-radius);
border-end-start-radius: var(--input-border-radius);
}
}
.input__dropdown-item--selected {
background-color: var(--input-dropdown-background-selected);
font-weight: bold;
text-decoration: underline;
&:first-child::before {
border-block-end-color: var(--input-dropdown-background-selected);
}
}
.input__uploads {
display: flex;
flex-wrap: wrap;
gap: var(--input-uploads-gap);
margin-block-start: var(--input-uploads-margin-block);
}
.input__upload {
align-items: center;
background-color: var(--input-uploads-background-color);
border-radius: 0.5rem;
color: var(--input-uploads-color);
display: flex;
font-size: 1.4rem;
gap: 0.3rem;
line-height: 2.4rem;
padding-inline: 0.5rem;
}
.input__typeahead {
background-color: var(--input-background-color);
position: relative;
}
.input__typeahead-hint {
border-color: transparent;
border-style: solid;
border-width: var(--input-border-width);
color: var(--color-grey-light);
font-size: var(--input-font-size);
inset: 0;
line-height: var(--input-line-height);
margin: 0 !important;
padding-block: var(--input-padding-block);
padding-inline: var(--input-padding-inline);
position: absolute;
z-index: 1;
}
import { on } from 'delegated-events';
on('click', 'input.input__input[data-select-on-click]', (event) => {
const { currentTarget: $input } = event;
$input.select();
$input.setSelectionRange(0, $input.value.length);
});
on('keypress', 'input.input__input--digit-code', (event) => {
const { key } = event;
if (!key.match(/^[0-9]$/)) {
event.preventDefault();
}
});
on('paste', 'input.input__input--digit-code', (event) => {
const { currentTarget: $input, clipboardData } = event;
event.preventDefault();
const pasteValue = clipboardData?.getData('text');
if (pasteValue) {
$input.value = pasteValue.trim().replace(/\D/g, '');
}
});
document
.querySelectorAll<
HTMLInputElement | HTMLTextAreaElement
>('input.input__input[data-limit], textarea.input__input[data-limit]')
.forEach(async ($input) => {
const { default: useInputLimits } = await import(
/* webpackChunkName: "use-input-limits" */ './useInputLimits'
);
useInputLimits($input);
});
document
.querySelectorAll<HTMLInputElement>('input.input__input[data-tel]')
.forEach(async ($input) => {
const { default: useIntlTelInput } = await import(
/* webpackChunkName: "use-intl-tel-input" */ './useIntlTelInput'
);
useIntlTelInput($input);
});
document
.querySelectorAll<HTMLInputElement>('input.input__input[data-richtext]')
.forEach(async ($input) => {
const { default: usePell } = await import(
/* webpackChunkName: "use-pell" */ './usePell'
);
usePell($input);
});
document
.querySelectorAll<HTMLInputElement>('input.input__input[data-tags]')
.forEach(async ($input) => {
const { default: useTags } = await import(
/* webpackChunkName: "use-tags" */ './useTags'
);
useTags($input);
});
document
.querySelectorAll<HTMLInputElement>('input.input__input[data-typeahead]')
.forEach(async ($input) => {
const { default: useTypeahead } = await import(
/* webpackChunkName: "use-typeahead" */ './useTypeahead'
);
useTypeahead($input);
});
document
.querySelectorAll<HTMLElement>('.input__digit-code-input')
.forEach(($el) => {
$el.scrollLeft = 0;
$el.addEventListener(
'scroll',
() => {
$el.scrollLeft = 0;
},
{ passive: true },
);
});
import abort from '../../../javascripts/utils/abort';
import charsCount from '../../../javascripts/utils/charsCount';
import wordsCount from '../../../javascripts/utils/wordsCount';
const updateCount = (
$input: HTMLInputElement | HTMLTextAreaElement,
invalidOnLoad: boolean,
) => {
const {
limit,
limitType = 'characters',
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();
// Update aria-invalid
if (!invalidOnLoad) {
$input.setAttribute('aria-invalid', left < 0 ? 'true' : 'false');
}
});
};
const useInputLimits = ($input: HTMLInputElement | HTMLTextAreaElement) => {
// Update on load
const invalidOnLoad = $input.getAttribute('aria-invalid') === 'true';
updateCount($input, invalidOnLoad);
// Update on keydown, change and paste
const eventListener = () => updateCount($input, invalidOnLoad);
$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);
};
};
export default useInputLimits;
import intlTelInput from 'intl-tel-input/intlTelInputWithUtils';
import 'intl-tel-input/build/css/intlTelInput.css';
const useIntlTelInput = async ($input: HTMLInputElement) => {
if ($input.type !== 'tel') {
return;
}
const configuration: Record<string, unknown> = JSON.parse(
$input.dataset.tel ?? '{}',
);
intlTelInput($input, {
i18n: configuration.i18n as {
selectedCountryAriaLabel?: string;
searchPlaceholder?: string;
countryListAriaLabel?: string;
oneSearchResult?: string;
multipleSearchResults?: string;
noCountrySelected?: string;
zeroSearchResults?: string;
},
separateDialCode: true,
initialCountry:
typeof configuration.initialCountry === 'string'
? configuration.initialCountry.toLowerCase()
: 'auto',
onlyCountries: Array.isArray(configuration.onlyCountries)
? configuration.onlyCountries.map((i) => i.toLowerCase())
: [],
countryOrder: Array.isArray(configuration.countryOrder)
? configuration.countryOrder.map((i) => i.toLowerCase())
: [],
});
};
export default useIntlTelInput;
import { init, exec } from 'pell';
import abort from '../../../javascripts/utils/abort';
const usePell = async ($input: HTMLInputElement) => {
const { richtext } = $input.dataset;
if (!richtext) {
return;
}
const $richtext = document.getElementById(richtext) ?? abort();
const actions: pell.pellAction[] = [];
$richtext
.querySelectorAll<HTMLButtonElement>('.input__richtext-toolbar-button')
.forEach(($button) => {
const { exec: name } = $button.dataset;
const mapped: Record<
string,
{ result: () => boolean; state?: () => boolean }
> = {
alignleft: {
result: () => {
exec('justifyLeft', '');
return true;
},
state: () => document.queryCommandState('justifyLeft'),
},
alignright: {
result: () => {
exec('justifyRight', '');
return true;
},
state: () => document.queryCommandState('justifyRight'),
},
aligncenter: {
result: () => {
exec('justifyCenter', '');
return true;
},
state: () => document.queryCommandState('justifyCenter'),
},
};
if (name === 'clear') {
const action: pell.pellCustomActionConfig = {
name,
icon: $button.innerHTML.trim(),
title: $button.title,
result: () => {
const selection = window.getSelection()?.toString();
if (selection) {
const linesToDelete = selection.split('\n').join('<br>');
exec('formatBlock', '<p>');
document.execCommand('insertHTML', false, linesToDelete);
} else {
exec('formatBlock', '<p>');
}
return true;
},
};
actions.push(action);
} else if (name && name in mapped) {
const action: pell.pellCustomActionConfig = {
name,
icon: $button.innerHTML.trim(),
title: $button.title,
// eslint-disable-next-line security/detect-object-injection
...mapped[name], // nosemgrep: eslint.detect-object-injection
};
actions.push(action);
} else if (name) {
const action: pell.pellActionConfig = {
name: name as pell.pellAction,
icon: $button.innerHTML.trim(),
title: $button.title,
};
actions.push(action);
}
});
$richtext.querySelector('.input__richtext-toolbar')?.remove();
$richtext.querySelector('.input__richtext-content')?.remove();
const editor = init({
element: $richtext,
defaultParagraphSeparator: 'p',
actions,
styleWithCSS: false,
onChange: (html) => {
$input.value = html;
},
classes: {
actionbar: 'input__richtext-toolbar',
button: 'input__richtext-toolbar-button',
selected: 'input__richtext-toolbar-button--selected',
content: 'input__richtext-content',
},
});
setTimeout(() => {
if ($input.value) {
editor.content.innerHTML = $input.value;
editor.content.dispatchEvent(new MouseEvent('mouseup'));
}
}, 1);
$richtext
.querySelectorAll('.input__richtext-toolbar-button')
.forEach(($button) => {
$button.addEventListener('mousedown', (e) => e.preventDefault());
$button.addEventListener('mouseup', () => {
setTimeout(() => {
editor.content.dispatchEvent(new MouseEvent('mouseup'));
}, 1);
});
});
};
export default usePell;
import Tagify from '@yaireo/tagify';
const useTags = ($input: HTMLInputElement) => {
// Hide original input
$input.type = 'hidden';
// Get tags
const tags = JSON.parse($input.dataset.tags ?? '[]');
const removeTagLabel = $input.dataset.tagsRemoveLabel ?? '';
return new Tagify($input, {
whitelist: tags,
a11y: {
focusableTags: true,
},
dropdown: {
appendTarget: $input.closest<HTMLElement>('.input'),
},
editTags: false,
templates: {
tag(tagData) {
return `<tag title="${tagData.title || tagData.value}" contenteditable="false" spellcheck="false" tabIndex="${
this.settings.a11y.focusableTags ? 0 : -1
}" class="${this.settings.classNames.tag} ${tagData.class ? tagData.class : ''}" ${this.getAttributes(tagData)}>
<span class="${this.settings.classNames.tagText}">${
tagData[this.settings.tagTextProp] || tagData.value
}</span>
<x class="${this.settings.classNames.tagX}" role="button" title="${removeTagLabel}: ${
tagData[this.settings.tagTextProp] || tagData.value
}" tabindex="0"></x>
</tag>`;
},
dropdownHeader: () => '',
dropdownFooter: () => '',
},
classNames: {
namespace: 'input__input--tags',
tag: 'input__tag',
input: 'input__tags-input',
tagNoAnimation: 'input__tag--no-animation',
tagX: 'input__tag-remove',
tagText: 'input__tag-text',
dropdown: 'input__dropdown',
dropdownWrapper: 'input__dropdown-inner',
dropdownItem: 'input__dropdown-item',
dropdownItemActive: 'input__dropdown-item--selected',
},
});
};
export default useTags;
import typeahead from 'typeahead-standalone';
type UseTypeaheadOptions = {
id: string;
url: string;
minLength?: number;
};
const useTypeahead = ($input: HTMLInputElement) => {
const { typeahead: rawOptions = '{}' } = $input.dataset;
const options = JSON.parse(rawOptions) as UseTypeaheadOptions;
typeahead({
input: $input,
minLength: options.minLength ?? 0,
preventSubmit: true,
source: {
remote: {
url: options.url,
wildcard: '__QUERY__',
debounce: 300,
requestOptions: {
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
},
},
},
},
classNames: {
wrapper: 'input__typeahead',
input: 'input__input--typeahead',
hint: 'input__typeahead-hint',
highlight: 'input__typeahead-highlight',
list: 'input__dropdown',
hide: 'input__dropdown--hide',
show: 'input__dropdown--show',
selected: 'input__dropdown-item--selected',
header: 'input__dropdown-header',
footer: 'input__dropdown-footer',
loader: 'input__dropdown-loader',
suggestion: 'input__dropdown-item',
group: 'input__dropdown-group',
empty: 'input__dropdown-empty',
notFound: 'input__dropdown-not-found',
},
});
const $listContainer = $input
.closest('.input__typeahead')
?.querySelector('.input__dropdown');
const $container = $input.closest('.input');
if ($listContainer && $container) {
$container.appendChild($listContainer);
$listContainer.addEventListener('mousedown', (event: Event) => {
event.stopPropagation();
event.preventDefault();
});
}
};
export default useTypeahead;
No notes defined.