@godxjp/ui 31.10.2 → 31.11.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.
@@ -0,0 +1,490 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Dialog as RacDialog, Modal, ModalOverlay } from "react-aria-components";
5
+ import {
6
+ ChevronLeft,
7
+ ChevronRight,
8
+ Eye,
9
+ FlipHorizontal2,
10
+ FlipVertical2,
11
+ RotateCcw,
12
+ RotateCw,
13
+ Undo2,
14
+ X,
15
+ ZoomIn,
16
+ ZoomOut
17
+ } from "lucide-react";
18
+ import { useTranslation } from "../../i18n/use-translation.js";
19
+ import { useOverlayPortalContainer } from "../../lib/overlay-portal.js";
20
+ import { cn } from "../../lib/utils.js";
21
+ import { useOverlayCloseFocus } from "../feedback/overlay-close-focus.js";
22
+ const ImageGroupContext = React.createContext(null);
23
+ const ImageGroupDisabledContext = React.createContext(false);
24
+ const BASE_SCALE_RATIO = 1;
25
+ const DEFAULT_SCALE_STEP = 0.5;
26
+ const DEFAULT_MIN_SCALE = 1;
27
+ const DEFAULT_MAX_SCALE = 50;
28
+ const INITIAL_TRANSFORM = {
29
+ x: 0,
30
+ y: 0,
31
+ scale: 1,
32
+ rotate: 0,
33
+ flipX: false,
34
+ flipY: false
35
+ };
36
+ function toEntry(item) {
37
+ return typeof item === "string" ? { src: item, alt: "" } : { src: item.src, alt: item.alt ?? "" };
38
+ }
39
+ function useMergedState(controlled, initial, onChange) {
40
+ const [inner, setInner] = React.useState(initial);
41
+ const value = controlled ?? inner;
42
+ const set = React.useCallback(
43
+ (next) => {
44
+ if (Object.is(next, value)) return;
45
+ if (controlled === void 0) setInner(next);
46
+ onChange?.(next, value);
47
+ },
48
+ [controlled, onChange, value]
49
+ );
50
+ return [value, set];
51
+ }
52
+ function inDocumentOrder(entries) {
53
+ return [...entries].sort((a, b) => {
54
+ const na = a.node();
55
+ const nb = b.node();
56
+ if (!na || !nb) return 0;
57
+ return na.compareDocumentPosition(nb) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
58
+ });
59
+ }
60
+ function isRtl(element) {
61
+ return element?.closest("[dir]")?.getAttribute("dir")?.toLowerCase() === "rtl";
62
+ }
63
+ function ImagePreviewDialog({
64
+ open,
65
+ onOpenChange,
66
+ items,
67
+ current,
68
+ onCurrentChange,
69
+ fallback,
70
+ scaleStep = DEFAULT_SCALE_STEP,
71
+ minScale = DEFAULT_MIN_SCALE,
72
+ maxScale = DEFAULT_MAX_SCALE
73
+ }) {
74
+ const { t } = useTranslation();
75
+ const portalContainer = useOverlayPortalContainer();
76
+ useOverlayCloseFocus(open);
77
+ const total = items.length;
78
+ const index = total === 0 ? 0 : Math.min(Math.max(current, 0), total - 1);
79
+ const entry = items[index];
80
+ const [transform, setTransform] = React.useState(INITIAL_TRANSFORM);
81
+ const [failedSrc, setFailedSrc] = React.useState(null);
82
+ const [dragging, setDragging] = React.useState(false);
83
+ const dragStart = React.useRef(null);
84
+ React.useEffect(() => {
85
+ setTransform(INITIAL_TRANSFORM);
86
+ setFailedSrc(null);
87
+ }, [index, open]);
88
+ const zoomBy = (ratio) => setTransform((prev) => {
89
+ const scale2 = Math.min(Math.max(prev.scale * ratio, minScale), maxScale);
90
+ return scale2 <= 1 ? { ...prev, scale: scale2, x: 0, y: 0 } : { ...prev, scale: scale2 };
91
+ });
92
+ const zoomIn = () => zoomBy(BASE_SCALE_RATIO + scaleStep);
93
+ const zoomOut = () => zoomBy(BASE_SCALE_RATIO / (BASE_SCALE_RATIO + scaleStep));
94
+ const hasPrev = index > 0;
95
+ const hasNext = index < total - 1;
96
+ const goPrev = () => hasPrev && onCurrentChange(index - 1);
97
+ const goNext = () => hasNext && onCurrentChange(index + 1);
98
+ const onKeyDown = (event) => {
99
+ if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
100
+ if (total < 2) return;
101
+ event.preventDefault();
102
+ const forward = event.key === "ArrowRight" !== isRtl(event.currentTarget);
103
+ if (forward) goNext();
104
+ else goPrev();
105
+ };
106
+ const onWheel = (event) => {
107
+ if (event.deltaY === 0) return;
108
+ if (event.deltaY < 0) zoomIn();
109
+ else zoomOut();
110
+ };
111
+ const onDoubleClick = () => {
112
+ if (transform.scale !== 1) setTransform((prev) => ({ ...prev, x: 0, y: 0, scale: 1 }));
113
+ else zoomIn();
114
+ };
115
+ const onPointerDown = (event) => {
116
+ if (event.button !== 0) return;
117
+ event.preventDefault();
118
+ event.currentTarget.setPointerCapture?.(event.pointerId);
119
+ dragStart.current = { px: event.clientX, py: event.clientY, x: transform.x, y: transform.y };
120
+ setDragging(true);
121
+ };
122
+ const onPointerMove = (event) => {
123
+ const start = dragStart.current;
124
+ if (!start) return;
125
+ setTransform((prev) => ({
126
+ ...prev,
127
+ x: start.x + event.clientX - start.px,
128
+ y: start.y + event.clientY - start.py
129
+ }));
130
+ };
131
+ const onPointerUp = () => {
132
+ if (!dragStart.current) return;
133
+ dragStart.current = null;
134
+ setDragging(false);
135
+ setTransform((prev) => prev.scale <= 1 ? { ...prev, x: 0, y: 0 } : prev);
136
+ };
137
+ const src = entry && failedSrc === entry.src && fallback ? fallback : entry?.src;
138
+ const { x, y, scale, rotate, flipX, flipY } = transform;
139
+ const imageTransform = `translate3d(${x}px, ${y}px, 0) scale3d(${flipX ? -scale : scale}, ${flipY ? -scale : scale}, 1) rotate(${rotate}deg)`;
140
+ const tools = [
141
+ {
142
+ key: "flipY",
143
+ label: t("dataDisplay.image.flipY"),
144
+ icon: /* @__PURE__ */ jsx(FlipVertical2, {}),
145
+ run: () => setTransform((p) => ({ ...p, flipY: !p.flipY }))
146
+ },
147
+ {
148
+ key: "flipX",
149
+ label: t("dataDisplay.image.flipX"),
150
+ icon: /* @__PURE__ */ jsx(FlipHorizontal2, {}),
151
+ run: () => setTransform((p) => ({ ...p, flipX: !p.flipX }))
152
+ },
153
+ {
154
+ key: "rotateLeft",
155
+ label: t("dataDisplay.image.rotateLeft"),
156
+ icon: /* @__PURE__ */ jsx(RotateCcw, {}),
157
+ run: () => setTransform((p) => ({ ...p, rotate: p.rotate - 90 }))
158
+ },
159
+ {
160
+ key: "rotateRight",
161
+ label: t("dataDisplay.image.rotateRight"),
162
+ icon: /* @__PURE__ */ jsx(RotateCw, {}),
163
+ run: () => setTransform((p) => ({ ...p, rotate: p.rotate + 90 }))
164
+ },
165
+ {
166
+ key: "zoomOut",
167
+ label: t("dataDisplay.image.zoomOut"),
168
+ icon: /* @__PURE__ */ jsx(ZoomOut, {}),
169
+ run: zoomOut,
170
+ off: scale <= minScale
171
+ },
172
+ {
173
+ key: "zoomIn",
174
+ label: t("dataDisplay.image.zoomIn"),
175
+ icon: /* @__PURE__ */ jsx(ZoomIn, {}),
176
+ run: zoomIn,
177
+ off: scale >= maxScale
178
+ },
179
+ {
180
+ key: "reset",
181
+ label: t("dataDisplay.image.reset"),
182
+ icon: /* @__PURE__ */ jsx(Undo2, {}),
183
+ run: () => setTransform(INITIAL_TRANSFORM)
184
+ }
185
+ ];
186
+ return /* @__PURE__ */ jsx(
187
+ ModalOverlay,
188
+ {
189
+ UNSTABLE_portalContainer: portalContainer,
190
+ isOpen: open,
191
+ onOpenChange,
192
+ isDismissable: false,
193
+ "data-slot": "image-preview-overlay",
194
+ className: "ui-image-preview-overlay",
195
+ children: /* @__PURE__ */ jsx(Modal, { className: "contents", children: /* @__PURE__ */ jsxs(
196
+ RacDialog,
197
+ {
198
+ "aria-label": t("dataDisplay.image.dialog"),
199
+ "data-slot": "image-preview",
200
+ className: "ui-image-preview",
201
+ render: (racProps) => {
202
+ const { "data-rac": _rac, ...rest } = racProps;
203
+ return /* @__PURE__ */ jsx("section", { ...rest, onKeyDown });
204
+ },
205
+ children: [
206
+ /* @__PURE__ */ jsx(
207
+ "div",
208
+ {
209
+ className: "ui-image-preview-stage",
210
+ "data-dragging": dragging ? "" : void 0,
211
+ onWheel,
212
+ onClick: (event) => {
213
+ if (event.target === event.currentTarget) onOpenChange(false);
214
+ },
215
+ children: src ? /* @__PURE__ */ jsx(
216
+ "img",
217
+ {
218
+ "data-slot": "image-preview-img",
219
+ className: "ui-image-preview-img",
220
+ src,
221
+ alt: entry?.alt ?? "",
222
+ draggable: false,
223
+ style: { transform: imageTransform },
224
+ onError: () => entry && setFailedSrc(entry.src),
225
+ onDoubleClick,
226
+ onPointerDown,
227
+ onPointerMove,
228
+ onPointerUp,
229
+ onPointerCancel: onPointerUp
230
+ },
231
+ index
232
+ ) : null
233
+ }
234
+ ),
235
+ /* @__PURE__ */ jsx(
236
+ "button",
237
+ {
238
+ type: "button",
239
+ "data-slot": "image-preview-close",
240
+ className: "ui-image-preview-close ui-image-preview-button ui-focus-ring",
241
+ "aria-label": t("dataDisplay.image.close"),
242
+ onClick: () => onOpenChange(false),
243
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true" })
244
+ }
245
+ ),
246
+ total > 1 ? /* @__PURE__ */ jsxs(Fragment, { children: [
247
+ /* @__PURE__ */ jsx(
248
+ "button",
249
+ {
250
+ type: "button",
251
+ "data-slot": "image-preview-previous",
252
+ className: "ui-image-preview-switch ui-image-preview-button ui-focus-ring",
253
+ "data-direction": "previous",
254
+ "aria-label": t("dataDisplay.image.previous"),
255
+ disabled: !hasPrev,
256
+ onClick: goPrev,
257
+ children: /* @__PURE__ */ jsx(ChevronLeft, { "aria-hidden": "true" })
258
+ }
259
+ ),
260
+ /* @__PURE__ */ jsx(
261
+ "button",
262
+ {
263
+ type: "button",
264
+ "data-slot": "image-preview-next",
265
+ className: "ui-image-preview-switch ui-image-preview-button ui-focus-ring",
266
+ "data-direction": "next",
267
+ "aria-label": t("dataDisplay.image.next"),
268
+ disabled: !hasNext,
269
+ onClick: goNext,
270
+ children: /* @__PURE__ */ jsx(ChevronRight, { "aria-hidden": "true" })
271
+ }
272
+ )
273
+ ] }) : null,
274
+ /* @__PURE__ */ jsxs("div", { className: "ui-image-preview-footer", children: [
275
+ total > 1 ? /* @__PURE__ */ jsxs(Fragment, { children: [
276
+ /* @__PURE__ */ jsx(
277
+ "span",
278
+ {
279
+ "data-slot": "image-preview-counter",
280
+ className: "ui-image-preview-counter",
281
+ "aria-hidden": "true",
282
+ children: t("dataDisplay.image.counter", { current: index + 1, total })
283
+ }
284
+ ),
285
+ /* @__PURE__ */ jsx("span", { className: "sr-only", "aria-live": "polite", children: t("dataDisplay.image.position", { current: index + 1, total }) })
286
+ ] }) : null,
287
+ /* @__PURE__ */ jsx(
288
+ "div",
289
+ {
290
+ role: "group",
291
+ "aria-label": t("dataDisplay.image.toolbar"),
292
+ "data-slot": "image-preview-toolbar",
293
+ className: "ui-image-preview-toolbar",
294
+ children: tools.map((tool) => /* @__PURE__ */ jsx(
295
+ "button",
296
+ {
297
+ type: "button",
298
+ "data-action": tool.key,
299
+ className: "ui-image-preview-button ui-focus-ring",
300
+ "aria-label": tool.label,
301
+ title: tool.label,
302
+ disabled: tool.off,
303
+ onClick: tool.run,
304
+ children: /* @__PURE__ */ jsx("span", { className: "ui-image-preview-icon", "aria-hidden": "true", children: tool.icon })
305
+ },
306
+ tool.key
307
+ ))
308
+ }
309
+ )
310
+ ] })
311
+ ]
312
+ }
313
+ ) })
314
+ }
315
+ );
316
+ }
317
+ function ImagePreviewGroup({
318
+ items,
319
+ fallback,
320
+ preview = true,
321
+ children
322
+ }) {
323
+ const config = typeof preview === "object" ? preview : {};
324
+ const enabled = preview !== false;
325
+ const registry = React.useRef(/* @__PURE__ */ new Map());
326
+ const [version, setVersion] = React.useState(0);
327
+ const [visible, setVisible] = useMergedState(config.visible, false, config.onVisibleChange);
328
+ const [current, setCurrent] = useMergedState(config.current, 0, config.onChange);
329
+ const registered = React.useMemo(() => {
330
+ void version;
331
+ return inDocumentOrder(registry.current.values());
332
+ }, [version]);
333
+ const list = React.useMemo(
334
+ () => items ? items.map(toEntry) : registered,
335
+ [items, registered]
336
+ );
337
+ const latest = React.useRef({ items, setCurrent, setVisible });
338
+ latest.current = { items, setCurrent, setVisible };
339
+ const context = React.useMemo(
340
+ () => ({
341
+ register: (entry) => {
342
+ registry.current.set(entry.id, entry);
343
+ setVersion((v) => v + 1);
344
+ return () => {
345
+ registry.current.delete(entry.id);
346
+ setVersion((v) => v + 1);
347
+ };
348
+ },
349
+ open: (id) => {
350
+ const { items: explicit, setCurrent: select, setVisible: show } = latest.current;
351
+ const entry = registry.current.get(id);
352
+ const index = explicit ? explicit.map(toEntry).findIndex((item) => item.src === entry?.src) : inDocumentOrder(registry.current.values()).findIndex((item) => item.id === id);
353
+ select(Math.max(index, 0));
354
+ show(true);
355
+ }
356
+ }),
357
+ []
358
+ );
359
+ return /* @__PURE__ */ jsx(ImageGroupDisabledContext.Provider, { value: !enabled, children: /* @__PURE__ */ jsxs(ImageGroupContext.Provider, { value: enabled ? context : null, children: [
360
+ children,
361
+ enabled ? /* @__PURE__ */ jsx(
362
+ ImagePreviewDialog,
363
+ {
364
+ open: visible && list.length > 0,
365
+ onOpenChange: setVisible,
366
+ items: list,
367
+ current,
368
+ onCurrentChange: setCurrent,
369
+ fallback,
370
+ scaleStep: config.scaleStep,
371
+ minScale: config.minScale,
372
+ maxScale: config.maxScale
373
+ }
374
+ ) : null
375
+ ] }) });
376
+ }
377
+ const ImageRoot = React.forwardRef(function Image({ src, alt, fallback, placeholder, preview = true, className, onLoad, onError, ...imgProps }, ref) {
378
+ const { t } = useTranslation();
379
+ const group = React.useContext(ImageGroupContext);
380
+ const groupDisabled = React.useContext(ImageGroupDisabledContext);
381
+ const id = React.useId();
382
+ const wrapperRef = React.useRef(null);
383
+ const [load, setLoad] = React.useState({
384
+ src,
385
+ status: "loading"
386
+ });
387
+ const status = load.src === src ? load.status : "loading";
388
+ const setStatus = React.useCallback((next) => setLoad({ src, status: next }), [src]);
389
+ const imgRef = React.useRef(null);
390
+ const setImgRef = React.useCallback(
391
+ (node) => {
392
+ imgRef.current = node;
393
+ if (node?.complete && node.naturalWidth > 0) setStatus("loaded");
394
+ if (typeof ref === "function") ref(node);
395
+ else if (ref) ref.current = node;
396
+ },
397
+ [ref, setStatus]
398
+ );
399
+ const config = typeof preview === "object" ? preview : {};
400
+ const isError = status === "error";
401
+ const canPreview = preview !== false && !groupDisabled && !isError;
402
+ const previewSrc = config.src ?? src;
403
+ const inGroup = group != null && canPreview;
404
+ React.useLayoutEffect(() => {
405
+ if (!inGroup || !group) return void 0;
406
+ return group.register({ id, src: previewSrc, alt, node: () => wrapperRef.current });
407
+ }, [inGroup, group, id, previewSrc, alt]);
408
+ const [visible, setVisible] = useMergedState(config.visible, false, config.onVisibleChange);
409
+ const shownSrc = isError && fallback ? fallback : src;
410
+ const showPlaceholder = placeholder != null && placeholder !== false && status === "loading";
411
+ const picture = /* @__PURE__ */ jsxs(Fragment, { children: [
412
+ /* @__PURE__ */ jsx(
413
+ "img",
414
+ {
415
+ ...imgProps,
416
+ ref: setImgRef,
417
+ "data-slot": "image-img",
418
+ className: "ui-image-img",
419
+ src: shownSrc,
420
+ alt,
421
+ onLoad: (event) => {
422
+ setStatus("loaded");
423
+ onLoad?.(event);
424
+ },
425
+ onError: (event) => {
426
+ setStatus("error");
427
+ onError?.(event);
428
+ }
429
+ }
430
+ ),
431
+ showPlaceholder ? /* @__PURE__ */ jsx("span", { "data-slot": "image-placeholder", className: "ui-image-placeholder", "aria-hidden": "true", children: placeholder === true ? null : placeholder }) : null
432
+ ] });
433
+ if (!canPreview) {
434
+ return /* @__PURE__ */ jsx(
435
+ "span",
436
+ {
437
+ ref: wrapperRef,
438
+ "data-slot": "image",
439
+ "data-status": status,
440
+ className: cn("ui-image", className),
441
+ children: picture
442
+ }
443
+ );
444
+ }
445
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
446
+ /* @__PURE__ */ jsxs(
447
+ "button",
448
+ {
449
+ ref: (node) => {
450
+ wrapperRef.current = node;
451
+ },
452
+ type: "button",
453
+ "data-slot": "image",
454
+ "data-status": status,
455
+ "data-preview": "",
456
+ className: cn("ui-image ui-focus-ring", className),
457
+ "aria-label": alt ? t("dataDisplay.image.previewLabel", { alt }) : t("dataDisplay.image.previewUnnamed"),
458
+ "aria-haspopup": "dialog",
459
+ onClick: () => inGroup && group ? group.open(id) : setVisible(true),
460
+ children: [
461
+ picture,
462
+ /* @__PURE__ */ jsx("span", { "data-slot": "image-mask", className: "ui-image-mask", "aria-hidden": "true", children: config.mask ?? /* @__PURE__ */ jsxs(Fragment, { children: [
463
+ /* @__PURE__ */ jsx(Eye, { className: "ui-image-mask-icon" }),
464
+ t("dataDisplay.image.preview")
465
+ ] }) })
466
+ ]
467
+ }
468
+ ),
469
+ inGroup ? null : /* @__PURE__ */ jsx(
470
+ ImagePreviewDialog,
471
+ {
472
+ open: visible,
473
+ onOpenChange: setVisible,
474
+ items: [{ src: previewSrc, alt }],
475
+ current: 0,
476
+ onCurrentChange: () => void 0,
477
+ fallback,
478
+ scaleStep: config.scaleStep,
479
+ minScale: config.minScale,
480
+ maxScale: config.maxScale
481
+ }
482
+ )
483
+ ] });
484
+ });
485
+ ImageRoot.displayName = "Image";
486
+ const Image2 = Object.assign(ImageRoot, { PreviewGroup: ImagePreviewGroup });
487
+ export {
488
+ Image2 as Image,
489
+ ImagePreviewGroup
490
+ };
@@ -59,3 +59,5 @@ export { Welcome } from "./welcome.js";
59
59
  export type { WelcomeProp, WelcomeProps, WelcomeVariantProp } from "./welcome.js";
60
60
  export { ThoughtChain, ThoughtChainItem } from "./thought-chain.js";
61
61
  export type { ThoughtChainProp, ThoughtChainProps, ThoughtChainItemsProp, ThoughtChainItemProp, ThoughtChainItemProps, ThoughtChainStatusProp, ThoughtChainLineProp, ThoughtChainVariantProp, } from "./thought-chain.js";
62
+ export { Image, ImagePreviewGroup } from "./image.js";
63
+ export type { ImagePreviewConfigProp, ImagePreviewGroupConfigProp, ImagePreviewGroupProp, ImagePreviewGroupProps, ImagePreviewItemProp, ImageProp, ImageProps, } from "./image.js";
@@ -63,6 +63,7 @@ import { Tree } from "./tree.js";
63
63
  import { OrgChart } from "./org-chart.js";
64
64
  import { Welcome } from "./welcome.js";
65
65
  import { ThoughtChain, ThoughtChainItem } from "./thought-chain.js";
66
+ import { Image, ImagePreviewGroup } from "./image.js";
66
67
  export {
67
68
  Accordion,
68
69
  AccordionContent,
@@ -101,6 +102,8 @@ export {
101
102
  HoverCard,
102
103
  HoverCardContent,
103
104
  HoverCardTrigger,
105
+ Image,
106
+ ImagePreviewGroup,
104
107
  Legend,
105
108
  ListRow,
106
109
  Marquee,
@@ -217,6 +217,22 @@ function DatePicker(props) {
217
217
  setText(displayAll(date));
218
218
  }
219
219
  };
220
+ const confirm = (next) => {
221
+ if (range) {
222
+ const nextRange = next;
223
+ if (!validRange(nextRange)) return;
224
+ emit(nextRange);
225
+ setFromText(display(nextRange?.from));
226
+ setToText(display(nextRange?.to));
227
+ } else {
228
+ const first = Array.isArray(next) ? next[0] : next;
229
+ if (!first || !allowed(first) || !timeAllowed(first)) return;
230
+ emit(next);
231
+ setText(displayAll(next));
232
+ }
233
+ setOpen(false);
234
+ };
235
+ const timeEnterRef = React.useRef(false);
220
236
  const commit = (raw) => {
221
237
  if (!raw.trim()) {
222
238
  choose(void 0);
@@ -494,42 +510,56 @@ function DatePicker(props) {
494
510
  index
495
511
  )) }) : null,
496
512
  isPeriod ? renderPeriodPanel() : renderDayPanel(),
497
- showTime ? /* @__PURE__ */ jsx(Flex, { pad: "sm", children: /* @__PURE__ */ jsx(
498
- TimePicker,
513
+ showTime ? /* @__PURE__ */ jsx(
514
+ Flex,
499
515
  {
500
- ...typeof showTime === "object" ? showTime : {},
501
- "aria-label": t("dataEntry.timePicker.openPicker"),
502
- value: selectedDate ? [
503
- selectedDate.getHours(),
504
- selectedDate.getMinutes(),
505
- ...typeof showTime === "object" && showTime.showSeconds ? [selectedDate.getSeconds()] : []
506
- ].map((n) => String(n).padStart(2, "0")).join(":") : "",
507
- onValueChange: (time) => {
508
- if (!time) return;
509
- const date = new Date(selectedDate ?? /* @__PURE__ */ new Date());
510
- const [h, m, sec = 0] = time.split(":").map(Number);
511
- date.setHours(h, m, sec, 0);
512
- choose(date);
513
- }
516
+ pad: "sm",
517
+ onKeyDownCapture: (event) => {
518
+ if (event.key === "Enter" && !isImeComposing(event)) timeEnterRef.current = true;
519
+ },
520
+ onKeyDown: (event) => {
521
+ if (event.key !== "Enter" || !timeEnterRef.current) return;
522
+ timeEnterRef.current = false;
523
+ event.preventDefault();
524
+ if (needConfirm) confirm(working);
525
+ else setOpen(false);
526
+ },
527
+ children: /* @__PURE__ */ jsx(
528
+ TimePicker,
529
+ {
530
+ ...typeof showTime === "object" ? showTime : {},
531
+ "aria-label": t("dataEntry.timePicker.openPicker"),
532
+ value: selectedDate ? [
533
+ selectedDate.getHours(),
534
+ selectedDate.getMinutes(),
535
+ ...typeof showTime === "object" && showTime.showSeconds ? [selectedDate.getSeconds()] : []
536
+ ].map((n) => String(n).padStart(2, "0")).join(":") : "",
537
+ onValueChange: (time) => {
538
+ if (!time) return;
539
+ const date = new Date(selectedDate ?? /* @__PURE__ */ new Date());
540
+ const [h, m, sec = 0] = time.split(":").map(Number);
541
+ date.setHours(h, m, sec, 0);
542
+ if (!timeEnterRef.current) {
543
+ choose(date);
544
+ return;
545
+ }
546
+ timeEnterRef.current = false;
547
+ if (needConfirm) confirm(date);
548
+ else {
549
+ choose(date);
550
+ setOpen(false);
551
+ }
552
+ }
553
+ }
554
+ )
514
555
  }
515
- ) }) : null,
556
+ ) : null,
516
557
  needConfirm ? /* @__PURE__ */ jsx(Flex, { pad: "sm", justify: "end", children: /* @__PURE__ */ jsx(
517
558
  Button,
518
559
  {
519
560
  type: "button",
520
561
  disabled: range ? !validRange(rangeValue) : !selectedDate || !allowed(selectedDate) || !timeAllowed(selectedDate),
521
- onClick: () => {
522
- if (range ? validRange(rangeValue) : selectedDate && allowed(selectedDate)) {
523
- emit(working);
524
- if (range) {
525
- setFromText(display(rangeValue?.from));
526
- setToText(display(rangeValue?.to));
527
- } else {
528
- setText(displayAll(working));
529
- }
530
- setOpen(false);
531
- }
532
- },
562
+ onClick: () => confirm(working),
533
563
  children: t("dataEntry.timePicker.confirm")
534
564
  }
535
565
  ) }) : null,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$comment": "AUTO-GENERATED by scripts/gen-measurement-contract.mjs — do not edit. Read this instead of guessing: docs/MEASUREMENT-CONTRACT.md.",
3
- "version": "31.10.2",
3
+ "version": "31.11.0",
4
4
  "targetSize": {
5
5
  "standard": "WCAG 2.2 SC 2.5.8 Target Size (Minimum), level AA — 24×24 CSS px",
6
6
  "min": 24,
@@ -384,6 +384,25 @@
384
384
  "statusDone": "Completed: ",
385
385
  "statusCurrent": "Current: ",
386
386
  "statusPending": "Upcoming: "
387
+ },
388
+ "image": {
389
+ "preview": "Preview",
390
+ "previewLabel": "Preview: {alt}",
391
+ "previewUnnamed": "Preview image",
392
+ "dialog": "Image preview",
393
+ "close": "Close preview",
394
+ "previous": "Previous image",
395
+ "next": "Next image",
396
+ "counter": "{current} / {total}",
397
+ "position": "Image {current} of {total}",
398
+ "toolbar": "Image tools",
399
+ "flipX": "Flip horizontally",
400
+ "flipY": "Flip vertically",
401
+ "rotateLeft": "Rotate left",
402
+ "rotateRight": "Rotate right",
403
+ "zoomIn": "Zoom in",
404
+ "zoomOut": "Zoom out",
405
+ "reset": "Reset"
387
406
  }
388
407
  },
389
408
  "ui": {
@@ -377,6 +377,25 @@
377
377
  "statusDone": "完了: ",
378
378
  "statusCurrent": "現在: ",
379
379
  "statusPending": "予定: "
380
+ },
381
+ "image": {
382
+ "preview": "プレビュー",
383
+ "previewLabel": "プレビュー: {alt}",
384
+ "previewUnnamed": "画像をプレビュー",
385
+ "dialog": "画像プレビュー",
386
+ "close": "プレビューを閉じる",
387
+ "previous": "前の画像",
388
+ "next": "次の画像",
389
+ "counter": "{current} / {total}",
390
+ "position": "{total} 枚中 {current} 枚目",
391
+ "toolbar": "画像ツール",
392
+ "flipX": "左右反転",
393
+ "flipY": "上下反転",
394
+ "rotateLeft": "左に回転",
395
+ "rotateRight": "右に回転",
396
+ "zoomIn": "拡大",
397
+ "zoomOut": "縮小",
398
+ "reset": "リセット"
380
399
  }
381
400
  },
382
401
  "ui": {