@tensor_1/react 2.30.0 → 2.32.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/README.md +430 -0
- package/dist/attachment-labels.d.ts +16 -0
- package/dist/attachment-labels.js +9 -0
- package/dist/calendar-model.d.ts +141 -1
- package/dist/calendar-model.js +200 -11
- package/dist/chart-preset.cjs +127 -7
- package/dist/chart-preset.d.cts +2 -2
- package/dist/chart-preset.d.ts +1 -0
- package/dist/chart-preset.js +6 -1
- package/dist/chart-theme-Cl0IrkKQ.d.cts +112 -0
- package/dist/chart-theme.d.ts +51 -0
- package/dist/chart-theme.js +99 -0
- package/dist/chart.cjs +127 -7
- package/dist/chart.d.cts +2 -2
- package/dist/chart.d.ts +1 -0
- package/dist/chart.js +6 -2
- package/dist/components/Alert.d.ts +17 -1
- package/dist/components/Alert.js +9 -4
- package/dist/components/AlertDismiss.d.ts +14 -0
- package/dist/components/AlertDismiss.js +11 -0
- package/dist/components/Attachment.d.ts +58 -0
- package/dist/components/Attachment.js +69 -0
- package/dist/components/AttachmentParts.d.ts +24 -0
- package/dist/components/AttachmentParts.js +20 -0
- package/dist/components/Chart.d.ts +1 -0
- package/dist/components/Chart.js +6 -1
- package/dist/components/ChartCore.d.ts +6 -1
- package/dist/components/ChartCore.js +23 -5
- package/dist/components/Chip.d.ts +17 -2
- package/dist/components/Chip.js +35 -11
- package/dist/components/ChipRemove.d.ts +17 -0
- package/dist/components/ChipRemove.js +13 -0
- package/dist/components/CollapsibleParts.d.ts +29 -0
- package/dist/components/CollapsibleParts.js +20 -0
- package/dist/components/CollapsibleSection.d.ts +30 -3
- package/dist/components/CollapsibleSection.js +7 -11
- package/dist/components/CollapsibleSectionState.d.ts +1 -1
- package/dist/components/CollapsibleSectionState.js +54 -27
- package/dist/components/DashboardGrid.d.ts +60 -0
- package/dist/components/DashboardGrid.js +40 -0
- package/dist/components/DateRangePicker.d.ts +7 -1
- package/dist/components/DateRangePicker.js +5 -1
- package/dist/components/DescriptionList.d.ts +12 -1
- package/dist/components/DescriptionList.js +5 -5
- package/dist/components/Dialogs.d.ts +24 -3
- package/dist/components/Dialogs.js +74 -16
- package/dist/components/EntityPicker.d.ts +56 -0
- package/dist/components/EntityPicker.js +168 -0
- package/dist/components/FileIcon.d.ts +9 -0
- package/dist/components/FileIcon.js +17 -0
- package/dist/components/FileUpload.d.ts +95 -0
- package/dist/components/FileUpload.js +145 -0
- package/dist/components/FilterEditor.d.ts +55 -0
- package/dist/components/FilterEditor.js +186 -0
- package/dist/components/Heartbeat.d.ts +31 -0
- package/dist/components/Heartbeat.js +28 -0
- package/dist/components/Lightbox.d.ts +45 -0
- package/dist/components/Lightbox.js +102 -0
- package/dist/components/Menu.d.ts +53 -5
- package/dist/components/Menu.js +104 -4
- package/dist/components/Meter.d.ts +7 -3
- package/dist/components/Meter.js +2 -1
- package/dist/components/Popover.d.ts +21 -2
- package/dist/components/Popover.js +94 -17
- package/dist/components/Prose.d.ts +6 -2
- package/dist/components/Prose.js +1 -1
- package/dist/components/RuleBuilder.d.ts +52 -4
- package/dist/components/RuleBuilder.js +109 -24
- package/dist/components/SchedulingCalendar.d.ts +142 -11
- package/dist/components/SchedulingCalendar.js +234 -57
- package/dist/components/SchedulingTimeline.d.ts +1 -0
- package/dist/components/SearchSuggest.d.ts +6 -2
- package/dist/components/SearchSuggest.js +4 -0
- package/dist/components/Segmented.d.ts +17 -3
- package/dist/components/Segmented.js +59 -5
- package/dist/components/Select.d.ts +66 -2
- package/dist/components/Select.js +800 -10
- package/dist/components/Spinner.d.ts +2 -1
- package/dist/components/Spinner.js +1 -1
- package/dist/components/StatusPill.d.ts +8 -2
- package/dist/components/StatusPill.js +2 -2
- package/dist/components/Steps.d.ts +28 -4
- package/dist/components/Steps.js +46 -16
- package/dist/components/TableControls.js +4 -3
- package/dist/components/Tabs.d.ts +7 -2
- package/dist/components/Tabs.js +14 -1
- package/dist/components/TextDiff.d.ts +49 -0
- package/dist/components/TextDiff.js +124 -0
- package/dist/{emoji-image-6mPZ15Zy.d.cts → emoji-image-DjCKpdwE.d.cts} +1 -1
- package/dist/emoji.cjs +169 -27
- package/dist/emoji.d.cts +3 -3
- package/dist/file-model.d.ts +56 -0
- package/dist/file-model.js +52 -0
- package/dist/filter-model.d.ts +112 -0
- package/dist/filter-model.js +103 -0
- package/dist/hooks/useAnchoredPosition.d.ts +24 -1
- package/dist/hooks/useAnchoredPosition.js +58 -8
- package/dist/hooks/useVirtualRows.d.ts +17 -2
- package/dist/hooks/useVirtualRows.js +6 -5
- package/dist/icons/Icon.d.ts +14 -14
- package/dist/index.cjs +4938 -1631
- package/dist/index.d.cts +1622 -178
- package/dist/index.d.ts +20 -6
- package/dist/index.js +88 -4
- package/dist/labels-context.d.ts +28 -0
- package/dist/{overlay-position-Cjv5E7mn.d.cts → overlay-position-2Afke51l.d.cts} +20 -0
- package/dist/overlay-position.d.ts +20 -0
- package/dist/overlay-position.js +17 -2
- package/dist/rich-text.cjs +169 -27
- package/dist/rich-text.d.cts +3 -3
- package/dist/rule-builder-model.d.ts +34 -2
- package/dist/rule-builder-model.js +48 -12
- package/dist/scheduling-calendar-model.d.ts +66 -0
- package/dist/scheduling-calendar-model.js +82 -0
- package/dist/select-model.d.ts +80 -0
- package/dist/select-model.js +109 -0
- package/dist/table-engine.d.ts +7 -0
- package/dist/table-engine.js +2 -1
- package/dist/text-diff-model.d.ts +71 -0
- package/dist/text-diff-model.js +261 -0
- package/package.json +1 -1
- package/dist/ChartCore-DzWe10bN.d.cts +0 -59
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useId, useRef, useState } from "react";
|
|
4
|
+
import { cx } from "../cx.js";
|
|
5
|
+
import { fillLabel } from "../fill-label.js";
|
|
6
|
+
import { formatFileSize, partitionFiles } from "../file-model.js";
|
|
7
|
+
import { useUixLabels } from "../labels-context.js";
|
|
8
|
+
import { Button } from "./Button.js";
|
|
9
|
+
import { Input } from "./Input.js";
|
|
10
|
+
import { Progress } from "./Progress.js";
|
|
11
|
+
import { FileIcon } from "./FileIcon.js";
|
|
12
|
+
const DEFAULT_FILE_UPLOAD_LABELS = {
|
|
13
|
+
title: "Drop files here",
|
|
14
|
+
choose: "Choose files",
|
|
15
|
+
drop: "Release to add the files",
|
|
16
|
+
list: "Files",
|
|
17
|
+
remove: "Remove {name}",
|
|
18
|
+
retry: "Retry {name}",
|
|
19
|
+
retryShort: "Retry",
|
|
20
|
+
queued: "Waiting",
|
|
21
|
+
uploading: "Uploading, {progress}%",
|
|
22
|
+
done: "Uploaded",
|
|
23
|
+
failed: "Upload failed",
|
|
24
|
+
alt: "Description of {name} for screen readers",
|
|
25
|
+
added: "{count} added",
|
|
26
|
+
wrongType: "{name} is not an accepted file type ({accept}).",
|
|
27
|
+
tooLarge: "{name} is larger than {max}.",
|
|
28
|
+
tooMany: "{name} was not added: at most {max} files."
|
|
29
|
+
};
|
|
30
|
+
function FileUpload({
|
|
31
|
+
items,
|
|
32
|
+
onFilesAdded,
|
|
33
|
+
onRemove,
|
|
34
|
+
onRetry,
|
|
35
|
+
onAltChange,
|
|
36
|
+
accept,
|
|
37
|
+
maxSize,
|
|
38
|
+
maxFiles,
|
|
39
|
+
multiple = true,
|
|
40
|
+
capture,
|
|
41
|
+
disabled,
|
|
42
|
+
hint,
|
|
43
|
+
locale,
|
|
44
|
+
sizeBase,
|
|
45
|
+
formatSize,
|
|
46
|
+
labels: labelOverrides,
|
|
47
|
+
className
|
|
48
|
+
}) {
|
|
49
|
+
const labels = { ...DEFAULT_FILE_UPLOAD_LABELS, ...useUixLabels().fileUpload, ...labelOverrides };
|
|
50
|
+
const sizeText = (bytes) => formatSize ? formatSize(bytes, locale) : formatFileSize(bytes, locale, { base: sizeBase });
|
|
51
|
+
const id = useId();
|
|
52
|
+
const inputRef = useRef(null);
|
|
53
|
+
const [over, setOver] = useState(false);
|
|
54
|
+
const [messages, setMessages] = useState([]);
|
|
55
|
+
const [announce, setAnnounce] = useState("");
|
|
56
|
+
const take = (list) => {
|
|
57
|
+
if (!list || disabled) return;
|
|
58
|
+
const files = Array.from(list);
|
|
59
|
+
const { accepted, rejected } = partitionFiles(files, { accept, maxSize, maxFiles, existing: items.length });
|
|
60
|
+
const max = maxSize !== void 0 ? sizeText(maxSize) : "";
|
|
61
|
+
setMessages(rejected.map(({ file, reason }) => fillLabel(
|
|
62
|
+
reason === "type" ? labels.wrongType : reason === "size" ? labels.tooLarge : labels.tooMany,
|
|
63
|
+
{ name: file.name, accept: accept ?? "", max: reason === "count" ? String(maxFiles) : max }
|
|
64
|
+
)));
|
|
65
|
+
setAnnounce(accepted.length ? fillLabel(labels.added, { count: accepted.length }) : "");
|
|
66
|
+
onFilesAdded(accepted, rejected);
|
|
67
|
+
};
|
|
68
|
+
const onDrop = (event) => {
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
setOver(false);
|
|
71
|
+
take(event.dataTransfer?.files ?? null);
|
|
72
|
+
};
|
|
73
|
+
return /* @__PURE__ */ jsxs("div", { className: cx("uix-file-upload", className), "data-disabled": disabled || void 0, children: [
|
|
74
|
+
/* @__PURE__ */ jsxs(
|
|
75
|
+
"div",
|
|
76
|
+
{
|
|
77
|
+
className: "uix-dropzone",
|
|
78
|
+
"data-dragover": over || void 0,
|
|
79
|
+
onDragOver: (event) => {
|
|
80
|
+
if (disabled) return;
|
|
81
|
+
event.preventDefault();
|
|
82
|
+
setOver(true);
|
|
83
|
+
},
|
|
84
|
+
onDragLeave: () => setOver(false),
|
|
85
|
+
onDrop,
|
|
86
|
+
onClick: (event) => {
|
|
87
|
+
if (event.target === event.currentTarget) inputRef.current?.click();
|
|
88
|
+
},
|
|
89
|
+
children: [
|
|
90
|
+
/* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.75, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M12 15V4M7 9l5-5 5 5M4 15v4a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-4" }) }),
|
|
91
|
+
/* @__PURE__ */ jsx("strong", { "aria-hidden": over || void 0, children: over ? labels.drop : labels.title }),
|
|
92
|
+
hint != null && /* @__PURE__ */ jsx("span", { className: "uix-file-upload__hint", id: `${id}-hint`, children: hint }),
|
|
93
|
+
/* @__PURE__ */ jsx(Button, { type: "button", size: "sm", disabled, "aria-describedby": hint != null ? `${id}-hint` : void 0, onClick: () => inputRef.current?.click(), children: labels.choose }),
|
|
94
|
+
/* @__PURE__ */ jsx(
|
|
95
|
+
"input",
|
|
96
|
+
{
|
|
97
|
+
ref: inputRef,
|
|
98
|
+
type: "file",
|
|
99
|
+
className: "uix-visually-hidden",
|
|
100
|
+
tabIndex: -1,
|
|
101
|
+
"aria-hidden": "true",
|
|
102
|
+
accept,
|
|
103
|
+
multiple,
|
|
104
|
+
capture,
|
|
105
|
+
disabled,
|
|
106
|
+
onChange: (event) => {
|
|
107
|
+
take(event.currentTarget.files);
|
|
108
|
+
event.currentTarget.value = "";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
)
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
),
|
|
115
|
+
messages.length > 0 && // The alert wraps the list: role="alert" on the <ul> itself replaced its list role and
|
|
116
|
+
// left the <li>s outside any list (axe `listitem`).
|
|
117
|
+
/* @__PURE__ */ jsx("div", { role: "alert", children: /* @__PURE__ */ jsx("ul", { className: "uix-file-upload__errors", children: messages.map((m, i) => /* @__PURE__ */ jsx("li", { children: m }, i)) }) }),
|
|
118
|
+
/* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", role: "status", "aria-live": "polite", children: announce }),
|
|
119
|
+
items.length > 0 && /* @__PURE__ */ jsx("ul", { className: "uix-filelist uix-file-upload__list", "aria-label": labels.list, children: items.map((item) => {
|
|
120
|
+
const statusText = item.status === "uploading" ? fillLabel(labels.uploading, { progress: item.progress ?? 0 }) : item.status === "done" ? labels.done : item.status === "error" ? labels.failed : labels.queued;
|
|
121
|
+
const image = !!item.previewUrl;
|
|
122
|
+
return /* @__PURE__ */ jsxs("li", { className: "uix-filelist__item uix-file-upload__item", "data-status": item.status, children: [
|
|
123
|
+
image ? /* @__PURE__ */ jsx("img", { className: "uix-file-upload__thumb", src: item.previewUrl, alt: "" }) : /* @__PURE__ */ jsx("span", { className: "uix-attachment__icon", children: /* @__PURE__ */ jsx(FileIcon, { name: item.name, type: item.type }) }),
|
|
124
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-file-upload__body", children: [
|
|
125
|
+
/* @__PURE__ */ jsx("span", { className: "uix-file-upload__name", children: item.name }),
|
|
126
|
+
/* @__PURE__ */ jsxs("span", { className: "uix-file-upload__meta", children: [
|
|
127
|
+
/* @__PURE__ */ jsx("span", { children: sizeText(item.size) }),
|
|
128
|
+
item.status !== "uploading" && /* @__PURE__ */ jsx("span", { className: "uix-file-upload__status", children: statusText })
|
|
129
|
+
] }),
|
|
130
|
+
item.status === "uploading" && /* @__PURE__ */ jsx(Progress, { value: item.progress, indeterminate: item.progress === void 0, max: 100, label: statusText }),
|
|
131
|
+
item.status === "error" && item.error != null && /* @__PURE__ */ jsx("span", { className: "uix-file-upload__error", children: item.error }),
|
|
132
|
+
image && onAltChange && /* @__PURE__ */ jsx(Input, { size: "sm", "aria-label": fillLabel(labels.alt, { name: item.name }), placeholder: fillLabel(labels.alt, { name: item.name }), value: item.alt ?? "", onChange: (event) => onAltChange(item.id, event.currentTarget.value) })
|
|
133
|
+
] }),
|
|
134
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-file-upload__actions", children: [
|
|
135
|
+
item.status === "error" && onRetry && /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", "aria-label": fillLabel(labels.retry, { name: item.name }), onClick: () => onRetry(item.id), children: labels.retryShort }),
|
|
136
|
+
onRemove && /* @__PURE__ */ jsx(Button, { type: "button", size: "xs", variant: "ghost", icon: true, "aria-label": fillLabel(labels.remove, { name: item.name }), onClick: () => onRemove(item.id), children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.75, strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M4 4l8 8M12 4l-8 8" }) }) })
|
|
137
|
+
] })
|
|
138
|
+
] }, item.id);
|
|
139
|
+
}) })
|
|
140
|
+
] });
|
|
141
|
+
}
|
|
142
|
+
export {
|
|
143
|
+
DEFAULT_FILE_UPLOAD_LABELS,
|
|
144
|
+
FileUpload
|
|
145
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { TextFilterOperator, NumberFilterOperator, FilterField, FilterValue } from '../filter-model.js';
|
|
3
|
+
|
|
4
|
+
interface FilterEditorLabels {
|
|
5
|
+
apply: string;
|
|
6
|
+
clear: string;
|
|
7
|
+
operator: string;
|
|
8
|
+
value: string;
|
|
9
|
+
from: string;
|
|
10
|
+
to: string;
|
|
11
|
+
/** Between two numbers: "and". */
|
|
12
|
+
and: string;
|
|
13
|
+
any: string;
|
|
14
|
+
yes: string;
|
|
15
|
+
no: string;
|
|
16
|
+
search: string;
|
|
17
|
+
searching: string;
|
|
18
|
+
noMatches: string;
|
|
19
|
+
searchFailed: string;
|
|
20
|
+
retry: string;
|
|
21
|
+
selectAll: string;
|
|
22
|
+
selectNone: string;
|
|
23
|
+
/** `{count}` chosen. */
|
|
24
|
+
selected: string;
|
|
25
|
+
/** Remove one chosen record. `{label}`. */
|
|
26
|
+
remove: string;
|
|
27
|
+
/** Names the row of `field.presets` chips. Optional so existing label objects still type-check; default "Presets". */
|
|
28
|
+
presets?: string;
|
|
29
|
+
operators: Record<TextFilterOperator | NumberFilterOperator, string>;
|
|
30
|
+
}
|
|
31
|
+
declare const DEFAULT_FILTER_EDITOR_LABELS: FilterEditorLabels;
|
|
32
|
+
interface FilterEditorProps {
|
|
33
|
+
field: FilterField;
|
|
34
|
+
/** Undefined means "not filtered yet". */
|
|
35
|
+
value: FilterValue | undefined;
|
|
36
|
+
onValueChange: (value: FilterValue) => void;
|
|
37
|
+
onApply: () => void;
|
|
38
|
+
onClear: () => void;
|
|
39
|
+
labels?: Partial<FilterEditorLabels>;
|
|
40
|
+
/** Show an option filter above an enum list once it has more options than this. Default 8. */
|
|
41
|
+
searchThreshold?: number;
|
|
42
|
+
/** Delay before a reference search runs, in ms. Default 250. */
|
|
43
|
+
searchDelay?: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Editor content for one typed column filter (HAR-1365; TENSOR C6, MOTUS C-1): an enum
|
|
47
|
+
* multi-select, text or number with a condition, a date range, yes/no, or records found by
|
|
48
|
+
* an async search. The product owns the popover trigger and the query; `summarizeFilter`
|
|
49
|
+
* turns the applied value into the chip text. `FilterPopover` stays for the one-string case.
|
|
50
|
+
* An enum field's `presets` (HAR-1505) render as toggle chips above its options. Words come
|
|
51
|
+
* from `labels`, or from `UixLabelsProvider`'s `filterEditor` entry.
|
|
52
|
+
*/
|
|
53
|
+
declare function FilterEditor({ field, value: valueProp, onValueChange, onApply, onClear, labels: labelOverrides, searchThreshold, searchDelay }: FilterEditorProps): react.JSX.Element;
|
|
54
|
+
|
|
55
|
+
export { DEFAULT_FILTER_EDITOR_LABELS, FilterEditor, type FilterEditorLabels, type FilterEditorProps };
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useId, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { Button } from "./Button.js";
|
|
5
|
+
import { Checkbox } from "./Checkbox.js";
|
|
6
|
+
import { Input } from "./Input.js";
|
|
7
|
+
import { Radio, RadioGroup } from "./Radio.js";
|
|
8
|
+
import { Select } from "./Select.js";
|
|
9
|
+
import { Chip } from "./Chip.js";
|
|
10
|
+
import { fillLabel } from "../fill-label.js";
|
|
11
|
+
import { useUixLabels } from "../labels-context.js";
|
|
12
|
+
import { foldForSearch } from "../search-suggest-model.js";
|
|
13
|
+
import { NUMBER_FILTER_OPERATORS, TEXT_FILTER_OPERATORS, emptyFilterValue, matchFilterPreset } from "../filter-model.js";
|
|
14
|
+
const DEFAULT_FILTER_EDITOR_LABELS = {
|
|
15
|
+
apply: "Apply",
|
|
16
|
+
clear: "Clear",
|
|
17
|
+
operator: "Condition",
|
|
18
|
+
value: "Value",
|
|
19
|
+
from: "From",
|
|
20
|
+
to: "To",
|
|
21
|
+
and: "and",
|
|
22
|
+
any: "Any",
|
|
23
|
+
yes: "Yes",
|
|
24
|
+
no: "No",
|
|
25
|
+
search: "Search",
|
|
26
|
+
searching: "Searching\u2026",
|
|
27
|
+
noMatches: "No matches.",
|
|
28
|
+
searchFailed: "Search failed.",
|
|
29
|
+
retry: "Try again",
|
|
30
|
+
selectAll: "Select all",
|
|
31
|
+
selectNone: "Clear selection",
|
|
32
|
+
selected: "{count} selected",
|
|
33
|
+
remove: "Remove {label}",
|
|
34
|
+
presets: "Presets",
|
|
35
|
+
operators: {
|
|
36
|
+
contains: "contains",
|
|
37
|
+
"not-contains": "does not contain",
|
|
38
|
+
is: "is",
|
|
39
|
+
"is-not": "is not",
|
|
40
|
+
"starts-with": "starts with",
|
|
41
|
+
eq: "equals",
|
|
42
|
+
neq: "does not equal",
|
|
43
|
+
lt: "less than",
|
|
44
|
+
lte: "at most",
|
|
45
|
+
gt: "more than",
|
|
46
|
+
gte: "at least",
|
|
47
|
+
between: "between"
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
function FilterEditor({ field, value: valueProp, onValueChange, onApply, onClear, labels: labelOverrides, searchThreshold = 8, searchDelay = 250 }) {
|
|
51
|
+
const provided = useUixLabels().filterEditor;
|
|
52
|
+
const labels = {
|
|
53
|
+
...DEFAULT_FILTER_EDITOR_LABELS,
|
|
54
|
+
...provided,
|
|
55
|
+
...labelOverrides,
|
|
56
|
+
operators: { ...DEFAULT_FILTER_EDITOR_LABELS.operators, ...provided?.operators, ...labelOverrides?.operators }
|
|
57
|
+
};
|
|
58
|
+
const id = useId();
|
|
59
|
+
const value = valueProp?.kind === field.kind ? valueProp : emptyFilterValue(field.kind);
|
|
60
|
+
let body = null;
|
|
61
|
+
switch (value.kind) {
|
|
62
|
+
case "enum":
|
|
63
|
+
body = /* @__PURE__ */ jsx(EnumBody, { field, value, onChange: onValueChange, labels, threshold: searchThreshold });
|
|
64
|
+
break;
|
|
65
|
+
case "reference":
|
|
66
|
+
body = /* @__PURE__ */ jsx(ReferenceBody, { field, value, onChange: onValueChange, labels, delay: searchDelay });
|
|
67
|
+
break;
|
|
68
|
+
case "text": {
|
|
69
|
+
const ops = field.operators ?? TEXT_FILTER_OPERATORS;
|
|
70
|
+
body = /* @__PURE__ */ jsxs("div", { className: "uix-filter-editor__row", children: [
|
|
71
|
+
ops.length > 1 && /* @__PURE__ */ jsx(Select, { size: "sm", "aria-label": labels.operator, value: value.operator, onChange: (e) => onValueChange({ ...value, operator: e.currentTarget.value }), children: ops.map((op) => /* @__PURE__ */ jsx("option", { value: op, children: labels.operators[op] }, op)) }),
|
|
72
|
+
/* @__PURE__ */ jsx(Input, { "aria-label": labels.value, value: value.text, onChange: (e) => onValueChange({ ...value, text: e.currentTarget.value }), onKeyDown: (e) => {
|
|
73
|
+
if (e.key === "Enter") onApply();
|
|
74
|
+
}, autoFocus: true })
|
|
75
|
+
] });
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case "number": {
|
|
79
|
+
const ops = field.operators ?? NUMBER_FILTER_OPERATORS;
|
|
80
|
+
const toNumber = (raw) => raw.trim() === "" ? void 0 : Number(raw);
|
|
81
|
+
body = /* @__PURE__ */ jsxs("div", { className: "uix-filter-editor__row", children: [
|
|
82
|
+
ops.length > 1 && /* @__PURE__ */ jsx(Select, { size: "sm", "aria-label": labels.operator, value: value.operator, onChange: (e) => onValueChange({ ...value, operator: e.currentTarget.value }), children: ops.map((op) => /* @__PURE__ */ jsx("option", { value: op, children: labels.operators[op] }, op)) }),
|
|
83
|
+
/* @__PURE__ */ jsx(Input, { type: "number", inputMode: "decimal", "aria-label": value.operator === "between" ? labels.from : labels.value, value: value.value ?? "", onChange: (e) => onValueChange({ ...value, value: toNumber(e.currentTarget.value) }), autoFocus: true }),
|
|
84
|
+
value.operator === "between" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
85
|
+
/* @__PURE__ */ jsx("span", { className: "uix-filter-editor__and", children: labels.and }),
|
|
86
|
+
/* @__PURE__ */ jsx(Input, { type: "number", inputMode: "decimal", "aria-label": labels.to, value: value.to ?? "", onChange: (e) => onValueChange({ ...value, to: toNumber(e.currentTarget.value) }) })
|
|
87
|
+
] }),
|
|
88
|
+
field.unit && /* @__PURE__ */ jsx("span", { className: "uix-filter-editor__unit", children: field.unit })
|
|
89
|
+
] });
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
case "date-range":
|
|
93
|
+
body = /* @__PURE__ */ jsxs("div", { className: "uix-filter-editor__row", children: [
|
|
94
|
+
/* @__PURE__ */ jsxs("label", { className: "uix-filter-editor__date", children: [
|
|
95
|
+
/* @__PURE__ */ jsx("span", { children: labels.from }),
|
|
96
|
+
/* @__PURE__ */ jsx(Input, { type: "date", value: value.from ?? "", max: value.to, onChange: (e) => onValueChange({ ...value, from: e.currentTarget.value || void 0 }) })
|
|
97
|
+
] }),
|
|
98
|
+
/* @__PURE__ */ jsxs("label", { className: "uix-filter-editor__date", children: [
|
|
99
|
+
/* @__PURE__ */ jsx("span", { children: labels.to }),
|
|
100
|
+
/* @__PURE__ */ jsx(Input, { type: "date", value: value.to ?? "", min: value.from, onChange: (e) => onValueChange({ ...value, to: e.currentTarget.value || void 0 }) })
|
|
101
|
+
] })
|
|
102
|
+
] });
|
|
103
|
+
break;
|
|
104
|
+
case "boolean": {
|
|
105
|
+
const pick = (v) => onValueChange({ kind: "boolean", value: v });
|
|
106
|
+
body = /* @__PURE__ */ jsxs(RadioGroup, { className: "uix-filter-editor__choices", children: [
|
|
107
|
+
/* @__PURE__ */ jsx(Radio, { name: `${id}-bool`, label: labels.any, checked: value.value === void 0, onChange: () => pick(void 0) }),
|
|
108
|
+
/* @__PURE__ */ jsx(Radio, { name: `${id}-bool`, label: field.trueLabel ?? labels.yes, checked: value.value === true, onChange: () => pick(true) }),
|
|
109
|
+
/* @__PURE__ */ jsx(Radio, { name: `${id}-bool`, label: field.falseLabel ?? labels.no, checked: value.value === false, onChange: () => pick(false) })
|
|
110
|
+
] });
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return /* @__PURE__ */ jsxs("div", { className: "uix-filter-popover uix-filter-editor", role: "group", "aria-labelledby": `${id}-label`, "data-kind": field.kind, children: [
|
|
115
|
+
/* @__PURE__ */ jsx("div", { id: `${id}-label`, className: "uix-filter-editor__label", children: field.label }),
|
|
116
|
+
body,
|
|
117
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-filter-popover__actions", children: [
|
|
118
|
+
/* @__PURE__ */ jsx(Button, { type: "button", size: "sm", onClick: onClear, children: labels.clear }),
|
|
119
|
+
/* @__PURE__ */ jsx(Button, { type: "button", size: "sm", variant: "primary", onClick: onApply, children: labels.apply })
|
|
120
|
+
] })
|
|
121
|
+
] });
|
|
122
|
+
}
|
|
123
|
+
function EnumBody({ field, value, onChange, labels, threshold }) {
|
|
124
|
+
const [query, setQuery] = useState("");
|
|
125
|
+
const options = field.options ?? [];
|
|
126
|
+
const shown = useMemo(() => {
|
|
127
|
+
const q = foldForSearch(query.trim()).text;
|
|
128
|
+
return q ? options.filter((o) => foldForSearch(o.label).text.includes(q)) : options;
|
|
129
|
+
}, [options, query]);
|
|
130
|
+
const selected = new Set(value.values);
|
|
131
|
+
const toggle = (v, on) => onChange({ kind: "enum", values: on ? [...value.values, v] : value.values.filter((x) => x !== v) });
|
|
132
|
+
const presets = field.presets ?? [];
|
|
133
|
+
const active = matchFilterPreset(field, value);
|
|
134
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
135
|
+
presets.length > 0 && /* @__PURE__ */ jsx("div", { className: "uix-chip-group uix-filter-editor__presets", role: "group", "aria-label": labels.presets ?? DEFAULT_FILTER_EDITOR_LABELS.presets, children: presets.map((p) => /* @__PURE__ */ jsx(Chip, { pressed: p === active, onPressedChange: () => onChange({ kind: "enum", values: [...p.values] }), children: p.label }, p.id)) }),
|
|
136
|
+
options.length > threshold && /* @__PURE__ */ jsx(Input, { type: "search", size: "sm", "aria-label": labels.search, placeholder: labels.search, value: query, onChange: (e) => setQuery(e.currentTarget.value), autoFocus: true }),
|
|
137
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-filter-editor__bulk", children: [
|
|
138
|
+
/* @__PURE__ */ jsx("span", { className: "uix-filter-editor__count", "aria-live": "polite", children: fillLabel(labels.selected, { count: value.values.length }) }),
|
|
139
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "link", size: "sm", onClick: () => onChange({ kind: "enum", values: [.../* @__PURE__ */ new Set([...value.values, ...shown.map((o) => o.value)])] }), children: labels.selectAll }),
|
|
140
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "link", size: "sm", onClick: () => onChange({ kind: "enum", values: [] }), children: labels.selectNone })
|
|
141
|
+
] }),
|
|
142
|
+
/* @__PURE__ */ jsx("div", { className: "uix-filter-editor__list", children: shown.length === 0 ? /* @__PURE__ */ jsx("p", { className: "uix-filter-editor__note", children: labels.noMatches }) : shown.map((o) => /* @__PURE__ */ jsx(Checkbox, { label: o.label, checked: selected.has(o.value), onChange: (e) => toggle(o.value, e.currentTarget.checked) }, o.value)) })
|
|
143
|
+
] });
|
|
144
|
+
}
|
|
145
|
+
function ReferenceBody({ field, value, onChange, labels, delay }) {
|
|
146
|
+
const [query, setQuery] = useState("");
|
|
147
|
+
const [state, setState] = useState({ status: "idle", results: [] });
|
|
148
|
+
const [attempt, setAttempt] = useState(0);
|
|
149
|
+
const latest = useRef(0);
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
if (!field.onSearch) return;
|
|
152
|
+
const run = ++latest.current;
|
|
153
|
+
const timer = window.setTimeout(() => {
|
|
154
|
+
setState((s) => ({ ...s, status: "loading" }));
|
|
155
|
+
field.onSearch(query.trim()).then(
|
|
156
|
+
(results) => {
|
|
157
|
+
if (run === latest.current) setState({ status: "done", results });
|
|
158
|
+
},
|
|
159
|
+
() => {
|
|
160
|
+
if (run === latest.current) setState({ status: "error", results: [] });
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
}, delay);
|
|
164
|
+
return () => window.clearTimeout(timer);
|
|
165
|
+
}, [query, attempt, field, delay]);
|
|
166
|
+
const chosen = new Map(value.values.map((v) => [v.value, v]));
|
|
167
|
+
const toggle = (choice, on) => onChange({ kind: "reference", values: on ? [...value.values, choice] : value.values.filter((v) => v.value !== choice.value) });
|
|
168
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
169
|
+
value.values.length > 0 && /* @__PURE__ */ jsx("div", { className: "uix-chip-group", role: "group", "aria-label": fillLabel(labels.selected, { count: value.values.length }), children: value.values.map((v) => /* @__PURE__ */ jsx(Chip, { removeLabel: fillLabel(labels.remove, { label: v.label }), onRemove: () => toggle(v, false), children: v.label }, v.value)) }),
|
|
170
|
+
/* @__PURE__ */ jsx(Input, { type: "search", size: "sm", "aria-label": labels.search, placeholder: labels.search, value: query, onChange: (e) => setQuery(e.currentTarget.value), autoFocus: true }),
|
|
171
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-filter-editor__list", "aria-busy": state.status === "loading" || void 0, children: [
|
|
172
|
+
state.status === "loading" && /* @__PURE__ */ jsx("p", { className: "uix-filter-editor__note", role: "status", children: labels.searching }),
|
|
173
|
+
state.status === "error" && /* @__PURE__ */ jsxs("p", { className: "uix-filter-editor__note", role: "alert", children: [
|
|
174
|
+
labels.searchFailed,
|
|
175
|
+
" ",
|
|
176
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "link", size: "sm", onClick: () => setAttempt((n) => n + 1), children: labels.retry })
|
|
177
|
+
] }),
|
|
178
|
+
state.status === "done" && state.results.length === 0 && /* @__PURE__ */ jsx("p", { className: "uix-filter-editor__note", children: labels.noMatches }),
|
|
179
|
+
state.status === "done" && state.results.map((r) => /* @__PURE__ */ jsx(Checkbox, { label: r.label, checked: chosen.has(r.value), onChange: (e) => toggle(r, e.currentTarget.checked) }, r.value))
|
|
180
|
+
] })
|
|
181
|
+
] });
|
|
182
|
+
}
|
|
183
|
+
export {
|
|
184
|
+
DEFAULT_FILTER_EDITOR_LABELS,
|
|
185
|
+
FilterEditor
|
|
186
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { HTMLAttributes, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
type HeartbeatState = 'live' | 'idle' | 'warning' | 'danger';
|
|
5
|
+
interface HeartbeatProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'children'> {
|
|
6
|
+
/**
|
|
7
|
+
* `live` (default): a green dot with a pulsing ring. `warning` / `danger`: the same in that
|
|
8
|
+
* tone. `idle`: a grey dot, no ring.
|
|
9
|
+
*/
|
|
10
|
+
state?: HeartbeatState;
|
|
11
|
+
/**
|
|
12
|
+
* What the dot means, for assistive technology ("Live", "Connection lost"). Without it the
|
|
13
|
+
* dot is decorative (`aria-hidden`): say the state in text beside it, as `LiveIndicator` does.
|
|
14
|
+
*/
|
|
15
|
+
label?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The pulsing status dot over `.uix-heartbeat` (HAR-1606), so products stop hand-writing its
|
|
19
|
+
* three spans. Decorative unless it has a `label`. The ring stops under
|
|
20
|
+
* `prefers-reduced-motion` (the kit's global motion guard); the dot and its colour stay.
|
|
21
|
+
*/
|
|
22
|
+
declare function Heartbeat({ state, label, className, ...props }: HeartbeatProps): react.JSX.Element;
|
|
23
|
+
interface LiveIndicatorProps extends HTMLAttributes<HTMLSpanElement> {
|
|
24
|
+
state?: HeartbeatState;
|
|
25
|
+
/** The state in words: "Live", "Paused", "Reconnecting…". It is what a screen reader gets. */
|
|
26
|
+
children?: ReactNode;
|
|
27
|
+
}
|
|
28
|
+
/** A `Heartbeat` with its state in text beside it, over `.uix-live`. */
|
|
29
|
+
declare function LiveIndicator({ state, children, className, ...props }: LiveIndicatorProps): react.JSX.Element;
|
|
30
|
+
|
|
31
|
+
export { Heartbeat, type HeartbeatProps, type HeartbeatState, LiveIndicator, type LiveIndicatorProps };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from "../cx.js";
|
|
3
|
+
function Heartbeat({ state = "live", label, className, ...props }) {
|
|
4
|
+
return /* @__PURE__ */ jsxs(
|
|
5
|
+
"span",
|
|
6
|
+
{
|
|
7
|
+
className: cx("uix-heartbeat", state !== "live" && `uix-heartbeat--${state}`, className),
|
|
8
|
+
role: label ? "img" : void 0,
|
|
9
|
+
"aria-label": label,
|
|
10
|
+
"aria-hidden": label ? void 0 : true,
|
|
11
|
+
...props,
|
|
12
|
+
children: [
|
|
13
|
+
/* @__PURE__ */ jsx("span", { className: "uix-heartbeat__ping" }),
|
|
14
|
+
/* @__PURE__ */ jsx("span", { className: "uix-heartbeat__dot" })
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
function LiveIndicator({ state = "live", children, className, ...props }) {
|
|
20
|
+
return /* @__PURE__ */ jsxs("span", { className: cx("uix-live", className), ...props, children: [
|
|
21
|
+
/* @__PURE__ */ jsx(Heartbeat, { state }),
|
|
22
|
+
children
|
|
23
|
+
] });
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
Heartbeat,
|
|
27
|
+
LiveIndicator
|
|
28
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
interface LightboxItem {
|
|
5
|
+
src: string;
|
|
6
|
+
/** Alternative text for an image; the video's name for a video. */
|
|
7
|
+
alt: string;
|
|
8
|
+
caption?: ReactNode;
|
|
9
|
+
kind?: 'image' | 'video';
|
|
10
|
+
/** Intrinsic size, so an SVG or a slow image keeps its box. */
|
|
11
|
+
width?: number;
|
|
12
|
+
height?: number;
|
|
13
|
+
}
|
|
14
|
+
interface LightboxLabels {
|
|
15
|
+
region: string;
|
|
16
|
+
previous: string;
|
|
17
|
+
next: string;
|
|
18
|
+
close: string;
|
|
19
|
+
/** `{index}`, `{total}`. */
|
|
20
|
+
counter: string;
|
|
21
|
+
zoomIn: string;
|
|
22
|
+
zoomOut: string;
|
|
23
|
+
}
|
|
24
|
+
declare const DEFAULT_LIGHTBOX_LABELS: LightboxLabels;
|
|
25
|
+
interface LightboxProps {
|
|
26
|
+
items: readonly LightboxItem[];
|
|
27
|
+
/** The item on show (0-based). */
|
|
28
|
+
index: number;
|
|
29
|
+
onIndexChange: (index: number) => void;
|
|
30
|
+
open: boolean;
|
|
31
|
+
onClose: () => void;
|
|
32
|
+
/** Wrap from the last item to the first. Default false. */
|
|
33
|
+
loop?: boolean;
|
|
34
|
+
labels?: Partial<LightboxLabels>;
|
|
35
|
+
className?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A full-screen media viewer over `.uix-lightbox` (HAR-1367; TENSOR C12 `capture-viewer.tsx`,
|
|
39
|
+
* MOTUS C-8 programme gallery): previous/next buttons, ←/→ and Home/End, swipe on touch, an
|
|
40
|
+
* announced "n of total" counter, a caption, and a fit/actual-size toggle. Native `<dialog>`,
|
|
41
|
+
* so focus stays inside and Escape closes; focus returns to the control that opened it.
|
|
42
|
+
*/
|
|
43
|
+
declare function Lightbox({ items, index, onIndexChange, open, onClose, loop, labels: labelOverrides, className }: LightboxProps): react.JSX.Element;
|
|
44
|
+
|
|
45
|
+
export { DEFAULT_LIGHTBOX_LABELS, Lightbox, type LightboxItem, type LightboxLabels, type LightboxProps };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef, useState } from "react";
|
|
4
|
+
import { cx } from "../cx.js";
|
|
5
|
+
import { fillLabel } from "../fill-label.js";
|
|
6
|
+
import { useDialog } from "../hooks/useDialog.js";
|
|
7
|
+
const DEFAULT_LIGHTBOX_LABELS = {
|
|
8
|
+
region: "Media viewer",
|
|
9
|
+
previous: "Previous",
|
|
10
|
+
next: "Next",
|
|
11
|
+
close: "Close",
|
|
12
|
+
counter: "{index} of {total}",
|
|
13
|
+
zoomIn: "Show actual size",
|
|
14
|
+
zoomOut: "Fit to screen"
|
|
15
|
+
};
|
|
16
|
+
const Chevron = ({ dir }) => /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: dir === "left" ? "m15 18-6-6 6-6" : "m9 18 6-6-6-6" }) });
|
|
17
|
+
function Lightbox({ items, index, onIndexChange, open, onClose, loop = false, labels: labelOverrides, className }) {
|
|
18
|
+
const labels = { ...DEFAULT_LIGHTBOX_LABELS, ...labelOverrides };
|
|
19
|
+
const ref = useDialog(open, onClose);
|
|
20
|
+
const closeRef = useRef(null);
|
|
21
|
+
const returnTo = useRef(null);
|
|
22
|
+
const swipe = useRef(null);
|
|
23
|
+
const [zoomed, setZoomed] = useState(false);
|
|
24
|
+
const total = items.length;
|
|
25
|
+
const current = items[Math.min(Math.max(index, 0), Math.max(total - 1, 0))];
|
|
26
|
+
const hasPrev = loop ? total > 1 : index > 0;
|
|
27
|
+
const hasNext = loop ? total > 1 : index < total - 1;
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (open) {
|
|
30
|
+
returnTo.current = document.activeElement;
|
|
31
|
+
closeRef.current?.focus();
|
|
32
|
+
} else if (returnTo.current) {
|
|
33
|
+
returnTo.current.focus?.();
|
|
34
|
+
returnTo.current = null;
|
|
35
|
+
}
|
|
36
|
+
}, [open]);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
setZoomed(false);
|
|
39
|
+
}, [index]);
|
|
40
|
+
const go = (to) => {
|
|
41
|
+
if (total === 0) return;
|
|
42
|
+
const next = loop ? (to + total) % total : Math.min(Math.max(to, 0), total - 1);
|
|
43
|
+
if (next !== index) onIndexChange(next);
|
|
44
|
+
};
|
|
45
|
+
const onKeyDown = (event) => {
|
|
46
|
+
if (event.target.closest("video")) return;
|
|
47
|
+
if (event.key === "ArrowLeft") {
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
go(index - 1);
|
|
50
|
+
} else if (event.key === "ArrowRight") {
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
go(index + 1);
|
|
53
|
+
} else if (event.key === "Home") {
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
go(0);
|
|
56
|
+
} else if (event.key === "End") {
|
|
57
|
+
event.preventDefault();
|
|
58
|
+
go(total - 1);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const onPointerDown = (event) => {
|
|
62
|
+
if (event.pointerType !== "mouse") swipe.current = { x: event.clientX, id: event.pointerId };
|
|
63
|
+
};
|
|
64
|
+
const onPointerUp = (event) => {
|
|
65
|
+
const start = swipe.current;
|
|
66
|
+
swipe.current = null;
|
|
67
|
+
if (!start || start.id !== event.pointerId || zoomed) return;
|
|
68
|
+
const dx = event.clientX - start.x;
|
|
69
|
+
if (Math.abs(dx) > 48) go(index + (dx < 0 ? 1 : -1));
|
|
70
|
+
};
|
|
71
|
+
return /* @__PURE__ */ jsxs(
|
|
72
|
+
"dialog",
|
|
73
|
+
{
|
|
74
|
+
ref,
|
|
75
|
+
className: cx("uix-lightbox", "uix-lightbox--gallery", className),
|
|
76
|
+
"aria-label": labels.region,
|
|
77
|
+
onKeyDown,
|
|
78
|
+
onClick: (event) => {
|
|
79
|
+
if (event.target === event.currentTarget) onClose();
|
|
80
|
+
},
|
|
81
|
+
children: [
|
|
82
|
+
current && /* @__PURE__ */ jsxs("figure", { className: "uix-lightbox__figure", children: [
|
|
83
|
+
/* @__PURE__ */ jsx("div", { className: "uix-lightbox__stage", "data-zoomed": zoomed || void 0, onPointerDown, onPointerUp, children: current.kind === "video" ? /* @__PURE__ */ jsx("video", { className: "uix-lightbox__media", src: current.src, controls: true, "aria-label": current.alt, width: current.width, height: current.height }) : /* @__PURE__ */ jsx("img", { className: "uix-lightbox__media", src: current.src, alt: current.alt, width: current.width, height: current.height }) }),
|
|
84
|
+
/* @__PURE__ */ jsxs("figcaption", { className: "uix-lightbox__bar", children: [
|
|
85
|
+
/* @__PURE__ */ jsx("span", { className: "uix-lightbox__counter", "aria-live": "polite", children: fillLabel(labels.counter, { index: index + 1, total }) }),
|
|
86
|
+
current.caption != null && /* @__PURE__ */ jsx("span", { className: "uix-lightbox__caption", children: current.caption }),
|
|
87
|
+
current.kind !== "video" && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-lightbox__tool", "aria-pressed": zoomed, onClick: () => setZoomed((z) => !z), children: zoomed ? labels.zoomOut : labels.zoomIn })
|
|
88
|
+
] })
|
|
89
|
+
] }),
|
|
90
|
+
total > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
91
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-lightbox__nav uix-lightbox__nav--prev", "aria-label": labels.previous, disabled: !hasPrev, onClick: () => go(index - 1), children: /* @__PURE__ */ jsx(Chevron, { dir: "left" }) }),
|
|
92
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-lightbox__nav uix-lightbox__nav--next", "aria-label": labels.next, disabled: !hasNext, onClick: () => go(index + 1), children: /* @__PURE__ */ jsx(Chevron, { dir: "right" }) })
|
|
93
|
+
] }),
|
|
94
|
+
/* @__PURE__ */ jsx("button", { ref: closeRef, type: "button", className: "uix-lightbox__close", "aria-label": labels.close, onClick: onClose, children: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: 1.75, strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M4 4l8 8M12 4l-8 8" }) }) })
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
export {
|
|
100
|
+
DEFAULT_LIGHTBOX_LABELS,
|
|
101
|
+
Lightbox
|
|
102
|
+
};
|