@dile/utils 3.0.44 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/time-duration/__screenshots__/time-duration.spec.js/dile-time-duration-renders-0-seconds-with-smallest-allowed-unit-1.png +0 -0
- package/components/time-duration/__screenshots__/time-duration.spec.js/dile-time-duration-renders-80-seconds-as--1m-20s--1.png +0 -0
- package/components/time-duration/src/DileTimeDuration.js +191 -0
- package/components/time-duration/time-duration.js +5 -0
- package/components/time-duration/time-duration.spec.js +68 -0
- package/package.json +3 -3
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { html, css, LitElement } from "lit";
|
|
2
|
+
|
|
3
|
+
export class DileTimeDuration extends LitElement {
|
|
4
|
+
static properties = {
|
|
5
|
+
seconds: {
|
|
6
|
+
type: Number
|
|
7
|
+
},
|
|
8
|
+
precision: {
|
|
9
|
+
type: String
|
|
10
|
+
},
|
|
11
|
+
extended: {
|
|
12
|
+
type: Boolean
|
|
13
|
+
},
|
|
14
|
+
language: {
|
|
15
|
+
type: String
|
|
16
|
+
},
|
|
17
|
+
icon: {
|
|
18
|
+
type: String
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
constructor() {
|
|
23
|
+
super();
|
|
24
|
+
this.seconds = 0;
|
|
25
|
+
this.precision = "seconds"; // seconds, minutes, hours, days
|
|
26
|
+
this.extended = false;
|
|
27
|
+
this.language = "en";
|
|
28
|
+
this.icon = "lucide.timer";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
connectedCallback() {
|
|
32
|
+
super.connectedCallback();
|
|
33
|
+
// Dynamically import dile-iconlib when component connects
|
|
34
|
+
if (this.icon) {
|
|
35
|
+
import('@dile/iconlib/dile-iconlib.js').catch(() => {
|
|
36
|
+
// Icon library not available, component will still work without icons
|
|
37
|
+
});
|
|
38
|
+
// Import the default lucide.timer icon
|
|
39
|
+
if (this.icon === 'lucide.timer') {
|
|
40
|
+
import('@dile/iconlib/lucide-icons/timer.js').catch(() => {
|
|
41
|
+
// Icon not available, will render empty
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static get styles() {
|
|
48
|
+
return css`
|
|
49
|
+
:host {
|
|
50
|
+
display: inline-flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
gap: 0.5rem;
|
|
53
|
+
font-size: var(--dile-time-duration-font-size, 0.875rem);
|
|
54
|
+
color: var(--dile-time-duration-color, var(--dile-on-background-color, #303030));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.icon {
|
|
58
|
+
display: flex;
|
|
59
|
+
align-items: center;
|
|
60
|
+
--dile-icon-color: var(--dile-primary-color, #888);
|
|
61
|
+
--dile-icon-size: var(--dile-time-duration-icon-size, 20px);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.time-text {
|
|
65
|
+
display: inline;
|
|
66
|
+
font-weight: var(--dile-time-duration-font-weight, normal);
|
|
67
|
+
font-family: var(--dile-time-duration-font-family, inherit);
|
|
68
|
+
letter-spacing: var(--dile-time-duration-letter-spacing, 0);
|
|
69
|
+
}
|
|
70
|
+
`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
#getTranslations() {
|
|
74
|
+
const translations = {
|
|
75
|
+
en: {
|
|
76
|
+
day: { singular: 'day', plural: 'days' },
|
|
77
|
+
hour: { singular: 'hour', plural: 'hours' },
|
|
78
|
+
minute: { singular: 'minute', plural: 'minutes' },
|
|
79
|
+
second: { singular: 'second', plural: 'seconds' },
|
|
80
|
+
and: 'and'
|
|
81
|
+
},
|
|
82
|
+
es: {
|
|
83
|
+
day: { singular: 'día', plural: 'días' },
|
|
84
|
+
hour: { singular: 'hora', plural: 'horas' },
|
|
85
|
+
minute: { singular: 'minuto', plural: 'minutos' },
|
|
86
|
+
second: { singular: 'segundo', plural: 'segundos' },
|
|
87
|
+
and: 'y'
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
return translations[this.language] || translations.en;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#getUnitLabel(unit, value) {
|
|
94
|
+
const t = this.#getTranslations();
|
|
95
|
+
const labels = {
|
|
96
|
+
day: t.day,
|
|
97
|
+
hour: t.hour,
|
|
98
|
+
minute: t.minute,
|
|
99
|
+
second: t.second
|
|
100
|
+
};
|
|
101
|
+
const label = labels[unit];
|
|
102
|
+
return value === 1 ? label.singular : label.plural;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
#formatTime() {
|
|
106
|
+
if (!Number.isFinite(this.seconds) || this.seconds < 0) {
|
|
107
|
+
return '';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const units = [
|
|
111
|
+
{ name: 'day', seconds: 86400 },
|
|
112
|
+
{ name: 'hour', seconds: 3600 },
|
|
113
|
+
{ name: 'minute', seconds: 60 },
|
|
114
|
+
{ name: 'second', seconds: 1 }
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
const precisionLevels = {
|
|
118
|
+
days: ['day'],
|
|
119
|
+
hours: ['day', 'hour'],
|
|
120
|
+
minutes: ['day', 'hour', 'minute'],
|
|
121
|
+
seconds: ['day', 'hour', 'minute', 'second']
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const allowedUnits = precisionLevels[this.precision] || precisionLevels.minutes;
|
|
125
|
+
const parts = [];
|
|
126
|
+
let remaining = Math.floor(this.seconds);
|
|
127
|
+
|
|
128
|
+
for (const unit of units) {
|
|
129
|
+
if (!allowedUnits.includes(unit.name)) continue;
|
|
130
|
+
|
|
131
|
+
const value = Math.floor(remaining / unit.seconds);
|
|
132
|
+
if (value > 0) {
|
|
133
|
+
parts.push({ name: unit.name, value });
|
|
134
|
+
remaining -= value * unit.seconds;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// If everything is 0, show 0 of the smallest allowed unit
|
|
139
|
+
if (parts.length === 0) {
|
|
140
|
+
const smallestUnit = allowedUnits[allowedUnits.length - 1];
|
|
141
|
+
parts.push({ name: smallestUnit, value: 0 });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Keep only two parts
|
|
145
|
+
const displayParts = parts.slice(0, 2);
|
|
146
|
+
|
|
147
|
+
if (this.extended) {
|
|
148
|
+
return this.#formatExtended(displayParts);
|
|
149
|
+
} else {
|
|
150
|
+
return this.#formatCompact(displayParts);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
#formatCompact(parts) {
|
|
155
|
+
const unitAbbreviations = {
|
|
156
|
+
en: { day: 'd', hour: 'h', minute: 'm', second: 's' },
|
|
157
|
+
es: { day: 'd', hour: 'h', minute: 'm', second: 's' }
|
|
158
|
+
};
|
|
159
|
+
const abbr = unitAbbreviations[this.language] || unitAbbreviations.en;
|
|
160
|
+
|
|
161
|
+
return parts
|
|
162
|
+
.map(part => `${part.value}${abbr[part.name]}`)
|
|
163
|
+
.join(' ');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
#formatExtended(parts) {
|
|
167
|
+
const t = this.#getTranslations();
|
|
168
|
+
const formattedParts = parts.map(part =>
|
|
169
|
+
`${part.value} ${this.#getUnitLabel(part.name, part.value)}`
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
if (formattedParts.length === 1) {
|
|
173
|
+
return formattedParts[0];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return formattedParts.join(` ${t.and} `);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
render() {
|
|
180
|
+
const formattedTime = this.#formatTime();
|
|
181
|
+
|
|
182
|
+
return html`
|
|
183
|
+
${this.icon ? html`
|
|
184
|
+
<div class="icon">
|
|
185
|
+
<dile-iconlib icon="${this.icon}"></dile-iconlib>
|
|
186
|
+
</div>
|
|
187
|
+
` : ''}
|
|
188
|
+
<span class="time-text">${formattedTime}</span>
|
|
189
|
+
`;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
+
import './time-duration.js';
|
|
3
|
+
|
|
4
|
+
describe('dile-time-duration', () => {
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
document.body.innerHTML = '';
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
async function renderTimeDuration(html) {
|
|
10
|
+
document.body.innerHTML = html;
|
|
11
|
+
const el = document.body.querySelector('dile-time-duration');
|
|
12
|
+
await el.updateComplete;
|
|
13
|
+
return el;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
it('renders 80 seconds as "1m 20s"', async () => {
|
|
17
|
+
const el = await renderTimeDuration('<dile-time-duration seconds="80"></dile-time-duration>');
|
|
18
|
+
const timeText = el.shadowRoot.querySelector('.time-text').textContent;
|
|
19
|
+
expect(timeText).toContain('1m');
|
|
20
|
+
expect(timeText).toContain('20s');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('renders 3665 seconds as "1h 1m"', async () => {
|
|
24
|
+
const el = await renderTimeDuration('<dile-time-duration seconds="3665"></dile-time-duration>');
|
|
25
|
+
const timeText = el.shadowRoot.querySelector('.time-text').textContent;
|
|
26
|
+
expect(timeText).toContain('1h');
|
|
27
|
+
expect(timeText).toContain('1m');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('renders with extended format', async () => {
|
|
31
|
+
const el = await renderTimeDuration('<dile-time-duration seconds="3665" extended></dile-time-duration>');
|
|
32
|
+
const timeText = el.shadowRoot.querySelector('.time-text').textContent;
|
|
33
|
+
expect(timeText).toContain('hour');
|
|
34
|
+
expect(timeText).toContain('minute');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('renders with Spanish language', async () => {
|
|
38
|
+
const el = await renderTimeDuration('<dile-time-duration seconds="3665" extended language="es"></dile-time-duration>');
|
|
39
|
+
const timeText = el.shadowRoot.querySelector('.time-text').textContent;
|
|
40
|
+
expect(timeText).toContain('hora');
|
|
41
|
+
expect(timeText).toContain('minuto');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('renders with days', async () => {
|
|
45
|
+
const el = await renderTimeDuration('<dile-time-duration seconds="90061"></dile-time-duration>');
|
|
46
|
+
const timeText = el.shadowRoot.querySelector('.time-text').textContent;
|
|
47
|
+
expect(timeText).toContain('1d');
|
|
48
|
+
expect(timeText).toContain('1h');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('respects precision attribute', async () => {
|
|
52
|
+
const el = await renderTimeDuration('<dile-time-duration seconds="80" precision="seconds"></dile-time-duration>');
|
|
53
|
+
const timeText = el.shadowRoot.querySelector('.time-text').textContent;
|
|
54
|
+
expect(timeText).toContain('1m');
|
|
55
|
+
expect(timeText).toContain('20s');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('renders 0 seconds with smallest allowed unit', async () => {
|
|
59
|
+
const el = await renderTimeDuration('<dile-time-duration seconds="0"></dile-time-duration>');
|
|
60
|
+
const timeText = el.shadowRoot.querySelector('.time-text').textContent;
|
|
61
|
+
expect(timeText).toContain('0s');
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('displays custom icon', async () => {
|
|
65
|
+
const el = await renderTimeDuration('<dile-time-duration seconds="80" icon="material.timer"></dile-time-duration>');
|
|
66
|
+
expect(el.shadowRoot.querySelector('dile-iconlib').getAttribute('icon')).toBe('material.timer');
|
|
67
|
+
});
|
|
68
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dile/utils",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Utility Components of Diverse Uses Based on the Lit Library and Web Components Standard.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"homepage": "https://dile-components.com/",
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@dile/iconlib": "^2.0.0",
|
|
24
|
-
"@dile/ui": "^3.9.
|
|
24
|
+
"@dile/ui": "^3.9.1",
|
|
25
25
|
"@lion/ui": "^0.21.0",
|
|
26
26
|
"linkify-string": "^4.1.3",
|
|
27
27
|
"linkifyjs": "^4.1.3",
|
|
@@ -30,5 +30,5 @@
|
|
|
30
30
|
"publishConfig": {
|
|
31
31
|
"access": "public"
|
|
32
32
|
},
|
|
33
|
-
"gitHead": "
|
|
33
|
+
"gitHead": "db6a4ba1aff1c34861146bc388ba888cb7e49450"
|
|
34
34
|
}
|