1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace Atk4\Ui\Form\Control;
6:
7: use Atk4\Ui\Js\Jquery;
8: use Atk4\Ui\Js\JsBlock;
9: use Atk4\Ui\Js\JsChain;
10: use Atk4\Ui\Js\JsExpression;
11: use Atk4\Ui\Js\JsExpressionable;
12: use Atk4\Ui\Js\JsFunction;
13:
14: class Calendar extends Input
15: {
16: public string $inputType = 'text';
17:
18: /**
19: * @var 'date'|'time'|'datetime'
20: */
21: public string $type = 'date';
22:
23: /**
24: * Any other options you'd like to pass to Flatpickr JS.
25: * See https://flatpickr.js.org/options/ for all possible options.
26: */
27: public array $options = [];
28:
29: /**
30: * Set Flatpickr option.
31: *
32: * @param mixed $value
33: */
34: public function setOption(string $name, $value): void
35: {
36: $this->options[$name] = $value;
37: }
38:
39: #[\Override]
40: protected function init(): void
41: {
42: parent::init();
43:
44: $this->options['allowInput'] ??= true;
45:
46: // setup format
47: $phpFormat = $this->getApp()->uiPersistence->{$this->type . 'Format'};
48: $this->options['dateFormat'] = $this->convertPhpDtFormatToFlatpickr($phpFormat, true);
49: if ($this->type === 'datetime' || $this->type === 'time') {
50: $this->options['noCalendar'] = $this->type === 'time';
51: $this->options['enableTime'] = true;
52: $this->options['time_24hr'] = $this->isDtFormatWith24hrTime($phpFormat);
53: $this->options['enableSeconds'] ??= $this->isDtFormatWithSeconds($phpFormat);
54: $this->options['formatSecondsPrecision'] ??= $this->isDtFormatWithMicroseconds($phpFormat) ? 6 : -1;
55: $this->options['disableMobile'] = true;
56: if (!$this->options['enableSeconds']) {
57: $this->options['formatDate'] = new JsFunction(
58: ['date', 'format', 'locale', 'formatSecondsPrecision'],
59: [new JsExpression('return flatpickr.formatDate(date, format, locale, formatSecondsPrecision).replace(/: ?0+(?! ?\.)(?=(?: |$))/, \'\');')]
60: );
61: }
62: }
63:
64: // setup locale
65: $this->options['locale'] = [
66: 'firstDayOfWeek' => $this->getApp()->uiPersistence->firstDayOfWeek,
67: ];
68: }
69:
70: #[\Override]
71: protected function renderView(): void
72: {
73: if ($this->readOnly) {
74: $this->options['clickOpens'] = false;
75: }
76:
77: $this->jsInput(true)->flatpickr($this->options);
78:
79: parent::renderView();
80: }
81:
82: /**
83: * @param JsExpressionable $expr
84: */
85: #[\Override]
86: public function onChange($expr, $default = []): void
87: {
88: if (!$expr instanceof JsBlock) {
89: $expr = [$expr];
90: }
91:
92: $this->options['onChange'] = new JsFunction(['date', 'text', 'mode'], $expr);
93: }
94:
95: /**
96: * Get the FlatPickr instance of this input in order to
97: * get it's properties like selectedDates or run it's methods.
98: * Ex: clearing date via JS
99: * $button->on('click', $f->getControl('date')->getJsInstance()->clear());.
100: *
101: * @return JsChain
102: */
103: public function getJsInstance(): JsExpressionable
104: {
105: return (new Jquery('#' . $this->name . '_input'))->get(0)->_flatpickr;
106: }
107:
108: private function expandPhpDtFormat(string $phpFormat): string
109: {
110: $phpFormat = str_replace('c', \DateTimeInterface::ISO8601, $phpFormat);
111: $phpFormat = str_replace('r', \DateTimeInterface::RFC2822, $phpFormat);
112:
113: return $phpFormat;
114: }
115:
116: public function convertPhpDtFormatToFlatpickr(string $phpFormat, bool $enforceSeconds): string
117: {
118: if ($enforceSeconds) {
119: $phpFormat = preg_replace('~: ?i\K(?!\w| ?: ?s)~', ':s', $phpFormat);
120: }
121:
122: $res = $this->expandPhpDtFormat($phpFormat);
123: foreach ([
124: '~[aA]~' => 'K',
125: '~[s]~' => 'S',
126: '~[g]~' => 'G',
127: ] as $k => $v) {
128: $res = preg_replace($k, $v, $res);
129: }
130:
131: return $res;
132: }
133:
134: public function isDtFormatWith24hrTime(string $phpFormat): bool
135: {
136: return !preg_match('~[gh]~', $this->expandPhpDtFormat($phpFormat));
137: }
138:
139: public function isDtFormatWithSeconds(string $phpFormat): bool
140: {
141: return (bool) preg_match('~[suv]~', $this->expandPhpDtFormat($phpFormat));
142: }
143:
144: public function isDtFormatWithMicroseconds(string $phpFormat): bool
145: {
146: return (bool) preg_match('~[uv]~', $this->expandPhpDtFormat($phpFormat));
147: }
148: }
149: