senangwebs-epoch 1.1.4 → 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 +149 -14
- 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/SKILLS.md +0 -81
- package/examples/index.html +0 -456
- package/src/css/swe.css +0 -50
- package/src/js/swe.js +0 -338
- package/src/sw_banner.webp +0 -0
- package/test/swe.test.js +0 -95
- 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
|
@@ -15,6 +15,8 @@ SenangWebs Epoch (SWE) is a lightweight JavaScript library for creating dynamic
|
|
|
15
15
|
- Responsive design out of the box
|
|
16
16
|
- Comprehensive event system
|
|
17
17
|
- Pause, resume, and reset functionality
|
|
18
|
+
- Explicit cleanup and instance ownership for dynamic pages
|
|
19
|
+
- CommonJS, native ESM, and TypeScript declarations
|
|
18
20
|
- Completed and expired countdowns stop cleanly and emit `swe:end` once
|
|
19
21
|
- No external dependencies
|
|
20
22
|
- Built-in default styling with CSS
|
|
@@ -28,15 +30,37 @@ SenangWebs Epoch (SWE) is a lightweight JavaScript library for creating dynamic
|
|
|
28
30
|
npm install senangwebs-epoch
|
|
29
31
|
```
|
|
30
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
|
+
|
|
31
51
|
### Using a CDN
|
|
32
52
|
|
|
33
53
|
Include SenangWebs Epoch directly in your HTML file:
|
|
34
54
|
|
|
35
55
|
```html
|
|
36
|
-
<link rel="stylesheet" href="https://unpkg.com/senangwebs-epoch@
|
|
37
|
-
<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>
|
|
38
58
|
```
|
|
39
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
|
+
|
|
40
64
|
## Usage
|
|
41
65
|
|
|
42
66
|
1. Include the SWE CSS and JavaScript files in your HTML:
|
|
@@ -70,6 +94,13 @@ Include SenangWebs Epoch directly in your HTML file:
|
|
|
70
94
|
</div>
|
|
71
95
|
```
|
|
72
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
|
+
|
|
73
104
|
### Current time display
|
|
74
105
|
|
|
75
106
|
```html
|
|
@@ -116,13 +147,30 @@ The format string determines zero-padding based on its length:
|
|
|
116
147
|
| Second | `s` | `3` | Single digit second |
|
|
117
148
|
| Second | `ss` | `03` | Two-digit second |
|
|
118
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
|
+
|
|
119
168
|
### JavaScript Initialization
|
|
120
169
|
|
|
121
170
|
```javascript
|
|
122
171
|
const timer = new SWE(element, {
|
|
123
172
|
autostart: true,
|
|
124
173
|
duration: 60, // for countdown-duration mode
|
|
125
|
-
countdownEnd: '2027-01-01T00:00:00', // ISO 8601, interpreted as local time
|
|
126
174
|
onTick: () => {
|
|
127
175
|
console.log('Timer ticked!');
|
|
128
176
|
},
|
|
@@ -147,13 +195,42 @@ const timer = new SWE(element, {
|
|
|
147
195
|
});
|
|
148
196
|
```
|
|
149
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
|
+
|
|
150
206
|
## Methods
|
|
151
207
|
|
|
152
|
-
- `start()`: Start or resume
|
|
153
|
-
|
|
154
|
-
- `
|
|
155
|
-
|
|
156
|
-
- `
|
|
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.
|
|
157
234
|
|
|
158
235
|
## Events
|
|
159
236
|
|
|
@@ -177,11 +254,53 @@ Available events:
|
|
|
177
254
|
- `swe:reset`
|
|
178
255
|
- `swe:stop`
|
|
179
256
|
- `swe:end`
|
|
257
|
+
- `swe:error`
|
|
180
258
|
|
|
181
259
|
`swe:end` is emitted once when a countdown reaches zero. A countdown that is
|
|
182
260
|
already expired during initialization displays zero and does not leave a
|
|
183
261
|
background interval running.
|
|
184
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
|
+
```
|
|
303
|
+
|
|
185
304
|
## Styling
|
|
186
305
|
|
|
187
306
|
SWE comes with default styles that can be customized using CSS:
|
|
@@ -203,17 +322,33 @@ SWE comes with default styles that can be customized using CSS:
|
|
|
203
322
|
|
|
204
323
|
## Browser Support
|
|
205
324
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
-
|
|
209
|
-
|
|
210
|
-
- Edge
|
|
211
|
-
- 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.
|
|
212
329
|
|
|
213
330
|
## Contributing
|
|
214
331
|
|
|
215
332
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
216
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
|
+
|
|
217
352
|
## License
|
|
218
353
|
|
|
219
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