@hjmds/react 1.39.0 → 1.41.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/dist/chart.js +1 -4
- package/dist/chart.js.map +1 -1
- package/dist/choice-row.d.ts +22 -0
- package/dist/choice-row.d.ts.map +1 -0
- package/dist/choice-row.js +31 -0
- package/dist/choice-row.js.map +1 -0
- package/dist/evidence.d.ts +1 -1
- package/dist/evidence.js +1 -1
- package/dist/evidence.js.map +1 -1
- package/dist/floating-action-button.d.ts +2 -0
- package/dist/floating-action-button.d.ts.map +1 -1
- package/dist/floating-action-button.js +4 -4
- package/dist/floating-action-button.js.map +1 -1
- package/dist/image-viewer.d.ts +25 -0
- package/dist/image-viewer.d.ts.map +1 -0
- package/dist/image-viewer.js +82 -0
- package/dist/image-viewer.js.map +1 -0
- package/dist/public-exports.generated.d.ts +2 -0
- package/dist/public-exports.generated.d.ts.map +1 -1
- package/dist/public-exports.generated.js +6 -0
- package/dist/public-exports.generated.js.map +1 -1
- package/dist/reaction-picker.d.ts.map +1 -1
- package/dist/reaction-picker.js +2 -4
- package/dist/reaction-picker.js.map +1 -1
- package/dist/styles.css +54 -30
- package/dist/styles.layered.css +54 -30
- package/dist/time-picker.d.ts.map +1 -1
- package/dist/time-picker.js +96 -32
- package/dist/time-picker.js.map +1 -1
- package/package.json +13 -3
package/dist/time-picker.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
4
|
-
import { timePickerDraft, timePickerItems, resolveTimePickerValue, validateTimePicker, timePickerRecipe } from "@hjmds/design-contracts/components/time-picker";
|
|
4
|
+
import { timePickerDraft, timePickerItems, resolveTimePickerValue, resolveTimePickerWheelIndex, validateTimePicker, timePickerRecipe } from "@hjmds/design-contracts/components/time-picker";
|
|
5
5
|
import { Button } from "./actions.js";
|
|
6
6
|
import { Icon } from "./supplemental-display.js";
|
|
7
7
|
import { Popover } from "./popover.js";
|
|
8
|
+
import { useHjmTheme } from "./provider.js";
|
|
8
9
|
/** One draft and one confirmation for both columns. Inline mode delegates confirmation to its form. */
|
|
9
10
|
export function TimePicker(props) {
|
|
10
11
|
validateTimePicker(props);
|
|
11
12
|
const { value, onValueChange, labels, disabled, readOnly, description, error, presentation = "field", layoutStyle, className } = props;
|
|
12
13
|
const [open, setOpen] = useState(false), [draft, setDraft] = useState(() => timePickerDraft(value));
|
|
13
14
|
const locked = !!(disabled || readOnly);
|
|
14
|
-
const
|
|
15
|
+
const firstRef = useRef(null);
|
|
16
|
+
const [moving, setMoving] = useState({ hour: false, minute: false });
|
|
15
17
|
const emitted = useRef(null);
|
|
16
18
|
useEffect(() => {
|
|
17
19
|
// Preserve an incomplete inline draft when the parent echoes its null validity value.
|
|
@@ -25,13 +27,6 @@ export function TimePicker(props) {
|
|
|
25
27
|
useEffect(() => { if (locked)
|
|
26
28
|
setOpen(false); }, [locked]);
|
|
27
29
|
const resolved = resolveTimePickerValue(draft, props);
|
|
28
|
-
useLayoutEffect(() => {
|
|
29
|
-
panelRef.current?.querySelectorAll('[role="listbox"]').forEach(list => {
|
|
30
|
-
const selected = list.querySelector('[aria-selected="true"]');
|
|
31
|
-
if (selected)
|
|
32
|
-
list.scrollTop = Math.max(0, selected.offsetTop - (list.clientHeight - selected.offsetHeight) / 2);
|
|
33
|
-
});
|
|
34
|
-
}, [draft.hour, draft.minute, open]);
|
|
35
30
|
function select(part, next) {
|
|
36
31
|
if (locked)
|
|
37
32
|
return;
|
|
@@ -43,32 +38,101 @@ export function TimePicker(props) {
|
|
|
43
38
|
onValueChange(result);
|
|
44
39
|
}
|
|
45
40
|
}
|
|
46
|
-
const panel = _jsxs("div", { style: { "--hjm-time-row": `${timePickerRecipe.rowHeight}px`, "--hjm-time-height": `${timePickerRecipe.rowHeight * timePickerRecipe.visibleRows}px`, "--hjm-time-summary": `${timePickerRecipe.readoutSize}px`, "--hjm-time-summary-line-height": `${timePickerRecipe.readoutLineHeight}px`, maxWidth: timePickerRecipe.panelWidth },
|
|
47
|
-
const items = timePickerItems(part, draft, props);
|
|
48
|
-
const active = items.find(item => item.value === draft[part] && !item.disabled)?.value ?? items.find(item => !item.disabled)?.value;
|
|
49
|
-
return _jsxs("div", { className: "hjm-time-picker__column", children: [_jsx("div", { className: "hjm-time-picker__caption", children: labels[part] }), _jsx("div", { role: "listbox", "aria-label": labels[part], "aria-disabled": locked, className: "hjm-time-picker__list", children: items.map(item => _jsx("button", { ref: part === "hour" && item.value === active ? firstRef : undefined, type: "button", role: "option", "aria-selected": draft[part] === item.value, "aria-label": `${item.value} ${labels[part]}`, disabled: locked || item.disabled, tabIndex: item.value === active ? 0 : -1, onClick: () => select(part, item.value), onFocus: e => {
|
|
50
|
-
const list = e.currentTarget.parentElement;
|
|
51
|
-
const row = e.currentTarget;
|
|
52
|
-
if (row.offsetTop < list.scrollTop)
|
|
53
|
-
list.scrollTop = row.offsetTop;
|
|
54
|
-
else if (row.offsetTop + row.offsetHeight > list.scrollTop + list.clientHeight)
|
|
55
|
-
list.scrollTop = row.offsetTop + row.offsetHeight - list.clientHeight;
|
|
56
|
-
}, onKeyDown: e => {
|
|
57
|
-
if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(e.key))
|
|
58
|
-
return;
|
|
59
|
-
e.preventDefault();
|
|
60
|
-
const enabled = Array.from(e.currentTarget.parentElement.querySelectorAll('button:not(:disabled)'));
|
|
61
|
-
const index = enabled.indexOf(e.currentTarget);
|
|
62
|
-
const target = e.key === "Home" ? enabled[0] : e.key === "End" ? enabled.at(-1) : enabled[index + (e.key === "ArrowDown" ? 1 : -1)];
|
|
63
|
-
if (target) {
|
|
64
|
-
target.focus();
|
|
65
|
-
select(part, target.textContent);
|
|
66
|
-
}
|
|
67
|
-
}, children: item.value }, item.value)) })] }, part);
|
|
68
|
-
}) }), presentation === "field" ? _jsx(Button, { disabled: locked || resolved === null, onClick: () => { const current = resolveTimePickerValue(draft, props); if (!locked && current !== null) {
|
|
41
|
+
const panel = _jsxs("div", { style: { "--hjm-time-row": `${timePickerRecipe.rowHeight}px`, "--hjm-time-height": `${timePickerRecipe.rowHeight * timePickerRecipe.visibleRows}px`, "--hjm-time-summary": `${timePickerRecipe.readoutSize}px`, "--hjm-time-summary-line-height": `${timePickerRecipe.readoutLineHeight}px`, maxWidth: timePickerRecipe.panelWidth }, className: "hjm-time-picker__panel", role: "group", "aria-label": labels.label, children: [_jsxs("div", { className: "hjm-time-picker__readout", "aria-live": "polite", "aria-atomic": "true", children: [draft.hour ?? "--", _jsx("span", { children: ":" }), draft.minute ?? "--"] }), _jsx("div", { className: "hjm-time-picker__columns", children: ["hour", "minute"].map(part => _jsx(TimeColumn, { part: part, draft: draft, options: props, locked: locked, firstRef: firstRef, onSelect: next => select(part, next), onMoving: next => setMoving(current => current[part] === next ? current : { ...current, [part]: next }) }, part)) }), presentation === "field" ? _jsx(Button, { disabled: locked || resolved === null || moving.hour || moving.minute, onClick: () => { const current = resolveTimePickerValue(draft, props); if (!locked && current !== null) {
|
|
69
42
|
onValueChange(current);
|
|
70
43
|
setOpen(false);
|
|
71
44
|
} }, children: labels.confirm }) : null] });
|
|
72
45
|
return _jsxs("div", { className: `hjm-time-picker ${className ?? ""}`, style: layoutStyle, "data-disabled": disabled || undefined, children: [presentation === "inline" ? panel : _jsxs(_Fragment, { children: [_jsx("span", { className: "hjm-time-picker__label", children: labels.label }), _jsx(Popover, { title: labels.label, closeLabel: labels.cancel, closeIcon: _jsx(Icon, { name: "close" }), open: open, onOpenChange: setOpen, initialFocusRef: firstRef, trigger: _jsx("button", { type: "button", className: "hjm-time-picker__trigger", disabled: locked, "aria-label": `${labels.label}, ${value ?? labels.placeholder}`, "aria-invalid": !!error, children: _jsx("span", { children: value ?? labels.placeholder }) }), children: panel })] }), description ? _jsx("span", { className: "hjm-time-picker__description", children: description }) : null, error ? _jsx("span", { role: "alert", className: "hjm-time-picker__error", children: error }) : null] });
|
|
73
46
|
}
|
|
47
|
+
function TimeColumn({ part, draft, options, locked, firstRef, onSelect, onMoving }) {
|
|
48
|
+
const { environment } = useHjmTheme();
|
|
49
|
+
const wheel = options.selectionStyle === "wheel";
|
|
50
|
+
const listRef = useRef(null);
|
|
51
|
+
const interacting = useRef(false);
|
|
52
|
+
const emitted = useRef(null);
|
|
53
|
+
const items = timePickerItems(part, draft, options);
|
|
54
|
+
const active = items.find(item => item.value === draft[part] && !item.disabled)?.value ?? items.find(item => !item.disabled)?.value;
|
|
55
|
+
const index = items.findIndex(item => item.value === draft[part]);
|
|
56
|
+
const rowHeight = timePickerRecipe.rowHeight;
|
|
57
|
+
const label = options.labels[part];
|
|
58
|
+
useLayoutEffect(() => {
|
|
59
|
+
const list = listRef.current;
|
|
60
|
+
if (!list)
|
|
61
|
+
return;
|
|
62
|
+
// Our own scroll edits must not interrupt momentum when a controlled parent echoes the draft.
|
|
63
|
+
if (wheel && emitted.current === draft[part] && draft[part] !== null) {
|
|
64
|
+
emitted.current = null;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
interacting.current = false;
|
|
68
|
+
onMoving(false);
|
|
69
|
+
list.scrollTop = wheel ? Math.max(0, index) * rowHeight : Math.max(0, (index - 2) * rowHeight);
|
|
70
|
+
}, [draft[part], wheel, locked, options.minuteStep]);
|
|
71
|
+
function emit(index) {
|
|
72
|
+
const item = items[index];
|
|
73
|
+
if (!item || item.disabled || locked || (wheel && item.value === draft[part]))
|
|
74
|
+
return;
|
|
75
|
+
emitted.current = wheel ? item.value : null;
|
|
76
|
+
onSelect(item.value);
|
|
77
|
+
}
|
|
78
|
+
function choose(index) {
|
|
79
|
+
interacting.current = false;
|
|
80
|
+
onMoving(false);
|
|
81
|
+
emit(index);
|
|
82
|
+
if (wheel) {
|
|
83
|
+
const list = listRef.current;
|
|
84
|
+
if (list?.contains(document.activeElement))
|
|
85
|
+
list.querySelectorAll("button")[index]?.focus({ preventScroll: true });
|
|
86
|
+
list?.scrollTo({ top: index * rowHeight, behavior: environment.reducedMotion ? "instant" : "smooth" });
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
const list = listRef.current;
|
|
91
|
+
if (!wheel || !list)
|
|
92
|
+
return;
|
|
93
|
+
// scrollend includes CSS snapping and waits for touch/trackpad release; no timeout guesses.
|
|
94
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollend_event
|
|
95
|
+
const finish = () => {
|
|
96
|
+
if (!interacting.current || locked)
|
|
97
|
+
return;
|
|
98
|
+
interacting.current = false;
|
|
99
|
+
onMoving(false);
|
|
100
|
+
const target = resolveTimePickerWheelIndex(items, list.scrollTop);
|
|
101
|
+
if (target !== null)
|
|
102
|
+
choose(target);
|
|
103
|
+
};
|
|
104
|
+
list.addEventListener("scrollend", finish);
|
|
105
|
+
return () => list.removeEventListener("scrollend", finish);
|
|
106
|
+
});
|
|
107
|
+
return _jsxs("div", { className: "hjm-time-picker__column", children: [_jsx("div", { className: "hjm-time-picker__caption", children: label }), _jsxs("div", { className: "hjm-time-picker__viewport", "data-wheel": wheel || undefined, children: [wheel && _jsx("div", { "aria-hidden": "true", className: "hjm-time-picker__center" }), _jsx("div", { ref: listRef, role: "listbox", "aria-label": label, "aria-disabled": locked, className: "hjm-time-picker__list", "data-wheel": wheel || undefined, onWheel: () => { if (wheel && !locked)
|
|
108
|
+
interacting.current = true; }, onPointerDown: () => { if (wheel && !locked)
|
|
109
|
+
interacting.current = true; }, onScroll: e => {
|
|
110
|
+
if (!wheel || !interacting.current || locked)
|
|
111
|
+
return;
|
|
112
|
+
onMoving(true);
|
|
113
|
+
const target = resolveTimePickerWheelIndex(items, e.currentTarget.scrollTop);
|
|
114
|
+
if (target !== null)
|
|
115
|
+
emit(target);
|
|
116
|
+
}, children: items.map((item, itemIndex) => _jsx("button", { ref: part === "hour" && item.value === active ? firstRef : undefined, type: "button", role: "option", "aria-selected": draft[part] === item.value, "aria-label": `${item.value} ${label}`, disabled: locked || item.disabled, tabIndex: item.value === active ? 0 : -1, onClick: () => choose(itemIndex), onFocus: e => {
|
|
117
|
+
if (wheel)
|
|
118
|
+
return;
|
|
119
|
+
const list = e.currentTarget.parentElement;
|
|
120
|
+
const row = e.currentTarget;
|
|
121
|
+
if (row.offsetTop < list.scrollTop)
|
|
122
|
+
list.scrollTop = row.offsetTop;
|
|
123
|
+
else if (row.offsetTop + row.offsetHeight > list.scrollTop + list.clientHeight)
|
|
124
|
+
list.scrollTop = row.offsetTop + row.offsetHeight - list.clientHeight;
|
|
125
|
+
}, onKeyDown: e => {
|
|
126
|
+
if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(e.key))
|
|
127
|
+
return;
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
const enabled = items.map((entry, index) => ({ ...entry, index })).filter(entry => !entry.disabled);
|
|
130
|
+
const position = enabled.findIndex(entry => entry.value === item.value);
|
|
131
|
+
const target = e.key === "Home" ? enabled[0] : e.key === "End" ? enabled.at(-1) : enabled[position + (e.key === "ArrowDown" ? 1 : -1)];
|
|
132
|
+
if (target) {
|
|
133
|
+
listRef.current?.querySelectorAll("button")[target.index]?.focus({ preventScroll: wheel });
|
|
134
|
+
choose(target.index);
|
|
135
|
+
}
|
|
136
|
+
}, children: item.value }, item.value)) })] })] });
|
|
137
|
+
}
|
|
74
138
|
//# sourceMappingURL=time-picker.js.map
|
package/dist/time-picker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"time-picker.js","sourceRoot":"","sources":["../src/time-picker.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AACb,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,EAAE,QAAQ,EAAsB,MAAM,OAAO,CAAC;AACzF,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,sBAAsB,EAAE,kBAAkB,EAAE,gBAAgB,EAA+C,MAAM,gDAAgD,CAAC;AAC7M,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,2BAA2B,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAKvC,uGAAuG;AACvG,MAAM,UAAU,UAAU,CAAC,KAAsB;IAC/C,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAC1B,MAAM,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC;IACvI,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC;IACpG,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,QAAQ,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,MAAM,CAAiB,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAoB,IAAI,CAAC,CAAC;IAC1F,MAAM,OAAO,GAAG,MAAM,CAAkC,IAAI,CAAC,CAAC;IAC9D,SAAS,CAAC,GAAG,EAAE;QACb,sFAAsF;QACtF,IAAI,YAAY,KAAK,QAAQ,IAAI,OAAO,CAAC,OAAO,EAAE,KAAK,KAAK,KAAK,EAAE,CAAC;YAAC,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;YAAC,OAAO;QAAC,CAAC;QACtG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;QAAC,QAAQ,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3D,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC,CAAC;IAChC,SAAS,CAAC,GAAG,EAAE,GAAG,IAAI,MAAM;QAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAC3D,MAAM,QAAQ,GAAG,sBAAsB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACtD,eAAe,CAAC,GAAG,EAAE;QACnB,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAc,kBAAkB,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;YACjF,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAc,wBAAwB,CAAC,CAAC;YAC3E,IAAI,QAAQ;gBAAE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,SAAS,GAAG,CAAC,IAAI,CAAC,YAAY,GAAG,QAAQ,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC;QACnH,CAAC,CAAC,CAAC;IACL,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;IACrC,SAAS,MAAM,CAAC,IAAoB,EAAE,IAAY;QAChD,IAAI,MAAM;YAAE,OAAO;QACnB,MAAM,OAAO,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC;QAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;QAC9D,MAAM,MAAM,GAAG,sBAAsB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACtD,IAAI,YAAY,KAAK,QAAQ,EAAE,CAAC;YAAC,OAAO,CAAC,OAAO,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;YAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAAC,CAAC;IAChG,CAAC;IACD,MAAM,KAAK,GAAG,eAAK,KAAK,EAAE,EAAE,gBAAgB,EAAE,GAAG,gBAAgB,CAAC,SAAS,IAAI,EAAE,mBAAmB,EAAE,GAAG,gBAAgB,CAAC,SAAS,GAAG,gBAAgB,CAAC,WAAW,IAAI,EAAE,oBAAoB,EAAE,GAAG,gBAAgB,CAAC,WAAW,IAAI,EAAE,gCAAgC,EAAE,GAAG,gBAAgB,CAAC,iBAAiB,IAAI,EAAE,QAAQ,EAAE,gBAAgB,CAAC,UAAU,EAAmB,EAAE,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAC,wBAAwB,EAAC,IAAI,EAAC,OAAO,gBAAa,MAAM,CAAC,KAAK,aAC/b,eAAK,SAAS,EAAC,0BAA0B,eAAW,QAAQ,iBAAa,MAAM,aAAE,KAAK,CAAC,IAAI,IAAI,IAAI,EAAC,+BAAc,EAAC,KAAK,CAAC,MAAM,IAAI,IAAI,IAAO,EAC9I,cAAK,SAAS,EAAC,0BAA0B,YACrC,CAAC,MAAM,EAAE,QAAQ,CAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;oBACxC,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;oBAClD,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,KAAK,CAAC;oBACpI,OAAO,eAAgB,SAAS,EAAC,yBAAyB,aAAC,cAAK,SAAS,EAAC,0BAA0B,YAAE,MAAM,CAAC,IAAI,CAAC,GAAO,EACvH,cAAK,IAAI,EAAC,SAAS,gBAAa,MAAM,CAAC,IAAI,CAAC,mBAAiB,MAAM,EAAE,SAAS,EAAC,uBAAuB,YACnG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,iBAAyB,GAAG,EAAE,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,EAAE,IAAI,EAAC,QAAQ,EAAC,IAAI,EAAC,QAAQ,mBAC5H,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,gBAAc,GAAG,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,QAAQ,EAAE,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EACnK,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE;wCACpD,MAAM,IAAI,GAAG,CAAC,CAAC,aAAa,CAAC,aAAc,CAAC;wCAAC,MAAM,GAAG,GAAG,CAAC,CAAC,aAAa,CAAC;wCACzE,IAAI,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS;4CAAE,IAAI,CAAC,SAAS,GAAG,GAAG,CAAC,SAAS,CAAC;6CAC9D,IAAI,GAAG,CAAC,SAAS,GAAG,GAAG,CAAC,YAAY,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,YAAY;4CAAE,IAAI,CAAC,SAAS,GAAG,GAAG,CAAC,SAAS,GAAG,GAAG,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC;oCACxJ,CAAC,EACD,SAAS,EAAE,CAAC,CAAC,EAAE;wCACb,IAAI,CAAC,CAAC,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC;4CAAE,OAAO;wCACrE,CAAC,CAAC,cAAc,EAAE,CAAC;wCACnB,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,aAAc,CAAC,gBAAgB,CAAoB,uBAAuB,CAAC,CAAC,CAAC;wCACxH,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;wCAC/C,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;wCACpI,IAAI,MAAM,EAAE,CAAC;4CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;4CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,WAAY,CAAC,CAAC;wCAAC,CAAC;oCACpE,CAAC,YAAG,IAAI,CAAC,KAAK,IAdgB,IAAI,CAAC,KAAK,CAchB,CAAC,GACvB,KAjBS,IAAI,CAiBP,CAAC;gBACjB,CAAC,CAAC,GACE,EACL,YAAY,KAAK,OAAO,CAAC,CAAC,CAAC,KAAC,MAAM,IAAC,QAAQ,EAAE,MAAM,IAAI,QAAQ,KAAK,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,MAAM,OAAO,GAAG,sBAAsB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;oBAAC,aAAa,CAAC,OAAO,CAAC,CAAC;oBAAC,OAAO,CAAC,KAAK,CAAC,CAAC;gBAAC,CAAC,CAAC,CAAC,YAAG,MAAM,CAAC,OAAO,GAAU,CAAC,CAAC,CAAC,IAAI,IAC9P,CAAC;IACP,OAAO,eAAK,SAAS,EAAE,mBAAmB,SAAS,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,WAAW,mBAAiB,QAAQ,IAAI,SAAS,aAClH,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,8BAAE,eAAM,SAAS,EAAC,wBAAwB,YAAE,MAAM,CAAC,KAAK,GAAQ,EAAA,KAAC,OAAO,IAAC,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,KAAC,IAAI,IAAC,IAAI,EAAC,OAAO,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,eAAe,EAAE,QAAQ,EAC1P,OAAO,EAAE,iBAAQ,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,0BAA0B,EAAC,QAAQ,EAAE,MAAM,gBAAc,GAAG,MAAM,CAAC,KAAK,KAAK,KAAK,IAAI,MAAM,CAAC,WAAW,EAAE,kBAAgB,CAAC,CAAC,KAAK,YAAE,yBAAO,KAAK,IAAI,MAAM,CAAC,WAAW,GAAQ,GAAS,YAAG,KAAK,GAAW,IAAG,EACtP,WAAW,CAAC,CAAC,CAAC,eAAM,SAAS,EAAC,8BAA8B,YAAE,WAAW,GAAQ,CAAC,CAAC,CAAC,IAAI,EACxF,KAAK,CAAC,CAAC,CAAC,eAAM,IAAI,EAAC,OAAO,EAAC,SAAS,EAAC,wBAAwB,YAAE,KAAK,GAAQ,CAAC,CAAC,CAAC,IAAI,IAChF,CAAC;AACT,CAAC","sourcesContent":["\"use client\";\nimport { useEffect, useLayoutEffect, useRef, useState, type CSSProperties } from \"react\";\nimport { timePickerDraft, timePickerItems, resolveTimePickerValue, validateTimePicker, timePickerRecipe, type TimePickerOptions, type TimePickerPart } from \"@hjmds/design-contracts/components/time-picker\";\nimport { Button } from \"./actions.js\";\nimport { Icon } from \"./supplemental-display.js\";\nimport { Popover } from \"./popover.js\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\n\nexport type TimePickerProps = TimePickerOptions & Readonly<{ layoutStyle?: HjmCompositionStyleProp; className?: string }>;\n\n/** One draft and one confirmation for both columns. Inline mode delegates confirmation to its form. */\nexport function TimePicker(props: TimePickerProps) {\n validateTimePicker(props);\n const { value, onValueChange, labels, disabled, readOnly, description, error, presentation = \"field\", layoutStyle, className } = props;\n const [open, setOpen] = useState(false), [draft, setDraft] = useState(() => timePickerDraft(value));\n const locked = !!(disabled || readOnly);\n const panelRef = useRef<HTMLDivElement>(null), firstRef = useRef<HTMLButtonElement>(null);\n const emitted = useRef<{ value: string | null } | null>(null);\n useEffect(() => {\n // Preserve an incomplete inline draft when the parent echoes its null validity value.\n if (presentation === \"inline\" && emitted.current?.value === value) { emitted.current = null; return; }\n emitted.current = null; setDraft(timePickerDraft(value));\n }, [value, open, presentation]);\n useEffect(() => { if (locked) setOpen(false); }, [locked]);\n const resolved = resolveTimePickerValue(draft, props);\n useLayoutEffect(() => {\n panelRef.current?.querySelectorAll<HTMLElement>('[role=\"listbox\"]').forEach(list => {\n const selected = list.querySelector<HTMLElement>('[aria-selected=\"true\"]');\n if (selected) list.scrollTop = Math.max(0, selected.offsetTop - (list.clientHeight - selected.offsetHeight) / 2);\n });\n }, [draft.hour, draft.minute, open]);\n function select(part: TimePickerPart, next: string) {\n if (locked) return;\n const updated = { ...draft, [part]: next }; setDraft(updated);\n const result = resolveTimePickerValue(updated, props);\n if (presentation === \"inline\") { emitted.current = { value: result }; onValueChange(result); }\n }\n const panel = <div style={{ \"--hjm-time-row\": `${timePickerRecipe.rowHeight}px`, \"--hjm-time-height\": `${timePickerRecipe.rowHeight * timePickerRecipe.visibleRows}px`, \"--hjm-time-summary\": `${timePickerRecipe.readoutSize}px`, \"--hjm-time-summary-line-height\": `${timePickerRecipe.readoutLineHeight}px`, maxWidth: timePickerRecipe.panelWidth } as CSSProperties} ref={panelRef} className=\"hjm-time-picker__panel\" role=\"group\" aria-label={labels.label}>\n <div className=\"hjm-time-picker__readout\" aria-live=\"polite\" aria-atomic=\"true\">{draft.hour ?? \"--\"}<span>:</span>{draft.minute ?? \"--\"}</div>\n <div className=\"hjm-time-picker__columns\">\n {([\"hour\", \"minute\"] as const).map(part => {\n const items = timePickerItems(part, draft, props);\n const active = items.find(item => item.value === draft[part] && !item.disabled)?.value ?? items.find(item => !item.disabled)?.value;\n return <div key={part} className=\"hjm-time-picker__column\"><div className=\"hjm-time-picker__caption\">{labels[part]}</div>\n <div role=\"listbox\" aria-label={labels[part]} aria-disabled={locked} className=\"hjm-time-picker__list\">\n {items.map(item => <button key={item.value} ref={part === \"hour\" && item.value === active ? firstRef : undefined} type=\"button\" role=\"option\"\n aria-selected={draft[part] === item.value} aria-label={`${item.value} ${labels[part]}`} disabled={locked || item.disabled} tabIndex={item.value === active ? 0 : -1}\n onClick={() => select(part, item.value)} onFocus={e => {\n const list = e.currentTarget.parentElement!; const row = e.currentTarget;\n if (row.offsetTop < list.scrollTop) list.scrollTop = row.offsetTop;\n else if (row.offsetTop + row.offsetHeight > list.scrollTop + list.clientHeight) list.scrollTop = row.offsetTop + row.offsetHeight - list.clientHeight;\n }}\n onKeyDown={e => {\n if (![\"ArrowDown\", \"ArrowUp\", \"Home\", \"End\"].includes(e.key)) return;\n e.preventDefault();\n const enabled = Array.from(e.currentTarget.parentElement!.querySelectorAll<HTMLButtonElement>('button:not(:disabled)'));\n const index = enabled.indexOf(e.currentTarget);\n const target = e.key === \"Home\" ? enabled[0] : e.key === \"End\" ? enabled.at(-1) : enabled[index + (e.key === \"ArrowDown\" ? 1 : -1)];\n if (target) { target.focus(); select(part, target.textContent!); }\n }}>{item.value}</button>)}\n </div></div>;\n })}\n </div>\n {presentation === \"field\" ? <Button disabled={locked || resolved === null} onClick={() => { const current = resolveTimePickerValue(draft, props); if (!locked && current !== null) { onValueChange(current); setOpen(false); } }}>{labels.confirm}</Button> : null}\n </div>;\n return <div className={`hjm-time-picker ${className ?? \"\"}`} style={layoutStyle} data-disabled={disabled || undefined}>\n {presentation === \"inline\" ? panel : <><span className=\"hjm-time-picker__label\">{labels.label}</span><Popover title={labels.label} closeLabel={labels.cancel} closeIcon={<Icon name=\"close\" />} open={open} onOpenChange={setOpen} initialFocusRef={firstRef}\n trigger={<button type=\"button\" className=\"hjm-time-picker__trigger\" disabled={locked} aria-label={`${labels.label}, ${value ?? labels.placeholder}`} aria-invalid={!!error}><span>{value ?? labels.placeholder}</span></button>}>{panel}</Popover></>}\n {description ? <span className=\"hjm-time-picker__description\">{description}</span> : null}\n {error ? <span role=\"alert\" className=\"hjm-time-picker__error\">{error}</span> : null}\n </div>;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"time-picker.js","sourceRoot":"","sources":["../src/time-picker.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\";\nimport { useEffect, useLayoutEffect, useRef, useState, type CSSProperties, type RefObject } from \"react\";\nimport { timePickerDraft, timePickerItems, resolveTimePickerValue, resolveTimePickerWheelIndex, validateTimePicker, timePickerRecipe, type TimePickerOptions, type TimePickerPart } from \"@hjmds/design-contracts/components/time-picker\";\nimport { Button } from \"./actions.js\";\nimport { Icon } from \"./supplemental-display.js\";\nimport { Popover } from \"./popover.js\";\nimport type { HjmCompositionStyleProp } from \"./composition-style.js\";\nimport { useHjmTheme } from \"./provider.js\";\n\nexport type TimePickerProps = TimePickerOptions & Readonly<{ layoutStyle?: HjmCompositionStyleProp; className?: string }>;\n\n/** One draft and one confirmation for both columns. Inline mode delegates confirmation to its form. */\nexport function TimePicker(props: TimePickerProps) {\n validateTimePicker(props);\n const { value, onValueChange, labels, disabled, readOnly, description, error, presentation = \"field\", layoutStyle, className } = props;\n const [open, setOpen] = useState(false), [draft, setDraft] = useState(() => timePickerDraft(value));\n const locked = !!(disabled || readOnly);\n const firstRef = useRef<HTMLButtonElement>(null);\n const [moving, setMoving] = useState({ hour: false, minute: false });\n const emitted = useRef<{ value: string | null } | null>(null);\n useEffect(() => {\n // Preserve an incomplete inline draft when the parent echoes its null validity value.\n if (presentation === \"inline\" && emitted.current?.value === value) { emitted.current = null; return; }\n emitted.current = null; setDraft(timePickerDraft(value));\n }, [value, open, presentation]);\n useEffect(() => { if (locked) setOpen(false); }, [locked]);\n const resolved = resolveTimePickerValue(draft, props);\n function select(part: TimePickerPart, next: string) {\n if (locked) return;\n const updated = { ...draft, [part]: next }; setDraft(updated);\n const result = resolveTimePickerValue(updated, props);\n if (presentation === \"inline\") { emitted.current = { value: result }; onValueChange(result); }\n }\n const panel = <div style={{ \"--hjm-time-row\": `${timePickerRecipe.rowHeight}px`, \"--hjm-time-height\": `${timePickerRecipe.rowHeight * timePickerRecipe.visibleRows}px`, \"--hjm-time-summary\": `${timePickerRecipe.readoutSize}px`, \"--hjm-time-summary-line-height\": `${timePickerRecipe.readoutLineHeight}px`, maxWidth: timePickerRecipe.panelWidth } as CSSProperties} className=\"hjm-time-picker__panel\" role=\"group\" aria-label={labels.label}>\n <div className=\"hjm-time-picker__readout\" aria-live=\"polite\" aria-atomic=\"true\">{draft.hour ?? \"--\"}<span>:</span>{draft.minute ?? \"--\"}</div>\n <div className=\"hjm-time-picker__columns\">\n {([\"hour\", \"minute\"] as const).map(part => <TimeColumn key={part} part={part} draft={draft} options={props} locked={locked} firstRef={firstRef}\n onSelect={next => select(part, next)} onMoving={next => setMoving(current => current[part] === next ? current : { ...current, [part]: next })} />)}\n </div>\n {presentation === \"field\" ? <Button disabled={locked || resolved === null || moving.hour || moving.minute} onClick={() => { const current = resolveTimePickerValue(draft, props); if (!locked && current !== null) { onValueChange(current); setOpen(false); } }}>{labels.confirm}</Button> : null}\n </div>;\n return <div className={`hjm-time-picker ${className ?? \"\"}`} style={layoutStyle} data-disabled={disabled || undefined}>\n {presentation === \"inline\" ? panel : <><span className=\"hjm-time-picker__label\">{labels.label}</span><Popover title={labels.label} closeLabel={labels.cancel} closeIcon={<Icon name=\"close\" />} open={open} onOpenChange={setOpen} initialFocusRef={firstRef}\n trigger={<button type=\"button\" className=\"hjm-time-picker__trigger\" disabled={locked} aria-label={`${labels.label}, ${value ?? labels.placeholder}`} aria-invalid={!!error}><span>{value ?? labels.placeholder}</span></button>}>{panel}</Popover></>}\n {description ? <span className=\"hjm-time-picker__description\">{description}</span> : null}\n {error ? <span role=\"alert\" className=\"hjm-time-picker__error\">{error}</span> : null}\n </div>;\n}\n\nfunction TimeColumn({ part, draft, options, locked, firstRef, onSelect, onMoving }: {\n part: TimePickerPart; draft: ReturnType<typeof timePickerDraft>; options: TimePickerOptions; locked: boolean;\n firstRef: RefObject<HTMLButtonElement | null>; onSelect(value: string): void; onMoving(moving: boolean): void;\n}) {\n const { environment } = useHjmTheme();\n const wheel = options.selectionStyle === \"wheel\";\n const listRef = useRef<HTMLDivElement>(null);\n const interacting = useRef(false);\n const emitted = useRef<string | null>(null);\n const items = timePickerItems(part, draft, options);\n const active = items.find(item => item.value === draft[part] && !item.disabled)?.value ?? items.find(item => !item.disabled)?.value;\n const index = items.findIndex(item => item.value === draft[part]);\n const rowHeight = timePickerRecipe.rowHeight;\n const label = options.labels[part];\n\n useLayoutEffect(() => {\n const list = listRef.current;\n if (!list) return;\n // Our own scroll edits must not interrupt momentum when a controlled parent echoes the draft.\n if (wheel && emitted.current === draft[part] && draft[part] !== null) { emitted.current = null; return; }\n interacting.current = false;\n onMoving(false);\n list.scrollTop = wheel ? Math.max(0, index) * rowHeight : Math.max(0, (index - 2) * rowHeight);\n }, [draft[part], wheel, locked, options.minuteStep]);\n\n function emit(index: number) {\n const item = items[index];\n if (!item || item.disabled || locked || (wheel && item.value === draft[part])) return;\n emitted.current = wheel ? item.value : null;\n onSelect(item.value);\n }\n function choose(index: number) {\n interacting.current = false;\n onMoving(false);\n emit(index);\n if (wheel) {\n const list = listRef.current;\n if (list?.contains(document.activeElement)) list.querySelectorAll<HTMLButtonElement>(\"button\")[index]?.focus({ preventScroll: true });\n list?.scrollTo({ top: index * rowHeight, behavior: environment.reducedMotion ? \"instant\" : \"smooth\" });\n }\n }\n useEffect(() => {\n const list = listRef.current;\n if (!wheel || !list) return;\n // scrollend includes CSS snapping and waits for touch/trackpad release; no timeout guesses.\n // https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollend_event\n const finish = () => {\n if (!interacting.current || locked) return;\n interacting.current = false;\n onMoving(false);\n const target = resolveTimePickerWheelIndex(items, list.scrollTop);\n if (target !== null) choose(target);\n };\n list.addEventListener(\"scrollend\", finish);\n return () => list.removeEventListener(\"scrollend\", finish);\n });\n\n return <div className=\"hjm-time-picker__column\">\n <div className=\"hjm-time-picker__caption\">{label}</div>\n <div className=\"hjm-time-picker__viewport\" data-wheel={wheel || undefined}>\n {wheel && <div aria-hidden=\"true\" className=\"hjm-time-picker__center\" />}\n <div ref={listRef} role=\"listbox\" aria-label={label} aria-disabled={locked} className=\"hjm-time-picker__list\" data-wheel={wheel || undefined}\n onWheel={() => { if (wheel && !locked) interacting.current = true; }}\n onPointerDown={() => { if (wheel && !locked) interacting.current = true; }}\n onScroll={e => {\n if (!wheel || !interacting.current || locked) return;\n onMoving(true);\n const target = resolveTimePickerWheelIndex(items, e.currentTarget.scrollTop);\n if (target !== null) emit(target);\n }}>\n {items.map((item, itemIndex) => <button key={item.value} ref={part === \"hour\" && item.value === active ? firstRef : undefined} type=\"button\" role=\"option\"\n aria-selected={draft[part] === item.value} aria-label={`${item.value} ${label}`} disabled={locked || item.disabled} tabIndex={item.value === active ? 0 : -1}\n onClick={() => choose(itemIndex)} onFocus={e => {\n if (wheel) return;\n const list = e.currentTarget.parentElement!; const row = e.currentTarget;\n if (row.offsetTop < list.scrollTop) list.scrollTop = row.offsetTop;\n else if (row.offsetTop + row.offsetHeight > list.scrollTop + list.clientHeight) list.scrollTop = row.offsetTop + row.offsetHeight - list.clientHeight;\n }} onKeyDown={e => {\n if (![\"ArrowDown\", \"ArrowUp\", \"Home\", \"End\"].includes(e.key)) return;\n e.preventDefault();\n const enabled = items.map((entry, index) => ({ ...entry, index })).filter(entry => !entry.disabled);\n const position = enabled.findIndex(entry => entry.value === item.value);\n const target = e.key === \"Home\" ? enabled[0] : e.key === \"End\" ? enabled.at(-1) : enabled[position + (e.key === \"ArrowDown\" ? 1 : -1)];\n if (target) {\n listRef.current?.querySelectorAll<HTMLButtonElement>(\"button\")[target.index]?.focus({ preventScroll: wheel });\n choose(target.index);\n }\n }}>{item.value}</button>)}\n </div>\n </div>\n </div>;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hjmds/react",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.41.0",
|
|
4
4
|
"description": "Accessible React 19 renderers for HJM design contracts.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -371,6 +371,11 @@
|
|
|
371
371
|
"import": "./dist/rating.js",
|
|
372
372
|
"default": "./dist/rating.js"
|
|
373
373
|
},
|
|
374
|
+
"./choice-row": {
|
|
375
|
+
"types": "./dist/choice-row.d.ts",
|
|
376
|
+
"import": "./dist/choice-row.js",
|
|
377
|
+
"default": "./dist/choice-row.js"
|
|
378
|
+
},
|
|
374
379
|
"./image-comparison": {
|
|
375
380
|
"types": "./dist/image-comparison.d.ts",
|
|
376
381
|
"import": "./dist/image-comparison.js",
|
|
@@ -598,6 +603,11 @@
|
|
|
598
603
|
"types": "./dist/time-picker.d.ts",
|
|
599
604
|
"import": "./dist/time-picker.js",
|
|
600
605
|
"default": "./dist/time-picker.js"
|
|
606
|
+
},
|
|
607
|
+
"./image-viewer": {
|
|
608
|
+
"types": "./dist/image-viewer.d.ts",
|
|
609
|
+
"import": "./dist/image-viewer.js",
|
|
610
|
+
"default": "./dist/image-viewer.js"
|
|
601
611
|
}
|
|
602
612
|
},
|
|
603
613
|
"files": [
|
|
@@ -608,7 +618,7 @@
|
|
|
608
618
|
"THIRD_PARTY_NOTICES.md"
|
|
609
619
|
],
|
|
610
620
|
"peerDependencies": {
|
|
611
|
-
"@hjmds/design-contracts": ">=1.
|
|
621
|
+
"@hjmds/design-contracts": ">=1.41.0 <1.42.0",
|
|
612
622
|
"react": ">=19.0.0",
|
|
613
623
|
"react-dom": ">=19.0.0",
|
|
614
624
|
"@number-flow/react": "0.6.2",
|
|
@@ -646,7 +656,7 @@
|
|
|
646
656
|
"@blobatar/react": "2.7.0",
|
|
647
657
|
"lucide-react": "1.59.0",
|
|
648
658
|
"vite": "8.3.4",
|
|
649
|
-
"@hjmds/design-contracts": "1.
|
|
659
|
+
"@hjmds/design-contracts": "1.41.0"
|
|
650
660
|
},
|
|
651
661
|
"peerDependenciesMeta": {
|
|
652
662
|
"@number-flow/react": {
|