├── .eslintrc.json ├── .gitattributes ├── .github └── workflows │ ├── issue_stale.yml │ └── lint.yml ├── .gitignore ├── .husky └── pre-commit ├── .npmrc ├── .nvmrc ├── .prettierrc.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets ├── images │ ├── timer.png │ └── timer_long.png └── js │ ├── service.js │ └── syotimer.examples.js ├── build ├── jquery.syotimer.d.ts ├── jquery.syotimer.js ├── jquery.syotimer.min.js └── jquery.syotimer.min.js.map ├── index.d.ts ├── index.html ├── package-lock.json ├── package.json ├── resources └── default.css ├── rollup.config.mjs ├── source ├── SyoTimer.ts ├── constants.ts ├── index.ts ├── localization.ts ├── types.ts ├── utils.spec.ts └── utils.ts ├── tsconfig.json ├── tsconfig.lib.json ├── tsconfig.spec.json └── vite.config.ts /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "jquery": true 6 | }, 7 | "extends": ["airbnb-base", "airbnb-typescript/base", "prettier"], 8 | "parser": "@typescript-eslint/parser", 9 | "parserOptions": { 10 | "ecmaVersion": 12, 11 | "sourceType": "module", 12 | "project": "./tsconfig.*?.json" 13 | }, 14 | "plugins": ["@typescript-eslint", "prettier"], 15 | "rules": { 16 | "prettier/prettier": "error" 17 | }, 18 | "overrides": [ 19 | { 20 | "files": "{rollup.config.mjs,vite.config.ts}", 21 | "rules": { 22 | "import/no-extraneous-dependencies": [ 23 | "error", 24 | { 25 | "devDependencies": true, 26 | "optionalDependencies": false 27 | } 28 | ] 29 | } 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | build/* text -diff 4 | yarn.lock text -diff 5 | package-lock.json text -diff 6 | -------------------------------------------------------------------------------- /.github/workflows/issue_stale.yml: -------------------------------------------------------------------------------- 1 | name: Stale 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | # This runs every day 20 minutes before midnight: https://crontab.guru/#40_23_*_*_* 7 | - cron: '40 23 * * *' 8 | 9 | jobs: 10 | stale: 11 | runs-on: ubuntu-latest 12 | if: github.repository_owner == 'mrfratello' 13 | permissions: 14 | issues: write 15 | pull-requests: write 16 | 17 | steps: 18 | - uses: actions/stale@v9 19 | name: 'Close stale issues' 20 | with: 21 | stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days.' 22 | stale-pr-message: 'This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days.' 23 | close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.' 24 | close-pr-message: 'This PR was closed because it has been stalled for 7 days with no activity.' 25 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | main: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: get sources 14 | uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 17 | - name: node version 18 | id: node_info 19 | run: echo "version=$(cat .nvmrc)" >> $GITHUB_OUTPUT 20 | shell: bash 21 | - name: setup node lts version 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: '${{ steps.node_info.outputs.version }}' 25 | - name: install dependencies 26 | run: npm ci 27 | shell: bash 28 | 29 | - name: lint 30 | run: npm run lint 31 | - name: test 32 | run: npm run test 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | node_modules/ 4 | .idea/ 5 | npm-debug.log 6 | tmp/ -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run lint 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/hydrogen -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/prettierrc", 3 | "printWidth": 100, 4 | "singleQuote": true, 5 | "trailingComma": "all" 6 | } 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [3.1.2](https://github.com/mrfratello/SyoTimer/compare/v3.1.1...v3.1.2) (2024-02-25) 6 | 7 | ### [3.1.1](https://github.com/mrfratello/SyoTimer/compare/v3.1.0...v3.1.1) (2021-09-15) 8 | 9 | ## [3.1.0](https://github.com/mrfratello/SyoTimer/compare/v3.0.0...v3.1.0) (2021-09-10) 10 | 11 | 12 | ### Features 13 | 14 | * add typescript and refactor source structure ([b1398c5](https://github.com/mrfratello/SyoTimer/commit/b1398c59978a2c91ae13f6aff29feade7b5d527f)) 15 | 16 | 17 | ### Bug Fixes 18 | 19 | * use correct font types in default styles ([ffb3a78](https://github.com/mrfratello/SyoTimer/commit/ffb3a78f435bb3c764d39dbf80fe954200353958)) 20 | 21 | ## [3.0.0](https://github.com/mrfratello/SyoTimer/compare/v2.1.3...v3.0.0) (2021-08-31) 22 | 23 | 24 | ### ⚠ BREAKING CHANGES 25 | 26 | * `$.syotimerLang[lang].handler` must be function with two arguments 27 | * API Changed: 28 | 29 | * use date (number, Date) property instead year, month, day, hour, minute, second props 30 | * remove properties timeZone and ignoreTransferTime 31 | 32 | ### Features 33 | 34 | * change api, update exaples ([d1abea1](https://github.com/mrfratello/SyoTimer/commit/d1abea14432e6fb4e86cbf8cc2499dc0eeb17e67)) 35 | * change localization api ([cdd3436](https://github.com/mrfratello/SyoTimer/commit/cdd34366b18564f1880f53f3479f51cbd764e1ce)) 36 | 37 | ### [2.1.1](https://github.com/mrfratello/SyoTimer/compare/v2.0.0...v2.1.1) (2019-10-17) 38 | 39 | ### Features 40 | 41 | * publish on npm 42 | * used universal module definition 43 | * added default CSS styles 44 | 45 | ### [2.0.0](https://github.com/mrfratello/SyoTimer/compare/v1.1.0...v2.0.0) (2017-06-24) 46 | 47 | ### Features 48 | 49 | * redesigned the structure of a plugin 50 | * `effectType` applies to all units 51 | * added possibility to sets an order of layout of units of the timer 52 | * added possibility to add new language 53 | * rename CSS classes by BEM methodology 54 | 55 | ### 1.1.0 (2016-07-30) 56 | 57 | ### Features 58 | 59 | * added time zone support 60 | * added support of the time transfer on summer/winter time 61 | * added methods support 62 | * added method of set value to option 63 | * added minified version of plugin 64 | 65 | ### 1.0.1 (2015-02-24) 66 | 67 | ### Features 68 | 69 | * added option for change effect of counting 70 | * added documentation 71 | * added examples 72 | 73 | ### 1.0.0 (2014-12-10) 74 | 75 | ### Features 76 | 77 | * first use timer on real web-site 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jQuery SyoTimer Plugin 2 | 3 | jQuery plugin of countdown on html page 4 | 5 | ## Demo 6 | 7 | [Examples of usage jQuery SyoTimer Plugin](https://mrfratello.github.io/SyoTimer) 8 | 9 | ## Features 10 | 11 | - Periodic counting with the specified period 12 | - Effects of change of indications of the countdown 13 | - The correct declension of nouns next to numeral numerals 14 | - An opportunity to add the language of countdown signatures which isn't included in the standard version of the plugin 15 | - Callback after the end of the countdown timer with the possibility of changing the structure of the timer 16 | - Custom formatting and styling timer 17 | 18 | ## Installing 19 | 20 | In a browser. Need download the [latest release](https://github.com/mrfratello/syotimer/releases/latest). And include the JavaScript file which you can find in the `build` folder: 21 | 22 | ```html 23 | 24 | 25 | ``` 26 | 27 | Using npm: 28 | 29 | ``` 30 | $ npm install jquery-syotimer 31 | ``` 32 | 33 | Using yarn: 34 | 35 | ``` 36 | $ yarn add jquery-syotimer 37 | ``` 38 | 39 | ## Usage 40 | 41 | Syotimer plugin can be integrated with plain JavaScript or with different module loaders. 42 | 43 | Script Tag: 44 | 45 | ```html 46 | 51 | ``` 52 | 53 | Common JS: 54 | 55 | ```javascript 56 | const $ = require("jquery"); 57 | require("jquery-syotimer"); 58 | 59 | $(".selector_to_countdown").syotimer(); 60 | ``` 61 | 62 | Bundlers (Webpack, etc): 63 | 64 | ```javascript 65 | import $ from "jquery"; 66 | import "jquery-syotimer"; 67 | 68 | $(".selector_to_countdown").syotimer(); 69 | ``` 70 | 71 | ## Markup 72 | 73 | Classes is named by [BEM methodology](https://en.bem.info/methodology/naming-convention/) 74 | 75 | ```html 76 |
77 |
78 |
79 |
80 |
1
81 |
day
82 |
83 |
84 |
1
85 |
hour
86 |
87 |
88 |
1
89 |
minute
90 |
91 |
92 |
1
93 |
second
94 |
95 |
96 | 97 |
98 | ``` 99 | 100 | Example of css styles for syotimer in [resources/default.css](resources/default.css). 101 | 102 | ## Options 103 | 104 | | Option | Description | Type of Value | Default Value | Available Values | 105 | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | ------------- | --------------------------------- | 106 | | `date` | date, which should count down timer | integer/Date | 0 | | 107 | | `layout` | sets an order of layout of units of the timer: days (d) of hours ('h'), minute ('m'), second ('s'). | string | 'dhms' | | 108 | | `doubleNumbers` | `true` - show hours, minutes and seconds with leading zeros (2 hours 5 minutes 4 seconds = 02:05:04) | boolean | true | | 109 | | `effectType` | The effect of changing the value of seconds | string | 'none' | 'none', 'opacity' | 110 | | `lang` | localization of a countdown signatures (days, hours, minutes, seconds) | string | 'eng' | see [Localization](#localization) | 111 | | `periodic` | `true` - the timer is periodic. If the date until which counts the timer is reached, the next value date which will count down the timer is incremented by the value `periodInterval` | boolean | false | | 112 | | `periodInterval` | the period of the timer in `periodUnit` (if `periodic` is set to `true`) | integer | 7 | >0 | 113 | | `periodUnit` | the unit of measurement period timer | string | 'd' | 'd', 'h', 'm', 's' | 114 | 115 | ## Methods 116 | 117 | The use of the methods has the following syntax: 118 | 119 | ```javascript 120 | $('.your_selector_to_countdown').syotimer(nameOfMethod, param1, param2, ... , paramN); 121 | ``` 122 | 123 | ### setOption 124 | 125 | `setOption` - assigns a value to the option 126 | 127 | **Parameters:** 128 | 129 | 1. Name of option 130 | 1. Value 131 | 132 | **Code examples:** 133 | 134 | ```javascript 135 | $(".your_selector_to_countdown").syotimer("setOption", "effectType", "opacity"); 136 | ``` 137 | 138 | ## Localization 139 | 140 | ### Default languages 141 | 142 | By default the supported plugin languages: 143 | 144 | | Language | Value of `lang` option | 145 | | ---------- | ---------------------- | 146 | | English | 'eng' | 147 | | Russian | 'rus' | 148 | | Spanish | 'spa' | 149 | | Portuguese | 'por' | 150 | | Hebrew | 'heb' | 151 | 152 | ### Adding new language 153 | 154 | It is very simple to execute localization of a plugin under the language. 155 | You need to add the translations of signatures to timer elements as the parameter of an object of `$.syotimerLang`. 156 | Then you need determine a new language in the syotimer options. For example we will add support of Spanish 157 | (though this language is supported by default): 158 | 159 | ```javascript 160 | $.syotimerLang.spa = { 161 | seconds: ["segundo", "segundos"], 162 | minute: ["minuto", "minutos"], 163 | hour: ["hora", "horas"], 164 | day: ["dia", "dias"], 165 | }; 166 | 167 | $(".your_selector_to_countdown").syotimer({ 168 | lang: "spa", 169 | }); 170 | ``` 171 | 172 | ### Inducement of a noun after a numeral 173 | 174 | At the majority of languages a simple algorithm of determination of inducement of a noun after a numeral. 175 | If numeral is equal `1` then need input first element from array. Otherwise - second element. 176 | 177 | But there are languages in which more difficult rules of determination of the correct inducement of nouns after a numeral (for example, Russian). 178 | 179 | For example, consider a completely synthetic language (let it be called "Nenglish"). 180 | It is very similar to English but there are significant differences in the spelling of nouns after numerals. 181 | Namely, the difference in the suffixes of these nouns: 182 | 183 | - if the number ends with the digit `1` then to the noun added the suffix "one" (21 secondone, 1 minuteone, ...); 184 | - if the number ends with the digit `5` then the suffix is equal "five" (35 hourfive, 5 secondfive); 185 | - otherwise the suffix is equal to "s" (24 minutes, 3 days). 186 | 187 | To add a Nenglish in Syotimer need first add all possible variants of a writing of the captions of the items of the plugin. 188 | The abbreviated name of the language will take "neng": 189 | 190 | ```javascript 191 | $.syotimerLang.neng = { 192 | second: ["secondone", "secondfive", "seconds"], 193 | minute: ["minuteone", "minutefive", "minutes"], 194 | hour: ["hourone", "hourfive", "hours"], 195 | day: ["dayone", "dayfive", "days"], 196 | handler: function nengNumeral(number, words) { 197 | var lastDigit = number % 10; 198 | var index = 2; 199 | if (lastDigit === 1) { 200 | index = 0; 201 | } else if (lastDigit === 5) { 202 | index = 1; 203 | } 204 | return words[index]; 205 | }, 206 | }; 207 | ``` 208 | 209 | The "handler" must contain a function that receive the two arguments: a number and an array of nouns. 210 | This method should return the the noun. 211 | 212 | Then only have to specify the language when you create the instance Syotimer: 213 | 214 | ```javascript 215 | $(".your_selector_to_countdown").syotimer({ 216 | lang: "neng", 217 | }); 218 | ``` 219 | 220 | ## Requirements 221 | 222 | jQuery SyoTimer Plugin has been tested with jQuery 1.7+ on all major browsers: 223 | 224 | - Firefox 2+ (Win, Mac, Linux); 225 | - IE8+ (Win); 226 | - Chrome 6+ (Win, Mac, Linux, Android, iPhone); 227 | - Safari 3.2+ (Win, Mac, iPhone); 228 | - Opera 8+ (Win, Mac, Linux, Android, iPhone). 229 | 230 | Gratitude to [Yuri Danilchenko](https://github.com/yuri-danilchenko) and Elena Levin. 231 | -------------------------------------------------------------------------------- /assets/images/timer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrfratello/SyoTimer/b7a8c8d5e371c9262d74d0ee141aca601b969efe/assets/images/timer.png -------------------------------------------------------------------------------- /assets/images/timer_long.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrfratello/SyoTimer/b7a8c8d5e371c9262d74d0ee141aca601b969efe/assets/images/timer_long.png -------------------------------------------------------------------------------- /assets/js/service.js: -------------------------------------------------------------------------------- 1 | jQuery(function ($) { 2 | $("pre") 3 | .hide() 4 | .before( 5 | '
' 6 | ); 7 | 8 | $(".toggle-source-code").on("click", function () { 9 | var button = $(this), 10 | code = button.next("pre"); 11 | if (button.hasClass("toggle-source-code_action_show")) { 12 | button 13 | .removeClass("toggle-source-code_action_show") 14 | .addClass("toggle-source-code_action_hide"); 15 | code.slideDown(); 16 | } else if (button.hasClass("toggle-source-code_action_hide")) { 17 | button 18 | .removeClass("toggle-source-code_action_hide") 19 | .addClass("toggle-source-code_action_show"); 20 | code.slideUp(); 21 | } 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /assets/js/syotimer.examples.js: -------------------------------------------------------------------------------- 1 | jQuery(function ($) { 2 | /* Simple Timer. The countdown to 20:30 2035.05.09 */ 3 | $('#simple-timer').syotimer({ 4 | date: new Date(2035, 4, 9, 20, 30), 5 | }); 6 | 7 | /* Timer with Head and Foot. Countdown is over */ 8 | $('#expired-timer').syotimer({ 9 | date: new Date(1990, 0), 10 | headTitle: '

Timer with header and footer. Countdown is over

', 11 | footTitle: 'Footer of timer.', 12 | }); 13 | 14 | /* Callback after the end of the countdown timer */ 15 | $('#expired-timer_event').syotimer({ 16 | date: new Date(2000, 11, 31), 17 | headTitle: '

Timer with header and footer. Countdown is over

', 18 | footTitle: 'Footer of timer.', 19 | afterDeadline: function (syotimer) { 20 | syotimer.bodyBlock.html('

The countdown is finished 00:00 2000.12.31

'); 21 | syotimer.headBlock.html('

Callback after the end of the countdown timer

'); 22 | syotimer.footBlock.html( 23 | '' + 'Footer of timer after countdown is finished' + '', 24 | ); 25 | }, 26 | }); 27 | 28 | /* Periodic Timer. Period is equal 3 minutes. Effect of fading in */ 29 | $('#periodic-timer_period_minutes').syotimer({ 30 | date: new Date(2015, 0, 1), 31 | layout: 'hms', 32 | doubleNumbers: false, 33 | effectType: 'opacity', 34 | 35 | periodUnit: 'm', 36 | periodic: true, 37 | periodInterval: 3, 38 | }); 39 | 40 | /* Periodic Timer. Period is equal 10 days */ 41 | $('#periodic-timer_period_days').syotimer({ 42 | date: new Date(2015, 0, 1, 20), 43 | layout: 'hms', 44 | periodic: true, 45 | periodInterval: 10, 46 | periodUnit: 'd', 47 | }); 48 | 49 | /* Demonstrate layout. Period is equal 2 hours. Display only seconds */ 50 | $('#layout-timer_only-seconds').syotimer({ 51 | layout: 's', 52 | periodic: true, 53 | periodInterval: 2, 54 | periodUnit: 'h', 55 | }); 56 | 57 | /* Demonstrate layout. Period is equal 2 days and 5 hours. 58 | Units of countdown in reverse order */ 59 | $('#layout-timer_reversed-units').syotimer({ 60 | layout: 'smhd', 61 | effectType: 'opacity', 62 | 63 | periodic: true, 64 | periodInterval: 53, 65 | periodUnit: 'h', 66 | }); 67 | 68 | /* Demonstrate layout. Period is equal 2 days and 5 hours. 69 | Display only days and minutes in reverse order */ 70 | $('#layout-timer_mixed-units').syotimer({ 71 | layout: 'md', 72 | 73 | periodic: true, 74 | periodInterval: 1777, 75 | periodUnit: 'm', 76 | }); 77 | 78 | /* Periodic Timer. Countdown timer with given time zone */ 79 | $('#periodic-timer_timezone_given').syotimer({ 80 | date: new Date('2000-07-01T18:00:00.000+02:00'), 81 | layout: 'hms', 82 | 83 | periodic: true, 84 | periodInterval: 1, 85 | periodUnit: 'd', 86 | }); 87 | 88 | /** 89 | * Periodic Timer. 90 | * Change options: doubleNumbers, effect type, language 91 | */ 92 | var changeOptionsTimer = $('#periodic-timer_change-options'); 93 | var effectTypeEl = $('#select-effect'); 94 | var formatNumberEl = $('#select-format-number'); 95 | var languageEl = $('#select-language'); 96 | 97 | changeOptionsTimer.syotimer({ 98 | periodic: true, 99 | periodInterval: 10, 100 | periodUnit: 'd', 101 | }); 102 | 103 | effectTypeEl.on('change', function () { 104 | var effectType = $('option:selected', this).val(); 105 | changeOptionsTimer.syotimer('setOption', 'effectType', effectType); 106 | }); 107 | 108 | formatNumberEl.on('change', function () { 109 | var formatNumberValue = $('option:selected', this).val(); 110 | var doubleNumbers = formatNumberValue === 'true'; 111 | changeOptionsTimer.syotimer('setOption', 'doubleNumbers', doubleNumbers); 112 | }); 113 | 114 | languageEl.on('change', function () { 115 | var language = $('option:selected', this).val(); 116 | changeOptionsTimer.syotimer('setOption', 'lang', language); 117 | }); 118 | 119 | /** 120 | * Localization in timer. 121 | * Add new language 122 | */ 123 | 124 | // Adding of a words for signatures of countdown 125 | $.syotimerLang.neng = { 126 | second: ['secondone', 'secondfive', 'seconds'], 127 | minute: ['minuteone', 'minutefive', 'minutes'], 128 | hour: ['hourone', 'hourfive', 'hours'], 129 | day: ['dayone', 'dayfive', 'days'], 130 | // Adding of the handler that selects an index from the list of words 131 | // based on ahead the going number 132 | handler: function nengNumeral(number, words) { 133 | var lastDigit = number % 10; 134 | var index = 2; 135 | if (lastDigit === 1) { 136 | index = 0; 137 | } else if (lastDigit === 5) { 138 | index = 1; 139 | } 140 | return words[index]; 141 | }, 142 | }; 143 | 144 | $('#periodic-timer_localization_new-english').syotimer({ 145 | lang: 'neng', 146 | layout: 'ms', 147 | 148 | periodic: true, 149 | periodInterval: 6, 150 | periodUnit: 'm', 151 | }); 152 | }); 153 | -------------------------------------------------------------------------------- /build/jquery.syotimer.d.ts: -------------------------------------------------------------------------------- 1 | interface SyoTimerInternalOptions { 2 | date: Date | number; 3 | /** 4 | * sets an order of layout of units of the timer: 5 | * days (d) of hours ('h'), minute ('m'), second ('s'). 6 | */ 7 | layout: string; 8 | /** 9 | * `true` - the timer is periodic. 10 | * If the date until which counts the timer is reached, 11 | * the next value date which will count down 12 | * the timer is incremented by the value `periodInterval` 13 | */ 14 | periodic: boolean; 15 | /** 16 | * the period of the timer in `periodUnit` (if `periodic` is set to `true`) 17 | */ 18 | periodInterval: number; 19 | /** 20 | * the unit of measurement period timer 21 | */ 22 | periodUnit: Unit; 23 | /** 24 | * `true` - show hours, minutes and seconds with leading zeros 25 | * (2 hours 5 minutes 4 seconds = 02:05:04) 26 | */ 27 | doubleNumbers: boolean; 28 | /** 29 | * The effect of changing the value of seconds 30 | */ 31 | effectType: SyoTimerEffectType; 32 | /** 33 | * localization of a countdown signatures (days, hours, minutes, seconds) 34 | */ 35 | lang: string; 36 | /** 37 | * text above the countdown (may be as html string) 38 | */ 39 | headTitle: string; 40 | /** 41 | * text under the countdown (may be as html string) 42 | */ 43 | footTitle: string; 44 | afterDeadline(timerBlock: SyoTimerTimerBlock): void; 45 | itemTypes: UnitLong[]; 46 | itemsHas: ItemsHas; 47 | } 48 | 49 | type SyoTimerOptions = Partial>; 50 | 51 | type SyoTimerOptionProps = Exclude; 52 | type SyoTimerOptionValues = Required[SyoTimerOptionProps]; 53 | 54 | interface SyoTimerTimerBlock { 55 | headBlock: JQuery; 56 | bodyBlock: JQuery; 57 | footBlock: JQuery; 58 | } 59 | 60 | type SyoTimerEffectType = 'none' | 'opacity'; 61 | 62 | type SyoTimerMethods = 'setOption'; 63 | 64 | type UnitLong = 'day' | 'hour' | 'minute' | 'second'; 65 | type UnitShort = 'd' | 'h' | 'm' | 's'; 66 | 67 | type Unit = UnitShort | UnitLong; 68 | 69 | type ItemsHas = Record; 70 | 71 | interface LanguageHandler { 72 | (n: number, words: string[]): string; 73 | } 74 | 75 | type LanguageConfigBase = Record; 76 | 77 | interface LanguageConfig extends LanguageConfigBase { 78 | handler?: LanguageHandler; 79 | } 80 | 81 | type SyoTimerLocalization = Record; 82 | 83 | declare global { 84 | interface JQuery { 85 | syotimer(options: SyoTimerOptions): JQuery; 86 | syotimer( 87 | method: SyoTimerMethods, 88 | property: SyoTimerOptionProps, 89 | value: SyoTimerOptionValues, 90 | ): JQuery; 91 | } 92 | 93 | interface JQueryStatic { 94 | syotimerLang: SyoTimerLocalization; 95 | } 96 | } 97 | 98 | export type { SyoTimerLocalization, SyoTimerOptions }; 99 | -------------------------------------------------------------------------------- /build/jquery.syotimer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SyoTimer - jquery countdown plugin 3 | * @version: 3.1.1 4 | * @author: John Syomochkin 5 | * @homepage: https://mrfratello.github.io/SyoTimer 6 | * @repository: git+https://github.com/mrfratello/SyoTimer.git 7 | * @license: under MIT license 8 | */ 9 | (function ($$1) { 10 | 'use strict'; 11 | 12 | $$1.syotimerLang = { 13 | rus: { 14 | second: ['секунда', 'секунды', 'секунд'], 15 | minute: ['минута', 'минуты', 'минут'], 16 | hour: ['час', 'часа', 'часов'], 17 | day: ['день', 'дня', 'дней'], 18 | handler: function rusNumeral(number, words) { 19 | var cases = [2, 0, 1, 1, 1, 2]; 20 | if (number % 100 > 4 && number % 100 < 20) { 21 | return words[2]; 22 | } 23 | var index = cases[number % 10 < 5 ? number % 10 : 5]; 24 | return words[index]; 25 | }, 26 | }, 27 | eng: { 28 | second: ['second', 'seconds'], 29 | minute: ['minute', 'minutes'], 30 | hour: ['hour', 'hours'], 31 | day: ['day', 'days'], 32 | }, 33 | por: { 34 | second: ['segundo', 'segundos'], 35 | minute: ['minuto', 'minutos'], 36 | hour: ['hora', 'horas'], 37 | day: ['dia', 'dias'], 38 | }, 39 | spa: { 40 | second: ['segundo', 'segundos'], 41 | minute: ['minuto', 'minutos'], 42 | hour: ['hora', 'horas'], 43 | day: ['día', 'días'], 44 | }, 45 | heb: { 46 | second: ['שניה', 'שניות'], 47 | minute: ['דקה', 'דקות'], 48 | hour: ['שעה', 'שעות'], 49 | day: ['יום', 'ימים'], 50 | }, 51 | }; 52 | 53 | var DAY = 'day'; 54 | var HOUR = 'hour'; 55 | var MINUTE = 'minute'; 56 | var SECOND = 'second'; 57 | var DAY_IN_SEC = 24 * 60 * 60; 58 | var HOUR_IN_SEC = 60 * 60; 59 | var MINUTE_IN_SEC = 60; 60 | var LAYOUT_TYPES = { 61 | d: DAY, 62 | h: HOUR, 63 | m: MINUTE, 64 | s: SECOND, 65 | }; 66 | var unitLinkedList = { 67 | list: [SECOND, MINUTE, HOUR, DAY], 68 | next: function (current) { 69 | var currentIndex = this.list.indexOf(current); 70 | return currentIndex < this.list.length ? this.list[currentIndex + 1] : null; 71 | }, 72 | prev: function (current) { 73 | var currentIndex = this.list.indexOf(current); 74 | return currentIndex > 0 ? this.list[currentIndex - 1] : null; 75 | }, 76 | }; 77 | var defaultItemsHas = { 78 | second: false, 79 | minute: false, 80 | hour: false, 81 | day: false, 82 | }; 83 | var defaultOptions = { 84 | date: 0, 85 | layout: 'dhms', 86 | periodic: false, 87 | periodInterval: 7, 88 | periodUnit: 'd', 89 | doubleNumbers: true, 90 | effectType: 'none', 91 | lang: 'eng', 92 | headTitle: '', 93 | footTitle: '', 94 | afterDeadline: function (timerBlock) { 95 | timerBlock.bodyBlock.html('

The countdown is finished!

'); 96 | }, 97 | itemTypes: ['day', 'hour', 'minute', 'second'], 98 | itemsHas: $$1.extend({}, defaultItemsHas), 99 | }; 100 | 101 | /** 102 | * Determine a unit of period in milliseconds 103 | */ 104 | function getPeriodUnit(periodUnit) { 105 | switch (periodUnit) { 106 | case 'd': 107 | case DAY: 108 | return DAY_IN_SEC; 109 | case 'h': 110 | case HOUR: 111 | return HOUR_IN_SEC; 112 | case 'm': 113 | case MINUTE: 114 | return MINUTE_IN_SEC; 115 | case 's': 116 | case SECOND: 117 | default: 118 | return 1; 119 | } 120 | } 121 | /** 122 | * Formation of numbers with leading zeros 123 | */ 124 | function format2(numb, isUse) { 125 | return numb <= 9 && !!isUse ? "0".concat(numb) : String(numb); 126 | } 127 | function getItemTypesByLayout(layout) { 128 | var itemTypes = []; 129 | for (var i = 0; i < layout.length; i += 1) { 130 | itemTypes.push(LAYOUT_TYPES[layout[i]]); 131 | } 132 | return itemTypes; 133 | } 134 | /** 135 | * Getting count of units to deadline 136 | */ 137 | function getUnitsToDeadLine(secondsToDeadLine) { 138 | var remainsSeconds = secondsToDeadLine; 139 | var unit = DAY; 140 | var unitsToDeadLine = { 141 | day: 0, 142 | hour: 0, 143 | minute: 0, 144 | second: 0, 145 | }; 146 | do { 147 | var unitInMilliSec = getPeriodUnit(unit); 148 | unitsToDeadLine[unit] = Math.floor(remainsSeconds / unitInMilliSec); 149 | remainsSeconds %= unitInMilliSec; 150 | // eslint-disable-next-line no-cond-assign 151 | } while ((unit = unitLinkedList.prev(unit))); 152 | return unitsToDeadLine; 153 | } 154 | /** 155 | * Return once cell DOM of countdown: day, hour, minute, second 156 | */ 157 | function getTimerItem() { 158 | var timerCellValue = $('
', { 159 | class: 'syotimer-cell__value', 160 | text: '0', 161 | }); 162 | var timerCellUnit = $('
', { class: 'syotimer-cell__unit' }); 163 | var timerCell = $('
', { class: 'syotimer-cell' }); 164 | timerCell.append(timerCellValue).append(timerCellUnit); 165 | return timerCell; 166 | } 167 | /** 168 | * Getting count of seconds to deadline 169 | */ 170 | function getSecondsToDeadLine(differenceInMilliSec, options) { 171 | var differenceInSeconds = differenceInMilliSec / 1000; 172 | differenceInSeconds = Math.floor(differenceInSeconds); 173 | if (!options.periodic) 174 | return differenceInSeconds; 175 | var differenceInUnit; 176 | var periodUnitInSeconds = getPeriodUnit(options.periodUnit); 177 | var fullTimeUnitsBetween = differenceInMilliSec / (periodUnitInSeconds * 1000); 178 | fullTimeUnitsBetween = Math.ceil(fullTimeUnitsBetween); 179 | fullTimeUnitsBetween = Math.abs(fullTimeUnitsBetween); 180 | if (differenceInSeconds >= 0) { 181 | differenceInUnit = fullTimeUnitsBetween % options.periodInterval; 182 | differenceInUnit = differenceInUnit === 0 ? options.periodInterval : differenceInUnit; 183 | differenceInUnit -= 1; 184 | } 185 | else { 186 | differenceInUnit = options.periodInterval - (fullTimeUnitsBetween % options.periodInterval); 187 | } 188 | var additionalInUnit = differenceInSeconds % periodUnitInSeconds; 189 | // fix когда дедлайн раньше текущей даты, 190 | // возникает баг с неправильным расчетом интервала при different пропорциональной periodUnit 191 | if (additionalInUnit === 0 && differenceInSeconds < 0) { 192 | differenceInUnit -= 1; 193 | } 194 | var secondsToDeadLine = Math.abs(differenceInUnit * periodUnitInSeconds + additionalInUnit); 195 | return secondsToDeadLine; 196 | } 197 | /** 198 | * Universal function for get correct inducement of nouns after a numeral (`number`) 199 | */ 200 | var universal = function (n, words) { return (n === 1 ? words[0] : words[1]); }; 201 | /** 202 | * Getting the correct declension of words after numerals 203 | */ 204 | function getNumeral(n, lang, unit) { 205 | var handler = $.syotimerLang[lang].handler || universal; 206 | var words = $.syotimerLang[lang][unit]; 207 | return handler(n, words); 208 | } 209 | 210 | var SyoTimer = /** @class */ (function () { 211 | function SyoTimer(element, options) { 212 | this.element = $$1(element); 213 | this.element.data('syotimer-options', options); 214 | this.render(); 215 | } 216 | /** 217 | * Rendering base elements of countdown 218 | * @private 219 | */ 220 | SyoTimer.prototype.render = function () { 221 | var options = this.element.data('syotimer-options'); 222 | var timerItem = getTimerItem(); 223 | var headBlock = $$1('
', { class: 'syotimer__head' }).html(options.headTitle); 224 | var bodyBlock = $$1('
', { class: 'syotimer__body' }); 225 | var footBlock = $$1('
', { class: 'syotimer__footer' }).html(options.footTitle); 226 | var itemBlocks = {}; 227 | for (var i = 0; i < options.itemTypes.length; i += 1) { 228 | var item = timerItem.clone(); 229 | item.addClass("syotimer-cell_type_".concat(options.itemTypes[i])); 230 | bodyBlock.append(item); 231 | itemBlocks[options.itemTypes[i]] = item; 232 | } 233 | var timerBlocks = { headBlock: headBlock, bodyBlock: bodyBlock, footBlock: footBlock }; 234 | this.element 235 | .data('syotimer-blocks', timerBlocks) 236 | .data('syotimer-items', itemBlocks) 237 | .addClass('syotimer') 238 | .append(headBlock) 239 | .append(bodyBlock) 240 | .append(footBlock); 241 | }; 242 | /** 243 | * Handler called per seconds while countdown is not over 244 | */ 245 | SyoTimer.prototype.tick = function () { 246 | var options = this.element.data('syotimer-options'); 247 | $$1('.syotimer-cell > .syotimer-cell__value', this.element).css('opacity', 1); 248 | var currentTime = new Date().getTime(); 249 | var deadLineTime = options.date instanceof Date ? options.date.getTime() : options.date; 250 | var differenceInMilliSec = deadLineTime - currentTime; 251 | var secondsToDeadLine = getSecondsToDeadLine(differenceInMilliSec, options); 252 | if (secondsToDeadLine >= 0) { 253 | this.refreshUnitsDom(secondsToDeadLine); 254 | this.applyEffectSwitch(options.effectType); 255 | } 256 | else { 257 | var elementBox = $$1.extend(this.element, this.element.data('syotimer-blocks')); 258 | options.afterDeadline(elementBox); 259 | } 260 | }; 261 | /** 262 | * Refresh unit DOM of countdown 263 | * @private 264 | */ 265 | SyoTimer.prototype.refreshUnitsDom = function (secondsToDeadLine) { 266 | var options = this.element.data('syotimer-options'); 267 | var itemBlocks = this.element.data('syotimer-items'); 268 | var unitList = options.itemTypes; 269 | var unitsToDeadLine = getUnitsToDeadLine(secondsToDeadLine); 270 | if (!options.itemsHas.day) { 271 | unitsToDeadLine.hour += unitsToDeadLine.day * 24; 272 | } 273 | if (!options.itemsHas.hour) { 274 | unitsToDeadLine.minute += unitsToDeadLine.hour * 60; 275 | } 276 | if (!options.itemsHas.minute) { 277 | unitsToDeadLine.second += unitsToDeadLine.minute * 60; 278 | } 279 | for (var i = 0; i < unitList.length; i += 1) { 280 | var unit = unitList[i]; 281 | var unitValue = unitsToDeadLine[unit]; 282 | var itemBlock = itemBlocks[unit]; 283 | itemBlock.data('syotimer-unit-value', unitValue); 284 | $$1('.syotimer-cell__value', itemBlock).html(format2(unitValue, unit !== DAY ? options.doubleNumbers : false)); 285 | $$1('.syotimer-cell__unit', itemBlock).html(getNumeral(unitValue, options.lang, unit)); 286 | } 287 | }; 288 | /** 289 | * Applying effect of changing numbers 290 | * @private 291 | */ 292 | SyoTimer.prototype.applyEffectSwitch = function (effectType, unit) { 293 | var _this = this; 294 | if (unit === void 0) { unit = SECOND; } 295 | switch (effectType) { 296 | case 'opacity': { 297 | var itemBlocks = this.element.data('syotimer-items'); 298 | var unitItemBlock = itemBlocks[unit]; 299 | if (unitItemBlock) { 300 | var nextUnit = unitLinkedList.next(unit); 301 | var unitValue = unitItemBlock.data('syotimer-unit-value'); 302 | $$1('.syotimer-cell__value', unitItemBlock).animate({ opacity: 0.1 }, 1000, 'linear', function () { 303 | return _this.tick(); 304 | }); 305 | if (nextUnit && unitValue === 0) { 306 | this.applyEffectSwitch(effectType, nextUnit); 307 | } 308 | } 309 | return; 310 | } 311 | case 'none': 312 | default: { 313 | setTimeout(function () { return _this.tick(); }, 1000); 314 | } 315 | } 316 | }; 317 | return SyoTimer; 318 | }()); 319 | function mapSyoTimer(elements, inputOptions) { 320 | var options = $$1.extend({}, defaultOptions, inputOptions || {}); 321 | options.itemTypes = getItemTypesByLayout(options.layout); 322 | options.itemsHas = $$1.extend({}, defaultItemsHas); 323 | for (var i = 0; i < options.itemTypes.length; i += 1) { 324 | options.itemsHas[options.itemTypes[i]] = true; 325 | } 326 | return elements.each(function init() { 327 | var timer = new SyoTimer(this, options); 328 | timer.tick(); 329 | }); 330 | } 331 | 332 | var methods = { 333 | setOption: function (name, value) { 334 | var elementBox = $$1(this); 335 | var options = elementBox.data('syotimer-options'); 336 | if (Object.prototype.hasOwnProperty.call(options, name)) { 337 | options[name] = value; 338 | elementBox.data('syotimer-options', options); 339 | } 340 | }, 341 | }; 342 | $$1.fn.extend({ 343 | syotimer: function (options, property, value) { 344 | if (typeof options === 'string' && options === 'setOption') { 345 | return this.each(function method() { 346 | methods[options].apply(this, [property, value]); 347 | }); 348 | } 349 | if (options === null || options === undefined || typeof options === 'object') { 350 | return mapSyoTimer(this, options); 351 | } 352 | return $$1.error('SyoTimer. Error in call methods: methods is not exist'); 353 | }, 354 | }); 355 | 356 | })(jQuery); 357 | -------------------------------------------------------------------------------- /build/jquery.syotimer.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SyoTimer v.3.1.1 | under MIT license 3 | * https://mrfratello.github.io/SyoTimer 4 | */ 5 | !function(e){"use strict";e.syotimerLang={rus:{second:["секунда","секунды","секунд"],minute:["минута","минуты","минут"],hour:["час","часа","часов"],day:["день","дня","дней"],handler:function(e,t){return e%100>4&&e%100<20?t[2]:t[[2,0,1,1,1,2][e%10<5?e%10:5]]}},eng:{second:["second","seconds"],minute:["minute","minutes"],hour:["hour","hours"],day:["day","days"]},por:{second:["segundo","segundos"],minute:["minuto","minutos"],hour:["hora","horas"],day:["dia","dias"]},spa:{second:["segundo","segundos"],minute:["minuto","minutos"],hour:["hora","horas"],day:["día","días"]},heb:{second:["שניה","שניות"],minute:["דקה","דקות"],hour:["שעה","שעות"],day:["יום","ימים"]}};var t="day",i="hour",n="minute",o="second",s=86400,r=3600,a=60,d={d:t,h:i,m:n,s:o},l={list:[o,n,i,t],next:function(e){var t=this.list.indexOf(e);return t0?this.list[t-1]:null}},u={second:!1,minute:!1,hour:!1,day:!1},c={date:0,layout:"dhms",periodic:!1,periodInterval:7,periodUnit:"d",doubleNumbers:!0,effectType:"none",lang:"eng",headTitle:"",footTitle:"",afterDeadline:function(e){e.bodyBlock.html('

The countdown is finished!

')},itemTypes:["day","hour","minute","second"],itemsHas:e.extend({},u)};function m(e){switch(e){case"d":case t:return s;case"h":case i:return r;case"m":case n:return a;default:return 1}}var h=function(e,t){return 1===e?t[0]:t[1]};function y(e,t,i){return($.syotimerLang[t].handler||h)(e,$.syotimerLang[t][i])}var p=function(){function i(t,i){this.element=e(t),this.element.data("syotimer-options",i),this.render()}return i.prototype.render=function(){for(var t,i,n,o=this.element.data("syotimer-options"),s=(t=$("
",{class:"syotimer-cell__value",text:"0"}),i=$("
",{class:"syotimer-cell__unit"}),(n=$("
",{class:"syotimer-cell"})).append(t).append(i),n),r=e("
",{class:"syotimer__head"}).html(o.headTitle),a=e("
",{class:"syotimer__body"}),d=e("
",{class:"syotimer__footer"}).html(o.footTitle),l={},u=0;u .syotimer-cell__value",this.element).css("opacity",1);var i=(new Date).getTime(),n=function(e,t){var i,n=e/1e3;if(n=Math.floor(n),!t.periodic)return n;var o=m(t.periodUnit),s=e/(1e3*o);s=Math.ceil(s),s=Math.abs(s),n>=0?(i=0==(i=s%t.periodInterval)?t.periodInterval:i,i-=1):i=t.periodInterval-s%t.periodInterval;var r=n%o;return 0===r&&n<0&&(i-=1),Math.abs(i*o+r)}((t.date instanceof Date?t.date.getTime():t.date)-i,t);if(n>=0)this.refreshUnitsDom(n),this.applyEffectSwitch(t.effectType);else{var o=e.extend(this.element,this.element.data("syotimer-blocks"));t.afterDeadline(o)}},i.prototype.refreshUnitsDom=function(i){var n,o,s=this.element.data("syotimer-options"),r=this.element.data("syotimer-items"),a=s.itemTypes,d=function(e){var i=e,n=t,o={day:0,hour:0,minute:0,second:0};do{var s=m(n);o[n]=Math.floor(i/s),i%=s}while(n=l.prev(n));return o}(i);s.itemsHas.day||(d.hour+=24*d.day),s.itemsHas.hour||(d.minute+=60*d.hour),s.itemsHas.minute||(d.second+=60*d.minute);for(var u=0;u 4 && number % 100 < 20) {\n return words[2];\n }\n const index = cases[number % 10 < 5 ? number % 10 : 5];\n return words[index];\n },\n },\n eng: {\n second: ['second', 'seconds'],\n minute: ['minute', 'minutes'],\n hour: ['hour', 'hours'],\n day: ['day', 'days'],\n },\n por: {\n second: ['segundo', 'segundos'],\n minute: ['minuto', 'minutos'],\n hour: ['hora', 'horas'],\n day: ['dia', 'dias'],\n },\n spa: {\n second: ['segundo', 'segundos'],\n minute: ['minuto', 'minutos'],\n hour: ['hora', 'horas'],\n day: ['día', 'días'],\n },\n heb: {\n second: ['שניה', 'שניות'],\n minute: ['דקה', 'דקות'],\n hour: ['שעה', 'שעות'],\n day: ['יום', 'ימים'],\n },\n};\n","import $ from 'jquery';\nimport type { SyoTimerInternalOptions, ItemsHas, LinkedList, UnitLong, UnitShort } from './types';\n\nexport const DAY: UnitLong = 'day';\nexport const HOUR: UnitLong = 'hour';\nexport const MINUTE: UnitLong = 'minute';\nexport const SECOND: UnitLong = 'second';\nexport const DAY_IN_SEC = 24 * 60 * 60;\nexport const HOUR_IN_SEC = 60 * 60;\nexport const MINUTE_IN_SEC = 60;\nexport const LAYOUT_TYPES: Record = {\n d: DAY,\n h: HOUR,\n m: MINUTE,\n s: SECOND,\n};\n\nexport const unitLinkedList: LinkedList = {\n list: [SECOND, MINUTE, HOUR, DAY],\n next(current) {\n const currentIndex = this.list.indexOf(current);\n return currentIndex < this.list.length ? this.list[currentIndex + 1] : null;\n },\n prev(current) {\n const currentIndex = this.list.indexOf(current);\n return currentIndex > 0 ? this.list[currentIndex - 1] : null;\n },\n};\n\nexport const defaultItemsHas: ItemsHas = {\n second: false,\n minute: false,\n hour: false,\n day: false,\n};\n\nexport const defaultOptions: SyoTimerInternalOptions = {\n date: 0,\n layout: 'dhms',\n periodic: false,\n periodInterval: 7,\n periodUnit: 'd',\n doubleNumbers: true,\n effectType: 'none',\n lang: 'eng',\n headTitle: '',\n footTitle: '',\n afterDeadline: (timerBlock) => {\n timerBlock.bodyBlock.html('

The countdown is finished!

');\n },\n itemTypes: ['day', 'hour', 'minute', 'second'],\n itemsHas: $.extend({}, defaultItemsHas),\n};\n","import {\n DAY,\n HOUR,\n MINUTE,\n SECOND,\n DAY_IN_SEC,\n HOUR_IN_SEC,\n MINUTE_IN_SEC,\n LAYOUT_TYPES,\n unitLinkedList,\n} from './constants';\nimport type { LanguageHandler, SyoTimerInternalOptions, Unit, UnitLong, UnitShort } from './types';\n\n/**\n * Determine a unit of period in milliseconds\n */\nfunction getPeriodUnit(periodUnit: Unit) {\n switch (periodUnit) {\n case 'd':\n case DAY:\n return DAY_IN_SEC;\n case 'h':\n case HOUR:\n return HOUR_IN_SEC;\n case 'm':\n case MINUTE:\n return MINUTE_IN_SEC;\n case 's':\n case SECOND:\n default:\n return 1;\n }\n}\n\n/**\n * Formation of numbers with leading zeros\n */\nexport function format2(numb: number, isUse?: boolean) {\n return numb <= 9 && !!isUse ? `0${numb}` : String(numb);\n}\n\nexport function getItemTypesByLayout(layout: string) {\n const itemTypes = [] as UnitLong[];\n for (let i = 0; i < layout.length; i += 1) {\n itemTypes.push(LAYOUT_TYPES[layout[i] as UnitShort]);\n }\n return itemTypes;\n}\n\n/**\n * Getting count of units to deadline\n */\nexport function getUnitsToDeadLine(secondsToDeadLine: number) {\n let remainsSeconds = secondsToDeadLine;\n let unit: UnitLong | null = DAY;\n const unitsToDeadLine: Record = {\n day: 0,\n hour: 0,\n minute: 0,\n second: 0,\n };\n do {\n const unitInMilliSec = getPeriodUnit(unit);\n unitsToDeadLine[unit] = Math.floor(remainsSeconds / unitInMilliSec);\n remainsSeconds %= unitInMilliSec;\n // eslint-disable-next-line no-cond-assign\n } while ((unit = unitLinkedList.prev(unit)));\n return unitsToDeadLine;\n}\n\n/**\n * Return once cell DOM of countdown: day, hour, minute, second\n */\nexport function getTimerItem() {\n const timerCellValue = $('
', {\n class: 'syotimer-cell__value',\n text: '0',\n });\n const timerCellUnit = $('
', { class: 'syotimer-cell__unit' });\n const timerCell = $('
', { class: 'syotimer-cell' });\n timerCell.append(timerCellValue).append(timerCellUnit);\n return timerCell;\n}\n\n/**\n * Getting count of seconds to deadline\n */\nexport function getSecondsToDeadLine(\n differenceInMilliSec: number,\n options: SyoTimerInternalOptions,\n) {\n let differenceInSeconds = differenceInMilliSec / 1000;\n differenceInSeconds = Math.floor(differenceInSeconds);\n\n if (!options.periodic) return differenceInSeconds;\n\n let differenceInUnit: number;\n const periodUnitInSeconds = getPeriodUnit(options.periodUnit);\n let fullTimeUnitsBetween = differenceInMilliSec / (periodUnitInSeconds * 1000);\n fullTimeUnitsBetween = Math.ceil(fullTimeUnitsBetween);\n fullTimeUnitsBetween = Math.abs(fullTimeUnitsBetween);\n if (differenceInSeconds >= 0) {\n differenceInUnit = fullTimeUnitsBetween % options.periodInterval;\n differenceInUnit = differenceInUnit === 0 ? options.periodInterval : differenceInUnit;\n differenceInUnit -= 1;\n } else {\n differenceInUnit = options.periodInterval - (fullTimeUnitsBetween % options.periodInterval);\n }\n\n const additionalInUnit = differenceInSeconds % periodUnitInSeconds;\n // fix когда дедлайн раньше текущей даты,\n // возникает баг с неправильным расчетом интервала при different пропорциональной periodUnit\n if (additionalInUnit === 0 && differenceInSeconds < 0) {\n differenceInUnit -= 1;\n }\n const secondsToDeadLine = Math.abs(differenceInUnit * periodUnitInSeconds + additionalInUnit);\n return secondsToDeadLine;\n}\n\n/**\n * Universal function for get correct inducement of nouns after a numeral (`number`)\n */\nconst universal: LanguageHandler = (n: number, words: string[]) => (n === 1 ? words[0] : words[1]);\n\n/**\n * Getting the correct declension of words after numerals\n */\nexport function getNumeral(n: number, lang: string, unit: UnitLong) {\n const handler: LanguageHandler = $.syotimerLang[lang].handler || universal;\n const words: string[] = $.syotimerLang[lang][unit];\n return handler(n, words);\n}\n","import $ from 'jquery';\nimport { defaultOptions, defaultItemsHas, DAY, SECOND, unitLinkedList } from './constants';\nimport {\n getItemTypesByLayout,\n getNumeral,\n getSecondsToDeadLine,\n getTimerItem,\n getUnitsToDeadLine,\n format2,\n} from './utils';\nimport type {\n SyoTimerOptions,\n SyoTimerInternalOptions,\n SyoTimerItemBlocks,\n SyoTimerEffectType,\n UnitLong,\n} from './types';\n\nexport class SyoTimer {\n element: JQuery;\n\n constructor(element: HTMLElement, options: SyoTimerInternalOptions) {\n this.element = $(element);\n this.element.data('syotimer-options', options);\n this.render();\n }\n\n /**\n * Rendering base elements of countdown\n * @private\n */\n private render() {\n const options = this.element.data('syotimer-options') as SyoTimerInternalOptions;\n\n const timerItem = getTimerItem();\n const headBlock = $('
', { class: 'syotimer__head' }).html(options.headTitle);\n const bodyBlock = $('
', { class: 'syotimer__body' });\n const footBlock = $('
', { class: 'syotimer__footer' }).html(options.footTitle);\n const itemBlocks: SyoTimerItemBlocks = {};\n\n for (let i = 0; i < options.itemTypes.length; i += 1) {\n const item = timerItem.clone();\n\n item.addClass(`syotimer-cell_type_${options.itemTypes[i]}`);\n bodyBlock.append(item);\n\n itemBlocks[options.itemTypes[i]] = item;\n }\n\n const timerBlocks = { headBlock, bodyBlock, footBlock };\n\n this.element\n .data('syotimer-blocks', timerBlocks)\n .data('syotimer-items', itemBlocks)\n .addClass('syotimer')\n .append(headBlock)\n .append(bodyBlock)\n .append(footBlock);\n }\n\n /**\n * Handler called per seconds while countdown is not over\n */\n tick() {\n const options = this.element.data('syotimer-options') as SyoTimerInternalOptions;\n $('.syotimer-cell > .syotimer-cell__value', this.element).css('opacity', 1);\n const currentTime = new Date().getTime();\n const deadLineTime = options.date instanceof Date ? options.date.getTime() : options.date;\n const differenceInMilliSec = deadLineTime - currentTime;\n const secondsToDeadLine = getSecondsToDeadLine(differenceInMilliSec, options);\n if (secondsToDeadLine >= 0) {\n this.refreshUnitsDom(secondsToDeadLine);\n this.applyEffectSwitch(options.effectType);\n } else {\n const elementBox = $.extend(this.element, this.element.data('syotimer-blocks'));\n options.afterDeadline(elementBox);\n }\n }\n\n /**\n * Refresh unit DOM of countdown\n * @private\n */\n private refreshUnitsDom(secondsToDeadLine: number) {\n const options = this.element.data('syotimer-options') as SyoTimerInternalOptions;\n const itemBlocks = this.element.data('syotimer-items');\n const unitList = options.itemTypes;\n const unitsToDeadLine = getUnitsToDeadLine(secondsToDeadLine);\n\n if (!options.itemsHas.day) {\n unitsToDeadLine.hour += unitsToDeadLine.day * 24;\n }\n if (!options.itemsHas.hour) {\n unitsToDeadLine.minute += unitsToDeadLine.hour * 60;\n }\n if (!options.itemsHas.minute) {\n unitsToDeadLine.second += unitsToDeadLine.minute * 60;\n }\n for (let i = 0; i < unitList.length; i += 1) {\n const unit = unitList[i];\n const unitValue = unitsToDeadLine[unit];\n const itemBlock = itemBlocks[unit];\n itemBlock.data('syotimer-unit-value', unitValue);\n $('.syotimer-cell__value', itemBlock).html(\n format2(unitValue, unit !== DAY ? options.doubleNumbers : false),\n );\n $('.syotimer-cell__unit', itemBlock).html(getNumeral(unitValue, options.lang, unit));\n }\n }\n\n /**\n * Applying effect of changing numbers\n * @private\n */\n private applyEffectSwitch(effectType: SyoTimerEffectType, unit: UnitLong = SECOND) {\n switch (effectType) {\n case 'opacity': {\n const itemBlocks = this.element.data('syotimer-items');\n const unitItemBlock = itemBlocks[unit];\n if (unitItemBlock) {\n const nextUnit = unitLinkedList.next(unit);\n const unitValue = unitItemBlock.data('syotimer-unit-value');\n $('.syotimer-cell__value', unitItemBlock).animate({ opacity: 0.1 }, 1000, 'linear', () =>\n this.tick(),\n );\n if (nextUnit && unitValue === 0) {\n this.applyEffectSwitch(effectType, nextUnit);\n }\n }\n return;\n }\n case 'none':\n default: {\n setTimeout(() => this.tick(), 1000);\n }\n }\n }\n}\n\nexport default function mapSyoTimer(elements: JQuery, inputOptions?: SyoTimerOptions) {\n const options = $.extend({}, defaultOptions, inputOptions || {});\n options.itemTypes = getItemTypesByLayout(options.layout);\n options.itemsHas = $.extend({}, defaultItemsHas);\n\n for (let i = 0; i < options.itemTypes.length; i += 1) {\n options.itemsHas[options.itemTypes[i]] = true;\n }\n\n return elements.each(function init() {\n const timer = new SyoTimer(this, options);\n timer.tick();\n });\n}\n","import $ from 'jquery';\nimport './localization';\nimport mapSyoTimer from './SyoTimer';\nimport type {\n SyoTimerOptions,\n SyoTimerMethods,\n SyoTimerOptionProps,\n SyoTimerOptionValues,\n} from './types';\n\nconst methods: Record = {\n setOption(name: SyoTimerOptionProps, value: SyoTimerOptionValues) {\n const elementBox = $(this);\n const options = elementBox.data('syotimer-options');\n if (Object.prototype.hasOwnProperty.call(options, name)) {\n options[name] = value;\n elementBox.data('syotimer-options', options);\n }\n },\n};\n\n$.fn.extend({\n syotimer(\n this: JQuery,\n options: SyoTimerOptions | SyoTimerMethods,\n property: SyoTimerOptionProps,\n value: SyoTimerOptionValues,\n ) {\n if (typeof options === 'string' && options === 'setOption') {\n return this.each(function method() {\n methods[options].apply(this, [property, value]);\n });\n }\n if (options === null || options === undefined || typeof options === 'object') {\n return mapSyoTimer(this, options);\n }\n return $.error('SyoTimer. Error in call methods: methods is not exist');\n },\n});\n"],"names":["$$1","syotimerLang","rus","second","minute","hour","day","handler","number","words","eng","por","spa","heb","DAY","HOUR","MINUTE","SECOND","DAY_IN_SEC","HOUR_IN_SEC","MINUTE_IN_SEC","LAYOUT_TYPES","d","h","m","s","unitLinkedList","list","next","current","currentIndex","this","indexOf","length","prev","defaultItemsHas","defaultOptions","date","layout","periodic","periodInterval","periodUnit","doubleNumbers","effectType","lang","headTitle","footTitle","afterDeadline","timerBlock","bodyBlock","html","itemTypes","itemsHas","$","extend","getPeriodUnit","universal","n","getNumeral","unit","SyoTimer","element","options","data","render","prototype","timerCellValue","timerCellUnit","timerCell","timerItem","class","text","append","headBlock","footBlock","itemBlocks","i","item","clone","addClass","concat","timerBlocks","tick","css","currentTime","Date","getTime","secondsToDeadLine","differenceInMilliSec","differenceInUnit","differenceInSeconds","Math","floor","periodUnitInSeconds","fullTimeUnitsBetween","ceil","abs","additionalInUnit","getSecondsToDeadLine","refreshUnitsDom","applyEffectSwitch","elementBox","numb","isUse","unitList","unitsToDeadLine","remainsSeconds","unitInMilliSec","getUnitsToDeadLine","unitValue","itemBlock","String","_this","setTimeout","unitItemBlock","nextUnit","animate","opacity","mapSyoTimer","elements","inputOptions","push","getItemTypesByLayout","each","methods","setOption","name","value","Object","hasOwnProperty","call","fn","syotimer","property","apply","error"],"mappings":"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"} -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | SyoTimerOptions, 3 | SyoTimerLocalization, 4 | SyoTimerMethods, 5 | SyoTimerOptionProps, 6 | SyoTimerOptionValues, 7 | } from './source/types'; 8 | 9 | export type { SyoTimerOptions, SyoTimerLocalization }; 10 | 11 | declare global { 12 | interface JQuery { 13 | syotimer(options: SyoTimerOptions): JQuery; 14 | syotimer( 15 | method: SyoTimerMethods, 16 | property: SyoTimerOptionProps, 17 | value: SyoTimerOptionValues, 18 | ): JQuery; 19 | } 20 | 21 | interface JQueryStatic { 22 | syotimerLang: SyoTimerLocalization; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Examples of usage jQuery SyoTimer Plugin 7 | 98 | 99 | 100 | 101 |

Examples of usage jQuery SyoTimer Plugin

102 | 103 |
104 |
105 |

Simple Timer

106 |
107 |
108 |
/* Simple Timer. The countdown to 20:30 2035.05.09 */
111 | $("#simple-timer").syotimer({
112 |   date: new Date(2035, 4, 9, 20, 30),
113 | });
114 | 
115 |
116 | 117 |
118 |
119 |
/* Timer with Head and Foot. Countdown is over */
122 | $("#expired-timer").syotimer({
123 |   date: new Date(1990, 0),
124 |   headTitle: "<h3>Timer with header and footer. Countdown is over</h3>",
125 |   footTitle: '<i style="color: brown;">Footer of timer.</i>',
126 | });
127 | 
128 |
129 | 130 |
131 |
132 |
/* Callback after the end of the countdown timer */
135 | $("#expired-timer_event").syotimer({
136 |   date: new Date(2000, 11, 31),
137 |   headTitle: "<h3>Timer with header and footer. Countdown is over</h3>",
138 |   footTitle: '<i style="color: brown;">Footer of timer.</i>',
139 |   afterDeadline: function (syotimer) {
140 |     syotimer.bodyBlock.html(
141 |       "<p>The countdown is finished 00:00 2000.12.31</p>"
142 |     );
143 |     syotimer.headBlock.html(
144 |       "<h3>Callback after the end of the countdown timer</h3>"
145 |     );
146 |     syotimer.footBlock.html(
147 |       '<em style="color:brown;">' +
148 |         "Footer of timer after countdown is finished" +
149 |         "</em>"
150 |     );
151 |   },
152 | });
153 | 
154 |
155 | 156 |
157 |

158 | Periodic Timer. The countdown begins first through each 3 minutes. Effect of fading in 159 |

160 |
161 |
/* Periodic Timer. Period is equal 3 minutes. Effect of fading in */
164 | $("#periodic-timer_period_minutes").syotimer({
165 |   date: new Date(2015, 0, 1),
166 |   layout: "hms",
167 |   doubleNumbers: false,
168 |   effectType: "opacity",
169 | 
170 |   periodUnit: "m",
171 |   periodic: true,
172 |   periodInterval: 3,
173 | });
174 | 
175 |
176 | 177 |
178 |

179 | Periodic Timer. The countdown begins first through each 10 days 180 |

181 |

The date equal 20:00 2015.01.01

182 |
183 | 184 |
/* Periodic Timer. Period is equal 10 days */
187 | $("#periodic-timer_period_days").syotimer({
188 |   date: new Date(2015, 0, 1, 20),
189 |   layout: "hms",
190 |   periodic: true,
191 |   periodInterval: 10,
192 |   periodUnit: "d",
193 | });
194 | 
195 |
196 | 197 |
198 |

Display only seconds

199 |

Demonstrate layout. Period is equal 2 hours.

200 |
201 | 202 |
/* Demonstrate layout. Period is equal 2 hours. Display only seconds */
205 | $("#layout-timer_only-seconds").syotimer({
206 |   layout: "s",
207 |   periodic: true,
208 |   periodInterval: 2,
209 |   periodUnit: "h",
210 | });
211 | 
212 |
213 | 214 |
215 |

Units of countdown in reverse order

216 |

Demonstrate layout. Period is equal 2 days and 5 hours.

217 |
218 | 219 |
/* Demonstrate layout. Period is equal 2 days and 5 hours.
222 |     Units of countdown in reverse order */
223 | $("#layout-timer_reversed-units").syotimer({
224 |   layout: "smhd",
225 |   effectType: "opacity",
226 | 
227 |   periodic: true,
228 |   periodInterval: 53,
229 |   periodUnit: "h",
230 | });
231 | 
232 |
233 | 234 |
235 |

Display only days and minutes in reverse order

236 |

237 | Demonstrate layout. Period is equal 1 days, 5 hours and 37 minutes. 238 |

239 |
240 | 241 |
/* Demonstrate layout. Period is equal 2 days and 5 hours.
244 |   Display only days and minutes in reverse order */
245 | $("#layout-timer_mixed-units").syotimer({
246 |   layout: "md",
247 | 
248 |   periodic: true,
249 |   periodInterval: 1777,
250 |   periodUnit: "m",
251 | });
252 | 
253 |
254 | 255 |
256 |

Countdown timer with given timezone

257 |

The deadline is 18:00:00 by the time Rome, Italy (UTC+2)

258 |
259 | 260 |
/* Periodic Timer. Countdown timer with given time zone */
263 | $("#periodic-timer_timezone_given").syotimer({
264 |   date: new Date("2000-07-01T18:00:00.000+02:00"),
265 |   layout: "hms",
266 | 
267 |   periodic: true,
268 |   periodInterval: 1,
269 |   periodUnit: "d",
270 | });
271 | 
272 |
273 | 274 |
275 |
276 |

Change options of countdown

277 |
278 | 279 | 283 | 284 | 285 | 289 | 290 | 291 | 296 |
297 |
298 |
299 | 300 |
/**
304 |  * Periodic Timer.
305 |  * Change options: doubleNumbers, effect type, language
306 |  */
307 | var changeOptionsTimer = $('#periodic-timer_change-options');
308 | var effectTypeEl = $('#select-effect');
309 | var formatNumberEl = $('#select-format-number');
310 | var languageEl = $('#select-language');
311 |  
312 | changeOptionsTimer.syotimer({
313 |   periodic: true,
314 |   periodInterval: 10,
315 |   periodUnit: 'd',
316 | });
317 |  
318 | effectTypeEl.on('change', function () {
319 |   var effectType = $('option:selected', this).val();
320 |   changeOptionsTimer.syotimer('setOption', 'effectType', effectType);
321 | });
322 |  
323 | formatNumberEl.on('change', function () {
324 |   var formatNumberValue = $('option:selected', this).val();
325 |   var doubleNumbers = formatNumberValue === 'true';
326 |   changeOptionsTimer.syotimer('setOption', 'doubleNumbers', doubleNumbers);
327 | });
328 |  
329 | languageEl.on('change', function () {
330 |   var language = $('option:selected', this).val();
331 |   changeOptionsTimer.syotimer('setOption', 'lang', language);
332 | });
333 |
334 | 335 |
336 |

Adding new language

337 |

Demonstrate adding the new language of signatures.

338 |
339 |
340 |

341 | Nenglish is a synthetic language. It is very similar to English but there are significant 342 | differences in the spelling of nouns after numerals. Namely, the difference in the 343 | suffixes of these nouns: 344 |

345 |
    346 |
  • 347 | if the number ends with the digit 1 then to the noun added the suffix "one" 348 | (21 secondone, 1 minuteone, ...); 349 |
  • 350 |
  • 351 | if the number ends with the digit 5 then the suffix is equal "five" (35 352 | hourfive, 5 secondfive); 353 |
  • 354 |
  • otherwise the suffix is equal to "s" (24 minutes, 3 days).
  • 355 |
356 |
357 | 358 |
/**
359 |  * Localization in timer.
360 |  * Add new language
361 |  */
362 | 
363 | // Adding of a words for signatures of countdown
364 | $.syotimerLang.neng = {
365 |   second: ["secondone", "secondfive", "seconds"],
366 |   minute: ["minuteone", "minutefive", "minutes"],
367 |   hour: ["hourone", "hourfive", "hours"],
368 |   day: ["dayone", "dayfive", "days"],
369 |   // Adding of the handler that selects an index from the list of words
370 |   // based on ahead the going number
371 |   handler: function nengNumeral(number, words) {
372 |     var lastDigit = number % 10;
373 |     var index = 2;
374 |     if (lastDigit === 1) {
375 |       index = 0;
376 |     } else if (lastDigit === 5) {
377 |       index = 1;
378 |     }
379 |     return words[index];
380 |   },
381 | };
382 | 
383 | $("#periodic-timer_localization_new-english").syotimer({
384 |   lang: "neng",
385 |   layout: "ms",
386 | 
387 |   periodic: true,
388 |   periodInterval: 6,
389 |   periodUnit: "m",
390 | });
391 | 
392 |
393 | 394 |
395 |
396 |
397 | SyoTimer on GitHub 398 | 399 | Fork me on GitHub 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jquery-syotimer", 3 | "projectName": "SyoTimer", 4 | "version": "3.1.2", 5 | "description": "jquery countdown plugin", 6 | "main": "./build/jquery.syotimer.js", 7 | "types": "./build/jquery.syotimer.d.ts", 8 | "files": [ 9 | "build", 10 | "resources" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/mrfratello/SyoTimer.git" 15 | }, 16 | "scripts": { 17 | "start": "rollup -c -w", 18 | "build": "rollup -c", 19 | "format": "prettier --write \"./source/\"", 20 | "format:check": "prettier --check \"./source/\"", 21 | "eslint": "eslint source/**", 22 | "tslint": "tsc -b --incremental", 23 | "lint": "npm run format:check && npm run tslint && npm run eslint", 24 | "test": "vitest --run", 25 | "test:ui": "vitest --ui", 26 | "release:before": "npm run lint && npm run build && git add .", 27 | "release": "standard-version -a", 28 | "prepare": "husky install" 29 | }, 30 | "keywords": [ 31 | "countdown", 32 | "jquery", 33 | "plugin" 34 | ], 35 | "author": "John Syomochkin ", 36 | "license": "MIT", 37 | "bugs": { 38 | "url": "https://github.com/mrfratello/SyoTimer/issues" 39 | }, 40 | "homepage": "https://mrfratello.github.io/SyoTimer", 41 | "standard-version": { 42 | "scripts": { 43 | "prerelease": "npm run release:before" 44 | } 45 | }, 46 | "devDependencies": { 47 | "@rollup/plugin-terser": "0.4.4", 48 | "@rollup/plugin-typescript": "11.1.6", 49 | "@types/jquery": "3.5.29", 50 | "@typescript-eslint/eslint-plugin": "6.21.0", 51 | "@typescript-eslint/parser": "6.21.0", 52 | "@vitest/ui": "1.3.1", 53 | "eslint": "8.56.0", 54 | "eslint-config-airbnb-base": "15.0.0", 55 | "eslint-config-airbnb-typescript": "17.1.0", 56 | "eslint-config-prettier": "9.1.0", 57 | "eslint-plugin-import": "2.29.1", 58 | "eslint-plugin-prettier": "5.1.3", 59 | "husky": "9.0.11", 60 | "jquery": "3.7.1", 61 | "jsdom": "24.0.0", 62 | "prettier": "3.2.5", 63 | "rollup": "4.12.0", 64 | "rollup-plugin-dts": "6.1.0", 65 | "standard-version": "9.5.0", 66 | "tslib": "2.6.2", 67 | "typescript": "5.3.3", 68 | "vite": "5.1.4", 69 | "vitest": "1.3.1" 70 | }, 71 | "peerDependencies": { 72 | "jquery": "^1.12 || ^2.0 || ^3.0" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /resources/default.css: -------------------------------------------------------------------------------- 1 | .syotimer { 2 | font-family: sans-serif; 3 | text-align: center; 4 | 5 | margin: 30px auto 0; 6 | padding: 0 0 10px; 7 | } 8 | .syotimer-cell { 9 | display: inline-block; 10 | margin: 0 5px; 11 | 12 | width: 79px; 13 | background: url("data:image/png;base64,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") 14 | no-repeat 0 0; 15 | } 16 | .syotimer-cell__value { 17 | font-size: 35px; 18 | color: #80a3ca; 19 | 20 | height: 81px; 21 | line-height: 81px; 22 | 23 | margin: 0 0 5px; 24 | } 25 | .syotimer-cell__unit { 26 | font-size: 12px; 27 | text-transform: uppercase; 28 | } 29 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import terser from '@rollup/plugin-terser'; 2 | import typescript from '@rollup/plugin-typescript'; 3 | import dts from 'rollup-plugin-dts'; 4 | import pkg from './package.json' assert { type: 'json' }; 5 | 6 | export default [ 7 | { 8 | input: 'source/index.ts', 9 | external: ['jquery'], 10 | output: [ 11 | { 12 | file: './build/jquery.syotimer.js', 13 | format: 'iife', 14 | globals: { 15 | jquery: 'jQuery', 16 | }, 17 | banner: `/** 18 | * ${pkg.projectName} - ${pkg.description} 19 | * @version: ${pkg.version} 20 | * @author: ${pkg.author} 21 | * @homepage: ${pkg.homepage} 22 | * @repository: ${pkg.repository.url} 23 | * @license: under MIT license 24 | */`, 25 | }, 26 | { 27 | file: './build/jquery.syotimer.min.js', 28 | format: 'iife', 29 | globals: { 30 | jquery: 'jQuery', 31 | }, 32 | sourcemap: true, 33 | banner: `/** 34 | * ${pkg.projectName} v.${pkg.version} | under MIT license 35 | * ${pkg.homepage} 36 | */`, 37 | 38 | plugins: [ 39 | terser({ 40 | output: { 41 | comments: (_node, { type, value }) => type === 'comment2' && /license/i.test(value), 42 | }, 43 | }), 44 | ], 45 | }, 46 | ], 47 | plugins: [ 48 | typescript({ 49 | tsconfig: './tsconfig.lib.json', 50 | }), 51 | ], 52 | }, 53 | { 54 | input: './index.d.ts', 55 | output: [{ file: 'build/jquery.syotimer.d.ts', format: 'es' }], 56 | plugins: [dts()], 57 | }, 58 | ]; 59 | -------------------------------------------------------------------------------- /source/SyoTimer.ts: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | import { defaultOptions, defaultItemsHas, DAY, SECOND, unitLinkedList } from './constants'; 3 | import { 4 | getItemTypesByLayout, 5 | getNumeral, 6 | getSecondsToDeadLine, 7 | getTimerItem, 8 | getUnitsToDeadLine, 9 | format2, 10 | } from './utils'; 11 | import type { 12 | SyoTimerOptions, 13 | SyoTimerInternalOptions, 14 | SyoTimerItemBlocks, 15 | SyoTimerEffectType, 16 | UnitLong, 17 | } from './types'; 18 | 19 | export class SyoTimer { 20 | element: JQuery; 21 | 22 | constructor(element: HTMLElement, options: SyoTimerInternalOptions) { 23 | this.element = $(element); 24 | this.element.data('syotimer-options', options); 25 | this.render(); 26 | } 27 | 28 | /** 29 | * Rendering base elements of countdown 30 | * @private 31 | */ 32 | private render() { 33 | const options = this.element.data('syotimer-options') as SyoTimerInternalOptions; 34 | 35 | const timerItem = getTimerItem(); 36 | const headBlock = $('
', { class: 'syotimer__head' }).html(options.headTitle); 37 | const bodyBlock = $('
', { class: 'syotimer__body' }); 38 | const footBlock = $('
', { class: 'syotimer__footer' }).html(options.footTitle); 39 | const itemBlocks: SyoTimerItemBlocks = {}; 40 | 41 | for (let i = 0; i < options.itemTypes.length; i += 1) { 42 | const item = timerItem.clone(); 43 | 44 | item.addClass(`syotimer-cell_type_${options.itemTypes[i]}`); 45 | bodyBlock.append(item); 46 | 47 | itemBlocks[options.itemTypes[i]] = item; 48 | } 49 | 50 | const timerBlocks = { headBlock, bodyBlock, footBlock }; 51 | 52 | this.element 53 | .data('syotimer-blocks', timerBlocks) 54 | .data('syotimer-items', itemBlocks) 55 | .addClass('syotimer') 56 | .append(headBlock) 57 | .append(bodyBlock) 58 | .append(footBlock); 59 | } 60 | 61 | /** 62 | * Handler called per seconds while countdown is not over 63 | */ 64 | tick() { 65 | const options = this.element.data('syotimer-options') as SyoTimerInternalOptions; 66 | $('.syotimer-cell > .syotimer-cell__value', this.element).css('opacity', 1); 67 | const currentTime = new Date().getTime(); 68 | const deadLineTime = options.date instanceof Date ? options.date.getTime() : options.date; 69 | const differenceInMilliSec = deadLineTime - currentTime; 70 | const secondsToDeadLine = getSecondsToDeadLine(differenceInMilliSec, options); 71 | if (secondsToDeadLine >= 0) { 72 | this.refreshUnitsDom(secondsToDeadLine); 73 | this.applyEffectSwitch(options.effectType); 74 | } else { 75 | const elementBox = $.extend(this.element, this.element.data('syotimer-blocks')); 76 | options.afterDeadline(elementBox); 77 | } 78 | } 79 | 80 | /** 81 | * Refresh unit DOM of countdown 82 | * @private 83 | */ 84 | private refreshUnitsDom(secondsToDeadLine: number) { 85 | const options = this.element.data('syotimer-options') as SyoTimerInternalOptions; 86 | const itemBlocks = this.element.data('syotimer-items'); 87 | const unitList = options.itemTypes; 88 | const unitsToDeadLine = getUnitsToDeadLine(secondsToDeadLine); 89 | 90 | if (!options.itemsHas.day) { 91 | unitsToDeadLine.hour += unitsToDeadLine.day * 24; 92 | } 93 | if (!options.itemsHas.hour) { 94 | unitsToDeadLine.minute += unitsToDeadLine.hour * 60; 95 | } 96 | if (!options.itemsHas.minute) { 97 | unitsToDeadLine.second += unitsToDeadLine.minute * 60; 98 | } 99 | for (let i = 0; i < unitList.length; i += 1) { 100 | const unit = unitList[i]; 101 | const unitValue = unitsToDeadLine[unit]; 102 | const itemBlock = itemBlocks[unit]; 103 | itemBlock.data('syotimer-unit-value', unitValue); 104 | $('.syotimer-cell__value', itemBlock).html( 105 | format2(unitValue, unit !== DAY ? options.doubleNumbers : false), 106 | ); 107 | $('.syotimer-cell__unit', itemBlock).html(getNumeral(unitValue, options.lang, unit)); 108 | } 109 | } 110 | 111 | /** 112 | * Applying effect of changing numbers 113 | * @private 114 | */ 115 | private applyEffectSwitch(effectType: SyoTimerEffectType, unit: UnitLong = SECOND) { 116 | switch (effectType) { 117 | case 'opacity': { 118 | const itemBlocks = this.element.data('syotimer-items'); 119 | const unitItemBlock = itemBlocks[unit]; 120 | if (unitItemBlock) { 121 | const nextUnit = unitLinkedList.next(unit); 122 | const unitValue = unitItemBlock.data('syotimer-unit-value'); 123 | $('.syotimer-cell__value', unitItemBlock).animate({ opacity: 0.1 }, 1000, 'linear', () => 124 | this.tick(), 125 | ); 126 | if (nextUnit && unitValue === 0) { 127 | this.applyEffectSwitch(effectType, nextUnit); 128 | } 129 | } 130 | return; 131 | } 132 | case 'none': 133 | default: { 134 | setTimeout(() => this.tick(), 1000); 135 | } 136 | } 137 | } 138 | } 139 | 140 | export default function mapSyoTimer(elements: JQuery, inputOptions?: SyoTimerOptions) { 141 | const options = $.extend({}, defaultOptions, inputOptions || {}); 142 | options.itemTypes = getItemTypesByLayout(options.layout); 143 | options.itemsHas = $.extend({}, defaultItemsHas); 144 | 145 | for (let i = 0; i < options.itemTypes.length; i += 1) { 146 | options.itemsHas[options.itemTypes[i]] = true; 147 | } 148 | 149 | return elements.each(function init() { 150 | const timer = new SyoTimer(this, options); 151 | timer.tick(); 152 | }); 153 | } 154 | -------------------------------------------------------------------------------- /source/constants.ts: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | import type { SyoTimerInternalOptions, ItemsHas, LinkedList, UnitLong, UnitShort } from './types'; 3 | 4 | export const DAY: UnitLong = 'day'; 5 | export const HOUR: UnitLong = 'hour'; 6 | export const MINUTE: UnitLong = 'minute'; 7 | export const SECOND: UnitLong = 'second'; 8 | export const DAY_IN_SEC = 24 * 60 * 60; 9 | export const HOUR_IN_SEC = 60 * 60; 10 | export const MINUTE_IN_SEC = 60; 11 | export const LAYOUT_TYPES: Record = { 12 | d: DAY, 13 | h: HOUR, 14 | m: MINUTE, 15 | s: SECOND, 16 | }; 17 | 18 | export const unitLinkedList: LinkedList = { 19 | list: [SECOND, MINUTE, HOUR, DAY], 20 | next(current) { 21 | const currentIndex = this.list.indexOf(current); 22 | return currentIndex < this.list.length ? this.list[currentIndex + 1] : null; 23 | }, 24 | prev(current) { 25 | const currentIndex = this.list.indexOf(current); 26 | return currentIndex > 0 ? this.list[currentIndex - 1] : null; 27 | }, 28 | }; 29 | 30 | export const defaultItemsHas: ItemsHas = { 31 | second: false, 32 | minute: false, 33 | hour: false, 34 | day: false, 35 | }; 36 | 37 | export const defaultOptions: SyoTimerInternalOptions = { 38 | date: 0, 39 | layout: 'dhms', 40 | periodic: false, 41 | periodInterval: 7, 42 | periodUnit: 'd', 43 | doubleNumbers: true, 44 | effectType: 'none', 45 | lang: 'eng', 46 | headTitle: '', 47 | footTitle: '', 48 | afterDeadline: (timerBlock) => { 49 | timerBlock.bodyBlock.html('

The countdown is finished!

'); 50 | }, 51 | itemTypes: ['day', 'hour', 'minute', 'second'], 52 | itemsHas: $.extend({}, defaultItemsHas), 53 | }; 54 | -------------------------------------------------------------------------------- /source/index.ts: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | import './localization'; 3 | import mapSyoTimer from './SyoTimer'; 4 | import type { 5 | SyoTimerOptions, 6 | SyoTimerMethods, 7 | SyoTimerOptionProps, 8 | SyoTimerOptionValues, 9 | } from './types'; 10 | 11 | const methods: Record = { 12 | setOption(name: SyoTimerOptionProps, value: SyoTimerOptionValues) { 13 | const elementBox = $(this); 14 | const options = elementBox.data('syotimer-options'); 15 | if (Object.prototype.hasOwnProperty.call(options, name)) { 16 | options[name] = value; 17 | elementBox.data('syotimer-options', options); 18 | } 19 | }, 20 | }; 21 | 22 | $.fn.extend({ 23 | syotimer( 24 | this: JQuery, 25 | options: SyoTimerOptions | SyoTimerMethods, 26 | property: SyoTimerOptionProps, 27 | value: SyoTimerOptionValues, 28 | ) { 29 | if (typeof options === 'string' && options === 'setOption') { 30 | return this.each(function method() { 31 | methods[options].apply(this, [property, value]); 32 | }); 33 | } 34 | if (options === null || options === undefined || typeof options === 'object') { 35 | return mapSyoTimer(this, options); 36 | } 37 | return $.error('SyoTimer. Error in call methods: methods is not exist'); 38 | }, 39 | }); 40 | -------------------------------------------------------------------------------- /source/localization.ts: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | 3 | $.syotimerLang = { 4 | rus: { 5 | second: ['секунда', 'секунды', 'секунд'], 6 | minute: ['минута', 'минуты', 'минут'], 7 | hour: ['час', 'часа', 'часов'], 8 | day: ['день', 'дня', 'дней'], 9 | handler: function rusNumeral(number, words) { 10 | const cases = [2, 0, 1, 1, 1, 2]; 11 | if (number % 100 > 4 && number % 100 < 20) { 12 | return words[2]; 13 | } 14 | const index = cases[number % 10 < 5 ? number % 10 : 5]; 15 | return words[index]; 16 | }, 17 | }, 18 | eng: { 19 | second: ['second', 'seconds'], 20 | minute: ['minute', 'minutes'], 21 | hour: ['hour', 'hours'], 22 | day: ['day', 'days'], 23 | }, 24 | por: { 25 | second: ['segundo', 'segundos'], 26 | minute: ['minuto', 'minutos'], 27 | hour: ['hora', 'horas'], 28 | day: ['dia', 'dias'], 29 | }, 30 | spa: { 31 | second: ['segundo', 'segundos'], 32 | minute: ['minuto', 'minutos'], 33 | hour: ['hora', 'horas'], 34 | day: ['día', 'días'], 35 | }, 36 | heb: { 37 | second: ['שניה', 'שניות'], 38 | minute: ['דקה', 'דקות'], 39 | hour: ['שעה', 'שעות'], 40 | day: ['יום', 'ימים'], 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /source/types.ts: -------------------------------------------------------------------------------- 1 | export interface SyoTimerInternalOptions { 2 | date: Date | number; 3 | /** 4 | * sets an order of layout of units of the timer: 5 | * days (d) of hours ('h'), minute ('m'), second ('s'). 6 | */ 7 | layout: string; 8 | /** 9 | * `true` - the timer is periodic. 10 | * If the date until which counts the timer is reached, 11 | * the next value date which will count down 12 | * the timer is incremented by the value `periodInterval` 13 | */ 14 | periodic: boolean; 15 | /** 16 | * the period of the timer in `periodUnit` (if `periodic` is set to `true`) 17 | */ 18 | periodInterval: number; 19 | /** 20 | * the unit of measurement period timer 21 | */ 22 | periodUnit: Unit; 23 | /** 24 | * `true` - show hours, minutes and seconds with leading zeros 25 | * (2 hours 5 minutes 4 seconds = 02:05:04) 26 | */ 27 | doubleNumbers: boolean; 28 | /** 29 | * The effect of changing the value of seconds 30 | */ 31 | effectType: SyoTimerEffectType; 32 | /** 33 | * localization of a countdown signatures (days, hours, minutes, seconds) 34 | */ 35 | lang: string; 36 | /** 37 | * text above the countdown (may be as html string) 38 | */ 39 | headTitle: string; 40 | /** 41 | * text under the countdown (may be as html string) 42 | */ 43 | footTitle: string; 44 | afterDeadline(timerBlock: SyoTimerTimerBlock): void; 45 | itemTypes: UnitLong[]; 46 | itemsHas: ItemsHas; 47 | } 48 | 49 | export type SyoTimerOptions = Partial>; 50 | 51 | export type SyoTimerOptionProps = Exclude; 52 | export type SyoTimerOptionValues = Required[SyoTimerOptionProps]; 53 | 54 | export interface SyoTimerTimerBlock { 55 | headBlock: JQuery; 56 | bodyBlock: JQuery; 57 | footBlock: JQuery; 58 | } 59 | 60 | export type SyoTimerItemBlocks = { 61 | [key in UnitLong]?: JQuery; 62 | }; 63 | 64 | export type SyoTimerEffectType = 'none' | 'opacity'; 65 | 66 | export type SyoTimerMethods = 'setOption'; 67 | 68 | export type UnitLong = 'day' | 'hour' | 'minute' | 'second'; 69 | export type UnitShort = 'd' | 'h' | 'm' | 's'; 70 | 71 | export type Unit = UnitShort | UnitLong; 72 | 73 | export interface LinkedList { 74 | list: T[]; 75 | next(current: T): T | null; 76 | prev(current: T): T | null; 77 | } 78 | 79 | export type ItemsHas = Record; 80 | 81 | export interface LanguageHandler { 82 | (n: number, words: string[]): string; 83 | } 84 | 85 | type LanguageConfigBase = Record; 86 | 87 | interface LanguageConfig extends LanguageConfigBase { 88 | handler?: LanguageHandler; 89 | } 90 | 91 | export type SyoTimerLocalization = Record; 92 | 93 | export interface SyoTimerLang extends JQueryStatic { 94 | syotimerLang: SyoTimerLocalization; 95 | } 96 | -------------------------------------------------------------------------------- /source/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import { format2 } from './utils'; 2 | 3 | describe('utils', () => { 4 | describe('format2', () => { 5 | test.each([ 6 | { input: 0, output: '0' }, 7 | { input: 3, output: '3' }, 8 | { input: 10, output: '10' }, 9 | { input: 21, output: '21' }, 10 | { input: 99, output: '99' }, 11 | ])('should return without lead zero: from $input to $output', ({ input, output }) => { 12 | expect(format2(input)).toBe(output); 13 | }); 14 | 15 | test.each([ 16 | { input: 0, output: '00' }, 17 | { input: 3, output: '03' }, 18 | { input: 10, output: '10' }, 19 | { input: 21, output: '21' }, 20 | { input: 99, output: '99' }, 21 | ])('should return with lead zero: from $input to $output', ({ input, output }) => { 22 | expect(format2(input, true)).toBe(output); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /source/utils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DAY, 3 | HOUR, 4 | MINUTE, 5 | SECOND, 6 | DAY_IN_SEC, 7 | HOUR_IN_SEC, 8 | MINUTE_IN_SEC, 9 | LAYOUT_TYPES, 10 | unitLinkedList, 11 | } from './constants'; 12 | import type { LanguageHandler, SyoTimerInternalOptions, Unit, UnitLong, UnitShort } from './types'; 13 | 14 | /** 15 | * Determine a unit of period in milliseconds 16 | */ 17 | function getPeriodUnit(periodUnit: Unit) { 18 | switch (periodUnit) { 19 | case 'd': 20 | case DAY: 21 | return DAY_IN_SEC; 22 | case 'h': 23 | case HOUR: 24 | return HOUR_IN_SEC; 25 | case 'm': 26 | case MINUTE: 27 | return MINUTE_IN_SEC; 28 | case 's': 29 | case SECOND: 30 | default: 31 | return 1; 32 | } 33 | } 34 | 35 | /** 36 | * Formation of numbers with leading zeros 37 | */ 38 | export function format2(numb: number, isUse?: boolean) { 39 | return numb <= 9 && !!isUse ? `0${numb}` : String(numb); 40 | } 41 | 42 | export function getItemTypesByLayout(layout: string) { 43 | const itemTypes = [] as UnitLong[]; 44 | for (let i = 0; i < layout.length; i += 1) { 45 | itemTypes.push(LAYOUT_TYPES[layout[i] as UnitShort]); 46 | } 47 | return itemTypes; 48 | } 49 | 50 | /** 51 | * Getting count of units to deadline 52 | */ 53 | export function getUnitsToDeadLine(secondsToDeadLine: number) { 54 | let remainsSeconds = secondsToDeadLine; 55 | let unit: UnitLong | null = DAY; 56 | const unitsToDeadLine: Record = { 57 | day: 0, 58 | hour: 0, 59 | minute: 0, 60 | second: 0, 61 | }; 62 | do { 63 | const unitInMilliSec = getPeriodUnit(unit); 64 | unitsToDeadLine[unit] = Math.floor(remainsSeconds / unitInMilliSec); 65 | remainsSeconds %= unitInMilliSec; 66 | // eslint-disable-next-line no-cond-assign 67 | } while ((unit = unitLinkedList.prev(unit))); 68 | return unitsToDeadLine; 69 | } 70 | 71 | /** 72 | * Return once cell DOM of countdown: day, hour, minute, second 73 | */ 74 | export function getTimerItem() { 75 | const timerCellValue = $('
', { 76 | class: 'syotimer-cell__value', 77 | text: '0', 78 | }); 79 | const timerCellUnit = $('
', { class: 'syotimer-cell__unit' }); 80 | const timerCell = $('
', { class: 'syotimer-cell' }); 81 | timerCell.append(timerCellValue).append(timerCellUnit); 82 | return timerCell; 83 | } 84 | 85 | /** 86 | * Getting count of seconds to deadline 87 | */ 88 | export function getSecondsToDeadLine( 89 | differenceInMilliSec: number, 90 | options: SyoTimerInternalOptions, 91 | ) { 92 | let differenceInSeconds = differenceInMilliSec / 1000; 93 | differenceInSeconds = Math.floor(differenceInSeconds); 94 | 95 | if (!options.periodic) return differenceInSeconds; 96 | 97 | let differenceInUnit: number; 98 | const periodUnitInSeconds = getPeriodUnit(options.periodUnit); 99 | let fullTimeUnitsBetween = differenceInMilliSec / (periodUnitInSeconds * 1000); 100 | fullTimeUnitsBetween = Math.ceil(fullTimeUnitsBetween); 101 | fullTimeUnitsBetween = Math.abs(fullTimeUnitsBetween); 102 | if (differenceInSeconds >= 0) { 103 | differenceInUnit = fullTimeUnitsBetween % options.periodInterval; 104 | differenceInUnit = differenceInUnit === 0 ? options.periodInterval : differenceInUnit; 105 | differenceInUnit -= 1; 106 | } else { 107 | differenceInUnit = options.periodInterval - (fullTimeUnitsBetween % options.periodInterval); 108 | } 109 | 110 | const additionalInUnit = differenceInSeconds % periodUnitInSeconds; 111 | // fix когда дедлайн раньше текущей даты, 112 | // возникает баг с неправильным расчетом интервала при different пропорциональной periodUnit 113 | if (additionalInUnit === 0 && differenceInSeconds < 0) { 114 | differenceInUnit -= 1; 115 | } 116 | const secondsToDeadLine = Math.abs(differenceInUnit * periodUnitInSeconds + additionalInUnit); 117 | return secondsToDeadLine; 118 | } 119 | 120 | /** 121 | * Universal function for get correct inducement of nouns after a numeral (`number`) 122 | */ 123 | const universal: LanguageHandler = (n: number, words: string[]) => (n === 1 ? words[0] : words[1]); 124 | 125 | /** 126 | * Getting the correct declension of words after numerals 127 | */ 128 | export function getNumeral(n: number, lang: string, unit: UnitLong) { 129 | const handler: LanguageHandler = $.syotimerLang[lang].handler || universal; 130 | const words: string[] = $.syotimerLang[lang][unit]; 131 | return handler(n, words); 132 | } 133 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "baseUrl": "./source", 7 | "isolatedModules": true, 8 | "allowSyntheticDefaultImports": true, 9 | "esModuleInterop": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "resolveJsonModule": true, 12 | "strict": true, 13 | "noImplicitAny": true, 14 | "skipDefaultLibCheck": true, 15 | "skipLibCheck": true 16 | }, 17 | "files": [], 18 | "include": [], 19 | "references": [ 20 | { 21 | "path": "./tsconfig.lib.json" 22 | }, 23 | { 24 | "path": "./tsconfig.spec.json" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./tmp/out-tsc" 5 | }, 6 | "include": ["source/**/*.ts", "index.d.ts"], 7 | "exclude": ["vite.config.ts", "source/**/*.spec.ts", "source/**/*.test.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./tmp/out-tsc", 5 | "types": ["vitest/globals", "vitest/importMeta", "vite/client"] 6 | }, 7 | "include": [ 8 | "vite.config.ts", 9 | "vitest.setup.ts", 10 | "source/**/*.test.ts", 11 | "source/**/*.spec.ts", 12 | "index.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | cache: { 7 | dir: './node_modules/.vitest', 8 | }, 9 | environment: 'jsdom', 10 | include: ['source/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts}'], 11 | }, 12 | }); 13 | --------------------------------------------------------------------------------