API dokumentace

Kompletní reference knihovny Gregory ve verzi 0.3.0. Všechno, co je tady popsané, je součástí veřejného API.

Instalace

Rovnou z GitHubu — do npm registru se knihovna vydávat nebude:

npm install github:svatekr70/gregory        # poslední main
npm install github:svatekr70/gregory#v0.3.0 # konkrétní verze

Balíček je ESM i UMD, typy jsou součástí:

import { Gregory } from '@svatekr70/gregory'
import '@svatekr70/gregory/style.css'

Bez build kroku, přímo v HTML:

<link rel="stylesheet" href="…/gregory/dist/gregory.css">
<script src="…/gregory/dist/gregory.umd.cjs"></script>
<script>
  new Gregory.Gregory('#vstup', { mode: 'range' })
</script>
Styly se importují zvlášť

Knihovna do stránky sama nic nevkládá. Buď načti gregory/style.css, nebo si napiš vlastní — komponenta používá jen třídy s prefixem gr-.

Rychlý start

Jako třída

const picker = new Gregory('#vstup', {
  mode: 'range',
  locale: 'cs',
})

picker.on('apply', ({ value }) => {
  // value je { from: Date, to: Date }
  odesliFiltr(value.from, value.to)
})

Jako custom element

import { defineElement } from '@svatekr70/gregory'
defineElement()
<gregory-picker mode="range" locale="cs" months="2"></gregory-picker>

Konstruktor

new Gregory(target: string | HTMLElement, options?: GregoryOptions)

target je CSS selektor nebo přímo prvek:

  • je-li to <input>, picker se na něj napojí jako popover a zapisuje do něj naformátovanou hodnotu,
  • je-li to jakýkoli jiný prvek a je zapnuté inline: true, vykreslí se dovnitř,
  • jiný prvek bez inline se stane spouštěčem — viz níž,
  • když selektor nic nenajde, konstruktor vyhodí Error.

Existuje i tovární funkce, když ti vadí new:

gregory(target, options?): Gregory

Picker na jiném prvku než inputu

Datum bývá i mimo formulář — v odznaku, v buňce tabulky, ve <span>. Takový prvek se stane spouštěčem: klik nebo Enter otevře panel a potvrzená hodnota se do něj vypíše.

<span id="termin" data-value="2026-08-13" data-placeholder="Nezadáno">
  📅 <b data-gr-value>13. 8. 2026</b>
</span>
new Gregory('#termin', { mode: 'date' })
AtributK čemu je
data-valueStrojová podoba hodnoty. Odsud se čte počáteční stav a sem se po potvrzení zapisuje — ISO datum, u rozsahu od/do. Text prvku je pro lidi, ten se neparsuje.
data-gr-valueNepovinný potomek, do kterého se vypíše text. Bez něj se přepíše obsah celého prvku, takže by ikona nebo popisek zmizely.
data-placeholderCo se vypíše, když je hodnota prázdná.

Prvek dostane tabindex="0" a role="button", pokud je sám nemá — u <button> se nesahá na nic. Po potvrzení prvek vyšle bublající gregory:change s hodnotou v event.detail, takže na něj jde reagovat i bez reference na picker. destroy() přidané atributy zase odebere.

Přehled voleb

VolbaTypVýchozíPopis
modeMode'date'Režim výběru.
classNamestringVlastní třídy pro kořen panelu; takhle se aplikují motivy.
valueDateLike | DateRange | [DateLike, DateLike]nullPočáteční hodnota.
localeLocaleInputjazyk prohlížečeBCP 47 tag nebo přebití částí locale.
minDateLikenullNejstarší volitelný den.
maxDateLikenullNejmladší volitelný den.
firstDayOfWeek0–6dle locale0 = neděle … 6 = sobota.
monthsnumber2 v range, jinak 1Počet panelů vedle sebe.
linkedCalendarsbooleanfalseListovat všemi panely najednou místo každým zvlášť.
weekNumbersbooleanfalseSloupec s ISO čísly týdnů.
showOutsideDaysbooleantrueZobrazovat dny přesahující ze sousedních měsíců.
weekSelection'off' | 'number' | 'day' | 'both''off'Výběr celého týdne jedním klikem.
dropdownsboolean | 'menu'falseVýběr měsíce a roku: nativní <select>, nebo seznam otevřený klikem na caption.
endInputstring | HTMLInputElementDruhé pole pro konec rozsahu.
allowTypingbooleantrueČíst datum napsané rukou do pole.
submitNamestring | { from, to }Skrytá pole s ISO hodnotou pro odeslání formuláře.
disabledbooleanfalseZamkne picker bez ohledu na stav pole.
lockOnReadonlybooleanfalseZamknout i nad polem s readonly. Bez toho se picker nad readonly polem normálně otevře.
inlinebooleanfalseVykreslit na místo místo popoveru.
autoApplybooleantrue jen v režimu datePotvrdit hned, bez tlačítek.
presetsRangePreset[] | booleanvestavěné v range režimechPostranní zkratky.
maxSpannumber | nullnullNejdelší rozsah ve dnech (včetně obou konců).
minSpannumber | nullnullNejkratší rozsah ve dnech (včetně obou konců).
stopAtDisabledbooleanfalseRozsah nesmí přeskočit den zakázaný přes isDisabled.
allowOpenRangebooleanfalsePovolí rozsah otevřený na jednom konci.
compareboolean | CompareKind | (range) => [from, to]falseSrovnávací období k vybranému rozsahu. Dopočítává se, nevybírá.
timeStepnumber5Krok minut v časových režimech.
timeUi'select' | 'slider' | 'input''select'Podoba ovládání času.
minTimestring | nullnullNejdřívější čas dne, 'HH:MM'. Včetně.
maxTimestring | nullnullNejpozdější čas dne, 'HH:MM'. Včetně.
timeWindow(date: Date) => { min?, max? } | nullČasové okno pro konkrétní den; přebíjí minTime a maxTime.
fullscreenBelownumber | null480Pod touto šířkou okna se panel otevře přes celou obrazovku.
opens'left' | 'right' | 'center''right'Vodorovné zarovnání popoveru.
drops'down' | 'up' | 'auto''auto'Směr otevření popoveru.
isDisabled(date: Date) => booleanZakáže konkrétní dny.
dayClass(date: Date) => string | nullPřidá vlastní třídu dni.
dayBadge(date: Date) => string | nullZnačka pod číslem dne — tečka nebo počet.
format(value, locale) => stringVlastní text do inputu.
summaryboolean | (value, locale) => stringfalseŘádek v panelu s právě vybranými daty.

Volby podrobně

mode

Určuje tvar hodnoty i výchozí hodnoty ostatních voleb.

HodnotaVracíVýchozí monthsVýchozí autoApply
'date'Date | null1true
'range'DateRange2false
'datetime'Date | null1false
'datetime-range'DateRange2false
'multiple'Date[]1false
'month'Date | null1true
'quarter'Date | null1true
'year'Date | null1true

Tlačítka v patičce

Vlevo jsou pomocné akce, vpravo potvrzení — stejné rozdělení jako v nativním kalendáři prohlížeče.

TlačítkoCo udělá
DnesPřeskočí na dnešek a vybere ho — stejně, jako by se kliklo na jeho buňku. Když je dnešek mimo min/max, je zašedlé.
NyníTotéž v režimech s časem (datetime, datetime-range), akorát nastaví i aktuální čas — srovnaný po timeStep do okna minTime/maxTime. Popisek je labels.now.
VymazatVyprázdní hodnotu a rovnou ji potvrdí: vyšle apply s null, vyprázdní input a zavře panel. Takhle se odesílá „nic". Zašedlé, dokud není co mazat.
ZrušitZahodí rozpracovaný výběr a vrátí poslední potvrzenou hodnotu. Jen mimo autoApply.
PoužítPovýší výběr na hodnotu. Jen mimo autoApply.

Prázdná hodnota má tvar null v single režimech a { from: null, to: null } v rozsazích.

Režimy month a year

Na filtry v přehledech („vyber měsíc") je denní kalendář zbytečný. Tyhle režimy místo dnů ukážou mřížku dvanácti měsíců, respektive dvanácti let.

new Gregory('#obdobi', { mode: 'month' })

picker.getValue()  // Date(2026-08-01) — první den období

Hodnotou je první den vybraného období, takže se s ní dá dál počítat jako s běžným datem. V poli se píše „srpen 2026", respektive „2026".

Šipky listují po celých obdobích — u měsíců po rocích, u roků po stránkách po dvanácti. Stránky jsou ukotvené na násobky dvanácti, takže listování tam a zpět skončí, kde začalo.

min a max zakážou období, které do rozmezí vůbec nezasahuje: s min: '2026-03-15' je únor zakázaný, ale březen ne. Potvrzuje se hned, bez tlačítek, a postranní zkratky ani čísla týdnů nedávají smysl, takže se nevykreslují.

Režim multiple

Seznam samostatných dnů, ne rozsah — rozvrhy, směny, termíny školení. Klik den přidá, druhý klik ho zase odebere.

new Gregory('#vstup', {
  mode: 'multiple',
  maxSelected: 5,
})

picker.getValue()  // [Date, Date, Date] — vždy vzestupně

Hodnota je pole seřazené vzestupně, bez ohledu na pořadí klikání. Vybrané dny se malují každý zvlášť — mezi nimi nevzniká rozsah, to je celý rozdíl proti range.

Do pole se vypíše seznam a za třetím dnem se zkrátí: 5. 8. 2026, 13. 8. 2026, 20. 8. 2026 +1. Souhrn místo toho hlásí počet („4 dny"). Na vstupu se čeká pole: value: ['2026-08-05', '2026-08-13'].

maxSelected omezí počet. Po dosažení limitu další den nepřibude, ale odebrat už vybraný jde pořád — jinak by se uživatel zasekl.

Panel se sám nezavírá

Seznam se pořád doplňuje, takže „hotovo" nemá kdy nastat. I s autoApply se hodnota průběžně potvrzuje, ale panel zůstane otevřený, dokud ho uživatel nezavře.

autoApply

Rozhoduje o tom, kdy se rozpracovaný výběr stane hodnotou.

  • true — poslední klik rovnou potvrdí, patička nemá Použít / Zrušit, kliknutí mimo panel jen zavře.
  • false — výběr žije odděleně, dokud uživatel nedá Použít. Escape, Zrušit i kliknutí mimo vrátí předchozí hodnotu.

Rozdíl je vidět na návratových hodnotách: getSelection() ukazuje rozpracovaný stav, getValue() až potvrzený.

Jeden den v rozsahu

Tlačítko Použít se odemkne už po prvním kliknutí. Potvrzení jediného dne uloží jednodenní rozsah{ from: 10. 8., to: 10. 8. }. Klikat dvakrát na stejné datum není potřeba. Se zapnutým allowOpenRange se místo toho uloží otevřený rozsah.

Zapnuté je jen v režimu date, protože jinde jeden klik hodnotu nedokončí: rozsah čeká na druhý konec a datetime na čas. Kdyby se datetime potvrzoval hned, panel by se zavřel dřív, než by šel čas vůbec nastavit.

months a linkedCalendars

Ve výchozím stavu má každý panel vlastní šipky a lze jím listovat nezávisle. Panely přitom musí zůstat v chronologickém pořadí, takže ten posunutý si v případě potřeby odsune sousedy:

AkceVýsledek
první panel vpředdruhý ustoupí, pokud by se překryly
první panel zpětdruhý zůstává, mezera se zvětší
druhý panel zpětprvní ustoupí, pokud by se překryly
druhý panel vpředprvní zůstává, mezera se zvětší

Odsun je vždy nejmenší možný. U sousedních měsíců (srpen / září) to vyjde na stejný posun, jaký jsi provedl. Když si ale mezi panely vytvoříš mezeru (srpen / listopad), zůstane zachovaná, dokud se panely nedotknou.

linkedCalendars: true vrátí spřažené chování: panely tvoří jeden blok, listuje se jedinou dvojicí šipek na krajích a mezera mezi měsíci je vždy nulová.

Pořadí panelů vs. pořadí hodnoty

Že by from vyšlo později než to, hlídá už samotný výběr — konce se v takovém případě prohodí. Pořadí panelů je věc přehlednosti: nemá se stát, aby levý kalendář ukazoval pozdější měsíc než pravý.

maxSpan

Platí jen v range režimech a měří se včetně obou konců: maxSpan: 7 dovolí sedmidenní rozsah, ne osmidenní. Jakmile je vybraný první konec, dny mimo okno se automaticky zakážou — a to na obě strany, protože druhý klik může jít i dozadu.

minSpan

Protějšek maxSpan, měří se stejně — včetně obou konců. Po prvním kliknutí zešednou dny, které jsou blíž, než kolik je potřeba. Samotný počáteční den zůstává klikatelný: je to způsob, jak výběr začít jinde, ne pokus o nulový rozsah.

stopAtDisabled

Bez něj může rozsah zakázané dny přeskočit — kdo zašedil víkendy, chce přes ně dovolenou normálně vybrat. Zapnutý naopak hlídá, že mezi konci rozsahu není ani jeden zakázaný den: po prvním kliknutí se výběr zastaví na nejbližším zakázaném dni na obě strany. To je chování rezervačních kalendářů, kde obsazený den rozsah přerušuje.

new Gregory('#vstup', {
  mode: 'range',
  isDisabled: (d) => jeObsazeno(d),
  stopAtDisabled: true,
})

disabled, readonly

Pole s disabled picker zamkne: panel se neotevře a napsaný text se nečte. Volba disabled udělá totéž bez ohledu na pole — hodí se na inline panel, který žádné pole nemá. Programové setValue() funguje dál, zamčené je jen ovládání.

Pole bez psaní, ale s pickerem

Nad readonly polem se picker otevře a datum do něj zapíše — pole, do kterého se hodnota dostane jen z kalendáře, je běžný vzor. Z pole se pak nic nečte, psát do něj stejně nejde. Když má být zamčené i to, je na to lockOnReadonly: true; když má jít jen o zákaz psaní u obyčejného pole, stačí allowTyping: false.

isDisabled a dayClass

Obě dostanou Date nastavené na půlnoc daného dne. isDisabled se ptá až po kontrole min, max a maxSpan — vrácené false tedy dřívější zákaz nepřebije.

new Gregory('#vstup', {
  isDisabled: (d) => d.getDay() === 0 || d.getDay() === 6,
  dayClass: (d) => jeSvatek(d) ? 'je-svatek' : null,
})

fullscreenBelow

Na telefonu je popover u pole nepohodlný a u pole při kraji obrazovky se navíc nemá kam vejít. Pod zadanou šířkou okna se proto panel otevře jako dialog uprostřed obrazovky i s podkladem, na který jde kliknout.

// výchozí je 480; vypnout jde null
new Gregory('#vstup', { fullscreenBelow: null })

Panel v tomhle režimu nese data-fullscreen, podklad má třídu .gr-backdrop. Přepíná se i za chodu, takže otočení telefonu ho přehodí bez zavření.

Nezávisle na tom se popover drží uvnitř okna — hlídá se levý i pravý okraj, takže u pole na kraji obrazovky panel nevyteče ven.

submitName

Ve viditelném poli je 13. 8. 2026 — datum pro lidi, které server spolehlivě nepřečte. submitName vyrobí vedle něj skryté pole s ISO hodnotou, které se odešle s formulářem:

new Gregory('#termin', {
  mode: 'date',
  submitName: 'termin',
})

// odešle se termin=2026-08-13

V range režimech vzniknou dvě pole, termin_from a termin_to. Vlastní jména jim dá dvojice:

submitName: { from: 'od', to: 'do' }

Hodnota je ISO datum, v datetime režimech 2026-08-13T14:30. Prázdný konec otevřeného rozsahu zůstane prázdný řetězec. Pole se vkládají hned za viditelný prvek, takže spadnou do stejného formuláře, a destroy() je zase odebere.

allowTyping

Datum se dá napsat rovnou do pole — zkušený uživatel je napíše rychleji, než naklikne. Čte se při opuštění pole nebo po Enteru.

NapsanéPřečte se jako
13. 8. 2026Plné datum v pořadí podle locale — en-US čeká 8/13/2026.
1.9.2026Mezery a tvar oddělovače nehrají roli; pole se pak přepíše na kanonický tvar.
13. 8.Bez roku — doplní se letošní.
13Jen den — doplní se aktuální měsíc a rok.
13. 8. 26Dvojciferný rok se rozvine na 2026.
2026-08-13ISO tvar projde v každém jazyce.
13. 8. 2026 14:30Čas na konci se v datetime režimech použije.
10. 8. – 14. 8.Rozsah v jednom poli; jako oddělovač projde pomlčka, „až" i „to".

Co se přečíst nedá, se zahodí a pole se vrátí na poslední platnou hodnotu — nesmyslný text tedy nikdy nepřepíše to, co bylo předtím. Nesmysl je i 31. 2., datum se nepřetáčí na březen. Vymazání pole hodnotu vymaže.

U rozděleného pickeru (endInput) se každé pole čte zvlášť, takže do nich jde psát nezávisle. Vypnout jde volbou allowTyping: false, čímž se pole stane jen výstupem.

Samotné čtení je i na locale: locale.parseInput(text).

endInput

Rozsah rozdělený do dvou polí — jedno drží „od", druhé „do". Cíl konstruktoru je to první, endInput to druhé:

<input id="od"> <input id="do">
new Gregory('#od', {
  mode: 'range',
  endInput: '#do',
})

Panel otevře kterékoli z polí a pověsí se pod to, kterým se otevřel. Vybírá se přitom jeden rozsah — teprve potvrzení ho rozdělí: from do prvního pole, to do druhého. Každé pole tedy nese jedno datum, ne celý rozsah s oddělovačem.

Počáteční hodnota se čte z obou polí, Vymazat obě vyprázdní a u otevřeného rozsahu zůstane druhé pole prázdné. Kliknutí do druhého pole panel nezavírá.

Platí jen pro rozsahy nad inputem

Mimo range a datetime-range se volba ignoruje, stejně jako v kombinaci s inline. Volba format se u rozděleného pickeru volá pro každý konec zvlášť, ne pro celý rozsah.

showOutsideDays

Mřížka má vždy šest řádků, takže první a poslední týden obvykle přetéká do sousedních měsíců. Ve výchozím stavu jsou tyhle dny vidět zašedle a jdou vybrat. showOutsideDays: false je skryje:

new Gregory('#vstup', {
  showOutsideDays: false,
})

Buňka přitom zůstane, jen prázdná — mřížka si drží šest řádků a panel při listování neposkakuje. Prázdná místa nesou třídu .gr-day-empty, nejsou to tlačítka a nereagují na myš.

dropdowns

Bez této volby je v hlavičce jen text „srpen 2026". Zapnout jde dvěma způsoby:

HodnotaHlavička
falseVýchozí. Jen titulek a šipky.
trueDva nativní <select>y, trvale viditelné.
'menu'Titulek zůstane, ale měsíc a rok jsou tlačítka — klik na ně vyroluje seznam.
new Gregory('#vstup', {
  dropdowns: 'menu',
})

Seznam roků se řídí min a max, jinak sahá deset let na obě strany, a otevře se rovnou na zobrazeném roce. Zavře ho druhý klik na stejné tlačítko, klik kamkoli jinam v panelu, nebo Escape — ten nejdřív zavře seznam a teprve podruhé celý picker.

Výběr ze seznamu jde stejnou cestou jako šipky, takže respektuje linkedCalendars i pořadí panelů. Každý panel má vlastní seznam.

weekSelection

Udělá z pickeru výběr týdnů — jeden klik vybere celých sedm dní jako rozsah. Platí jen v range režimech.

HodnotaChování
'off'Výchozí. Klikání po dnech jako obvykle.
'number'Čísla týdnů se stanou tlačítky. Vyžaduje weekNumbers. Dny se vybírají normálně.
'day'Klik na kterýkoli den vybere jeho týden. Čísla týdnů nejsou potřeba.
'both'Obojí zároveň.
new Gregory('#vstup', {
  mode: 'range',
  weekSelection: 'day',
})

V režimu 'day' se týden pod kurzorem rovnou předsvítí, takže je před kliknutím vidět, co se vybere.

Vybraný týden se řídí zobrazeným řádkem, tedy volbou firstDayOfWeek. V české locale to je pondělí až neděle, v en-US neděle až sobota — číslo vlevo přitom zůstává ISO týdnem, který se počítá vždy od pondělí.

Omezení platí i tady: týden se ořízne podle min a max, takže z částečně dostupného týdne vybere jen použitelnou část. Když je maxSpan menší než 7, klik se ignoruje — sedm dní by se do limitu nevešlo.

allowOpenRange

Někdy je hodnota „od 1. 8. 2026 dál" — bez konce. S touto volbou smí jeden konec rozsahu zůstat null:

const picker = new Gregory('#vstup', {
  mode: 'range',
  allowOpenRange: true,
})

picker.getValue()
// { from: Date(2026-08-01), to: null }

Chování se tím mění na třech místech:

  • Potvrzení jediného vybraného dne uloží otevřený rozsah { from, to: null }. Bez této volby by se stejný klik uložil jako jednodenní rozsah.
  • V patičce přibudou tlačítka Bez začátku a Bez konce, která příslušný konec zahodí. Zbylý konec se přesune na správnou stranu, takže z „1. 8." udělá Bez začátku hodnotu { from: null, to: 1. 8. }.
  • Kalendář vybarví celý zbytek směrem, kterým je rozsah otevřený — je tak vidět, co by potvrzení právě teď znamenalo.

V inputu se otevřená hodnota píše s předponou z locale: od 1. 8. 2026, respektive do 14. 8. 2026. Vlastní format to samozřejmě přebije.

autoApply zůstává na obou koncích

I se zapnutým allowOpenRange se panel sám zavře až po vybrání obou konců. Kdyby se zavíral po prvním kliknutí — které je teď „hotové" — nešel by druhý konec vybrat vůbec. Otevřený rozsah se proto potvrzuje výhradně tlačítkem.

Jako vstup projde { from, to: null }, dvojice [from, null] i řetězec s prázdnou stranou: '2026-08-01/' nebo '/2026-08-14'. Ve stejném tvaru se hodnota zrcadlí do atributu value custom elementu.

compare

Reporty se skoro vždycky dívají na zvolené období a na to předchozí. compare ho dopočítá z hlavního rozsahu — nevybírá se, v mřížce se jen vyznačí pruhem pod dny:

const picker = new Gregory('#obdobi', {
  mode: 'range',
  summary: true,
  compare: 'previous',
})

picker.on('apply', ({ value, compare }) => {
  nacti(value.from, value.to)
  if (compare) nacti(compare.from, compare.to)
})
HodnotaCo spočítá
'previous' (nebo true)Stejně dlouhé období těsně před začátkem.
'year'Stejná data o rok zpět.
'year-weekday'Posun o 364 dní, tedy o 52 celých týdnů — sedí dny v týdnu.
(range) => [from, to]Vlastní výpočet. Dostane rozsah setříděný; null znamená neporovnávat.
falseVypnuto. Výchozí stav.
Celý měsíc se porovnává s celým předchozím měsícem

Únor je kratší než leden, takže počítáno po dnech by „předchozí období“ k 1.–28. 2. vyšlo na 4.–31. 1. Když výběr přesně odpovídá celému kalendářnímu měsíci, čtvrtletí nebo roku, vrací se celý předchozí celek. U 'year' vychází totéž z ořezu dne: celý přestupný únor 2024 dá celý únor 2023.

Hodnota je v getCompare() a v payloadu událostí change a apply. Se zapnutým summary přibude i do souhrnu: 18. – 31. 8. 2026 vs. 4. – 17. 8. 2026 · 14 dní. Spojku nese locale.labels.comparedTo.

Porovnávat jde jen v režimech rozsahu; jinde je volba bez efektu. Srovnávací období navíc nepodléhá min, max ani maxSpan — jsou to meze výběru, a tohle se nevybírá. Období může vyjít i mimo ně; je na aplikaci, jak s tím naloží.

Samotný výpočet je čistá funkce a je exportovaný, takže se dá použít i mimo picker — třeba když stejné období potřebuje i dotaz na serveru. Viz Datové utility.

Čas: timeStep, timeUi, minTime, maxTime

V režimech datetime a datetime-range je v patičce ovládání času — v range režimu zvlášť pro každý konec, s popiskem Od / Do.

timeUiOvládání
'select'Výchozí. Dva selecty — hodiny a minuty. Minuty jdou po timeStep, takže při kroku 15 nabídnou 00, 15, 30, 45.
'slider'Posuvník na hodiny a na minuty, nad nimi výsledný čas. Pohodlné na dotyku.
'input'Nativní <input type="time"> s odpovídajícím step, min a max.

Posuvníky se přepočítávají za tahu a panel se přitom nepřestavuje — vyměnit uzel pod táhlem by tažení přerušilo. Když hodina dojede na kraj okna, rozsah minut se sám zúží; při maxTime: '18:00' tak v osmnácté hodině zbude jediná poloha.

minTime a maxTime vymezí použitelné okno dne. Obě hranice jsou inkluzivní:

new Gregory('#vstup', {
  mode: 'datetime-range',
  timeStep: 30,
  minTime: '08:00',
  maxTime: '18:00',
})

Select hodin pak nabídne 8 až 18. V osmnácté hodině zbude jediná volba :00, protože 18:30 by už bylo za hranicí — seznam minut se počítá pro právě zvolenou hodinu.

Co se stane s časem mimo okno

Čerstvě vybraný den startuje na začátku okna, ne o půlnoci. Hodnota vložená přes setValue() nebo value se do okna zatáhne a zarovná na krok. Hranice zadané obráceně (minTime pozdější než maxTime) picker prohodí místo toho, aby znepřístupnil celý den.

timeWindow

minTime a maxTime platí pro celý picker, což nestačí tam, kde se v sobotu otevírá jinak než ve všední den. timeWindow dostane den a vrátí meze pro něj:

new Gregory('#vstup', {
  mode: 'datetime',
  minTime: '08:00',
  maxTime: '18:00',
  timeWindow: (d) => d.getDay() === 6 ? { min: '09:00', max: '12:00' } : null,
})

Co funkce neuvede, zůstává na globálních mezích: { max: '12:00' } zkrátí jen konec dne, začátek si drží minTime. Explicitní null naopak mez pro ten den ruší, null místo celého okna znamená „nic zvláštního". Každý konec rozsahu se řídí oknem svého vlastního dne a čas, který se přesunem dostane mimo okno, se srovná dovnitř.

summary

Přidá do panelu řádek, který slovy říká, co je právě vybrané. Hodí se hlavně u inline pickeru, kde není input, do kterého by se hodnota psala.

new Gregory('#kalendar', {
  mode: 'range',
  inline: true,
  summary: true,
})
// „10. 8. 2026 – 16. 8. 2026 · 7 dní"

Řádek sleduje rozpracovaný výběr, ne potvrzenou hodnotu — mění se hned při klikání. Prázdný stav hlásí „Nic nevybráno" a nese třídu .gr-summary.is-empty. Otevřený rozsah se píše jako od 10. 8. 2026.

Počet dní se skloňuje přes Intl.PluralRules, takže česky vyjde 1 den, 2 dny i 7 dní správně. Skloňování vlastního jazyka jde dodat přes locale.formatDayCount.

Vlastní znění nahradí to vestavěné:

summary: (value) => value?.to ? `Rezervace na ${dny(value)} dní` : 'Vyber odjezd'

Text je dostupný i programově přes picker.summaryText().

dayBadge

Pod číslo dne se vejde krátká značka z vlastních dat — kolik je ten den rezervací, jestli je někdo na směně, že tam něco je:

dayBadge: (date) => {
  const count = rezervace.get(formatISODate(date))
  return count ? String(count) : null
}

null značku nevykreslí, prázdný řetězec vykreslí jen tečku — na „tady něco je" bez čísla. Volá se i pro dny přesahující ze sousedních měsíců (stejně jako dayClass); jejich značka zdědí ztlumení z .is-outside. Kdo je nechce, vrátí pro ně null.

Dny v takové mřížce se přepnou na sloupec — číslo nahoře, značka pod ním. Řádek pro značku si drží i den, který žádnou nedostal, takže čísla v řádku sedí na jedné lince. Značka nereaguje na myš, den zůstává klikatelný celý. Stylovat jde přes .gr-day-badge, den se značkou nese .has-badge.

Do výchozího čtverce 24 × 24 px se číslo se značkou vejde jen tak tak. Když má být značka čitelnější, zvyš jen výšku buňky — šířku sloupců drží --gr-day-size, takže kalendář zůstane stejně široký:

.gr {
  --gr-day-height: 38px;
  --gr-day-badge-gap: 3px;
  --gr-day-badge-size: 11px;
}

--gr-day-badge-size je zároveň velikost písma ve značce a výška jejího řádku — tou se řídí i prázdné místo pod dny bez značky, takže se čísla nikdy nerozejdou.

format

Přebíjí text zapsaný do inputu. Dostane potvrzenou hodnotu a rozřešené locale.

format: (value, locale) => {
  if (!value || value instanceof Date) return ''
  return `od ${locale.formatDate(value.from, false)}`
}

Hodnoty a formáty

Všude, kde se dá zadat datum, přijímá Gregory typ DateLike:

VstupChování
DatePoužije se kopie, neplatné datum dá null.
'2026-08-13'Parsuje se jako lokální půlnoc.
'2026-08-13T14:30'Volitelná časová část, sekundy se ignorují.
'2026-08-01/2026-08-13'V range režimech rozsah zapsaný lomítkem.
numberUnixové milisekundy.
null, undefined, ''Prázdná hodnota.
nesmysl ('2026-02-31')null — datum se nepřetáčí na březen.
Proč se ISO řetězce parsují ručně

new Date('2026-08-13') je podle specifikace UTC půlnoc, takže v našem časovém pásmu vyjde 13. srpna 02:00 — ale kdekoli západně od Greenwiche to spadne na 12. srpna. Gregory proto ISO tvary rozebírá sám a staví lokální datum.

Metody

getValue()

getValue(): Date | DateRange | null

Potvrzená hodnota. V single režimech Date (nebo null), v range režimech vždy objekt { from, to }, jehož položky mohou být null.

getSelection()

getSelection(): DateRange

Rozpracovaný výběr včetně stavu „mám první konec, čekám na druhý". Užitečné při autoApply: false.

getCompare()

getCompare(): { from: Date; to: Date } | null

Srovnávací období k potvrzené hodnotě. null, když se neporovnává, rozsah nemá oba konce, nebo picker není v režimu rozsahu. Oba konce jsou vždy vyplněné — otevřený rozsah se neporovnává.

setValue()

setValue(value: DateLike | DateRange | [DateLike, DateLike], options?: { silent?: boolean }): void

Nastaví a rovnou potvrdí hodnotu, přesune pohled na příslušný měsíc a zapíše do inputu. Se silent: true nevyvolá change ani apply.

clear()

clear(options?: { silent?: boolean }): void

Vyprázdní hodnotu i input.

setOptions()

setOptions(patch: GregoryOptions): void

Sloučí nové volby se stávajícími a překreslí. Hodí se na přepnutí jazyka nebo rozšíření min/max za běhu.

goTo()

goTo(date: DateLike): void

Přesune pohled tak, aby zadané datum bylo v prvním viditelném měsíci. Výběr nemění. Vyvolá month-change.

openPanel(), close(), toggle()

openPanel(): void close(): void toggle(): void

Ovládání popoveru. V inline režimu nedělají nic — panel je vždy viditelný. Otevření obnoví panel do stavu poslední potvrzené hodnoty.

apply(), cancel()

apply(): void cancel(): void

Programová obdoba tlačítek v patičce. apply() povýší výběr na hodnotu a zavře, cancel() výběr zahodí a vrátí poslední potvrzený.

formatValue()

formatValue(): string

Text, který by se zapsal do inputu — buď z format, nebo z locale.

on(), once(), off()

on(event, listener): () => void once(event, listener): () => void off(event, listener): void

on() i once() vracejí funkci pro odhlášení, takže nemusíš držet referenci na posluchače.

const odhlasit = picker.on('change', handler)
odhlasit()

destroy()

destroy(): void

Odstraní panel z DOM, odpojí posluchače na inputu i na dokumentu a zahodí všechny odběratele událostí. Volat při odstranění komponenty ze stránky.

element

readonly element: HTMLElement

Kořenový prvek panelu. Existuje i když je panel zavřený (má hidden).

Události

UdálostPayloadKdy
change{ value, complete, compare }Při každém kliknutí na den, i na první konec rozsahu. value je rozpracovaný výběr, ne potvrzená hodnota.
apply{ value, compare }Při potvrzení — tlačítkem, nebo automaticky při autoApply.
cancel{ value }Zrušit, Escape nebo kliknutí mimo panel.
open{ value }Otevření popoveru.
close{ value }Zavření popoveru.
invalid{ value, reason }Hodnota neprošla omezeními a picker ji nepřijal. reason je 'min' | 'max' | 'disabled' | 'maxSpan' | 'minSpan' | 'unreadable'.
month-change{ year, month, index }Změna zobrazeného měsíce. month je 0–11, index je pořadí panelu.
Na co se navázat

Na apply, pokud tě zajímá jen hotový výsledek — v range režimu se change ozve i s polovičním rozsahem. Příznak complete říká, jestli je hodnota kompletní.

compare je srovnávací období (viz volba compare), nebo null — bez té volby vždycky. V change se počítá z rozpracovaného výběru, takže po prvním kliknutí je ještě null.

Pozor na rozdíl mezi událostmi: change nese rozpracovaný výběr (totéž co getSelection()), zatímco apply, cancel, open a close nesou potvrzenou hodnotu (totéž co getValue()). Při zapnutém autoApply je to jedno a totéž.

Custom element

Registrace (bezpečně opakovatelná, volitelně pod jiným jménem):

defineElement(tagName = 'gregory-picker'): void

Element si vyrobí vnitřní <input>, nebo převezme ten, který do něj vložíš.

Atributy

AtributOdpovídá volbě
modemode
valuevalue — u rozsahu od/do
localelocale
min, maxmin, max
monthsmonths
max-spanmaxSpan
min-spanminSpan
stop-at-disabledstopAtDisabled
disableddisabled
first-day-of-weekfirstDayOfWeek
placeholderplaceholder vnitřního inputu
inlineinline (přítomnost atributu)
week-numbersweekNumbers
dropdownsdropdowns
auto-applyautoApply
presetspresets; presets="false" je skryje
comparecompare; atribut bez hodnoty znamená 'previous', chybějící atribut porovnávání vypíná

Všechny uvedené atributy jsou sledované — změna za běhu se projeví okamžitě.

Vlastnosti a události

element.value čte i zapisuje hodnotu stejně jako getValue() / setValue(). Po potvrzení se hodnota zrcadlí zpět do atributu value.

Události se přeposílají jako bublající CustomEvent s prefixem: gregory:change, gregory:apply, gregory:open, gregory:close. Data jsou v event.detail.

document.addEventListener('gregory:apply', (e) => {
  console.log(e.target.id, e.detail.value)
})

Lokalizace

Vše jde přes Intl — knihovna nenese žádné jazykové balíčky. Stačí tag:

new Gregory('#vstup', { locale: 'de-AT' })

Neplatný tag picker neshodí — degraduje na angličtinu.

Co jde z Intl a co ne

Názvy měsíců, zkratky dnů, první den v týdnu i formát data řeší prohlížeč, takže fungují pro jakýkoli jazyk. Slova jako „Použít" nebo „Posledních 7 dní" ale odnikud vzít nejdou — ty knihovna nese sama a má je pro:

csskde plenes frit

Rozhoduje jazyk, ne region — de-AT i de-CH dostanou němčinu. Pro ostatní jazyky se měsíce a formát data lokalizují dál, ale popisky spadnou na angličtinu. Seznam vrací availableTranslations().

Doplnění jazyka

Chybějící jazyk se dodá zvenčí, není potřeba forkovat:

import { registerTranslation } from '@svatekr70/gregory'

registerTranslation('ja', {
  labels: { apply: '適用', cancel: 'キャンセル', today: '今日', now: '現在', /* … */ },
  presets: { today: '今日', yesterday: '昨日', /* … */ },
  days: { other: '日' },
})

Volá se před vytvořením pickeru a přepíše i jazyk, který knihovna nese. Tvary v days se vybírají přes Intl.PluralRules, takže čeština potřebuje one, few, many i other, kdežto němčině stačí one a other.

Na jednorázovou úpravu jednoho popisku stačí objekt v locale, viz níž — registerTranslation je pro celý jazyk napříč aplikací.

Přebití částí locale

Objekt se sloučí přes rozřešené locale, takže můžeš změnit jedinou položku:

locale: {
  code: 'cs',
  rangeSeparator: ' až ',
  labels: { apply: 'OK' },
}

rangeSeparator platí pro obě podoby rozsahu — plnou i zkrácenou se společným měsícem — a zároveň se podle něj rozsah čte zpátky, když ho někdo napíše do pole.

Rozhraní Locale

PoložkaTypPopis
codestringBCP 47 tag.
firstDayOfWeek0–6Z Intl.Locale, s tabulkovou zálohou.
monthLabel(date)stringTitulek panelu, např. „srpen 2026".
monthNames()string[]12 názvů měsíců pro dropdown.
weekdayNames(first)string[]7 zkratek, už otočených.
formatDate(date, withTime)stringText do inputu.
rangeSeparatorstringOddělovač konců rozsahu.
labelsobjectPopisky tlačítek a ARIA. Mimo jiné today a now — druhý se použije v režimech s časem.

Přímé rozřešení mimo picker: resolveLocale(input?).

Presety

V range režimech se vlevo zobrazí zkratky. Vestavěná sada: Dnes, Včera, Posledních 7 dní, Posledních 30 dní, Tento měsíc, Minulý měsíc, Letos.

interface RangePreset { label: string range: () => [DateLike, DateLike] }

range je funkce, ne hodnota — vyhodnotí se až při kliknutí. Díky tomu picker otevřený přes půlnoc nevrátí včerejší „dnes".

presets: [
  { label: 'Tento týden', range: () => [pondeli(), nedele()] },
  { label: 'Q1', range: () => [new Date(2026, 0, 1), new Date(2026, 3, 0)] },
]

presets: false panel skryje, presets: true vynutí vestavěné i v single režimu. Vestavěnou sadu si můžeš vyžádat i sám: defaultPresets(locale).

Datové utility

Knihovna exportuje i funkce, které používá vnitřně. Všechny pracují v lokálním čase a porovnávají na úrovni dne.

FunkcePopis
today()Dnešek na půlnoci.
startOfDay(date)Ořízne čas.
createDate(y, m, d, h?, min?)Bezpečné sestavení data (řeší roky 0–99).
parseDate(value)DateLikeDate | null.
formatISODate(date)'2026-08-13'
formatISOTime(date)'14:30'
addDays(date, n)Posun o dny.
addMonths(date, n)Posun o měsíce s ořezem dne.
isSameDay(a, b)Stejný kalendářní den.
compareDay(a, b)-1 | 0 | 1, ignoruje čas.
isWithinDay(d, from, to)Inkluzivní na obou koncích.
startOfWeek(date, first)Začátek týdne dle prvního dne.
isoWeekNumber(date)Číslo týdne podle ISO 8601.
parseTimeOfDay(value)'08:30' → 510 minut od půlnoci.
formatTimeOfDay(minutes)510 → '08:30'.
minutesOfDay(date)Čas data jako minuty od půlnoci.
withTimeOfDay(date, minutes)Přenese čas na kalendářní den.
hourOptions(step, min, max)Použitelné hodiny v okně.
minuteOptions(hour, step, min, max)Použitelné minuty dané hodiny.
comparePeriod(range, kind?)Srovnávací období k rozsahu; kind je 'previous' (výchozí), 'year' nebo 'year-weekday'.
wholePeriodOf(range)Odpovídá rozsah přesně celému měsíci, čtvrtletí, nebo roku? 'month' | 'quarter' | 'year' | null.
resolveCompare(range, compare)Vyhodnotí volbu compare v její plné podobě, včetně vlastní funkce.
buildMonth(year, month, ctx)Mřížka měsíce bez DOM — pro vlastní vykreslení.
import { comparePeriod } from '@svatekr70/gregory'

comparePeriod({ from: new Date(2026, 1, 1), to: new Date(2026, 1, 28) })
// { from: 1. 1. 2026, to: 31. 1. 2026 } — celý měsíc, ne posledních 28 dní

CSS proměnné

Motiv se ladí výhradně proměnnými na .gr. Není potřeba přepisovat jediný selektor.

ProměnnáVýchozí (světlá)Význam
--gr-fontsystem-ui…Písmo panelu.
--gr-radius10pxZaoblení panelu.
--gr-radius-day6pxZaoblení dne a tlačítek.
--gr-gap2pxMezera v mřížce.
--gr-day-size24pxVelikost políčka dne — čtverce, do kterého se vykreslí číslo. Určuje šířku celého panelu, takže je to hlavní páka na hustotu.
--gr-day-heightvar(--gr-day-size)Výška políčka dne. Ve výchozím stavu kopíruje šířku, takže den je čtverec. Zvýšit se dá zvlášť — kalendář kvůli tomu nenaroste do šířky.
--gr-day-badge-gap2pxMezera mezi číslem dne a značkou pod ním (dayBadge).
--gr-day-badge-size9pxPísmo ve značce a výška jejího řádku. Řádek drží i dny bez značky, aby čísla v řádku seděla.
--gr-pad10pxOdsazení uvnitř panelu. Odvozuje se z něj i odsazení patičky, zkratek a tlačítek.
--gr-font-size14pxPísmo zbytku panelu — hlavička, tlačítka, souhrn. Čísel dnů se netýká.
--gr-day-font-size50 % políčkaVelikost čísel dnů. Odvozuje se z políčka, ne ze základního písma.
--gr-weekday-font-size11pxZkratky dnů v týdnu (Po, Út…) a čísla týdnů.
--gr-bg#fffPozadí panelu.
--gr-fg#1c1f23Text.
--gr-muted#8b9199Sekundární text.
--gr-border#e3e6eaLinky.
--gr-hover#f1f3f5Pozadí pod myší.
--gr-accent#2f6fedVybraný den, primární tlačítko.
--gr-accent-fg#fffText na akcentu.
--gr-range-bg#e7efffVýplň mezi konci rozsahu.
--gr-today#e8590cObrys dnešku.
--gr-compare#7048e8Pruh pod dny srovnávacího období (compare). Třetí barva schválně — musí být rozeznatelná od výběru i od dneška.
--gr-compare-bar2pxTloušťka toho pruhu.
--gr-shadowStín popoveru.
.gr {
  --gr-accent: #0f766e;
  --gr-range-bg: #ccfbf1;
  --gr-radius-day: 999px;
}

Panel je vždy jen tak široký, jak potřebuje — šířku určuje --gr-day-size, ne dostupné místo. Kompaktnější kalendář se tedy dělá zmenšením políčka dne, ne přebíjením rozměrů panelu:

Hustotu drží čtyři proměnné: velikost dne, mezera v mřížce, odsazení a velikost písma. Ostatní odsazení se z --gr-pad dopočítává, takže stačí přenastavit je:

/* hustší než výchozí */
.gr {
  --gr-day-size: 21px;
  --gr-gap: 0;
  --gr-pad: 7px;
  --gr-font-size: 13px;
}

/* jen větší čísla dnů, zbytek beze změny */
.gr { --gr-day-font-size: 16px; }
Panel v úzkém místě

Do kontejneru užšího, než je jeden měsíc, se panel nevejde — a protože mřížka má pevnou velikost políčka, není co zmenšovat. Takový panel se proto odroluje vodorovně, ne ořízne: poslední sloupce dnů zůstanou dosažitelné. Nejmenší rozumná šířka je zhruba 7 × --gr-day-size + 2 × --gr-pad, u dvou měsíců dvojnásobek a u zapnutých presets ještě postranní panel navrch.

Hotové stupně hustoty

themes.css nese třídy gr-density-compact a gr-density-comfortable. Barvy neřeší, takže se s motivy kombinují: className: 'gr-theme-riso gr-density-compact'.

Výchozí hustota je kompaktní — jeden měsíc vyjde na 196 × 268 px, tedy znatelně méně než nativní kalendář prohlížeče. Rozsah se dvěma měsíci a čísly týdnů má 443 × 219 px, v compact 379 × 188 px a v comfortable 622 × 302 px.

Pro dotyk je výchozí políčko malé

24 px je pod doporučenými 44 px pro dotykové cíle. Na mobilu tedy buď zvyš --gr-day-size, nebo použij gr-density-comfortable.

Vyšší den bez širšího kalendáře

Šířku sloupců řídí --gr-day-size, výšku buňky --gr-day-height — a ta z první ve výchozím stavu vychází, takže den je čtverec. Kdo pod čísla vykresluje značky (dayBadge), zvýší jen --gr-day-height a panel si nechá stejně široký.

Prázdno okolo číslic je poměr, ne odsazení

Den nemá žádný vnitřní padding — číslice je vycentrovaná ve čtverci o hraně --gr-day-size. Kolik okolo ní zbude místa, tedy určuje poměr písma k políčku. Zmenšit políčko a zároveň zmenšit písmo proto prázdno nezmenší.

Proto se --gr-day-font-size odvozuje z velikosti políčka (50 %) a ne ze základního písma. Plnější dojem se dělá vyšším poměrem, vzdušnější nižším:

/* plnější */
.gr { --gr-day-font-size: calc(var(--gr-day-size) * 0.6); }

/* vzdušnější */
.gr { --gr-day-font-size: calc(var(--gr-day-size) * 0.42); }

Pozor jen na to, že buňka má pevný rozměr — poměr nad zhruba 0,6 už z ní dvojciferná čísla vytlačí.

Hotové motivy

Volitelný soubor themes.css nese čtyři připravené motivy. Načítá se až po základních stylech:

import '@svatekr70/gregory/style.css'
import '@svatekr70/gregory/themes.css'

new Gregory('#vstup', { className: 'gr-theme-riso' })
TřídaCharakter
gr-theme-blueprintTechnický výkres — tmavě modrá, monospace číslice, hustá mřížka.
gr-theme-risoDvoubarevný tisk — papír, fluorescentní růžová, posunutý stín místo rozostřeného.
gr-theme-clinicObjednávkový systém — vzdušná bílá, modrozelená, vybrané dny jako pilulky.
gr-theme-nocturneNoční provoz — skoro černá s teplým jantarem.

Motivy nepřepisují jediný selektor komponenty — mění jen proměnné --gr-*. Vlastní motiv se dělá stejně: zkopíruj kterýkoli z nich a přebarvi. Posunutý stín u riso je vidět jen u popoveru; inline panel stín zásadně nemá, protože sedí uvnitř cizího rámečku.

className

Panel si vyrábí knihovna sama a v režimu popoveru ho věší na <body>, takže se k němu nedá dostat přes obalový prvek. className je proto jediná cesta, jak mu přidat vlastní třídu — motivy toho využívají, ale hodí se na jakékoli cílení.

Tmavý motiv naskočí sám podle prefers-color-scheme. Natvrdo se přepíná atributem na kořenovém prvku pickeru: data-theme="dark" nebo data-theme="light".

CSS třídy

TřídaPrvek
.grKořen panelu. Nese data-mode, data-inline / data-popover.
.gr-presets, .gr-presetPostranní panel a jeho tlačítka.
.gr-calendars, .gr-calendarObal měsíců a jeden měsíc.
.gr-head, .gr-nav, .gr-captionHlavička s šipkami a titulkem.
.gr-selectNativní dropdown měsíce a roku (dropdowns: true).
.gr-caption-btnMěsíc a rok jako tlačítka (dropdowns: 'menu').
.gr-menu, .gr-menu-itemVyrolovaný seznam; aktuální položka má .is-current.
.gr-gridMřížka dnů, role="grid".
.gr-weekday, .gr-weeknumZáhlaví dnů a sloupec čísel týdnů.
.gr-weeknum-buttonČíslo týdne při zapnutém selectableWeeks.
.gr-dayJeden den. Stavy: .is-outside, .is-today, .is-selected, .is-in-range, .is-start, .is-end, .is-weekend, .is-compare se .is-compare-start a .is-compare-end.
.gr-foot, .gr-timesPatička a blok s časem.
.gr-time-group, .gr-time-labelJeden konec rozsahu a jeho popisek (nese data-bound).
.gr-time, .gr-time-select, .gr-time-sepObal času, selecty hodin/minut a dvojtečka mezi nimi.
.gr-time-sliders, .gr-slider, .gr-time-readoutPosuvníky a vypsaný čas nad nimi.
.gr-summaryInformační řádek; prázdný stav má .is-empty.
.gr-actions, .gr-btnTlačítka Vymazat / Zrušit / Použít.

Klávesnice

KlávesaAkce
na inputu, EnterOtevře panel.
O den zpět / vpřed.
O týden zpět / vpřed.
PageUp PageDownO měsíc zpět / vpřed.
Enter, MezerníkVybere zaměřený den.
EscZruší rozpracovaný výběr, zavře panel a vrátí fokus do inputu.

Pohyb fokusu respektuje min a max a sám odroluje na měsíc, do kterého fokus utekl.

TypeScript typy

Exportované typy: Mode, WeekDay, DateLike, DateRange, GregoryValue, GregoryOptions, ResolvedOptions, GregoryEvents, Locale, LocaleInput, RangePreset, DayCell, WeekRow, MonthView, MonthContext, ClosedDateRange, CompareKind, CompareInput, CompareResolver, WholePeriod.

import type { DateRange, GregoryOptions } from '@svatekr70/gregory'

const volby: GregoryOptions = { mode: 'range', maxSpan: 31 }

Posluchače událostí typuje GregoryEvents, takže payload zná i editor:

picker.on('month-change', ({ year, month }) => { /* number, number */ })

Okrajové chování

Inkluzivní konce

Rozsah 10.–12. srpna jsou tři dny. Platí to i pro maxSpan a pro isWithinDay().

Pozpátku vybraný rozsah

Když uživatel klikne nejdřív na pozdější den, Gregory konce sám otočí — from je vždy dřívější.

Přetečení měsíce

addMonths(31. ledna, 1) je 28. února (v přestupném roce 29.), ne 2. nebo 3. března.

Celek se porovnává s celkem

Výběr, který přesně odpovídá kalendářnímu měsíci, čtvrtletí nebo roku, má u compare: 'previous' za protějšek celý předchozí celek, ne posledních n dní. Tři měsíce, které nesedí na hranice čtvrtletí, jsou pořád jen úsek dnů.

Čas při změně dne

V datetime režimech se čas váže ke konci rozsahu a přežije překliknutí na jiný den.

Stálá výška panelu

Každý měsíc se vykresluje na šest řádků, takže popover při listování neposkakuje. Přebytečné dny mají .is-outside.

Vykreslování

Panel se při změně stavu staví znovu přes replaceChildren(). Žádný diffing — u 42 buněk na měsíc je to rychlejší než ho udržovat. Výjimkou je náhled pod myší, který jen přepíná třídy na existujících tlačítkách: přestavět DOM pod kurzorem znamená ztracené kliknutí, protože prohlížeč vyvolá click jen tehdy, když stisk i puštění skončí na tomtéž prvku.

Odolnost vůči stylům stránky

Panel používá běžné značky (section, header, footer, aside, button…), takže si je hned na začátku vynuluje pravidlem s nulovou specificitou přes :where(). Nuluje se typografie i rozvržení: globální section { padding: 52px 0 } ani header { flex-direction: column } panel nerozhodí — to druhé jinak postaví šipky nad a pod název měsíce, protože .gr-head si sám nastavuje jen display: flex. Přitom pořád stačí přepsat kteroukoli vlastnost bez souboje o specificitu.

Dvě vlastnosti se schválně nenulují: display, protože přepsat ho pro všechny značky najednou by rozbilo tlačítka i inputy, a text-align, protože čísla dnů mají vycentrování od prohlížeče.