@kenos-ui/react-datepicker 0.3.1 → 0.4.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.
- package/CHANGELOG.md +27 -1
- package/README.md +115 -90
- package/dist/index.cjs +25 -88
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +25 -88
- package/dist/index.js.map +1 -1
- package/package.json +7 -4
package/dist/index.d.cts
CHANGED
|
@@ -74,6 +74,8 @@ interface DatePickerConfig {
|
|
|
74
74
|
disabled?: boolean | ((date: Date) => boolean);
|
|
75
75
|
readOnly: boolean;
|
|
76
76
|
closeOnSelect: boolean;
|
|
77
|
+
/** Opt-in focus trap + aria-modal. Default: false (popup-policy). */
|
|
78
|
+
modal: boolean;
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
type RootProps = DatePickerRootProps & {
|
package/dist/index.d.ts
CHANGED
|
@@ -74,6 +74,8 @@ interface DatePickerConfig {
|
|
|
74
74
|
disabled?: boolean | ((date: Date) => boolean);
|
|
75
75
|
readOnly: boolean;
|
|
76
76
|
closeOnSelect: boolean;
|
|
77
|
+
/** Opt-in focus trap + aria-modal. Default: false (popup-policy). */
|
|
78
|
+
modal: boolean;
|
|
77
79
|
}
|
|
78
80
|
|
|
79
81
|
type RootProps = DatePickerRootProps & {
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import React, { createContext, useContext, useMemo, useRef, useState, useEffect,
|
|
|
2
2
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
3
|
import { useTimescape } from 'timescape/react';
|
|
4
4
|
import { createPortal } from 'react-dom';
|
|
5
|
+
import { restoreFocus, useClickOutside, useEscapeKey, useFocusTrap } from '@kenos-ui/utils';
|
|
5
6
|
import { useFloating, autoUpdate, offset, flip, shift } from '@floating-ui/react-dom';
|
|
6
7
|
|
|
7
8
|
var __defProp = Object.defineProperty;
|
|
@@ -412,6 +413,7 @@ function resolveConfig(props) {
|
|
|
412
413
|
mode: props.mode ?? "single",
|
|
413
414
|
locale: props.locale ?? (typeof navigator !== "undefined" ? navigator.language : "en-US"),
|
|
414
415
|
readOnly: props.readOnly ?? false,
|
|
416
|
+
modal: props.modal ?? false,
|
|
415
417
|
closeOnSelect: props.closeOnSelect ?? (props.mode !== "range" && props.mode !== "multiple"),
|
|
416
418
|
...props.weekStartsOn !== void 0 && { weekStartsOn: props.weekStartsOn },
|
|
417
419
|
...props.minDate !== void 0 && { minDate: props.minDate },
|
|
@@ -437,7 +439,7 @@ function resolveInitialValue(props) {
|
|
|
437
439
|
}
|
|
438
440
|
function useDatePicker(props) {
|
|
439
441
|
const uid = useId();
|
|
440
|
-
const { mode, locale, weekStartsOn, minDate, maxDate, disabled, readOnly, closeOnSelect } = props;
|
|
442
|
+
const { mode, locale, weekStartsOn, minDate, maxDate, disabled, readOnly, closeOnSelect, modal } = props;
|
|
441
443
|
const config = useMemo(
|
|
442
444
|
() => resolveConfig({
|
|
443
445
|
mode,
|
|
@@ -447,9 +449,10 @@ function useDatePicker(props) {
|
|
|
447
449
|
maxDate,
|
|
448
450
|
disabled,
|
|
449
451
|
readOnly,
|
|
450
|
-
closeOnSelect
|
|
452
|
+
closeOnSelect,
|
|
453
|
+
modal
|
|
451
454
|
}),
|
|
452
|
-
[mode, locale, weekStartsOn, minDate, maxDate, disabled, readOnly, closeOnSelect]
|
|
455
|
+
[mode, locale, weekStartsOn, minDate, maxDate, disabled, readOnly, closeOnSelect, modal]
|
|
453
456
|
);
|
|
454
457
|
const initialValue = resolveInitialValue(props);
|
|
455
458
|
const [state, dispatch] = useReducer(
|
|
@@ -651,6 +654,7 @@ function Segments({
|
|
|
651
654
|
}
|
|
652
655
|
if (e.key === "Escape") {
|
|
653
656
|
e.preventDefault();
|
|
657
|
+
e.stopPropagation();
|
|
654
658
|
if (state.open) dispatch({ type: "CLOSE" });
|
|
655
659
|
}
|
|
656
660
|
}
|
|
@@ -801,76 +805,6 @@ function Trigger({ children, onClick, disabled, ...props }) {
|
|
|
801
805
|
}
|
|
802
806
|
);
|
|
803
807
|
}
|
|
804
|
-
function useClickOutside(refs, handler, enabled = true) {
|
|
805
|
-
const refsRef = useRef(refs);
|
|
806
|
-
refsRef.current = refs;
|
|
807
|
-
useEffect(() => {
|
|
808
|
-
if (!enabled) return;
|
|
809
|
-
function onPointerDown(e) {
|
|
810
|
-
const target = e.target;
|
|
811
|
-
if (refsRef.current.every((r) => !r.current?.contains(target))) {
|
|
812
|
-
handler();
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
document.addEventListener("pointerdown", onPointerDown, true);
|
|
816
|
-
return () => document.removeEventListener("pointerdown", onPointerDown, true);
|
|
817
|
-
}, [enabled, handler]);
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
// src/utils/aria.ts
|
|
821
|
-
function getFocusableElements(container) {
|
|
822
|
-
const selector = [
|
|
823
|
-
"a[href]",
|
|
824
|
-
"button:not([disabled])",
|
|
825
|
-
"input:not([disabled])",
|
|
826
|
-
"select:not([disabled])",
|
|
827
|
-
"textarea:not([disabled])",
|
|
828
|
-
'[tabindex]:not([tabindex="-1"])'
|
|
829
|
-
].join(", ");
|
|
830
|
-
return Array.from(container.querySelectorAll(selector)).filter(
|
|
831
|
-
(el) => !el.closest("[hidden]") && el.offsetParent !== null
|
|
832
|
-
);
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
// src/date-picker/use-focus-trap.ts
|
|
836
|
-
function useFocusTrap(containerRef, enabled = true) {
|
|
837
|
-
useEffect(() => {
|
|
838
|
-
if (!enabled || !containerRef.current) return;
|
|
839
|
-
const container = containerRef.current;
|
|
840
|
-
function onKeyDown(e) {
|
|
841
|
-
if (e.key !== "Tab") return;
|
|
842
|
-
const focusable = getFocusableElements(container);
|
|
843
|
-
if (!focusable.length) return;
|
|
844
|
-
const first = focusable[0];
|
|
845
|
-
const last = focusable[focusable.length - 1];
|
|
846
|
-
if (e.shiftKey) {
|
|
847
|
-
if (document.activeElement === first) {
|
|
848
|
-
e.preventDefault();
|
|
849
|
-
last.focus();
|
|
850
|
-
}
|
|
851
|
-
} else {
|
|
852
|
-
if (document.activeElement === last) {
|
|
853
|
-
e.preventDefault();
|
|
854
|
-
first.focus();
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
const observer = new MutationObserver(() => {
|
|
859
|
-
const active = document.activeElement;
|
|
860
|
-
if (active?.getAttribute("role") === "spinbutton") return;
|
|
861
|
-
if (!container.contains(active)) {
|
|
862
|
-
const firstFocusable = getFocusableElements(container)[0];
|
|
863
|
-
firstFocusable?.focus();
|
|
864
|
-
}
|
|
865
|
-
});
|
|
866
|
-
observer.observe(container, { childList: true, subtree: true });
|
|
867
|
-
container.addEventListener("keydown", onKeyDown);
|
|
868
|
-
return () => {
|
|
869
|
-
container.removeEventListener("keydown", onKeyDown);
|
|
870
|
-
observer.disconnect();
|
|
871
|
-
};
|
|
872
|
-
}, [enabled, containerRef]);
|
|
873
|
-
}
|
|
874
808
|
function toPlacement(side, align) {
|
|
875
809
|
return align === "center" ? side : `${side}-${align}`;
|
|
876
810
|
}
|
|
@@ -969,12 +903,22 @@ function Content({
|
|
|
969
903
|
},
|
|
970
904
|
[setFloating]
|
|
971
905
|
);
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
906
|
+
const close = useCallback(() => {
|
|
907
|
+
const source = state.openSource;
|
|
908
|
+
dispatch({ type: "CLOSE" });
|
|
909
|
+
restoreFocus({
|
|
910
|
+
openSource: source === "input" ? "input" : source === "trigger" ? "trigger" : "unknown",
|
|
911
|
+
trigger: document.getElementById(ids.trigger),
|
|
912
|
+
input: document.getElementById(ids.input) ?? document.getElementById(`${ids.input}-0`)
|
|
913
|
+
});
|
|
914
|
+
}, [dispatch, ids.input, ids.trigger, state.openSource]);
|
|
915
|
+
useClickOutside([contentRef, triggerRef, inputRef, input0Ref, input1Ref], close, isOpen);
|
|
916
|
+
useEscapeKey({
|
|
917
|
+
enabled: isOpen,
|
|
918
|
+
stopPropagation: true,
|
|
919
|
+
onEscape: close
|
|
920
|
+
});
|
|
921
|
+
useFocusTrap(contentRef, isOpen && config.modal);
|
|
978
922
|
const [transitionsReady, setTransitionsReady] = useState(false);
|
|
979
923
|
useEffect(() => {
|
|
980
924
|
if (!isOpen || !isPositioned) {
|
|
@@ -1003,7 +947,7 @@ function Content({
|
|
|
1003
947
|
ref: mergedRef,
|
|
1004
948
|
id: ids.content,
|
|
1005
949
|
role: "dialog",
|
|
1006
|
-
"aria-modal": "true",
|
|
950
|
+
"aria-modal": config.modal ? "true" : void 0,
|
|
1007
951
|
"aria-labelledby": ids.label,
|
|
1008
952
|
"data-state": isOpen ? "open" : "closed",
|
|
1009
953
|
style: {
|
|
@@ -1017,14 +961,7 @@ function Content({
|
|
|
1017
961
|
...isOpen && !transitionsReady ? { transition: "none" } : void 0,
|
|
1018
962
|
...style
|
|
1019
963
|
},
|
|
1020
|
-
onKeyDown
|
|
1021
|
-
if (e.key === "Escape") {
|
|
1022
|
-
e.preventDefault();
|
|
1023
|
-
dispatch({ type: "CLOSE" });
|
|
1024
|
-
document.getElementById(ids.trigger)?.focus();
|
|
1025
|
-
}
|
|
1026
|
-
onKeyDown?.(e);
|
|
1027
|
-
},
|
|
964
|
+
onKeyDown,
|
|
1028
965
|
...props,
|
|
1029
966
|
children: [
|
|
1030
967
|
/* @__PURE__ */ jsx(
|