@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.
@@ -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,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.44",
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.0",
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": "771621d06e1fa74693baba84ed3befd6f41df664"
33
+ "gitHead": "db6a4ba1aff1c34861146bc388ba888cb7e49450"
34
34
  }