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 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@latest/dist/swe.css">
37
- <script src="https://unpkg.com/senangwebs-epoch@latest/dist/swe.js"></script>
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 the timer
153
- - `pause()`: Pause the timer
154
- - `resume()`: Resume the timer from paused state
155
- - `reset()`: Reset the timer to initial state
156
- - `stop()`: Stop the timer completely
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
- SenangWebs Epoch works on all modern browsers, including:
207
- - Chrome
208
- - Firefox
209
- - Safari
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()}(this,()=>(()=>{"use strict";var t={d:(e,n)=>{for(var i in n)t.o(n,i)&&!t.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:n[i]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e)},e={};function n(t){return n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},n(t)}function i(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,i=Array(e);n<e;n++)i[n]=t[n];return i}function o(t,e){for(var n=0;n<e.length;n++){var i=e[n];i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(t,r(i.key),i)}}function r(t){var e=function(t){if("object"!=n(t)||!t)return t;var e=t[Symbol.toPrimitive];if(void 0!==e){var i=e.call(t,"string");if("object"!=n(i))return i;throw new TypeError("@@toPrimitive must return a primitive value.")}return String(t)}(t);return"symbol"==n(e)?e:e+""}t.d(e,{default:()=>s});var a=function(){return t=function t(e){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};!function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,t),this.element=e,this.options={autostart:void 0===n.autostart||n.autostart,duration:n.duration,countdownEnd:n.countdownEnd,onTick:n.onTick,onEnd:n.onEnd,onStart:n.onStart,onPause:n.onPause,onResume:n.onResume,onReset:n.onReset,onStop:n.onStop},this.intervalId=null,this.isPaused=!1,this.startTime=null,this.pausedTimeRemaining=null,this.endTime=null,this.initialize()},(e=[{key:"initialize",value:function(){if(!(this.element&&this.element instanceof HTMLElement))throw new Error("SWE: Invalid element provided");if(this.mode=this.determineMode(),this.setupElements(),this.options.autostart)this.start();else if("countdown-duration"===this.mode&&this.options.duration)this.updateDisplay(1e3*this.options.duration);else if("countdown-end"===this.mode&&this.options.countdownEnd){var t=new Date(this.options.countdownEnd),e=new Date,n=t.getTime()-e.getTime();this.updateDisplay(n>0?n:0)}}},{key:"determineMode",value:function(){if(this.options.countdownEnd)return"countdown-end";if(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";if(this.options.countdownEnd)return"countdown-end";throw new Error("SWE: Invalid mode - missing required configuration")}},{key:"setupElements",value:function(){this.elements={year:this.element.querySelector("[data-swe-year]"),month:this.element.querySelector("[data-swe-month]"),day:this.element.querySelector("[data-swe-day]"),hour:this.element.querySelector("[data-swe-hour]"),minute:this.element.querySelector("[data-swe-minute]"),second:this.element.querySelector("[data-swe-second]")}}},{key:"start",value:function(){if(!this.intervalId){switch(this.isPaused=!1,this.startTime=Date.now(),this.mode){case"countdown-end":this.startCountdownToDate();break;case"countdown-duration":this.startCountdownDuration();break;case"current":this.startCurrentTime()}this.dispatchEvent("start")}}},{key:"startCountdownToDate",value:function(){var t=new Date(this.options.countdownEnd||this.element.getAttribute("data-swe-countdown-end"));if(isNaN(t.getTime()))throw new Error("SWE: Invalid end date format");this.endTime=t.getTime(),this.updateTimer()}},{key:"startCountdownDuration",value:function(){var t=this.options.duration||parseInt(this.element.getAttribute("data-swe-countdown-duration"),10);if(isNaN(t)||t<0)throw new Error("SWE: Invalid duration");null!==this.pausedTimeRemaining?this.endTime=Date.now()+this.pausedTimeRemaining:this.endTime=Date.now()+1e3*t,this.updateTimer()}},{key:"updateTimer",value:function(){var t=this,e=function(){var e=Date.now(),n=t.endTime-e;return n<=0?(t.stop(),t.updateDisplay(0),t.dispatchEvent("end"),!1):(t.updateDisplay(n),t.dispatchEvent("tick"),!0)};e()&&(this.intervalId=setInterval(e,1e3))}},{key:"startCurrentTime",value:function(){var t=this,e=function(){var e=new Date;t.updateCurrentTimeDisplay(e),t.dispatchEvent("tick")};e(),this.intervalId=setInterval(e,1e3)}},{key:"updateDisplay",value:function(t){var e=this,n=this.calculateTimeUnits(t);Object.entries(this.elements).forEach(function(t){var o=function(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){var n=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=n){var i,o,r,a,s=[],u=!0,d=!1;try{if(r=(n=n.call(t)).next,0===e){if(Object(n)!==n)return;u=!1}else for(;!(u=(i=r.call(n)).done)&&(s.push(i.value),s.length!==e);u=!0);}catch(t){d=!0,o=t}finally{try{if(!u&&null!=n.return&&(a=n.return(),Object(a)!==a))return}finally{if(d)throw o}}return s}}(t,e)||function(t,e){if(t){if("string"==typeof t)return i(t,e);var n={}.toString.call(t).slice(8,-1);return"Object"===n&&t.constructor&&(n=t.constructor.name),"Map"===n||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?i(t,e):void 0}}(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}(t,2),r=o[0],a=o[1];a&&e.updateElement(a,n[r])})}},{key:"updateCurrentTimeDisplay",value:function(t){this.elements.hour&&this.updateElement(this.elements.hour,t.getHours()),this.elements.minute&&this.updateElement(this.elements.minute,t.getMinutes()),this.elements.second&&this.updateElement(this.elements.second,t.getSeconds())}},{key:"updateElement",value:function(t,e){var n=t.getAttribute("data-swe-format");t.textContent=this.formatValue(e,n)}},{key:"formatValue",value:function(t,e){return e?t.toString().padStart(e.length,"0"):t.toString()}},{key:"calculateTimeUnits",value:function(t){var e=Math.floor(t/1e3),n=Math.floor(e/60),i=Math.floor(n/60),o=(Math.floor(i/24),new Date),r=new Date(o.getTime()+t),a=r.getFullYear()-o.getFullYear(),s=r.getMonth()-o.getMonth(),u=r.getDate()-o.getDate();return u<0&&(s--,u+=new Date(r.getFullYear(),r.getMonth(),0).getDate()),s<0&&(a--,s+=12),{year:a,month:s,day:u,hour:i%24,minute:n%60,second:e%60}}},{key:"dispatchEvent",value:function(t){var e="on".concat(t.charAt(0).toUpperCase()+t.slice(1));"function"==typeof this.options[e]&&this.options[e].call(this);var n=new CustomEvent("swe:".concat(t),{bubbles:!0,detail:{instance:this}});this.element.dispatchEvent(n)}},{key:"pause",value:function(){this.intervalId&&!this.isPaused&&(clearInterval(this.intervalId),this.intervalId=null,this.isPaused=!0,this.pausedTimeRemaining=this.endTime-Date.now(),this.dispatchEvent("pause"))}},{key:"resume",value:function(){if(this.isPaused){switch(this.isPaused=!1,this.startTime=Date.now(),this.mode){case"countdown-end":this.startCountdownToDate();break;case"countdown-duration":this.startCountdownDuration();break;case"current":this.startCurrentTime()}this.dispatchEvent("resume")}}},{key:"reset",value:function(){if(this.stop(),this.pausedTimeRemaining=null,this.endTime=null,"countdown-end"===this.mode&&this.options.countdownEnd){var t=new Date(this.options.countdownEnd),e=new Date,n=t.getTime()-e.getTime();this.updateDisplay(n>0?n:0)}else"countdown-duration"===this.mode&&this.options.duration&&this.updateDisplay(1e3*this.options.duration);this.dispatchEvent("reset"),this.options.autostart&&this.start()}},{key:"stop",value:function(){var t=null!==this.intervalId;this.intervalId&&(clearInterval(this.intervalId),this.intervalId=null),this.isPaused=!1,t&&this.dispatchEvent("stop")}}])&&o(t.prototype,e),Object.defineProperty(t,"prototype",{writable:!1}),t;var t,e}();document.addEventListener("DOMContentLoaded",function(){document.querySelectorAll("[data-swe]").forEach(function(t){new a(t)})});const s=a;return"undefined"!=typeof window&&(window.SWE=a),e.default})());
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
@@ -4,4 +4,4 @@
4
4
  */
5
5
 
6
6
  // Export the main library
7
- module.exports = require('./dist/swe.js');
7
+ module.exports = require('./dist/swe.cjs');