@dile/utils 3.0.45 → 3.1.1

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.
@@ -0,0 +1,177 @@
1
+ import { html, css, LitElement } from "lit";
2
+ import '@dile/iconlib/dile-iconlib.js';
3
+ import '@dile/iconlib/lucide-icons/timer.js';
4
+
5
+ export class DileTimeDuration extends LitElement {
6
+ static properties = {
7
+ seconds: {
8
+ type: Number
9
+ },
10
+ precision: {
11
+ type: String
12
+ },
13
+ extended: {
14
+ type: Boolean
15
+ },
16
+ language: {
17
+ type: String
18
+ },
19
+ icon: {
20
+ type: String
21
+ }
22
+ };
23
+
24
+ constructor() {
25
+ super();
26
+ this.seconds = 0;
27
+ this.precision = "seconds"; // seconds, minutes, hours, days
28
+ this.extended = false;
29
+ this.language = "en";
30
+ this.icon = "lucide.timer";
31
+ }
32
+
33
+ static get styles() {
34
+ return css`
35
+ :host {
36
+ display: inline-flex;
37
+ align-items: center;
38
+ gap: 0.5rem;
39
+ font-size: var(--dile-time-duration-font-size, 0.875rem);
40
+ color: var(--dile-time-duration-color, var(--dile-on-background-color, #303030));
41
+ }
42
+
43
+ .icon {
44
+ display: flex;
45
+ align-items: center;
46
+ --dile-icon-color: var(--dile-primary-color, #888);
47
+ --dile-icon-size: var(--dile-time-duration-icon-size, 20px);
48
+ }
49
+
50
+ .time-text {
51
+ display: inline;
52
+ font-weight: var(--dile-time-duration-font-weight, normal);
53
+ font-family: var(--dile-time-duration-font-family, inherit);
54
+ letter-spacing: var(--dile-time-duration-letter-spacing, 0);
55
+ }
56
+ `;
57
+ }
58
+
59
+ #getTranslations() {
60
+ const translations = {
61
+ en: {
62
+ day: { singular: 'day', plural: 'days' },
63
+ hour: { singular: 'hour', plural: 'hours' },
64
+ minute: { singular: 'minute', plural: 'minutes' },
65
+ second: { singular: 'second', plural: 'seconds' },
66
+ and: 'and'
67
+ },
68
+ es: {
69
+ day: { singular: 'día', plural: 'días' },
70
+ hour: { singular: 'hora', plural: 'horas' },
71
+ minute: { singular: 'minuto', plural: 'minutos' },
72
+ second: { singular: 'segundo', plural: 'segundos' },
73
+ and: 'y'
74
+ }
75
+ };
76
+ return translations[this.language] || translations.en;
77
+ }
78
+
79
+ #getUnitLabel(unit, value) {
80
+ const t = this.#getTranslations();
81
+ const labels = {
82
+ day: t.day,
83
+ hour: t.hour,
84
+ minute: t.minute,
85
+ second: t.second
86
+ };
87
+ const label = labels[unit];
88
+ return value === 1 ? label.singular : label.plural;
89
+ }
90
+
91
+ #formatTime() {
92
+ if (!Number.isFinite(this.seconds) || this.seconds < 0) {
93
+ return '';
94
+ }
95
+
96
+ const units = [
97
+ { name: 'day', seconds: 86400 },
98
+ { name: 'hour', seconds: 3600 },
99
+ { name: 'minute', seconds: 60 },
100
+ { name: 'second', seconds: 1 }
101
+ ];
102
+
103
+ const precisionLevels = {
104
+ days: ['day'],
105
+ hours: ['day', 'hour'],
106
+ minutes: ['day', 'hour', 'minute'],
107
+ seconds: ['day', 'hour', 'minute', 'second']
108
+ };
109
+
110
+ const allowedUnits = precisionLevels[this.precision] || precisionLevels.minutes;
111
+ const parts = [];
112
+ let remaining = Math.floor(this.seconds);
113
+
114
+ for (const unit of units) {
115
+ if (!allowedUnits.includes(unit.name)) continue;
116
+
117
+ const value = Math.floor(remaining / unit.seconds);
118
+ if (value > 0) {
119
+ parts.push({ name: unit.name, value });
120
+ remaining -= value * unit.seconds;
121
+ }
122
+ }
123
+
124
+ // If everything is 0, show 0 of the smallest allowed unit
125
+ if (parts.length === 0) {
126
+ const smallestUnit = allowedUnits[allowedUnits.length - 1];
127
+ parts.push({ name: smallestUnit, value: 0 });
128
+ }
129
+
130
+ // Keep only two parts
131
+ const displayParts = parts.slice(0, 2);
132
+
133
+ if (this.extended) {
134
+ return this.#formatExtended(displayParts);
135
+ } else {
136
+ return this.#formatCompact(displayParts);
137
+ }
138
+ }
139
+
140
+ #formatCompact(parts) {
141
+ const unitAbbreviations = {
142
+ en: { day: 'd', hour: 'h', minute: 'm', second: 's' },
143
+ es: { day: 'd', hour: 'h', minute: 'm', second: 's' }
144
+ };
145
+ const abbr = unitAbbreviations[this.language] || unitAbbreviations.en;
146
+
147
+ return parts
148
+ .map(part => `${part.value}${abbr[part.name]}`)
149
+ .join(' ');
150
+ }
151
+
152
+ #formatExtended(parts) {
153
+ const t = this.#getTranslations();
154
+ const formattedParts = parts.map(part =>
155
+ `${part.value} ${this.#getUnitLabel(part.name, part.value)}`
156
+ );
157
+
158
+ if (formattedParts.length === 1) {
159
+ return formattedParts[0];
160
+ }
161
+
162
+ return formattedParts.join(` ${t.and} `);
163
+ }
164
+
165
+ render() {
166
+ const formattedTime = this.#formatTime();
167
+
168
+ return html`
169
+ ${this.icon ? html`
170
+ <div class="icon">
171
+ <dile-iconlib icon="${this.icon}"></dile-iconlib>
172
+ </div>
173
+ ` : ''}
174
+ <span class="time-text">${formattedTime}</span>
175
+ `;
176
+ }
177
+ }
@@ -0,0 +1,5 @@
1
+ import { DileTimeDuration } from './src/DileTimeDuration.js';
2
+
3
+ if (!customElements.get('dile-time-duration')) {
4
+ window.customElements.define('dile-time-duration', DileTimeDuration);
5
+ }
@@ -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.45",
3
+ "version": "3.1.1",
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": {
@@ -30,5 +30,5 @@
30
30
  "publishConfig": {
31
31
  "access": "public"
32
32
  },
33
- "gitHead": "9e326dc3d88fc6a0154d7e8833034469aae9c8d1"
33
+ "gitHead": "d2de3865921d48f4c1973ba8f1afae8c74e4d68d"
34
34
  }