shoelace/src/components/relative-time/relative-time.ts

126 wiersze
4.1 KiB
TypeScript
Czysty Zwykły widok Historia

import { LitElement, html } from 'lit';
import { customElement, property, state } from 'lit/decorators';
2021-03-18 13:04:23 +00:00
import { watch } from '../../internal/decorators';
2020-11-11 22:31:53 +00:00
/**
* @since 2.0
* @status stable
*/
2021-03-18 13:04:23 +00:00
@customElement('sl-relative-time')
2021-03-09 00:14:32 +00:00
export default class SlRelativeTime extends LitElement {
2021-02-26 14:09:13 +00:00
private updateTimeout: any;
2020-11-11 22:31:53 +00:00
@state() private isoTime = '';
@state() private relativeTime = '';
@state() private titleTime = '';
2021-03-06 17:01:39 +00:00
2020-11-11 22:31:53 +00:00
/** The date from which to calculate time from. */
2021-03-06 17:01:39 +00:00
@property() date: Date | string;
2020-11-11 22:31:53 +00:00
/** The locale to use when formatting the number. */
2021-03-06 17:01:39 +00:00
@property() locale: string;
2020-11-11 22:31:53 +00:00
/** The formatting style to use. */
2021-03-06 17:01:39 +00:00
@property() format: 'long' | 'short' | 'narrow' = 'long';
2020-11-11 22:31:53 +00:00
/**
* When `auto`, values such as "yesterday" and "tomorrow" will be shown when possible. When `always`, values such as
* "1 day ago" and "in 1 day" will be shown.
*/
2021-03-06 17:01:39 +00:00
@property() numeric: 'always' | 'auto' = 'auto';
2020-11-11 22:31:53 +00:00
2020-11-20 22:02:38 +00:00
/** Keep the displayed value up to date as time passes. */
2021-03-06 17:01:39 +00:00
@property({ type: Boolean }) sync = false;
2020-11-20 22:02:38 +00:00
2021-03-06 17:01:39 +00:00
firstUpdated() {
2020-11-20 22:02:38 +00:00
this.updateTime();
}
2021-03-06 17:01:39 +00:00
disconnectedCallback() {
super.disconnectedCallback();
2020-11-20 22:02:38 +00:00
clearTimeout(this.updateTimeout);
}
2021-03-06 19:39:48 +00:00
@watch('date')
@watch('locale')
@watch('format')
@watch('numeric')
@watch('sync')
2020-11-20 22:02:38 +00:00
updateTime() {
const now = new Date();
2020-11-11 22:31:53 +00:00
const date = new Date(this.date);
2020-11-20 22:02:38 +00:00
// Check for an invalid date
if (isNaN(date.getMilliseconds())) {
this.relativeTime = '';
this.isoTime = '';
return;
2020-11-11 22:31:53 +00:00
}
2020-11-20 22:02:38 +00:00
const diff = +date - +now;
const availableUnits = [
{ max: 2760000, value: 60000, unit: 'minute' }, // max 46 minutes
{ max: 72000000, value: 3600000, unit: 'hour' }, // max 20 hours
{ max: 518400000, value: 86400000, unit: 'day' }, // max 6 days
{ max: 2419200000, value: 604800000, unit: 'week' }, // max 28 days
{ max: 28512000000, value: 2592000000, unit: 'month' }, // max 11 months
{ max: Infinity, value: 31536000000, unit: 'year' }
];
2021-02-26 14:09:13 +00:00
const { unit, value } = availableUnits.find(unit => Math.abs(diff) < unit.max) as any;
2020-11-20 22:02:38 +00:00
this.isoTime = date.toISOString();
this.titleTime = new Intl.DateTimeFormat(this.locale, {
month: 'long',
year: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
timeZoneName: 'short'
}).format(date);
2020-11-11 22:31:53 +00:00
2020-11-20 22:02:38 +00:00
this.relativeTime = new Intl.RelativeTimeFormat(this.locale, {
numeric: this.numeric,
style: this.format
}).format(Math.round(diff / value), unit);
// If sync is enabled, update as time passes
clearTimeout(this.updateTimeout);
if (this.sync) {
// Calculates the number of milliseconds until the next respective unit changes. This ensures that all components
// update at the same time which is less distracting than updating independently.
const getTimeUntilNextUnit = (unit: 'second' | 'minute' | 'hour' | 'day') => {
const units = { second: 1000, minute: 60000, hour: 3600000, day: 86400000 };
const value = units[unit];
return value - (now.getTime() % value);
};
let nextInterval: number;
// NOTE: this could be optimized to determine when the next update should actually occur, but the size and cost of
// that logic probably isn't worth the performance benefit
if (unit === 'minute') {
nextInterval = getTimeUntilNextUnit('second');
} else if (unit === 'hour') {
nextInterval = getTimeUntilNextUnit('minute');
} else if (unit === 'day') {
nextInterval = getTimeUntilNextUnit('hour');
} else {
// Cap updates at once per day. It's unlikely a user will reach this value, plus setTimeout has a limit on the
// value it can accept. https://stackoverflow.com/a/3468650/567486
nextInterval = getTimeUntilNextUnit('day'); // next day
}
this.updateTimeout = setTimeout(() => this.updateTime(), nextInterval);
}
}
render() {
2021-02-26 14:09:13 +00:00
return html` <time datetime=${this.isoTime} title=${this.titleTime}>${this.relativeTime}</time> `;
2020-11-11 22:31:53 +00:00
}
}
2021-03-12 14:09:08 +00:00
declare global {
interface HTMLElementTagNameMap {
'sl-relative-time': SlRelativeTime;
}
}