Расширяем возможности HTML5 тега time с помощью веб-компонента wc-time
Текушее время и мы продолжаем наш цикл статей про разработку веб-компонентов и сегодня мы будем расширять возможности HTML5 тега time с помощью возможностей браузера и идеологии веб-компонентов
<time is="wc-time"></time>
Мультиязычность с помощью lang
аттрибута
По умолчанию будем ориентироваться на navigator.language
или на указанный в теге аттрибут lang
, пропишем эту логику в конструкторе веб-компонента wc-likes
this.Lang = this.getAttribute('lang') || navigator.language.replace(/-\w{1,2}/gm, "");
Проверим, как получается
1 febrary lang=ru
🇷🇺
13 мая lang=en
🏴
<time is="wc-time" lang="ru" datetime="2023-02-01">1 febrary 2023</time>
Добавим кейс, что текущий год wc-time скрывает по умолчанию т.к. дата актуальна в контексте текущего года. Посмотрим на дату из другого года
1 февраля 2020 lang=en
🏴
Для удобочитаемости и поддержки склонения разных языков, добавим вспомогательную функцию склонения слов и фразы для двух самых популярных языков в интернете
Функция склонения и набор фраз
/** * Declension * @param value * @param words * @returns */ const nw = (value:number, words:string[]):string => { value = Math.abs(value) % 100; var num = value % 10; if(value > 10 && value < 20) return words[2]; if(num > 1 && num < 5) return words[1]; if(num == 1) return words[0]; return words[2]; } /** * Default translates for TOP2 internet languages */ const Translate = { en: { ago: 'ago', year: ['year', 'years', 'years'], month: ['month', 'months', 'months'], week: ['week', 'weeks', 'weeks'], day: ['day', 'days', 'days'], hour: ['hour', 'hours', 'hours'], minute: ['minute', 'minutes', 'minutes'], }, ru: { ago: 'назад', year: ['год', 'года', 'лет'], month: ['месяц', 'месяца', 'месяцев'], week: ['неделя', 'недели', 'недель'], day: ['день', 'дня', 'дней'], hour: ['час', 'часа', 'часов'], minute: ['минута', 'минуты', 'минут'], } };
Формат вывода даты\времени\прошедшего времени data-view-format
При помощи data-view-format
аттрибута мы можем управлять видом выводимого времени в time
. По умолчанию, установим, что будет показываться дата и время (datetime) date,time
Проверим комбинации
<time is="wc-time" data-view-format="date" datetime="2022-02-01"></time>
data-view-format="time"
data-view-format="date"
data-view-format="ago"
data-view-format="time,ago"
Формат вывода даты в всплывающем окне data-dialog-format
Добавим поддержку настройки формата вывода даты в всплывающем dialog
при помощи data-dialog-format
аттрибута, установим значение по умолчанию максимально возможное — date,time,ago
data-dialog-format="time"
data-dialog-format="date"
data-dialog-format="ago"
data-dialog-format="time,ago"
По умолчанию диалоговое окно показывается, добавим возможность указать data-show-dialog="0"
чтобы не загружать HTML разметку. Кстати про нагрузку разметки, давай будем показывать в DOM dialog элемент только при событиях pointerenter
и скрываем при pointercancel
pointerleave
//in wc-time constructor //Listen pointer events in
Методы показа и закрытия диалогового окна очень просты
hideDateDialog() { if(this.ShowDialog && this.DateDialog instanceof HTMLDialogElement) { this.DateDialog.close(); this.DateDialog.parentElement.removeChild(this.DateDialog); this.DateDialog = null; } } showDateDialog() { if(this.ShowDialog && this.DateCssHint === false) { this.DateDialog = document.createElement('dialog'); this.DateDialog.style.whiteSpace = 'nowrap'; this.append(this.DateDialog); this.DateDialog.innerHTML = this.renderTimeString(this.DateDialogFormat); if(this.DateHint) { const spanHint = document.createElement('span'); spanHint.classList.add('-hint'); spanHint.innerText = this.DateHint; this.DateDialog.prepend(spanHint); } this.DateDialog.show(); } }
Проверим что получается
<time is="wc-time" data-show-dialog="0" data-view-format="date" datetime="2022-02-01"></time>
Переиспользуем Intl.DateTimeFormat
возможности через аттрибуты
Более подробно про возможности Intl.DateTimeFormat() читайте тут
// prepare Intl.DateTimeFormat options const language = this.Lang; const defaultIntDateOptions = this.defaultIntDateOptions = { timeZone: this.getAttribute('data-format-timeZone') || Intl.DateTimeFormat().resolvedOptions().timeZone, hour12: this.getAttribute('data-format-hour12') !== null || false, weekday: this.getAttribute('data-format-weekday') as "narrow" | "short" | "long" || 'long', era: this.getAttribute('data-format-era') as "narrow" | "short" | "long" || 'long', year: this.getAttribute('data-format-year') as "numeric" | "2-digit" || 'numeric', month: this.getAttribute('data-format-month') as "narrow" | "short" | "long" | "numeric" | "2-digit" || 'long', day: this.getAttribute('data-format-day') as "numeric" | "2-digit" || 'numeric', hour: this.getAttribute('data-format-hour') as "numeric" | "2-digit" || '2-digit', minute: this.getAttribute('data-format-minute') as "numeric" | "2-digit" || '2-digit', second: this.getAttribute('data-format-second') as "numeric" | "2-digit" || '2-digit', timeZoneName: this.getAttribute('data-format-timeZoneName') as "short" | "long" | "shortOffset" | "longOffset" | "shortGeneric" | "longGeneric" || 'short', }; //short usage intl in component context this.intl =(propDateNames:string[], declension = false) => { let options = {}; for (let i = 0; i < propDateNames.length; i++) { const propDateName = propDateNames[i]; options[propDateName] = defaultIntDateOptions[propDateName]; if(declension) { if(propDateName === 'month') { options['day'] = defaultIntDateOptions['day']; return new Intl.DateTimeFormat([language, 'en'], options).format(this.Date).replace(/\d\d? /, ''); } } if(propDateName === 'era') { options['year'] = 'numeric'; } } return new Intl.DateTimeFormat([language, 'en'], options).format(this.Date); }
Проверяем, что получилось
data-format-month="numeric"
data-format-year="2-digit"
data-format-date="era,day,month,weekday"
<time is="wc-time" data-format-date="era,day,month,weekday" datetime="2012-05-22"></time>
data-format-time="minute,hour,second"
<time is="wc-time" data-view-format="time" data-format-date="minute,hour,second" datetime="2012-05-22"></time>
Наблюдаемые изменения за аттрибутом
Из коробки веб-компоненты дают нам возможность наблюдать за изменениями аттриубтов, грех не воспользоваться такой возможностью, добавим поведение на изменение нативного аттрибута datetime
у тега time
. Теперь можем посмотреть, как быстро летит время)
<time is="wc-time" id="timereact2" data-view-format="date,time,ago" data-format-date="era,day,month,weekday" data-format-time="minute,hour,second" data-view-format="date" datetime="2012-05-22"></time>
Пример простенького javascript изменяющего аттрибут, тут мог бы быть и ваш любимый frontend js фреймворк)
document.addEventListener('DOMContentLoaded', e => { let now = Date.now(); const timereact = document.getElementById('timereact'); const timereact2 = document.getElementById('timereact2'); setInterval(() => { now += 100001231; timereact.setAttribute('datetime', new Date(Date.now()).toISOString()); timereact2.setAttribute('datetime', new Date(now).toISOString()); }, 100) })
Рендерим wc-time в текстовом режиме
По умолчанию wc-time использует span для оборачивания частей текста в теги, это позволяет кастомизировать веб-компонент при помощи CSS но давайте добавим возможность указать data-as-string="1"
чтобы внутри тега рендерилась только строка
а что если к месту и элемент dialog не удобен, но подсказки хочется оставить без лишних dom элементов, добавим поддержку и такого варианта через data-css-hint="1"
Проверим, наведите курсор ->
Но пользователь все еще не может добавить свой текст как подсказку к дате в нашем всплывающем блоке, добавим и этот момент при помощи аттрибута data-date-hint="foo bar biz"
который мы будем prepend к нашей dialog\css подсказке
wc-likes
ссылки
p.s. Если вам понравился компонент и идея, поддержите звездочкой на гитхабе автора, спасибо)