@tensor_1/react 2.23.0 → 2.24.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 +20 -0
- package/dist/components/DiffViewer.d.ts +15 -1
- package/dist/components/DiffViewer.js +11 -6
- package/dist/components/EditorIcons.d.ts +1 -0
- package/dist/components/EditorIcons.js +2 -1
- package/dist/components/TableControls.d.ts +46 -7
- package/dist/components/TableControls.js +326 -39
- package/dist/emoji.cjs +2 -1
- package/dist/index.cjs +439 -148
- package/dist/index.d.cts +61 -8
- package/dist/index.d.ts +1 -1
- package/dist/rich-text.cjs +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -205,4 +205,24 @@ Radial mode still shortens long labels to fit its ring, but every node carries i
|
|
|
205
205
|
<CopyButton value={email} label={t('copyEmail')} copiedLabel={t('copied')} failedLabel={t('copyFailed')} />
|
|
206
206
|
```
|
|
207
207
|
|
|
208
|
+
### DiffViewer words and control size
|
|
209
|
+
|
|
210
|
+
- **Every word is a label.** `labels` (a `Partial<DiffViewerLabels>`, English defaults in `DEFAULT_DIFF_VIEWER_LABELS`) covers the version names, group titles, the summary, the live progress text, and the action buttons.
|
|
211
|
+
- **Each action is named for its entry.** The visible words come from `acceptIncoming` / `keepCurrent` / `markPending`. The accessible names come from `acceptIncomingFor` / `keepCurrentFor` / `markPendingFor`, e.g. "Accept incoming for $.limit". Each entry's three buttons sit in a group named by `resolve`. When you translate a name, keep the visible word inside it, so people using speech input can say what they see (WCAG 2.5.3).
|
|
212
|
+
- **`controlSize`** sets the height of the action buttons and the entry rows. `sm` (the default) is the compact 28px kit size. `md` follows `--uix-control-h`, so a theme with 60px controls gets 60px actions. `lg` is 44px.
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
<DiffViewer
|
|
216
|
+
base={published} current={draft} incoming={remote}
|
|
217
|
+
controlSize="md"
|
|
218
|
+
labels={{
|
|
219
|
+
acceptIncoming: t('acceptIncoming'), acceptIncomingFor: t('acceptIncomingFor'), // 'Prihvati dolazno za {path}'
|
|
220
|
+
keepCurrent: t('keepCurrent'), keepCurrentFor: t('keepCurrentFor'),
|
|
221
|
+
markPending: t('markPending'), markPendingFor: t('markPendingFor'),
|
|
222
|
+
resolve: t('resolve'), /* …the rest of DiffViewerLabels */
|
|
223
|
+
}}
|
|
224
|
+
onResolutionChange={record}
|
|
225
|
+
/>
|
|
226
|
+
```
|
|
227
|
+
|
|
208
228
|
Ships **ESM + CJS + types**, with per-file `"use client"` so it's safe under React Server Components. Part of the **[UIx v2 styleguide](https://github.com/COx-ORG-H/UIx)**.
|
|
@@ -6,6 +6,11 @@ import { DiffEntry, DiffResolution } from '../diff-model.js';
|
|
|
6
6
|
* Every word the diff viewer renders (TENSOR RX-125, UIX-12). `base` / `current` /
|
|
7
7
|
* `incoming` keep their meaning from before; `{path}`, `{version}`, `{resolved}` and
|
|
8
8
|
* `{total}` are placeholders.
|
|
9
|
+
*
|
|
10
|
+
* `acceptIncomingFor` / `keepCurrentFor` / `markPendingFor` are the accessible names of
|
|
11
|
+
* the three action buttons, so a screen reader hears which entry each one resolves. Keep
|
|
12
|
+
* the visible word inside the translated name (WCAG 2.5.3 Label in Name): speech-input
|
|
13
|
+
* users say what they see.
|
|
9
14
|
*/
|
|
10
15
|
interface DiffViewerLabels {
|
|
11
16
|
base: string;
|
|
@@ -26,6 +31,9 @@ interface DiffViewerLabels {
|
|
|
26
31
|
acceptIncoming: string;
|
|
27
32
|
keepCurrent: string;
|
|
28
33
|
markPending: string;
|
|
34
|
+
acceptIncomingFor: string;
|
|
35
|
+
keepCurrentFor: string;
|
|
36
|
+
markPendingFor: string;
|
|
29
37
|
resolutionAccept: string;
|
|
30
38
|
resolutionSkip: string;
|
|
31
39
|
resolutionPending: string;
|
|
@@ -41,9 +49,15 @@ interface DiffViewerProps {
|
|
|
41
49
|
resolutions?: Record<string, DiffResolution>;
|
|
42
50
|
onResolutionChange?: (path: string, resolution: DiffResolution) => void;
|
|
43
51
|
labels?: Partial<DiffViewerLabels>;
|
|
52
|
+
/**
|
|
53
|
+
* Height of the resolution buttons and the entry disclosure rows. `sm` (the default) is
|
|
54
|
+
* the compact 28px kit size; `md` follows `--uix-control-h`, so a theme that sets its
|
|
55
|
+
* own control height (for example 60px touch targets) gets it here too; `lg` is 44px.
|
|
56
|
+
*/
|
|
57
|
+
controlSize?: 'sm' | 'md' | 'lg';
|
|
44
58
|
className?: string;
|
|
45
59
|
}
|
|
46
60
|
/** Three-way configuration diff with per-entry keyboard-operable resolution. */
|
|
47
|
-
declare function DiffViewer({ base, current, incoming, entries: providedEntries, resolutions: controlledResolutions, onResolutionChange, labels: labelOverrides, className, }: DiffViewerProps): react.JSX.Element;
|
|
61
|
+
declare function DiffViewer({ base, current, incoming, entries: providedEntries, resolutions: controlledResolutions, onResolutionChange, labels: labelOverrides, controlSize, className, }: DiffViewerProps): react.JSX.Element;
|
|
48
62
|
|
|
49
63
|
export { DEFAULT_DIFF_VIEWER_LABELS, DiffViewer, type DiffViewerLabels, type DiffViewerProps };
|
|
@@ -24,6 +24,9 @@ const DEFAULT_DIFF_VIEWER_LABELS = {
|
|
|
24
24
|
acceptIncoming: "Accept incoming",
|
|
25
25
|
keepCurrent: "Keep current",
|
|
26
26
|
markPending: "Mark pending",
|
|
27
|
+
acceptIncomingFor: "Accept incoming for {path}",
|
|
28
|
+
keepCurrentFor: "Keep current for {path}",
|
|
29
|
+
markPendingFor: "Mark pending for {path}",
|
|
27
30
|
resolutionAccept: "accepted",
|
|
28
31
|
resolutionSkip: "kept",
|
|
29
32
|
resolutionPending: "pending",
|
|
@@ -40,10 +43,12 @@ function DiffViewer({
|
|
|
40
43
|
resolutions: controlledResolutions,
|
|
41
44
|
onResolutionChange,
|
|
42
45
|
labels: labelOverrides,
|
|
46
|
+
controlSize = "sm",
|
|
43
47
|
className
|
|
44
48
|
}) {
|
|
45
49
|
const labels = { ...DEFAULT_DIFF_VIEWER_LABELS, ...labelOverrides };
|
|
46
50
|
const resolutionLabel = { accept: labels.resolutionAccept, skip: labels.resolutionSkip, pending: labels.resolutionPending };
|
|
51
|
+
const buttonSize = controlSize === "md" ? void 0 : `uix-btn--${controlSize}`;
|
|
47
52
|
const id = useId();
|
|
48
53
|
const entries = useMemo(() => providedEntries ?? buildThreeWayDiff(base, current, incoming), [providedEntries, base, current, incoming]);
|
|
49
54
|
const [internalResolutions, setInternalResolutions] = useState({});
|
|
@@ -57,8 +62,8 @@ function DiffViewer({
|
|
|
57
62
|
/* @__PURE__ */ jsx("h3", { children: labels.noDifferences }),
|
|
58
63
|
/* @__PURE__ */ jsx("p", { children: labels.noDifferencesDetail })
|
|
59
64
|
] });
|
|
60
|
-
return /* @__PURE__ */ jsxs("section", { className: cx("uix-diff-viewer", className), "aria-label": labels.region, children: [
|
|
61
|
-
/* @__PURE__ */ jsxs("div", { className: "uix-diff-viewer__summary", "aria-label": labels.summary, children: [
|
|
65
|
+
return /* @__PURE__ */ jsxs("section", { className: cx("uix-diff-viewer", className), "aria-label": labels.region, "data-control-size": controlSize, children: [
|
|
66
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-diff-viewer__summary", role: "group", "aria-label": labels.summary, children: [
|
|
62
67
|
GROUPS.map((kind) => /* @__PURE__ */ jsxs("span", { children: [
|
|
63
68
|
/* @__PURE__ */ jsx("b", { children: summary[kind] }),
|
|
64
69
|
" ",
|
|
@@ -95,10 +100,10 @@ function DiffViewer({
|
|
|
95
100
|
/* @__PURE__ */ jsx("strong", { children: labels[version] }),
|
|
96
101
|
/* @__PURE__ */ jsx("pre", { children: renderValue(entry[version], labels.notPresent) })
|
|
97
102
|
] }, version)) }),
|
|
98
|
-
/* @__PURE__ */ jsxs("div", { className: "uix-diff-viewer__actions", "aria-label": fillLabel(labels.resolve, { path: entry.path }), children: [
|
|
99
|
-
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--primary
|
|
100
|
-
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--secondary
|
|
101
|
-
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost
|
|
103
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-diff-viewer__actions", role: "group", "aria-label": fillLabel(labels.resolve, { path: entry.path }), children: [
|
|
104
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: cx("uix-btn uix-btn--primary", buttonSize), "aria-label": fillLabel(labels.acceptIncomingFor, { path: entry.path }), "aria-pressed": resolution === "accept", onClick: () => resolve(entry.path, "accept"), children: labels.acceptIncoming }),
|
|
105
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: cx("uix-btn uix-btn--secondary", buttonSize), "aria-label": fillLabel(labels.keepCurrentFor, { path: entry.path }), "aria-pressed": resolution === "skip", onClick: () => resolve(entry.path, "skip"), children: labels.keepCurrent }),
|
|
106
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: cx("uix-btn uix-btn--ghost", buttonSize), "aria-label": fillLabel(labels.markPendingFor, { path: entry.path }), "aria-pressed": resolution === "pending", onClick: () => resolve(entry.path, "pending"), children: labels.markPending })
|
|
102
107
|
] })
|
|
103
108
|
] }, entry.path);
|
|
104
109
|
})
|
|
@@ -31,6 +31,7 @@ declare const PATHS: {
|
|
|
31
31
|
readonly richMode: "M14 2v4a2 2 0 0 0 2 2h4M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7ZM8 13h8M8 17h5";
|
|
32
32
|
readonly clock: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20ZM12 6v6l4 2";
|
|
33
33
|
readonly grip: "M9 5h.01M9 12h.01M9 19h.01M15 5h.01M15 12h.01M15 19h.01";
|
|
34
|
+
readonly more: "M5 12h.01M12 12h.01M19 12h.01";
|
|
34
35
|
};
|
|
35
36
|
type EditorIconName = keyof typeof PATHS;
|
|
36
37
|
declare function EditorIcon({ name, ...props }: {
|
|
@@ -28,7 +28,8 @@ const PATHS = {
|
|
|
28
28
|
sourceMode: "M10 12.5 8 15l2 2.5M14 12.5l2 2.5-2 2.5M14 2v4a2 2 0 0 0 2 2h4M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z",
|
|
29
29
|
richMode: "M14 2v4a2 2 0 0 0 2 2h4M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7ZM8 13h8M8 17h5",
|
|
30
30
|
clock: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20ZM12 6v6l4 2",
|
|
31
|
-
grip: "M9 5h.01M9 12h.01M9 19h.01M15 5h.01M15 12h.01M15 19h.01"
|
|
31
|
+
grip: "M9 5h.01M9 12h.01M9 19h.01M15 5h.01M15 12h.01M15 19h.01",
|
|
32
|
+
more: "M5 12h.01M12 12h.01M19 12h.01"
|
|
32
33
|
};
|
|
33
34
|
function EditorIcon({ name, ...props }) {
|
|
34
35
|
return /* @__PURE__ */ jsx(
|
|
@@ -6,15 +6,40 @@ interface ViewMenuColumn {
|
|
|
6
6
|
label: ReactNode;
|
|
7
7
|
visible: boolean;
|
|
8
8
|
disabled?: boolean;
|
|
9
|
+
/** Always shown (e.g. the record id): the row has no checkbox and no Hide / Show, its name sits in the checkbox's slot. */
|
|
10
|
+
required?: boolean;
|
|
11
|
+
/** Plain-text name for the `{label}` templates in `columnLabels` when `label` is not a string. */
|
|
12
|
+
textLabel?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Words for the column rows. `{label}` is replaced by the column name; `moved` also gets
|
|
16
|
+
* `{position}` and `{count}`. Every key has an English default.
|
|
17
|
+
*/
|
|
18
|
+
interface ViewMenuColumnLabels {
|
|
19
|
+
/** Name of a row's ⋯ menu button and of the menu. Default `Column actions: {label}`. */
|
|
20
|
+
rowActions: string;
|
|
21
|
+
/** Name of a row's drag grip. Default `Drag to reorder: {label}`. */
|
|
22
|
+
reorder: string;
|
|
23
|
+
moveUp: string;
|
|
24
|
+
moveDown: string;
|
|
25
|
+
hide: string;
|
|
26
|
+
show: string;
|
|
27
|
+
/** Tooltip on a required column's name. Default `Always shown`. */
|
|
28
|
+
required: string;
|
|
29
|
+
/** Polite announcement after a move. Default `{label} moved to position {position} of {count}`. */
|
|
30
|
+
moved: string;
|
|
9
31
|
}
|
|
10
32
|
interface ViewMenuProps {
|
|
11
|
-
density
|
|
12
|
-
|
|
13
|
-
|
|
33
|
+
/** The density section renders when `densityOptions` and `onDensityChange` are given. */
|
|
34
|
+
density?: string;
|
|
35
|
+
densityLabel?: ReactNode;
|
|
36
|
+
densityOptions?: readonly {
|
|
14
37
|
value: string;
|
|
15
38
|
label: ReactNode;
|
|
16
39
|
}[];
|
|
17
|
-
onDensityChange
|
|
40
|
+
onDensityChange?: (value: string) => void;
|
|
41
|
+
/** Optional title over the zebra / freeze switches. */
|
|
42
|
+
displayLabel?: ReactNode;
|
|
18
43
|
zebra?: {
|
|
19
44
|
checked: boolean;
|
|
20
45
|
label: ReactNode;
|
|
@@ -28,9 +53,23 @@ interface ViewMenuProps {
|
|
|
28
53
|
columns?: readonly ViewMenuColumn[];
|
|
29
54
|
columnsLabel?: ReactNode;
|
|
30
55
|
onColumnVisibilityChange?: (id: string, visible: boolean) => void;
|
|
56
|
+
/**
|
|
57
|
+
* Turns on reordering: every column row gets a drag grip (pointer; ArrowUp / ArrowDown once
|
|
58
|
+
* focused) and a ⋯ menu with Move up · Move down · Hide / Show, which is the keyboard path.
|
|
59
|
+
* Receives every column id, first to last. The order is the application's to persist.
|
|
60
|
+
*/
|
|
61
|
+
onReorder?: (orderedIds: string[]) => void;
|
|
62
|
+
columnLabels?: Partial<ViewMenuColumnLabels>;
|
|
63
|
+
/** Pinned under the columns, e.g. a Reset sort `.uix-menu__item` button. */
|
|
64
|
+
footer?: ReactNode;
|
|
65
|
+
className?: string;
|
|
31
66
|
}
|
|
32
|
-
/**
|
|
33
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Controlled table presentation panel with its own surface: density, zebra / freeze and a column
|
|
69
|
+
* list under sticky section titles. A column row is grip · checkbox · name · ⋯ (grip and ⋯ with
|
|
70
|
+
* `onReorder`). Usable inside a Popover or any overlay shell, including one that adds no chrome.
|
|
71
|
+
*/
|
|
72
|
+
declare function ViewMenu({ density, densityLabel, densityOptions, onDensityChange, displayLabel, zebra, freeze, columns, columnsLabel, onColumnVisibilityChange, onReorder, columnLabels, footer, className, }: ViewMenuProps): react.JSX.Element;
|
|
34
73
|
interface FilterOption {
|
|
35
74
|
value: string;
|
|
36
75
|
label: string;
|
|
@@ -102,4 +141,4 @@ type SavedViewMenuProps = SavedViewMenuBaseProps & ({
|
|
|
102
141
|
*/
|
|
103
142
|
declare function SavedViewMenu({ items, sections, onSelect, onPinChange, pinLabel, unpinLabel, onReorder, reorderLabel, emptyLabel, actions, footer }: SavedViewMenuProps): react.JSX.Element;
|
|
104
143
|
|
|
105
|
-
export { type FilterOption, FilterPopover, type FilterPopoverProps, type SavedViewItem, SavedViewMenu, type SavedViewMenuProps, type SavedViewSection, ViewMenu, type ViewMenuColumn, type ViewMenuProps };
|
|
144
|
+
export { type FilterOption, FilterPopover, type FilterPopoverProps, type SavedViewItem, SavedViewMenu, type SavedViewMenuProps, type SavedViewSection, ViewMenu, type ViewMenuColumn, type ViewMenuColumnLabels, type ViewMenuProps };
|
|
@@ -1,22 +1,210 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useId, useRef, useState } from "react";
|
|
4
|
+
import { cx } from "../cx.js";
|
|
4
5
|
import { Button } from "./Button.js";
|
|
5
6
|
import { Checkbox } from "./Checkbox.js";
|
|
6
7
|
import { EditorIcon } from "./EditorIcons.js";
|
|
7
8
|
import { Input } from "./Input.js";
|
|
9
|
+
import { Popover } from "./Popover.js";
|
|
8
10
|
import { Segmented, SegmentedOption } from "./Segmented.js";
|
|
9
11
|
import { Switch } from "./Switch.js";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
const VIEW_MENU_COLUMN_LABELS = {
|
|
13
|
+
rowActions: "Column actions: {label}",
|
|
14
|
+
reorder: "Drag to reorder: {label}",
|
|
15
|
+
moveUp: "Move up",
|
|
16
|
+
moveDown: "Move down",
|
|
17
|
+
hide: "Hide",
|
|
18
|
+
show: "Show",
|
|
19
|
+
required: "Always shown",
|
|
20
|
+
moved: "{label} moved to position {position} of {count}"
|
|
21
|
+
};
|
|
22
|
+
const fill = (template, values) => template.replace(/\{(\w+)\}/g, (match, name) => name in values ? String(values[name]) : match);
|
|
23
|
+
const columnText = (column) => column.textLabel ?? (typeof column.label === "string" ? column.label : column.id);
|
|
24
|
+
const isPopoverOpen = (el) => {
|
|
25
|
+
try {
|
|
26
|
+
return !!el?.matches(":popover-open");
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
function ColumnRowMenu({ label, actions, triggerRef }) {
|
|
32
|
+
const [open, setOpen] = useState(false);
|
|
33
|
+
const menuId = useId();
|
|
34
|
+
const button = useRef(null);
|
|
35
|
+
const items = useRef([]);
|
|
36
|
+
const wasOpenAtPress = useRef(false);
|
|
37
|
+
const enabled = () => items.current.filter((el) => !!el && !el.disabled);
|
|
38
|
+
const close = (returnFocus) => {
|
|
39
|
+
const el = document.getElementById(menuId);
|
|
40
|
+
if (isPopoverOpen(el)) el.hidePopover();
|
|
41
|
+
setOpen(false);
|
|
42
|
+
if (returnFocus) button.current?.focus();
|
|
43
|
+
};
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (!open) return;
|
|
46
|
+
const el = document.getElementById(menuId);
|
|
47
|
+
if (el && typeof el.showPopover === "function" && !isPopoverOpen(el)) {
|
|
48
|
+
try {
|
|
49
|
+
el.showPopover();
|
|
50
|
+
} catch {
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
enabled()[0]?.focus();
|
|
54
|
+
const onToggle = (event) => {
|
|
55
|
+
if (event.newState === "closed") setOpen(false);
|
|
56
|
+
};
|
|
57
|
+
const onKeyDown = (event) => {
|
|
58
|
+
if (event.key !== "Escape") return;
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
event.stopPropagation();
|
|
61
|
+
close(true);
|
|
62
|
+
};
|
|
63
|
+
el?.addEventListener("toggle", onToggle);
|
|
64
|
+
document.addEventListener("keydown", onKeyDown, true);
|
|
65
|
+
return () => {
|
|
66
|
+
el?.removeEventListener("toggle", onToggle);
|
|
67
|
+
document.removeEventListener("keydown", onKeyDown, true);
|
|
68
|
+
};
|
|
69
|
+
}, [open, menuId]);
|
|
70
|
+
const onMenuKeyDown = (event) => {
|
|
71
|
+
const list = enabled();
|
|
72
|
+
const at = list.indexOf(document.activeElement);
|
|
73
|
+
const focus = (index) => {
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
list[(index + list.length) % list.length]?.focus();
|
|
76
|
+
};
|
|
77
|
+
if (event.key === "ArrowDown") focus(at + 1);
|
|
78
|
+
else if (event.key === "ArrowUp") focus(at - 1);
|
|
79
|
+
else if (event.key === "Home") focus(0);
|
|
80
|
+
else if (event.key === "End") focus(list.length - 1);
|
|
81
|
+
else if (event.key === "Tab") close(false);
|
|
82
|
+
};
|
|
83
|
+
return /* @__PURE__ */ jsxs("span", { className: "uix-view-menu__actions", children: [
|
|
84
|
+
/* @__PURE__ */ jsx(
|
|
85
|
+
"button",
|
|
86
|
+
{
|
|
87
|
+
ref: (el) => {
|
|
88
|
+
button.current = el;
|
|
89
|
+
triggerRef(el);
|
|
90
|
+
},
|
|
91
|
+
type: "button",
|
|
92
|
+
className: "uix-btn uix-btn--ghost uix-btn--sm uix-btn--icon",
|
|
93
|
+
"aria-label": label,
|
|
94
|
+
"aria-haspopup": "menu",
|
|
95
|
+
"aria-expanded": open,
|
|
96
|
+
"aria-controls": open ? menuId : void 0,
|
|
97
|
+
onPointerDown: () => {
|
|
98
|
+
wasOpenAtPress.current = isPopoverOpen(document.getElementById(menuId));
|
|
99
|
+
},
|
|
100
|
+
onClick: () => {
|
|
101
|
+
const wasOpen = wasOpenAtPress.current;
|
|
102
|
+
wasOpenAtPress.current = false;
|
|
103
|
+
if (wasOpen) return;
|
|
104
|
+
if (open) close(false);
|
|
105
|
+
else setOpen(true);
|
|
106
|
+
},
|
|
107
|
+
children: /* @__PURE__ */ jsx(EditorIcon, { name: "more" })
|
|
108
|
+
}
|
|
109
|
+
),
|
|
110
|
+
open && /* @__PURE__ */ jsx(Popover, { id: menuId, anchor: button, placement: "bottom-end", offset: 4, role: "menu", "aria-label": label, className: "uix-view-menu__row-menu", onKeyDown: onMenuKeyDown, children: actions.map((action, index) => /* @__PURE__ */ jsx(
|
|
111
|
+
"button",
|
|
112
|
+
{
|
|
113
|
+
ref: (el) => {
|
|
114
|
+
items.current[index] = el;
|
|
115
|
+
},
|
|
116
|
+
type: "button",
|
|
117
|
+
role: "menuitem",
|
|
118
|
+
tabIndex: -1,
|
|
119
|
+
className: "uix-menu__item",
|
|
120
|
+
disabled: action.disabled,
|
|
121
|
+
onClick: () => {
|
|
122
|
+
close(true);
|
|
123
|
+
action.onSelect();
|
|
124
|
+
},
|
|
125
|
+
children: action.label
|
|
126
|
+
},
|
|
127
|
+
action.id
|
|
128
|
+
)) })
|
|
129
|
+
] });
|
|
130
|
+
}
|
|
131
|
+
function ViewMenu({
|
|
132
|
+
density,
|
|
133
|
+
densityLabel,
|
|
134
|
+
densityOptions,
|
|
135
|
+
onDensityChange,
|
|
136
|
+
displayLabel,
|
|
137
|
+
zebra,
|
|
138
|
+
freeze,
|
|
139
|
+
columns,
|
|
140
|
+
columnsLabel,
|
|
141
|
+
onColumnVisibilityChange,
|
|
142
|
+
onReorder,
|
|
143
|
+
columnLabels,
|
|
144
|
+
footer,
|
|
145
|
+
className
|
|
146
|
+
}) {
|
|
147
|
+
const baseId = useId();
|
|
148
|
+
const densityLabelId = `${baseId}-density`;
|
|
149
|
+
const zebraLabelId = `${baseId}-zebra`;
|
|
150
|
+
const freezeLabelId = `${baseId}-freeze`;
|
|
151
|
+
const columnsLabelId = `${baseId}-columns`;
|
|
152
|
+
const labels = { ...VIEW_MENU_COLUMN_LABELS, ...columnLabels };
|
|
153
|
+
const list = columns ?? [];
|
|
154
|
+
const [draft, setDraft] = useState(null);
|
|
155
|
+
const [announcement, setAnnouncement] = useState("");
|
|
156
|
+
const dragStart = useRef(null);
|
|
157
|
+
const listRef = useRef(null);
|
|
158
|
+
const triggers = useRef(/* @__PURE__ */ new Map());
|
|
159
|
+
const refocus = useRef(null);
|
|
160
|
+
const orderKey = JSON.stringify(list.map((column) => column.id));
|
|
161
|
+
useEffect(() => setDraft(null), [orderKey]);
|
|
162
|
+
const order = draft ?? list.map((column) => column.id);
|
|
163
|
+
const byId = new Map(list.map((column) => [column.id, column]));
|
|
164
|
+
const ordered = order.map((id) => byId.get(id)).filter((column) => !!column);
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
const target = refocus.current;
|
|
167
|
+
refocus.current = null;
|
|
168
|
+
if (!target) return;
|
|
169
|
+
const el = target.part === "actions" ? triggers.current.get(target.id) : Array.from(listRef.current?.children ?? []).find((row) => row.dataset.columnId === target.id)?.querySelector(".uix-view-menu__grip");
|
|
170
|
+
if (el && document.activeElement !== el) el.focus();
|
|
171
|
+
});
|
|
172
|
+
const commit = (next, id) => {
|
|
173
|
+
setDraft(next);
|
|
174
|
+
onReorder?.([...next]);
|
|
175
|
+
const column = byId.get(id);
|
|
176
|
+
if (column) setAnnouncement(fill(labels.moved, { label: columnText(column), position: next.indexOf(id) + 1, count: next.length }));
|
|
177
|
+
};
|
|
178
|
+
const move = (id, delta, part) => {
|
|
179
|
+
const next = placeId(order, id, order.indexOf(id) + delta);
|
|
180
|
+
if (next === order) return;
|
|
181
|
+
refocus.current = { id, part };
|
|
182
|
+
commit(next, id);
|
|
183
|
+
};
|
|
184
|
+
const { dragging, start: startDrag } = useRowDrag(".uix-view-menu__col", {
|
|
185
|
+
over: (id, index) => {
|
|
186
|
+
const next = placeId(order, id, index);
|
|
187
|
+
if (next !== order) setDraft(next);
|
|
188
|
+
},
|
|
189
|
+
drop: (id) => {
|
|
190
|
+
const start = dragStart.current;
|
|
191
|
+
dragStart.current = null;
|
|
192
|
+
if (start && !sameOrder(start, order)) commit(order, id);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
const rowActions = (column, index) => [
|
|
196
|
+
{ id: "up", label: labels.moveUp, disabled: index === 0, onSelect: () => move(column.id, -1, "actions") },
|
|
197
|
+
{ id: "down", label: labels.moveDown, disabled: index === ordered.length - 1, onSelect: () => move(column.id, 1, "actions") },
|
|
198
|
+
...column.required || column.disabled ? [] : [column.visible ? { id: "hide", label: labels.hide, onSelect: () => onColumnVisibilityChange?.(column.id, false) } : { id: "show", label: labels.show, onSelect: () => onColumnVisibilityChange?.(column.id, true) }]
|
|
199
|
+
];
|
|
200
|
+
const showDensity = !!densityOptions?.length && !!onDensityChange;
|
|
201
|
+
return /* @__PURE__ */ jsxs("div", { className: cx("uix-view-menu", className), "data-dragging": dragging ? "" : void 0, children: [
|
|
202
|
+
showDensity && /* @__PURE__ */ jsxs("div", { className: "uix-view-menu__group", children: [
|
|
16
203
|
/* @__PURE__ */ jsx("div", { id: densityLabelId, className: "uix-view-menu__label", children: densityLabel }),
|
|
17
|
-
/* @__PURE__ */ jsx(Segmented, { value: density, onChange: onDensityChange, "aria-labelledby": densityLabelId, children: densityOptions.map((option) => /* @__PURE__ */ jsx(SegmentedOption, { value: option.value, children: option.label }, option.value)) })
|
|
204
|
+
/* @__PURE__ */ jsx(Segmented, { value: density ?? "", onChange: onDensityChange, "aria-labelledby": densityLabelId, children: densityOptions.map((option) => /* @__PURE__ */ jsx(SegmentedOption, { value: option.value, children: option.label }, option.value)) })
|
|
18
205
|
] }),
|
|
19
206
|
(zebra || freeze) && /* @__PURE__ */ jsxs("div", { className: "uix-view-menu__group", children: [
|
|
207
|
+
displayLabel != null && /* @__PURE__ */ jsx("div", { className: "uix-view-menu__label", children: displayLabel }),
|
|
20
208
|
zebra && /* @__PURE__ */ jsxs("div", { className: "uix-view-menu__row", children: [
|
|
21
209
|
/* @__PURE__ */ jsx("span", { id: zebraLabelId, children: zebra.label }),
|
|
22
210
|
/* @__PURE__ */ jsx(Switch, { checked: zebra.checked, "aria-labelledby": zebraLabelId, onChange: (event) => zebra.onChange(event.currentTarget.checked) })
|
|
@@ -26,10 +214,73 @@ function ViewMenu({ density, densityLabel, densityOptions, onDensityChange, zebr
|
|
|
26
214
|
/* @__PURE__ */ jsx(Switch, { checked: freeze.checked, "aria-labelledby": freezeLabelId, onChange: (event) => freeze.onChange(event.currentTarget.checked) })
|
|
27
215
|
] })
|
|
28
216
|
] }),
|
|
29
|
-
|
|
30
|
-
columnsLabel != null && /* @__PURE__ */ jsx("div", { className: "uix-view-menu__label", children: columnsLabel }),
|
|
31
|
-
/* @__PURE__ */ jsx("
|
|
32
|
-
|
|
217
|
+
ordered.length > 0 && /* @__PURE__ */ jsxs("div", { className: "uix-view-menu__group", children: [
|
|
218
|
+
columnsLabel != null && /* @__PURE__ */ jsx("div", { id: columnsLabelId, className: "uix-view-menu__label", children: columnsLabel }),
|
|
219
|
+
/* @__PURE__ */ jsx("ul", { ref: listRef, className: "uix-view-menu__cols", "aria-labelledby": columnsLabel != null ? columnsLabelId : void 0, children: ordered.map((column, index) => {
|
|
220
|
+
const text = columnText(column);
|
|
221
|
+
return /* @__PURE__ */ jsxs(
|
|
222
|
+
"li",
|
|
223
|
+
{
|
|
224
|
+
className: "uix-view-menu__col",
|
|
225
|
+
"data-column-id": column.id,
|
|
226
|
+
"data-visible": String(column.visible),
|
|
227
|
+
"data-required": column.required ? "true" : void 0,
|
|
228
|
+
"data-dragging": dragging === column.id || void 0,
|
|
229
|
+
children: [
|
|
230
|
+
onReorder && /* @__PURE__ */ jsx(
|
|
231
|
+
"button",
|
|
232
|
+
{
|
|
233
|
+
type: "button",
|
|
234
|
+
className: "uix-view-menu__grip",
|
|
235
|
+
tabIndex: -1,
|
|
236
|
+
"aria-label": fill(labels.reorder, { label: text }),
|
|
237
|
+
onPointerDown: (event) => {
|
|
238
|
+
if (event.button !== 0) return;
|
|
239
|
+
event.preventDefault();
|
|
240
|
+
event.currentTarget.focus({ preventScroll: true });
|
|
241
|
+
dragStart.current = order;
|
|
242
|
+
startDrag(event, column.id);
|
|
243
|
+
},
|
|
244
|
+
onKeyDown: (event) => {
|
|
245
|
+
if (event.key !== "ArrowUp" && event.key !== "ArrowDown") return;
|
|
246
|
+
event.preventDefault();
|
|
247
|
+
move(column.id, event.key === "ArrowUp" ? -1 : 1, "grip");
|
|
248
|
+
},
|
|
249
|
+
children: /* @__PURE__ */ jsx(EditorIcon, { name: "grip" })
|
|
250
|
+
}
|
|
251
|
+
),
|
|
252
|
+
column.required ? /* @__PURE__ */ jsxs("span", { className: "uix-view-menu__col-toggle", title: labels.required, children: [
|
|
253
|
+
/* @__PURE__ */ jsx("span", { className: "uix-view-menu__col-box", "aria-hidden": "true" }),
|
|
254
|
+
/* @__PURE__ */ jsx("span", { className: "uix-view-menu__col-name", children: column.label })
|
|
255
|
+
] }) : /* @__PURE__ */ jsx(
|
|
256
|
+
Checkbox,
|
|
257
|
+
{
|
|
258
|
+
className: "uix-view-menu__col-toggle",
|
|
259
|
+
label: /* @__PURE__ */ jsx("span", { className: "uix-view-menu__col-name", children: column.label }),
|
|
260
|
+
checked: column.visible,
|
|
261
|
+
disabled: column.disabled,
|
|
262
|
+
onChange: (event) => onColumnVisibilityChange?.(column.id, event.currentTarget.checked)
|
|
263
|
+
}
|
|
264
|
+
),
|
|
265
|
+
onReorder && /* @__PURE__ */ jsx(
|
|
266
|
+
ColumnRowMenu,
|
|
267
|
+
{
|
|
268
|
+
label: fill(labels.rowActions, { label: text }),
|
|
269
|
+
actions: rowActions(column, index),
|
|
270
|
+
triggerRef: (el) => {
|
|
271
|
+
if (el) triggers.current.set(column.id, el);
|
|
272
|
+
else triggers.current.delete(column.id);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
)
|
|
276
|
+
]
|
|
277
|
+
},
|
|
278
|
+
column.id
|
|
279
|
+
);
|
|
280
|
+
}) })
|
|
281
|
+
] }),
|
|
282
|
+
footer != null && /* @__PURE__ */ jsx("div", { className: "uix-view-menu__footer", children: footer }),
|
|
283
|
+
onReorder && /* @__PURE__ */ jsx("div", { role: "status", className: "uix-visually-hidden", children: announcement })
|
|
33
284
|
] });
|
|
34
285
|
}
|
|
35
286
|
function FilterPopover({ label, type = "text", value, onValueChange, options, applyLabel, clearLabel, onApply, onClear, placeholder }) {
|
|
@@ -45,6 +296,51 @@ function FilterPopover({ label, type = "text", value, onValueChange, options, ap
|
|
|
45
296
|
] });
|
|
46
297
|
}
|
|
47
298
|
const sameOrder = (a, b) => a.length === b.length && a.every((id, i) => id === b[i]);
|
|
299
|
+
function dropIndex(rows, dragged, clientY) {
|
|
300
|
+
let index = 0;
|
|
301
|
+
for (const row of rows) {
|
|
302
|
+
if (row === dragged) continue;
|
|
303
|
+
const box = row.getBoundingClientRect();
|
|
304
|
+
if (clientY > box.top + box.height / 2) index++;
|
|
305
|
+
}
|
|
306
|
+
return index;
|
|
307
|
+
}
|
|
308
|
+
function useRowDrag(rowSelector, handlers) {
|
|
309
|
+
const [dragging, setDragging] = useState(null);
|
|
310
|
+
const drag = useRef(null);
|
|
311
|
+
const latest = useRef(handlers);
|
|
312
|
+
latest.current = handlers;
|
|
313
|
+
useEffect(() => {
|
|
314
|
+
if (!dragging) return;
|
|
315
|
+
const move = (event) => {
|
|
316
|
+
const current = drag.current;
|
|
317
|
+
if (!current) return;
|
|
318
|
+
latest.current.over(current.id, dropIndex(Array.from(current.list.querySelectorAll(`:scope > ${rowSelector}`)), current.row, event.clientY));
|
|
319
|
+
};
|
|
320
|
+
const end = () => {
|
|
321
|
+
const current = drag.current;
|
|
322
|
+
drag.current = null;
|
|
323
|
+
setDragging(null);
|
|
324
|
+
if (current) latest.current.drop(current.id);
|
|
325
|
+
};
|
|
326
|
+
window.addEventListener("pointermove", move);
|
|
327
|
+
window.addEventListener("pointerup", end);
|
|
328
|
+
window.addEventListener("pointercancel", end);
|
|
329
|
+
return () => {
|
|
330
|
+
window.removeEventListener("pointermove", move);
|
|
331
|
+
window.removeEventListener("pointerup", end);
|
|
332
|
+
window.removeEventListener("pointercancel", end);
|
|
333
|
+
};
|
|
334
|
+
}, [dragging, rowSelector]);
|
|
335
|
+
const start = (event, id) => {
|
|
336
|
+
const row = event.currentTarget.closest("li");
|
|
337
|
+
if (!row?.parentElement) return;
|
|
338
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
339
|
+
drag.current = { id, list: row.parentElement, row };
|
|
340
|
+
setDragging(id);
|
|
341
|
+
};
|
|
342
|
+
return { dragging, start };
|
|
343
|
+
}
|
|
48
344
|
function placeId(ids, id, index) {
|
|
49
345
|
const from = ids.indexOf(id);
|
|
50
346
|
const to = Math.max(0, Math.min(index, ids.length - 1));
|
|
@@ -57,8 +353,8 @@ function SavedViewMenu({ items, sections, onSelect, onPinChange, pinLabel, unpin
|
|
|
57
353
|
const baseId = useId();
|
|
58
354
|
const groups = sections ?? [{ id: void 0, items: items ?? [] }];
|
|
59
355
|
const [draft, setDraft] = useState(null);
|
|
60
|
-
const [dragging, setDragging] = useState(null);
|
|
61
356
|
const dragStart = useRef(null);
|
|
357
|
+
const dragGroup = useRef(void 0);
|
|
62
358
|
const orderKey = JSON.stringify(groups.map((group) => [group.id, group.items.map((item) => item.id)]));
|
|
63
359
|
useEffect(() => setDraft(null), [orderKey]);
|
|
64
360
|
const orderOf = (group) => draft && draft.group === group.id ? draft.ids : group.items.map((item) => item.id);
|
|
@@ -69,29 +365,23 @@ function SavedViewMenu({ items, sections, onSelect, onPinChange, pinLabel, unpin
|
|
|
69
365
|
setDraft({ group: group.id, ids: next });
|
|
70
366
|
onReorder?.([...next], group.id);
|
|
71
367
|
};
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
368
|
+
const { dragging, start: startDrag } = useRowDrag(".uix-saved-views__row", {
|
|
369
|
+
over: (id, index) => {
|
|
370
|
+
const group = groups.find((candidate) => candidate.id === dragGroup.current);
|
|
371
|
+
if (!group) return;
|
|
372
|
+
const current = orderOf(group);
|
|
373
|
+
const next = placeId(current, id, index);
|
|
374
|
+
if (next !== current) setDraft({ group: group.id, ids: next });
|
|
375
|
+
},
|
|
376
|
+
drop: () => {
|
|
377
|
+
const group = groups.find((candidate) => candidate.id === dragGroup.current);
|
|
378
|
+
const start = dragStart.current;
|
|
379
|
+
dragStart.current = null;
|
|
380
|
+
if (!group) return;
|
|
381
|
+
const end = orderOf(group);
|
|
382
|
+
if (start && !sameOrder(start, end)) onReorder?.([...end], group.id);
|
|
82
383
|
}
|
|
83
|
-
|
|
84
|
-
const next = placeId(current, id, index);
|
|
85
|
-
if (next !== current) setDraft({ group: group.id, ids: next });
|
|
86
|
-
};
|
|
87
|
-
const drop = (group, id) => {
|
|
88
|
-
if (dragging !== id) return;
|
|
89
|
-
setDragging(null);
|
|
90
|
-
const start = dragStart.current;
|
|
91
|
-
dragStart.current = null;
|
|
92
|
-
const end = orderOf(group);
|
|
93
|
-
if (start && !sameOrder(start, end)) onReorder?.([...end], group.id);
|
|
94
|
-
};
|
|
384
|
+
});
|
|
95
385
|
const visible = groups.filter((group) => group.items.length > 0);
|
|
96
386
|
const renderRows = (group, groupIndex) => {
|
|
97
387
|
const byId = new Map(group.items.map((item) => [item.id, item]));
|
|
@@ -111,13 +401,10 @@ function SavedViewMenu({ items, sections, onSelect, onPinChange, pinLabel, unpin
|
|
|
111
401
|
onPointerDown: (event) => {
|
|
112
402
|
if (event.button !== 0) return;
|
|
113
403
|
event.preventDefault();
|
|
114
|
-
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
115
404
|
dragStart.current = orderOf(group);
|
|
116
|
-
|
|
405
|
+
dragGroup.current = group.id;
|
|
406
|
+
startDrag(event, item.id);
|
|
117
407
|
},
|
|
118
|
-
onPointerMove: (event) => dragOver(event, group, item.id),
|
|
119
|
-
onPointerUp: () => drop(group, item.id),
|
|
120
|
-
onPointerCancel: () => drop(group, item.id),
|
|
121
408
|
onKeyDown: (event) => {
|
|
122
409
|
if (event.key !== "ArrowUp" && event.key !== "ArrowDown") return;
|
|
123
410
|
event.preventDefault();
|
package/dist/emoji.cjs
CHANGED
|
@@ -665,7 +665,8 @@ var PATHS = {
|
|
|
665
665
|
sourceMode: "M10 12.5 8 15l2 2.5M14 12.5l2 2.5-2 2.5M14 2v4a2 2 0 0 0 2 2h4M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z",
|
|
666
666
|
richMode: "M14 2v4a2 2 0 0 0 2 2h4M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7ZM8 13h8M8 17h5",
|
|
667
667
|
clock: "M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20ZM12 6v6l4 2",
|
|
668
|
-
grip: "M9 5h.01M9 12h.01M9 19h.01M15 5h.01M15 12h.01M15 19h.01"
|
|
668
|
+
grip: "M9 5h.01M9 12h.01M9 19h.01M15 5h.01M15 12h.01M15 19h.01",
|
|
669
|
+
more: "M5 12h.01M12 12h.01M19 12h.01"
|
|
669
670
|
};
|
|
670
671
|
function EditorIcon({ name, ...props }) {
|
|
671
672
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|