@kopexa/date-picker 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -139,6 +139,55 @@ function XIcon({ className }) {
139
139
  }
140
140
  );
141
141
  }
142
+ function getDatePartOrder(locale) {
143
+ try {
144
+ const fmt = new Intl.DateTimeFormat(locale, {
145
+ day: "2-digit",
146
+ month: "2-digit",
147
+ year: "numeric"
148
+ });
149
+ return fmt.formatToParts(new Date(2024, 0, 1)).filter(
150
+ (p) => p.type === "day" || p.type === "month" || p.type === "year"
151
+ ).map((p) => p.type);
152
+ } catch {
153
+ return ["day", "month", "year"];
154
+ }
155
+ }
156
+ function finalizeDateParts(parts) {
157
+ var _a, _b, _c;
158
+ const day = (_a = parts.day) != null ? _a : 1;
159
+ const month = (_b = parts.month) != null ? _b : 1;
160
+ let year = (_c = parts.year) != null ? _c : (/* @__PURE__ */ new Date()).getFullYear();
161
+ if (year < 100) year = year < 50 ? 2e3 + year : 1900 + year;
162
+ if (month < 1 || month > 12) return void 0;
163
+ if (day < 1 || day > 31) return void 0;
164
+ return { year, month, day };
165
+ }
166
+ function parseLocalizedDate(value, locale) {
167
+ const trimmed = value.trim();
168
+ if (!trimmed) return void 0;
169
+ const order = getDatePartOrder(locale);
170
+ const segments = trimmed.split(/[./\-\s]+/).filter(Boolean);
171
+ if (segments.length === 3 && segments.every((s) => /^\d+$/.test(s))) {
172
+ const map2 = {};
173
+ order.forEach((field, i) => {
174
+ map2[field] = Number.parseInt(segments[i], 10);
175
+ });
176
+ return finalizeDateParts(map2);
177
+ }
178
+ const digits = trimmed.replace(/\D/g, "");
179
+ const widths = digits.length === 8 ? { day: 2, month: 2, year: 4 } : digits.length === 6 ? { day: 2, month: 2, year: 2 } : digits.length === 4 ? { day: 2, month: 2, year: 0 } : null;
180
+ if (!widths) return void 0;
181
+ let pos = 0;
182
+ const map = {};
183
+ for (const field of order) {
184
+ const w = widths[field];
185
+ if (w === 0) continue;
186
+ map[field] = Number.parseInt(digits.slice(pos, pos + w), 10);
187
+ pos += w;
188
+ }
189
+ return finalizeDateParts(map);
190
+ }
142
191
  var styles = {
143
192
  control: "relative flex items-center",
144
193
  input: "w-full h-9 rounded-md border bg-transparent pl-3 pr-9 text-sm outline-none focus:ring-2 focus:ring-ring",
@@ -164,7 +213,11 @@ var styles = {
164
213
  footerButton: "text-sm px-2 py-1 rounded-md hover:bg-muted transition-colors",
165
214
  timeInput: "h-9 rounded-md border bg-transparent px-3 text-sm outline-none focus:ring-2 focus:ring-ring",
166
215
  label: "text-sm font-medium",
167
- timeTrigger: "flex-1 h-9 rounded-md border bg-transparent px-3 text-sm text-left hover:bg-muted transition-colors flex items-center justify-between"
216
+ timeTrigger: "flex-1 h-9 rounded-md border bg-transparent px-3 text-sm text-left hover:bg-muted transition-colors flex items-center justify-between",
217
+ defaultGhostTrigger: "inline-flex items-center gap-2 h-9 rounded-md px-2.5 text-sm font-normal text-foreground transition-colors hover:bg-muted focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[placeholder]:text-muted-foreground",
218
+ defaultGhostPlaceholder: "text-muted-foreground",
219
+ timeRow: "mt-2 pt-2 border-t flex items-center gap-2",
220
+ timeRowLabel: "text-xs text-muted-foreground"
168
221
  };
169
222
  function DayView() {
170
223
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker.DatePickerView, { view: "day", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker.DatePickerContext, { children: (api) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
@@ -269,9 +322,17 @@ function YearView() {
269
322
  )) }) })
270
323
  ] }) }) });
271
324
  }
325
+ function CalendarPanel() {
326
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
327
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DayView, {}),
328
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MonthView, {}),
329
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(YearView, {})
330
+ ] });
331
+ }
272
332
  function CalendarFooter({
273
333
  todayLabel,
274
- clearLabel
334
+ clearLabel,
335
+ clearable = true
275
336
  }) {
276
337
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker.DatePickerContext, { children: (api) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: styles.footer, children: [
277
338
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -283,7 +344,7 @@ function CalendarFooter({
283
344
  children: todayLabel
284
345
  }
285
346
  ),
286
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
347
+ clearable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
287
348
  "button",
288
349
  {
289
350
  type: "button",
@@ -294,6 +355,34 @@ function CalendarFooter({
294
355
  )
295
356
  ] }) });
296
357
  }
358
+ function DateTimeFooter({
359
+ todayLabel,
360
+ clearLabel,
361
+ clearable = true,
362
+ onSelectNow,
363
+ onClear
364
+ }) {
365
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: styles.footer, children: [
366
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
367
+ "button",
368
+ {
369
+ type: "button",
370
+ onClick: onSelectNow,
371
+ className: `${styles.footerButton} text-foreground`,
372
+ children: todayLabel
373
+ }
374
+ ),
375
+ clearable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
376
+ "button",
377
+ {
378
+ type: "button",
379
+ onClick: onClear,
380
+ className: `${styles.footerButton} text-destructive`,
381
+ children: clearLabel
382
+ }
383
+ )
384
+ ] });
385
+ }
297
386
  function DatePickerField({
298
387
  label,
299
388
  value,
@@ -310,7 +399,10 @@ function DatePickerField({
310
399
  todayLabel: todayLabelProp,
311
400
  clearLabel: clearLabelProp,
312
401
  className,
313
- rootProps
402
+ rootProps,
403
+ variant = "input",
404
+ trigger,
405
+ formatValue
314
406
  }) {
315
407
  var _a;
316
408
  const intl = (0, import_i18n2.useSafeIntl)();
@@ -320,6 +412,31 @@ function DatePickerField({
320
412
  const placeholder = placeholderProp != null ? placeholderProp : intl.formatMessage(
321
413
  showTime ? datePickerMessages.select_date_and_time : datePickerMessages.select_date
322
414
  );
415
+ if (variant === "trigger") {
416
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
417
+ DateTriggerPickerField,
418
+ {
419
+ label,
420
+ value,
421
+ defaultValue,
422
+ onValueChange,
423
+ showTime,
424
+ clearable,
425
+ locale,
426
+ min,
427
+ max,
428
+ disabled,
429
+ readOnly,
430
+ placeholder,
431
+ todayLabel,
432
+ clearLabel,
433
+ className,
434
+ rootProps,
435
+ trigger,
436
+ formatValue
437
+ }
438
+ );
439
+ }
323
440
  if (showTime) {
324
441
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
325
442
  DateTimePickerField,
@@ -356,6 +473,10 @@ function DatePickerField({
356
473
  selectionMode: "single",
357
474
  outsideDaySelectable: true,
358
475
  closeOnSelect: true,
476
+ parse: (input, details) => {
477
+ const parts = parseLocalizedDate(input, details.locale);
478
+ return parts ? new import_date.CalendarDate(parts.year, parts.month, parts.day) : void 0;
479
+ },
359
480
  className,
360
481
  ...rootProps,
361
482
  children: [
@@ -374,10 +495,15 @@ function DatePickerField({
374
495
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker.DatePickerTrigger, { className: styles.trigger, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CalendarIcon, { className: "size-4" }) })
375
496
  ] }),
376
497
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_portal.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker.DatePickerPositioner, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_date_picker.DatePickerContent, { className: styles.content, children: [
377
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DayView, {}),
378
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MonthView, {}),
379
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(YearView, {}),
380
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CalendarFooter, { todayLabel, clearLabel })
498
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CalendarPanel, {}),
499
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
500
+ CalendarFooter,
501
+ {
502
+ todayLabel,
503
+ clearLabel,
504
+ clearable
505
+ }
506
+ )
381
507
  ] }) }) })
382
508
  ]
383
509
  }
@@ -475,6 +601,22 @@ function DateTimePickerField({
475
601
  view: "day"
476
602
  });
477
603
  }, [onValueChange]);
604
+ const handleSelectNow = (0, import_react.useCallback)(() => {
605
+ const now = /* @__PURE__ */ new Date();
606
+ const nowValue = new import_date.CalendarDateTime(
607
+ now.getFullYear(),
608
+ now.getMonth() + 1,
609
+ now.getDate(),
610
+ now.getHours(),
611
+ now.getMinutes()
612
+ );
613
+ setInternalValue([nowValue]);
614
+ onValueChange == null ? void 0 : onValueChange({
615
+ value: [nowValue],
616
+ valueAsString: [nowValue.toString()],
617
+ view: "day"
618
+ });
619
+ }, [onValueChange]);
478
620
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
479
621
  import_date_picker.DatePickerRoot,
480
622
  {
@@ -488,13 +630,33 @@ function DateTimePickerField({
488
630
  selectionMode: "single",
489
631
  outsideDaySelectable: true,
490
632
  closeOnSelect: false,
633
+ parse: (input, details) => {
634
+ const parts = parseLocalizedDate(input, details.locale);
635
+ if (!parts) return void 0;
636
+ const prev = currentValue[0];
637
+ const hour = prev && "hour" in prev ? prev.hour : 0;
638
+ const minute = prev && "minute" in prev ? prev.minute : 0;
639
+ return new import_date.CalendarDateTime(
640
+ parts.year,
641
+ parts.month,
642
+ parts.day,
643
+ hour,
644
+ minute
645
+ );
646
+ },
491
647
  className,
492
648
  ...rootProps,
493
649
  children: [
494
650
  label && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker.DatePickerLabel, { className: styles.label, children: label }),
495
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_date_picker.DatePickerControl, { className: "flex items-center gap-1", children: [
651
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_date_picker.DatePickerControl, { className: "flex items-center", children: [
496
652
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative flex-1 flex items-center", children: [
497
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker.DatePickerInput, { className: styles.input, placeholder }),
653
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
654
+ import_date_picker.DatePickerInput,
655
+ {
656
+ className: "w-full h-9 rounded-l-md border border-r-0 bg-transparent pl-3 pr-9 text-sm outline-none focus:ring-2 focus:ring-ring",
657
+ placeholder
658
+ }
659
+ ),
498
660
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker.DatePickerTrigger, { className: styles.trigger, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CalendarIcon, { className: "size-4" }) })
499
661
  ] }),
500
662
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -505,23 +667,174 @@ function DateTimePickerField({
505
667
  onChange: handleTimeChange,
506
668
  disabled,
507
669
  readOnly,
508
- className: styles.timeInput
670
+ className: `h-9 border bg-transparent px-3 text-sm outline-none focus:ring-2 focus:ring-ring ${clearable && !disabled && !readOnly ? "border-r-0" : "rounded-r-md"}`
509
671
  }
510
672
  ),
511
673
  clearable && !disabled && !readOnly && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
512
- import_date_picker.DatePickerClearTrigger,
674
+ "button",
513
675
  {
514
- className: "inline-flex items-center justify-center size-9 rounded-md border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors",
676
+ type: "button",
677
+ className: "inline-flex items-center justify-center size-9 rounded-r-md border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors",
515
678
  onClick: handleClear,
516
679
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(XIcon, { className: "size-4" })
517
680
  }
518
681
  )
519
682
  ] }),
520
683
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_portal.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker.DatePickerPositioner, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_date_picker.DatePickerContent, { className: styles.content, children: [
521
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DayView, {}),
522
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MonthView, {}),
523
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(YearView, {}),
524
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CalendarFooter, { todayLabel, clearLabel })
684
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CalendarPanel, {}),
685
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
686
+ DateTimeFooter,
687
+ {
688
+ todayLabel,
689
+ clearLabel,
690
+ clearable,
691
+ onSelectNow: handleSelectNow,
692
+ onClear: handleClear
693
+ }
694
+ )
695
+ ] }) }) })
696
+ ]
697
+ }
698
+ );
699
+ }
700
+ function DateTriggerPickerField({
701
+ label,
702
+ value: valueProp,
703
+ defaultValue,
704
+ onValueChange,
705
+ showTime = false,
706
+ clearable = true,
707
+ locale: localeProp,
708
+ min,
709
+ max,
710
+ disabled,
711
+ readOnly,
712
+ placeholder: placeholderProp,
713
+ todayLabel: todayLabelProp,
714
+ clearLabel: clearLabelProp,
715
+ className,
716
+ rootProps,
717
+ trigger,
718
+ formatValue
719
+ }) {
720
+ var _a;
721
+ const intl = (0, import_i18n2.useSafeIntl)();
722
+ const locale = (_a = localeProp != null ? localeProp : intl.locale) != null ? _a : "en-US";
723
+ const todayLabel = todayLabelProp != null ? todayLabelProp : intl.formatMessage(datePickerMessages.today);
724
+ const clearLabel = clearLabelProp != null ? clearLabelProp : intl.formatMessage(datePickerMessages.clear);
725
+ const placeholder = placeholderProp != null ? placeholderProp : intl.formatMessage(
726
+ showTime ? datePickerMessages.select_date_and_time : datePickerMessages.select_date
727
+ );
728
+ const [internalValue, setInternalValue] = (0, import_react.useState)(
729
+ () => {
730
+ var _a2;
731
+ return (_a2 = valueProp != null ? valueProp : defaultValue) != null ? _a2 : [];
732
+ }
733
+ );
734
+ const currentValue = (0, import_react.useMemo)(() => {
735
+ if (valueProp === void 0) return internalValue;
736
+ return valueProp;
737
+ }, [valueProp, internalValue]);
738
+ const handleDateChange = (0, import_react.useCallback)(
739
+ (details) => {
740
+ const next = details.value[0];
741
+ if (!showTime || !next) {
742
+ setInternalValue(details.value);
743
+ onValueChange == null ? void 0 : onValueChange(details);
744
+ return;
745
+ }
746
+ const prev = currentValue[0];
747
+ const prevHour = prev && "hour" in prev ? prev.hour : 0;
748
+ const prevMinute = prev && "minute" in prev ? prev.minute : 0;
749
+ const merged = new import_date.CalendarDateTime(
750
+ next.year,
751
+ next.month,
752
+ next.day,
753
+ prevHour,
754
+ prevMinute
755
+ );
756
+ setInternalValue([merged]);
757
+ onValueChange == null ? void 0 : onValueChange({ ...details, value: [merged] });
758
+ },
759
+ [currentValue, onValueChange, showTime]
760
+ );
761
+ const handleTimeChange = (0, import_react.useCallback)(
762
+ (e) => {
763
+ const [hours, minutes] = e.currentTarget.value.split(":").map(Number);
764
+ const prev = currentValue[0];
765
+ const base = prev && "hour" in prev ? prev : prev ? new import_date.CalendarDateTime(prev.year, prev.month, prev.day, 0, 0) : (() => {
766
+ const now = /* @__PURE__ */ new Date();
767
+ return new import_date.CalendarDateTime(
768
+ now.getFullYear(),
769
+ now.getMonth() + 1,
770
+ now.getDate(),
771
+ 0,
772
+ 0
773
+ );
774
+ })();
775
+ const updated = base.set({ hour: hours, minute: minutes });
776
+ setInternalValue([updated]);
777
+ onValueChange == null ? void 0 : onValueChange({
778
+ value: [updated],
779
+ valueAsString: [updated.toString()],
780
+ view: "day"
781
+ });
782
+ },
783
+ [currentValue, onValueChange]
784
+ );
785
+ const formatter = (0, import_react.useMemo)(() => {
786
+ if (formatValue) return formatValue;
787
+ const fmt = new Intl.DateTimeFormat(
788
+ locale,
789
+ showTime ? { dateStyle: "medium", timeStyle: "short" } : { dateStyle: "medium" }
790
+ );
791
+ return (v) => fmt.format(v.toDate((0, import_date.getLocalTimeZone)()));
792
+ }, [formatValue, locale, showTime]);
793
+ const timeValue = currentValue[0] && "hour" in currentValue[0] ? `${String(currentValue[0].hour).padStart(2, "0")}:${String(currentValue[0].minute).padStart(2, "0")}` : "";
794
+ const triggerElement = trigger != null ? trigger : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: styles.defaultGhostTrigger, children: [
795
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CalendarIcon, { className: "size-4 shrink-0 opacity-70" }),
796
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker.DatePickerValueText, { placeholder, children: ({ value }) => formatter(value) })
797
+ ] });
798
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
799
+ import_date_picker.DatePickerRoot,
800
+ {
801
+ value: currentValue,
802
+ defaultValue,
803
+ onValueChange: handleDateChange,
804
+ locale,
805
+ min,
806
+ max,
807
+ disabled,
808
+ readOnly,
809
+ selectionMode: "single",
810
+ outsideDaySelectable: true,
811
+ closeOnSelect: !showTime,
812
+ className,
813
+ ...rootProps,
814
+ children: [
815
+ label && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker.DatePickerLabel, { className: styles.label, children: label }),
816
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker.DatePickerControl, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker.DatePickerTrigger, { asChild: true, children: triggerElement }) }),
817
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_portal.Portal, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_date_picker.DatePickerPositioner, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_date_picker.DatePickerContent, { className: styles.content, children: [
818
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CalendarPanel, {}),
819
+ showTime && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: styles.timeRow, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
820
+ "input",
821
+ {
822
+ type: "time",
823
+ value: timeValue,
824
+ onChange: handleTimeChange,
825
+ disabled,
826
+ readOnly,
827
+ className: styles.timeInput
828
+ }
829
+ ) }),
830
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
831
+ CalendarFooter,
832
+ {
833
+ todayLabel,
834
+ clearLabel,
835
+ clearable
836
+ }
837
+ )
525
838
  ] }) }) })
526
839
  ]
527
840
  }
@@ -2,7 +2,7 @@
2
2
  "use client";
3
3
  import {
4
4
  DatePickerField
5
- } from "./chunk-VYQ6BFFN.mjs";
5
+ } from "./chunk-RWJEVZ4B.mjs";
6
6
  import "./chunk-HPM5Y2V6.mjs";
7
7
  import "./chunk-C7GZJJIK.mjs";
8
8
  export {
package/dist/index.d.mts CHANGED
@@ -4,3 +4,4 @@ export { DatePickerField, DatePickerFieldProps } from './date-picker-field.mjs';
4
4
  export { datePickerMessages } from './date-picker-messages.mjs';
5
5
  export { DatePickerValueChangeDetails, DateValue, parseDate } from '@ark-ui/react/date-picker';
6
6
  import 'react/jsx-runtime';
7
+ import 'react';
package/dist/index.d.ts CHANGED
@@ -4,3 +4,4 @@ export { DatePickerField, DatePickerFieldProps } from './date-picker-field.js';
4
4
  export { datePickerMessages } from './date-picker-messages.js';
5
5
  export { DatePickerValueChangeDetails, DateValue, parseDate } from '@ark-ui/react/date-picker';
6
6
  import 'react/jsx-runtime';
7
+ import 'react';