senangwebs-epoch 1.1.3 → 1.2.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/CHANGELOG.md +33 -0
- package/README.md +161 -19
- package/dist/swe.cjs +1 -0
- package/dist/swe.d.cts +66 -0
- package/dist/swe.d.mts +63 -0
- package/dist/swe.d.ts +63 -0
- package/dist/swe.js +1 -1
- package/dist/swe.mjs +1 -0
- package/index.js +1 -1
- package/package.json +79 -28
- package/.babelrc +0 -3
- package/.gitattributes +0 -2
- package/examples/index.html +0 -456
- package/src/css/swe.css +0 -50
- package/src/js/swe.js +0 -339
- package/src/sw_banner.webp +0 -0
- package/webpack.config.js +0 -36
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.2.0
|
|
4
|
+
|
|
5
|
+
- Add SSR-safe CommonJS and native ESM entry points with TypeScript declarations.
|
|
6
|
+
npm consumers explicitly initialize timers and import optional CSS; legacy
|
|
7
|
+
`dist/swe.js` retains the browser global and automatic initialization.
|
|
8
|
+
- Fix countdowns across midnight, month ends, leap years and omitted units.
|
|
9
|
+
Days/hours use elapsed time; displayed years/months use clamped local calendars.
|
|
10
|
+
- Accept zero and fractional durations; validate configuration before scheduling
|
|
11
|
+
or replacing an existing instance, including when autostart is disabled.
|
|
12
|
+
- Use monotonic duration timing, preserve pause progress, and discard that progress
|
|
13
|
+
on stop. Completed countdowns now require reset before restarting.
|
|
14
|
+
- Add lifecycle state, `destroy()`, `SWE.getInstance()`, and scoped `SWE.init()`.
|
|
15
|
+
Valid construction replaces old ownership, while repeated scans reuse timers.
|
|
16
|
+
- Render and reset attribute-configured timers and all current-clock date fields.
|
|
17
|
+
- Emit start/resume before the first tick; complete and render zero before
|
|
18
|
+
stop/end. Emit `swe:stop` on immediate completion as well. Isolate callback
|
|
19
|
+
failures and automatic-initialization errors through `swe:error`.
|
|
20
|
+
- Support late script loading and elements from another document realm.
|
|
21
|
+
- Refresh development dependencies, control package contents, build before
|
|
22
|
+
packing, and add unit, browser, declaration, package-consumer and CI checks.
|
|
23
|
+
|
|
24
|
+
### Upgrading from 1.1.x
|
|
25
|
+
|
|
26
|
+
- Add explicit `SWE.init()` after mounting if your npm application relied on
|
|
27
|
+
import-time automatic initialization. Script-tag users retain automatic setup.
|
|
28
|
+
- Use `reset()` to restart a completed timer. `start()` on a paused timer resumes;
|
|
29
|
+
`stop()` followed by `start()` restarts the configured duration.
|
|
30
|
+
- Expect totals to roll into visible units and positive seconds to round up.
|
|
31
|
+
Malformed numeric strings that were previously partially parsed now fail.
|
|
32
|
+
- Subscribe to `swe:error` to observe callback/markup failures, and call `destroy()`
|
|
33
|
+
when removing a timer's component. Existing attribute/event names remain valid.
|
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
SenangWebs Epoch (SWE) is a lightweight JavaScript library for creating dynamic countdown timers and time displays. Whether you need to count down to a specific date, create a duration-based timer, or display the current time, SWE provides an elegant solution with minimal setup. The library supports flexible formatting, responsive design, and comprehensive event handling.
|
|
4
4
|
|
|
5
|
+
[](LICENSE.md)
|
|
6
|
+
|
|
5
7
|
## Features
|
|
6
8
|
|
|
7
9
|
- Multiple timer modes:
|
|
@@ -12,8 +14,11 @@ SenangWebs Epoch (SWE) is a lightweight JavaScript library for creating dynamic
|
|
|
12
14
|
- Customizable time unit formatting
|
|
13
15
|
- Responsive design out of the box
|
|
14
16
|
- Comprehensive event system
|
|
15
|
-
- Pause, resume, and reset functionality
|
|
16
|
-
-
|
|
17
|
+
- Pause, resume, and reset functionality
|
|
18
|
+
- Explicit cleanup and instance ownership for dynamic pages
|
|
19
|
+
- CommonJS, native ESM, and TypeScript declarations
|
|
20
|
+
- Completed and expired countdowns stop cleanly and emit `swe:end` once
|
|
21
|
+
- No external dependencies
|
|
17
22
|
- Built-in default styling with CSS
|
|
18
23
|
- Works on all modern browsers
|
|
19
24
|
|
|
@@ -25,15 +30,37 @@ SenangWebs Epoch (SWE) is a lightweight JavaScript library for creating dynamic
|
|
|
25
30
|
npm install senangwebs-epoch
|
|
26
31
|
```
|
|
27
32
|
|
|
33
|
+
ESM and bundlers:
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
import SWE from 'senangwebs-epoch';
|
|
37
|
+
import 'senangwebs-epoch/dist/swe.css'; // optional default styling
|
|
38
|
+
|
|
39
|
+
const timer = new SWE(document.querySelector('#timer'), { duration: 60 });
|
|
40
|
+
// Or explicitly initialize markup using data attributes:
|
|
41
|
+
SWE.init(document);
|
|
42
|
+
// When the component or page is removed:
|
|
43
|
+
timer.destroy();
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
CommonJS uses `const SWE = require('senangwebs-epoch')`. Both npm entries can be
|
|
47
|
+
imported during server rendering without a DOM; construct timers or call `init`
|
|
48
|
+
only after mounting in a browser. npm imports do not scan the DOM, set a global,
|
|
49
|
+
or load CSS automatically. No framework wrappers are required.
|
|
50
|
+
|
|
28
51
|
### Using a CDN
|
|
29
52
|
|
|
30
53
|
Include SenangWebs Epoch directly in your HTML file:
|
|
31
54
|
|
|
32
55
|
```html
|
|
33
|
-
<link rel="stylesheet" href="https://unpkg.com/senangwebs-epoch@
|
|
34
|
-
<script src="https://unpkg.com/senangwebs-epoch@
|
|
56
|
+
<link rel="stylesheet" href="https://unpkg.com/senangwebs-epoch@1.2.0/dist/swe.css">
|
|
57
|
+
<script src="https://unpkg.com/senangwebs-epoch@1.2.0/dist/swe.js"></script>
|
|
35
58
|
```
|
|
36
59
|
|
|
60
|
+
The browser script exposes `window.SWE` and initializes `[data-swe]` elements
|
|
61
|
+
when the DOM is ready, including when the script loads after DOM readiness.
|
|
62
|
+
Version 1.2.0 URLs become available after the release is published.
|
|
63
|
+
|
|
37
64
|
## Usage
|
|
38
65
|
|
|
39
66
|
1. Include the SWE CSS and JavaScript files in your HTML:
|
|
@@ -48,7 +75,7 @@ Include SenangWebs Epoch directly in your HTML file:
|
|
|
48
75
|
### Countdown to a specific date
|
|
49
76
|
|
|
50
77
|
```html
|
|
51
|
-
<div data-swe data-swe-countdown-end="
|
|
78
|
+
<div data-swe data-swe-countdown-end="2027-01-01T00:00:00">
|
|
52
79
|
<div data-swe-year data-swe-format="yyyy"></div>
|
|
53
80
|
<div data-swe-month data-swe-format="mm"></div>
|
|
54
81
|
<div data-swe-day data-swe-format="dd"></div>
|
|
@@ -67,6 +94,13 @@ Include SenangWebs Epoch directly in your HTML file:
|
|
|
67
94
|
</div>
|
|
68
95
|
```
|
|
69
96
|
|
|
97
|
+
Duration values are finite nonnegative seconds, including fractions such as
|
|
98
|
+
`0.5`. Numeric strings use decimal digits with an optional fractional part,
|
|
99
|
+
such as `60` or `0.5`; scientific and hexadecimal strings are rejected.
|
|
100
|
+
Zero completes immediately when started. Empty values, malformed numbers,
|
|
101
|
+
infinity, negative values, and values beyond the supported date range throw
|
|
102
|
+
during construction, even with `autostart: false`.
|
|
103
|
+
|
|
70
104
|
### Current time display
|
|
71
105
|
|
|
72
106
|
```html
|
|
@@ -113,13 +147,30 @@ The format string determines zero-padding based on its length:
|
|
|
113
147
|
| Second | `s` | `3` | Single digit second |
|
|
114
148
|
| Second | `ss` | `03` | Two-digit second |
|
|
115
149
|
|
|
150
|
+
Formatting sets a minimum width and never truncates values. Countdown values
|
|
151
|
+
roll omitted units into the next visible unit: a minutes/seconds timer can show
|
|
152
|
+
`90:00`; a days-only countdown shows total elapsed days. Years and months use
|
|
153
|
+
local calendar arithmetic only when those units are present, clamping dates
|
|
154
|
+
such as January 31 to the last day of February. Remaining days are 24-hour
|
|
155
|
+
periods, including across daylight-saving changes. Positive remaining time is
|
|
156
|
+
rounded up to the next second; completion occurs at the actual deadline.
|
|
157
|
+
|
|
158
|
+
In current-clock mode all units show local calendar/time values, including the
|
|
159
|
+
four-digit year and one-based month. Countdown years mean remaining years.
|
|
160
|
+
|
|
161
|
+
Use ISO date-time strings with `T`. `2027-01-01T00:00:00` means midnight in the
|
|
162
|
+
visitor's local timezone; `2027-01-01T00:00:00Z` means UTC, and
|
|
163
|
+
`2027-01-01T00:00:00+08:00` specifies an offset. Date-only ISO strings follow
|
|
164
|
+
JavaScript's UTC interpretation. Nonstandard date strings may parse differently
|
|
165
|
+
between browsers and are not recommended. Numeric options are milliseconds
|
|
166
|
+
since the Unix epoch, and `Date` options are also accepted.
|
|
167
|
+
|
|
116
168
|
### JavaScript Initialization
|
|
117
169
|
|
|
118
170
|
```javascript
|
|
119
171
|
const timer = new SWE(element, {
|
|
120
172
|
autostart: true,
|
|
121
173
|
duration: 60, // for countdown-duration mode
|
|
122
|
-
countdownEnd: '2025-01-01 00:00:00', // for countdown-end mode
|
|
123
174
|
onTick: () => {
|
|
124
175
|
console.log('Timer ticked!');
|
|
125
176
|
},
|
|
@@ -144,13 +195,42 @@ const timer = new SWE(element, {
|
|
|
144
195
|
});
|
|
145
196
|
```
|
|
146
197
|
|
|
198
|
+
For a deadline use `{ countdownEnd: '2027-01-01T00:00:00+08:00' }` instead of
|
|
199
|
+
`duration`. Explicit options win over attributes; within each configuration
|
|
200
|
+
source, a deadline wins over a duration, then current-clock mode. Configuration
|
|
201
|
+
is normalized once, so changing attributes/options requires a replacement
|
|
202
|
+
instance. Callbacks receive the timer as `this`; use regular functions to access
|
|
203
|
+
it. Constructors synchronously render and may emit events when autostarting;
|
|
204
|
+
attach DOM listeners before construction if you need the first events.
|
|
205
|
+
|
|
147
206
|
## Methods
|
|
148
207
|
|
|
149
|
-
- `start()`: Start or resume
|
|
150
|
-
|
|
151
|
-
- `
|
|
152
|
-
|
|
153
|
-
- `
|
|
208
|
+
- `start()`: Start an idle/stopped timer or resume a paused timer. Running,
|
|
209
|
+
completed, and destroyed timers ignore this call.
|
|
210
|
+
- `pause()`: Freeze a duration's remaining time. For deadlines and clocks,
|
|
211
|
+
pause rendering while real time continues.
|
|
212
|
+
- `resume()`: Resume a paused timer. An elapsed deadline completes on resume.
|
|
213
|
+
- `reset()`: Restore the configured display and begin again if autostart is
|
|
214
|
+
enabled. This is required to restart a completed countdown.
|
|
215
|
+
- `stop()`: Stop an active timer and discard saved progress; the next start uses
|
|
216
|
+
the original duration or deadline. The displayed value is retained.
|
|
217
|
+
- `destroy()`: Release scheduling and registration. Later control calls do
|
|
218
|
+
nothing; existing markup is retained.
|
|
219
|
+
- `SWE.getInstance(element)`: Return the registered timer, or `undefined`.
|
|
220
|
+
- `SWE.init(root = document)`: Initialize a document, element, or fragment,
|
|
221
|
+
including the root itself when marked `[data-swe]`. Return successfully
|
|
222
|
+
initialized/reused instances. Call after inserting new markup.
|
|
223
|
+
|
|
224
|
+
`state` is `idle`, `running`, `paused`, `stopped`, `completed`, or `destroyed`.
|
|
225
|
+
Repeated controls do not create duplicate intervals. Constructing a valid new
|
|
226
|
+
timer on an owned element destroys the previous instance; invalid configuration
|
|
227
|
+
leaves the previous instance working. `init` reuses existing timers.
|
|
228
|
+
|
|
229
|
+
Duration timers use monotonic elapsed time and preserve fractional progress
|
|
230
|
+
across pause/resume. Deadlines and clocks follow system time. Browser throttling
|
|
231
|
+
may delay rendering/events, but each update recalculates the actual remaining
|
|
232
|
+
time rather than subtracting a tick. These are display timers, not a server-side
|
|
233
|
+
authority for authentication, pricing, or scheduled jobs.
|
|
154
234
|
|
|
155
235
|
## Events
|
|
156
236
|
|
|
@@ -166,14 +246,60 @@ timer.element.addEventListener('swe:end', (e) => {
|
|
|
166
246
|
});
|
|
167
247
|
```
|
|
168
248
|
|
|
169
|
-
Available events:
|
|
249
|
+
Available events:
|
|
170
250
|
- `swe:start`
|
|
171
251
|
- `swe:tick`
|
|
172
252
|
- `swe:pause`
|
|
173
253
|
- `swe:resume`
|
|
174
254
|
- `swe:reset`
|
|
175
255
|
- `swe:stop`
|
|
176
|
-
- `swe:end`
|
|
256
|
+
- `swe:end`
|
|
257
|
+
- `swe:error`
|
|
258
|
+
|
|
259
|
+
`swe:end` is emitted once when a countdown reaches zero. A countdown that is
|
|
260
|
+
already expired during initialization displays zero and does not leave a
|
|
261
|
+
background interval running.
|
|
262
|
+
|
|
263
|
+
Start/resume notifications precede the first tick. Completion sets `state` to
|
|
264
|
+
`completed`, clears scheduling, and displays zero before emitting `swe:stop`
|
|
265
|
+
and `swe:end` (including immediate completion). Reset emits stop when active,
|
|
266
|
+
then reset, then start when autostart is enabled. Callbacks run before their
|
|
267
|
+
corresponding bubbling DOM events, whose detail is `{ instance }`.
|
|
268
|
+
|
|
269
|
+
Callback exceptions do not suppress the original DOM event or interrupt timer
|
|
270
|
+
cleanup; they produce a bubbling `swe:error` event with `{ instance, error }`.
|
|
271
|
+
`SWE.init` isolates invalid elements and emits the same error with
|
|
272
|
+
`instance: null`, then continues scanning. Listen on the root before initializing:
|
|
273
|
+
|
|
274
|
+
```javascript
|
|
275
|
+
document.addEventListener('swe:error', event => {
|
|
276
|
+
console.error('Timer error:', event.detail.error);
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Lifecycle events are synchronous. Controls called inside callbacks take effect
|
|
281
|
+
immediately, so only reset on completion when you intend to begin another run.
|
|
282
|
+
|
|
283
|
+
## Accessibility
|
|
284
|
+
|
|
285
|
+
Provide visible unit labels; format tokens are padding instructions, not
|
|
286
|
+
accessible names. Keep per-second updates out of live announcements and report
|
|
287
|
+
completion separately when it is meaningful:
|
|
288
|
+
|
|
289
|
+
```html
|
|
290
|
+
<div id="timer" role="timer" aria-live="off" aria-label="Remaining time">
|
|
291
|
+
<span data-swe-minute data-swe-format="mm"></span> minutes
|
|
292
|
+
<span data-swe-second data-swe-format="ss"></span> seconds
|
|
293
|
+
</div>
|
|
294
|
+
<p id="timer-status" role="status"></p>
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
```javascript
|
|
298
|
+
new SWE(document.querySelector('#timer'), {
|
|
299
|
+
duration: 60,
|
|
300
|
+
onEnd() { document.querySelector('#timer-status').textContent = 'Timer finished'; }
|
|
301
|
+
});
|
|
302
|
+
```
|
|
177
303
|
|
|
178
304
|
## Styling
|
|
179
305
|
|
|
@@ -196,17 +322,33 @@ SWE comes with default styles that can be customized using CSS:
|
|
|
196
322
|
|
|
197
323
|
## Browser Support
|
|
198
324
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
-
|
|
202
|
-
|
|
203
|
-
- Edge
|
|
204
|
-
- Opera
|
|
325
|
+
Targets the latest two major versions of Chrome, Edge, Firefox, Safari, and iOS
|
|
326
|
+
Safari. Browser integration tests run on Playwright Chromium, Firefox, and WebKit;
|
|
327
|
+
these do not replace device-specific verification. Build and CI use Node 22/24;
|
|
328
|
+
Node timers without a DOM are not supported.
|
|
205
329
|
|
|
206
330
|
## Contributing
|
|
207
331
|
|
|
208
332
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
209
333
|
|
|
334
|
+
```bash
|
|
335
|
+
npm ci
|
|
336
|
+
npm run build
|
|
337
|
+
npm test
|
|
338
|
+
npm run check:types
|
|
339
|
+
npm run check:package
|
|
340
|
+
npx playwright install chromium firefox webkit
|
|
341
|
+
npm run test:browser
|
|
342
|
+
npm audit
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Ask before running unit tests when working under this repository's AGENTS.md
|
|
346
|
+
instruction. `npm test` tests the built distribution; rebuild after source edits.
|
|
347
|
+
`check:package` packs without scripts into a temporary fixture and verifies
|
|
348
|
+
actual CJS/ESM imports, declarations, CSS resolution and a bundler consumer.
|
|
349
|
+
Normal `npm pack`/`npm publish` runs the build through `prepack`. Commit rebuilt
|
|
350
|
+
distribution files; CI checks that rebuilding produces no changes.
|
|
351
|
+
|
|
210
352
|
## License
|
|
211
353
|
|
|
212
354
|
MIT License
|
package/dist/swe.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(()=>{"use strict";const t={d:(e,i)=>{if(Array.isArray(i))for(var n=0;n<i.length;){var s=i[n++],a=i[n++],o=0===a?{enumerable:!0,value:i[n++]}:{enumerable:!0,get:a};t.o(e,s)||Object.defineProperty(e,s,o)}else for(var s in i)t.o(i,s)&&!t.o(e,s)&&Object.defineProperty(e,s,{enumerable:!0,get:i[s]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)};let e={};const i=Symbol.for("senangwebs-epoch.instance"),n=["year","month","day","hour","minute","second"],s={day:864e5,hour:36e5,minute:6e4,second:1e3};function a(t,e,i){const n=t.ownerDocument&&t.ownerDocument.defaultView,s=n?n.CustomEvent:globalThis.CustomEvent;t.dispatchEvent(new s(`swe:${e}`,{bubbles:!0,detail:i}))}function o(t,e){const i=new Date(t.getTime());if(0===e)return i;const n=i.getDate();i.setDate(1),i.setMonth(i.getMonth()+e);const s=i.getFullYear(),a=[31,s%4!=0||s%100==0&&s%400!=0?28:29,31,30,31,30,31,31,30,31,30,31][i.getMonth()];return i.setDate(Math.min(n,a)),i}class r{constructor(t,e={}){const n=t&&t.ownerDocument&&t.ownerDocument.defaultView,s=n?n.HTMLElement:globalThis.HTMLElement;if(!(s&&t instanceof s))throw new Error("SWE: Invalid element provided");if(!e||"object"!=typeof e)throw new Error("SWE: Invalid options provided");this.element=t,this.options={...e,autostart:void 0===e.autostart||e.autostart},this._view=n||globalThis,this.mode=this.determineMode(),this._normalizeConfiguration(),this.setupElements(),this.intervalId=null,this.state="idle",this.isPaused=!1,this.startTime=null,this.endTime=null,this.pausedTimeRemaining=null,this._generation=0;const a=r.getInstance(t);a&&a.destroy(),t[i]=this,this._renderInitial(),this.options.autostart&&this.start()}static getInstance(t){return t&&t[i]&&"destroyed"!==t[i].state?t[i]:void 0}static init(t=globalThis.document){if(!t||"function"!=typeof t.querySelectorAll)throw new Error("SWE: init requires a DOM root");const e=[...t.querySelectorAll("[data-swe]")];"function"==typeof t.matches&&t.matches("[data-swe]")&&e.unshift(t);const i=[];return e.forEach(t=>{try{i.push(r.getInstance(t)||new r(t))}catch(e){a(t,"error",{instance:null,error:e})}}),i}determineMode(){if(void 0!==this.options.countdownEnd)return"countdown-end";if(void 0!==this.options.duration)return"countdown-duration";if(this.element.hasAttribute("data-swe-countdown-end"))return"countdown-end";if(this.element.hasAttribute("data-swe-countdown-duration"))return"countdown-duration";if(this.element.hasAttribute("data-swe-current"))return"current";throw new Error("SWE: Invalid mode - missing required configuration")}_normalizeConfiguration(){if("countdown-end"===this.mode){const t=void 0!==this.options.countdownEnd?this.options.countdownEnd:this.element.getAttribute("data-swe-countdown-end"),e="[object Date]"===Object.prototype.toString.call(t);if(null===t||""===t||"string"!=typeof t&&!e&&"number"!=typeof t)throw new Error("SWE: Invalid end date format");if(this._targetTime=new Date(t).getTime(),!Number.isFinite(this._targetTime))throw new Error("SWE: Invalid end date format")}else if("countdown-duration"===this.mode){const t=void 0!==this.options.duration?this.options.duration:this.element.getAttribute("data-swe-countdown-duration"),e="number"==typeof t||"string"==typeof t&&/^\s*\d+(?:\.\d+)?\s*$/.test(t)?Number(t):NaN;if(this._durationMilliseconds=1e3*e,!Number.isFinite(e)||e<0||!Number.isSafeInteger(Math.ceil(this._durationMilliseconds))||!Number.isFinite(new Date(Date.now()+this._durationMilliseconds).getTime()))throw new Error("SWE: Invalid duration")}}setupElements(){this.elements={},n.forEach(t=>{const e=`[data-swe-${t}]`,i=this.element.querySelectorAll?[...this.element.querySelectorAll(e)]:[this.element.querySelector(e)];this.elements[t]=i.find(t=>{if(!t)return!1;const e=t.closest&&t.closest("[data-swe]");return!e||e===this.element||!this.element.contains(e)})||null})}_now(){return this._view.performance?this._view.performance.now():Date.now()}_remaining(){return"countdown-duration"===this.mode?this._deadline-this._now():this._targetTime-Date.now()}_clearSchedule(){this._generation++,null!==this.intervalId&&this._view.clearInterval(this.intervalId),this.intervalId=null}_renderInitial(){"current"===this.mode?this.updateCurrentTimeDisplay(new Date):this.updateDisplay("countdown-duration"===this.mode?this._durationMilliseconds:Math.max(0,this._targetTime-Date.now()))}start(){if("paused"===this.state)return this.resume();["running","completed","destroyed"].includes(this.state)||this._begin("start")}_begin(t){if(this._clearSchedule(),this.state="running",this.isPaused=!1,this.startTime=Date.now(),"countdown-duration"===this.mode){const t=null===this.pausedTimeRemaining?this._durationMilliseconds:this.pausedTimeRemaining;this._deadline=this._now()+t,this.endTime=Date.now()+t,this.pausedTimeRemaining=null}else"countdown-end"===this.mode&&(this.endTime=this._targetTime);const e=this._generation;this.dispatchEvent(t),"running"===this.state&&e===this._generation&&(this._tick(),"running"===this.state&&e===this._generation&&(this.intervalId=this._view.setInterval(()=>{e===this._generation&&"running"===this.state&&this._tick()},1e3)))}_tick(){if("current"===this.mode)return this.updateCurrentTimeDisplay(new Date),void this.dispatchEvent("tick");const t=this._remaining();t<=0?(this._clearSchedule(),this.state="completed",this.isPaused=!1,this.pausedTimeRemaining=null,this.updateDisplay(0),this.dispatchEvent("stop"),this.dispatchEvent("end")):(this.updateDisplay(t),this.dispatchEvent("tick"))}pause(){"running"===this.state&&(this.pausedTimeRemaining="countdown-duration"===this.mode?Math.max(0,this._remaining()):null,this._clearSchedule(),this.state="paused",this.isPaused=!0,this.dispatchEvent("pause"))}resume(){"paused"===this.state&&this._begin("resume")}stop(){if("destroyed"===this.state||"completed"===this.state)return;const t="running"===this.state||"paused"===this.state;this._clearSchedule(),this.state="stopped",this.isPaused=!1,this.pausedTimeRemaining=null,this.endTime=null,t&&this.dispatchEvent("stop")}reset(){if("destroyed"===this.state)return;const t="running"===this.state||"paused"===this.state;this._clearSchedule(),this.state="idle",this.isPaused=!1,this.pausedTimeRemaining=null,this.startTime=null,this.endTime=null,this._renderInitial();const e=this._generation;t&&this.dispatchEvent("stop"),e===this._generation&&"idle"===this.state&&(this.dispatchEvent("reset"),this.options.autostart&&e===this._generation&&"idle"===this.state&&this.start())}destroy(){"destroyed"!==this.state&&(this._clearSchedule(),this.state="destroyed",this.isPaused=!1,this.pausedTimeRemaining=null,this.startTime=null,this.endTime=null,this.element[i]===this&&delete this.element[i],this.elements={})}dispatchEvent(t){const e=this.options[`on${t[0].toUpperCase()}${t.slice(1)}`];let i,n=!1;try{"function"==typeof e&&e.call(this)}catch(t){n=!0,i=t}a(this.element,t,{instance:this}),n&&a(this.element,"error",{instance:this,error:i})}updateDisplay(t){const e=this.calculateTimeUnits(t);n.forEach(t=>{this.elements[t]&&this.updateElement(this.elements[t],e[t])})}updateCurrentTimeDisplay(t){const e={year:t.getFullYear(),month:t.getMonth()+1,day:t.getDate(),hour:t.getHours(),minute:t.getMinutes(),second:t.getSeconds()};n.forEach(t=>{this.elements[t]&&this.updateElement(this.elements[t],e[t])})}updateElement(t,e){t.textContent=this.formatValue(e,t.getAttribute("data-swe-format"))}formatValue(t,e){return String(t).padStart(e?e.length:0,"0")}calculateTimeUnits(t){const e={year:0,month:0,day:0,hour:0,minute:0,second:0};let i=1e3*Math.ceil(Math.max(0,t)/1e3),n=new Date;const a=new Date(Math.min(864e13,n.getTime()+i));return["year","month"].forEach(t=>{if(!this.elements[t]||0===i)return;const s="year"===t?12:1,r=12*(a.getFullYear()-n.getFullYear())+a.getMonth()-n.getMonth();let h=Math.max(0,Math.floor(r/s)),d=o(n,h*s);(!Number.isFinite(d.getTime())||d>a)&&h>0&&(d=o(n,--h*s)),e[t]=h,n=d,i=Math.max(0,a.getTime()-n.getTime())}),["day","hour","minute","second"].forEach(t=>{this.elements[t]&&(e[t]=Math.floor(i/s[t]),i%=s[t])}),e}}const h=r;t.d(e,["default",0,h]),module.exports=e.default})();
|
package/dist/swe.d.cts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
declare namespace SWE {
|
|
2
|
+
/** Options take precedence over data attributes. countdownEnd wins over duration. */
|
|
3
|
+
interface SWEOptions {
|
|
4
|
+
autostart?: boolean;
|
|
5
|
+
/** Finite, nonnegative seconds; fractional seconds are supported. */
|
|
6
|
+
duration?: number | string;
|
|
7
|
+
countdownEnd?: string | number | Date;
|
|
8
|
+
onTick?: (this: SWE) => void;
|
|
9
|
+
onEnd?: (this: SWE) => void;
|
|
10
|
+
onStart?: (this: SWE) => void;
|
|
11
|
+
onPause?: (this: SWE) => void;
|
|
12
|
+
onResume?: (this: SWE) => void;
|
|
13
|
+
onReset?: (this: SWE) => void;
|
|
14
|
+
onStop?: (this: SWE) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type SWEState = 'idle' | 'running' | 'paused' | 'stopped' | 'completed' | 'destroyed';
|
|
18
|
+
type SWEMode = 'countdown-end' | 'countdown-duration' | 'current';
|
|
19
|
+
interface SWEEventDetail { instance: SWE; }
|
|
20
|
+
interface SWEErrorDetail { instance: SWE | null; error: unknown; }
|
|
21
|
+
interface SWETimeUnits {
|
|
22
|
+
year: number; month: number; day: number; hour: number; minute: number; second: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
declare class SWE {
|
|
28
|
+
constructor(element: HTMLElement, options?: SWE.SWEOptions);
|
|
29
|
+
static init(root?: Document | Element | DocumentFragment): SWE[];
|
|
30
|
+
static getInstance(element: HTMLElement): SWE | undefined;
|
|
31
|
+
readonly element: HTMLElement;
|
|
32
|
+
readonly options: SWE.SWEOptions;
|
|
33
|
+
readonly mode: SWE.SWEMode;
|
|
34
|
+
readonly state: SWE.SWEState;
|
|
35
|
+
readonly isPaused: boolean;
|
|
36
|
+
readonly intervalId: number | null;
|
|
37
|
+
readonly startTime: number | null;
|
|
38
|
+
readonly endTime: number | null;
|
|
39
|
+
readonly pausedTimeRemaining: number | null;
|
|
40
|
+
readonly elements: Partial<Record<keyof SWE.SWETimeUnits, HTMLElement | null>>;
|
|
41
|
+
start(): void;
|
|
42
|
+
pause(): void;
|
|
43
|
+
resume(): void;
|
|
44
|
+
stop(): void;
|
|
45
|
+
reset(): void;
|
|
46
|
+
destroy(): void;
|
|
47
|
+
calculateTimeUnits(milliseconds: number): SWE.SWETimeUnits;
|
|
48
|
+
formatValue(value: number, format?: string | null): string;
|
|
49
|
+
updateDisplay(milliseconds: number): void;
|
|
50
|
+
updateCurrentTimeDisplay(date: Date): void;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
declare global {
|
|
54
|
+
interface HTMLElementEventMap {
|
|
55
|
+
'swe:start': CustomEvent<SWE.SWEEventDetail>;
|
|
56
|
+
'swe:tick': CustomEvent<SWE.SWEEventDetail>;
|
|
57
|
+
'swe:pause': CustomEvent<SWE.SWEEventDetail>;
|
|
58
|
+
'swe:resume': CustomEvent<SWE.SWEEventDetail>;
|
|
59
|
+
'swe:reset': CustomEvent<SWE.SWEEventDetail>;
|
|
60
|
+
'swe:stop': CustomEvent<SWE.SWEEventDetail>;
|
|
61
|
+
'swe:end': CustomEvent<SWE.SWEEventDetail>;
|
|
62
|
+
'swe:error': CustomEvent<SWE.SWEErrorDetail>;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export = SWE;
|
package/dist/swe.d.mts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** Options take precedence over data attributes. countdownEnd wins over duration. */
|
|
2
|
+
export interface SWEOptions {
|
|
3
|
+
autostart?: boolean;
|
|
4
|
+
/** Finite, nonnegative seconds; fractional seconds are supported. */
|
|
5
|
+
duration?: number | string;
|
|
6
|
+
countdownEnd?: string | number | Date;
|
|
7
|
+
onTick?: (this: SWE) => void;
|
|
8
|
+
onEnd?: (this: SWE) => void;
|
|
9
|
+
onStart?: (this: SWE) => void;
|
|
10
|
+
onPause?: (this: SWE) => void;
|
|
11
|
+
onResume?: (this: SWE) => void;
|
|
12
|
+
onReset?: (this: SWE) => void;
|
|
13
|
+
onStop?: (this: SWE) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type SWEState = 'idle' | 'running' | 'paused' | 'stopped' | 'completed' | 'destroyed';
|
|
17
|
+
export type SWEMode = 'countdown-end' | 'countdown-duration' | 'current';
|
|
18
|
+
export interface SWEEventDetail { instance: SWE; }
|
|
19
|
+
export interface SWEErrorDetail { instance: SWE | null; error: unknown; }
|
|
20
|
+
export interface SWETimeUnits {
|
|
21
|
+
year: number; month: number; day: number; hour: number; minute: number; second: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
declare class SWE {
|
|
25
|
+
constructor(element: HTMLElement, options?: SWEOptions);
|
|
26
|
+
static init(root?: Document | Element | DocumentFragment): SWE[];
|
|
27
|
+
static getInstance(element: HTMLElement): SWE | undefined;
|
|
28
|
+
readonly element: HTMLElement;
|
|
29
|
+
readonly options: SWEOptions;
|
|
30
|
+
readonly mode: SWEMode;
|
|
31
|
+
readonly state: SWEState;
|
|
32
|
+
readonly isPaused: boolean;
|
|
33
|
+
readonly intervalId: number | null;
|
|
34
|
+
readonly startTime: number | null;
|
|
35
|
+
readonly endTime: number | null;
|
|
36
|
+
readonly pausedTimeRemaining: number | null;
|
|
37
|
+
readonly elements: Partial<Record<keyof SWETimeUnits, HTMLElement | null>>;
|
|
38
|
+
start(): void;
|
|
39
|
+
pause(): void;
|
|
40
|
+
resume(): void;
|
|
41
|
+
stop(): void;
|
|
42
|
+
reset(): void;
|
|
43
|
+
destroy(): void;
|
|
44
|
+
calculateTimeUnits(milliseconds: number): SWETimeUnits;
|
|
45
|
+
formatValue(value: number, format?: string | null): string;
|
|
46
|
+
updateDisplay(milliseconds: number): void;
|
|
47
|
+
updateCurrentTimeDisplay(date: Date): void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
declare global {
|
|
51
|
+
interface HTMLElementEventMap {
|
|
52
|
+
'swe:start': CustomEvent<SWEEventDetail>;
|
|
53
|
+
'swe:tick': CustomEvent<SWEEventDetail>;
|
|
54
|
+
'swe:pause': CustomEvent<SWEEventDetail>;
|
|
55
|
+
'swe:resume': CustomEvent<SWEEventDetail>;
|
|
56
|
+
'swe:reset': CustomEvent<SWEEventDetail>;
|
|
57
|
+
'swe:stop': CustomEvent<SWEEventDetail>;
|
|
58
|
+
'swe:end': CustomEvent<SWEEventDetail>;
|
|
59
|
+
'swe:error': CustomEvent<SWEErrorDetail>;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default SWE;
|
package/dist/swe.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/** Options take precedence over data attributes. countdownEnd wins over duration. */
|
|
2
|
+
export interface SWEOptions {
|
|
3
|
+
autostart?: boolean;
|
|
4
|
+
/** Finite, nonnegative seconds; fractional seconds are supported. */
|
|
5
|
+
duration?: number | string;
|
|
6
|
+
countdownEnd?: string | number | Date;
|
|
7
|
+
onTick?: (this: SWE) => void;
|
|
8
|
+
onEnd?: (this: SWE) => void;
|
|
9
|
+
onStart?: (this: SWE) => void;
|
|
10
|
+
onPause?: (this: SWE) => void;
|
|
11
|
+
onResume?: (this: SWE) => void;
|
|
12
|
+
onReset?: (this: SWE) => void;
|
|
13
|
+
onStop?: (this: SWE) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type SWEState = 'idle' | 'running' | 'paused' | 'stopped' | 'completed' | 'destroyed';
|
|
17
|
+
export type SWEMode = 'countdown-end' | 'countdown-duration' | 'current';
|
|
18
|
+
export interface SWEEventDetail { instance: SWE; }
|
|
19
|
+
export interface SWEErrorDetail { instance: SWE | null; error: unknown; }
|
|
20
|
+
export interface SWETimeUnits {
|
|
21
|
+
year: number; month: number; day: number; hour: number; minute: number; second: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
declare class SWE {
|
|
25
|
+
constructor(element: HTMLElement, options?: SWEOptions);
|
|
26
|
+
static init(root?: Document | Element | DocumentFragment): SWE[];
|
|
27
|
+
static getInstance(element: HTMLElement): SWE | undefined;
|
|
28
|
+
readonly element: HTMLElement;
|
|
29
|
+
readonly options: SWEOptions;
|
|
30
|
+
readonly mode: SWEMode;
|
|
31
|
+
readonly state: SWEState;
|
|
32
|
+
readonly isPaused: boolean;
|
|
33
|
+
readonly intervalId: number | null;
|
|
34
|
+
readonly startTime: number | null;
|
|
35
|
+
readonly endTime: number | null;
|
|
36
|
+
readonly pausedTimeRemaining: number | null;
|
|
37
|
+
readonly elements: Partial<Record<keyof SWETimeUnits, HTMLElement | null>>;
|
|
38
|
+
start(): void;
|
|
39
|
+
pause(): void;
|
|
40
|
+
resume(): void;
|
|
41
|
+
stop(): void;
|
|
42
|
+
reset(): void;
|
|
43
|
+
destroy(): void;
|
|
44
|
+
calculateTimeUnits(milliseconds: number): SWETimeUnits;
|
|
45
|
+
formatValue(value: number, format?: string | null): string;
|
|
46
|
+
updateDisplay(milliseconds: number): void;
|
|
47
|
+
updateCurrentTimeDisplay(date: Date): void;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
declare global {
|
|
51
|
+
interface HTMLElementEventMap {
|
|
52
|
+
'swe:start': CustomEvent<SWEEventDetail>;
|
|
53
|
+
'swe:tick': CustomEvent<SWEEventDetail>;
|
|
54
|
+
'swe:pause': CustomEvent<SWEEventDetail>;
|
|
55
|
+
'swe:resume': CustomEvent<SWEEventDetail>;
|
|
56
|
+
'swe:reset': CustomEvent<SWEEventDetail>;
|
|
57
|
+
'swe:stop': CustomEvent<SWEEventDetail>;
|
|
58
|
+
'swe:end': CustomEvent<SWEEventDetail>;
|
|
59
|
+
'swe:error': CustomEvent<SWEErrorDetail>;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default SWE;
|
package/dist/swe.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.SWE=e():t.SWE=e()}(
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.SWE=e():t.SWE=e()}(globalThis,()=>(()=>{"use strict";const t={d:(e,i)=>{for(var n in i)t.o(i,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:i[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)};let e={};t.d(e,{default:()=>d});const i=Symbol.for("senangwebs-epoch.instance"),n=["year","month","day","hour","minute","second"],s={day:864e5,hour:36e5,minute:6e4,second:1e3};function o(t,e,i){const n=t.ownerDocument&&t.ownerDocument.defaultView,s=n?n.CustomEvent:globalThis.CustomEvent;t.dispatchEvent(new s(`swe:${e}`,{bubbles:!0,detail:i}))}function a(t,e){const i=new Date(t.getTime());if(0===e)return i;const n=i.getDate();i.setDate(1),i.setMonth(i.getMonth()+e);const s=i.getFullYear(),o=[31,s%4!=0||s%100==0&&s%400!=0?28:29,31,30,31,30,31,31,30,31,30,31][i.getMonth()];return i.setDate(Math.min(n,o)),i}class r{constructor(t,e={}){const n=t&&t.ownerDocument&&t.ownerDocument.defaultView,s=n?n.HTMLElement:globalThis.HTMLElement;if(!(s&&t instanceof s))throw new Error("SWE: Invalid element provided");if(!e||"object"!=typeof e)throw new Error("SWE: Invalid options provided");this.element=t,this.options={...e,autostart:void 0===e.autostart||e.autostart},this._view=n||globalThis,this.mode=this.determineMode(),this._normalizeConfiguration(),this.setupElements(),this.intervalId=null,this.state="idle",this.isPaused=!1,this.startTime=null,this.endTime=null,this.pausedTimeRemaining=null,this._generation=0;const o=r.getInstance(t);o&&o.destroy(),t[i]=this,this._renderInitial(),this.options.autostart&&this.start()}static getInstance(t){return t&&t[i]&&"destroyed"!==t[i].state?t[i]:void 0}static init(t=globalThis.document){if(!t||"function"!=typeof t.querySelectorAll)throw new Error("SWE: init requires a DOM root");const e=[...t.querySelectorAll("[data-swe]")];"function"==typeof t.matches&&t.matches("[data-swe]")&&e.unshift(t);const i=[];return e.forEach(t=>{try{i.push(r.getInstance(t)||new r(t))}catch(e){o(t,"error",{instance:null,error:e})}}),i}determineMode(){if(void 0!==this.options.countdownEnd)return"countdown-end";if(void 0!==this.options.duration)return"countdown-duration";if(this.element.hasAttribute("data-swe-countdown-end"))return"countdown-end";if(this.element.hasAttribute("data-swe-countdown-duration"))return"countdown-duration";if(this.element.hasAttribute("data-swe-current"))return"current";throw new Error("SWE: Invalid mode - missing required configuration")}_normalizeConfiguration(){if("countdown-end"===this.mode){const t=void 0!==this.options.countdownEnd?this.options.countdownEnd:this.element.getAttribute("data-swe-countdown-end"),e="[object Date]"===Object.prototype.toString.call(t);if(null===t||""===t||"string"!=typeof t&&!e&&"number"!=typeof t)throw new Error("SWE: Invalid end date format");if(this._targetTime=new Date(t).getTime(),!Number.isFinite(this._targetTime))throw new Error("SWE: Invalid end date format")}else if("countdown-duration"===this.mode){const t=void 0!==this.options.duration?this.options.duration:this.element.getAttribute("data-swe-countdown-duration"),e="number"==typeof t||"string"==typeof t&&/^\s*\d+(?:\.\d+)?\s*$/.test(t)?Number(t):NaN;if(this._durationMilliseconds=1e3*e,!Number.isFinite(e)||e<0||!Number.isSafeInteger(Math.ceil(this._durationMilliseconds))||!Number.isFinite(new Date(Date.now()+this._durationMilliseconds).getTime()))throw new Error("SWE: Invalid duration")}}setupElements(){this.elements={},n.forEach(t=>{const e=`[data-swe-${t}]`,i=this.element.querySelectorAll?[...this.element.querySelectorAll(e)]:[this.element.querySelector(e)];this.elements[t]=i.find(t=>{if(!t)return!1;const e=t.closest&&t.closest("[data-swe]");return!e||e===this.element||!this.element.contains(e)})||null})}_now(){return this._view.performance?this._view.performance.now():Date.now()}_remaining(){return"countdown-duration"===this.mode?this._deadline-this._now():this._targetTime-Date.now()}_clearSchedule(){this._generation++,null!==this.intervalId&&this._view.clearInterval(this.intervalId),this.intervalId=null}_renderInitial(){"current"===this.mode?this.updateCurrentTimeDisplay(new Date):this.updateDisplay("countdown-duration"===this.mode?this._durationMilliseconds:Math.max(0,this._targetTime-Date.now()))}start(){if("paused"===this.state)return this.resume();["running","completed","destroyed"].includes(this.state)||this._begin("start")}_begin(t){if(this._clearSchedule(),this.state="running",this.isPaused=!1,this.startTime=Date.now(),"countdown-duration"===this.mode){const t=null===this.pausedTimeRemaining?this._durationMilliseconds:this.pausedTimeRemaining;this._deadline=this._now()+t,this.endTime=Date.now()+t,this.pausedTimeRemaining=null}else"countdown-end"===this.mode&&(this.endTime=this._targetTime);const e=this._generation;this.dispatchEvent(t),"running"===this.state&&e===this._generation&&(this._tick(),"running"===this.state&&e===this._generation&&(this.intervalId=this._view.setInterval(()=>{e===this._generation&&"running"===this.state&&this._tick()},1e3)))}_tick(){if("current"===this.mode)return this.updateCurrentTimeDisplay(new Date),void this.dispatchEvent("tick");const t=this._remaining();t<=0?(this._clearSchedule(),this.state="completed",this.isPaused=!1,this.pausedTimeRemaining=null,this.updateDisplay(0),this.dispatchEvent("stop"),this.dispatchEvent("end")):(this.updateDisplay(t),this.dispatchEvent("tick"))}pause(){"running"===this.state&&(this.pausedTimeRemaining="countdown-duration"===this.mode?Math.max(0,this._remaining()):null,this._clearSchedule(),this.state="paused",this.isPaused=!0,this.dispatchEvent("pause"))}resume(){"paused"===this.state&&this._begin("resume")}stop(){if("destroyed"===this.state||"completed"===this.state)return;const t="running"===this.state||"paused"===this.state;this._clearSchedule(),this.state="stopped",this.isPaused=!1,this.pausedTimeRemaining=null,this.endTime=null,t&&this.dispatchEvent("stop")}reset(){if("destroyed"===this.state)return;const t="running"===this.state||"paused"===this.state;this._clearSchedule(),this.state="idle",this.isPaused=!1,this.pausedTimeRemaining=null,this.startTime=null,this.endTime=null,this._renderInitial();const e=this._generation;t&&this.dispatchEvent("stop"),e===this._generation&&"idle"===this.state&&(this.dispatchEvent("reset"),this.options.autostart&&e===this._generation&&"idle"===this.state&&this.start())}destroy(){"destroyed"!==this.state&&(this._clearSchedule(),this.state="destroyed",this.isPaused=!1,this.pausedTimeRemaining=null,this.startTime=null,this.endTime=null,this.element[i]===this&&delete this.element[i],this.elements={})}dispatchEvent(t){const e=this.options[`on${t[0].toUpperCase()}${t.slice(1)}`];let i,n=!1;try{"function"==typeof e&&e.call(this)}catch(t){n=!0,i=t}o(this.element,t,{instance:this}),n&&o(this.element,"error",{instance:this,error:i})}updateDisplay(t){const e=this.calculateTimeUnits(t);n.forEach(t=>{this.elements[t]&&this.updateElement(this.elements[t],e[t])})}updateCurrentTimeDisplay(t){const e={year:t.getFullYear(),month:t.getMonth()+1,day:t.getDate(),hour:t.getHours(),minute:t.getMinutes(),second:t.getSeconds()};n.forEach(t=>{this.elements[t]&&this.updateElement(this.elements[t],e[t])})}updateElement(t,e){t.textContent=this.formatValue(e,t.getAttribute("data-swe-format"))}formatValue(t,e){return String(t).padStart(e?e.length:0,"0")}calculateTimeUnits(t){const e={year:0,month:0,day:0,hour:0,minute:0,second:0};let i=1e3*Math.ceil(Math.max(0,t)/1e3),n=new Date;const o=new Date(Math.min(864e13,n.getTime()+i));return["year","month"].forEach(t=>{if(!this.elements[t]||0===i)return;const s="year"===t?12:1,r=12*(o.getFullYear()-n.getFullYear())+o.getMonth()-n.getMonth();let h=Math.max(0,Math.floor(r/s)),d=a(n,h*s);(!Number.isFinite(d.getTime())||d>o)&&h>0&&(d=a(n,--h*s)),e[t]=h,n=d,i=Math.max(0,o.getTime()-n.getTime())}),["day","hour","minute","second"].forEach(t=>{this.elements[t]&&(e[t]=Math.floor(i/s[t]),i%=s[t])}),e}}const h=r;"undefined"!=typeof document&&("undefined"!=typeof window&&(window.SWE=h),"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>h.init(document),{once:!0}):h.init(document));const d=h;return e=e.default,e})());
|
package/dist/swe.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const t={d:(e,i)=>{if(Array.isArray(i))for(var n=0;n<i.length;){var s=i[n++],a=i[n++],o=0===a?{enumerable:!0,value:i[n++]}:{enumerable:!0,get:a};t.o(e,s)||Object.defineProperty(e,s,o)}else for(var s in i)t.o(i,s)&&!t.o(e,s)&&Object.defineProperty(e,s,{enumerable:!0,get:i[s]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)};let e={};const i=Symbol.for("senangwebs-epoch.instance"),n=["year","month","day","hour","minute","second"],s={day:864e5,hour:36e5,minute:6e4,second:1e3};function a(t,e,i){const n=t.ownerDocument&&t.ownerDocument.defaultView,s=n?n.CustomEvent:globalThis.CustomEvent;t.dispatchEvent(new s(`swe:${e}`,{bubbles:!0,detail:i}))}function o(t,e){const i=new Date(t.getTime());if(0===e)return i;const n=i.getDate();i.setDate(1),i.setMonth(i.getMonth()+e);const s=i.getFullYear(),a=[31,s%4!=0||s%100==0&&s%400!=0?28:29,31,30,31,30,31,31,30,31,30,31][i.getMonth()];return i.setDate(Math.min(n,a)),i}class r{constructor(t,e={}){const n=t&&t.ownerDocument&&t.ownerDocument.defaultView,s=n?n.HTMLElement:globalThis.HTMLElement;if(!(s&&t instanceof s))throw new Error("SWE: Invalid element provided");if(!e||"object"!=typeof e)throw new Error("SWE: Invalid options provided");this.element=t,this.options={...e,autostart:void 0===e.autostart||e.autostart},this._view=n||globalThis,this.mode=this.determineMode(),this._normalizeConfiguration(),this.setupElements(),this.intervalId=null,this.state="idle",this.isPaused=!1,this.startTime=null,this.endTime=null,this.pausedTimeRemaining=null,this._generation=0;const a=r.getInstance(t);a&&a.destroy(),t[i]=this,this._renderInitial(),this.options.autostart&&this.start()}static getInstance(t){return t&&t[i]&&"destroyed"!==t[i].state?t[i]:void 0}static init(t=globalThis.document){if(!t||"function"!=typeof t.querySelectorAll)throw new Error("SWE: init requires a DOM root");const e=[...t.querySelectorAll("[data-swe]")];"function"==typeof t.matches&&t.matches("[data-swe]")&&e.unshift(t);const i=[];return e.forEach(t=>{try{i.push(r.getInstance(t)||new r(t))}catch(e){a(t,"error",{instance:null,error:e})}}),i}determineMode(){if(void 0!==this.options.countdownEnd)return"countdown-end";if(void 0!==this.options.duration)return"countdown-duration";if(this.element.hasAttribute("data-swe-countdown-end"))return"countdown-end";if(this.element.hasAttribute("data-swe-countdown-duration"))return"countdown-duration";if(this.element.hasAttribute("data-swe-current"))return"current";throw new Error("SWE: Invalid mode - missing required configuration")}_normalizeConfiguration(){if("countdown-end"===this.mode){const t=void 0!==this.options.countdownEnd?this.options.countdownEnd:this.element.getAttribute("data-swe-countdown-end"),e="[object Date]"===Object.prototype.toString.call(t);if(null===t||""===t||"string"!=typeof t&&!e&&"number"!=typeof t)throw new Error("SWE: Invalid end date format");if(this._targetTime=new Date(t).getTime(),!Number.isFinite(this._targetTime))throw new Error("SWE: Invalid end date format")}else if("countdown-duration"===this.mode){const t=void 0!==this.options.duration?this.options.duration:this.element.getAttribute("data-swe-countdown-duration"),e="number"==typeof t||"string"==typeof t&&/^\s*\d+(?:\.\d+)?\s*$/.test(t)?Number(t):NaN;if(this._durationMilliseconds=1e3*e,!Number.isFinite(e)||e<0||!Number.isSafeInteger(Math.ceil(this._durationMilliseconds))||!Number.isFinite(new Date(Date.now()+this._durationMilliseconds).getTime()))throw new Error("SWE: Invalid duration")}}setupElements(){this.elements={},n.forEach(t=>{const e=`[data-swe-${t}]`,i=this.element.querySelectorAll?[...this.element.querySelectorAll(e)]:[this.element.querySelector(e)];this.elements[t]=i.find(t=>{if(!t)return!1;const e=t.closest&&t.closest("[data-swe]");return!e||e===this.element||!this.element.contains(e)})||null})}_now(){return this._view.performance?this._view.performance.now():Date.now()}_remaining(){return"countdown-duration"===this.mode?this._deadline-this._now():this._targetTime-Date.now()}_clearSchedule(){this._generation++,null!==this.intervalId&&this._view.clearInterval(this.intervalId),this.intervalId=null}_renderInitial(){"current"===this.mode?this.updateCurrentTimeDisplay(new Date):this.updateDisplay("countdown-duration"===this.mode?this._durationMilliseconds:Math.max(0,this._targetTime-Date.now()))}start(){if("paused"===this.state)return this.resume();["running","completed","destroyed"].includes(this.state)||this._begin("start")}_begin(t){if(this._clearSchedule(),this.state="running",this.isPaused=!1,this.startTime=Date.now(),"countdown-duration"===this.mode){const t=null===this.pausedTimeRemaining?this._durationMilliseconds:this.pausedTimeRemaining;this._deadline=this._now()+t,this.endTime=Date.now()+t,this.pausedTimeRemaining=null}else"countdown-end"===this.mode&&(this.endTime=this._targetTime);const e=this._generation;this.dispatchEvent(t),"running"===this.state&&e===this._generation&&(this._tick(),"running"===this.state&&e===this._generation&&(this.intervalId=this._view.setInterval(()=>{e===this._generation&&"running"===this.state&&this._tick()},1e3)))}_tick(){if("current"===this.mode)return this.updateCurrentTimeDisplay(new Date),void this.dispatchEvent("tick");const t=this._remaining();t<=0?(this._clearSchedule(),this.state="completed",this.isPaused=!1,this.pausedTimeRemaining=null,this.updateDisplay(0),this.dispatchEvent("stop"),this.dispatchEvent("end")):(this.updateDisplay(t),this.dispatchEvent("tick"))}pause(){"running"===this.state&&(this.pausedTimeRemaining="countdown-duration"===this.mode?Math.max(0,this._remaining()):null,this._clearSchedule(),this.state="paused",this.isPaused=!0,this.dispatchEvent("pause"))}resume(){"paused"===this.state&&this._begin("resume")}stop(){if("destroyed"===this.state||"completed"===this.state)return;const t="running"===this.state||"paused"===this.state;this._clearSchedule(),this.state="stopped",this.isPaused=!1,this.pausedTimeRemaining=null,this.endTime=null,t&&this.dispatchEvent("stop")}reset(){if("destroyed"===this.state)return;const t="running"===this.state||"paused"===this.state;this._clearSchedule(),this.state="idle",this.isPaused=!1,this.pausedTimeRemaining=null,this.startTime=null,this.endTime=null,this._renderInitial();const e=this._generation;t&&this.dispatchEvent("stop"),e===this._generation&&"idle"===this.state&&(this.dispatchEvent("reset"),this.options.autostart&&e===this._generation&&"idle"===this.state&&this.start())}destroy(){"destroyed"!==this.state&&(this._clearSchedule(),this.state="destroyed",this.isPaused=!1,this.pausedTimeRemaining=null,this.startTime=null,this.endTime=null,this.element[i]===this&&delete this.element[i],this.elements={})}dispatchEvent(t){const e=this.options[`on${t[0].toUpperCase()}${t.slice(1)}`];let i,n=!1;try{"function"==typeof e&&e.call(this)}catch(t){n=!0,i=t}a(this.element,t,{instance:this}),n&&a(this.element,"error",{instance:this,error:i})}updateDisplay(t){const e=this.calculateTimeUnits(t);n.forEach(t=>{this.elements[t]&&this.updateElement(this.elements[t],e[t])})}updateCurrentTimeDisplay(t){const e={year:t.getFullYear(),month:t.getMonth()+1,day:t.getDate(),hour:t.getHours(),minute:t.getMinutes(),second:t.getSeconds()};n.forEach(t=>{this.elements[t]&&this.updateElement(this.elements[t],e[t])})}updateElement(t,e){t.textContent=this.formatValue(e,t.getAttribute("data-swe-format"))}formatValue(t,e){return String(t).padStart(e?e.length:0,"0")}calculateTimeUnits(t){const e={year:0,month:0,day:0,hour:0,minute:0,second:0};let i=1e3*Math.ceil(Math.max(0,t)/1e3),n=new Date;const a=new Date(Math.min(864e13,n.getTime()+i));return["year","month"].forEach(t=>{if(!this.elements[t]||0===i)return;const s="year"===t?12:1,r=12*(a.getFullYear()-n.getFullYear())+a.getMonth()-n.getMonth();let h=Math.max(0,Math.floor(r/s)),d=o(n,h*s);(!Number.isFinite(d.getTime())||d>a)&&h>0&&(d=o(n,--h*s)),e[t]=h,n=d,i=Math.max(0,a.getTime()-n.getTime())}),["day","hour","minute","second"].forEach(t=>{this.elements[t]&&(e[t]=Math.floor(i/s[t]),i%=s[t])}),e}}const h=r;t.d(e,["A",0,h]);const d=e.A;export{d as default};
|
package/index.js
CHANGED