├── .editorconfig
├── .github
└── FUNDING.yml
├── .gitignore
├── .npmrc
├── CHANGELOG.md
├── README.md
├── jsconfig.json
├── mdsvex.config.js
├── package.json
├── pnpm-lock.yaml
├── src
├── app.d.ts
├── app.html
├── lib
│ ├── .gitignore
│ ├── components
│ │ ├── Calendar.svelte
│ │ ├── SveltyPicker.svelte
│ │ └── Time.svelte
│ ├── i18n
│ │ └── index.js
│ ├── index.js
│ ├── settings.js
│ ├── types
│ │ └── internal.d.ts
│ └── utils
│ │ ├── actions.js
│ │ ├── constants.js
│ │ ├── custom-element.js
│ │ ├── dateUtils.js
│ │ ├── grid.js
│ │ ├── state.js
│ │ └── transitions.js
├── routes
│ ├── +layout.server.js
│ ├── +layout.svelte
│ ├── +page.svelte.md
│ ├── autocommit-changes
│ │ └── +page.svelte.md
│ ├── disabling-dates
│ │ └── +page.svelte.md
│ ├── global-config
│ │ └── +page.svelte.md
│ ├── localization
│ │ └── +page.svelte.md
│ ├── migration-guide
│ │ └── +page.svelte.md
│ ├── modes-and-formats
│ │ ├── +page.svelte.md
│ │ ├── Tabs.svelte
│ │ ├── format-php.md
│ │ └── format-standard.md
│ ├── properties
│ │ └── +page.svelte.md
│ ├── snippets
│ │ └── +page.svelte.md
│ ├── theme
│ │ └── +page.svelte.md
│ └── working-with-dates
│ │ └── +page.svelte.md
├── style
│ ├── base.css
│ ├── code.css
│ ├── doc.css
│ ├── style.css
│ └── vars.css
└── utils
│ └── codeHighlighter.js
├── static
└── favicon.png
├── svelte.config.js
├── test
├── 01-render.test.js
├── 02-datepicker.test.svelte.js
├── 03-timepicker.test.svelte.js
├── 04-datetime.test.svelte.js
├── 05-daterange.test.svelte.js
└── vitest-setup.js
└── vite.config.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 | [*]
3 | end_of_line = lf
4 | insert_final_newline = true
5 | charset = utf-8
6 | trim_trailing_whitespace = true
7 |
8 | [*.{svelte,js,css,md}]
9 | indent_style = space
10 | indent_size = 2
11 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: mskocik
4 | # patreon: # Replace with a single Patreon username
5 | # open_collective: # Replace with a single Open Collective username
6 | # ko_fi: # Replace with a single Ko-fi username
7 | # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | # liberapay: # Replace with a single Liberapay username
10 | # issuehunt: # Replace with a single IssueHunt username
11 | # otechie: # Replace with a single Otechie username
12 | # lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | # custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
3 | # Output
4 | .output
5 | .vercel
6 | .netlify
7 | .wrangler
8 | /.svelte-kit
9 | /build
10 | /dist
11 |
12 | # OS
13 | .DS_Store
14 | Thumbs.db
15 |
16 | # Env
17 | .env
18 | .env.*
19 | !.env.example
20 | !.env.test
21 |
22 | # Vite
23 | vite.config.js.timestamp-*
24 | vite.config.ts.timestamp-*
25 |
26 | /__old
27 | /docs
28 | *.todo
29 | src/routes/test/*
30 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 6.1
2 |
3 | - breaking: parameters passed to `actionRow` are now object for easier usage
4 | - feat: add `children` slot
5 | - feat: add `.picker-active` class to main component wrapper for easier styling of `children` when popup visible
6 |
7 | ## 6.0
8 |
9 | - breaking: svelte 5 only
10 | - breaking: remove `initialDate` property in favor of function bindings (https://svelte.dev/docs/svelte/bind#Function-bindings)
11 | - breaking: fix wrapper class names typos: `std-component-wrap` to `sdt-component-wrap` and `std-calendar-wrap` to `sdt-calendar-wrap`
12 | - breaking: event emitting logic (on every change there is event emitted, if it's not desired, disable autocommit)
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 📅 Svelty Picker [](https://www.npmjs.org/package/svelty-picker)
2 |
3 | Simple date & time picker implemented in svelte.
4 |
5 | Features:
6 | - date/time/datetime/range picker mode
7 | - various formatting options
8 | - keyboard navigation
9 | - replacable slots
10 | - themable
11 | - customizable disabled dates
12 |
13 | ## ⚙️ Install
14 |
15 | ```js
16 | npm install svelty-picker
17 | ```
18 |
19 | If you need svelte 4 version:
20 |
21 | ```js
22 | npm install svelty-picker@5
23 | ```
24 |
25 | ### Documentation
26 |
27 | For more details check the [documentation](https://svelty-picker.vercel.app/)
28 |
29 | ## 🏆 Thanks to:
30 |
31 | - [Bootstrap datepicker](https://github.com/smalot/bootstrap-datetimepicker/blob/master/js/bootstrap-datetimepicker.js) for some internal date and format handling
32 |
33 | ## Licence
34 |
35 | MIT
36 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "esModuleInterop": true,
5 | "forceConsistentCasingInFileNames": true,
6 | "resolveJsonModule": true,
7 | "skipLibCheck": true,
8 | "sourceMap": true,
9 | "allowJs": true,
10 | "checkJs": true,
11 | "moduleResolution": "bundler",
12 | "strictNullChecks": true,
13 | "exactOptionalPropertyTypes": true
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/mdsvex.config.js:
--------------------------------------------------------------------------------
1 | import { defineMDSveXConfig } from 'mdsvex';
2 | import highlighter from './src/utils/codeHighlighter.js';
3 | import autolinkHeadings from 'rehype-autolink-headings';
4 | import slugPlugin from 'rehype-slug';
5 |
6 | // const __dirname = resolve();
7 |
8 | const config = defineMDSveXConfig({
9 | extensions: ['.svelte.md'],
10 | highlight: {
11 | highlighter,
12 | },
13 | rehypePlugins: [
14 | slugPlugin,
15 | [
16 | autolinkHeadings,
17 | {
18 | behavior: 'wrap',
19 | },
20 | ],
21 | ]
22 | // layout: join(__dirname, './src/lib/components/MarkdownLayout.svelte'),
23 | });
24 |
25 | export default config;
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "svelty-picker",
3 | "description": "Sweet date/time picker written in svelte",
4 | "version": "6.1.5",
5 | "scripts": {
6 | "dev": "vite dev",
7 | "build": "vite build && npm run package",
8 | "preview": "vite preview",
9 | "package": "svelte-kit sync && svelte-package && publint",
10 | "prepublishOnly": "npm run package",
11 | "check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
12 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
13 | "test": "vitest",
14 | "test:ui": "vitest --ui",
15 | "test:run": "vitest run"
16 | },
17 | "files": [
18 | "dist",
19 | "!dist/**/*.test.*",
20 | "!dist/**/*.spec.*"
21 | ],
22 | "sideEffects": [
23 | "**/*.css"
24 | ],
25 | "svelte": "./dist/index.js",
26 | "types": "./dist/index.d.ts",
27 | "type": "module",
28 | "exports": {
29 | ".": {
30 | "types": "./dist/index.d.ts",
31 | "svelte": "./dist/index.js",
32 | "default": "./dist/index.js"
33 | },
34 | "./i18n": {
35 | "types": "./dist/i18n/index.d.ts",
36 | "default": "./dist/i18n/index.js"
37 | },
38 | "./element": {
39 | "types": "./dist/utils/custom-element.d.ts",
40 | "default": "./dist/utils/custom-element.js"
41 | }
42 | },
43 | "peerDependencies": {
44 | "svelte": "^5.0.0"
45 | },
46 | "devDependencies": {
47 | "@shikijs/transformers": "^1.29.2",
48 | "@sveltejs/adapter-auto": "^3.3.1",
49 | "@sveltejs/adapter-vercel": "^5.6.3",
50 | "@sveltejs/kit": "^2.17.2",
51 | "@sveltejs/package": "^2.3.10",
52 | "@sveltejs/vite-plugin-svelte": "^5.0.3",
53 | "@testing-library/jest-dom": "^6.6.3",
54 | "@testing-library/svelte": "^5.2.7",
55 | "@testing-library/user-event": "^14.6.1",
56 | "@vitest/browser": "^2.1.9",
57 | "jsdom": "^25.0.1",
58 | "mdsvex": "^0.11.2",
59 | "playwright": "^1.50.1",
60 | "publint": "^0.2.12",
61 | "rehype-autolink-headings": "^7.1.0",
62 | "rehype-slug": "^6.0.0",
63 | "shiki": "^1.29.2",
64 | "svelte": "^5.20.4",
65 | "svelte-check": "^4.1.4",
66 | "typescript": "^5.7.3",
67 | "vite": "^6.2.0",
68 | "vitest": "^2.1.9",
69 | "vitest-browser-svelte": "^0.0.1"
70 | },
71 | "dependencies": {
72 | "@floating-ui/dom": "^1.6.13"
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/app.d.ts:
--------------------------------------------------------------------------------
1 | // See https://svelte.dev/docs/kit/types#app.d.ts
2 | // for information about these interfaces
3 | declare global {
4 | namespace App {
5 | // interface Error {}
6 | // interface Locals {}
7 | // interface PageData {}
8 | // interface PageState {}
9 | // interface Platform {}
10 | }
11 | }
12 |
13 | export {};
14 |
--------------------------------------------------------------------------------
/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
30 |
38 | %sveltekit.head%
39 |
40 |
41 | %sveltekit.body%
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/lib/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 | .vscode
10 |
--------------------------------------------------------------------------------
/src/lib/components/Time.svelte:
--------------------------------------------------------------------------------
1 |
384 |
385 |
386 |
387 | {#if hasDateComponent}
388 |
395 |
396 |
397 | {/if}
398 | {#if !hourOnly}
399 |
isMinuteView = false}
404 | >
405 | {view(selectedHour, showMeridian)}
406 |
407 |
:
408 |
isMinuteView = true}
413 | >
414 | {view(selectedMinutes, false)}
415 |
416 | {:else}
417 |
{view(selectedHour, showMeridian)}
418 | {#if showMeridian}
419 |
{(isPM ? i18n.meridiem[1] : i18n.meridiem[0]).toUpperCase()}
420 | {:else}
421 |
:
422 |
00
423 | {/if}
424 | {/if}
425 | {#if showMeridian}
426 |
427 |
432 | {isPM ? i18n.meridiem[1] : i18n.meridiem[0]}
433 |
434 |
435 | {/if}
436 |
437 |
438 |
439 |
440 |
{ e.preventDefault(); onClick(e) } }
445 | onmousedown={onToggleMove}
446 | onmousemove={e => { handleMoveMove && onClick(e) }}
447 | onmouseup={onToggleMove}
448 | >
449 |
450 |
453 | {#each pos as p, i(p.val)}
454 |
{p.val}
459 | {/each}
460 | {#each innerHours as p, i}
461 |
{p.val}
466 | {/each}
467 |
468 |
469 |
470 |
613 |
--------------------------------------------------------------------------------
/src/lib/i18n/index.js:
--------------------------------------------------------------------------------
1 | //==============================================================
2 | /* *
3 | * always keep sorted alphabetically, when adding new locale *
4 | * */
5 | //==============================================================
6 |
7 | /**
8 | * @typedef {object} i18nType
9 | * @property {string[]} days
10 | * @property {string[]} daysShort
11 | * @property {string[]} daysMin
12 | * @property {string[]} months
13 | * @property {string[]} monthsShort
14 | * @property {string[]} meridiem
15 | * @property {string[]} suffix
16 | * @property {string} todayBtn
17 | * @property {string} clearBtn
18 | * @property {string} okBtn
19 | * @property {string} cancelBtn
20 | * @property {string} timeView
21 | * @property {string} backToDate
22 | */
23 |
24 | /**
25 | * @type {i18nType} Arabic translation by Amine Laaraf
26 | */
27 | export const ar_DZ = {
28 | days: ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت', 'الأحد'],
29 | daysShort: ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت', 'أحد'],
30 | daysMin: ['أح', 'اث', 'ثل', 'أر', 'خم', 'جم', 'سب', 'أح'],
31 | months: ['جانفي', 'فيفري', 'مارس', 'أفريل', 'ماي', 'جوان', 'جويلية', 'أوت', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'],
32 | monthsShort: ['جان', 'فيف', 'مار', 'أفر', 'ماي', 'جوا', 'جوي', 'أوت', 'سبت', 'أكت', 'نوف', 'ديس'],
33 | meridiem: ['صباح', 'مساء'],
34 | suffix: ['', '', '', ''],
35 | todayBtn: 'اليوم',
36 | clearBtn: 'مسح',
37 | okBtn: 'تأكيد',
38 | cancelBtn: 'إلغاء',
39 | timeView: 'عرض الوقت',
40 | backToDate: 'العودة إلى عرض التقويم'
41 | };
42 |
43 | /**
44 | * @type {i18nType} Arabic translation by Amine Laaraf
45 | */
46 | export const ar_MR = {
47 | days: ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت', 'الأحد'],
48 | daysShort: ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت', 'أحد'],
49 | daysMin: ['أح', 'اث', 'ثل', 'أر', 'خم', 'جم', 'سب', 'أح'],
50 | months: ['يناير', 'فبراير', 'مارس', 'أبريل', 'ماي', 'يونيو', 'يوليوز', 'غشت', 'شتنبر', 'أكتوبر', 'نونبر', 'دجنبر'],
51 | monthsShort: ['ينا', 'فبر', 'مار', 'أبر', 'ماي', 'يون', 'يول', 'غش', 'شت', 'أكت', 'نون', 'دج'],
52 | meridiem: ['صباح', 'مساء'],
53 | suffix: ['', '', '', ''],
54 | todayBtn: 'اليوم',
55 | clearBtn: 'مسح',
56 | okBtn: 'تأكيد',
57 | cancelBtn: 'إلغاء',
58 | timeView: 'عرض الوقت',
59 | backToDate: 'العودة إلى عرض التقويم'
60 | };
61 |
62 | /**
63 | * @type {i18nType} Arabic translation by Amine Laaraf
64 | */
65 | export const ar_YE = {
66 | days: ['الأحد', 'الاثنين', 'الثلاثاء', 'الأربعاء', 'الخميس', 'الجمعة', 'السبت', 'الأحد'],
67 | daysShort: ['أحد', 'اثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت', 'أحد'],
68 | daysMin: ['أح', 'اث', 'ثل', 'أر', 'خم', 'جم', 'سب', 'أح'],
69 | months: ['يناير', 'فبراير', 'مارس', 'إبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر'],
70 | monthsShort: ['ينا', 'فبر', 'مار', 'إبر', 'ماي', 'يون', 'يول', 'أغس', 'سبت', 'أكت', 'نوف', 'ديس'],
71 | meridiem: ['صباح', 'مساء'],
72 | suffix: ['', '', '', ''],
73 | todayBtn: 'اليوم',
74 | clearBtn: 'مسح',
75 | okBtn: 'تأكيد',
76 | cancelBtn: 'إلغاء',
77 | timeView: 'عرض الوقت',
78 | backToDate: 'العودة إلى عرض التقويم'
79 | }
80 |
81 | /**
82 | * @type {i18nType} Czech translation
83 | */
84 | export const cz = {
85 | days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'],
86 | daysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
87 | daysMin: ['Ne', 'Po', 'Út', 'St', 'Čt', 'Pá', 'So', 'Ne'],
88 | months: ['Leden', 'Únor', 'Březen', 'Duben', 'Květen', 'Červen', 'Červenec', 'Srpen', 'Září', 'Říjen', 'Listopad', 'Prosinec'],
89 | monthsShort: ['Led', 'Úno', 'Bře', 'Dub', 'Kvě', 'Čer', 'Čer', 'Srp', 'Zář', 'Říj', 'List', 'Pro'],
90 | meridiem: ['am', 'pm'],
91 | suffix: ['st', 'nd', 'rd', 'th'],
92 | todayBtn: 'Dnes',
93 | clearBtn: 'Smazat',
94 | okBtn: 'Ok',
95 | cancelBtn: 'Zrušit',
96 | timeView: 'Zobrazit hodiny',
97 | backToDate: 'Zpátky na kalendář'
98 | }
99 |
100 | /**
101 | * @type {i18nType} German translation by emroc GmbH
102 | */
103 | export const de = {
104 | days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag', 'Sonntag'],
105 | daysShort: ['Son', 'Mon', 'Die', 'Mie', 'Don', 'Fre', 'Sam', 'Son'],
106 | daysMin: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'],
107 | months: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
108 | monthsShort: ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
109 | meridiem: ['am', 'pm'],
110 | suffix: ['', '', '', ''],
111 | todayBtn: 'Heute',
112 | clearBtn: 'Zurücksetzen',
113 | okBtn: 'Ok',
114 | cancelBtn: 'Abbrechen',
115 | timeView: 'Zeitansicht anzeigen',
116 | backToDate: 'Zurück zur Kalenderansicht'
117 | }
118 |
119 | /**
120 | * @type {i18nType} English translation
121 | */
122 | export const en = {
123 | days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'],
124 | daysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
125 | daysMin: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'],
126 | months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
127 | monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
128 | meridiem: ['am', 'pm'],
129 | suffix: ['st', 'nd', 'rd', 'th'],
130 | todayBtn: 'Today',
131 | clearBtn: 'Clear',
132 | okBtn: 'Ok',
133 | cancelBtn: 'Cancel',
134 | timeView: 'Show time view',
135 | backToDate: 'Back to calendar view'
136 | }
137 |
138 |
139 | /**
140 | * @type {i18nType} Spanish translation by markoan
141 | */
142 |
143 | export const es = {
144 | days: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado', 'Domingo'],
145 | daysShort: ['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb', 'Dom'],
146 | daysMin: ['Do', 'Lu', 'Ma', 'Mi', 'Ju', 'Vi', 'Sa', 'Do'],
147 | months: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'],
148 | monthsShort: ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic'],
149 | meridiem: ['am', 'pm'],
150 | suffix: ['o', '', '', ''],
151 | todayBtn: 'Hoy',
152 | clearBtn: 'Borrar',
153 | okBtn: 'Aceptar',
154 | cancelBtn: 'Cancelar',
155 | timeView: 'Mostrar hora',
156 | backToDate: 'Regresar al calendario'
157 | }
158 |
159 | /**
160 | * @type {i18nType} Persian (Farsi) translation by sadegh19b
161 | */
162 | export const fa = {
163 | days: ['یکشنبه', 'دوشنبه', 'سهشنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه', 'شنبه', 'یکشنبه'],
164 | daysShort: ['یکش', 'دوش', 'سهش', 'چهار', 'پنج', 'جمع', 'شنب', 'یکش'],
165 | daysMin: ['یک', 'دو', 'سه', 'چا', 'پن', 'جم', 'شن', 'یک'],
166 | months: ['ژانویه', 'فوریه', 'مارس', 'آپریل', 'می', 'ژوئن', 'جولای', 'آگوست', 'سپتامبر', 'اکتبر', 'نوامبر', 'دسامبر'],
167 | monthsShort: ['ژان', 'فور', 'مار', 'آپر', 'می', 'ژو', 'جول', 'آگو', 'سپت', 'اکت', 'نوا', 'دسا'],
168 | meridiem: ['ق.ض', 'ب.ض'],
169 | suffix: ['st', 'nd', 'rd', 'th'],
170 | todayBtn: 'امروز',
171 | clearBtn: 'پاککردن',
172 | okBtn: 'تایید',
173 | cancelBtn: 'لغو',
174 | timeView: 'نمایش بخش زمان',
175 | backToDate: 'بازگشت به بخش تقویم'
176 | }
177 |
178 | /**
179 | * @type {i18nType} French translation by Tuditi
180 | */
181 | export const fr = {
182 | days: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi', 'Dimanche'],
183 | daysShort: ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim'],
184 | daysMin: ['Di', 'Lu', 'Ma', 'Me', 'Je', 'Ve', 'Sa', 'Di'],
185 | months: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
186 | monthsShort: ['Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Juin', 'Juil', 'Aoû', 'Sep', 'Oct', 'Nov', 'Déc'],
187 | meridiem: ['AM', 'PM'],
188 | suffix: ['er', 'ème', 'ème', 'ème'],
189 | todayBtn: 'Aujourd\'hui',
190 | clearBtn: 'Effacer',
191 | okBtn: 'OK',
192 | cancelBtn: 'Annuler',
193 | timeView: 'Afficher l\'heure',
194 | backToDate: 'Retour au calendrier'
195 | }
196 |
197 | /**
198 | * @type {i18nType} Croatian translation by AntonioStipic
199 | */
200 | export const hr = {
201 | days: ['Nedjelja', 'Ponedjeljak', 'Utorak', 'Srijeda', 'Četvrtak', 'Petak', 'Subota', 'Nedjelja'],
202 | daysShort: ['Ned', 'Pon', 'Uto', 'Sri', 'Čet', 'Pet', 'Sub', 'Ned'],
203 | daysMin: ['Ne', 'Po', 'Ut', 'Sr', 'Čt', 'Pt', 'Su', 'Ne'],
204 | months: ['Siječanj', 'Veljača', 'Ožujak', 'Travanj', 'Svibanj', 'Lipanj', 'Srpanj', 'Kolovoz', 'Rujan', 'Listopad', 'Studeni', 'Prosinac'],
205 | monthsShort: ['Sij', 'Vel', 'Ožu', 'Tra', 'Svi', 'Lip', 'Srp', 'Kol', 'Ruj', 'Lis', 'Stu', 'Pro'],
206 | meridiem: ['am', 'pm'],
207 | suffix: ['', '', '', ''],
208 | todayBtn: 'Danas',
209 | clearBtn: 'Očisti',
210 | okBtn: 'OK',
211 | cancelBtn: 'Prekid',
212 | timeView: 'Prikaži vrijeme',
213 | backToDate: 'Nazad na kalendar'
214 | }
215 |
216 | /**
217 | * @type {i18nType} Hungarian translation by Tuditi
218 | */
219 | export const hu = {
220 | days: ['Vasárnap', 'Hétfő', 'Kedd', 'Szerda', 'Csütörtök', 'Péntek', 'Szombat', 'Vasárnap'],
221 | daysShort: ['V', 'H', 'K', 'Sze', 'Cs', 'P', 'Szo', 'V'],
222 | daysMin: ['V', 'H', 'K', 'Sze', 'Cs', 'P', 'Szo', 'V'],
223 | months: ['Január', 'Február', 'Március', 'Április', 'Május', 'Június', 'Július', 'Augusztus', 'Szeptember', 'Október', 'November', 'December'],
224 | monthsShort: ['Jan', 'Feb', 'Már', 'Ápr', 'Máj', 'Jún', 'Júl', 'Aug', 'Szept', 'Okt', 'Nov', 'Dec'],
225 | meridiem: ['de', 'du'],
226 | suffix: ['.', '.', '.', '.'],
227 | todayBtn: 'Ma',
228 | clearBtn: 'Törlés',
229 | okBtn: 'OK',
230 | cancelBtn: 'Áthúz',
231 | timeView: 'Óra nézet',
232 | backToDate: 'Vissza a naptárhoz'
233 | };
234 |
235 | /**
236 | * @type {i18nType} Indonesian translation
237 | */
238 | export const id = {
239 | days: ['Minggu', 'Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu', 'Minggu'],
240 | daysShort: ['Min', 'Sen', 'Sel', 'Rab', 'Kam', 'Jum', 'Sab', 'Min'],
241 | daysMin: ['Mn', 'Sn', 'Sl', 'Rb', 'Km', 'Jm', 'Sb', 'Mn'],
242 | months: ['Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni', 'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'],
243 | monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'],
244 | meridiem: ['am', 'pm'],
245 | suffix: ['st', 'nd', 'rd', 'th'],
246 | todayBtn: 'Hari Ini',
247 | clearBtn: 'Hapus',
248 | okBtn: 'Mengkonfirmasi',
249 | cancelBtn: 'Batal',
250 | timeView: 'Tampilkan tampilan waktu',
251 | backToDate: 'Kembali ke tampilan kalender'
252 | }
253 |
254 | /**
255 | * @type {i18nType} Japanese translation by aska
256 | */
257 | export const jp = {
258 | days: ['日曜日', '月曜日', '火曜日', '水曜日', '木曜日', '金曜日', '土曜日', '日曜日'],
259 | daysShort: ['日曜', '月曜', '火曜', '水曜', '木曜', '金曜', '土曜', '日曜'],
260 | daysMin: ['日', '月', '火', '水', '木', '金', '土', '日'],
261 | months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
262 | monthsShort: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
263 | meridiem: ['am', 'pm'],
264 | suffix: ['', '', '', ''],
265 | todayBtn: '今日',
266 | clearBtn: 'クリア',
267 | okBtn: '確認する',
268 | cancelBtn: 'キャンセル',
269 | timeView: 'タイムを表示',
270 | backToDate: 'カレンダーに戻る'
271 | }
272 |
273 | /**
274 | * @type {i18nType} Dutch Translation by Tuditi
275 | */
276 | export const nl = {
277 | days: ['Zondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrijdag', 'Zaterdag', 'Zondag'],
278 | daysShort: ['Zon', 'Maa', 'Din', 'Woe', 'Don', 'Vri', 'Zat', 'Zon'],
279 | daysMin: ['Zo', 'Ma', 'Di', 'Wo', 'Do', 'Vr', 'Za', 'Zo'],
280 | months: ['Januari', 'Februari', 'Maart', 'April', 'Mei', 'Juni', 'Juli', 'Augustus', 'September', 'Oktober', 'November', 'December'],
281 | monthsShort: ['Jan', 'Feb', 'Mrt', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'],
282 | meridiem: ['AM', 'PM'],
283 | suffix: ['e', 'e', 'e', 'e'],
284 | todayBtn: 'Vandaag',
285 | clearBtn: 'Wissen',
286 | okBtn: 'OK',
287 | cancelBtn: 'Annuleren',
288 | timeView: 'Uurweergave',
289 | backToDate: 'Terug naar de kalender'
290 | };
291 |
292 | /**
293 | * @type {i18nType} Slovak Translation
294 | */
295 | export const sk = {
296 | days: ['Nedeľa', 'Pondelok', 'Utorok', 'Streda', 'Štvrtok', 'Piatok', 'Sobota', 'Nedeľa'],
297 | daysShort: ['Ned', 'Pon', 'Uto', 'Str', 'Štv', 'Pia', 'Sob', 'Ned'],
298 | daysMin: ['Ne', 'Po', 'Ut', 'St', 'Št', 'Pi', 'So', 'Ne'],
299 | months: ['Január', 'Február', 'Marec', 'Apríl', 'Máj', 'Jún', 'Júl', 'August', 'September', 'Október', 'November', 'December'],
300 | monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'Máj', 'Jún', 'Júl', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'],
301 | meridiem: ['am', 'pm'],
302 | suffix: ['st', 'nd', 'rd', 'th'],
303 | todayBtn: 'Dnes',
304 | clearBtn: 'Zmazať',
305 | okBtn: 'Ok',
306 | cancelBtn: 'Zrušiť',
307 | timeView: 'Zobraziť hodiny',
308 | backToDate: 'Späť na kalendár'
309 | }
310 |
311 | /**
312 | * @type {i18nType} Korean translation
313 | */
314 | export const ko = {
315 | days: ['일요일', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일', '일요일'],
316 | daysShort: ['일', '월', '화', '수', '목', '금', '토', '일'],
317 | daysMin: ['일', '월', '화', '수', '목', '금', '토', '일'],
318 | months: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
319 | monthsShort: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
320 | meridiem: ['오전', '오후'],
321 | suffix: ['', '', '', ''],
322 | todayBtn: '오늘',
323 | clearBtn: '지우기',
324 | okBtn: '확인하다',
325 | cancelBtn: '취소',
326 | timeView: '시계보기',
327 | backToDate: '달력보기'
328 | }
329 |
330 | /**
331 | * @type {i18nType} Latin translation by PSYCHONOISE
332 | */
333 | export const la = {
334 | days: ['Solis', 'Lunae', 'Martis', 'Mercuri', 'Jovis', 'Veneris', 'Saturni', 'Solis'],
335 | daysShort: ['Sol', 'Lun', 'Mar', 'Mer', 'Jov', 'Ven', 'Sat', 'Sol'],
336 | daysMin: ['So', 'Lu', 'Ma', 'Me', 'Jo', 'Ve', 'Sa', 'So'],
337 | months: ['Januarius', 'Februarius', 'Martius', 'Aprilis', 'Maius', 'Iunius', 'Iulius', 'Augustus', 'September', 'October', 'November', 'December'],
338 | monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Iun', 'Qui', 'Sex', 'Sep', 'Oct', 'Nov', 'Dec'],
339 | meridiem: ['am', 'pm'],
340 | suffix: ['us', 'us', 'us', 'us'],
341 | todayBtn: 'Hodie',
342 | clearBtn: 'Patet',
343 | okBtn: 'ОК',
344 | cancelBtn: 'Cancel',
345 | timeView: 'Ostende tempus visum',
346 | backToDate: 'Ad visum calendarium'
347 | }
348 |
349 | /**
350 | * @type {i18nType} Brazilian portuguese translation by IgorDalepiane
351 | */
352 | export const pt_BR = {
353 | days: ['Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado', 'Domingo'],
354 | daysShort: ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb', 'Dom'],
355 | daysMin: ['Do', 'Se', 'Te', 'Qu', 'Qu', 'Se', 'Sá', 'Do'],
356 | months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
357 | monthsShort: ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'],
358 | meridiem: ['am', 'pm'],
359 | suffix: ['º', 'º', 'º', 'º'],
360 | todayBtn: 'Hoje',
361 | clearBtn: 'Limpar',
362 | okBtn: 'OK',
363 | cancelBtn: 'Cancelar',
364 | timeView: 'Mostrar hora',
365 | backToDate: 'Voltar para o calendário'
366 | }
367 |
368 | /**
369 | * @type {i18nType} Russian translation by PSYCHONOISE
370 | */
371 | export const ru = {
372 | days: ['Воскресенье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота', 'Воскресенье'],
373 | daysShort: ['Вск', 'Пнд', 'Втр', 'Срд', 'Чтв', 'Птн', 'Сбт', 'Вск'],
374 | daysMin: ['Вс', 'Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'],
375 | months: ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'],
376 | monthsShort: ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек'],
377 | meridiem: ['AM', 'PM'],
378 | suffix: ['.', '.', '.', '.'],
379 | todayBtn: 'Сегодня',
380 | clearBtn: 'Очистить',
381 | okBtn: 'ОК',
382 | cancelBtn: 'Отмена',
383 | timeView: 'Показать вид времени',
384 | backToDate: 'Вернуться к виду календаря'
385 | }
386 |
387 | /**
388 | * @type {i18nType} Italian translation by MarkNerdi
389 | */
390 | export const it = {
391 | days: ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato', 'Domenica'],
392 | daysShort: ['Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab', 'Dom'],
393 | daysMin: ['Do', 'Lu', 'Ma', 'Me', 'Gi', 'Ve', 'Sa', 'Do'],
394 | months: ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'],
395 | monthsShort: ['Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic'],
396 | meridiem: ['am', 'pm'],
397 | suffix: ['º', 'º', 'º', 'º'],
398 | todayBtn: 'Oggi',
399 | clearBtn: 'Cancella',
400 | okBtn: 'Ok',
401 | cancelBtn: 'Annulla',
402 | timeView: 'Mostra orario',
403 | backToDate: 'Torna alla vista calendario'
404 | }
405 |
406 | /**
407 | * @type {i18nType} Turkish translation by semih-ky
408 | */
409 | export const tr = {
410 | days: ['Pazar', 'Pazartesi', 'Salı', 'Çarşamba', 'Perşembe', 'Cuma', 'Cumartesi', 'Pazar'],
411 | daysShort: ['Paz', 'Pzt', 'Sal', 'Çar', 'Per', 'Cum', 'Cmt', 'Paz'],
412 | daysMin: ['Pa', 'Pz', 'Sa', 'Ça', 'Pe', 'Cu', 'Cm', 'Pa'],
413 | months: ['Ocak', 'Şubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'Ağustos', 'Eylül', 'Ekim', 'Kasım', 'Aralık'],
414 | monthsShort: ['Oca', 'Şub', 'Mar', 'Nis', 'May', 'Haz', 'Tem', 'Ağu', 'Eyl', 'Eki', 'Kas', 'Ara'],
415 | meridiem: ['öö', 'ös'],
416 | suffix: ['.', '.', '.', '.'],
417 | todayBtn: 'Bugün',
418 | clearBtn: 'Temizle',
419 | okBtn: 'Ok',
420 | cancelBtn: 'İptal',
421 | timeView: 'Zaman görünümünü göster',
422 | backToDate: 'Takvim görünümüne geri dön'
423 | }
424 |
425 | /**
426 | * @type {i18nType} Swedish translation by brantsrasmus
427 | */
428 | export const sv = {
429 | days: ['Söndag', 'Måndag', 'Tisdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lördag', 'Söndag'],
430 | daysShort: ['Sön', 'Mån', 'Tis', 'Ons', 'Tor', 'Fre', 'Lör', 'Sön'],
431 | daysMin: ['Sö', 'Må', 'Ti', 'On', 'To', 'Fr', 'Lö', 'Sö'],
432 | months: ['Januari', 'Februari', 'Mars', 'April', 'Maj', 'Juni', 'Juli', 'Augusti', 'September', 'Oktober', 'November', 'December'],
433 | monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'],
434 | meridiem: ['am', 'pm'],
435 | suffix: ['.', '.', '.', '.'],
436 | todayBtn: 'Idag',
437 | clearBtn: 'Rensa',
438 | okBtn: 'Ok',
439 | cancelBtn: 'Avbryt',
440 | timeView: 'Visa tid',
441 | backToDate: 'Tillbaka till kalender'
442 | }
443 |
444 | /**
445 | * @type {i18nType} Danish translation by brantsrasmus
446 | */
447 | export const da = {
448 | days: ['Søndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'Lørdag', 'Søndag'],
449 | daysShort: ['Søn', 'Man', 'Tir', 'Ons', 'Tor', 'Fre', 'Lør', 'Søn'],
450 | daysMin: ['Sø', 'Ma', 'Ti', 'On', 'To', 'Fr', 'Lø', 'Sø'],
451 | months: ['Januar', 'Februar', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December'],
452 | monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'Maj', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dec'],
453 | meridiem: ['am', 'pm'],
454 | suffix: ['.', '.', '.', '.'],
455 | todayBtn: 'I dag',
456 | clearBtn: 'Slet',
457 | okBtn: 'Ok',
458 | cancelBtn: 'Annuller',
459 | timeView: 'Vis tid',
460 | backToDate: 'Tilbage til kalenderen'
461 | }
462 |
463 | /**
464 | * @type {i18nType} Thai translation by kodaicoder
465 | */
466 | export const th = {
467 | days: ['อาทิตย์', 'จันทร์', 'อังคาร', 'พุธ', 'พฤหัสบดี', 'ศุกร์', 'เสาร์', 'อาทิตย์'],
468 | daysShort: ['อา.', 'จ.', 'อ.', 'พ.', 'พฤ.', 'ศ.', 'ส.', 'อา.'],
469 | daysMin: ['อา.', 'จ.', 'อ.', 'พ.', 'พฤ.', 'ศ.', 'ส.', 'อา.'],
470 | months: ['มกราคม', 'กุมภาพันธ์', 'มีนาคม', 'เมษายน', 'พฤษภาคม', 'มิถุนายน', 'กรกฎาคม', 'สิงหาคม', 'กันยายน', 'ตุลาคม', 'พฤศจิกายน', 'ธันวาคม'],
471 | monthsShort: ['ม.ค.', 'ก.พ.', 'มี.ค.', 'เม.ย.', 'พ.ค.', 'มิ.ย.', 'ก.ค.', 'ส.ค.', 'ก.ย.', 'ต.ค.', 'พ.ย.', 'ธ.ค.'],
472 | meridiem: ['am', 'pm'],
473 | suffix: ['', '', '', ''],
474 | todayBtn: 'วันนี้',
475 | clearBtn: 'ล้างข้อมูล',
476 | okBtn: 'ยืนยัน',
477 | cancelBtn: 'ปิด',
478 | timeView: 'แสดงหน้าเลือกเวลา',
479 | backToDate: 'กลับไปหน้าปฏิทิน'
480 | }
481 |
482 | /**
483 | * @type {i18nType} Chinese Simplified translation by shiroko
484 | */
485 | export const zh_CN = {
486 | days: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'],
487 | daysShort: ['周日', '周一', '周二', '周三', '周四', '周五', '周六', '周日'],
488 | daysMin: ['日', '一', '二', '三', '四', '五', '六', '日'],
489 | months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
490 | monthsShort: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
491 | meridiem: ['上午', '下午'],
492 | suffix: ['', '', '', ''],
493 | todayBtn: '今天',
494 | clearBtn: '清空',
495 | okBtn: '好的',
496 | cancelBtn: '取消',
497 | timeView: '显示时间选择',
498 | backToDate: '回退到日历选项卡'
499 | }
500 |
501 | /**
502 | * IMPORTANT: always keep it sorted alphabetically
503 | *
504 | * @type {Record}
505 | */
506 | export default {
507 | ar_DZ, ar_MR, ar_YE, cz, da, de, en, es, fr, hr, hu, id, it, jp, ko, la, nl, pt_BR, ru, sk, sv, th, tr, zh_CN
508 | }
--------------------------------------------------------------------------------
/src/lib/index.js:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import Component, { config } from './components/SveltyPicker.svelte';
3 |
4 | export default Component;
5 | export { config };
6 | export { parseDate, formatDate } from './utils/dateUtils.js';
7 |
--------------------------------------------------------------------------------
/src/lib/settings.js:
--------------------------------------------------------------------------------
1 | import { en } from '$lib/i18n/index.js';
2 |
3 | export default {
4 | theme: 'sdt-calendar-colors',
5 | format: 'yyyy-mm-dd',
6 | formatType: 'standard',
7 | displayFormat: null,
8 | displayFormatType: null,
9 | minuteIncrement: 1,
10 | weekStart: 1,
11 | inputClasses: '',
12 | todayBtnClasses: 'sdt-action-btn sdt-today-btn',
13 | clearBtnClasses: 'sdt-action-btn sdt-clear-btn',
14 | hourOnly: false,
15 | todayBtn: true,
16 | clearBtn: true,
17 | clearToggle: true,
18 | autocommit: true,
19 | i18n: en
20 | }
21 |
--------------------------------------------------------------------------------
/src/lib/types/internal.d.ts:
--------------------------------------------------------------------------------
1 | export type UpdateProp = {
2 | type: 'date' | 'datetime' | 'hour' | 'minute',
3 | date: Date,
4 | isKeyboard: boolean,
5 | dateIndex?: number
6 | }
7 |
8 | export type DateChange = {
9 | value: string|string[]|null,
10 | dateValue: Date|Date[]|null,
11 | displayValue: string,
12 | valueFormat: string,
13 | displayFormat: string | null,
14 | event: 'date'|'hour'|'minute'|'datetime'
15 | }
16 |
--------------------------------------------------------------------------------
/src/lib/utils/actions.js:
--------------------------------------------------------------------------------
1 | import { computePosition, autoUpdate, shift, flip } from '@floating-ui/dom';
2 |
3 | /**
4 | * @param {HTMLDivElement} node
5 | * @returns void
6 | */
7 | export function usePosition(node) {
8 | const inputElement = node.parentElement?.querySelector('input[type=text]');
9 | if (!inputElement) return;
10 |
11 | const removeFloating = autoUpdate(inputElement, node, () =>
12 | computePosition(inputElement, node, {
13 | placement: 'bottom-start',
14 | middleware: [
15 | shift({
16 | padding: 5
17 | }),
18 | flip()
19 | ]
20 | }).then(({x, y}) => {
21 | Object.assign(node.style, {
22 | left: `${x}px`,
23 | top: `${y}px`,
24 | });
25 | })
26 | )
27 |
28 | return {
29 | destroy() {
30 | removeFloating();
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/lib/utils/constants.js:
--------------------------------------------------------------------------------
1 | export const MODE_DECADE = 0;
2 | export const MODE_YEAR = 1;
3 | export const MODE_MONTH = 2;
4 |
5 | export const STARTVIEW_TIME = 3;
6 |
--------------------------------------------------------------------------------
/src/lib/utils/custom-element.js:
--------------------------------------------------------------------------------
1 | // @ts-nocheck
2 |
3 | const OPTION_LIST = [
4 | 'value', 'name', 'placeholder', 'start-date', 'end-date', 'disabled', 'input-classes',
5 | 'mode', 'format', 'format-type', 'display-format', 'display-format-type', 'week-start', 'today-btn', 'clear-btn', 'autoclose', 'required'
6 | ];
7 |
8 | function formatValue(name, value) {
9 | switch (name) {
10 | case 'value':
11 | return value || '';
12 | case 'required':
13 | case 'disabled':
14 | case 'today-btn':
15 | case 'clear-btn':
16 | case 'autoclose':
17 | return value !== null && value !== 'false';
18 | case 'weekStart':
19 | return parseInt(value);
20 | }
21 | return value;
22 | }
23 |
24 | function formatProp(name) {
25 | if (name.includes('-')) return name.split('-').reduce((res, w, i) => {
26 | if (i) w = w[0].toUpperCase() + w.substr(1);
27 | return res+w;
28 | }, '');
29 | return name;
30 | }
31 |
32 | let _component;
33 | let _config;
34 |
35 | class PickerElement extends HTMLElement {
36 | constructor() {
37 | super();
38 | this.picker = null;
39 | this.valueElement = null;
40 | this.displayElement = null;
41 |
42 | const simpleProps = [
43 | 'value',
44 | 'name', 'placeholder', 'mode', 'format'
45 | ].reduce((res, name) => {
46 | res[name] = {
47 | get() {
48 | return formatValue(name, this.getAttribute(name))
49 | },
50 | set(val) {
51 | this.setAttribute(name, val)
52 | }
53 | }
54 | return res;
55 | }, {});
56 | const baseProps = {
57 | 'form': {
58 | get() {
59 | return this.closest('form');
60 | }
61 | },
62 | 'weekStart': {
63 | get() {
64 | return this.getAttribute('week-start');
65 | },
66 | set(val) {
67 | this.setAttribute('week-start', val);
68 | }
69 | },
70 | 'startDate': {
71 | get() {
72 | return this.getAttribute('start-date');
73 | },
74 | set(val) {
75 | if (val) this.setAttribute('start-date', val);
76 | !val && this.removeAttribute('start-date');
77 | }
78 | },
79 | 'endDate': {
80 | get() {
81 | return this.getAttribute('end-date');
82 | },
83 | set(val) {
84 | val && this.setAttribute('end-date', val);
85 | !val && this.removeAttribute('end-date');
86 | }
87 | },
88 | 'inputClasses': {
89 | get() {
90 | return this.getAttribute('input-classes')
91 | },
92 | set(val) {
93 | val && this.setAttribute('input-classes', val)
94 | !val && this.removeAttribute('input-classes');
95 | }
96 | },
97 | 'formatType': {
98 | get() {
99 | return this.getAttribute('format-type');
100 | },
101 | set(val) {
102 | val && ['standard', 'php'].includes(val) && this.setAttribute('format-type', val);
103 | !val && this.removeAttribute('format-type');
104 | }
105 | },
106 | 'displayFormat': {
107 | get() {
108 | return this.getAttribute('display-format');
109 | },
110 | set(val) {
111 | val && this.setAttribute('display-format', val);
112 | !val && this.removeAttribute('display-format');
113 | }
114 | },
115 | 'displayFormatType': {
116 | get() {
117 | return this.getAttribute('display-format-type');
118 | },
119 | set(val) {
120 | val && ['standard', 'php'].includes(val) && this.setAttribute('display-format-type', val);
121 | !val && this.removeAttribute('display-format-type');
122 | }
123 | }
124 | }
125 | const boolProps = ['required', 'disabled', 'today-btn', 'clear-btn','autoclose']
126 | .reduce((res, propName) => {
127 | const formatted = formatProp(propName);
128 | res[formatted] = {
129 | get() {
130 | const hasProp = this.hasAttribute(propName);
131 | const notFalse = hasProp ? this.getAttribute(propName) !== 'false' : true;
132 | return !hasProp ? _config[formatted] : notFalse;
133 | },
134 | set(value) {
135 | if (!value) {
136 | if (this.hasAttribute(propName)) {
137 | this.removeAttribute(propName);
138 | } else {
139 | // set directly to false, when config default is true
140 | this.picker.$set({ [formatted]: value });
141 | }
142 | } else {
143 | this.setAttribute(propName, value = true ? '' : value);
144 | }
145 | }
146 | }
147 | return res;
148 | }, {});
149 | Object.defineProperties(this, Object.assign({}, simpleProps, baseProps, boolProps));
150 | }
151 |
152 | focus() {
153 | if (this.disabled) return;
154 | const input = this.querySelector('input[type="text"]');
155 | input && input.focus();
156 | }
157 |
158 | static get observedAttributes() {
159 | return OPTION_LIST;
160 | }
161 |
162 | attributeChangedCallback(name, oldValue, newValue) {
163 | if (this.picker && oldValue !== newValue) {
164 | this.picker.$set({ [formatProp(name)]: formatValue(name, newValue) });
165 | }
166 | }
167 |
168 | connectedCallback() {
169 | setTimeout(() => this.init());
170 | }
171 |
172 | init() {
173 | if (this.picker) return;
174 | const props = {};
175 | for (const attr of OPTION_LIST) {
176 | if (this.hasAttribute(attr)) {
177 | props[formatProp(attr)] = formatValue(attr, this.getAttribute(attr));
178 | }
179 | }
180 | // resolve existing elements
181 | this.valueElement = this.querySelector('input[type="hidden"]');
182 | this.displayElement = this.querySelector('input[type="text"]');
183 | if (this.valueElement) props.ce_valueElement = this.valueElement;
184 | if (this.displayElement) props.ce_displayElement = this.displayElement;
185 |
186 | this.picker = new _component({
187 | target: this,
188 | props: props
189 | });
190 | this.picker.$on('input', e => {
191 | this.setAttribute('value', e.detail || '');
192 | this.dispatchEvent(new Event('input'));
193 | });
194 | this.picker.$on('blur', e => {
195 | this.dispatchEvent(new Event('blur'));
196 | });
197 | // bind from/to
198 | if (this.hasAttribute('from')) {
199 | const el = document.getElementById(this.getAttribute('from'));
200 | el.oninput = e => {
201 | this.picker.$set({ startDate: el.value });
202 | }
203 | }
204 | if (this.hasAttribute('to')) {
205 | const el = document.getElementById(this.getAttribute('to'));
206 | el.oninput = e => {
207 | this.picker.$set({ endDate: el.value });
208 | }
209 | }
210 | }
211 |
212 | disconnectedCallback() {
213 | this.picker && this.picker.$destroy();
214 | }
215 | }
216 |
217 | /**
218 | * Define custom element
219 | *
220 | * @param {string} name name of custom element
221 | * @param {object} component `SveltyPicker` component
222 | * @param {config} globalConfig globally available config
223 | */
224 | export function registerSveltyPicker(name, component, globalConfig) {
225 | _component = component;
226 | _config = globalConfig;
227 | window.customElements.define(name, PickerElement);
228 | }
229 |
--------------------------------------------------------------------------------
/src/lib/utils/dateUtils.js:
--------------------------------------------------------------------------------
1 | /** @typedef {import("$lib/i18n").i18nType} i18nType */
2 |
3 | /**
4 | *
5 | * @param {Date|string} date
6 | * @param {string} format
7 | * @param {i18nType} i18n
8 | * @param {string} type
9 | * @returns {Date}
10 | */
11 | export function parseDate(date, format, i18n, type) {
12 | if (date instanceof Date) {
13 | return date;
14 | // const dateUTC = new Date(date.valueOf() + date.getTimezoneOffset() * 60000);
15 | // dateUTC.setMilliseconds(0);
16 | // return dateUTC;
17 | }
18 | const commonFormats = type === 'php'
19 | ? { date: 'Y-m-d', datetime: 'Y-m-d H:i', datetime_s: 'Y-m-d H:i:s' }
20 | : { date: 'yyyy-mm-dd', datetime: 'yyyy-mm-dd hh:ii', datetime_s: 'yyyy-mm-dd hh:ii:ss' };
21 | /** @var {{ separators: string[], parts: string[]}} */
22 | let parsedFormat;
23 | let useParsedTime;
24 | if (/^\d{4}\-\d{1,2}\-\d{1,2}$/.test(date)) {
25 | parsedFormat = formatHelper.parseFormat(commonFormats.date, type);
26 | } else
27 | if (/^\d{4}\-\d{1,2}\-\d{1,2}[T ]\d{1,2}\:\d{1,2}$/.test(date)) {
28 | parsedFormat = formatHelper.parseFormat(commonFormats.datetime, type);
29 | } else
30 | if (/^\d{4}\-\d{1,2}\-\d{1,2}[T ]\d{1,2}\:\d{1,2}\:\d{1,2}[Z]{0,1}$/.test(date)) {
31 | parsedFormat = formatHelper.parseFormat(commonFormats.datetime_s, type);
32 | } else
33 | // specific case when parsing time without 'nonPunctuation' ref #102
34 | if (/^([01]*\d|2[0-3])([0-5]\d)(?:[ ]([ap][m]|[AP][M]))?$/.test(date)) {
35 | useParsedTime = date.match(/^([01]*\d|2[0-3])([0-5]\d)(?:[ ]([ap][m]|[AP][M]))?$/)?.slice(1).filter(e => e);
36 | parsedFormat = formatHelper.parseFormat(format, type);
37 |
38 | } else {
39 | parsedFormat = formatHelper.parseFormat(format, type);
40 | }
41 | const parts = useParsedTime
42 | ? useParsedTime
43 | : (date && date.toString().match(formatHelper.nonpunctuation) || []);
44 | date = new Date(); // reset date
45 | date.setHours(0,0,0,0);
46 | /** @type {Record} */
47 | const parsed = {};
48 | const { setters_order, setters_map } = formatHelper.setters(type);
49 | let val, part;
50 | if (parts.length !== parsedFormat.parts.length && parsedFormat.parts.includes('S')) { // specific suffix parsing from string like '14th'
51 | const splitSuffix = parts[parsedFormat.parts.indexOf('S') - 1].match(/(\d+)([a-zA-Z]+)/)?.slice(1,3);
52 | // @ts-ignore
53 | parts.splice(parsedFormat.parts.indexOf('S') - 1, 1, ...splitSuffix);
54 | }
55 | if (parts.length === parsedFormat.parts.length) {
56 | for (var i = 0, cnt = parsedFormat.parts.length; i < cnt; i++) {
57 | val = parseInt(parts[i], 10);
58 | part = parsedFormat.parts[i];
59 | if (isNaN(val)) {
60 | if (type === 'standard') {
61 | switch (part) {
62 | case 'MM':
63 | val = i18n.months.indexOf(parts[i]) + 1;
64 | break;
65 | case 'M':
66 | val= i18n.monthsShort.indexOf(parts[i]) + 1;
67 | break;
68 | case 'p':
69 | case 'P':
70 | val = i18n.meridiem.indexOf(parts[i].toLowerCase());
71 | break;
72 | }
73 | } else {
74 | // php
75 | switch (part) {
76 | case 'D':
77 | val = i18n.daysShort.indexOf(parts[i]) + 1;
78 | break;
79 | case 'l':
80 | val = i18n.days.indexOf(parts[i]) + 1;
81 | break;
82 | case 'F':
83 | val = i18n.months.indexOf(parts[i]) + 1;
84 | break;
85 | case 'M':
86 | val= i18n.monthsShort.indexOf(parts[i]) + 1;
87 | break;
88 | case 'a':
89 | case 'A':
90 | val = i18n.meridiem.indexOf(parts[i].toLowerCase());
91 | break;
92 | }
93 | }
94 | }
95 | parsed[part] = val;
96 | }
97 | for (var i = 0, s; i < setters_order.length; i++) {
98 | s = setters_order[i];
99 | if (s in parsed && !isNaN(parsed[s]))
100 | setters_map[`${s}`] && setters_map[`${s}`](date, parsed[s])
101 | }
102 | }
103 | return date;
104 | }
105 |
106 | /**
107 | * @param {Date} date
108 | * @param {string} format
109 | * @param {i18nType} i18n
110 | * @param {string} type
111 | * @returns {string} date' string representation
112 | */
113 | export function formatDate(date, format, i18n, type) {
114 | if (date === null) {
115 | return '';
116 | }
117 | const dateVal = date.getDate();
118 | /** @type {Record} */
119 | let val;
120 | if (type === 'standard') {
121 | val = {
122 | t: date.getTime(),
123 | // year
124 | yy: date.getFullYear().toString().substring(2),
125 | yyyy: date.getFullYear(),
126 | // month
127 | m: date.getMonth() + 1,
128 | M: i18n.monthsShort[date.getMonth()],
129 | MM: i18n.months[date.getMonth()],
130 | // day
131 | d: dateVal,
132 | D: i18n.daysShort[date.getDay()],
133 | DD: i18n.days[date.getDay()],
134 | S: (dateVal % 10 && dateVal % 10 < 4 && (dateVal < 10 || dateVal > 14) ? i18n.suffix[dateVal % 10 - 1] : i18n.suffix[i18n.suffix.length -1 ]),
135 | p: (i18n.meridiem.length === 2 ? i18n.meridiem[date.getHours() < 12 ? 0 : 1] : ''),
136 | // hour
137 | h: date.getHours(),
138 | // minute
139 | ii: (date.getMinutes() < 10 ? '0' : '') + date.getMinutes(),
140 | // second
141 | ss: (date.getUTCSeconds() < 10 ? '0' : '') + date.getUTCSeconds()
142 | };
143 |
144 | if (i18n.meridiem.length === 2) {
145 | val.H = (val.h % 12 === 0 ? 12 : val.h % 12);
146 | }
147 | else {
148 | val.H = val.h;
149 | }
150 | val.HH = (val.H < 10 ? '0' : '') + val.H;
151 | val.P = val.p.toUpperCase();
152 | val.hh = (val.h < 10 ? '0' : '') + val.h;
153 | val.i = val.ii;
154 | val.s = val.ss;
155 | val.dd = (val.d < 10 ? '0' : '') + val.d;
156 | val.mm = (val.m < 10 ? '0' : '') + val.m;
157 | } else if (type === 'php') {
158 | // php format
159 | val = {
160 | // year
161 | y: date.getFullYear().toString().substring(2),
162 | Y: date.getFullYear(),
163 | // month
164 | F: i18n.months[date.getMonth()],
165 | M: i18n.monthsShort[date.getMonth()],
166 | n: date.getMonth() + 1,
167 | t: getDaysInMonth(date.getFullYear(), date.getMonth()),
168 | // day
169 | j: date.getDate(),
170 | l: i18n.days[date.getDay()],
171 | D: i18n.daysShort[date.getDay()],
172 | w: date.getDay(), // 0 -> 6
173 | N: (date.getDay() === 0 ? 7 : date.getDay()), // 1 -> 7
174 | S: (dateVal % 10 && dateVal % 10 < 4 && (dateVal < 10 || dateVal > 14) ? i18n.suffix[dateVal % 10 - 1] : i18n.suffix[i18n.suffix.length -1 ]),
175 | // hour
176 | a: (i18n.meridiem.length === 2 ? i18n.meridiem[date.getHours() < 12 ? 0 : 1] : ''),
177 | g: (date.getHours() % 12 === 0 ? 12 : date.getHours() % 12),
178 | G: date.getHours(),
179 | // minute
180 | i: date.getMinutes(),
181 | // second
182 | s: date.getSeconds(),
183 | U: Math.floor(date.getTime() / 1000)
184 | };
185 | val.m = (val.n < 10 ? '0' : '') + val.n;
186 | val.d = (val.j < 10 ? '0' : '') + val.j;
187 | val.A = val.a.toString().toUpperCase();
188 | val.h = (val.g < 10 ? '0' : '') + val.g;
189 | val.H = (val.G < 10 ? '0' : '') + val.G;
190 | val.i = (val.i < 10 ? '0' : '') + val.i;
191 | val.s = (val.s < 10 ? '0' : '') + val.s;
192 | } else {
193 | throw new Error('Invalid format type.');
194 | }
195 | let dateArr = [];
196 | const pFormat = formatHelper.parseFormat(format, type);
197 | for (var i = 0, cnt = pFormat.parts?.length || 0; i < cnt; i++) {
198 | if (pFormat.separators.length) {
199 | dateArr.push(pFormat.separators.shift());
200 | }
201 | dateArr.push(val[pFormat.parts[i]]);
202 | }
203 | if (pFormat.separators.length) {
204 | dateArr.push(pFormat.separators.shift());
205 | }
206 | return dateArr.join('');
207 | }
208 |
209 |
210 | /**
211 | * @param {number} year
212 | * @param {number} month
213 | * @returns {number}
214 | */
215 | export function getDaysInMonth(year, month) {
216 | const isLeapYear = (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0));
217 | return [31, (isLeapYear ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]
218 | }
219 |
220 | /**
221 | * Date comparison a < b
222 | *
223 | * @param {Date|string} a
224 | * @param {Date} b
225 | * @returns
226 | */
227 | export function isLower(a, b) {
228 | if (!(a instanceof Date)) return false;
229 | return a.getFullYear() < b.getFullYear()
230 | || (a.getMonth() < b.getMonth() || a.getDate() < b.getDate());
231 | }
232 |
233 | /**
234 | * Date comparison a > b
235 | *
236 | * @param {Date|string} a
237 | * @param {Date} b
238 | * @returns
239 | */
240 | export function isGreater(a, b) {
241 | if (!(a instanceof Date)) return false;
242 | return a.getFullYear() > b.getFullYear()
243 | || (a.getMonth() > b.getMonth() || a.getDate() > b.getDate());
244 | }
245 |
246 | /**
247 | * @callback MapperFunction
248 | * @param {Date} d date
249 | * @param {number} v value to be set according to format
250 | * @returns void
251 | *
252 | * @typedef {Record} SetterMap
253 | */
254 |
255 | const formatHelper = {
256 | validParts: function(/** @type {string} */ type) {
257 | if (type === 'standard') {
258 | return /t|hh?|HH?|p|P|z|ii?|ss?|dd?|DD?|S|mm?|MM?|yy(?:yy)?/g;
259 | } else if (type === 'php') {
260 | return /[dDjlNwzFmMnStyYaABgGhHisU]/g;
261 | } else {
262 | throw new Error('Invalid format type.');
263 | }
264 | },
265 | nonpunctuation: /[^ -\/:-@\[-`{-~\t\n\rTZ]+/g,
266 | /**
267 | *
268 | * @param {string} format
269 | * @param {string} type
270 | * @returns {{ separators: string[], parts: string[]} }
271 | */
272 | parseFormat: function (/** @type {string} */ format, /** @type {string} */ type) {
273 | // IE treats \0 as a string end in inputs (truncating the value),
274 | // so it's a bad format delimiter, anyway
275 | var separators = format.replace(this.validParts(type), '\0').split('\0'),
276 | parts = format.match(this.validParts(type)) || [];
277 | if (!separators || !separators.length || !parts || parts.length === 0) {
278 | // throw new Error('Invalid date format.');
279 | console.warn('invalid date format', separators, parts);
280 | }
281 | return {separators: separators, parts: parts};
282 | },
283 | /**
284 | * @param {string} type
285 | * @returns {{setters_map: SetterMap, setters_order: string[]}}
286 | */
287 | setters: function(type) {
288 | /** @type {string[]} */
289 | let setters_order
290 | /** @type {SetterMap} */
291 | let setters_map = {};
292 | if (type === 'standard') {
293 | setters_order = ['yyyy', 'yy', 'm', 'mm', 'M', 'MM','d', 'dd', 'D','DD', 'hh', 'h', 'HH', 'H', 'ii', 'i', 'ss', 's', 'S', 'p', 'P', 't'];
294 | setters_map = {
295 | hh: (d, v) => d.setHours(v),
296 | h: (d, v) => d.setHours(v),
297 | HH: (d, v) => d.setHours(v === 12 ? 0 : v),
298 | H: (d, v) => d.setHours(v === 12 ? 0 : v),
299 | i: (d, v) => d.setMinutes(v),
300 | s: (d, v) => d.setSeconds(v),
301 | yyyy: (d, v) => d.setFullYear(v),
302 | yy: (d, v) => d.setFullYear((v < 50 ? 2000 : 1900) + v),
303 | m: (d, v) => {
304 | v -= 1;
305 | while (v < 0) v += 12;
306 | v %= 12;
307 | d.setMonth(v);
308 | while (d.getMonth() !== v)
309 | if (isNaN(d.getMonth()))
310 | return d;
311 | else
312 | d.setDate(d.getDate() - 1);
313 | return d;
314 | },
315 | d: (d, v) => d.setDate(v),
316 | p: (d, v) => d.setHours(v === 1 && d.getHours() < 12 ? d.getHours() + 12 : d.getHours()),
317 | t: (d, v) => d.setTime(v),
318 | mm: ()=>{},
319 | M: ()=>{},
320 | MM: ()=>{},
321 | ii: ()=>{},
322 | ss: ()=>{},
323 | dd: ()=>{},
324 | D: ()=>{},
325 | DD: ()=>{},
326 | P: ()=>{}
327 | };
328 | setters_map.mm = setters_map.M = setters_map.MM = setters_map.m;
329 | setters_map.ii = setters_map.i;
330 | setters_map.ss = setters_map.s;
331 | setters_map.dd = setters_map.D = setters_map.DD = setters_map.d;
332 | setters_map.P = setters_map.p;
333 | } else {
334 | // php
335 | setters_order = ['Y','yy','m','M','F','n','d','D','j','l','N','S','H','G','h','g','i','s','p','P','U'];
336 | setters_map = {
337 | H: (d, v) => d.setHours(v),
338 | G: (d, v) => d.setHours(v),
339 | h: (d, v) => d.setHours(v === 12 ? 0 : v),
340 | g: (d, v) => d.setHours(v === 12 ? 0 : v),
341 | i: (d, v) => d.setMinutes(v),
342 | s: (d, v) => d.setSeconds(v),
343 | Y: (d, v) => d.setFullYear(v),
344 | yy: (d, v) => d.setFullYear((v < 50 ? 2000 : 1900) + v),
345 | m: (d, v) => {
346 | v -= 1;
347 | while (v < 0) v += 12;
348 | v %= 12;
349 | d.setMonth(v);
350 | while (d.getMonth() !== v)
351 | if (isNaN(d.getMonth()))
352 | return d;
353 | else
354 | d.setDate(d.getDate() - 1);
355 | return d;
356 | },
357 | n: (d, v) => d.setMonth(v - 1),
358 | d: (d, v) => d.setDate(v),
359 | a: (d, v) => d.setHours(v === 1 ? d.getHours() + 12 : d.getHours()),
360 | U: (d, v) => d.setTime(v * 1000)
361 | };
362 | setters_map.F = setters_map.M = setters_map.m;
363 | setters_map.D = setters_map.j = setters_map.l = setters_map.N = setters_map.d;
364 | setters_map.A = setters_map.a;
365 | }
366 | return { setters_order, setters_map };
367 | }
368 | }
369 |
--------------------------------------------------------------------------------
/src/lib/utils/grid.js:
--------------------------------------------------------------------------------
1 |
2 | import { MODE_YEAR, MODE_DECADE } from "./constants.js";
3 | import { getDaysInMonth } from "./dateUtils.js";
4 |
5 | /**
6 | * @typedef {object} Dataset
7 | * @property {any[][]} grid
8 | * @property {Date[][]} days
9 | * @property {string[][]} months
10 | * @property {number[][]} years
11 | * @property {number[]} selectionMark
12 | * @property {number} todayMark
13 | * @property {number} prevTo
14 | * @property {number} nextFrom
15 | *
16 | * @typedef {import("$lib/i18n").i18nType} i18nType
17 | */
18 |
19 | /**
20 | * Compute view grid content based on given 'currentView' property
21 | *
22 | * @param {Date} currentDate
23 | * @param {Date[]} selectedDates
24 | * @param {number} view
25 | * @param {i18nType} locale
26 | * @param {number} weekStart
27 | * @returns {Dataset}
28 | */
29 | export function compute(currentDate, selectedDates, view, locale, weekStart) {
30 |
31 | /** ************************************ MODE_DECADE: */
32 | /** ************************************ years 4 x 3 */
33 | if (view === MODE_DECADE) {
34 | let prevTo = 10; // base is year 2000
35 | let nextFrom = 20;
36 | const todayMark = -1;
37 | const grid = [];
38 | let yearRow = [];
39 | let currYear = currentDate.getFullYear() - (currentDate.getFullYear() % 10);
40 | currYear -= (currYear % 20 ? 12 : 10);
41 | if (currYear % 10) { // if start is 10
42 | prevTo = 12;
43 | nextFrom = 22;
44 | }
45 | for (let i = 0; i < 32; i++) {
46 | yearRow.push(currYear + i);
47 | if (yearRow.length === 4) {
48 | grid.push(yearRow);
49 | yearRow = [];
50 | }
51 | }
52 | /** @var number[] */
53 | let selectionMark = [];
54 | if (!selectedDates[0]) {
55 | selectedDates[0] = new Date();
56 | }
57 | if (selectedDates[0].getFullYear() >= currYear) {
58 | selectionMark.push(selectedDates[0].getFullYear() % currYear);
59 | }
60 |
61 | // @ts-ignore
62 | return {
63 | years: grid, todayMark, nextFrom, prevTo, selectionMark
64 | }
65 | }
66 |
67 | /** ************************************ MODE_YEAR: */
68 | /** ************************************ months 4 x 3 */
69 | if (view === MODE_YEAR) {
70 | let grid = [];
71 | let monthRow = [];
72 | let prevTo = 12;
73 | let nextFrom = 24;
74 | const ISO = currentDate.toISOString().split('T')[0].substring(0, 8);
75 | const dateNormalized = new Date(ISO + '01 00:00:00');
76 | const initYear = dateNormalized.getFullYear() - 1;
77 | dateNormalized.setFullYear(initYear);
78 | let todayMark = 0;
79 | for (let y = 0; y < 3; y++) {
80 | for (let i = 0; i < 12; i++) {
81 | dateNormalized.setMonth(i);
82 | monthRow.push(locale.monthsShort[i % 12]);
83 | if (monthRow.length === 4) {
84 | grid.push(monthRow);
85 | monthRow = [];
86 | }
87 | }
88 | dateNormalized.setFullYear(dateNormalized.getFullYear() + 1);
89 | }
90 | /** @type {number[]} */
91 | let selectionMark = [];
92 | if (!selectedDates[0]) {
93 | selectedDates[0] = new Date();
94 | }
95 | if (selectedDates[0].getFullYear() - initYear >= 0 && selectedDates[0].getFullYear() - initYear <= 2) {
96 | selectionMark.push(selectedDates[0].getMonth() + ((selectedDates[0].getFullYear() - initYear || 0) * 12));
97 | }
98 | // @ts-ignore
99 | return {
100 | months: grid, todayMark, nextFrom, prevTo, selectionMark
101 | }
102 | }
103 |
104 | /** ************************************ MONTH */
105 | /** ************************************ days 7x6 */
106 | let d = currentDate || new Date(), // or currently selected date
107 | y = d.getFullYear(),
108 | m = d.getMonth(),
109 | dM = d.getDate(),
110 | h = d.getHours(),
111 | today = new Date();
112 | let prevMonth = new Date(y, m-1, 28, 0, 0, 0, 0),
113 | day = getDaysInMonth(prevMonth.getFullYear(), prevMonth.getMonth());
114 | prevMonth.setDate(day);
115 | prevMonth.setDate(day - (prevMonth.getDay() - weekStart + 7) % 7);
116 |
117 | let nextMonth = new Date(prevMonth);
118 | nextMonth.setDate(nextMonth.getDate() + 42);
119 | let nextMonthValue = nextMonth.valueOf();
120 |
121 | let grid = [];
122 | let dayRow = [];
123 | let todayMark = -1;
124 | /** @type {number[]} */
125 | let selectionMark = [];
126 | let prevTo = 0;
127 | let nextFrom = 42;
128 | let inc = 0;
129 | while(prevMonth.valueOf() < nextMonthValue) {
130 | inc++;
131 | dayRow.push(new Date(prevMonth));
132 | if (prevMonth.getFullYear() < y || (prevMonth.getFullYear() === y && prevMonth.getMonth() < m)) {
133 | prevTo = inc;
134 | } else if (nextFrom === 42 && (prevMonth.getFullYear() > y || (prevMonth.getFullYear() === y && prevMonth.getMonth() > m))) {
135 | nextFrom = inc - 1;
136 | }
137 |
138 | prevMonth.setDate(prevMonth.getDate() + 1);
139 |
140 |
141 | if (prevMonth.getFullYear() === today.getFullYear() &&
142 | prevMonth.getMonth() === today.getMonth() &&
143 | prevMonth.getDate() === today.getDate()
144 | ) {
145 | todayMark = inc;
146 | }
147 | if (selectionMark.length !== selectedDates.length) {
148 | selectedDates.map(s => {
149 | if (prevMonth.getFullYear() === s.getFullYear()
150 | && prevMonth.getMonth() === s.getMonth()
151 | && prevMonth.getDate() === s.getDate()
152 | )
153 | selectionMark.push(inc);
154 | })
155 | }
156 |
157 | if (dayRow.length === 7) {
158 | grid.push(dayRow);
159 | dayRow = [];
160 | }
161 | }
162 | // @ts-ignore
163 | return {
164 | grid, days: grid, todayMark, prevTo, nextFrom, selectionMark,
165 | };
166 | }
167 |
--------------------------------------------------------------------------------
/src/lib/utils/state.js:
--------------------------------------------------------------------------------
1 | import { formatDate, parseDate } from "./dateUtils.js";
2 |
3 | /**
4 | * @typedef {object} ValueInit
5 | * @property {string[]} iValues
6 | * @property {string|null} iValueCombined
7 | * @property {Date[]} iDates
8 | */
9 |
10 | /**
11 | * Init internal props
12 | *
13 | * @param {string|string[]|null} value
14 | * @param {string} format
15 | * @param {import("$lib/i18n/index.js").i18nType} i18n
16 | * @param {string} formatType
17 | * @returns {ValueInit}
18 | */
19 | export function initProps(value, format, i18n, formatType) {
20 | /** @type {string[]} */
21 | let valueArray = value
22 | ? (Array.isArray(value)
23 | ? value
24 | : value.split(',') // for range we can get combined string
25 | )
26 | : [];
27 |
28 | // strip seconds if present in initial value
29 | valueArray = valueArray.map(value => value.replace(/(:\d+):\d+(.*)/, "$1$2"));
30 |
31 | return {
32 | iValues: valueArray,
33 | iValueCombined: valueArray.length ? valueArray.join() : null,
34 | iDates: valueArray.map(val => parseDate(val, format, i18n, formatType)),
35 | }
36 | }
37 |
38 | /**
39 | * FUTURE: test that this works for PHP format type as well
40 | *
41 | * @param {'auto'|'date'|'datetime'|'time'} mode
42 | * @param {string} format
43 | * @returns {'date'|'datetime'|'time'}
44 | */
45 | export function computeResolvedMode(mode, format) {
46 | return mode === "auto"
47 | ? format.match(/g|hh?|ii?/i) && format.match(/y|m|d/i)
48 | ? "datetime"
49 | : format.match(/g|hh?|ii?/i)
50 | ? "time"
51 | : "date"
52 | : mode;
53 | }
54 |
--------------------------------------------------------------------------------
/src/lib/utils/transitions.js:
--------------------------------------------------------------------------------
1 | import { cubicOut } from 'svelte/easing';
2 |
3 |
4 | /**
5 | *
6 | * @typedef {object} ScaleParams
7 | * @property {number} duration
8 | * @property {number} start
9 | * @property {number} opacity
10 | * @property {number|undefined} end
11 | *
12 | * @param {HTMLElement} node
13 | * @param {ScaleParams | import('svelte/transition').FadeParams} params
14 | * @returns {import('svelte/transition').TransitionConfig}
15 | */
16 | // @ts-ignore
17 | export function scale(node, { duration = 400, start = 0, end = 1, opacity = 0 }) {
18 | const style = getComputedStyle(node);
19 | const target_opacity = +style.opacity;
20 | const transform = style.transform === 'none' ? '' : style.transform;
21 | const sd = 1 - start;
22 | const od = target_opacity * (1 - opacity);
23 | end = end || 1;
24 | return {
25 | delay: 0,
26 | duration,
27 | easing: cubicOut,
28 | css: (/** @type number */ _t, /** @type number */ u) => `
29 | transform: ${transform} scale(${end !== 1 ? start + end * u : 1 - (sd * u)});
30 | opacity: ${target_opacity - (od * u)};
31 | `
32 | };
33 | }
34 |
--------------------------------------------------------------------------------
/src/routes/+layout.server.js:
--------------------------------------------------------------------------------
1 | export const prerender = true;
2 |
--------------------------------------------------------------------------------
/src/routes/+layout.svelte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
95 |
96 |
97 |
98 | {#if !isWide}
99 |
100 |
101 | {
104 | e.stopPropagation();
105 | onNavToggle();
106 | }}
107 | >
108 |
109 |
110 |
111 | Menu
112 |
113 |
114 |
115 | {/if}
116 |
117 | 📅 Svelty Picker
118 |
119 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
158 |
159 |
160 | {@render children?.()}
161 |
162 |
163 | {#if page_nav.length}
164 | On this page
165 |
166 | {#each page_nav as link}
167 | {link.text}
168 | {/each}
169 |
170 |
171 | {/if}
172 |
173 |
174 |
175 |
176 |
177 | Made with
Svelte ❤ by
Martin Skocik .
178 | ♦
179 | You can support me through
GitHub .
180 |
181 |
182 |
183 |
184 |
288 |
--------------------------------------------------------------------------------
/src/routes/+page.svelte.md:
--------------------------------------------------------------------------------
1 |
42 |
43 |
44 | Simple date & time / daterange picker implemented in svelte.
45 |
46 |
47 |
48 |
49 |
50 | Date Picker: {date_datepicker}
51 |
52 | updatePickersValue(date, 'date')} />
53 |
54 |
55 | Time Picker: {date_timepicker}
56 |
57 | updatePickersValue(time, 'time')} />
58 |
59 |
60 |
61 |
62 | ### Features
63 |
64 | - date/time/datetime/range picker mode
65 | - various formatting options
66 | - keyboard navigation
67 | - replacable slots
68 | - themable
69 | - customizable disabled dates
70 | - custom element
71 |
72 | ## Install
73 |
74 | ```bash
75 | npm install svelty-picker
76 | ```
77 |
78 | For Svelte 4
79 |
80 | ```bash
81 | npm install svelty-picker@5.2.11
82 | ```
83 |
84 | ## More Examples
85 |
86 |
87 |
88 |
89 | Date Time Picker: {date_datetime}
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | Date Range picker: {date_range}
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | Date Time Range picker: {datetime_range}
109 |
110 |
111 |
112 |
113 |
114 |
115 |
139 |
--------------------------------------------------------------------------------
/src/routes/autocommit-changes/+page.svelte.md:
--------------------------------------------------------------------------------
1 |
31 |
32 |
33 | # Auto-commit changes
34 |
35 |
36 | On every change by mouse/touch, change callbacks are triggered. So if you use `datetime` picker, for every part - day, hour, minute there will be one event triggered. In some situations this may not be desired, therefore you can disable `autocommit`
37 |
38 |
39 |
40 |
41 |
42 |
43 | Historical context
44 |
45 |
46 | Previous versions (below v4) had problem that every interaction triggered change event. This was visible especially when picker was used in `datetime` mode.
47 | Therefore the whole logic has been reworked and basically we can talk about two modes - "auto commit" and "manual commit".
48 |
49 |
50 |
51 | In v6 it was reverted back, because it simplified codebase massively and it's really straightforward
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | ### Auto-commit mode
60 |
61 | For datepicker it's simple. User picks a date, bound value (if any) is updated, `change` event triggered and picker is closed.
62 | For timepicker it's very similar (`change` triggered on every update) with the exception that picker closes automatically only
63 | after _minute_ selection. So you can change hour many times and picker will stay open. Select minutes and it will close. Also event is triggered etc.
64 |
65 | When using keyboard to change date, bound value is not updated immediately, but only by hitting Enter
or on input
66 | blur. This is very important distinction against manual value setting.
67 |
68 | ### Manual-commit mode
69 |
70 | When `autocommit` is set to `false`, buttons **_Ok_** and **_Cancel_** are shown by default (can be overwritten by `actionRow` snippet).
71 | In this mode the main difference is, that value is set and event triggered only on `Enter` key or pressing `Ok` button.
72 | Pressing `Cancel` or leaving input _resets_ internal state to previous value (if set).
73 |
74 | ** 💡 IMPORTANT NOTE**
75 |
76 | When using `isRange` with combination of `'datetime` mode., auto-commit is _always_ OFF automatically to provide meaningful user experience.
77 |
78 |
79 | ## Example
80 |
81 |
82 |
83 | Settings
84 |
85 |
86 | Auto-commit
87 |
88 |
89 |
90 | Date range mode
91 |
92 |
93 |
94 | Modes
95 | {#each modes as mode}
96 | {mode}
97 | {/each}
98 |
99 |
100 |
101 |
102 | {#if isRange}
103 |
104 | Selected value: {rangeValue}
105 | {:else}
106 | {#key activeMode}
107 |
108 | {/key}
109 | Selected value: {value}
110 | {/if}
111 |
112 |
113 |
121 |
--------------------------------------------------------------------------------
/src/routes/disabling-dates/+page.svelte.md:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 | # Disabling dates
14 |
15 | Restricting selectable is supported by `startDate` and `endDate` properties. You can provide `string` or `Date`.
16 | When `string` is passed, it must match `format` of given Svelty-picker. These border dates are *INCLUDED* in allowed date range.
17 |
18 | It is recommended to pass `strings` and not `Dates` to these properties. When datetime is passed, time range will be restricted
19 | as well if applicable. If conversion from `Date` to `string` is required, you can use ulitity functions as mentioned on [Working with dates](/working-with-dates) page.
20 |
21 | If you need something advanced you can provide function through `disableDatesFn`, in which you can resolve whether given date is disabled or not.
22 | Just take into account that all `Date` objects passed into this function will be in local time of given user.
23 |
24 | ```js
25 | function isDateDisabled(date: Date): bool
26 | ```
27 |
28 | Both methods can be combined together if needed.
29 |
30 | ### Example
31 |
32 |
33 |
34 |
35 |
36 |
37 | ```svelte
38 |
48 |
49 |
50 |
51 |
52 |
53 | ```
54 |
55 |
60 |
--------------------------------------------------------------------------------
/src/routes/global-config/+page.svelte.md:
--------------------------------------------------------------------------------
1 | # Global config
2 |
3 | Svelty picker exports `config` property, where you can set/change some of the properties globally, which will affect all newly created instances.
4 |
5 | Can be usefull especially for formatting, i18n and theme setting to be consistent across the whole app. You should edit the config on your app start to reflect new settings for all _newly created_ pickers.
6 |
7 | ### Structure
8 |
9 | ```js
10 | type Config = {
11 | theme: string;
12 | format: string;
13 | formatType: string;
14 | displayFormat: string?;
15 | displayFormatType: string?;
16 | minuteIncrement: number;
17 | weekStart: number;
18 | inputClasses: string;
19 | todayBtnClasses: string;
20 | clearBtnClasses: string;
21 | hourOnly: boolean;
22 | todayBtn: boolean;
23 | clearBtn: boolean;
24 | autocommit: boolean;
25 | i18n: i18nType;
26 | }
27 | ```
28 |
29 | ### Defaults
30 |
31 | ```js
32 | export default {
33 | theme: 'sdt-calendar-colors',
34 | format: 'yyyy-mm-dd',
35 | formatType: 'standard',
36 | displayFormat: null,
37 | displayFormatType: null,
38 | minuteIncrement: 1,
39 | weekStart: 1,
40 | inputClasses: '',
41 | todayBtnClasses: 'sdt-action-btn sdt-today-btn',
42 | clearBtnClasses: 'sdt-action-btn sdt-clear-btn',
43 | hourOnly: false,
44 | todayBtn: true,
45 | clearBtn: true,
46 | autocommit: true,
47 | i18n: en
48 | }
49 | ```
50 |
51 | ### Example
52 |
53 | ```svelte
54 |
62 |
63 |
64 |
65 |
My german picker (global)
66 |
67 |
68 |
69 |
Japanese override
70 |
71 |
72 |
73 | ```
74 |
--------------------------------------------------------------------------------
/src/routes/localization/+page.svelte.md:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 | # Localization
11 |
12 | By default date picker uses english locale. And at the moment there is only few locales available
13 | (PRs for additional locales are more than welcome).
14 |
15 | Available locales: {keys}
16 |
17 | Setting locale is perfect use case for using [global configuration](/global-config).
18 |
19 | ## Example
20 |
21 |
22 |
23 |
My german picker
24 |
25 |
26 |
27 |
Japanese override
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | ```svelte
38 |
46 |
47 |
48 |
49 |
My german picker
50 |
51 |
52 |
53 |
Japanese override
54 |
55 |
56 |
57 | ```
58 |
59 |
65 |
--------------------------------------------------------------------------------
/src/routes/migration-guide/+page.svelte.md:
--------------------------------------------------------------------------------
1 | # Migration from v5
2 |
3 | Version 6 requires at least `svelte@5`.
4 |
5 | Main point of version 6 was to align with svelte 5. So deprecated features in svelte 5 were removed completely and replaced accordingly.
6 | Therefore amount of BC breaks is minimal.
7 |
8 | ## Slots
9 |
10 | Slots were removed in favour of snippets. Slot `inputs` has been removed completely with no replacement. New snippet
11 | `actionRow` has extended parameters to be fully compatible with default implementation. See [Snippets](/snippets) for
12 | more details.
13 |
14 | ## Properties
15 |
16 | ### `initialDate`
17 |
18 | Since svelte 5.9 where function bindings became available, this property has no meaning. Function bindings are more
19 | powerful, allowing things like binding to `Date` object directly and handle timezones as well.
20 |
21 | ```svelte
22 | // [!code --]
23 | // [!code ++]
24 | ```
25 |
26 | [Function bindings example](https://svelte.dev/playground/1ddd82f573b94201b3c8fcab33bf0a46?version=5.9.0)
27 |
28 | ## Events
29 |
30 | Due to deprecation of `createEventDispatcher` all events has been replaced by event callback properties.
31 |
32 | ```svelte
33 | // [!code --]
34 | // [!code ++]
35 | ```
36 | This is the event to prop mapping list:
37 |
38 | - `change` changed to `onChange`
39 | - `dateChange` changed to `onDateChange`
40 | - `focus` changed to `onBlur`
41 | - `input` changed to `onInput`
42 | - `blur` changed to `onBlur`
43 |
44 | Visit [Properties](/properties#event-callback-props) for more details.
45 |
46 | ## Event dispatching
47 |
48 | During migration to rune mode logic behind triggering event callback properties has been reworked completely and _regressed_
49 | back as it was before version 4.
50 |
51 | What it means is that if you have `datetime` picker, `onChange` callback will be called 3 times, once on `date`, `hour` and `minute` event.
52 | If you need to check for specific event, you have 2 options:
53 |
54 | - use `onDateChange` event callback. It passes which event `type` triggered the callback
55 | - use picker with `autocommit=false`
56 |
57 | ## CSS update
58 |
59 | Last occurrences of `std-` class prefix fixed, namely `std-component-wrap` and `std-calendar-wrap` has been renamed.
60 |
--------------------------------------------------------------------------------
/src/routes/modes-and-formats/+page.svelte.md:
--------------------------------------------------------------------------------
1 |
22 |
23 |
24 | # Modes & formats
25 |
26 |
27 | {#snippet standard()}
28 | | Format | Description | Example
29 | |--------|----------------------------------------------------------------------------------|--------------------------------------
30 | | `d` | day of the month without leading zeros | 1 to 31
31 | | `dd` | day of the month, 2 digits with leading zeros | 01 to 31
32 | | `D` | short textual representation of a weekday (i18n.daysShort) | Mon through Sun
33 | | `DD` | long textual representation of a weekday (i18n.days) | Sunday through Saturday
34 | | `S` | English ordinal suffix for the day of the month, (i18n.suffix) | st, nd, rd or th. Works well with `d`
35 | | `m` | numeric representation of month without leading zeros | 1 to 12
36 | | `mm` | numeric representation of the month, 2 digits with leading zeros | 01 to 12
37 | | `M` | short textual representation of a month, three letters (i18n.monthsShort) | Jan through Dec
38 | | `MM` | full textual representation of a month, such as January or March (i18n.months) | January through December
39 | | `yy` | two digit representation of a year | 99 or 03
40 | | `yyyy` | full numeric representation of a year, 4 digits | 1999, 2003
41 | | `h` | hour without leading zeros - 24-hour format | 0 - 23
42 | | `hh` | hour, 2 digits with leading zeros - 24-hour format | 00 - 23
43 | | `H` | hour without leading zeros - 12-hour format | 1 - 12
44 | | `HH` | hour, 2 digits with leading zeros - 12-hour format | 01 - 12
45 | | `i` | minutes, 2 digits with leading zeros | 00 - 59
46 | | `ii` | alias for `i` | 00 - 59
47 | | `s` | seconds, 2 digits with leading zeros | 00
48 | | `ss` | alias for `s` | 00
49 | | `p` | meridian in lower case ('am' or 'pm') - according to locale file (i18n.meridiem) | am or pm
50 | | `P` | meridian in upper case ('AM' or 'PM') - according to locale file (i18n.meridiem) | AM or PM
51 | | `t` | timestamp in milliseconds (although milliseconds are always 0). |
52 | {/snippet}
53 |
54 | {#snippet php()}
55 | | Format | Description | Example
56 | |------|---------------------------------------------------------------------|--------------------------------------
57 | | `d` | Day of the month, 2 digits with leading zeros | 01 to 31
58 | | `D` | A textual representation of a day, three letters | Mon through Sun
59 | | `j` | Day of the month without leading zeros | 1 to 31
60 | | `l` | A full textual representation of the day of the week | Sunday through Saturday
61 | | `N` | ISO 8601 numeric representation of the day of the week | 1 (for Monday) through 7 (for Sunday)
62 | | `S` | English ordinal suffix for the day of the month, 2 characters | st, nd, rd or th. Works well with `j`
63 | | `F` | A full textual representation of a month, such as January or March | January through December
64 | | `m` | Numeric representation of a month, with leading zeros | 01 through 12
65 | | `M` | A short textual representation of a month, three letters | Jan through Dec
66 | | `n` | Numeric representation of a month, without leading zeros | 1 through 12
67 | | `Y` | A full numeric representation of a year, at least 4 digits | 0787, 1999, 2003
68 | | `y` | A two digit representation of a year | 99 or 03
69 | | `a` | Lowercase Ante meridiem and Post meridiem | am or pm
70 | | `A` | Uppercase Ante meridiem and Post meridiem | AM or PM
71 | | `g` | 12-hour format of an hour without leading zeros | 1 through 12
72 | | `G` | 24-hour format of an hour without leading zeros | 0 through 23
73 | | `h` | 12-hour format of an hour with leading zeros | 01 through 12
74 | | `H` | 24-hour format of an hour with leading zeros | 00 through 23
75 | | `i` | Minutes with leading zeros | 00 to 59
76 | | `s` | Seconds with leading zeros | 00
77 | | `U` | timestamp in seconds. |
78 | {/snippet}
79 |
80 | ### Mode
81 |
82 | By `mode` property you can restrict modes in which picker operates. This property can have following values:
83 |
84 | - `date` - only date picker
85 | - `time` - only time picker
86 | - `datetime` - date & time picker
87 | - `auto` (default) - mode is determined based on passed `format` property. This basically means you can activate `time` picker mode by setting `displayFormat` (or `format` if those 2 values should be the same) to `hh:ii` for example
88 |
89 | *Note:* range-picker is activated by `isRange` property, so you can still set appropriate mode for range-pickers.
90 |
91 | ### Format
92 |
93 | Component has 2 separate format-related properties:
94 |
95 | - `format` - defines string representation of selected Date object(s). This string is sent in `change` event or when form is submitted. Default value is `yyyy-mm-dd`
96 | - `displayFormat` - independent date format controlling how the date is being displayed to the user. When not set, `format` value is being used.
97 |
98 | Both propertie also have corresponding _type_ property, ie. `formatType` for `format` and `displayFormatType` for `displayFormat`.
99 | There are 2 available options for `formatType` props - `standard` (default) and `php`. And again if `displayFormatType` is undefined, `formatType` is being used as fallback.
100 |
101 |
102 |
103 |
104 |
105 | 💡 For timestamp in ***seconds*** use `php` formatting. For timestamp with ***miliseconds*** use `standard` formatting.
106 |
107 | ### Example
108 |
109 |
110 |
111 |
112 | Format
=== displayFormat
Internal value: {valueDefault || 'null'}
113 |
114 |
115 |
116 |
117 |
118 | Format
!== displayFormat
Internal value: {valueDifferent || 'null'}
119 |
120 |
121 |
122 |
123 |
124 | Time only format: Internal value: {valueTimeOnly || 'null'}
125 |
126 |
127 |
128 |
129 |
130 | ```svelte
131 |
132 |
133 |
134 | Format === displayFormat Internal value: {valueDefault}
135 |
136 |
137 |
138 |
139 |
140 | Format !== displayFormat Internal value: {valueDifferent}
141 |
142 |
143 |
144 |
145 |
146 | Time only format: Internal value: {valueTimeOnly}
147 |
148 |
149 |
150 |
151 | ```
152 |
153 |
159 |
--------------------------------------------------------------------------------
/src/routes/modes-and-formats/Tabs.svelte:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 | {#each items as item}
14 |
15 | {item.label}
16 |
17 | {/each}
18 |
19 | {#each items as item}
20 | {#if activeTabValue == item.value}
21 |
22 |
23 |
24 | {/if}
25 | {/each}
26 |
27 |
69 |
--------------------------------------------------------------------------------
/src/routes/modes-and-formats/format-php.md:
--------------------------------------------------------------------------------
1 | | Format | Description | Example
2 | |------|---------------------------------------------------------------------|--------------------------------------
3 | | `d` | Day of the month, 2 digits with leading zeros | 01 to 31
4 | | `D` | A textual representation of a day, three letters | Mon through Sun
5 | | `j` | Day of the month without leading zeros | 1 to 31
6 | | `l` | A full textual representation of the day of the week | Sunday through Saturday
7 | | `N` | ISO 8601 numeric representation of the day of the week | 1 (for Monday) through 7 (for Sunday)
8 | | `S` | English ordinal suffix for the day of the month, 2 characters | st, nd, rd or th. Works well with `j`
9 | | `F` | A full textual representation of a month, such as January or March | January through December
10 | | `m` | Numeric representation of a month, with leading zeros | 01 through 12
11 | | `M` | A short textual representation of a month, three letters | Jan through Dec
12 | | `n` | Numeric representation of a month, without leading zeros | 1 through 12
13 | | `Y` | A full numeric representation of a year, at least 4 digits | 0787, 1999, 2003
14 | | `y` | A two digit representation of a year | 99 or 03
15 | | `a` | Lowercase Ante meridiem and Post meridiem | am or pm
16 | | `A` | Uppercase Ante meridiem and Post meridiem | AM or PM
17 | | `g` | 12-hour format of an hour without leading zeros | 1 through 12
18 | | `G` | 24-hour format of an hour without leading zeros | 0 through 23
19 | | `h` | 12-hour format of an hour with leading zeros | 01 through 12
20 | | `H` | 24-hour format of an hour with leading zeros | 00 through 23
21 | | `i` | Minutes with leading zeros | 00 to 59
22 | | `s` | Seconds with leading zeros | 00
23 | | `U` | timestamp in seconds. |
24 |
25 |
26 | ☝️ For timestamp with miliseconds use `standard` formatting.
27 |
--------------------------------------------------------------------------------
/src/routes/modes-and-formats/format-standard.md:
--------------------------------------------------------------------------------
1 | | Format | Description | Example
2 | |--------|----------------------------------------------------------------------------------|--------------------------------------
3 | | `d` | day of the month without leading zeros | 1 to 31
4 | | `dd` | day of the month, 2 digits with leading zeros | 01 to 31
5 | | `D` | short textual representation of a weekday (i18n.daysShort) | Mon through Sun
6 | | `DD` | long textual representation of a weekday (i18n.days) | Sunday through Saturday
7 | | `S` | English ordinal suffix for the day of the month, (i18n.suffix) | st, nd, rd or th. Works well with `d`
8 | | `m` | numeric representation of month without leading zeros | 1 to 12
9 | | `mm` | numeric representation of the month, 2 digits with leading zeros | 01 to 12
10 | | `M` | short textual representation of a month, three letters (i18n.monthsShort) | Jan through Dec
11 | | `MM` | full textual representation of a month, such as January or March (i18n.months) | January through December
12 | | `yy` | two digit representation of a year | 99 or 03
13 | | `yyyy` | full numeric representation of a year, 4 digits | 1999, 2003
14 | | `h` | hour without leading zeros - 24-hour format | 0 - 23
15 | | `hh` | hour, 2 digits with leading zeros - 24-hour format | 00 - 23
16 | | `H` | hour without leading zeros - 12-hour format | 1 - 12
17 | | `HH` | hour, 2 digits with leading zeros - 12-hour format | 01 - 12
18 | | `i` | minutes, 2 digits with leading zeros | 00 - 59
19 | | `ii` | alias for `i` | 00 - 59
20 | | `s` | seconds, 2 digits with leading zeros | 00
21 | | `ss` | alias for `s` | 00
22 | | `p` | meridian in lower case ('am' or 'pm') - according to locale file (i18n.meridiem) | am or pm
23 | | `P` | meridian in upper case ('AM' or 'PM') - according to locale file (i18n.meridiem) | AM or PM
24 | | `t` | timestamp in milliseconds (although milliseconds are always 0). |
25 |
26 |
27 | ☝️ For timestamp in seconds use `php` formatting.
28 |
--------------------------------------------------------------------------------
/src/routes/properties/+page.svelte.md:
--------------------------------------------------------------------------------
1 | # Properties
2 |
3 | Scroll down for [event callback props](#event-callback-props).
4 |
5 |
6 |
7 | | Property | Type | Default | Description |
8 | |-----------------|-----------------------------|-------------------|-------------------|
9 | | inputId | `string` | `""` | id attribute for input element
10 | | name | `string` | `'date'` | html attribute for underlying ` ` element |
11 | | disabled | `bool` | `false` | html attribute for underlying ` ` element |
12 | | placeholder | `string` | `null` | html attribute for underlying ` ` element |
13 | | required | `bool` | `false` | html attribute for underlying ` ` element |
14 | | value | `string`\|`string[]`\|`null`| `null` | string representation of selected value. When in daterange mode (`isRange` prop), array of two strings is required |
15 | | isRange | `bool` | `false` | enables range picker mode |
16 | | startDate | string\|Date
| `null` | limit minimal selectable date |
17 | | endDate | string\|Date
| `null` | limit maximal selectable date |
18 | | pickerOnly | `bool` | `false` | Picker is always visible and input field is then hidden, but still present |
19 | | startView | `number` | `2` | Which mode should picker at, `0` - decade, `1` - year, `2` - month (default), `3` - time picker
20 | | mode | `string` | `auto` | restrict picker's mode. Possible values: `auto\|date\|datetime\|time`. By default it try to guess the mode from `format` |
21 | | disableDatesFn | `function` | `null` | Function whether passed date should be disabled or not |
22 | | manualInput | `bool` | `false` | Whether manual date entry is allowed |
23 | | format | `string` | `'yyyy-mm-dd'` | Format of entered date/time. |
24 | | formatType | `string` | `'standard'` | Format type (`standard` or `php`) |
25 | | displayFormat | `string` | `null` | Display format of entered date/time. |
26 | | displayFormatType | `string` | `null` | Display format type (`standard` or `php`) |
27 | | hourOnly | `bool` | `false` | Only allow hour selection for the time portion of the datetime selection
28 | | minuteIncrement | `number` | `1` | number in range `1-60` to set the increment of minutes choosable |
29 | | weekStart | `number` | `1` | number in range `0-6` to select first day of the week. Sunday is `0` |
30 | | inputClasses | `string` | `""` | input css class string |
31 | | todayBtnClasses | `string` | `'sdt-action-btn sdt-today-btn'` | today button css classes |
32 | | clearBtnClasses | `string` | `'sdt-action-btn sdt-clear-btn'` | clear button css classes |
33 | | todayBtn | `bool` | `true` | Show today button |
34 | | clearBtn | `bool` | `true` | Show clear button |
35 | | clearToggle | `bool` | `true` | Allows to clear selected date when clicking on the same date when in `mode='date'` or `mode='auto'` resolving to `'date'` |
36 | | autocommit | `bool` | `true` | Whether date/time selection is automatic or manual |
37 | | i18n | `object` | `en` | localization object, english is by default |
38 | | validatorAction | `array` | `null` | Bind validator action for inner ` ` element. Designed to be used with `svelte-use-form`.
39 | | positionResolver | `function` | internal | Action which resolves floating position of picker. Default one uses `@floating-ui` under the hood. So you can use this library for your custom position resolver function
40 |
41 |
42 |
43 |
44 | ## Event callback props
45 |
46 |
47 |
48 |
49 | | Property | Arguments | Description |
50 | |-----------------|-------------------------------|--------------
51 | | `onChange` | `string`\|`string[]`\|`null` | Happens when value is changed. New value is passed as argument
52 | | `onDatechange` | `prop: onChangeProperty` | Happens when value is changed, but details passed on the event are more detailed, see the type below
53 | | `onFocus` | `` | When input is focused. Won't be trigger in `pickerOnly` mode
54 | | `onInput` | `string` | When manually entered input matches given `format`. `manualInput` must be set to allow value entering
55 | | `onBlur` | `` | When input loses focus. Won't be trigger in `pickerOnly` mode
56 |
57 |
58 |
59 | ```ts
60 | type onChangeProperty = {
61 | value: string|string[],
62 | dateValue: Date|Date[],
63 | displayValue: string,
64 | valueFormat: string,
65 | displayFormat: string,
66 | event: 'date'|'hour'|'minute'|'datetime' // which event triggered the callback
67 | }
68 | ```
69 |
--------------------------------------------------------------------------------
/src/routes/snippets/+page.svelte.md:
--------------------------------------------------------------------------------
1 |
24 |
25 | # Snippets
26 |
27 | There are 2 available snippets to override/customize.
28 |
29 |
30 | ## children
31 |
32 | This snippet can be used for adding icon. Don't forget to position your icon absolutely. Or move your styling to ` `'s parent element `.sdt-input-wrap`.
33 |
34 | Example of custom interactive svg icon showing currently selected date:
35 |
36 |
37 |
38 |
45 |
49 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | Pick
72 |
73 | ?
82 |
83 | Date
92 |
93 |
94 |
95 | ```svelte
96 |
97 |
98 |
99 | ```
100 |
101 | ## actionRow
102 |
103 | Snippet represents row below calendar/time picker. By default showing ***Today*** and ***Clear*** buttons. And if `autocommit`=`false` also
104 | ***Ok*** and ***Cancel*** buttons.
105 |
106 | 💡 If you just want to hide ***Today*** or ***Clear*** buttons, use controlling props and provide appropriate `i18n` message
107 | instead of overriding `actionRow` snippet.
108 |
109 | ### Snipet type
110 |
111 | ```ts
112 | Snippet<[ prop: {
113 | autocloseSupported: boolean,
114 | todayBtnClasses: string,
115 | clearBtnClasses: string,
116 | onCancel: function,
117 | onConfirm: function,
118 | onClear: function,
119 | onToday: function,
120 | isTodayDisabled: boolean,
121 | i18n: import('svelty-picker/i18n').i18nType,
122 | currentMode: string
123 | }]>
124 | ```
125 |
126 | Parameter description:
127 |
128 | - `autocloseSupported` - determined by `autocommit` and `isRange` properties
129 | - `todayBtnClasses` - CSS classes for 'today' button
130 | - `clearBtnClasses` - CSS classes for 'clear' button
131 | - `onCancel` - discards current selection (when `autocommit` is set to `false`)
132 | - `onConfirm` - handler which confirms current selection (when `autocommit` is set to `false`)
133 | - `onClear` - clears current selection
134 | - `onToday` - selects today date
135 | - `isTodayDisabled` - useful when you use `startDate` or `endDate` or `disableDatesFn` to disable dates. Serves for disabled 'Today' button
136 | - `i18n` - current locale
137 | - `currentMode` - returns currently active mode: `date` or `time` (where `time` is available only for datetime or time picker)
138 |
139 | ### Default implementation
140 |
141 | ```svelte
142 | {#snippet action_row({
143 | autocloseSupported,
144 | todayBtnClasses,
145 | clearBtnClasses,
146 | onCancel,
147 | onConfirm,
148 | onClear,
149 | onToday,
150 | isTodayDisabled,
151 | i18n,
152 | currentMode
153 | })}
154 | {#if !autocloseSupported || true}
155 |
156 | {#if !autocloseSupported}
157 |
158 |
163 | {i18n.cancelBtn}
164 |
165 |
170 | {i18n.okBtn}
171 |
172 |
173 | {/if}
174 | {#if todayBtn || clearBtn}
175 |
176 | {#if todayBtn && currentMode === 'date'}
177 |
183 | {i18n.todayBtn}
184 |
185 | {/if}
186 | {#if clearBtn}
187 |
192 | {i18n.clearBtn}
193 |
194 | {/if}
195 |
196 | {/if}
197 |
198 | {/if}
199 | {/snippet}
200 | ```
201 |
202 |
223 |
--------------------------------------------------------------------------------
/src/routes/theme/+page.svelte.md:
--------------------------------------------------------------------------------
1 | # Theme
2 |
3 | Component exposes following CSS variables to customize it's appearance as you can see through whole in example below or on the homepage.
4 |
5 | ```css
6 | :root {
7 | /* general */
8 | --sdt-bg-main: #fff; /** wrap background color */
9 | --sdt-shadow-color: #ccc; /** wrap shadow color */
10 | --sdt-wrap-shadow: 0 1px 6px var(--sdt-shadow-color); /** wrap shadow settings */
11 | --sdt-radius: 4px; /** wrap radius */
12 | --sdt-color: #000; /** data to select(e.g date/time) text color (except header & buttons) */
13 | --sdt-color-selected: #fff; /** selected data(e.g date/time) text color */
14 | --sdt-header-color: #000; /** header items color (e.g. text & buttons) */
15 | --sdt-header-btn-bg-hover: #dfdfdf; /** header items hover background color */
16 | --sdt-bg-selected: #286090; /** selected data(e.g date/time) background color */
17 |
18 | /* action buttons */
19 | --sdt-today-bg: #1e486d; /** date picker today button hover background color */
20 | --sdt-today-color: var(--sdt-bg-main); /** date picker today button text & border color */
21 | --sdt-clear-color: #dc3545; /** clear button text & border color */
22 | --sdt-clear-bg: transparent; /** clear button background color */
23 | --sdt-clear-hover-color: var(--sdt-bg-main); /** clear button hover text color */
24 | --sdt-clear-hover-bg: #dc3545; /** clear button hover background color */
25 |
26 | /* time picker */
27 | --sdt-clock-selected-bg: var(--sdt-bg-selected); /** selected time background color */
28 | --sdt-clock-bg: #eeeded; /** time picker inner circle background color */
29 | --sdt-clock-color: var(--sdt-color); /** time picker text color (watch "--sdt-color") */
30 | --sdt-clock-color-hover: var(--sdt-color); /** time picker hover text color (watch "--sdt-color") */
31 | --sdt-clock-time-bg: transparent; /** time picker time background color */
32 | --sdt-clock-time-bg-hover: transparent; /** time picker time selection hover background color */
33 | --sdt-clock-disabled-time: #b22222; /** disabled time picker time text color */
34 | --sdt-clock-disabled-time-bg: #eee; /** disabled time picker time background color */
35 |
36 | /* date picker */
37 | --sdt-table-selected-bg: var(--sdt-bg-selected); /** selected date background color */
38 | --sdt-table-disabled-date: #b22222; /** disabled dates text color */
39 | --sdt-table-disabled-date-bg: #eee; /** disabled dates background color */
40 | --sdt-table-bg: transparent; /** date picker inner table background color */
41 | --sdt-table-data-bg-hover: #eee; /** table selection data hover background color */
42 | --sdt-table-today-indicator: #ccc; /** date picker current day marker color */
43 |
44 | }
45 | ```
46 |
47 | ### Definitions from this website
48 |
49 | ```html
50 |
109 | ```
110 |
--------------------------------------------------------------------------------
/src/routes/working-with-dates/+page.svelte.md:
--------------------------------------------------------------------------------
1 |
11 |
12 | # Working with dates
13 |
14 | Although library works internally `Date` objects, `value` property must be `string` or `string[]` when in daterange mode.
15 | It makes internal logic simpler and makes library dependency-free.
16 |
17 | Since svelte 5.9 you can use [function binding](https://svelte.dev/docs/svelte/bind#Function-bindings) for binding `value` property.
18 | Therefore you can convert your date objects to strings and vice versa. Even handle timezones! There are 2 helper functions
19 | available to help you with these transformations. Library use them internaly to transform to/from specified [formats](/modes-and-formats).
20 |
21 |
22 | ```js
23 | /**
24 | * String -> date
25 | *
26 | * @param {Date|string} date
27 | * @param {string} format
28 | * @param {i18nType} i18n
29 | * @param {string} type
30 | * @returns {Date}
31 | */
32 | export function parseDate(date, format, i18n, type) { /* ... */}
33 |
34 | /**
35 | * Date -> string
36 | *
37 | * @param {Date} date
38 | * @param {string} format
39 | * @param {i18nType} i18n
40 | * @param {string} type
41 | * @returns {string}
42 | */
43 | export function formatDate(date, format, i18n, type) { /* ... */}
44 | ```
45 |
46 |
47 |
48 | Version 5 and below
49 |
50 |
51 | In v5 it was possible to set initial date through initialDate
, which has been removed in v6.
52 |
53 |
54 |
55 |
56 |
57 | ### Example
58 |
59 | `date` variable value: {date}
60 | {
65 | return formatDate(date, 'yyyy/mm/dd', en, 'standard');
66 | },
67 | (v) => {
68 | date = v ? parseDate(v, 'yyyy/mm/dd', en, 'standard') : null;
69 | }
70 | }
71 | />
72 |
73 | ```svelte
74 |
84 |
85 | Selected date: {date}
86 | {
91 | return formatDate(date, 'yyyy/mm/dd', en, 'standard');
92 | },
93 | (v) => {
94 | date = v ? parseDate(v, 'yyyy/mm/dd', en, 'standard') : null;
95 | }
96 | }
97 | />
98 | ```
99 |
--------------------------------------------------------------------------------
/src/style/base.css:
--------------------------------------------------------------------------------
1 | @media (prefers-reduced-motion: reduce) {
2 | *,
3 | ::before,
4 | ::after {
5 | animation-delay: -1ms !important;
6 | animation-duration: 1ms !important;
7 | animation-iteration-count: 1 !important;
8 | background-attachment: initial !important;
9 | scroll-behavior: auto !important;
10 | transition-duration: 0s !important;
11 | transition-delay: 0s !important;
12 | }
13 | }
14 |
15 | *,
16 | ::before,
17 | ::after {
18 | box-sizing: border-box;
19 | }
20 |
21 | html {
22 | line-height: 1.4;
23 | font-size: 16px;
24 | -webkit-text-size-adjust: 100%;
25 | }
26 |
27 | html.dark {
28 | color-scheme: dark;
29 | }
30 |
31 | body {
32 | margin: 0;
33 | width: 100%;
34 | min-width: 320px;
35 | min-height: 100vh;
36 | line-height: 24px;
37 | font-family: var(--vp-font-family-base);
38 | font-size: 16px;
39 | font-weight: 400;
40 | color: var(--vp-c-text-1);
41 | background-color: var(--vp-c-bg);
42 | font-synthesis: style;
43 | text-rendering: optimizeLegibility;
44 | -webkit-font-smoothing: antialiased;
45 | -moz-osx-font-smoothing: grayscale;
46 | }
47 |
48 | main {
49 | display: block;
50 | }
51 |
52 | h1,
53 | h2,
54 | h3,
55 | h4,
56 | h5,
57 | h6 {
58 | margin: 0;
59 | line-height: 24px;
60 | font-size: 16px;
61 | font-weight: 400;
62 | }
63 |
64 | p {
65 | margin: 0;
66 | }
67 |
68 | strong,
69 | b {
70 | font-weight: 600;
71 | }
72 |
73 | /**
74 | * Avoid 300ms click delay on touch devices that support the `touch-action`
75 | * CSS property.
76 | *
77 | * In particular, unlike most other browsers, IE11+Edge on Windows 10 on
78 | * touch devices and IE Mobile 10-11 DON'T remove the click delay when
79 | * ` ` is present.
80 | * However, they DO support removing the click delay via
81 | * `touch-action: manipulation`.
82 | *
83 | * See:
84 | * - http://v4-alpha.getbootstrap.com/content/reboot/#click-delay-optimization-for-touch
85 | * - http://caniuse.com/#feat=css-touch-action
86 | * - http://patrickhlauke.github.io/touch/tests/results/#suppressing-300ms-delay
87 | */
88 | a,
89 | area,
90 | button,
91 | [role='button'],
92 | input,
93 | label,
94 | select,
95 | summary,
96 | textarea {
97 | touch-action: manipulation;
98 | }
99 |
100 | a {
101 | color: inherit;
102 | text-decoration: inherit;
103 | }
104 |
105 | ol,
106 | ul {
107 | list-style: none;
108 | margin: 0;
109 | padding: 0;
110 | }
111 |
112 | blockquote {
113 | margin: 0;
114 | }
115 |
116 | pre,
117 | code,
118 | kbd,
119 | samp {
120 | font-family: var(--vp-font-family-mono);
121 | }
122 |
123 | img,
124 | svg,
125 | video,
126 | canvas,
127 | audio,
128 | iframe,
129 | embed,
130 | object {
131 | display: block;
132 | }
133 |
134 | figure {
135 | margin: 0;
136 | }
137 |
138 | img,
139 | video {
140 | max-width: 100%;
141 | height: auto;
142 | }
143 |
144 | button,
145 | input,
146 | optgroup,
147 | select,
148 | textarea {
149 | border: 0;
150 | padding: 0;
151 | line-height: inherit;
152 | color: inherit;
153 | }
154 |
155 | button {
156 | padding: 0;
157 | font-family: inherit;
158 | background-color: transparent;
159 | background-image: none;
160 | }
161 |
162 | button:enabled,
163 | [role='button']:enabled {
164 | cursor: pointer;
165 | }
166 |
167 | button:focus,
168 | button:focus-visible {
169 | outline: 1px dotted;
170 | outline: 4px auto -webkit-focus-ring-color;
171 | }
172 |
173 | button:focus:not(:focus-visible) {
174 | outline: none !important;
175 | }
176 |
177 | input:focus,
178 | textarea:focus,
179 | select:focus {
180 | outline: none;
181 | }
182 |
183 | table {
184 | border-collapse: collapse;
185 | }
186 |
187 | input {
188 | background-color: transparent;
189 | }
190 |
191 | input:-ms-input-placeholder,
192 | textarea:-ms-input-placeholder {
193 | color: var(--vp-c-text-3);
194 | }
195 |
196 | input::-ms-input-placeholder,
197 | textarea::-ms-input-placeholder {
198 | color: var(--vp-c-text-3);
199 | }
200 |
201 | input::placeholder,
202 | textarea::placeholder {
203 | color: var(--vp-c-text-3);
204 | }
205 |
206 | input::-webkit-outer-spin-button,
207 | input::-webkit-inner-spin-button {
208 | -webkit-appearance: none;
209 | margin: 0;
210 | }
211 |
212 | input[type='number'] {
213 | -moz-appearance: textfield;
214 | }
215 |
216 | textarea {
217 | resize: vertical;
218 | }
219 |
220 | select {
221 | -webkit-appearance: none;
222 | }
223 |
224 | fieldset {
225 | margin: 0;
226 | padding: 0;
227 | }
228 |
229 | h1,
230 | h2,
231 | h3,
232 | h4,
233 | h5,
234 | h6,
235 | li,
236 | p {
237 | overflow-wrap: break-word;
238 | }
239 |
240 | vite-error-overlay {
241 | z-index: 9999;
242 | }
243 |
244 | mjx-container {
245 | display: inline-block;
246 | margin: auto 2px -2px;
247 | }
248 |
249 | mjx-container > svg {
250 | display: inline-block;
251 | margin: auto;
252 | }
253 |
254 | .app-wrap {
255 | min-height: 100vh;
256 | display: flex;
257 | }
258 | .page-container {
259 | display: flex;
260 | flex: 1;
261 | flex-direction: column;
262 | justify-content: space-between;
263 | }
264 |
--------------------------------------------------------------------------------
/src/style/code.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Headings
3 | * -------------------------------------------------------------------------- */
4 |
5 | .vp-doc h1,
6 | .vp-doc h2,
7 | .vp-doc h3,
8 | .vp-doc h4,
9 | .vp-doc h5,
10 | .vp-doc h6 {
11 | position: relative;
12 | font-weight: 600;
13 | outline: none;
14 | }
15 |
16 | .vp-doc h1 {
17 | letter-spacing: -0.02em;
18 | line-height: 40px;
19 | font-size: 28px;
20 | }
21 |
22 | .vp-doc h2 {
23 | margin: 48px 0 16px;
24 | border-top: 1px solid var(--vp-c-divider);
25 | padding-top: 24px;
26 | letter-spacing: -0.02em;
27 | line-height: 32px;
28 | font-size: 24px;
29 | }
30 |
31 | .vp-doc h3 {
32 | margin: 32px 0 0;
33 | letter-spacing: -0.01em;
34 | line-height: 28px;
35 | font-size: 20px;
36 | }
37 |
38 | .vp-doc .header-anchor {
39 | position: absolute;
40 | top: 0;
41 | left: 0;
42 | margin-left: -0.87em;
43 | font-weight: 500;
44 | user-select: none;
45 | opacity: 0;
46 | text-decoration: none;
47 | transition: color 0.25s, opacity 0.25s;
48 | }
49 |
50 | .vp-doc .header-anchor:before {
51 | content: var(--vp-header-anchor-symbol);
52 | }
53 |
54 | .vp-doc h1:hover .header-anchor,
55 | .vp-doc h1 .header-anchor:focus,
56 | .vp-doc h2:hover .header-anchor,
57 | .vp-doc h2 .header-anchor:focus,
58 | .vp-doc h3:hover .header-anchor,
59 | .vp-doc h3 .header-anchor:focus,
60 | .vp-doc h4:hover .header-anchor,
61 | .vp-doc h4 .header-anchor:focus,
62 | .vp-doc h5:hover .header-anchor,
63 | .vp-doc h5 .header-anchor:focus,
64 | .vp-doc h6:hover .header-anchor,
65 | .vp-doc h6 .header-anchor:focus {
66 | opacity: 1;
67 | }
68 |
69 | @media (min-width: 768px) {
70 | .vp-doc h1 {
71 | letter-spacing: -0.02em;
72 | line-height: 40px;
73 | font-size: 32px;
74 | }
75 | }
76 |
77 | .vp-doc h2 .header-anchor {
78 | top: 24px;
79 | }
80 |
81 | /**
82 | * Paragraph and inline elements
83 | * -------------------------------------------------------------------------- */
84 |
85 | .vp-doc p,
86 | .vp-doc summary {
87 | margin: 16px 0;
88 | }
89 |
90 | .vp-doc p {
91 | line-height: 28px;
92 | }
93 |
94 | .vp-doc blockquote {
95 | margin: 16px 0;
96 | border-left: 2px solid var(--vp-c-divider);
97 | padding-left: 16px;
98 | transition: border-color 0.5s;
99 | }
100 |
101 | .vp-doc blockquote > p {
102 | margin: 0;
103 | font-size: 16px;
104 | color: var(--vp-c-text-2);
105 | transition: color 0.5s;
106 | }
107 |
108 | .vp-doc a {
109 | font-weight: 500;
110 | color: var(--vp-c-brand-1);
111 | text-decoration: underline;
112 | text-underline-offset: 2px;
113 | transition: color 0.25s, opacity 0.25s;
114 | }
115 |
116 | .vp-doc a:hover {
117 | color: var(--vp-c-brand-2);
118 | }
119 |
120 | .vp-doc strong {
121 | font-weight: 600;
122 | }
123 |
124 | /**
125 | * Lists
126 | * -------------------------------------------------------------------------- */
127 |
128 | .vp-doc ul,
129 | .vp-doc ol {
130 | padding-left: 1.25rem;
131 | margin: 16px 0;
132 | }
133 |
134 | .vp-doc ul {
135 | list-style: disc;
136 | }
137 |
138 | .vp-doc ol {
139 | list-style: decimal;
140 | }
141 |
142 | .vp-doc li + li {
143 | margin-top: 8px;
144 | }
145 |
146 | .vp-doc li > ol,
147 | .vp-doc li > ul {
148 | margin: 8px 0 0;
149 | }
150 |
151 | /**
152 | * Table
153 | * -------------------------------------------------------------------------- */
154 |
155 | .vp-doc table {
156 | display: block;
157 | border-collapse: collapse;
158 | margin: 20px 0;
159 | overflow-x: auto;
160 | }
161 |
162 | .vp-doc tr {
163 | background-color: var(--vp-c-bg);
164 | border-top: 1px solid var(--vp-c-divider);
165 | transition: background-color 0.5s;
166 | }
167 |
168 | .vp-doc tr:nth-child(2n) {
169 | background-color: var(--vp-c-bg-soft);
170 | }
171 |
172 | .vp-doc th,
173 | .vp-doc td {
174 | border: 1px solid var(--vp-c-divider);
175 | padding: 8px 16px;
176 | }
177 |
178 | .vp-doc th {
179 | text-align: left;
180 | font-size: 14px;
181 | font-weight: 600;
182 | color: var(--vp-c-text-2);
183 | background-color: var(--vp-c-bg-soft);
184 | }
185 |
186 | .vp-doc td {
187 | font-size: 14px;
188 | }
189 |
190 | /**
191 | * Decorational elements
192 | * -------------------------------------------------------------------------- */
193 |
194 | .vp-doc hr {
195 | margin: 16px 0;
196 | border: none;
197 | border-top: 1px solid var(--vp-c-divider);
198 | }
199 |
200 | /**
201 | * Custom Block
202 | * -------------------------------------------------------------------------- */
203 |
204 | .vp-doc .custom-block {
205 | margin: 16px 0;
206 | }
207 |
208 | .vp-doc .custom-block p {
209 | margin: 8px 0;
210 | line-height: 24px;
211 | }
212 |
213 | .vp-doc .custom-block p:first-child {
214 | margin: 0;
215 | }
216 |
217 | .vp-doc .custom-block div[class*="language-"] {
218 | margin: 8px 0;
219 | border-radius: 8px;
220 | }
221 |
222 | .vp-doc .custom-block div[class*="language-"] code {
223 | font-weight: 400;
224 | background-color: transparent;
225 | }
226 |
227 | .vp-doc .custom-block .vp-code-group .tabs {
228 | margin: 0;
229 | border-radius: 8px 8px 0 0;
230 | }
231 |
232 | /**
233 | * Code
234 | * -------------------------------------------------------------------------- */
235 |
236 | /* inline code */
237 | .vp-doc :not(pre, h1, h2, h3, h4, h5, h6) > code {
238 | font-size: var(--vp-code-font-size);
239 | color: var(--vp-code-color);
240 | }
241 |
242 | .vp-doc :not(pre) > code {
243 | border-radius: 4px;
244 | padding: 3px 6px;
245 | background-color: var(--vp-code-bg);
246 | transition: color 0.25s, background-color 0.5s;
247 | }
248 |
249 | .vp-doc a > code {
250 | color: var(--vp-code-link-color);
251 | }
252 |
253 | .vp-doc a:hover > code {
254 | color: var(--vp-code-link-hover-color);
255 | }
256 |
257 | .vp-doc h1 > code,
258 | .vp-doc h2 > code,
259 | .vp-doc h3 > code {
260 | font-size: 0.9em;
261 | }
262 |
263 | .vp-doc div[class*="language-"],
264 | .vp-block {
265 | position: relative;
266 | margin: 16px -24px;
267 | background-color: var(--vp-code-block-bg);
268 | overflow-x: auto;
269 | transition: background-color 0.5s;
270 | }
271 |
272 | @media (min-width: 640px) {
273 | .vp-doc div[class*="language-"],
274 | .vp-block {
275 | border-radius: 8px;
276 | margin: 16px 0;
277 | }
278 | }
279 |
280 | @media (max-width: 639px) {
281 | .vp-doc li div[class*="language-"] {
282 | border-radius: 8px 0 0 8px;
283 | }
284 | }
285 |
286 | .vp-doc div[class*="language-"] + div[class*="language-"],
287 | .vp-doc div[class$="-api"] + div[class*="language-"],
288 | .vp-doc div[class*="language-"] + div[class$="-api"] > div[class*="language-"] {
289 | margin-top: -8px;
290 | }
291 |
292 | .vp-doc [class*="language-"] pre,
293 | .vp-doc [class*="language-"] code {
294 | /*rtl:ignore*/
295 | direction: ltr;
296 | /*rtl:ignore*/
297 | text-align: left;
298 | white-space: pre;
299 | word-spacing: normal;
300 | word-break: normal;
301 | word-wrap: normal;
302 | -moz-tab-size: 4;
303 | -o-tab-size: 4;
304 | tab-size: 4;
305 | -webkit-hyphens: none;
306 | -moz-hyphens: none;
307 | -ms-hyphens: none;
308 | hyphens: none;
309 | }
310 |
311 | .vp-doc [class*="language-"] pre {
312 | position: relative;
313 | z-index: 1;
314 | margin: 0;
315 | padding: 20px 0;
316 | background: transparent;
317 | overflow-x: auto;
318 | }
319 |
320 | .vp-doc [class*="language-"] code {
321 | display: block;
322 | padding: 0 24px;
323 | width: fit-content;
324 | min-width: 100%;
325 | line-height: var(--vp-code-line-height);
326 | font-size: var(--vp-code-font-size);
327 | color: var(--vp-code-block-color);
328 | transition: color 0.5s;
329 | }
330 |
331 | .vp-doc [class*="language-"] code .highlighted {
332 | background-color: var(--vp-code-line-highlight-color);
333 | transition: background-color 0.5s;
334 | margin: 0 -24px;
335 | padding: 0 24px;
336 | width: calc(100% + 2 * 24px);
337 | display: inline-block;
338 | }
339 |
340 | .vp-doc [class*="language-"] code .highlighted.error {
341 | background-color: var(--vp-code-line-error-color);
342 | }
343 |
344 | .vp-doc [class*="language-"] code .highlighted.warning {
345 | background-color: var(--vp-code-line-warning-color);
346 | }
347 |
348 | .vp-doc [class*="language-"] code .diff {
349 | transition: background-color 0.5s;
350 | margin: 0 -24px;
351 | padding: 0 24px;
352 | width: calc(100% + 2 * 24px);
353 | display: inline-block;
354 | }
355 |
356 | .vp-doc [class*="language-"] code .diff::before {
357 | position: absolute;
358 | left: 10px;
359 | }
360 |
361 | .vp-doc [class*="language-"] .has-focused-lines .line:not(.has-focus) {
362 | filter: blur(0.095rem);
363 | opacity: 0.4;
364 | transition: filter 0.35s, opacity 0.35s;
365 | }
366 |
367 | .vp-doc [class*="language-"] .has-focused-lines .line:not(.has-focus) {
368 | opacity: 0.7;
369 | transition: filter 0.35s, opacity 0.35s;
370 | }
371 |
372 | .vp-doc [class*="language-"]:hover .has-focused-lines .line:not(.has-focus) {
373 | filter: blur(0);
374 | opacity: 1;
375 | }
376 |
377 | .vp-doc [class*="language-"] code .diff.remove {
378 | background-color: var(--vp-code-line-diff-remove-color);
379 | opacity: 0.7;
380 | }
381 |
382 | .vp-doc [class*="language-"] code .diff.remove::before {
383 | content: "-";
384 | color: var(--vp-code-line-diff-remove-symbol-color);
385 | }
386 |
387 | .vp-doc [class*="language-"] code .diff.add {
388 | background-color: var(--vp-code-line-diff-add-color);
389 | }
390 |
391 | .vp-doc [class*="language-"] code .diff.add::before {
392 | content: "+";
393 | color: var(--vp-code-line-diff-add-symbol-color);
394 | }
395 |
396 | .vp-doc div[class*="language-"].line-numbers-mode {
397 | /*rtl:ignore*/
398 | padding-left: 32px;
399 | }
400 |
401 | .vp-doc .line-numbers-wrapper {
402 | position: absolute;
403 | top: 0;
404 | bottom: 0;
405 | /*rtl:ignore*/
406 | left: 0;
407 | z-index: 3;
408 | /*rtl:ignore*/
409 | border-right: 1px solid var(--vp-code-block-divider-color);
410 | padding-top: 20px;
411 | width: 32px;
412 | text-align: center;
413 | font-family: var(--vp-font-family-mono);
414 | line-height: var(--vp-code-line-height);
415 | font-size: var(--vp-code-font-size);
416 | color: var(--vp-code-line-number-color);
417 | transition: border-color 0.5s, color 0.5s;
418 | }
419 |
420 | .vp-doc [class*="language-"] > button.copy {
421 | /*rtl:ignore*/
422 | direction: ltr;
423 | position: absolute;
424 | top: 12px;
425 | /*rtl:ignore*/
426 | right: 12px;
427 | z-index: 3;
428 | border: 1px solid var(--vp-code-copy-code-border-color);
429 | border-radius: 4px;
430 | width: 40px;
431 | height: 40px;
432 | background-color: var(--vp-code-copy-code-bg);
433 | opacity: 0;
434 | cursor: pointer;
435 | background-image: var(--vp-icon-copy);
436 | background-position: 50%;
437 | background-size: 20px;
438 | background-repeat: no-repeat;
439 | transition: border-color 0.25s, background-color 0.25s, opacity 0.25s;
440 | }
441 |
442 | .vp-doc [class*="language-"]:hover > button.copy,
443 | .vp-doc [class*="language-"] > button.copy:focus {
444 | opacity: 1;
445 | }
446 |
447 | .vp-doc [class*="language-"] > button.copy:hover,
448 | .vp-doc [class*="language-"] > button.copy.copied {
449 | border-color: var(--vp-code-copy-code-hover-border-color);
450 | background-color: var(--vp-code-copy-code-hover-bg);
451 | }
452 |
453 | .vp-doc [class*="language-"] > button.copy.copied,
454 | .vp-doc [class*="language-"] > button.copy:hover.copied {
455 | /*rtl:ignore*/
456 | border-radius: 0 4px 4px 0;
457 | background-color: var(--vp-code-copy-code-hover-bg);
458 | background-image: var(--vp-icon-copied);
459 | }
460 |
461 | .vp-doc [class*="language-"] > button.copy.copied::before,
462 | .vp-doc [class*="language-"] > button.copy:hover.copied::before {
463 | position: relative;
464 | top: -1px;
465 | /*rtl:ignore*/
466 | transform: translateX(calc(-100% - 1px));
467 | display: flex;
468 | justify-content: center;
469 | align-items: center;
470 | border: 1px solid var(--vp-code-copy-code-hover-border-color);
471 | /*rtl:ignore*/
472 | border-right: 0;
473 | border-radius: 4px 0 0 4px;
474 | padding: 0 10px;
475 | width: fit-content;
476 | height: 40px;
477 | text-align: center;
478 | font-size: 12px;
479 | font-weight: 500;
480 | color: var(--vp-code-copy-code-active-text);
481 | background-color: var(--vp-code-copy-code-hover-bg);
482 | white-space: nowrap;
483 | content: var(--vp-code-copy-copied-text-content);
484 | }
485 |
486 | .vp-doc [class*="language-"] > span.lang {
487 | position: absolute;
488 | top: 2px;
489 | /*rtl:ignore*/
490 | right: 8px;
491 | z-index: 2;
492 | font-size: 12px;
493 | font-weight: 500;
494 | color: var(--vp-code-lang-color);
495 | transition: color 0.4s, opacity 0.4s;
496 | }
497 |
498 | .vp-doc [class*="language-"]:hover > button.copy + span.lang,
499 | .vp-doc [class*="language-"] > button.copy:focus + span.lang {
500 | opacity: 0;
501 | }
502 |
503 | /**
504 | * Component: Team
505 | * -------------------------------------------------------------------------- */
506 |
507 | .vp-doc .VPTeamMembers {
508 | margin-top: 24px;
509 | }
510 |
511 | .vp-doc .VPTeamMembers.small.count-1 .container {
512 | margin: 0 !important;
513 | max-width: calc((100% - 24px) / 2) !important;
514 | }
515 |
516 | .vp-doc .VPTeamMembers.small.count-2 .container,
517 | .vp-doc .VPTeamMembers.small.count-3 .container {
518 | max-width: 100% !important;
519 | }
520 |
521 | .vp-doc .VPTeamMembers.medium.count-1 .container {
522 | margin: 0 !important;
523 | max-width: calc((100% - 24px) / 2) !important;
524 | }
525 |
526 | /**
527 | * External links
528 | * -------------------------------------------------------------------------- */
529 |
530 | /* prettier-ignore */
531 | :is(.vp-external-link-icon, .vp-doc a[href*='://'], .vp-doc a[target='_blank']):not(.no-icon)::after {
532 | display: inline-block;
533 | margin-top: -1px;
534 | margin-left: 4px;
535 | width: 11px;
536 | height: 11px;
537 | background: currentColor;
538 | color: var(--vp-c-text-3);
539 | flex-shrink: 0;
540 | --icon: url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' %3E%3Cpath d='M0 0h24v24H0V0z' fill='none' /%3E%3Cpath d='M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5H9z' /%3E%3C/svg%3E");
541 | -webkit-mask-image: var(--icon);
542 | mask-image: var(--icon);
543 | }
544 |
545 | .vp-external-link-icon::after {
546 | content: "";
547 | }
548 |
549 | /* prettier-ignore */
550 | .external-link-icon-enabled :is(.vp-doc a[href*='://'], .vp-doc a[target='_blank'])::after {
551 | content: '';
552 | color: currentColor;
553 | }
554 |
555 | :global(.vp-adaptive-theme) {
556 | max-width: 98vw !important;
557 | }
558 |
--------------------------------------------------------------------------------
/src/style/doc.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Headings
3 | * -------------------------------------------------------------------------- */
4 |
5 | .vp-doc h1,
6 | .vp-doc h2,
7 | .vp-doc h3,
8 | .vp-doc h4,
9 | .vp-doc h5,
10 | .vp-doc h6 {
11 | position: relative;
12 | font-weight: 600;
13 | outline: none;
14 | }
15 |
16 | .vp-doc h1 {
17 | letter-spacing: -0.02em;
18 | line-height: 40px;
19 | font-size: 28px;
20 | }
21 |
22 | .vp-doc h2 {
23 | margin: 48px 0 16px;
24 | border-top: 1px solid var(--vp-c-divider);
25 | padding-top: 24px;
26 | letter-spacing: -0.02em;
27 | line-height: 32px;
28 | font-size: 24px;
29 | }
30 |
31 | .vp-doc h3 {
32 | margin: 32px 0 0;
33 | letter-spacing: -0.01em;
34 | line-height: 28px;
35 | font-size: 20px;
36 | }
37 |
38 | .vp-doc .header-anchor {
39 | position: absolute;
40 | top: 0;
41 | left: 0;
42 | margin-left: -0.87em;
43 | font-weight: 500;
44 | user-select: none;
45 | opacity: 0;
46 | text-decoration: none;
47 | transition:
48 | color 0.25s,
49 | opacity 0.25s;
50 | }
51 |
52 | .vp-doc .header-anchor:before {
53 | content: var(--vp-header-anchor-symbol);
54 | }
55 |
56 | .vp-doc h1:hover .header-anchor,
57 | .vp-doc h1 .header-anchor:focus,
58 | .vp-doc h2:hover .header-anchor,
59 | .vp-doc h2 .header-anchor:focus,
60 | .vp-doc h3:hover .header-anchor,
61 | .vp-doc h3 .header-anchor:focus,
62 | .vp-doc h4:hover .header-anchor,
63 | .vp-doc h4 .header-anchor:focus,
64 | .vp-doc h5:hover .header-anchor,
65 | .vp-doc h5 .header-anchor:focus,
66 | .vp-doc h6:hover .header-anchor,
67 | .vp-doc h6 .header-anchor:focus {
68 | opacity: 1;
69 | }
70 |
71 | @media (min-width: 768px) {
72 | .vp-doc h1 {
73 | letter-spacing: -0.02em;
74 | line-height: 40px;
75 | font-size: 32px;
76 | }
77 | }
78 |
79 | .vp-doc h2 .header-anchor {
80 | top: 24px;
81 | }
82 |
83 | /**
84 | * Paragraph and inline elements
85 | * -------------------------------------------------------------------------- */
86 |
87 | .vp-doc p,
88 | .vp-doc summary {
89 | margin: 16px 0;
90 | }
91 |
92 | .vp-doc p {
93 | line-height: 28px;
94 | }
95 |
96 | .vp-doc blockquote {
97 | margin: 16px 0;
98 | border-left: 2px solid var(--vp-c-divider);
99 | padding-left: 16px;
100 | transition: border-color 0.5s;
101 | }
102 |
103 | .vp-doc blockquote > p {
104 | margin: 0;
105 | font-size: 16px;
106 | color: var(--vp-c-text-2);
107 | transition: color 0.5s;
108 | }
109 |
110 | .vp-doc a {
111 | font-weight: 500;
112 | color: var(--vp-c-brand-1);
113 | text-decoration: underline;
114 | text-underline-offset: 2px;
115 | transition:
116 | color 0.25s,
117 | opacity 0.25s;
118 | }
119 |
120 | .vp-doc a:hover {
121 | color: var(--vp-c-brand-2);
122 | }
123 |
124 | .vp-doc strong {
125 | font-weight: 600;
126 | }
127 |
128 | /**
129 | * Lists
130 | * -------------------------------------------------------------------------- */
131 |
132 | .vp-doc ul,
133 | .vp-doc ol {
134 | padding-left: 1.25rem;
135 | margin: 16px 0;
136 | }
137 |
138 | .vp-doc ul {
139 | list-style: disc;
140 | }
141 |
142 | .vp-doc ol {
143 | list-style: decimal;
144 | }
145 |
146 | .vp-doc li + li {
147 | margin-top: 8px;
148 | }
149 |
150 | .vp-doc li > ol,
151 | .vp-doc li > ul {
152 | margin: 8px 0 0;
153 | }
154 |
155 | /**
156 | * Table
157 | * -------------------------------------------------------------------------- */
158 |
159 | .vp-doc-table table {
160 | display: block;
161 | border-collapse: collapse;
162 | margin: 20px 0;
163 | overflow-x: auto;
164 | }
165 |
166 | .vp-doc-table tr {
167 | background-color: var(--vp-c-bg);
168 | border-top: 1px solid var(--vp-c-divider);
169 | transition: background-color 0.5s;
170 | }
171 |
172 | .vp-doc-table tr:nth-child(2n) {
173 | background-color: var(--vp-c-bg-soft);
174 | }
175 |
176 | .vp-doc-table th,
177 | .vp-doc-table td {
178 | border: 1px solid var(--vp-c-divider);
179 | padding: 8px 16px;
180 | }
181 |
182 | .vp-doc-table th {
183 | text-align: left;
184 | font-size: 14px;
185 | font-weight: 600;
186 | color: var(--vp-c-text-2);
187 | background-color: var(--vp-c-bg-soft);
188 | }
189 |
190 | .vp-doc-table td {
191 | font-size: 14px;
192 | }
193 |
194 | /**
195 | * Decorational elements
196 | * -------------------------------------------------------------------------- */
197 |
198 | .vp-doc hr {
199 | margin: 16px 0;
200 | border: none;
201 | border-top: 1px solid var(--vp-c-divider);
202 | }
203 |
204 | /**
205 | * Custom Block
206 | * -------------------------------------------------------------------------- */
207 |
208 | .vp-doc .custom-block {
209 | margin: 16px 0;
210 | }
211 |
212 | .vp-doc .custom-block p {
213 | margin: 8px 0;
214 | line-height: 24px;
215 | }
216 |
217 | .vp-doc .custom-block p:first-child {
218 | margin: 0;
219 | }
220 |
221 | .vp-doc .custom-block div[class*='language-'] {
222 | margin: 8px 0;
223 | border-radius: 8px;
224 | }
225 |
226 | .vp-doc .custom-block div[class*='language-'] code {
227 | font-weight: 400;
228 | background-color: transparent;
229 | }
230 |
231 | .vp-doc .custom-block .vp-code-group .tabs {
232 | margin: 0;
233 | border-radius: 8px 8px 0 0;
234 | }
235 |
236 | /**
237 | * Code
238 | * -------------------------------------------------------------------------- */
239 |
240 | /* inline code */
241 | .vp-doc :not(pre, h1, h2, h3, h4, h5, h6) > code {
242 | font-size: var(--vp-code-font-size);
243 | color: var(--vp-code-color);
244 | }
245 |
246 | .vp-doc :not(pre) > code {
247 | border-radius: 4px;
248 | padding: 3px 6px;
249 | background-color: var(--vp-code-bg);
250 | transition:
251 | color 0.25s,
252 | background-color 0.5s;
253 | }
254 |
255 | .vp-doc a > code {
256 | color: var(--vp-code-link-color);
257 | }
258 |
259 | .vp-doc a:hover > code {
260 | color: var(--vp-code-link-hover-color);
261 | }
262 |
263 | .vp-doc h1 > code,
264 | .vp-doc h2 > code,
265 | .vp-doc h3 > code {
266 | font-size: 0.9em;
267 | }
268 |
269 | .vp-doc div[class*='language-'],
270 | .vp-block {
271 | position: relative;
272 | margin: 16px -24px;
273 | max-width: 100vw;
274 | background-color: var(--vp-code-block-bg);
275 | overflow-x: auto;
276 | transition: background-color 0.5s;
277 | }
278 |
279 | @media (min-width: 640px) {
280 | .vp-doc div[class*='language-'],
281 | .vp-block {
282 | border-radius: 8px;
283 | margin: 16px 0;
284 | }
285 | }
286 |
287 | @media (max-width: 639px) {
288 | .vp-doc li div[class*='language-'] {
289 | border-radius: 8px 0 0 8px;
290 | }
291 | }
292 |
293 | .vp-doc div[class*='language-'] + div[class*='language-'],
294 | .vp-doc div[class$='-api'] + div[class*='language-'],
295 | .vp-doc div[class*='language-'] + div[class$='-api'] > div[class*='language-'] {
296 | margin-top: -8px;
297 | }
298 |
299 | .vp-doc [class*='language-'] pre,
300 | .vp-doc [class*='language-'] code {
301 | /*rtl:ignore*/
302 | direction: ltr;
303 | /*rtl:ignore*/
304 | text-align: left;
305 | white-space: pre;
306 | word-spacing: normal;
307 | word-break: normal;
308 | word-wrap: normal;
309 | -moz-tab-size: 4;
310 | -o-tab-size: 4;
311 | tab-size: 4;
312 | -webkit-hyphens: none;
313 | -moz-hyphens: none;
314 | -ms-hyphens: none;
315 | hyphens: none;
316 | }
317 |
318 | .vp-doc [class*='language-'] pre {
319 | position: relative;
320 | z-index: 1;
321 | margin: 0;
322 | padding: 20px 0;
323 | background: transparent;
324 | overflow-x: auto;
325 | }
326 |
327 | .vp-doc [class*='language-'] code {
328 | display: block;
329 | padding: 0 24px;
330 | width: fit-content;
331 | min-width: 100%;
332 | line-height: var(--vp-code-line-height);
333 | font-size: var(--vp-code-font-size);
334 | color: var(--vp-code-block-color);
335 | transition: color 0.5s;
336 | }
337 |
338 | .vp-doc [class*='language-'] code .highlighted {
339 | background-color: var(--vp-code-line-highlight-color);
340 | transition: background-color 0.5s;
341 | margin: 0 -24px;
342 | padding: 0 24px;
343 | width: calc(100% + 2 * 24px);
344 | display: inline-block;
345 | }
346 |
347 | .vp-doc [class*='language-'] code .highlighted.error {
348 | background-color: var(--vp-code-line-error-color);
349 | }
350 |
351 | .vp-doc [class*='language-'] code .highlighted.warning {
352 | background-color: var(--vp-code-line-warning-color);
353 | }
354 |
355 | .vp-doc [class*='language-'] code .diff {
356 | transition: background-color 0.5s;
357 | margin: 0 -24px;
358 | padding: 0 24px;
359 | width: calc(100% + 2 * 24px);
360 | display: inline-block;
361 | }
362 |
363 | .vp-doc [class*='language-'] code .diff::before {
364 | position: absolute;
365 | left: 10px;
366 | }
367 |
368 | .vp-doc [class*='language-'] .has-focused-lines .line:not(.has-focus) {
369 | filter: blur(0.095rem);
370 | opacity: 0.4;
371 | transition:
372 | filter 0.35s,
373 | opacity 0.35s;
374 | }
375 |
376 | .vp-doc [class*='language-'] .has-focused-lines .line:not(.has-focus) {
377 | opacity: 0.7;
378 | transition:
379 | filter 0.35s,
380 | opacity 0.35s;
381 | }
382 |
383 | .vp-doc [class*='language-']:hover .has-focused-lines .line:not(.has-focus) {
384 | filter: blur(0);
385 | opacity: 1;
386 | }
387 |
388 | .vp-doc [class*='language-'] code .diff.remove {
389 | background-color: var(--vp-code-line-diff-remove-color);
390 | opacity: 0.7;
391 | }
392 |
393 | .vp-doc [class*='language-'] code .diff.remove::before {
394 | content: '-';
395 | color: var(--vp-code-line-diff-remove-symbol-color);
396 | }
397 |
398 | .vp-doc [class*='language-'] code .diff.add {
399 | background-color: var(--vp-code-line-diff-add-color);
400 | }
401 |
402 | .vp-doc [class*='language-'] code .diff.add::before {
403 | content: '+';
404 | color: var(--vp-code-line-diff-add-symbol-color);
405 | }
406 |
407 | .vp-doc div[class*='language-'].line-numbers-mode {
408 | /*rtl:ignore*/
409 | padding-left: 32px;
410 | }
411 |
412 | .vp-doc .line-numbers-wrapper {
413 | position: absolute;
414 | top: 0;
415 | bottom: 0;
416 | /*rtl:ignore*/
417 | left: 0;
418 | z-index: 3;
419 | /*rtl:ignore*/
420 | border-right: 1px solid var(--vp-code-block-divider-color);
421 | padding-top: 20px;
422 | width: 32px;
423 | text-align: center;
424 | font-family: var(--vp-font-family-mono);
425 | line-height: var(--vp-code-line-height);
426 | font-size: var(--vp-code-font-size);
427 | color: var(--vp-code-line-number-color);
428 | transition:
429 | border-color 0.5s,
430 | color 0.5s;
431 | }
432 |
433 | .vp-doc [class*='language-'] > button.copy {
434 | /*rtl:ignore*/
435 | direction: ltr;
436 | position: absolute;
437 | top: 12px;
438 | /*rtl:ignore*/
439 | right: 12px;
440 | z-index: 3;
441 | border: 1px solid var(--vp-code-copy-code-border-color);
442 | border-radius: 4px;
443 | width: 40px;
444 | height: 40px;
445 | background-color: var(--vp-code-copy-code-bg);
446 | opacity: 0;
447 | cursor: pointer;
448 | background-image: var(--vp-icon-copy);
449 | background-position: 50%;
450 | background-size: 20px;
451 | background-repeat: no-repeat;
452 | transition:
453 | border-color 0.25s,
454 | background-color 0.25s,
455 | opacity 0.25s;
456 | }
457 |
458 | .vp-doc [class*='language-']:hover > button.copy,
459 | .vp-doc [class*='language-'] > button.copy:focus {
460 | opacity: 1;
461 | }
462 |
463 | .vp-doc [class*='language-'] > button.copy:hover,
464 | .vp-doc [class*='language-'] > button.copy.copied {
465 | border-color: var(--vp-code-copy-code-hover-border-color);
466 | background-color: var(--vp-code-copy-code-hover-bg);
467 | }
468 |
469 | .vp-doc [class*='language-'] > button.copy.copied,
470 | .vp-doc [class*='language-'] > button.copy:hover.copied {
471 | /*rtl:ignore*/
472 | border-radius: 0 4px 4px 0;
473 | background-color: var(--vp-code-copy-code-hover-bg);
474 | background-image: var(--vp-icon-copied);
475 | }
476 |
477 | .vp-doc [class*='language-'] > button.copy.copied::before,
478 | .vp-doc [class*='language-'] > button.copy:hover.copied::before {
479 | position: relative;
480 | top: -1px;
481 | /*rtl:ignore*/
482 | transform: translateX(calc(-100% - 1px));
483 | display: flex;
484 | justify-content: center;
485 | align-items: center;
486 | border: 1px solid var(--vp-code-copy-code-hover-border-color);
487 | /*rtl:ignore*/
488 | border-right: 0;
489 | border-radius: 4px 0 0 4px;
490 | padding: 0 10px;
491 | width: fit-content;
492 | height: 40px;
493 | text-align: center;
494 | font-size: 12px;
495 | font-weight: 500;
496 | color: var(--vp-code-copy-code-active-text);
497 | background-color: var(--vp-code-copy-code-hover-bg);
498 | white-space: nowrap;
499 | content: var(--vp-code-copy-copied-text-content);
500 | }
501 |
502 | .vp-doc [class*='language-'] > span.lang {
503 | position: absolute;
504 | top: 2px;
505 | /*rtl:ignore*/
506 | right: 8px;
507 | z-index: 2;
508 | font-size: 12px;
509 | font-weight: 500;
510 | color: var(--vp-code-lang-color);
511 | transition:
512 | color 0.4s,
513 | opacity 0.4s;
514 | }
515 |
516 | .vp-doc [class*='language-']:hover > button.copy + span.lang,
517 | .vp-doc [class*='language-'] > button.copy:focus + span.lang {
518 | opacity: 0;
519 | }
520 |
521 | /**
522 | * Component: Team
523 | * -------------------------------------------------------------------------- */
524 |
525 | .vp-doc .VPTeamMembers {
526 | margin-top: 24px;
527 | }
528 |
529 | .vp-doc .VPTeamMembers.small.count-1 .container {
530 | margin: 0 !important;
531 | max-width: calc((100% - 24px) / 2) !important;
532 | }
533 |
534 | .vp-doc .VPTeamMembers.small.count-2 .container,
535 | .vp-doc .VPTeamMembers.small.count-3 .container {
536 | max-width: 100% !important;
537 | }
538 |
539 | .vp-doc .VPTeamMembers.medium.count-1 .container {
540 | margin: 0 !important;
541 | max-width: calc((100% - 24px) / 2) !important;
542 | }
543 |
544 | /**
545 | * External links
546 | * -------------------------------------------------------------------------- */
547 |
548 | /* prettier-ignore */
549 | :is(.vp-external-link-icon, .vp-doc a[href*='://'], .vp-doc a[target='_blank']):not(.no-icon)::after {
550 | display: inline-block;
551 | margin-top: -1px;
552 | margin-left: 4px;
553 | width: 11px;
554 | height: 11px;
555 | background: currentColor;
556 | color: var(--vp-c-text-3);
557 | flex-shrink: 0;
558 | --icon: url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' %3E%3Cpath d='M0 0h24v24H0V0z' fill='none' /%3E%3Cpath d='M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5H9z' /%3E%3C/svg%3E");
559 | -webkit-mask-image: var(--icon);
560 | mask-image: var(--icon);
561 | }
562 |
563 | .vp-external-link-icon::after {
564 | content: '';
565 | }
566 |
567 | /* prettier-ignore */
568 | .external-link-icon-enabled :is(.vp-doc a[href*='://'], .vp-doc a[target='_blank'])::after {
569 | content: '';
570 | color: currentColor;
571 | }
572 |
--------------------------------------------------------------------------------
/src/style/style.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Customize default theme styling by overriding CSS variables:
3 | * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css
4 | */
5 |
6 | /**
7 | * Colors
8 | *
9 | * Each colors have exact same color scale system with 3 levels of solid
10 | * colors with different brightness, and 1 soft color.
11 | *
12 | * - `XXX-1`: The most solid color used mainly for colored text. It must
13 | * satisfy the contrast ratio against when used on top of `XXX-soft`.
14 | *
15 | * - `XXX-2`: The color used mainly for hover state of the button.
16 | *
17 | * - `XXX-3`: The color for solid background, such as bg color of the button.
18 | * It must satisfy the contrast ratio with pure white (#ffffff) text on
19 | * top of it.
20 | *
21 | * - `XXX-soft`: The color used for subtle background such as custom container
22 | * or badges. It must satisfy the contrast ratio when putting `XXX-1` colors
23 | * on top of it.
24 | *
25 | * The soft color must be semi transparent alpha channel. This is crucial
26 | * because it allows adding multiple "soft" colors on top of each other
27 | * to create a accent, such as when having inline code block inside
28 | * custom containers.
29 | *
30 | * - `default`: The color used purely for subtle indication without any
31 | * special meanings attched to it such as bg color for menu hover state.
32 | *
33 | * - `brand`: Used for primary brand colors, such as link text, button with
34 | * brand theme, etc.
35 | *
36 | * - `tip`: Used to indicate useful information. The default theme uses the
37 | * brand color for this by default.
38 | *
39 | * - `warning`: Used to indicate warning to the users. Used in custom
40 | * container, badges, etc.
41 | *
42 | * - `danger`: Used to show error, or dangerous message to the users. Used
43 | * in custom container, badges, etc.
44 | * -------------------------------------------------------------------------- */
45 |
46 | :root {
47 | /* --vp-c-brand-1: #ca4b22; */
48 | --vp-c-brand-1: #286090;
49 | --vp-c-brand-2: #bc6063;
50 | --vp-c-brand-3: #b34e52;
51 | --vp-c-brand-soft: #83d0da50;
52 | --vp-c-bg-alt: #f9f9f9;
53 |
54 | --vp-c-yellow-1: #edb253;
55 | --vp-c-yellow-2: #daac61;
56 | --vp-c-yellow-3: #e6cc78;
57 |
58 | --vp-c-red-1: #b34e52;
59 | --vp-c-red-2: #bc6063;
60 | --vp-c-red-3: #cb7676;
61 | }
62 |
63 | .dark {
64 | --sk-theme-1-hsl: hsl(38.77deg 71.8% 58.64%);
65 | /* --vp-c-brand-1: #cb7676; */
66 | --vp-c-brand-1: var(--sk-theme-1-hsl);
67 | --vp-c-brand-2: #bc6063;
68 | --vp-c-brand-3: #b34e52;
69 | --vp-c-brand-soft: #83d0da50;
70 | --vp-c-bg-alt: #18181b;
71 |
72 | --vp-c-yellow-1: #e6cc78;
73 | --vp-c-yellow-2: #daac61;
74 | --vp-c-yellow-3: #edb253;
75 |
76 | --vp-c-red-1: #cb7676;
77 | --vp-c-red-2: #bc6063;
78 | --vp-c-red-3: #b34e52;
79 | }
80 |
81 | :root {
82 | --vp-c-default-1: var(--vp-c-gray-1);
83 | --vp-c-default-2: var(--vp-c-gray-2);
84 | --vp-c-default-3: var(--vp-c-gray-3);
85 | --vp-c-default-soft: var(--vp-c-gray-soft);
86 |
87 | --vp-c-tip-1: var(--vp-c-brand-1);
88 | --vp-c-tip-2: var(--vp-c-brand-2);
89 | --vp-c-tip-3: var(--vp-c-brand-3);
90 | --vp-c-tip-soft: var(--vp-c-brand-soft);
91 |
92 | --vp-c-warning-1: var(--vp-c-yellow-1);
93 | --vp-c-warning-2: var(--vp-c-yellow-2);
94 | --vp-c-warning-3: var(--vp-c-yellow-3);
95 | --vp-c-warning-soft: var(--vp-c-yellow-soft);
96 |
97 | --vp-c-danger-1: var(--vp-c-red-1);
98 | --vp-c-danger-2: var(--vp-c-red-2);
99 | --vp-c-danger-3: var(--vp-c-red-3);
100 | --vp-c-danger-soft: var(--vp-c-red-soft);
101 | }
102 |
103 | :root {
104 | -vp-c-text-1: rgba(42, 40, 47);
105 | -vp-c-text-2: rgba(42, 40, 47, 0.78);
106 | -vp-c-text-3: rgba(42, 40, 47, 0.56);
107 | --black-text-1: rgba(42, 40, 47);
108 | }
109 |
110 | .dark {
111 | --vp-c-text-1: rgba(255, 255, 245, 0.86);
112 | --vp-c-text-2: rgba(235, 235, 245, 0.6);
113 | --vp-c-text-3: rgba(235, 235, 245, 0.38);
114 | }
115 |
116 | /**
117 | * Component: Button
118 | * -------------------------------------------------------------------------- */
119 |
120 | :root {
121 | --vp-button-brand-border: transparent;
122 | --vp-button-brand-text: var(--vp-c-white);
123 | --vp-button-brand-bg: var(--vp-c-brand-1);
124 | --vp-button-brand-hover-border: transparent;
125 | --vp-button-brand-hover-text: var(--vp-c-white);
126 | --vp-button-brand-hover-bg: var(--vp-c-brand-2);
127 | --vp-button-brand-active-border: transparent;
128 | --vp-button-brand-active-text: var(--vp-c-white);
129 | --vp-button-brand-active-bg: var(--vp-c-brand-1);
130 | }
131 |
132 | .dark {
133 | --vp-button-brand-text: var(--black-text-1);
134 | --vp-button-brand-bg: var(--vp-c-brand-2);
135 | --vp-button-brand-hover-text: var(--black-text-1);
136 | --vp-button-brand-hover-bg: var(--vp-c-brand-1);
137 | --vp-button-brand-active-text: var(--black-text-1);
138 | --vp-button-brand-active-bg: var(--vp-c-brand-3);
139 | }
140 |
141 | /**
142 | * Component: Home
143 | * -------------------------------------------------------------------------- */
144 |
145 | :root {
146 | --vp-home-hero-name-color: var(--vp-c-brand-1);
147 | }
148 |
149 | @media (min-width: 640px) {
150 | :root {
151 | --vp-home-hero-image-filter: blur(56px);
152 | }
153 | }
154 |
155 | @media (min-width: 960px) {
156 | :root {
157 | --vp-home-hero-image-filter: blur(72px);
158 | }
159 | }
160 |
161 | /**
162 | * Component: Custom Block
163 | * -------------------------------------------------------------------------- */
164 |
165 | :root {
166 | --vp-custom-block-tip-border: transparent;
167 | --vp-custom-block-tip-text: var(--vp-c-text-1);
168 | --vp-custom-block-tip-bg: var(--vp-c-brand-soft);
169 | --vp-custom-block-tip-code-bg: var(--vp-c-brand-soft);
170 | }
171 |
172 | /**
173 | * Component: Algolia
174 | * -------------------------------------------------------------------------- */
175 |
176 | .DocSearch {
177 | --docsearch-primary-color: var(--vp-c-brand-1) !important;
178 | }
179 |
180 | .dark {
181 | --sdt-bg-main: #585858;
182 | --sdt-shadow-color: #333;
183 | --sdt-color: #eee;
184 | --sdt-clock-color: var(--sdt-color);
185 | --sdt-clock-color-hover: var(--sdt-color);
186 | --sdt-clock-time-bg: transparent;
187 | --sdt-clock-time-bg-hover: transparent;
188 | --sdt-clock-disabled: #b22222;
189 | --sdt-clock-disabled-bg: var(--sdt-bg-main);
190 | --sdt-clock-selected-bg: var(--sdt-bg-selected);
191 | --sdt-header-color: #eee;
192 | --sdt-bg-selected: #e1ac4a;
193 | --sdt-table-disabled-date: #b22222;
194 | --sdt-table-disabled-date-bg: var(--sdt-bg-main);
195 | --sdt-table-data-bg-hover: #777;
196 | --sdt-table-selected-bg: var(--sdt-bg-selected);
197 | --sdt-header-btn-bg-hover: #777;
198 | --sdt-color-selected: #fff;
199 | --sdt-table-today-indicator: #ccc;
200 |
201 | --sdt-clock-bg: #999;
202 |
203 | /* custom buttons */
204 | --sdt-today-bg: #e4a124;
205 | --sdt-today-color: #fff;
206 | --sdt-clear-color: #666;
207 | --sdt-clear-bg: #ddd;
208 | --sdt-clear-hover-color: #fff;
209 | --sdt-clear-hover-bg: #dc3545;
210 | }
211 | .dark button {
212 | border-radius: 0;
213 | }
214 |
215 | h1 {
216 | margin-top: 16px;
217 | margin-bottom: 28px;
218 | }
219 | body {
220 | overflow: scroll;
221 | }
222 |
223 | .vp-doc {
224 | h1 > a {
225 | color: inherit;
226 | text-decoration: none;
227 | }
228 | h2 > a,
229 | h3 > a,
230 | h4 > a,
231 | h5 > a,
232 | h6 > a {
233 | text-decoration: none;
234 | color: inherit;
235 | }
236 | }
237 | .toc {
238 | .toc-level-1 {
239 | font-size: $font-size-3;
240 | font-weight: $font-weight-bold;
241 | }
242 | }
243 |
244 | .dark .light-toggle {
245 | display: none;
246 | }
247 | html:not(.dark) .dark-toggle {
248 | display: none;
249 | }
250 | .nav-footer {
251 | display: flex;
252 | justify-content: space-between;
253 | padding-left: 10px;
254 | }
255 |
256 | .sdt-component-wrap input {
257 | border: 1px solid var(--vp-c-text-1);
258 | padding: 4px;
259 | border-radius: 4px;
260 | }
261 |
--------------------------------------------------------------------------------
/src/style/vars.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Colors: Solid
3 | * -------------------------------------------------------------------------- */
4 |
5 | :root {
6 | --vp-c-white: #ffffff;
7 | --vp-c-black: #000000;
8 |
9 | --vp-c-neutral: var(--vp-c-black);
10 | --vp-c-neutral-inverse: var(--vp-c-white);
11 | }
12 |
13 | .dark {
14 | --vp-c-neutral: var(--vp-c-white);
15 | --vp-c-neutral-inverse: var(--vp-c-black);
16 | }
17 |
18 | /**
19 | * Colors: Palette
20 | *
21 | * The primitive colors used for accent colors. These colors are referenced
22 | * by functional colors such as "Text", "Background", or "Brand".
23 | *
24 | * Each colors have exact same color scale system with 3 levels of solid
25 | * colors with different brightness, and 1 soft color.
26 | *
27 | * - `XXX-1`: The most solid color used mainly for colored text. It must
28 | * satisfy the contrast ratio against when used on top of `XXX-soft`.
29 | *
30 | * - `XXX-2`: The color used mainly for hover state of the button.
31 | *
32 | * - `XXX-3`: The color for solid background, such as bg color of the button.
33 | * It must satisfy the contrast ratio with pure white (#ffffff) text on
34 | * top of it.
35 | *
36 | * - `XXX-soft`: The color used for subtle background such as custom container
37 | * or badges. It must satisfy the contrast ratio when putting `XXX-1` colors
38 | * on top of it.
39 | *
40 | * The soft color must be semi transparent alpha channel. This is crucial
41 | * because it allows adding multiple "soft" colors on top of each other
42 | * to create a accent, such as when having inline code block inside
43 | * custom containers.
44 | * -------------------------------------------------------------------------- */
45 |
46 | :root {
47 | --vp-c-gray-1: #dddde3;
48 | --vp-c-gray-2: #e4e4e9;
49 | --vp-c-gray-3: #ebebef;
50 | --vp-c-gray-soft: rgba(142, 150, 170, 0.14);
51 |
52 | --vp-c-indigo-1: #3451b2;
53 | --vp-c-indigo-2: #3a5ccc;
54 | --vp-c-indigo-3: #5672cd;
55 | --vp-c-indigo-soft: rgba(100, 108, 255, 0.14);
56 |
57 | --vp-c-purple-1: #6f42c1;
58 | --vp-c-purple-2: #7e4cc9;
59 | --vp-c-purple-3: #8e5cd9;
60 | --vp-c-purple-soft: rgba(159, 122, 234, 0.14);
61 |
62 | --vp-c-green-1: #18794e;
63 | --vp-c-green-2: #299764;
64 | --vp-c-green-3: #30a46c;
65 | --vp-c-green-soft: rgba(16, 185, 129, 0.14);
66 |
67 | --vp-c-yellow-1: #915930;
68 | --vp-c-yellow-2: #946300;
69 | --vp-c-yellow-3: #9f6a00;
70 | --vp-c-yellow-soft: rgba(234, 179, 8, 0.14);
71 |
72 | --vp-c-red-1: #b8272c;
73 | --vp-c-red-2: #d5393e;
74 | --vp-c-red-3: #e0575b;
75 | --vp-c-red-soft: rgba(244, 63, 94, 0.14);
76 |
77 | --vp-c-sponsor: #db2777;
78 | }
79 |
80 | .dark {
81 | --vp-c-gray-1: #515c67;
82 | --vp-c-gray-2: #414853;
83 | --vp-c-gray-3: #32363f;
84 | --vp-c-gray-soft: rgba(101, 117, 133, 0.16);
85 |
86 | --vp-c-indigo-1: #a8b1ff;
87 | --vp-c-indigo-2: #5c73e7;
88 | --vp-c-indigo-3: #3e63dd;
89 | --vp-c-indigo-soft: rgba(100, 108, 255, 0.16);
90 |
91 | --vp-c-purple-1: #c8abfa;
92 | --vp-c-purple-2: #a879e6;
93 | --vp-c-purple-3: #8e5cd9;
94 | --vp-c-purple-soft: rgba(159, 122, 234, 0.16);
95 |
96 | --vp-c-green-1: #3dd68c;
97 | --vp-c-green-2: #30a46c;
98 | --vp-c-green-3: #298459;
99 | --vp-c-green-soft: rgba(16, 185, 129, 0.16);
100 |
101 | --vp-c-yellow-1: #f9b44e;
102 | --vp-c-yellow-2: #da8b17;
103 | --vp-c-yellow-3: #a46a0a;
104 | --vp-c-yellow-soft: rgba(234, 179, 8, 0.16);
105 |
106 | --vp-c-red-1: #f66f81;
107 | --vp-c-red-2: #f14158;
108 | --vp-c-red-3: #b62a3c;
109 | --vp-c-red-soft: rgba(244, 63, 94, 0.16);
110 | }
111 |
112 | /**
113 | * Colors: Background
114 | *
115 | * - `bg`: The bg color used for main screen.
116 | *
117 | * - `bg-alt`: The alternative bg color used in places such as "sidebar",
118 | * or "code block".
119 | *
120 | * - `bg-elv`: The elevated bg color. This is used at parts where it "floats",
121 | * such as "dialog".
122 | *
123 | * - `bg-soft`: The bg color to slightly distinguish some components from
124 | * the page. Used for things like "carbon ads" or "table".
125 | * -------------------------------------------------------------------------- */
126 |
127 | :root {
128 | --vp-c-bg: #ffffff;
129 | --vp-c-bg-alt: #f6f6f7;
130 | --vp-c-bg-elv: #ffffff;
131 | --vp-c-bg-soft: #f6f6f7;
132 | }
133 |
134 | .dark {
135 | --vp-c-bg: #1b1b1f;
136 | --vp-c-bg-alt: #161618;
137 | --vp-c-bg-elv: #202127;
138 | --vp-c-bg-soft: #202127;
139 | }
140 |
141 | /**
142 | * Colors: Borders
143 | *
144 | * - `divider`: This is used for separators. This is used to divide sections
145 | * within the same components, such as having separator on "h2" heading.
146 | *
147 | * - `border`: This is designed for borders on interactive components.
148 | * For example this should be used for a button outline.
149 | *
150 | * - `gutter`: This is used to divide components in the page. For example
151 | * the header and the lest of the page.
152 | * -------------------------------------------------------------------------- */
153 |
154 | :root {
155 | --vp-c-border: #c2c2c4;
156 | --vp-c-divider: #e2e2e3;
157 | --vp-c-gutter: #e2e2e3;
158 | }
159 |
160 | .dark {
161 | --vp-c-border: #3c3f44;
162 | --vp-c-divider: #2e2e32;
163 | --vp-c-gutter: #000000;
164 | }
165 |
166 | /**
167 | * Colors: Text
168 | *
169 | * - `text-1`: Used for primary text.
170 | *
171 | * - `text-2`: Used for muted texts, such as "inactive menu" or "info texts".
172 | *
173 | * - `text-3`: Used for subtle texts, such as "placeholders" or "caret icon".
174 | * -------------------------------------------------------------------------- */
175 |
176 | :root {
177 | --vp-c-text-1: rgba(60, 60, 67);
178 | --vp-c-text-2: rgba(60, 60, 67, 0.78);
179 | --vp-c-text-3: rgba(60, 60, 67, 0.56);
180 | }
181 |
182 | .dark {
183 | --vp-c-text-1: rgba(255, 255, 245, 0.86);
184 | --vp-c-text-2: rgba(235, 235, 245, 0.6);
185 | --vp-c-text-3: rgba(235, 235, 245, 0.38);
186 | }
187 |
188 | /**
189 | * Colors: Function
190 | *
191 | * - `default`: The color used purely for subtle indication without any
192 | * special meanings attached to it such as bg color for menu hover state.
193 | *
194 | * - `brand`: Used for primary brand colors, such as link text, button with
195 | * brand theme, etc.
196 | *
197 | * - `tip`: Used to indicate useful information. The default theme uses the
198 | * brand color for this by default.
199 | *
200 | * - `warning`: Used to indicate warning to the users. Used in custom
201 | * container, badges, etc.
202 | *
203 | * - `danger`: Used to show error, or dangerous message to the users. Used
204 | * in custom container, badges, etc.
205 | *
206 | * To understand the scaling system, refer to "Colors: Palette" section.
207 | * -------------------------------------------------------------------------- */
208 |
209 | :root {
210 | --vp-c-default-1: var(--vp-c-gray-1);
211 | --vp-c-default-2: var(--vp-c-gray-2);
212 | --vp-c-default-3: var(--vp-c-gray-3);
213 | --vp-c-default-soft: var(--vp-c-gray-soft);
214 |
215 | --vp-c-brand-1: var(--vp-c-indigo-1);
216 | --vp-c-brand-2: var(--vp-c-indigo-2);
217 | --vp-c-brand-3: var(--vp-c-indigo-3);
218 | --vp-c-brand-soft: var(--vp-c-indigo-soft);
219 |
220 | /* DEPRECATED: Use `--vp-c-brand-1` instead. */
221 | --vp-c-brand: var(--vp-c-brand-1);
222 |
223 | --vp-c-tip-1: var(--vp-c-brand-1);
224 | --vp-c-tip-2: var(--vp-c-brand-2);
225 | --vp-c-tip-3: var(--vp-c-brand-3);
226 | --vp-c-tip-soft: var(--vp-c-brand-soft);
227 |
228 | --vp-c-note-1: var(--vp-c-brand-1);
229 | --vp-c-note-2: var(--vp-c-brand-2);
230 | --vp-c-note-3: var(--vp-c-brand-3);
231 | --vp-c-note-soft: var(--vp-c-brand-soft);
232 |
233 | --vp-c-success-1: var(--vp-c-green-1);
234 | --vp-c-success-2: var(--vp-c-green-2);
235 | --vp-c-success-3: var(--vp-c-green-3);
236 | --vp-c-success-soft: var(--vp-c-green-soft);
237 |
238 | --vp-c-important-1: var(--vp-c-purple-1);
239 | --vp-c-important-2: var(--vp-c-purple-2);
240 | --vp-c-important-3: var(--vp-c-purple-3);
241 | --vp-c-important-soft: var(--vp-c-purple-soft);
242 |
243 | --vp-c-warning-1: var(--vp-c-yellow-1);
244 | --vp-c-warning-2: var(--vp-c-yellow-2);
245 | --vp-c-warning-3: var(--vp-c-yellow-3);
246 | --vp-c-warning-soft: var(--vp-c-yellow-soft);
247 |
248 | --vp-c-danger-1: var(--vp-c-red-1);
249 | --vp-c-danger-2: var(--vp-c-red-2);
250 | --vp-c-danger-3: var(--vp-c-red-3);
251 | --vp-c-danger-soft: var(--vp-c-red-soft);
252 |
253 | --vp-c-caution-1: var(--vp-c-red-1);
254 | --vp-c-caution-2: var(--vp-c-red-2);
255 | --vp-c-caution-3: var(--vp-c-red-3);
256 | --vp-c-caution-soft: var(--vp-c-red-soft);
257 | }
258 |
259 | /**
260 | * Typography
261 | * -------------------------------------------------------------------------- */
262 |
263 | :root {
264 | --vp-font-family-base: 'Chinese Quotes', 'Inter var', 'Inter', ui-sans-serif,
265 | system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
266 | 'Helvetica Neue', Helvetica, Arial, 'Noto Sans', sans-serif,
267 | 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
268 | --vp-font-family-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Monaco,
269 | Consolas, 'Liberation Mono', 'Courier New', monospace;
270 | }
271 |
272 | /**
273 | * Shadows
274 | * -------------------------------------------------------------------------- */
275 |
276 | :root {
277 | --vp-shadow-1: 0 1px 2px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.06);
278 | --vp-shadow-2: 0 3px 12px rgba(0, 0, 0, 0.07), 0 1px 4px rgba(0, 0, 0, 0.07);
279 | --vp-shadow-3: 0 12px 32px rgba(0, 0, 0, 0.1), 0 2px 6px rgba(0, 0, 0, 0.08);
280 | --vp-shadow-4: 0 14px 44px rgba(0, 0, 0, 0.12), 0 3px 9px rgba(0, 0, 0, 0.12);
281 | --vp-shadow-5: 0 18px 56px rgba(0, 0, 0, 0.16), 0 4px 12px rgba(0, 0, 0, 0.16);
282 | }
283 |
284 | /**
285 | * Z-indexes
286 | * -------------------------------------------------------------------------- */
287 |
288 | :root {
289 | --vp-z-index-footer: 10;
290 | --vp-z-index-local-nav: 20;
291 | --vp-z-index-nav: 30;
292 | --vp-z-index-layout-top: 40;
293 | --vp-z-index-backdrop: 50;
294 | --vp-z-index-sidebar: 60;
295 | }
296 |
297 | @media (min-width: 960px) {
298 | :root {
299 | --vp-z-index-sidebar: 25;
300 | }
301 | }
302 |
303 | /**
304 | * Layouts
305 | * -------------------------------------------------------------------------- */
306 |
307 | :root {
308 | --vp-layout-max-width: 1440px;
309 | }
310 |
311 | /**
312 | * Component: Header Anchor
313 | * -------------------------------------------------------------------------- */
314 |
315 | :root {
316 | --vp-header-anchor-symbol: '#';
317 | }
318 |
319 | /**
320 | * Component: Code
321 | * -------------------------------------------------------------------------- */
322 |
323 | :root {
324 | --vp-code-line-height: 1.7;
325 | --vp-code-font-size: 0.875em;
326 | --vp-code-color: var(--vp-c-brand-1);
327 | --vp-code-link-color: var(--vp-c-brand-1);
328 | --vp-code-link-hover-color: var(--vp-c-brand-2);
329 | --vp-code-bg: var(--vp-c-default-soft);
330 |
331 | --vp-code-block-color: var(--vp-c-text-2);
332 | --vp-code-block-bg: var(--vp-c-bg-alt);
333 | --vp-code-block-divider-color: var(--vp-c-gutter);
334 |
335 | --vp-code-lang-color: var(--vp-c-text-3);
336 |
337 | --vp-code-line-highlight-color: var(--vp-c-default-soft);
338 | --vp-code-line-number-color: var(--vp-c-text-3);
339 |
340 | --vp-code-line-diff-add-color: var(--vp-c-success-soft);
341 | --vp-code-line-diff-add-symbol-color: var(--vp-c-success-1);
342 |
343 | --vp-code-line-diff-remove-color: var(--vp-c-danger-soft);
344 | --vp-code-line-diff-remove-symbol-color: var(--vp-c-danger-1);
345 |
346 | --vp-code-line-warning-color: var(--vp-c-warning-soft);
347 | --vp-code-line-error-color: var(--vp-c-danger-soft);
348 |
349 | --vp-code-copy-code-border-color: var(--vp-c-divider);
350 | --vp-code-copy-code-bg: var(--vp-c-bg-soft);
351 | --vp-code-copy-code-hover-border-color: var(--vp-c-divider);
352 | --vp-code-copy-code-hover-bg: var(--vp-c-bg);
353 | --vp-code-copy-code-active-text: var(--vp-c-text-2);
354 | --vp-code-copy-copied-text-content: 'Copied';
355 |
356 | --vp-code-tab-divider: var(--vp-code-block-divider-color);
357 | --vp-code-tab-text-color: var(--vp-c-text-2);
358 | --vp-code-tab-bg: var(--vp-code-block-bg);
359 | --vp-code-tab-hover-text-color: var(--vp-c-text-1);
360 | --vp-code-tab-active-text-color: var(--vp-c-text-1);
361 | --vp-code-tab-active-bar-color: var(--vp-c-brand-1);
362 | }
363 |
364 | /**
365 | * Component: Button
366 | * -------------------------------------------------------------------------- */
367 |
368 | :root {
369 | --vp-button-brand-border: transparent;
370 | --vp-button-brand-text: var(--vp-c-white);
371 | --vp-button-brand-bg: var(--vp-c-brand-3);
372 | --vp-button-brand-hover-border: transparent;
373 | --vp-button-brand-hover-text: var(--vp-c-white);
374 | --vp-button-brand-hover-bg: var(--vp-c-brand-2);
375 | --vp-button-brand-active-border: transparent;
376 | --vp-button-brand-active-text: var(--vp-c-white);
377 | --vp-button-brand-active-bg: var(--vp-c-brand-1);
378 |
379 | --vp-button-alt-border: transparent;
380 | --vp-button-alt-text: var(--vp-c-text-1);
381 | --vp-button-alt-bg: var(--vp-c-default-3);
382 | --vp-button-alt-hover-border: transparent;
383 | --vp-button-alt-hover-text: var(--vp-c-text-1);
384 | --vp-button-alt-hover-bg: var(--vp-c-default-2);
385 | --vp-button-alt-active-border: transparent;
386 | --vp-button-alt-active-text: var(--vp-c-text-1);
387 | --vp-button-alt-active-bg: var(--vp-c-default-1);
388 |
389 | --vp-button-sponsor-border: var(--vp-c-text-2);
390 | --vp-button-sponsor-text: var(--vp-c-text-2);
391 | --vp-button-sponsor-bg: transparent;
392 | --vp-button-sponsor-hover-border: var(--vp-c-sponsor);
393 | --vp-button-sponsor-hover-text: var(--vp-c-sponsor);
394 | --vp-button-sponsor-hover-bg: transparent;
395 | --vp-button-sponsor-active-border: var(--vp-c-sponsor);
396 | --vp-button-sponsor-active-text: var(--vp-c-sponsor);
397 | --vp-button-sponsor-active-bg: transparent;
398 | }
399 |
400 | /**
401 | * Component: Custom Block
402 | * -------------------------------------------------------------------------- */
403 |
404 | :root {
405 | --vp-custom-block-font-size: 14px;
406 | --vp-custom-block-code-font-size: 13px;
407 |
408 | --vp-custom-block-info-border: transparent;
409 | --vp-custom-block-info-text: var(--vp-c-text-1);
410 | --vp-custom-block-info-bg: var(--vp-c-default-soft);
411 | --vp-custom-block-info-code-bg: var(--vp-c-default-soft);
412 |
413 | --vp-custom-block-note-border: transparent;
414 | --vp-custom-block-note-text: var(--vp-c-text-1);
415 | --vp-custom-block-note-bg: var(--vp-c-default-soft);
416 | --vp-custom-block-note-code-bg: var(--vp-c-default-soft);
417 |
418 | --vp-custom-block-tip-border: transparent;
419 | --vp-custom-block-tip-text: var(--vp-c-text-1);
420 | --vp-custom-block-tip-bg: var(--vp-c-tip-soft);
421 | --vp-custom-block-tip-code-bg: var(--vp-c-tip-soft);
422 |
423 | --vp-custom-block-important-border: transparent;
424 | --vp-custom-block-important-text: var(--vp-c-text-1);
425 | --vp-custom-block-important-bg: var(--vp-c-important-soft);
426 | --vp-custom-block-important-code-bg: var(--vp-c-important-soft);
427 |
428 | --vp-custom-block-warning-border: transparent;
429 | --vp-custom-block-warning-text: var(--vp-c-text-1);
430 | --vp-custom-block-warning-bg: var(--vp-c-warning-soft);
431 | --vp-custom-block-warning-code-bg: var(--vp-c-warning-soft);
432 |
433 | --vp-custom-block-danger-border: transparent;
434 | --vp-custom-block-danger-text: var(--vp-c-text-1);
435 | --vp-custom-block-danger-bg: var(--vp-c-danger-soft);
436 | --vp-custom-block-danger-code-bg: var(--vp-c-danger-soft);
437 |
438 | --vp-custom-block-caution-border: transparent;
439 | --vp-custom-block-caution-text: var(--vp-c-text-1);
440 | --vp-custom-block-caution-bg: var(--vp-c-caution-soft);
441 | --vp-custom-block-caution-code-bg: var(--vp-c-caution-soft);
442 |
443 | --vp-custom-block-details-border: var(--vp-custom-block-info-border);
444 | --vp-custom-block-details-text: var(--vp-custom-block-info-text);
445 | --vp-custom-block-details-bg: var(--vp-custom-block-info-bg);
446 | --vp-custom-block-details-code-bg: var(--vp-custom-block-info-code-bg);
447 | }
448 |
449 | /**
450 | * Component: Input
451 | * -------------------------------------------------------------------------- */
452 |
453 | :root {
454 | --vp-input-border-color: var(--vp-c-border);
455 | --vp-input-bg-color: var(--vp-c-bg-alt);
456 |
457 | --vp-input-switch-bg-color: var(--vp-c-default-soft);
458 | }
459 |
460 | /**
461 | * Component: Nav
462 | * -------------------------------------------------------------------------- */
463 |
464 | :root {
465 | --vp-nav-height: 64px;
466 | --vp-nav-bg-color: var(--vp-c-bg);
467 | --vp-nav-screen-bg-color: var(--vp-c-bg);
468 | --vp-nav-logo-height: 24px;
469 | }
470 |
471 | .hide-nav {
472 | --vp-nav-height: 0px;
473 | }
474 |
475 | .hide-nav .VPSidebar {
476 | --vp-nav-height: 22px;
477 | }
478 |
479 | /**
480 | * Component: Local Nav
481 | * -------------------------------------------------------------------------- */
482 |
483 | :root {
484 | --vp-local-nav-bg-color: var(--vp-c-bg);
485 | }
486 |
487 | /**
488 | * Component: Sidebar
489 | * -------------------------------------------------------------------------- */
490 |
491 | :root {
492 | --vp-sidebar-width: 272px;
493 | --vp-sidebar-bg-color: var(--vp-c-bg-alt);
494 | }
495 |
496 | /**
497 | * Colors Backdrop
498 | * -------------------------------------------------------------------------- */
499 |
500 | :root {
501 | --vp-backdrop-bg-color: rgba(0, 0, 0, 0.6);
502 | }
503 |
504 | /**
505 | * Component: Home
506 | * -------------------------------------------------------------------------- */
507 |
508 | :root {
509 | --vp-home-hero-name-color: var(--vp-c-brand-1);
510 | --vp-home-hero-name-background: transparent;
511 |
512 | --vp-home-hero-image-background-image: none;
513 | --vp-home-hero-image-filter: none;
514 | }
515 |
516 | /**
517 | * Component: Badge
518 | * -------------------------------------------------------------------------- */
519 |
520 | :root {
521 | --vp-badge-info-border: transparent;
522 | --vp-badge-info-text: var(--vp-c-text-2);
523 | --vp-badge-info-bg: var(--vp-c-default-soft);
524 |
525 | --vp-badge-tip-border: transparent;
526 | --vp-badge-tip-text: var(--vp-c-tip-1);
527 | --vp-badge-tip-bg: var(--vp-c-tip-soft);
528 |
529 | --vp-badge-warning-border: transparent;
530 | --vp-badge-warning-text: var(--vp-c-warning-1);
531 | --vp-badge-warning-bg: var(--vp-c-warning-soft);
532 |
533 | --vp-badge-danger-border: transparent;
534 | --vp-badge-danger-text: var(--vp-c-danger-1);
535 | --vp-badge-danger-bg: var(--vp-c-danger-soft);
536 | }
537 |
538 | /**
539 | * Component: Carbon Ads
540 | * -------------------------------------------------------------------------- */
541 |
542 | :root {
543 | --vp-carbon-ads-text-color: var(--vp-c-text-1);
544 | --vp-carbon-ads-poweredby-color: var(--vp-c-text-2);
545 | --vp-carbon-ads-bg-color: var(--vp-c-bg-soft);
546 | --vp-carbon-ads-hover-text-color: var(--vp-c-brand-1);
547 | --vp-carbon-ads-hover-poweredby-color: var(--vp-c-text-1);
548 | }
549 |
550 | /**
551 | * Component: Local Search
552 | * -------------------------------------------------------------------------- */
553 |
554 | :root {
555 | --vp-local-search-bg: var(--vp-c-bg);
556 | --vp-local-search-result-bg: var(--vp-c-bg);
557 | --vp-local-search-result-border: var(--vp-c-divider);
558 | --vp-local-search-result-selected-bg: var(--vp-c-bg);
559 | --vp-local-search-result-selected-border: var(--vp-c-brand-1);
560 | --vp-local-search-highlight-bg: var(--vp-c-brand-1);
561 | --vp-local-search-highlight-text: var(--vp-c-neutral-inverse);
562 | }
563 |
--------------------------------------------------------------------------------
/src/utils/codeHighlighter.js:
--------------------------------------------------------------------------------
1 | import { getSingletonHighlighter } from 'shiki';
2 | import { transformerNotationDiff } from '@shikijs/transformers';
3 | import { escapeSvelte } from 'mdsvex';
4 |
5 | const THEME = 'github-dark';
6 |
7 | /**
8 | * Returns code with curly braces and backticks replaced by HTML entity equivalents
9 | * @param {string} code - highlighted HTML
10 | * @returns {string} - escaped HTML
11 | */
12 | function escapeHtml(code) {
13 | return code.replace(
14 | /[{}`]/g,
15 | (character) => ({ '{': '{', '}': '}', '`': '`' }[character]),
16 | );
17 | }
18 |
19 | /**
20 | * @param {string} code - code to highlight
21 | * @param {string} lang - code language
22 | * @param {string} meta - code meta
23 | * @returns {Promise} - highlighted html
24 | */
25 | async function highlighter(code, lang, meta) {
26 | const highlighter = await getSingletonHighlighter({
27 | langs: ['svelte', 'css', 'javascript', 'bash', 'html'],
28 | themes: ['catppuccin-latte', 'monokai']
29 | });
30 | const darkHtml = escapeHtml(escapeSvelte(highlighter.codeToHtml(code, {
31 | lang,
32 | theme: 'monokai',
33 | transformers: [
34 | transformerNotationDiff()
35 | ]
36 | })));
37 | const lightHtml = escapeHtml(escapeSvelte(highlighter.codeToHtml(code, {
38 | lang,
39 | theme: 'catppuccin-latte',
40 | transformers: [
41 | transformerNotationDiff()
42 | ]
43 | })));
44 | // return `{@html \`${darkHtml}${lightHtml}\` }`;
45 | return `{@html \`${lightHtml}${darkHtml}
\` }`;
46 | // const html = shikiHighlighter.codeToHtml(code, {
47 | // lang: lang,
48 | // theme: THEME,
49 | // transformers: [
50 | // transformerNotationDiff()
51 | // ]
52 | // });
53 | // return escapeSvelte(html);
54 | }
55 |
56 | export default highlighter;
57 |
--------------------------------------------------------------------------------
/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mskocik/svelty-picker/3c291e4425ea258e4d042f3eb6e1e68bd46adde0/static/favicon.png
--------------------------------------------------------------------------------
/svelte.config.js:
--------------------------------------------------------------------------------
1 | import { mdsvex } from "mdsvex";
2 | import adapter from '@sveltejs/adapter-auto';
3 | import mdsvexConfig from './mdsvex.config.js';
4 |
5 | /** @type {import('@sveltejs/kit').Config} */
6 | const config = {
7 | kit: {
8 | // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
9 | // If your environment is not supported, or you settled on a specific environment, switch out the adapter.
10 | // See https://svelte.dev/docs/kit/adapters for more information about adapters.
11 | adapter: adapter()
12 | },
13 |
14 | preprocess: [mdsvex(mdsvexConfig)],
15 | extensions: [".svelte", ".svelte.md"]
16 | };
17 |
18 | export default config;
19 |
--------------------------------------------------------------------------------
/test/01-render.test.js:
--------------------------------------------------------------------------------
1 | import { render } from "@testing-library/svelte";
2 | import { describe, it, expect } from "vitest";
3 | import SveltyPicker from '$lib/components/SveltyPicker.svelte';
4 | import userEvent from "@testing-library/user-event";
5 |
6 | describe('DOM Render', async () => {
7 | it('input only', () => {
8 | const { container } = render(SveltyPicker, { props: {
9 | // value: new Date()
10 | }});
11 |
12 | expect(container.querySelector('input[type=text]')).toBeInTheDocument();
13 | });
14 |
15 |
16 | it('show popup after focus', async () => {
17 | const { container } = render(SveltyPicker, {
18 | onfocus: () => console.log('focuesed')
19 | });
20 |
21 | expect(container.querySelector('.sdt-calendar-wrap')).not.toBeInTheDocument();
22 |
23 | const user = userEvent.setup();
24 | await user.click(container.querySelector('input[type=text]'));
25 |
26 | expect(container.querySelector('.sdt-calendar-wrap')).toBeInTheDocument();
27 | });
28 |
29 |
30 | it('pickerOnly', () => {
31 | const { container } = render(SveltyPicker, { props: {
32 | pickerOnly: true
33 | }});
34 |
35 | expect(container.querySelector('.sdt-component-wrap')).toBeInTheDocument();
36 | expect(container.querySelector('.sdt-calendar-wrap')).toBeInTheDocument();
37 | });
38 |
39 | });
40 |
--------------------------------------------------------------------------------
/test/02-datepicker.test.svelte.js:
--------------------------------------------------------------------------------
1 | import { describe, it, expect } from "vitest";
2 | // import { render } from "vitest-browser-svelte";
3 | import { render } from "@testing-library/svelte";
4 | import SveltyPicker from "$lib/components/SveltyPicker.svelte";
5 | import { userEvent } from "@testing-library/user-event";
6 | import { test } from "vitest";
7 | import { flushSync } from "svelte";
8 |
9 | function sleep(ms) {
10 | return new Promise(resolve => {
11 | setTimeout(resolve, ms);
12 | });
13 | }
14 |
15 |
16 |
17 | describe('Date selection [pickerOnly]', async () => {
18 | it('simple', async () => {
19 | let props = $state({
20 | value: null,
21 | pickerOnly: true
22 | });
23 |
24 | const screen = render(SveltyPicker, { props });
25 |
26 | const user = userEvent.setup();
27 |
28 | const day = screen.getByText('12', { selector: 'button' });
29 | await user.click(day);
30 |
31 | // console.log(screen.container.innerHTML);
32 |
33 | expect(screen.container.querySelector('td.is-selected')).toBeInTheDocument();
34 | expect(props.value.split('-').pop()).toEqual('12');
35 |
36 | });
37 |
38 | it('with initial value', async () => {
39 | let props = $state({
40 | value: '2024-12-09',
41 | pickerOnly: true
42 | });
43 |
44 | const screen = render(SveltyPicker, { props });
45 |
46 | expect(screen.container.querySelector('td.is-selected').textContent).toEqual('9');
47 |
48 | const user = userEvent.setup();
49 | await user.click(screen.getByText('12', { exact: true }));
50 |
51 |
52 | expect(screen.container.querySelector('td.is-selected')).toBeInTheDocument();
53 | expect(screen.container.querySelector('td.is-selected').textContent).toEqual('12');
54 | expect(props.value).toEqual('2024-12-12');
55 | });
56 |
57 |
58 | it('autocommit: false', async () => {
59 | let props = $state({
60 | value: '2024-12-09',
61 | pickerOnly: true,
62 | autocommit: false,
63 | });
64 |
65 | const screen = render(SveltyPicker, { props });
66 |
67 | const user = userEvent.setup();
68 | // await user.click(screen.container.querySelector('input[type=text]'));
69 |
70 | await user.click(screen.getByText('12', { exact: true }));
71 |
72 | expect(props.value).toEqual('2024-12-09');
73 |
74 | // expect(screen.container.querySelector('.value-dirty')).toBeInTheDocument();
75 |
76 | await user.click(screen.getByText('Ok', { exact: true }));
77 |
78 | expect(screen.container.querySelector('td.is-selected')).toBeInTheDocument();
79 | expect(screen.container.querySelector('td.is-selected').textContent).toEqual('12');
80 | expect(props.value).toEqual('2024-12-12');
81 | });
82 |
83 |
84 | it('update value from parent', async () => {
85 | let props = $state({
86 | value: '2024-12-09',
87 | pickerOnly: true
88 | });
89 |
90 | const screen = render(SveltyPicker, { props });
91 |
92 | expect(screen.container.querySelector('td.is-selected').textContent).toEqual('9');
93 |
94 | props.value = '2024-12-12';
95 |
96 | await sleep(500);
97 |
98 | expect(screen.container.querySelector('td.is-selected').textContent).toEqual('12');
99 | });
100 | });
101 |
102 | describe('Date selection', async () => {
103 | it('simple', async () => {
104 | let props = $state({
105 | value: null,
106 | });
107 |
108 | const screen = render(SveltyPicker, { props });
109 |
110 | const user = userEvent.setup();
111 | await user.click(screen.container.querySelector('input[type=text]'));
112 |
113 | expect(props.value).toBeNull();
114 |
115 | const day = screen.getByText('12', { selector: 'button' });
116 | await user.click(day);
117 |
118 | expect(props.value.split('-').pop()).toEqual('12');
119 |
120 | });
121 |
122 | it('with initial value', async () => {
123 | let props = $state({
124 | value: '2024-12-09',
125 | });
126 |
127 | const screen = render(SveltyPicker, { props });
128 |
129 | const user = userEvent.setup();
130 | await user.click(screen.container.querySelector('input[type=text]'));
131 |
132 | expect(screen.container.querySelector('td.is-selected').textContent).toEqual('9');
133 |
134 | await user.click(screen.getByText('12', { exact: true }));
135 |
136 | expect(props.value).toEqual('2024-12-12');
137 |
138 | await user.click(screen.container.querySelector('input[type=text]'));
139 |
140 | expect(screen.container.querySelector('td.is-selected')).toBeInTheDocument();
141 | expect(screen.container.querySelector('td.is-selected').textContent).toEqual('12');
142 | });
143 |
144 |
145 | it('autocommit: false', async () => {
146 | let props = $state({
147 | value: '2024-12-09',
148 | autocommit: false,
149 | });
150 |
151 | const screen = render(SveltyPicker, { props });
152 |
153 | const user = userEvent.setup();
154 | await user.click(screen.container.querySelector('input[type=text]'));
155 |
156 | await user.click(screen.getByText('12', { exact: true }));
157 |
158 | expect(props.value).toEqual('2024-12-09');
159 |
160 | expect(screen.container.querySelector('.value-dirty')).toBeInTheDocument();
161 |
162 | await user.click(screen.getByText('Ok', { exact: true }));
163 |
164 | flushSync();
165 |
166 | // popup should be closed, but is not in jsdom 🤷 TODO: use vite-browser here
167 | // expect(screen.container.querySelector('.sdt-calendar-wrap')).not.toBeInTheDocument();
168 | // expect(screen.container.querySelector('td.is-selected')).not.toBeInTheDocument();
169 |
170 | expect(props.value).toEqual('2024-12-12');
171 | });
172 |
173 |
174 | it('update value from parent', async () => {
175 | let props = $state({
176 | value: '2024-12-09',
177 | });
178 |
179 | const screen = render(SveltyPicker, { props });
180 |
181 | const user = userEvent.setup();
182 | await user.click(screen.container.querySelector('input[type=text]'));
183 |
184 | expect(screen.container.querySelector('td.is-selected').textContent).toEqual('9');
185 |
186 | props.value = '2024-12-12';
187 |
188 | await sleep(500);
189 |
190 | expect(screen.container.querySelector('td.is-selected').textContent).toEqual('12');
191 | });
192 | });
193 |
194 |
195 | describe('formatting', () => {
196 | it('simple format test', async () => {
197 | let props = $state({
198 | value: '2024-12-09',
199 | displayFormat: 'yyyy.mm.dd',
200 | autocommit: false,
201 | });
202 |
203 | const screen = render(SveltyPicker, { props });
204 |
205 | expect(screen.container.querySelector('input[type=hidden]').value).toEqual('2024-12-09');
206 | expect(screen.container.querySelector('input[type=text]').value).toEqual('2024.12.09');
207 | });
208 |
209 | it('update [format]', async () => {
210 | let props = $state({
211 | value: '2024-12-09',
212 | autocommit: false,
213 | });
214 |
215 | const screen = render(SveltyPicker, { props });
216 |
217 | screen.rerender({
218 | format: 'dd.mm.yyyy'
219 | });
220 |
221 | await sleep(500);
222 |
223 | expect(screen.container.querySelector('input[type=hidden]').value).toEqual('09.12.2024');
224 | expect(screen.container.querySelector('input[type=text]').value).toEqual('09.12.2024');
225 | });
226 |
227 | it('update [displayFormat]', async () => {
228 | let props = $state({
229 | value: '2024-12-09',
230 | autocommit: false,
231 | });
232 |
233 | const screen = render(SveltyPicker, { props });
234 |
235 | screen.rerender({
236 | displayFormat: 'dd.mm.yyyy'
237 | });
238 |
239 | await sleep(500);
240 |
241 | expect(screen.container.querySelector('input[type=hidden]').value).toEqual('2024-12-09');
242 | expect(screen.container.querySelector('input[type=text]').value).toEqual('09.12.2024');
243 | });
244 | })
245 |
246 | test('clear', async () => {
247 |
248 | let props = $state({
249 | value: '10:24',
250 | mode: 'time',
251 | format: 'hh:ii',
252 | pickerOnly: true
253 | });
254 | const screen = render(SveltyPicker, { props });
255 |
256 | const user = userEvent.setup();
257 | await user.click(screen.getByText('Clear'));
258 |
259 | expect(props.value || null).toEqual(null);
260 | });
261 |
--------------------------------------------------------------------------------
/test/03-timepicker.test.svelte.js:
--------------------------------------------------------------------------------
1 | import { describe, it, expect } from "vitest";
2 | // import { render } from "vitest-browser-svelte";
3 | import { render } from "@testing-library/svelte";
4 | import SveltyPicker from "$lib/components/SveltyPicker.svelte";
5 | import { userEvent } from "@testing-library/user-event";
6 |
7 | function sleep(ms) {
8 | return new Promise(resolve => {
9 | setTimeout(resolve, ms);
10 | });
11 | }
12 |
13 | describe('Time selection', async () => {
14 | it('simple', async () => {
15 | let props = $state({
16 | value: null,
17 | format: 'hh:ii'
18 | });
19 |
20 | const screen = render(SveltyPicker, { props });
21 |
22 | const mainInput = screen.container.querySelector('input[type=text]');
23 |
24 | const user = userEvent.setup();
25 | await user.click(mainInput);
26 |
27 | const hour = screen.getByText('12', { selector: 'button.sdt-tick' });
28 | await user.click(hour);
29 |
30 | const minute = screen.getByText('30', { selector: 'button.sdt-tick' });
31 | await user.click(minute);
32 |
33 |
34 | // popup should be closed, but is not in jsdom 🤷 TODO: use vite-browser here
35 | // expect(screen.container.querySelector('.sdt-calendar-wrap')).not.toBeInTheDocument();
36 | expect(props.value).toEqual('12:30');
37 |
38 | });
39 | });
40 |
41 |
42 | describe('Time selection [pickerOnly]', async () => {
43 | it('simple', async () => {
44 | let props = $state({
45 | value: null,
46 | format: 'hh:ii',
47 | pickerOnly: true
48 | });
49 |
50 | const screen = render(SveltyPicker, { props });
51 |
52 | const user = userEvent.setup();
53 |
54 | const hour = screen.getByText('12', { selector: 'button.sdt-tick' });
55 | await user.click(hour);
56 |
57 | const minute = screen.getByText('30', { selector: 'button.sdt-tick' });
58 | await user.click(minute);
59 |
60 | expect(minute.classList.contains('is-selected')).toBeTruthy();
61 | expect(screen.container.querySelector('button.is-selected[data-value="30"]')).toBeInTheDocument();
62 | expect(props.value).toEqual('12:30');
63 |
64 | });
65 | });
66 |
--------------------------------------------------------------------------------
/test/04-datetime.test.svelte.js:
--------------------------------------------------------------------------------
1 | import { test } from "vitest";
2 |
3 | test.skip('datetime tests', () => {
4 | // TODO:
5 | });
6 |
--------------------------------------------------------------------------------
/test/05-daterange.test.svelte.js:
--------------------------------------------------------------------------------
1 | import { test } from "vitest";
2 |
3 | test.skip('daterange tests', () => {
4 | // TODO:
5 | });
6 |
--------------------------------------------------------------------------------
/test/vitest-setup.js:
--------------------------------------------------------------------------------
1 | import '@testing-library/jest-dom/vitest';
2 | import { vi } from 'vitest';
3 |
4 | // mocking Element.animate
5 | Element.prototype.animate = vi
6 | .fn()
7 | .mockImplementation(() => ({ cancel: vi.fn(), finished: Promise.resolve() }));
8 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitest/config";
2 | import { sveltekit } from '@sveltejs/kit/vite';
3 | import {svelteTesting} from '@testing-library/svelte/vite'
4 |
5 | export default defineConfig({
6 | plugins: [sveltekit(), svelteTesting()],
7 | optimizeDeps: {
8 | include: ['@floating-ui/dom']
9 | },
10 | test: {
11 | environment: 'jsdom',
12 | include: ['test/**/*.{test,spec,test.svelte}.{js,ts}'],
13 | setupFiles: ['test/vitest-setup.js'],
14 | // browser: {
15 | // enabled: true,
16 | // provider: 'playwright',
17 | // name: 'firefox',
18 | // }
19 | }
20 | });
21 |
--------------------------------------------------------------------------------