version 1.1.0

This commit is contained in:
David Ali
2026-01-06 01:50:20 +01:00
parent f9e2615d11
commit 2451b1a9e7
8 changed files with 153 additions and 88 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
e2e/cypress/screenshots/* e2e/cypress/screenshots/*
*.old

View File

@@ -1,13 +0,0 @@
# Ignoruj narzędzia CI/CD
Jenkinsfile
Dockerfile
docker-compose.test.yml
.git
.gitignore
# Ignoruj testy i demo
e2e/
public/
# Ignoruj konfigurację IDE
.vscode/

23
CHANGELOG.md Normal file
View File

@@ -0,0 +1,23 @@
# CHANGELOG
## [1.1.0] : Refactor and Optimization
### Performance
* **IntlCache**: Implemented caching for `Intl` objects. Date formatting no longer creates new instances on every refresh.
* **Shared Styles**: Used `adoptedStyleSheets` instead of `<style>` tags. The browser parses CSS only once.
* **Date Math**: Removed `while` loops for year/month calculations. Replaced them with faster O(1) mathematical formulas.
### Stability and UI
* **Layout Shift (CLS)**: Fixed content jumping on load. The component now has a default `min-height`.
* **Initialization**: Added a non-breaking space (`\u00A0`) at start. This prevents zero height before the first render.
* **TickHub Safety**: Iteration over subscribers now uses a copy. An error in one timer will not stop others.
### Code Quality
* **Private Fields**: Changed `_variable` convention to native private fields `#variable`. Internal state is now fully protected.
* **Attribute Handling**: Improved attribute update logic (`#isAttrUpdate`). This eliminates unnecessary render cycles.
* **Auto-restart**: The stopped timer restarts automatically if the target date changes to the future.
* **Fallback**: Added a safety check returning `00:00:00` if formatting results in an empty string.
## [1.0.1] : Added README.md
## [1.0.0] : First version

View File

@@ -7,6 +7,7 @@ WORKDIR /app/wc-timer
# Kopiowanie plików manifestu i instalacja zależności # Kopiowanie plików manifestu i instalacja zależności
COPY package.json ./ COPY package.json ./
COPY README.md ./ COPY README.md ./
COPY CHANGELOG.md ./
RUN npm install RUN npm install
# Kopiowanie reszty plików projektu (kod źródłowy) # Kopiowanie reszty plików projektu (kod źródłowy)

6
Jenkinsfile vendored
View File

@@ -68,10 +68,10 @@ pipeline {
} }
if (versionExists) { if (versionExists) {
echo " BŁĄD: Wersja ${localVersion} jest już opublikowana w NPM!" echo "[ERROR] BŁĄD: Wersja ${localVersion} jest już opublikowana w NPM!"
error("Przerwano publikację: Wersja ${localVersion} już istnieje.") error("Przerwano publikację: Wersja ${localVersion} już istnieje.")
} else { } else {
echo "🚀 Wersja ${localVersion} jest nowa. Publikuję..." echo "[OK] Wersja ${localVersion} jest nowa. Publikuję..."
sh """ sh """
docker compose -f docker-compose.test.yml run --rm \ docker compose -f docker-compose.test.yml run --rm \
@@ -81,7 +81,7 @@ pipeline {
sh -c "echo '//registry.npmjs.org/:_authToken=\${NPM_TOKEN}' > .npmrc && npm publish --access public" sh -c "echo '//registry.npmjs.org/:_authToken=\${NPM_TOKEN}' > .npmrc && npm publish --access public"
""" """
echo " SUKCES: Wersja ${localVersion} została opublikowana." echo "[OK] SUKCES: Wersja ${localVersion} została opublikowana."
} }
} }
} }

View File

@@ -1,14 +1,14 @@
# wc-timer # wc-timer
A lightweight, dependency-free Web Component for countdowns and elapsed time. It uses the native `Intl` API for formatting and supports automatic language detection. A high-performance, dependency-free Web Component for countdowns, elapsed time, and static dates. It uses a shared ticker and cached `Intl` formatters for maximum efficiency.
## Features ## Features
* **Zero Dependencies**: Built with standard Web Components API. * **High Performance**: Uses a shared global ticker (TickHub) to sync all instances. It calculates dates using O(1) math and caches `Intl` objects to save memory.
* **Smart Formatting**: Supports various presets like `hms`, `dhms`, or `ydhms`. * **Smart Formatting**: Supports `Intl.DurationFormat` with a robust fallback for older browsers.
* **Performance**: Uses a shared global ticker (TickHub) to sync all instances and save CPU. * **Versatile Presets**: Choose from `hms`, `dhms`, `ydhms`, `auto`, or static `date`.
* **Light DOM**: Renders text directly, making it easy to style with global CSS. * **Light DOM**: Renders as a standard Text Node. The text is selectable and inherits global CSS styles easily.
* **Responsive**: Automatically updates when attributes change. * **Auto-Restart**: If you update the target to a future date, the timer restarts automatically.
## Installation ## Installation
@@ -23,24 +23,26 @@ npm install wc-timer
```javascript ```javascript
import 'wc-timer'; import 'wc-timer';
// Or via script tag // Or via script tag:
// <script type="module" src="path/to/wc-timer.js"></script> // <script type="module" src="wc-timer.js"></script>
``` ```
2. **Use the tag in HTML**: 2. **Use in HTML**:
```html ```html
<wc-timer target="2025-12-31T23:59:59Z"></wc-timer> <wc-timer target="2026-01-01T00:00:00Z"></wc-timer>
<wc-timer <wc-timer
target="2026-01-01T00:00:00Z" target="2026-06-01T12:00:00Z"
preset="dhms" preset="dhms"
labels="short" labels="long"
compact compact
locale="en-US"> locale="pl">
</wc-timer> </wc-timer>
<wc-timer preset="date" target="2025-12-31"></wc-timer>
``` ```
## API Reference ## API Reference
@@ -49,32 +51,60 @@ import 'wc-timer';
| Attribute | Type | Default | Description | | Attribute | Type | Default | Description |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `target` | `ISO 8601` | `now` | The target date/time (e.g., `2025-12-31T23:59:59Z`). | | `target` | `string` | `now` | The target date (ISO 8601 string or Date compatible). |
| `preset` | `string` | `auto` | Format preset: `auto`, `hms`, `dhms`, `ydhms`, `date`. | | `preset` | `string` | `auto` | formatting mode: `auto`, `hms`, `dhms`, `ydhms`, `date`. |
| `labels` | `string` | `long` | Unit labels: `none` (00:00:00), `short` (1h 5m), `long` (1 hour 5 minutes). | | `labels` | `string` | `short` | unit labels: `none` (00:00:00), `short` (1h 5m), `long` (1 hour 5 minutes). |
| `compact` | `boolean` | `false` | If present, hides zero-value units (e.g., skips "0 years"). | | `compact` | `boolean` | `false` | if present, hides zero-value high units (e.g. hides "0 years"). |
| `locale` | `BCP 47` | `auto` | Forces a specific language (e.g., `pl`, `en`, `de`). Defaults to browser settings. | | `locale` | `string` | `auto` | overrides browser language (e.g. `pl`, `en-US`). |
### Presets Details
* **auto**: shows `HH:MM:SS`. If duration > 30 days, it adds days.
* **hms**: hours, minutes, seconds.
* **dhms**: days, hours, minutes, seconds.
* **ydhms**: years, months, days, hours, minutes, seconds.
* **date**: static date formatted via `Intl.DateTimeFormat`.
### JavaScript Properties ### JavaScript Properties
All attributes are reflected as properties on the DOM element. You can control the element programmatically.
```javascript ```javascript
const el = document.querySelector('wc-timer'); const el = document.querySelector('wc-timer');
// Update target dynamically // Update target (accepts Date object, string, or timestamp)
el.target = new Date('2030-01-01'); el.target = new Date('2030-01-01');
// Change configuration // Change settings on the fly
el.preset = 'ydhms'; el.preset = 'ydhms';
el.compact = true; el.compact = true;
el.labels = 'long';
```
## Styling
Since `wc-timer` uses Light DOM, you can style it with standard CSS.
```css
wc-timer {
font-family: 'Courier New', monospace;
font-weight: bold;
color: #2563eb;
}
``` ```
## Browser Support ## Browser Support
Works in all modern browsers supporting Web Components and `Intl.DurationFormat` (or falls back gracefully). Works in all modern browsers. It uses `Intl.DurationFormat` if available. Otherwise, it falls back to a custom formatter using `Intl.PluralRules`.
## License ## License
MIT Copyright 2026 Dávid Ali
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,6 +1,6 @@
{ {
"name": "wc-timer", "name": "wc-timer",
"version": "1.0.1", "version": "1.1.0",
"description": "A lightweight, dependency-free countdown timer Web Component.", "description": "A lightweight, dependency-free countdown timer Web Component.",
"type": "module", "type": "module",
"main": "src/wc-timer.js", "main": "src/wc-timer.js",
@@ -9,6 +9,7 @@
}, },
"files": [ "files": [
"src/wc-timer.js", "src/wc-timer.js",
"CHANGELOG.md",
"README.md" "README.md"
], ],
"scripts": { "scripts": {

View File

@@ -1,16 +1,18 @@
/** /**
* wc-timer.js — Web Component (Refactored) * wc-timer
* @version 1.1.0
* @description A lightweight, efficient countdown/uptimer Web Component.
* @license MIT
*/ */
/* ---------------- Memoizacja Intl ---------------- /**
* Obiekty Intl są kosztowne. Tworzymy je raz i używamy wielokrotnie. * Internal cache for Intl objects to optimize performance.
* Kluczem cache jest locale i zserializowane opcje. * @private
*/ */
const IntlCache = (() => { const IntlCache = (() => {
const cache = new Map(); const cache = new Map();
function get(Ctor, locale, opts = {}) { function get(Ctor, locale, opts = {}) {
// Tworzymy unikalny klucz dla kombinacji konstruktora, języka i opcji.
const key = `${Ctor.name}:${locale}:${JSON.stringify(opts)}`; const key = `${Ctor.name}:${locale}:${JSON.stringify(opts)}`;
if (!cache.has(key)) { if (!cache.has(key)) {
cache.set(key, new Ctor(locale, opts)); cache.set(key, new Ctor(locale, opts));
@@ -22,12 +24,14 @@ const IntlCache = (() => {
dtf: (loc, opts) => get(Intl.DateTimeFormat, loc, opts), dtf: (loc, opts) => get(Intl.DateTimeFormat, loc, opts),
pr: (loc) => get(Intl.PluralRules, loc), pr: (loc) => get(Intl.PluralRules, loc),
df: (loc, opts) => get(Intl.DurationFormat, loc, opts), df: (loc, opts) => get(Intl.DurationFormat, loc, opts),
// Sprawdzamy wsparcie tylko raz
hasIDF: typeof Intl !== 'undefined' && 'DurationFormat' in Intl hasIDF: typeof Intl !== 'undefined' && 'DurationFormat' in Intl
}; };
})(); })();
/* ---------------- Global synced tick ---------------- */ /**
* Centralized ticker to sync all instances and prevent drift.
* @private
*/
const TickHub = (() => { const TickHub = (() => {
const subs = new Set(); const subs = new Set();
let timer = null; let timer = null;
@@ -35,12 +39,10 @@ const TickHub = (() => {
function scheduleNext() { function scheduleNext() {
if (subs.size === 0) return; if (subs.size === 0) return;
const now = performance.now(); const now = performance.now();
// Wyrównanie do pełnej sekundy
const msToNext = 1000 - (Math.floor(now) % 1000); const msToNext = 1000 - (Math.floor(now) % 1000);
timer = setTimeout(() => { timer = setTimeout(() => {
const t = Date.now(); const t = Date.now();
// Kopiujemy Set, aby uniknąć problemów przy dodawaniu/usuwaniu w trakcie pętli
for (const cb of [...subs]) { for (const cb of [...subs]) {
try { cb(t); } catch (e) { console.error(e); } try { cb(t); } catch (e) { console.error(e); }
} }
@@ -63,7 +65,7 @@ const TickHub = (() => {
}; };
})(); })();
/* ---------------- Locale & Helpers ---------------- */ /* ---------------- Locale Helpers ---------------- */
const LABELS = { const LABELS = {
en: { en: {
y: { long: { one: 'year', other: 'years' }, short: 'y' }, y: { long: { one: 'year', other: 'years' }, short: 'y' },
@@ -89,9 +91,8 @@ function resolveLocale(el) {
return (navigator.language || 'en').toLowerCase(); return (navigator.language || 'en').toLowerCase();
} }
/* ---------------- Algorytmy dat ---------------- */ /* ---------------- Date Logic ---------------- */
// Obliczanie różnicy kalendarzowej bez pętli (O(1))
function diffCalendar(a, b, wantY, wantM) { function diffCalendar(a, b, wantY, wantM) {
let anchor = new Date(a); let anchor = new Date(a);
let y = 0; let y = 0;
@@ -99,7 +100,6 @@ function diffCalendar(a, b, wantY, wantM) {
if (wantY) { if (wantY) {
y = b.getFullYear() - anchor.getFullYear(); y = b.getFullYear() - anchor.getFullYear();
// Sprawdzamy, czy "przeskoczyliśmy" datę końcową
const probe = new Date(anchor); const probe = new Date(anchor);
probe.setFullYear(anchor.getFullYear() + y); probe.setFullYear(anchor.getFullYear() + y);
if (probe > b) y--; if (probe > b) y--;
@@ -107,7 +107,6 @@ function diffCalendar(a, b, wantY, wantM) {
} }
if (wantM) { if (wantM) {
// Różnica w miesiącach całkowitych
M = (b.getFullYear() - anchor.getFullYear()) * 12 + (b.getMonth() - anchor.getMonth()); M = (b.getFullYear() - anchor.getFullYear()) * 12 + (b.getMonth() - anchor.getMonth());
const probe = new Date(anchor); const probe = new Date(anchor);
probe.setMonth(anchor.getMonth() + M); probe.setMonth(anchor.getMonth() + M);
@@ -120,8 +119,6 @@ function diffCalendar(a, b, wantY, wantM) {
function collapseForUnits(start, end, show) { function collapseForUnits(start, end, show) {
const { y, M, anchor } = diffCalendar(start, end, !!show.y, !!show.M); const { y, M, anchor } = diffCalendar(start, end, !!show.y, !!show.M);
// Jeśli liczyliśmy Y/M, resztę liczymy od 'anchor'. Jeśli nie, od 'start'.
const base = (show.y || show.M) ? anchor : start; const base = (show.y || show.M) ? anchor : start;
let rest = Math.max(0, end - base); let rest = Math.max(0, end - base);
@@ -136,11 +133,10 @@ function collapseForUnits(start, end, show) {
return res; return res;
} }
/* ---------------- Formatter ---------------- */ /* ---------------- Formatting ---------------- */
const pad2 = (n) => String(n ?? 0).padStart(2, '0'); const pad2 = (n) => String(n ?? 0).padStart(2, '0');
function formatDurationSmart(locale, labelsMode, show, parts) { function formatDurationSmart(locale, labelsMode, show, parts) {
// Tryb: none (czysty tekst z dwukropkami)
if (labelsMode === 'none') { if (labelsMode === 'none') {
const head = []; const head = [];
if (show.y) head.push(parts.years); if (show.y) head.push(parts.years);
@@ -148,28 +144,25 @@ function formatDurationSmart(locale, labelsMode, show, parts) {
if (show.d) head.push(parts.days); if (show.d) head.push(parts.days);
const hms = [pad2(parts.hours), pad2(parts.minutes), pad2(parts.seconds)]; const hms = [pad2(parts.hours), pad2(parts.minutes), pad2(parts.seconds)];
if (!show.h) hms.shift(); // jeśli ukryte godziny, usuń if (!show.h) hms.shift();
return [...head, hms.join(':')].join(' '); return [...head, hms.join(':')].join(' ');
} }
// Tryb: Intl.DurationFormat (jeśli dostępny)
if (IntlCache.hasIDF) { if (IntlCache.hasIDF) {
const opts = { style: labelsMode === 'short' ? 'short' : 'long' }; const opts = { style: labelsMode === 'short' ? 'short' : 'long' };
if (show.h) opts.hours = '2-digit'; if (show.h) opts.hours = '2-digit';
if (show.m) opts.minutes = '2-digit'; if (show.m) opts.minutes = '2-digit';
if (show.s) opts.seconds = '2-digit'; if (show.s) opts.seconds = '2-digit';
// Przekazujemy tylko niezerowe/wymagane jednostki
const inParts = {}; const inParts = {};
for (const key in parts) { for (const key in parts) {
// Mapowanie kluczy (years -> years) jest 1:1 w tym przypadku
if (show[key.slice(0, 1)] || show[key.slice(0, 2)]) inParts[key] = parts[key]; if (show[key.slice(0, 1)] || show[key.slice(0, 2)]) inParts[key] = parts[key];
} }
return IntlCache.df(locale, opts).format(inParts); return IntlCache.df(locale, opts).format(inParts);
} }
// Tryb: Fallback (słownikowy) // Fallback
const lang2 = LABELS[locale.slice(0, 2)] ? locale.slice(0, 2) : 'en'; const lang2 = LABELS[locale.slice(0, 2)] ? locale.slice(0, 2) : 'en';
const L = LABELS[lang2]; const L = LABELS[lang2];
const pr = IntlCache.pr(locale); const pr = IntlCache.pr(locale);
@@ -201,14 +194,13 @@ function getShowConfig(preset, diffMs) {
case 'dhms': return base; case 'dhms': return base;
case 'ydhms': return { y: true, M: true, d: true, h: true, m: true, s: true }; case 'ydhms': return { y: true, M: true, d: true, h: true, m: true, s: true };
case 'auto': case 'auto':
// Jeśli więcej niż 30 dni, pokaż dni.
return diffMs >= 2592000000 ? base : { ...base, d: false }; return diffMs >= 2592000000 ? base : { ...base, d: false };
default: return base; default: return base;
} }
} }
/* ---------------- Web Component ---------------- */ /* ---------------- Web Component ---------------- */
// Style współdzielone (wydajność pamięci)
const sheet = new CSSStyleSheet(); const sheet = new CSSStyleSheet();
sheet.replaceSync(` sheet.replaceSync(`
:host { :host {
@@ -218,13 +210,26 @@ sheet.replaceSync(`
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
white-space: nowrap; white-space: nowrap;
min-height: 1.2em;
line-height: 1.2;
} }
`); `);
/**
* A versatile countdown/uptimer Web Component that renders as selectable text.
* @element wc-timer
* @attr {string} target - The target date/time (ISO string or anything Date.parse accepts).
* @attr {string} [preset="auto"] - Display configuration ('auto', 'hms', 'dhms', 'ydhms', 'date').
* @attr {string} [labels="short"] - Label style ('none', 'short', 'long').
* @attr {boolean} [compact] - If set, hides high-order units that are zero.
* @attr {string} [locale] - Force a specific locale (e.g. 'pl', 'en-US').
* @example
* <wc-timer target="2025-01-01T00:00:00Z"></wc-timer>
* <wc-timer preset="dhms" labels="long" compact></wc-timer>
*/
class WcTimer extends HTMLElement { class WcTimer extends HTMLElement {
static get observedAttributes() { return ['target', 'preset', 'labels', 'locale', 'lang', 'compact']; } static get observedAttributes() { return ['target', 'preset', 'labels', 'locale', 'lang', 'compact']; }
// Pola prywatne
#target = new Date(); #target = new Date();
#preset = 'auto'; #preset = 'auto';
#labels = 'short'; #labels = 'short';
@@ -242,10 +247,10 @@ class WcTimer extends HTMLElement {
} }
connectedCallback() { connectedCallback() {
// Inicjalizacja węzła tekstowego w Light DOM // Ensures element has dimensions before JS runs (CLS prevention)
if (!this.firstChild || this.firstChild.nodeType !== Node.TEXT_NODE) { if (!this.firstChild || this.firstChild.nodeType !== Node.TEXT_NODE) {
this.textContent = ''; this.textContent = '';
this.#textNode = document.createTextNode(''); this.#textNode = document.createTextNode('\u00A0');
this.appendChild(this.#textNode); this.appendChild(this.#textNode);
} else { } else {
this.#textNode = this.firstChild; this.#textNode = this.firstChild;
@@ -262,9 +267,7 @@ class WcTimer extends HTMLElement {
attributeChangedCallback() { attributeChangedCallback() {
if (this.#isAttrUpdate) return; if (this.#isAttrUpdate) return;
this.#syncAttrs(); this.#syncAttrs();
// Renderujemy natychmiast po zmianie atrybutu
this.#render(Date.now()); this.#render(Date.now());
// Jeśli zmieniono cel na przyszły, upewnij się, że zegar tyka
this.#checkRestart(); this.#checkRestart();
} }
@@ -294,7 +297,6 @@ class WcTimer extends HTMLElement {
} }
#checkRestart() { #checkRestart() {
// Jeśli mamy przyszły target, a zegar stoi -> wznów go
const now = Date.now(); const now = Date.now();
if (this.#target > now && !this.#unsub) { if (this.#target > now && !this.#unsub) {
this.#startTicking(); this.#startTicking();
@@ -304,7 +306,6 @@ class WcTimer extends HTMLElement {
#render(nowMs) { #render(nowMs) {
if (!this.#textNode) return; if (!this.#textNode) return;
// Tryb daty statycznej
if (this.#preset === 'date') { if (this.#preset === 'date') {
const dtf = IntlCache.dtf(this.#locale, { const dtf = IntlCache.dtf(this.#locale, {
year: 'numeric', month: '2-digit', day: '2-digit', year: 'numeric', month: '2-digit', day: '2-digit',
@@ -320,7 +321,6 @@ class WcTimer extends HTMLElement {
const diff = this.#target - now; const diff = this.#target - now;
const isFuture = diff >= 0; const isFuture = diff >= 0;
// Auto-stop, jeśli minęliśmy czas
if (!isFuture && this.#unsub) { if (!isFuture && this.#unsub) {
this.#stopTicking(); this.#stopTicking();
} }
@@ -330,24 +330,30 @@ class WcTimer extends HTMLElement {
let show = getShowConfig(this.#preset, Math.abs(diff)); let show = getShowConfig(this.#preset, Math.abs(diff));
// Logika compact (ukrywanie zerowych jednostek wysokiego rzędu)
if (this.#compact) { if (this.#compact) {
const probe = collapseForUnits(start, end, show); const probe = collapseForUnits(start, end, show);
if (show.y && probe.years === 0) show.y = false; if (show.y && probe.years === 0) show.y = false;
if (show.M && !show.y && probe.months === 0) show.M = false; if (show.M && !show.y && probe.months === 0) show.M = false;
if (show.d && !show.y && !show.M && probe.days === 0) show.d = false; if (show.d && !show.y && !show.M && probe.days === 0) show.d = false;
// Przelicz ponownie po redukcji jednostek
const probe2 = collapseForUnits(start, end, show); const probe2 = collapseForUnits(start, end, show);
if (show.d && show.h && probe2.hours === 0) show.h = false; if (show.d && show.h && probe2.hours === 0) show.h = false;
} }
const parts = collapseForUnits(start, end, show); const parts = collapseForUnits(start, end, show);
const txt = formatDurationSmart(this.#locale, this.#labels, show, parts);
let txt = formatDurationSmart(this.#locale, this.#labels, show, parts);
if (!txt) txt = '00:00:00';
if (this.#textNode.data !== txt) this.#textNode.data = txt; if (this.#textNode.data !== txt) this.#textNode.data = txt;
} }
/* Public API */ /* Public API */
/**
* The target date object.
* @type {Date}
*/
get target() { return new Date(this.#target); } get target() { return new Date(this.#target); }
set target(val) { set target(val) {
const d = new Date(val); const d = new Date(val);
@@ -358,6 +364,10 @@ class WcTimer extends HTMLElement {
this.#checkRestart(); this.#checkRestart();
} }
/**
* Current preset configuration.
* @type {string} - 'auto' | 'hms' | 'dhms' | 'ydhms' | 'date'
*/
get preset() { return this.#preset; } get preset() { return this.#preset; }
set preset(val) { set preset(val) {
this.#preset = String(val).toLowerCase(); this.#preset = String(val).toLowerCase();
@@ -365,6 +375,10 @@ class WcTimer extends HTMLElement {
this.#render(Date.now()); this.#render(Date.now());
} }
/**
* Label display mode.
* @type {string} - 'none' | 'short' | 'long'
*/
get labels() { return this.#labels; } get labels() { return this.#labels; }
set labels(val) { set labels(val) {
this.#labels = String(val).toLowerCase(); this.#labels = String(val).toLowerCase();
@@ -372,16 +386,24 @@ class WcTimer extends HTMLElement {
this.#render(Date.now()); this.#render(Date.now());
} }
/**
* Whether compact mode is enabled (hides empty high units).
* @type {boolean}
*/
get compact() { return this.#compact; } get compact() { return this.#compact; }
set compact(val) { set compact(val) {
this.#compact = !!val; this.#compact = !!val;
if (this.#compact) this.setAttribute('compact', ''); if (this.#compact) this.setAttribute('compact', '');
else this.removeAttribute('compact'); else this.removeAttribute('compact');
this.#isAttrUpdate = true; // Flaga dla AttributeChanged this.#isAttrUpdate = true;
this.#render(Date.now()); this.#render(Date.now());
this.#isAttrUpdate = false; this.#isAttrUpdate = false;
} }
/**
* Current locale override.
* @type {string}
*/
set locale(val) { set locale(val) {
if (!val) return; if (!val) return;
this.#locale = String(val).toLowerCase(); this.#locale = String(val).toLowerCase();