@keepkit/ui 0.17.0 → 0.18.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 +6 -4
- package/dist/index.d.ts +192 -189
- package/dist/index.js +1523 -1381
- package/dist/index.js.map +1 -1
- package/dist/styles/base.css +16 -0
- package/dist/styles/collection.css +121 -0
- package/dist/styles/status.css +44 -0
- package/dist/styles/sync.css +45 -32
- package/dist/theme.css +1 -0
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -6,32 +6,94 @@ import {
|
|
|
6
6
|
createKeepKit as createCoreKeepKit
|
|
7
7
|
} from "@keepkit/core/react";
|
|
8
8
|
|
|
9
|
-
// src/
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
9
|
+
// src/adapters/url-sync.tsx
|
|
10
|
+
import {
|
|
11
|
+
DEFAULT_KEEP_URL_PARAMS,
|
|
12
|
+
decodeKeepListQuery,
|
|
13
|
+
encodeKeepListQuery
|
|
14
|
+
} from "@keepkit/core/core";
|
|
15
|
+
import { useEffect, useRef } from "react";
|
|
16
|
+
function createNextPagesRouterAdapter(router) {
|
|
17
|
+
const getUrl = () => router.asPath ?? (typeof window === "undefined" ? "/" : window.location.href);
|
|
18
|
+
return {
|
|
19
|
+
getUrl,
|
|
20
|
+
subscribe: router.events ? (listener) => {
|
|
21
|
+
router.events?.on("routeChangeComplete", listener);
|
|
22
|
+
return () => router.events?.off("routeChangeComplete", listener);
|
|
23
|
+
} : void 0,
|
|
24
|
+
navigate: (url, mode) => {
|
|
25
|
+
void router[mode](url, void 0, { shallow: true });
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function useKeepUrlSync({
|
|
30
|
+
enabled = true,
|
|
31
|
+
query,
|
|
32
|
+
onQueryChange,
|
|
33
|
+
options = {},
|
|
34
|
+
adapter: providedAdapter
|
|
35
|
+
}) {
|
|
36
|
+
const browserAdapterRef = useRef(getBrowserAdapter());
|
|
37
|
+
const adapter = providedAdapter ?? browserAdapterRef.current;
|
|
38
|
+
const onQueryChangeRef = useRef(onQueryChange);
|
|
39
|
+
onQueryChangeRef.current = onQueryChange;
|
|
40
|
+
const skipWriteRef = useRef(true);
|
|
41
|
+
const params = options.params;
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (!enabled) return;
|
|
44
|
+
const read = () => {
|
|
45
|
+
const url = adapter.getUrl();
|
|
46
|
+
const decoded = decodeKeepListQuery(url, { params });
|
|
47
|
+
skipWriteRef.current = true;
|
|
48
|
+
onQueryChangeRef.current((previousQuery) => ({
|
|
49
|
+
...previousQuery,
|
|
50
|
+
...decoded.search ? { search: decoded.search } : { search: void 0 },
|
|
51
|
+
...decoded.tags ? { tags: decoded.tags } : { tags: void 0 },
|
|
52
|
+
...decoded.sort ? { sort: decoded.sort } : {},
|
|
53
|
+
...decoded.pagination ? { pagination: { ...previousQuery.pagination, ...decoded.pagination } } : {}
|
|
54
|
+
}));
|
|
55
|
+
};
|
|
56
|
+
read();
|
|
57
|
+
return adapter.subscribe?.(read);
|
|
58
|
+
}, [adapter, enabled, params]);
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (!enabled) return;
|
|
61
|
+
if (skipWriteRef.current) {
|
|
62
|
+
skipWriteRef.current = false;
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const currentUrl = new URL(adapter.getUrl(), "http://keepkit.invalid");
|
|
66
|
+
const urlParams = { ...DEFAULT_KEEP_URL_PARAMS, ...params };
|
|
67
|
+
for (const key of Object.values(urlParams)) currentUrl.searchParams.delete(key);
|
|
68
|
+
const nextParams = encodeKeepListQuery(query, { params });
|
|
69
|
+
nextParams.forEach((value, key) => {
|
|
70
|
+
currentUrl.searchParams.append(key, value);
|
|
71
|
+
});
|
|
72
|
+
const nextUrl = `${currentUrl.pathname}${currentUrl.search}${currentUrl.hash}`;
|
|
73
|
+
adapter.navigate(nextUrl, options.history ?? "push");
|
|
74
|
+
}, [adapter, enabled, options.history, params, query]);
|
|
75
|
+
}
|
|
76
|
+
function getBrowserAdapter() {
|
|
77
|
+
return {
|
|
78
|
+
getUrl: () => typeof window === "undefined" ? "/" : window.location.href,
|
|
79
|
+
subscribe: (listener) => {
|
|
80
|
+
if (typeof window === "undefined") return () => void 0;
|
|
81
|
+
window.addEventListener("popstate", listener);
|
|
82
|
+
return () => window.removeEventListener("popstate", listener);
|
|
24
83
|
},
|
|
25
|
-
|
|
26
|
-
|
|
84
|
+
navigate: (url, mode) => {
|
|
85
|
+
if (typeof window === "undefined") return;
|
|
86
|
+
window.history[mode === "push" ? "pushState" : "replaceState"]({}, "", url);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
27
89
|
}
|
|
28
90
|
|
|
29
|
-
// src/hooks/useKeepBackup.ts
|
|
91
|
+
// src/features/actions/hooks/useKeepBackup.ts
|
|
30
92
|
import { useKeepContext } from "@keepkit/core/react";
|
|
31
|
-
import { useRef, useState } from "react";
|
|
93
|
+
import { useRef as useRef2, useState } from "react";
|
|
32
94
|
|
|
33
|
-
// src/ui-context.tsx
|
|
34
|
-
import { createContext, useCallback
|
|
95
|
+
// src/foundation/ui-context.tsx
|
|
96
|
+
import { createContext, useCallback, useContext, useMemo } from "react";
|
|
35
97
|
|
|
36
98
|
// src/locales/de.ts
|
|
37
99
|
var DE_LABELS = {
|
|
@@ -1356,7 +1418,7 @@ function getKeepLocaleLabels(locale) {
|
|
|
1356
1418
|
return { ...KEEP_LOCALE_LABELS[normalizeKeepLocale(locale)] };
|
|
1357
1419
|
}
|
|
1358
1420
|
|
|
1359
|
-
// src/ui-context.tsx
|
|
1421
|
+
// src/foundation/ui-context.tsx
|
|
1360
1422
|
import { jsx } from "react/jsx-runtime";
|
|
1361
1423
|
var DEFAULT_LABELS = KEEP_LOCALE_LABELS.en;
|
|
1362
1424
|
var KeepUiLabelsContext = createContext({
|
|
@@ -1370,7 +1432,7 @@ function KeepUiProvider({
|
|
|
1370
1432
|
onFeedback,
|
|
1371
1433
|
children
|
|
1372
1434
|
}) {
|
|
1373
|
-
const emitFeedback =
|
|
1435
|
+
const emitFeedback = useCallback(
|
|
1374
1436
|
(event) => {
|
|
1375
1437
|
onFeedback?.(event);
|
|
1376
1438
|
},
|
|
@@ -1393,13 +1455,13 @@ function useUiLabel(key, override) {
|
|
|
1393
1455
|
}
|
|
1394
1456
|
function useKeepUiFeedback() {
|
|
1395
1457
|
const { emitFeedback } = useKeepUiLabels();
|
|
1396
|
-
return
|
|
1458
|
+
return useCallback((event) => emitFeedback(event), [emitFeedback]);
|
|
1397
1459
|
}
|
|
1398
1460
|
|
|
1399
|
-
// src/hooks/useKeepBackup.ts
|
|
1461
|
+
// src/features/actions/hooks/useKeepBackup.ts
|
|
1400
1462
|
function useKeepBackup({ filename, onExport, onImported }) {
|
|
1401
1463
|
const context = useKeepContext();
|
|
1402
|
-
const inputRef =
|
|
1464
|
+
const inputRef = useRef2(null);
|
|
1403
1465
|
const [mode, setMode] = useState("merge");
|
|
1404
1466
|
const [result, setResult] = useState();
|
|
1405
1467
|
const [error, setError] = useState();
|
|
@@ -1456,7 +1518,7 @@ function useKeepBackup({ filename, onExport, onImported }) {
|
|
|
1456
1518
|
};
|
|
1457
1519
|
}
|
|
1458
1520
|
|
|
1459
|
-
// src/KeepBackup.tsx
|
|
1521
|
+
// src/features/actions/KeepBackup.tsx
|
|
1460
1522
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
1461
1523
|
function KeepBackup({
|
|
1462
1524
|
filename = "keepkit-backup.json",
|
|
@@ -1533,11 +1595,7 @@ function getErrorMessage(error) {
|
|
|
1533
1595
|
return error instanceof Error ? error.message : "Something went wrong.";
|
|
1534
1596
|
}
|
|
1535
1597
|
|
|
1536
|
-
// src/
|
|
1537
|
-
import { useKeepList } from "@keepkit/core/react";
|
|
1538
|
-
import { useState as useState2 } from "react";
|
|
1539
|
-
|
|
1540
|
-
// src/shared.tsx
|
|
1598
|
+
// src/foundation/shared.tsx
|
|
1541
1599
|
import {
|
|
1542
1600
|
cloneElement,
|
|
1543
1601
|
createContext as createContext2,
|
|
@@ -1614,7 +1672,9 @@ function renderRoot(asChild, child, props, body, componentName) {
|
|
|
1614
1672
|
return /* @__PURE__ */ jsx3("div", { ...props, children: body });
|
|
1615
1673
|
}
|
|
1616
1674
|
|
|
1617
|
-
// src/hooks/useKeepBulkActions.ts
|
|
1675
|
+
// src/features/actions/hooks/useKeepBulkActions.ts
|
|
1676
|
+
import { useKeepList } from "@keepkit/core/react";
|
|
1677
|
+
import { useState as useState2 } from "react";
|
|
1618
1678
|
function isAllSelected(items, selectedIds) {
|
|
1619
1679
|
if (items.length === 0) return false;
|
|
1620
1680
|
const selected = new Set(selectedIds);
|
|
@@ -1707,7 +1767,7 @@ function useKeepBulkActions(options) {
|
|
|
1707
1767
|
};
|
|
1708
1768
|
}
|
|
1709
1769
|
|
|
1710
|
-
// src/KeepItemCheckbox.tsx
|
|
1770
|
+
// src/features/actions/KeepItemCheckbox.tsx
|
|
1711
1771
|
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
1712
1772
|
function KeepItemCheckbox({
|
|
1713
1773
|
item,
|
|
@@ -1740,7 +1800,7 @@ function getItemLabel(item) {
|
|
|
1740
1800
|
return typeof title === "string" && title.trim() ? title.trim() : void 0;
|
|
1741
1801
|
}
|
|
1742
1802
|
|
|
1743
|
-
// src/KeepBulkActions.tsx
|
|
1803
|
+
// src/features/actions/KeepBulkActions.tsx
|
|
1744
1804
|
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1745
1805
|
function KeepBulkActions({
|
|
1746
1806
|
query,
|
|
@@ -1855,13 +1915,13 @@ function KeepBulkActions({
|
|
|
1855
1915
|
);
|
|
1856
1916
|
}
|
|
1857
1917
|
|
|
1858
|
-
// src/KeepButton.tsx
|
|
1918
|
+
// src/features/actions/KeepButton.tsx
|
|
1859
1919
|
import {
|
|
1860
1920
|
KeepButton as CoreKeepButton
|
|
1861
1921
|
} from "@keepkit/core/react";
|
|
1862
|
-
import { createElement, useEffect, useRef as
|
|
1922
|
+
import { createElement, useEffect as useEffect2, useRef as useRef3 } from "react";
|
|
1863
1923
|
|
|
1864
|
-
// src/hooks/useKeepButton.ts
|
|
1924
|
+
// src/features/actions/hooks/useKeepButton.ts
|
|
1865
1925
|
import { useKeepItem } from "@keepkit/core/react";
|
|
1866
1926
|
function useKeepButton({ item, labels, icons, children }) {
|
|
1867
1927
|
return {
|
|
@@ -1881,7 +1941,7 @@ function useKeepButton({ item, labels, icons, children }) {
|
|
|
1881
1941
|
};
|
|
1882
1942
|
}
|
|
1883
1943
|
|
|
1884
|
-
// src/KeepButton.tsx
|
|
1944
|
+
// src/features/actions/KeepButton.tsx
|
|
1885
1945
|
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1886
1946
|
function KeepButton({
|
|
1887
1947
|
labels,
|
|
@@ -1894,8 +1954,8 @@ function KeepButton({
|
|
|
1894
1954
|
...props
|
|
1895
1955
|
}) {
|
|
1896
1956
|
const view = useKeepButton({ item: props.item, labels, icons, children: props.children });
|
|
1897
|
-
const pendingToggle =
|
|
1898
|
-
|
|
1957
|
+
const pendingToggle = useRef3(null);
|
|
1958
|
+
useEffect2(() => {
|
|
1899
1959
|
const pending = pendingToggle.current;
|
|
1900
1960
|
if (!pending || view.buttonState.isMutating || pending.wasSaved === view.buttonState.isSaved) return;
|
|
1901
1961
|
pendingToggle.current = null;
|
|
@@ -1962,130 +2022,390 @@ function renderIcon(icon, className) {
|
|
|
1962
2022
|
return icon ?? null;
|
|
1963
2023
|
}
|
|
1964
2024
|
|
|
1965
|
-
// src/
|
|
1966
|
-
import {
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
const
|
|
2025
|
+
// src/features/actions/hooks/useKeepUndo.ts
|
|
2026
|
+
import { useKeepContext as useKeepContext2 } from "@keepkit/core/react";
|
|
2027
|
+
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
2028
|
+
function useKeepUndo() {
|
|
2029
|
+
const context = useKeepContext2();
|
|
2030
|
+
const emitFeedback = useKeepUiFeedback();
|
|
2031
|
+
const restoredMessage = useUiLabel("restoredMessage");
|
|
2032
|
+
const { canUndo, startedAt, expiresAt } = context.undo;
|
|
2033
|
+
const [now, setNow] = useState3(() => Date.now());
|
|
2034
|
+
useEffect3(() => {
|
|
2035
|
+
if (!canUndo || expiresAt === void 0) return;
|
|
2036
|
+
setNow(Date.now());
|
|
2037
|
+
const timer = setInterval(() => setNow(Date.now()), 250);
|
|
2038
|
+
return () => clearInterval(timer);
|
|
2039
|
+
}, [canUndo, expiresAt]);
|
|
2040
|
+
const duration = Math.max(1, (expiresAt ?? now) - (startedAt ?? now));
|
|
2041
|
+
const remainingMs = Math.max(0, (expiresAt ?? now) - now);
|
|
2042
|
+
const remainingSeconds = Math.max(0, Math.ceil(remainingMs / 1e3));
|
|
2043
|
+
const progress = Math.min(1, Math.max(0, remainingMs / duration));
|
|
1981
2044
|
return {
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
2045
|
+
canUndo,
|
|
2046
|
+
expiresAt,
|
|
2047
|
+
remainingMs,
|
|
2048
|
+
remainingSeconds,
|
|
2049
|
+
progress,
|
|
2050
|
+
undo: async () => {
|
|
2051
|
+
const items = context.lastChange?.items ?? (context.lastChange?.item ? [context.lastChange.item] : []);
|
|
2052
|
+
await context.undoLastRemoval();
|
|
2053
|
+
if (items.length > 0) {
|
|
2054
|
+
emitFeedback({ type: "item-restored", item: items[0], items, message: restoredMessage });
|
|
2055
|
+
}
|
|
2056
|
+
},
|
|
2057
|
+
message: useUiLabel("undoAvailable"),
|
|
2058
|
+
label: useUiLabel("undo")
|
|
1990
2059
|
};
|
|
1991
2060
|
}
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
...decoded.pagination ? { pagination: { ...previousQuery.pagination, ...decoded.pagination } } : {}
|
|
2017
|
-
}));
|
|
2018
|
-
};
|
|
2019
|
-
read();
|
|
2020
|
-
return adapter.subscribe?.(read);
|
|
2021
|
-
}, [adapter, enabled, params]);
|
|
2022
|
-
useEffect2(() => {
|
|
2023
|
-
if (!enabled) return;
|
|
2024
|
-
if (skipWriteRef.current) {
|
|
2025
|
-
skipWriteRef.current = false;
|
|
2026
|
-
return;
|
|
2027
|
-
}
|
|
2028
|
-
const currentUrl = new URL(adapter.getUrl(), "http://keepkit.invalid");
|
|
2029
|
-
const urlParams = { ...DEFAULT_KEEP_URL_PARAMS, ...params };
|
|
2030
|
-
for (const key of Object.values(urlParams)) currentUrl.searchParams.delete(key);
|
|
2031
|
-
const nextParams = encodeKeepListQuery(query, { params });
|
|
2032
|
-
nextParams.forEach((value, key) => {
|
|
2033
|
-
currentUrl.searchParams.append(key, value);
|
|
2034
|
-
});
|
|
2035
|
-
const nextUrl = `${currentUrl.pathname}${currentUrl.search}${currentUrl.hash}`;
|
|
2036
|
-
adapter.navigate(nextUrl, options.history ?? "push");
|
|
2037
|
-
}, [adapter, enabled, options.history, params, query]);
|
|
2061
|
+
|
|
2062
|
+
// src/features/actions/KeepUndo.tsx
|
|
2063
|
+
import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2064
|
+
function KeepUndo({ children, label, ...props }) {
|
|
2065
|
+
const view = useKeepUndo();
|
|
2066
|
+
if (!view.canUndo) return null;
|
|
2067
|
+
return /* @__PURE__ */ jsxs4("div", { ...props, role: "status", "aria-live": "polite", "data-keepkit": "undo", "data-state": "available", children: [
|
|
2068
|
+
/* @__PURE__ */ jsx7("span", { "data-keepkit": "undo-message", children: children ?? view.message }),
|
|
2069
|
+
/* @__PURE__ */ jsxs4("span", { "data-keepkit": "undo-countdown", "aria-hidden": "true", children: [
|
|
2070
|
+
view.remainingSeconds,
|
|
2071
|
+
"s"
|
|
2072
|
+
] }),
|
|
2073
|
+
/* @__PURE__ */ jsx7(
|
|
2074
|
+
"progress",
|
|
2075
|
+
{
|
|
2076
|
+
"data-keepkit": "undo-progress",
|
|
2077
|
+
max: 1,
|
|
2078
|
+
value: view.progress,
|
|
2079
|
+
"aria-label": String(view.label),
|
|
2080
|
+
"aria-valuetext": `${view.remainingSeconds}s`
|
|
2081
|
+
}
|
|
2082
|
+
),
|
|
2083
|
+
/* @__PURE__ */ jsx7("button", { type: "button", "data-keep-action": "undo", onClick: () => void view.undo(), children: label ?? view.label })
|
|
2084
|
+
] });
|
|
2038
2085
|
}
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2086
|
+
|
|
2087
|
+
// src/features/collection/KeepCollection.tsx
|
|
2088
|
+
import { KeepErrorBoundary as KeepErrorBoundary2 } from "@keepkit/core/react";
|
|
2089
|
+
|
|
2090
|
+
// src/features/query/KeepTagFilter.tsx
|
|
2091
|
+
import { isValidElement as isValidElement2 } from "react";
|
|
2092
|
+
|
|
2093
|
+
// src/features/query/hooks/useKeepTagFilter.ts
|
|
2094
|
+
import { useKeepList as useKeepList2 } from "@keepkit/core/react";
|
|
2095
|
+
import { useCallback as useCallback2, useMemo as useMemo2, useState as useState4 } from "react";
|
|
2096
|
+
function useKeepTagFilter(options) {
|
|
2097
|
+
const { query, controlledValue, defaultValue, onChange, onValueChange } = options;
|
|
2098
|
+
const [uncontrolledValue, setUncontrolledValue] = useState4(defaultValue);
|
|
2099
|
+
const resolvedValue = controlledValue ?? uncontrolledValue;
|
|
2100
|
+
const list = useKeepList2({
|
|
2101
|
+
...query,
|
|
2102
|
+
tags: resolvedValue ? [...query?.tags ?? [], resolvedValue] : query?.tags
|
|
2103
|
+
});
|
|
2104
|
+
const select = useCallback2(
|
|
2105
|
+
(tag) => {
|
|
2106
|
+
if (controlledValue === void 0) setUncontrolledValue(tag);
|
|
2107
|
+
onChange?.(tag);
|
|
2108
|
+
onValueChange?.(tag);
|
|
2046
2109
|
},
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
}
|
|
2110
|
+
[controlledValue, onChange, onValueChange]
|
|
2111
|
+
);
|
|
2112
|
+
const state = useMemo2(
|
|
2113
|
+
() => ({ tags: list.tags, tagCounts: list.tagCounts, value: resolvedValue, select }),
|
|
2114
|
+
[list.tagCounts, list.tags, resolvedValue, select]
|
|
2115
|
+
);
|
|
2116
|
+
return {
|
|
2117
|
+
state,
|
|
2118
|
+
isLoading: list.isLoading,
|
|
2119
|
+
labels: { all: useUiLabel("allTags"), aria: useUiLabel("filterTags") }
|
|
2051
2120
|
};
|
|
2052
2121
|
}
|
|
2053
2122
|
|
|
2054
|
-
// src/
|
|
2055
|
-
|
|
2123
|
+
// src/features/query/KeepTagFilter.tsx
|
|
2124
|
+
import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
2125
|
+
function KeepTagFilter({
|
|
2056
2126
|
query,
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2127
|
+
value: controlledValue,
|
|
2128
|
+
defaultValue,
|
|
2129
|
+
onChange,
|
|
2130
|
+
onValueChange,
|
|
2131
|
+
allLabel,
|
|
2132
|
+
ariaLabel,
|
|
2133
|
+
renderTag,
|
|
2134
|
+
render,
|
|
2135
|
+
children,
|
|
2136
|
+
asChild = false,
|
|
2137
|
+
className,
|
|
2138
|
+
...rootProps
|
|
2061
2139
|
}) {
|
|
2062
|
-
const
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
() => (
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2140
|
+
const view = useKeepTagFilter({ query, controlledValue, defaultValue, onChange, onValueChange });
|
|
2141
|
+
const contentChildren = asChild && isValidElement2(children) ? void 0 : children;
|
|
2142
|
+
const body = render ? render(view.state) : typeof contentChildren === "function" ? contentChildren(view.state) : contentChildren ?? /* @__PURE__ */ jsxs5("fieldset", { children: [
|
|
2143
|
+
/* @__PURE__ */ jsx8("legend", { children: ariaLabel ?? view.labels.aria }),
|
|
2144
|
+
/* @__PURE__ */ jsx8(
|
|
2145
|
+
"button",
|
|
2146
|
+
{
|
|
2147
|
+
type: "button",
|
|
2148
|
+
"data-keep-action": "filter-all-tags",
|
|
2149
|
+
"aria-pressed": view.state.value === void 0,
|
|
2150
|
+
onClick: () => view.state.select(),
|
|
2151
|
+
children: allLabel ?? view.labels.all
|
|
2152
|
+
}
|
|
2153
|
+
),
|
|
2154
|
+
view.state.tags.map((tag) => /* @__PURE__ */ jsxs5(
|
|
2155
|
+
"button",
|
|
2156
|
+
{
|
|
2157
|
+
type: "button",
|
|
2158
|
+
"data-keep-action": "filter-tag",
|
|
2159
|
+
"aria-pressed": view.state.value === tag,
|
|
2160
|
+
onClick: () => view.state.select(tag),
|
|
2161
|
+
children: [
|
|
2162
|
+
renderTag ? renderTag(tag, view.state.tagCounts[tag] ?? 0, view.state.value === tag) : tag,
|
|
2163
|
+
/* @__PURE__ */ jsxs5("span", { children: [
|
|
2164
|
+
" (",
|
|
2165
|
+
view.state.tagCounts[tag] ?? 0,
|
|
2166
|
+
")"
|
|
2167
|
+
] })
|
|
2168
|
+
]
|
|
2169
|
+
},
|
|
2170
|
+
tag
|
|
2171
|
+
))
|
|
2172
|
+
] });
|
|
2173
|
+
return renderRoot(
|
|
2174
|
+
asChild,
|
|
2175
|
+
isValidElement2(children) ? children : void 0,
|
|
2176
|
+
{
|
|
2177
|
+
...rootProps,
|
|
2178
|
+
className,
|
|
2179
|
+
"data-keepkit": "tag-filter",
|
|
2180
|
+
"data-state": view.state.value === void 0 ? "all" : "filtered",
|
|
2181
|
+
"data-loading": view.isLoading ? "true" : void 0
|
|
2182
|
+
},
|
|
2183
|
+
body,
|
|
2184
|
+
"KeepTagFilter"
|
|
2185
|
+
);
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
// src/features/query/hooks/useQueryControls.ts
|
|
2189
|
+
import { useEffect as useEffect4, useState as useState5 } from "react";
|
|
2190
|
+
function useKeepSearchInput(options) {
|
|
2191
|
+
const { controlledValue, defaultValue, debounceMs, onValueChange } = options;
|
|
2192
|
+
const [uncontrolledValue, setUncontrolledValue] = useState5(defaultValue);
|
|
2193
|
+
const value = controlledValue ?? uncontrolledValue;
|
|
2194
|
+
useEffect4(() => {
|
|
2195
|
+
if (!onValueChange) return;
|
|
2196
|
+
if (debounceMs <= 0) {
|
|
2197
|
+
onValueChange(value);
|
|
2198
|
+
return;
|
|
2199
|
+
}
|
|
2200
|
+
const timer = window.setTimeout(() => onValueChange(value), debounceMs);
|
|
2201
|
+
return () => window.clearTimeout(timer);
|
|
2202
|
+
}, [debounceMs, onValueChange, value]);
|
|
2203
|
+
return {
|
|
2204
|
+
value,
|
|
2205
|
+
label: useUiLabel("search"),
|
|
2206
|
+
change: (event) => {
|
|
2207
|
+
if (controlledValue === void 0) setUncontrolledValue(event.currentTarget.value);
|
|
2208
|
+
}
|
|
2209
|
+
};
|
|
2210
|
+
}
|
|
2211
|
+
function useKeepSortSelect(options) {
|
|
2212
|
+
const { controlledValue, defaultValue, onValueChange } = options;
|
|
2213
|
+
const [uncontrolledValue, setUncontrolledValue] = useState5(defaultValue);
|
|
2214
|
+
const value = controlledValue ?? uncontrolledValue;
|
|
2215
|
+
return {
|
|
2216
|
+
value,
|
|
2217
|
+
change: (event) => {
|
|
2218
|
+
const nextValue = event.currentTarget.value;
|
|
2219
|
+
if (controlledValue === void 0) setUncontrolledValue(nextValue);
|
|
2220
|
+
const [by, direction] = nextValue.split(":");
|
|
2221
|
+
onValueChange?.(nextValue, { by, direction });
|
|
2222
|
+
},
|
|
2223
|
+
labels: {
|
|
2224
|
+
sort: useUiLabel("sort"),
|
|
2225
|
+
updatedNewest: useUiLabel("updatedNewest"),
|
|
2226
|
+
updatedOldest: useUiLabel("updatedOldest"),
|
|
2227
|
+
savedNewest: useUiLabel("savedNewest"),
|
|
2228
|
+
savedOldest: useUiLabel("savedOldest")
|
|
2229
|
+
}
|
|
2230
|
+
};
|
|
2231
|
+
}
|
|
2232
|
+
function useKeepPagination(options) {
|
|
2233
|
+
const { totalCount, pageSize, page, maxPageButtons, onPageChange } = options;
|
|
2234
|
+
const pageCount = Math.max(1, Math.ceil(totalCount / Math.max(1, pageSize)));
|
|
2235
|
+
const currentPage = Math.min(Math.max(1, page), pageCount);
|
|
2236
|
+
const goToPage = (nextPage) => {
|
|
2237
|
+
const next = Math.min(Math.max(1, nextPage), pageCount);
|
|
2238
|
+
onPageChange?.(next, (next - 1) * pageSize);
|
|
2239
|
+
};
|
|
2240
|
+
return {
|
|
2241
|
+
pageCount,
|
|
2242
|
+
currentPage,
|
|
2243
|
+
goToPage,
|
|
2244
|
+
visiblePages: getVisiblePages(currentPage, pageCount, Math.max(1, maxPageButtons)),
|
|
2245
|
+
labels: {
|
|
2246
|
+
previous: useUiLabel("previousPage"),
|
|
2247
|
+
next: useUiLabel("nextPage"),
|
|
2248
|
+
page: useUiLabel("page"),
|
|
2249
|
+
pagination: useUiLabel("pagination")
|
|
2250
|
+
}
|
|
2251
|
+
};
|
|
2252
|
+
}
|
|
2253
|
+
function getVisiblePages(currentPage, pageCount, maxPageButtons) {
|
|
2254
|
+
if (pageCount <= maxPageButtons) return Array.from({ length: pageCount }, (_, index) => index + 1);
|
|
2255
|
+
const half = Math.floor(maxPageButtons / 2);
|
|
2256
|
+
const start = Math.min(Math.max(1, currentPage - half), pageCount - maxPageButtons + 1);
|
|
2257
|
+
return Array.from({ length: maxPageButtons }, (_, index) => start + index);
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2260
|
+
// src/features/query/query-controls.tsx
|
|
2261
|
+
import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2262
|
+
function KeepSearchInput({
|
|
2263
|
+
value: controlledValue,
|
|
2264
|
+
defaultValue = "",
|
|
2265
|
+
debounceMs = 300,
|
|
2266
|
+
onValueChange,
|
|
2267
|
+
"aria-label": ariaLabel,
|
|
2268
|
+
placeholder,
|
|
2269
|
+
...props
|
|
2270
|
+
}) {
|
|
2271
|
+
const view = useKeepSearchInput({ controlledValue, defaultValue, debounceMs, onValueChange });
|
|
2272
|
+
return /* @__PURE__ */ jsx9(
|
|
2273
|
+
"input",
|
|
2274
|
+
{
|
|
2275
|
+
...props,
|
|
2276
|
+
"data-keepkit": "search-input",
|
|
2277
|
+
"data-keep-action": "search",
|
|
2278
|
+
type: "search",
|
|
2279
|
+
value: view.value,
|
|
2280
|
+
"data-state": view.value ? "active" : "idle",
|
|
2281
|
+
"data-disabled": props.disabled ? "true" : void 0,
|
|
2282
|
+
"aria-label": ariaLabel ?? view.label,
|
|
2283
|
+
placeholder: placeholder ?? view.label,
|
|
2284
|
+
onChange: view.change
|
|
2285
|
+
}
|
|
2286
|
+
);
|
|
2287
|
+
}
|
|
2288
|
+
function KeepSortSelect({
|
|
2289
|
+
value: controlledValue,
|
|
2290
|
+
defaultValue = "updatedAt:desc",
|
|
2291
|
+
onValueChange,
|
|
2292
|
+
"aria-label": ariaLabel,
|
|
2293
|
+
children,
|
|
2294
|
+
...props
|
|
2295
|
+
}) {
|
|
2296
|
+
const view = useKeepSortSelect({ controlledValue, defaultValue, onValueChange });
|
|
2297
|
+
const options = children ?? /* @__PURE__ */ jsxs6(Fragment4, { children: [
|
|
2298
|
+
/* @__PURE__ */ jsx9("option", { value: "updatedAt:desc", children: view.labels.updatedNewest }),
|
|
2299
|
+
/* @__PURE__ */ jsx9("option", { value: "updatedAt:asc", children: view.labels.updatedOldest }),
|
|
2300
|
+
/* @__PURE__ */ jsx9("option", { value: "savedAt:desc", children: view.labels.savedNewest }),
|
|
2301
|
+
/* @__PURE__ */ jsx9("option", { value: "savedAt:asc", children: view.labels.savedOldest })
|
|
2302
|
+
] });
|
|
2303
|
+
return /* @__PURE__ */ jsx9(
|
|
2304
|
+
"select",
|
|
2305
|
+
{
|
|
2306
|
+
...props,
|
|
2307
|
+
"data-keepkit": "sort-select",
|
|
2308
|
+
"data-keep-action": "sort",
|
|
2309
|
+
value: view.value,
|
|
2310
|
+
"data-state": "selected",
|
|
2311
|
+
"data-disabled": props.disabled ? "true" : void 0,
|
|
2312
|
+
"aria-label": ariaLabel ?? view.labels.sort,
|
|
2313
|
+
onChange: view.change,
|
|
2314
|
+
children: options
|
|
2315
|
+
}
|
|
2316
|
+
);
|
|
2317
|
+
}
|
|
2318
|
+
function KeepPagination({
|
|
2319
|
+
totalCount,
|
|
2320
|
+
pageSize,
|
|
2321
|
+
page = 1,
|
|
2322
|
+
maxPageButtons = 7,
|
|
2323
|
+
onPageChange,
|
|
2324
|
+
render,
|
|
2325
|
+
...props
|
|
2326
|
+
}) {
|
|
2327
|
+
const view = useKeepPagination({ totalCount, pageSize, page, maxPageButtons, onPageChange });
|
|
2328
|
+
const navProps = {
|
|
2329
|
+
...props,
|
|
2330
|
+
"data-keepkit": "pagination",
|
|
2331
|
+
"aria-label": props["aria-label"] ?? view.labels.pagination,
|
|
2332
|
+
"data-state": view.pageCount > 1 ? "active" : "idle"
|
|
2333
|
+
};
|
|
2334
|
+
if (render)
|
|
2335
|
+
return /* @__PURE__ */ jsx9("nav", { ...navProps, children: render({ page: view.currentPage, pageCount: view.pageCount, goToPage: view.goToPage }) });
|
|
2336
|
+
return /* @__PURE__ */ jsxs6("nav", { ...navProps, children: [
|
|
2337
|
+
/* @__PURE__ */ jsx9(
|
|
2338
|
+
"button",
|
|
2339
|
+
{
|
|
2340
|
+
type: "button",
|
|
2341
|
+
"data-keep-action": "previous-page",
|
|
2342
|
+
onClick: () => view.goToPage(view.currentPage - 1),
|
|
2343
|
+
disabled: view.currentPage <= 1,
|
|
2344
|
+
children: view.labels.previous
|
|
2345
|
+
}
|
|
2346
|
+
),
|
|
2347
|
+
view.visiblePages.map((nextPage) => /* @__PURE__ */ jsx9(
|
|
2348
|
+
"button",
|
|
2349
|
+
{
|
|
2350
|
+
type: "button",
|
|
2351
|
+
"data-keep-action": "select-page",
|
|
2352
|
+
"aria-current": nextPage === view.currentPage ? "page" : void 0,
|
|
2353
|
+
"aria-label": `${view.labels.page} ${nextPage}`,
|
|
2354
|
+
onClick: () => view.goToPage(nextPage),
|
|
2355
|
+
children: nextPage
|
|
2356
|
+
},
|
|
2357
|
+
nextPage
|
|
2358
|
+
)),
|
|
2359
|
+
/* @__PURE__ */ jsx9(
|
|
2360
|
+
"button",
|
|
2361
|
+
{
|
|
2362
|
+
type: "button",
|
|
2363
|
+
"data-keep-action": "next-page",
|
|
2364
|
+
onClick: () => view.goToPage(view.currentPage + 1),
|
|
2365
|
+
disabled: view.currentPage >= view.pageCount,
|
|
2366
|
+
children: view.labels.next
|
|
2367
|
+
}
|
|
2368
|
+
)
|
|
2369
|
+
] });
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
// src/features/collection/hooks/useKeepCollection.ts
|
|
2373
|
+
import { useKeepList as useKeepList3 } from "@keepkit/core/react";
|
|
2374
|
+
import { useMemo as useMemo3, useState as useState6 } from "react";
|
|
2375
|
+
function useKeepCollection({
|
|
2376
|
+
query,
|
|
2377
|
+
pageSize,
|
|
2378
|
+
urlSync,
|
|
2379
|
+
urlAdapter,
|
|
2380
|
+
features
|
|
2381
|
+
}) {
|
|
2382
|
+
const enabled = {
|
|
2383
|
+
search: true,
|
|
2384
|
+
sort: true,
|
|
2385
|
+
pagination: true,
|
|
2386
|
+
tagFilter: false,
|
|
2387
|
+
bulkActions: false,
|
|
2388
|
+
...features
|
|
2389
|
+
};
|
|
2390
|
+
const [searchValue, setSearchValue] = useState6(query.search?.query ?? "");
|
|
2391
|
+
const [sort, setSort] = useState6(query.sort ?? { by: "updatedAt", direction: "desc" });
|
|
2392
|
+
const [tag, setTag] = useState6(query.tags?.[0]);
|
|
2393
|
+
const [page, setPage] = useState6(query.pagination?.page ?? 1);
|
|
2394
|
+
const resolvedPageSize = query.pagination?.pageSize ?? pageSize;
|
|
2395
|
+
const resolvedQuery = useMemo3(
|
|
2396
|
+
() => ({
|
|
2397
|
+
...query,
|
|
2398
|
+
search: enabled.search ? { ...query.search, query: searchValue } : query.search,
|
|
2399
|
+
sort: enabled.sort ? sort : query.sort,
|
|
2400
|
+
tags: tag ? [.../* @__PURE__ */ new Set([...query.tags ?? [], tag])] : query.tags,
|
|
2401
|
+
pagination: enabled.pagination ? { ...query.pagination, page, pageSize: resolvedPageSize } : query.pagination
|
|
2402
|
+
}),
|
|
2403
|
+
[enabled.pagination, enabled.search, enabled.sort, page, query, resolvedPageSize, searchValue, sort, tag]
|
|
2404
|
+
);
|
|
2405
|
+
useKeepUrlSync({
|
|
2406
|
+
enabled: Boolean(urlSync),
|
|
2407
|
+
query: resolvedQuery,
|
|
2408
|
+
onQueryChange: (nextOrUpdater) => {
|
|
2089
2409
|
const next = typeof nextOrUpdater === "function" ? nextOrUpdater(resolvedQuery) : nextOrUpdater;
|
|
2090
2410
|
setSearchValue(next.search?.query ?? "");
|
|
2091
2411
|
setSort(next.sort ?? { by: "updatedAt", direction: "desc" });
|
|
@@ -2095,7 +2415,7 @@ function useKeepCollection({
|
|
|
2095
2415
|
options: typeof urlSync === "object" ? urlSync : {},
|
|
2096
2416
|
adapter: urlAdapter
|
|
2097
2417
|
});
|
|
2098
|
-
const list =
|
|
2418
|
+
const list = useKeepList3(resolvedQuery);
|
|
2099
2419
|
return {
|
|
2100
2420
|
enabled,
|
|
2101
2421
|
searchValue,
|
|
@@ -2120,175 +2440,38 @@ function useKeepCollection({
|
|
|
2120
2440
|
};
|
|
2121
2441
|
}
|
|
2122
2442
|
|
|
2123
|
-
// src/KeepList.tsx
|
|
2443
|
+
// src/features/collection/KeepList.tsx
|
|
2124
2444
|
import { KeepErrorBoundary } from "@keepkit/core/react";
|
|
2125
|
-
import { isValidElement as
|
|
2445
|
+
import { isValidElement as isValidElement4 } from "react";
|
|
2126
2446
|
|
|
2127
|
-
// src/
|
|
2128
|
-
import {
|
|
2129
|
-
|
|
2447
|
+
// src/features/item/KeepItemCard.tsx
|
|
2448
|
+
import {
|
|
2449
|
+
createContext as createContext3,
|
|
2450
|
+
createElement as createElement2,
|
|
2451
|
+
isValidElement as isValidElement3,
|
|
2452
|
+
useContext as useContext3,
|
|
2453
|
+
useEffect as useEffect5,
|
|
2454
|
+
useState as useState8
|
|
2455
|
+
} from "react";
|
|
2456
|
+
|
|
2457
|
+
// src/features/item/hooks/useKeepItemStatusBadge.ts
|
|
2458
|
+
function useKeepItemStatusBadge(status) {
|
|
2130
2459
|
return {
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
empty: useUiLabel("noItems"),
|
|
2135
|
-
error: useUiLabel("errorItems")
|
|
2136
|
-
}
|
|
2460
|
+
resolvedStatus: getDisplayStatus(status),
|
|
2461
|
+
statusLabel: useUiLabel(getStatusLabelKey(status)),
|
|
2462
|
+
icon: getStatusIcon(status)
|
|
2137
2463
|
};
|
|
2138
2464
|
}
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
const getItems = useCallback3(() => {
|
|
2145
|
-
const root = ref.current;
|
|
2146
|
-
if (!root) return [];
|
|
2147
|
-
return Array.from(root.querySelectorAll('[data-keepkit="card"]')).filter(
|
|
2148
|
-
(item) => item.getAttribute("aria-hidden") !== "true" && item.getAttribute("data-roving-disabled") !== "true"
|
|
2149
|
-
);
|
|
2150
|
-
}, []);
|
|
2151
|
-
const syncTabIndices = useCallback3(() => {
|
|
2152
|
-
const items = getItems();
|
|
2153
|
-
if (items.length === 0) return;
|
|
2154
|
-
const activeElement = document.activeElement;
|
|
2155
|
-
const activeItem = items.find((item) => item === activeElement || item.contains(activeElement));
|
|
2156
|
-
const activeIndex = activeItem ? items.indexOf(activeItem) : 0;
|
|
2157
|
-
items.forEach((item, index) => {
|
|
2158
|
-
item.tabIndex = index === activeIndex ? 0 : -1;
|
|
2159
|
-
});
|
|
2160
|
-
}, [getItems]);
|
|
2161
|
-
useEffect3(() => {
|
|
2162
|
-
const root = ref.current;
|
|
2163
|
-
if (!root) return;
|
|
2164
|
-
syncTabIndices();
|
|
2165
|
-
const observer = new MutationObserver(syncTabIndices);
|
|
2166
|
-
observer.observe(root, { childList: true, subtree: true });
|
|
2167
|
-
return () => observer.disconnect();
|
|
2168
|
-
}, [syncTabIndices]);
|
|
2169
|
-
const onFocusCapture = useCallback3(
|
|
2170
|
-
(event) => {
|
|
2171
|
-
if (!(event.target instanceof HTMLElement)) return;
|
|
2172
|
-
const item = event.target.closest('[data-keepkit="card"]');
|
|
2173
|
-
if (!item || !ref.current?.contains(item)) return;
|
|
2174
|
-
getItems().forEach((candidate) => {
|
|
2175
|
-
candidate.tabIndex = candidate === item ? 0 : -1;
|
|
2176
|
-
});
|
|
2177
|
-
},
|
|
2178
|
-
[getItems]
|
|
2179
|
-
);
|
|
2180
|
-
const onKeyDown = useCallback3(
|
|
2181
|
-
(event) => {
|
|
2182
|
-
if (event.defaultPrevented) return;
|
|
2183
|
-
const items = getItems();
|
|
2184
|
-
if (!(event.target instanceof HTMLElement)) return;
|
|
2185
|
-
const current = event.target.closest('[data-keepkit="card"]');
|
|
2186
|
-
if (!current || current !== event.target || !ref.current?.contains(current)) return;
|
|
2187
|
-
const currentIndex = items.indexOf(current);
|
|
2188
|
-
if (currentIndex < 0) return;
|
|
2189
|
-
let nextIndex;
|
|
2190
|
-
if (event.key === "Home") nextIndex = 0;
|
|
2191
|
-
if (event.key === "End") nextIndex = items.length - 1;
|
|
2192
|
-
if (event.key === "ArrowRight" || event.key === "ArrowDown")
|
|
2193
|
-
nextIndex = Math.min(currentIndex + 1, items.length - 1);
|
|
2194
|
-
if (event.key === "ArrowLeft" || event.key === "ArrowUp") nextIndex = Math.max(currentIndex - 1, 0);
|
|
2195
|
-
if (nextIndex === void 0) return;
|
|
2196
|
-
event.preventDefault();
|
|
2197
|
-
if (nextIndex === currentIndex) return;
|
|
2198
|
-
items[nextIndex]?.focus();
|
|
2199
|
-
},
|
|
2200
|
-
[getItems]
|
|
2201
|
-
);
|
|
2202
|
-
return { ref, onKeyDown, onFocusCapture };
|
|
2203
|
-
}
|
|
2204
|
-
|
|
2205
|
-
// src/KeepItemCard.tsx
|
|
2206
|
-
import {
|
|
2207
|
-
createContext as createContext3,
|
|
2208
|
-
createElement as createElement2,
|
|
2209
|
-
isValidElement as isValidElement2,
|
|
2210
|
-
useContext as useContext3,
|
|
2211
|
-
useEffect as useEffect4,
|
|
2212
|
-
useState as useState5
|
|
2213
|
-
} from "react";
|
|
2214
|
-
|
|
2215
|
-
// src/hooks/useKeepItemCard.ts
|
|
2216
|
-
import { useKeepItem as useKeepItem2 } from "@keepkit/core/react";
|
|
2217
|
-
function useKeepItemCard(options) {
|
|
2218
|
-
const {
|
|
2219
|
-
item,
|
|
2220
|
-
title,
|
|
2221
|
-
getTitle,
|
|
2222
|
-
getImageProps,
|
|
2223
|
-
href: hrefOption,
|
|
2224
|
-
linkTargetAttribute,
|
|
2225
|
-
linkRel,
|
|
2226
|
-
onRemoveError,
|
|
2227
|
-
onRemoved
|
|
2228
|
-
} = options;
|
|
2229
|
-
const itemState = useKeepItem2(item);
|
|
2230
|
-
const emitFeedback = useKeepUiFeedback();
|
|
2231
|
-
const removedMessage = useUiLabel("removedMessage");
|
|
2232
|
-
const restoredMessage = useUiLabel("restoredMessage");
|
|
2233
|
-
const undoLabel = useUiLabel("undo");
|
|
2234
|
-
const resolvedTitle = typeof title === "function" ? title(item) : title ?? getTitle?.(item) ?? getMetaTitle(item.meta) ?? item.id;
|
|
2235
|
-
const imageProps = getImageProps?.(item, resolvedTitle);
|
|
2236
|
-
const href = typeof hrefOption === "function" ? hrefOption(item) : hrefOption;
|
|
2237
|
-
const isAvailable = item.status === void 0 || item.status === "available";
|
|
2238
|
-
const isExternalLink = href ? /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href) : false;
|
|
2239
|
-
const statusLabelKey = item.status && item.status !== "available" ? getStatusLabelKey(item.status) : "statusUnknown";
|
|
2240
|
-
const unavailableLabel = useUiLabel(statusLabelKey);
|
|
2241
|
-
const remove = async () => {
|
|
2242
|
-
const wasSaved = itemState.isSaved;
|
|
2243
|
-
try {
|
|
2244
|
-
await itemState.removeWithUndo();
|
|
2245
|
-
onRemoved?.(item);
|
|
2246
|
-
if (!wasSaved) return;
|
|
2247
|
-
emitFeedback({
|
|
2248
|
-
type: "item-removed",
|
|
2249
|
-
item,
|
|
2250
|
-
message: removedMessage,
|
|
2251
|
-
undoLabel,
|
|
2252
|
-
undo: async () => {
|
|
2253
|
-
await itemState.undo();
|
|
2254
|
-
emitFeedback({ type: "item-restored", item, items: [item], message: restoredMessage });
|
|
2255
|
-
}
|
|
2256
|
-
});
|
|
2257
|
-
} catch (cause) {
|
|
2258
|
-
onRemoveError?.(cause);
|
|
2259
|
-
}
|
|
2260
|
-
};
|
|
2261
|
-
const state = {
|
|
2262
|
-
item,
|
|
2263
|
-
isSaved: itemState.isSaved,
|
|
2264
|
-
isMutating: itemState.isMutating,
|
|
2265
|
-
error: itemState.error,
|
|
2266
|
-
remove,
|
|
2267
|
-
status: item.status
|
|
2268
|
-
};
|
|
2269
|
-
return {
|
|
2270
|
-
itemState,
|
|
2271
|
-
state,
|
|
2272
|
-
resolvedTitle,
|
|
2273
|
-
imageProps,
|
|
2274
|
-
href,
|
|
2275
|
-
isAvailable,
|
|
2276
|
-
displayStatus: getDisplayStatus(item.status),
|
|
2277
|
-
resolvedLinkTarget: linkTargetAttribute ?? (isExternalLink ? "_blank" : void 0),
|
|
2278
|
-
resolvedLinkRel: linkRel ?? (isExternalLink ? "noreferrer" : void 0),
|
|
2279
|
-
statusLabel: item.status && item.status !== "available" ? unavailableLabel : void 0,
|
|
2280
|
-
remove,
|
|
2281
|
-
labels: {
|
|
2282
|
-
save: useUiLabel("save"),
|
|
2283
|
-
savedAt: useUiLabel("saved"),
|
|
2284
|
-
error: useUiLabel("error"),
|
|
2285
|
-
remove: useUiLabel("remove"),
|
|
2286
|
-
tags: useUiLabel("tags")
|
|
2287
|
-
}
|
|
2288
|
-
};
|
|
2465
|
+
function getStatusIcon(status) {
|
|
2466
|
+
if (status === "available") return "check";
|
|
2467
|
+
if (status === "expired") return "clock";
|
|
2468
|
+
if (status === "removed" || status === "deleted") return "ban";
|
|
2469
|
+
return "lock";
|
|
2289
2470
|
}
|
|
2290
2471
|
function getStatusLabelKey(status) {
|
|
2291
2472
|
switch (status) {
|
|
2473
|
+
case "available":
|
|
2474
|
+
return "statusAvailable";
|
|
2292
2475
|
case "expired":
|
|
2293
2476
|
return "statusExpired";
|
|
2294
2477
|
case "removed":
|
|
@@ -2297,28 +2480,85 @@ function getStatusLabelKey(status) {
|
|
|
2297
2480
|
return "statusDeleted";
|
|
2298
2481
|
case "private":
|
|
2299
2482
|
return "statusPrivate";
|
|
2300
|
-
|
|
2483
|
+
case "unknown":
|
|
2301
2484
|
return "statusUnknown";
|
|
2485
|
+
case "restricted":
|
|
2486
|
+
return "statusPrivate";
|
|
2302
2487
|
}
|
|
2303
2488
|
}
|
|
2304
2489
|
function getDisplayStatus(status) {
|
|
2305
|
-
if (status ===
|
|
2490
|
+
if (status === "available") return "available";
|
|
2306
2491
|
if (status === "expired") return "expired";
|
|
2307
|
-
if (status === "removed") return "removed";
|
|
2492
|
+
if (status === "removed" || status === "deleted") return "removed";
|
|
2308
2493
|
return "restricted";
|
|
2309
2494
|
}
|
|
2310
2495
|
|
|
2311
|
-
// src/
|
|
2312
|
-
import {
|
|
2313
|
-
|
|
2496
|
+
// src/features/item/KeepItemStatusBadge.tsx
|
|
2497
|
+
import { Fragment as Fragment5, jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
2498
|
+
function KeepItemStatusBadge({ status = "available", label, className, ...props }) {
|
|
2499
|
+
const view = useKeepItemStatusBadge(status);
|
|
2500
|
+
return /* @__PURE__ */ jsxs7(
|
|
2501
|
+
"span",
|
|
2502
|
+
{
|
|
2503
|
+
...props,
|
|
2504
|
+
className,
|
|
2505
|
+
role: "img",
|
|
2506
|
+
"aria-label": props["aria-label"] ?? view.statusLabel,
|
|
2507
|
+
"data-keepkit": "status-badge",
|
|
2508
|
+
"data-status": status,
|
|
2509
|
+
"data-item-status": view.resolvedStatus,
|
|
2510
|
+
children: [
|
|
2511
|
+
/* @__PURE__ */ jsx10(StatusIcon, { name: view.icon }),
|
|
2512
|
+
/* @__PURE__ */ jsx10("span", { "data-status-label": "true", children: label ?? view.statusLabel })
|
|
2513
|
+
]
|
|
2514
|
+
}
|
|
2515
|
+
);
|
|
2516
|
+
}
|
|
2517
|
+
function StatusIcon({ name }) {
|
|
2518
|
+
return /* @__PURE__ */ jsxs7(
|
|
2519
|
+
"svg",
|
|
2520
|
+
{
|
|
2521
|
+
"data-status-icon": name,
|
|
2522
|
+
viewBox: "0 0 24 24",
|
|
2523
|
+
width: "1em",
|
|
2524
|
+
height: "1em",
|
|
2525
|
+
fill: "none",
|
|
2526
|
+
stroke: "currentColor",
|
|
2527
|
+
strokeWidth: "2",
|
|
2528
|
+
strokeLinecap: "round",
|
|
2529
|
+
strokeLinejoin: "round",
|
|
2530
|
+
"aria-hidden": "true",
|
|
2531
|
+
focusable: "false",
|
|
2532
|
+
children: [
|
|
2533
|
+
name === "check" ? /* @__PURE__ */ jsx10("path", { d: "m5 12 4 4L19 6" }) : null,
|
|
2534
|
+
name === "clock" ? /* @__PURE__ */ jsxs7(Fragment5, { children: [
|
|
2535
|
+
/* @__PURE__ */ jsx10("circle", { cx: "12", cy: "12", r: "8" }),
|
|
2536
|
+
/* @__PURE__ */ jsx10("path", { d: "M12 7v5l3 2" })
|
|
2537
|
+
] }) : null,
|
|
2538
|
+
name === "ban" ? /* @__PURE__ */ jsxs7(Fragment5, { children: [
|
|
2539
|
+
/* @__PURE__ */ jsx10("circle", { cx: "12", cy: "12", r: "8" }),
|
|
2540
|
+
/* @__PURE__ */ jsx10("path", { d: "m6.5 6.5 11 11" })
|
|
2541
|
+
] }) : null,
|
|
2542
|
+
name === "lock" ? /* @__PURE__ */ jsxs7(Fragment5, { children: [
|
|
2543
|
+
/* @__PURE__ */ jsx10("rect", { x: "5", y: "10", width: "14", height: "10", rx: "2" }),
|
|
2544
|
+
/* @__PURE__ */ jsx10("path", { d: "M8 10V7a4 4 0 0 1 8 0v3" })
|
|
2545
|
+
] }) : null
|
|
2546
|
+
]
|
|
2547
|
+
}
|
|
2548
|
+
);
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
// src/features/status/hooks/useKeepStaleNotice.ts
|
|
2552
|
+
import { useKeepContext as useKeepContext3 } from "@keepkit/core/react";
|
|
2553
|
+
import { useState as useState7 } from "react";
|
|
2314
2554
|
function useKeepStaleNotice({ item, onRetry, onRemoved }) {
|
|
2315
|
-
const context =
|
|
2555
|
+
const context = useKeepContext3();
|
|
2316
2556
|
const emitFeedback = useKeepUiFeedback();
|
|
2317
2557
|
const removedMessage = useUiLabel("removedMessage");
|
|
2318
2558
|
const restoredMessage = useUiLabel("restoredMessage");
|
|
2319
2559
|
const undoLabel = useUiLabel("undo");
|
|
2320
|
-
const [isRetrying, setIsRetrying] =
|
|
2321
|
-
const [error, setError] =
|
|
2560
|
+
const [isRetrying, setIsRetrying] = useState7(false);
|
|
2561
|
+
const [error, setError] = useState7(null);
|
|
2322
2562
|
async function retry() {
|
|
2323
2563
|
setError(null);
|
|
2324
2564
|
setIsRetrying(true);
|
|
@@ -2364,7 +2604,7 @@ function useKeepStaleNotice({ item, onRetry, onRemoved }) {
|
|
|
2364
2604
|
};
|
|
2365
2605
|
}
|
|
2366
2606
|
function useKeepPruneStale({ statuses, onPruned }) {
|
|
2367
|
-
const context =
|
|
2607
|
+
const context = useKeepContext3();
|
|
2368
2608
|
const emitFeedback = useKeepUiFeedback();
|
|
2369
2609
|
const staleItems = context.items.filter((item) => item.status && statuses.includes(item.status));
|
|
2370
2610
|
const staleIds = staleItems.map((item) => item.id);
|
|
@@ -2397,54 +2637,8 @@ function useKeepPruneStale({ statuses, onPruned }) {
|
|
|
2397
2637
|
};
|
|
2398
2638
|
}
|
|
2399
2639
|
|
|
2400
|
-
// src/
|
|
2401
|
-
|
|
2402
|
-
return { resolvedStatus: getDisplayStatus2(status), statusLabel: useUiLabel(getStatusLabelKey2(status)) };
|
|
2403
|
-
}
|
|
2404
|
-
function getStatusLabelKey2(status) {
|
|
2405
|
-
switch (status) {
|
|
2406
|
-
case "available":
|
|
2407
|
-
return "statusAvailable";
|
|
2408
|
-
case "expired":
|
|
2409
|
-
return "statusExpired";
|
|
2410
|
-
case "removed":
|
|
2411
|
-
return "statusRemoved";
|
|
2412
|
-
case "deleted":
|
|
2413
|
-
return "statusDeleted";
|
|
2414
|
-
case "private":
|
|
2415
|
-
return "statusPrivate";
|
|
2416
|
-
case "unknown":
|
|
2417
|
-
return "statusUnknown";
|
|
2418
|
-
case "restricted":
|
|
2419
|
-
return "statusPrivate";
|
|
2420
|
-
}
|
|
2421
|
-
}
|
|
2422
|
-
function getDisplayStatus2(status) {
|
|
2423
|
-
if (status === "available") return "available";
|
|
2424
|
-
if (status === "expired") return "expired";
|
|
2425
|
-
if (status === "removed") return "removed";
|
|
2426
|
-
return "restricted";
|
|
2427
|
-
}
|
|
2428
|
-
|
|
2429
|
-
// src/KeepItemStatusBadge.tsx
|
|
2430
|
-
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
2431
|
-
function KeepItemStatusBadge({ status = "available", label, className, ...props }) {
|
|
2432
|
-
const view = useKeepItemStatusBadge(status);
|
|
2433
|
-
return /* @__PURE__ */ jsx7(
|
|
2434
|
-
"span",
|
|
2435
|
-
{
|
|
2436
|
-
...props,
|
|
2437
|
-
className,
|
|
2438
|
-
"data-keepkit": "status-badge",
|
|
2439
|
-
"data-status": status,
|
|
2440
|
-
"data-item-status": view.resolvedStatus,
|
|
2441
|
-
children: label ?? view.statusLabel
|
|
2442
|
-
}
|
|
2443
|
-
);
|
|
2444
|
-
}
|
|
2445
|
-
|
|
2446
|
-
// src/KeepStaleNotice.tsx
|
|
2447
|
-
import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2640
|
+
// src/features/status/KeepStaleNotice.tsx
|
|
2641
|
+
import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2448
2642
|
function KeepStaleNotice({
|
|
2449
2643
|
item,
|
|
2450
2644
|
onRetry,
|
|
@@ -2456,11 +2650,11 @@ function KeepStaleNotice({
|
|
|
2456
2650
|
...props
|
|
2457
2651
|
}) {
|
|
2458
2652
|
const view = useKeepStaleNotice({ item, onRetry, onRemoved });
|
|
2459
|
-
return /* @__PURE__ */
|
|
2460
|
-
/* @__PURE__ */
|
|
2461
|
-
children ?? (item.statusReason ? /* @__PURE__ */
|
|
2462
|
-
/* @__PURE__ */
|
|
2463
|
-
/* @__PURE__ */
|
|
2653
|
+
return /* @__PURE__ */ jsxs8("aside", { ...props, className, "data-keepkit": "stale-notice", "data-state": view.error ? "error" : "stale", children: [
|
|
2654
|
+
/* @__PURE__ */ jsx11(KeepItemStatusBadge, { status: view.status }),
|
|
2655
|
+
children ?? (item.statusReason ? /* @__PURE__ */ jsx11("p", { children: item.statusReason }) : null),
|
|
2656
|
+
/* @__PURE__ */ jsxs8("div", { children: [
|
|
2657
|
+
/* @__PURE__ */ jsx11(
|
|
2464
2658
|
"button",
|
|
2465
2659
|
{
|
|
2466
2660
|
type: "button",
|
|
@@ -2470,7 +2664,7 @@ function KeepStaleNotice({
|
|
|
2470
2664
|
children: retryLabel ?? view.labels.retry
|
|
2471
2665
|
}
|
|
2472
2666
|
),
|
|
2473
|
-
/* @__PURE__ */
|
|
2667
|
+
/* @__PURE__ */ jsx11(
|
|
2474
2668
|
"button",
|
|
2475
2669
|
{
|
|
2476
2670
|
type: "button",
|
|
@@ -2481,7 +2675,7 @@ function KeepStaleNotice({
|
|
|
2481
2675
|
}
|
|
2482
2676
|
)
|
|
2483
2677
|
] }),
|
|
2484
|
-
view.error ? /* @__PURE__ */
|
|
2678
|
+
view.error ? /* @__PURE__ */ jsx11("p", { role: "alert", children: view.error instanceof Error ? view.error.message : view.labels.error }) : null
|
|
2485
2679
|
] });
|
|
2486
2680
|
}
|
|
2487
2681
|
function KeepPruneStaleButton({
|
|
@@ -2493,7 +2687,7 @@ function KeepPruneStaleButton({
|
|
|
2493
2687
|
...props
|
|
2494
2688
|
}) {
|
|
2495
2689
|
const view = useKeepPruneStale({ statuses, onPruned });
|
|
2496
|
-
return /* @__PURE__ */
|
|
2690
|
+
return /* @__PURE__ */ jsx11(
|
|
2497
2691
|
"button",
|
|
2498
2692
|
{
|
|
2499
2693
|
...props,
|
|
@@ -2509,47 +2703,143 @@ function KeepPruneStaleButton({
|
|
|
2509
2703
|
);
|
|
2510
2704
|
}
|
|
2511
2705
|
|
|
2512
|
-
// src/
|
|
2513
|
-
import {
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2706
|
+
// src/features/item/hooks/useKeepItemCard.ts
|
|
2707
|
+
import { useKeepItem as useKeepItem2 } from "@keepkit/core/react";
|
|
2708
|
+
function useKeepItemCard(options) {
|
|
2709
|
+
const {
|
|
2710
|
+
item,
|
|
2711
|
+
title,
|
|
2712
|
+
getTitle,
|
|
2713
|
+
getImageProps,
|
|
2714
|
+
href: hrefOption,
|
|
2715
|
+
linkTargetAttribute,
|
|
2716
|
+
linkRel,
|
|
2717
|
+
onRemoveError,
|
|
2718
|
+
onRemoved
|
|
2719
|
+
} = options;
|
|
2720
|
+
const itemState = useKeepItem2(item);
|
|
2721
|
+
const emitFeedback = useKeepUiFeedback();
|
|
2722
|
+
const removedMessage = useUiLabel("removedMessage");
|
|
2723
|
+
const restoredMessage = useUiLabel("restoredMessage");
|
|
2724
|
+
const undoLabel = useUiLabel("undo");
|
|
2725
|
+
const resolvedTitle = typeof title === "function" ? title(item) : title ?? getTitle?.(item) ?? getMetaTitle(item.meta) ?? item.id;
|
|
2726
|
+
const imageProps = getImageProps?.(item, resolvedTitle);
|
|
2727
|
+
const href = typeof hrefOption === "function" ? hrefOption(item) : hrefOption;
|
|
2728
|
+
const isAvailable = item.status === void 0 || item.status === "available";
|
|
2729
|
+
const isExternalLink = href ? /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href) : false;
|
|
2730
|
+
const statusLabelKey = item.status && item.status !== "available" ? getStatusLabelKey2(item.status) : "statusUnknown";
|
|
2731
|
+
const unavailableLabel = useUiLabel(statusLabelKey);
|
|
2732
|
+
const remove = async () => {
|
|
2733
|
+
const wasSaved = itemState.isSaved;
|
|
2734
|
+
try {
|
|
2735
|
+
await itemState.removeWithUndo();
|
|
2736
|
+
onRemoved?.(item);
|
|
2737
|
+
if (!wasSaved) return;
|
|
2738
|
+
emitFeedback({
|
|
2739
|
+
type: "item-removed",
|
|
2740
|
+
item,
|
|
2741
|
+
message: removedMessage,
|
|
2742
|
+
undoLabel,
|
|
2743
|
+
undo: async () => {
|
|
2744
|
+
await itemState.undo();
|
|
2745
|
+
emitFeedback({ type: "item-restored", item, items: [item], message: restoredMessage });
|
|
2746
|
+
}
|
|
2747
|
+
});
|
|
2748
|
+
} catch (cause) {
|
|
2749
|
+
onRemoveError?.(cause);
|
|
2750
|
+
}
|
|
2751
|
+
};
|
|
2752
|
+
const state = {
|
|
2753
|
+
item,
|
|
2754
|
+
isSaved: itemState.isSaved,
|
|
2755
|
+
isMutating: itemState.isMutating,
|
|
2756
|
+
error: itemState.error,
|
|
2757
|
+
remove,
|
|
2758
|
+
status: item.status
|
|
2759
|
+
};
|
|
2760
|
+
return {
|
|
2761
|
+
itemState,
|
|
2762
|
+
state,
|
|
2763
|
+
resolvedTitle,
|
|
2764
|
+
imageProps,
|
|
2765
|
+
href,
|
|
2766
|
+
isAvailable,
|
|
2767
|
+
displayStatus: getDisplayStatus2(item.status),
|
|
2768
|
+
resolvedLinkTarget: linkTargetAttribute ?? (isExternalLink ? "_blank" : void 0),
|
|
2769
|
+
resolvedLinkRel: linkRel ?? (isExternalLink ? "noreferrer" : void 0),
|
|
2770
|
+
statusLabel: item.status && item.status !== "available" ? unavailableLabel : void 0,
|
|
2771
|
+
remove,
|
|
2772
|
+
labels: {
|
|
2773
|
+
save: useUiLabel("save"),
|
|
2774
|
+
savedAt: useUiLabel("saved"),
|
|
2775
|
+
error: useUiLabel("error"),
|
|
2776
|
+
remove: useUiLabel("remove"),
|
|
2777
|
+
tags: useUiLabel("tags")
|
|
2778
|
+
}
|
|
2779
|
+
};
|
|
2780
|
+
}
|
|
2781
|
+
function getStatusLabelKey2(status) {
|
|
2782
|
+
switch (status) {
|
|
2783
|
+
case "expired":
|
|
2784
|
+
return "statusExpired";
|
|
2785
|
+
case "removed":
|
|
2786
|
+
return "statusRemoved";
|
|
2787
|
+
case "deleted":
|
|
2788
|
+
return "statusDeleted";
|
|
2789
|
+
case "private":
|
|
2790
|
+
return "statusPrivate";
|
|
2791
|
+
default:
|
|
2792
|
+
return "statusUnknown";
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
function getDisplayStatus2(status) {
|
|
2796
|
+
if (status === void 0 || status === "available") return "available";
|
|
2797
|
+
if (status === "expired") return "expired";
|
|
2798
|
+
if (status === "removed") return "removed";
|
|
2799
|
+
return "restricted";
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2802
|
+
// src/features/item/KeepItemCard.tsx
|
|
2803
|
+
import { Fragment as Fragment6, jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2804
|
+
var KeepItemCardContext = createContext3(null);
|
|
2805
|
+
function useKeepItemCardCompound(part) {
|
|
2806
|
+
const context = useContext3(KeepItemCardContext);
|
|
2807
|
+
if (!context) throw new Error(`KeepItemCard.${part} must be rendered inside KeepItemCard.`);
|
|
2808
|
+
return context;
|
|
2809
|
+
}
|
|
2810
|
+
function KeepItemCardRoot({
|
|
2811
|
+
item,
|
|
2812
|
+
title,
|
|
2813
|
+
getTitle,
|
|
2814
|
+
getImageProps,
|
|
2815
|
+
imageComponent: ImageComponent,
|
|
2816
|
+
renderImage,
|
|
2817
|
+
renderTags,
|
|
2818
|
+
showTags = true,
|
|
2819
|
+
showSavedAt = true,
|
|
2820
|
+
imageAlt,
|
|
2821
|
+
render,
|
|
2822
|
+
children,
|
|
2823
|
+
removeLabel,
|
|
2824
|
+
onRemoveError,
|
|
2825
|
+
onRemoved,
|
|
2826
|
+
onRetry,
|
|
2827
|
+
showSaveButton = true,
|
|
2828
|
+
saveButtonLabels,
|
|
2829
|
+
asChild = false,
|
|
2830
|
+
href: hrefOption,
|
|
2831
|
+
onOpen,
|
|
2832
|
+
linkTarget = "title",
|
|
2833
|
+
linkComponent: LinkComponent,
|
|
2834
|
+
linkTargetAttribute,
|
|
2835
|
+
linkRel,
|
|
2836
|
+
highlightQuery,
|
|
2837
|
+
className,
|
|
2838
|
+
...rootProps
|
|
2839
|
+
}) {
|
|
2840
|
+
const view = useKeepItemCard({
|
|
2841
|
+
item,
|
|
2842
|
+
title,
|
|
2553
2843
|
getTitle,
|
|
2554
2844
|
getImageProps,
|
|
2555
2845
|
href: hrefOption,
|
|
@@ -2560,7 +2850,7 @@ function KeepItemCardRoot({
|
|
|
2560
2850
|
});
|
|
2561
2851
|
const contextQuery = useKeepSearchQuery();
|
|
2562
2852
|
const searchQuery = highlightQuery ?? contextQuery;
|
|
2563
|
-
const contentChildren = asChild &&
|
|
2853
|
+
const contentChildren = asChild && isValidElement3(children) ? void 0 : children;
|
|
2564
2854
|
function renderLink(content) {
|
|
2565
2855
|
if (!view.href || !view.isAvailable) return content;
|
|
2566
2856
|
const linkProps = {
|
|
@@ -2570,17 +2860,17 @@ function KeepItemCardRoot({
|
|
|
2570
2860
|
onClick: (event) => onOpen?.(item, event),
|
|
2571
2861
|
children: content
|
|
2572
2862
|
};
|
|
2573
|
-
return LinkComponent ? /* @__PURE__ */
|
|
2863
|
+
return LinkComponent ? /* @__PURE__ */ jsx12(LinkComponent, { ...linkProps }) : /* @__PURE__ */ jsx12("a", { ...linkProps });
|
|
2574
2864
|
}
|
|
2575
2865
|
function renderTitle(content) {
|
|
2576
2866
|
if (linkTarget !== "title") return content;
|
|
2577
2867
|
if (view.isAvailable) return renderLink(content);
|
|
2578
|
-
return view.href ? /* @__PURE__ */
|
|
2868
|
+
return view.href ? /* @__PURE__ */ jsx12("span", { "aria-disabled": "true", "data-link-disabled": "true", children: content }) : content;
|
|
2579
2869
|
}
|
|
2580
2870
|
const resolvedImageProps = view.imageProps ? { ...view.imageProps, alt: imageAlt ?? view.imageProps.alt } : void 0;
|
|
2581
2871
|
const imageSource = resolvedImageProps?.src;
|
|
2582
|
-
const [imageStatus, setImageStatus] =
|
|
2583
|
-
|
|
2872
|
+
const [imageStatus, setImageStatus] = useState8(imageSource ? "loading" : "error");
|
|
2873
|
+
useEffect5(() => {
|
|
2584
2874
|
setImageStatus(imageSource ? "loading" : "error");
|
|
2585
2875
|
}, [imageSource]);
|
|
2586
2876
|
let image = null;
|
|
@@ -2598,21 +2888,21 @@ function KeepItemCardRoot({
|
|
|
2598
2888
|
setImageStatus("error");
|
|
2599
2889
|
}
|
|
2600
2890
|
};
|
|
2601
|
-
image = renderImage?.(imagePropsWithHandlers, item) ?? (ImageComponent ? /* @__PURE__ */
|
|
2891
|
+
image = renderImage?.(imagePropsWithHandlers, item) ?? (ImageComponent ? /* @__PURE__ */ jsx12(ImageComponent, { ...imagePropsWithHandlers }) : /* @__PURE__ */ jsx12("img", { ...imagePropsWithHandlers, alt: imagePropsWithHandlers.alt }));
|
|
2602
2892
|
}
|
|
2603
2893
|
const tags = showTags ? item.tags ?? [] : [];
|
|
2604
2894
|
const renderedTags = showTags && tags.length > 0 ? renderTags?.(tags, item) ?? null : null;
|
|
2605
|
-
const meta = showSavedAt ? /* @__PURE__ */
|
|
2606
|
-
/* @__PURE__ */
|
|
2895
|
+
const meta = showSavedAt ? /* @__PURE__ */ jsxs9("div", { "data-card-meta": true, children: [
|
|
2896
|
+
/* @__PURE__ */ jsxs9("span", { children: [
|
|
2607
2897
|
view.labels.savedAt,
|
|
2608
2898
|
":"
|
|
2609
2899
|
] }),
|
|
2610
2900
|
" ",
|
|
2611
|
-
/* @__PURE__ */
|
|
2901
|
+
/* @__PURE__ */ jsx12("time", { dateTime: new Date(item.savedAt).toISOString(), children: formatSavedAt(item.savedAt) })
|
|
2612
2902
|
] }) : null;
|
|
2613
|
-
const error = view.itemState.error ? /* @__PURE__ */
|
|
2614
|
-
const actions = view.statusLabel ? /* @__PURE__ */
|
|
2615
|
-
showSaveButton ? /* @__PURE__ */
|
|
2903
|
+
const error = view.itemState.error ? /* @__PURE__ */ jsx12("p", { role: "alert", children: getErrorMessage2(view.itemState.error, view.labels.error) }) : null;
|
|
2904
|
+
const actions = view.statusLabel ? /* @__PURE__ */ jsx12(KeepStaleNotice, { item, onRetry, onRemoved }) : /* @__PURE__ */ jsxs9(Fragment6, { children: [
|
|
2905
|
+
showSaveButton ? /* @__PURE__ */ jsx12(
|
|
2616
2906
|
KeepButton,
|
|
2617
2907
|
{
|
|
2618
2908
|
item: toKeepButtonItem(item),
|
|
@@ -2620,7 +2910,7 @@ function KeepItemCardRoot({
|
|
|
2620
2910
|
getAriaLabel: (buttonState) => `${buttonState.isSaved ? view.labels.remove : view.labels.save} ${String(view.resolvedTitle)}`
|
|
2621
2911
|
}
|
|
2622
2912
|
) : null,
|
|
2623
|
-
/* @__PURE__ */
|
|
2913
|
+
/* @__PURE__ */ jsx12(
|
|
2624
2914
|
"button",
|
|
2625
2915
|
{
|
|
2626
2916
|
type: "button",
|
|
@@ -2643,18 +2933,18 @@ function KeepItemCardRoot({
|
|
|
2643
2933
|
meta,
|
|
2644
2934
|
error,
|
|
2645
2935
|
actions,
|
|
2646
|
-
renderText: (content) => /* @__PURE__ */
|
|
2936
|
+
renderText: (content) => /* @__PURE__ */ jsx12(KeepHighlight, { query: searchQuery, children: content })
|
|
2647
2937
|
};
|
|
2648
|
-
const defaultBody = /* @__PURE__ */
|
|
2649
|
-
/* @__PURE__ */
|
|
2650
|
-
/* @__PURE__ */
|
|
2651
|
-
/* @__PURE__ */
|
|
2938
|
+
const defaultBody = /* @__PURE__ */ jsxs9(Fragment6, { children: [
|
|
2939
|
+
/* @__PURE__ */ jsx12(KeepItemCardMedia, {}),
|
|
2940
|
+
/* @__PURE__ */ jsx12(KeepItemCardContent, {}),
|
|
2941
|
+
/* @__PURE__ */ jsx12(KeepItemCardActions, {})
|
|
2652
2942
|
] });
|
|
2653
2943
|
const body = render ? render(view.state) : typeof contentChildren === "function" ? contentChildren(view.state) : contentChildren ?? defaultBody;
|
|
2654
2944
|
const linkedBody = linkTarget === "card" && view.isAvailable ? renderLink(body) : body;
|
|
2655
2945
|
const root = renderRoot(
|
|
2656
2946
|
asChild,
|
|
2657
|
-
|
|
2947
|
+
isValidElement3(children) ? children : void 0,
|
|
2658
2948
|
{
|
|
2659
2949
|
...rootProps,
|
|
2660
2950
|
className,
|
|
@@ -2669,18 +2959,18 @@ function KeepItemCardRoot({
|
|
|
2669
2959
|
linkedBody,
|
|
2670
2960
|
"KeepItemCard"
|
|
2671
2961
|
);
|
|
2672
|
-
return /* @__PURE__ */
|
|
2962
|
+
return /* @__PURE__ */ jsx12(KeepItemCardContext.Provider, { value: compoundValue, children: root });
|
|
2673
2963
|
}
|
|
2674
2964
|
function KeepItemCardMedia({ children, fallback, ...props }) {
|
|
2675
2965
|
const context = useKeepItemCardCompound("Media");
|
|
2676
|
-
return /* @__PURE__ */
|
|
2966
|
+
return /* @__PURE__ */ jsx12("div", { ...props, "data-keep-card-part": "media", "data-media-status": context.imageStatus, "data-aspect-ratio": "1/1", children: children ?? context.image ?? /* @__PURE__ */ jsx12("span", { role: "img", "aria-label": context.fallbackLabel, "data-keep-card-fallback": "true", children: fallback ?? /* @__PURE__ */ jsx12(KeepMediaPlaceholderIcon, {}) }) });
|
|
2677
2967
|
}
|
|
2678
2968
|
function KeepItemCardContent({ children, ...props }) {
|
|
2679
2969
|
const context = useKeepItemCardCompound("Content");
|
|
2680
|
-
return /* @__PURE__ */
|
|
2681
|
-
/* @__PURE__ */
|
|
2970
|
+
return /* @__PURE__ */ jsx12("div", { ...props, "data-keep-card-part": "content", children: children === void 0 ? /* @__PURE__ */ jsxs9(Fragment6, { children: [
|
|
2971
|
+
/* @__PURE__ */ jsx12(KeepItemCardTitle, {}),
|
|
2682
2972
|
context.meta,
|
|
2683
|
-
/* @__PURE__ */
|
|
2973
|
+
/* @__PURE__ */ jsx12(KeepItemCardTags, {}),
|
|
2684
2974
|
context.error
|
|
2685
2975
|
] }) : context.renderText(children) });
|
|
2686
2976
|
}
|
|
@@ -2688,13 +2978,13 @@ function KeepItemCardTitle({ as = "h3", children, ...props }) {
|
|
|
2688
2978
|
const context = useKeepItemCardCompound("Title");
|
|
2689
2979
|
return createElement2(
|
|
2690
2980
|
as,
|
|
2691
|
-
{ ...props, "data-keep-card-part": "title" },
|
|
2981
|
+
{ ...props, "data-keep-card-part": "title", "data-line-clamp": "2" },
|
|
2692
2982
|
context.renderTitle(context.renderText(children ?? context.resolvedTitle))
|
|
2693
2983
|
);
|
|
2694
2984
|
}
|
|
2695
2985
|
function KeepMediaPlaceholderIcon() {
|
|
2696
|
-
return /* @__PURE__ */
|
|
2697
|
-
/* @__PURE__ */
|
|
2986
|
+
return /* @__PURE__ */ jsxs9("svg", { "data-media-fallback-icon": "true", viewBox: "0 0 24 24", "aria-hidden": "true", children: [
|
|
2987
|
+
/* @__PURE__ */ jsx12(
|
|
2698
2988
|
"path",
|
|
2699
2989
|
{
|
|
2700
2990
|
d: "M4 5.5A1.5 1.5 0 0 1 5.5 4h13A1.5 1.5 0 0 1 20 5.5v13a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 4 18.5v-13Z",
|
|
@@ -2702,19 +2992,19 @@ function KeepMediaPlaceholderIcon() {
|
|
|
2702
2992
|
stroke: "currentColor"
|
|
2703
2993
|
}
|
|
2704
2994
|
),
|
|
2705
|
-
/* @__PURE__ */
|
|
2706
|
-
/* @__PURE__ */
|
|
2995
|
+
/* @__PURE__ */ jsx12("circle", { cx: "9", cy: "9", r: "1.5", fill: "currentColor" }),
|
|
2996
|
+
/* @__PURE__ */ jsx12("path", { d: "m5.5 18 4.5-4.5 3 3 2-2L19 18", fill: "none", stroke: "currentColor" })
|
|
2707
2997
|
] });
|
|
2708
2998
|
}
|
|
2709
2999
|
function KeepItemCardTags({ children, ...props }) {
|
|
2710
3000
|
const context = useKeepItemCardCompound("Tags");
|
|
2711
3001
|
if (children === void 0 && context.renderedTags) return context.renderedTags;
|
|
2712
3002
|
if (children === void 0 && context.tags.length === 0) return null;
|
|
2713
|
-
return /* @__PURE__ */
|
|
3003
|
+
return /* @__PURE__ */ jsx12("ul", { ...props, "aria-label": props["aria-label"] ?? context.tagsLabel, "data-keep-card-part": "tags", children: children ?? context.tags.map((tag) => /* @__PURE__ */ jsx12("li", { children: tag }, tag)) });
|
|
2714
3004
|
}
|
|
2715
3005
|
function KeepItemCardActions({ children, ...props }) {
|
|
2716
3006
|
const context = useKeepItemCardCompound("Actions");
|
|
2717
|
-
return /* @__PURE__ */
|
|
3007
|
+
return /* @__PURE__ */ jsx12("div", { ...props, "data-keep-card-part": "actions", children: children ?? context.actions });
|
|
2718
3008
|
}
|
|
2719
3009
|
var KeepItemCard = Object.assign(KeepItemCardRoot, {
|
|
2720
3010
|
Media: KeepItemCardMedia,
|
|
@@ -2724,12 +3014,12 @@ var KeepItemCard = Object.assign(KeepItemCardRoot, {
|
|
|
2724
3014
|
Actions: KeepItemCardActions
|
|
2725
3015
|
});
|
|
2726
3016
|
function KeepItemCardSkeleton({ layout = "list", ...props }) {
|
|
2727
|
-
return /* @__PURE__ */
|
|
2728
|
-
/* @__PURE__ */
|
|
2729
|
-
/* @__PURE__ */
|
|
2730
|
-
/* @__PURE__ */
|
|
2731
|
-
/* @__PURE__ */
|
|
2732
|
-
/* @__PURE__ */
|
|
3017
|
+
return /* @__PURE__ */ jsxs9("article", { ...props, "aria-hidden": "true", "data-keepkit": "card-skeleton", "data-layout": layout, "data-state": "loading", children: [
|
|
3018
|
+
/* @__PURE__ */ jsx12("span", { "data-skeleton-part": "media" }),
|
|
3019
|
+
/* @__PURE__ */ jsx12("span", { "data-skeleton-part": "title" }),
|
|
3020
|
+
/* @__PURE__ */ jsx12("span", { "data-skeleton-part": "meta" }),
|
|
3021
|
+
/* @__PURE__ */ jsx12("span", { "data-skeleton-part": "tag" }),
|
|
3022
|
+
/* @__PURE__ */ jsx12("span", { "data-skeleton-part": "tag" })
|
|
2733
3023
|
] });
|
|
2734
3024
|
}
|
|
2735
3025
|
function formatSavedAt(timestamp) {
|
|
@@ -2739,388 +3029,184 @@ function getErrorMessage2(error, fallback) {
|
|
|
2739
3029
|
return error instanceof Error ? error.message : fallback;
|
|
2740
3030
|
}
|
|
2741
3031
|
|
|
2742
|
-
// src/
|
|
2743
|
-
import { Fragment as Fragment5, jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
2744
|
-
function KeepList(props) {
|
|
2745
|
-
const { fallback, onBoundaryError, boundaryResetKey, ...listProps } = props;
|
|
2746
|
-
const content = /* @__PURE__ */ jsx10(KeepListContent, { ...listProps });
|
|
2747
|
-
if (fallback === void 0 && onBoundaryError === void 0) return content;
|
|
2748
|
-
return /* @__PURE__ */ jsx10(KeepErrorBoundary, { fallback, onError: onBoundaryError, resetKey: boundaryResetKey, children: content });
|
|
2749
|
-
}
|
|
2750
|
-
function KeepListContent({
|
|
2751
|
-
query,
|
|
2752
|
-
children,
|
|
2753
|
-
renderItem,
|
|
2754
|
-
loading,
|
|
2755
|
-
renderLoading,
|
|
2756
|
-
loadingCount = 6,
|
|
2757
|
-
empty,
|
|
2758
|
-
error: errorContent,
|
|
2759
|
-
itemCardProps,
|
|
2760
|
-
layout = "list",
|
|
2761
|
-
asChild = false,
|
|
2762
|
-
onKeyDown,
|
|
2763
|
-
onFocusCapture,
|
|
2764
|
-
className,
|
|
2765
|
-
...rootProps
|
|
2766
|
-
}) {
|
|
2767
|
-
const view = useKeepListView(query);
|
|
2768
|
-
const roving = useRovingTabIndex();
|
|
2769
|
-
const { state } = view;
|
|
2770
|
-
const body = getListBody(state, {
|
|
2771
|
-
children,
|
|
2772
|
-
renderItem,
|
|
2773
|
-
loading: renderLoading !== void 0 ? renderLoading : loading,
|
|
2774
|
-
loadingCount,
|
|
2775
|
-
loadingLabel: view.labels.loading,
|
|
2776
|
-
empty: empty ?? view.labels.empty,
|
|
2777
|
-
error: errorContent ?? view.labels.error,
|
|
2778
|
-
itemCardProps,
|
|
2779
|
-
layout
|
|
2780
|
-
});
|
|
2781
|
-
return renderRoot(
|
|
2782
|
-
asChild,
|
|
2783
|
-
asChild && isValidElement3(children) ? children : void 0,
|
|
2784
|
-
{
|
|
2785
|
-
...rootProps,
|
|
2786
|
-
className,
|
|
2787
|
-
"data-keepkit": "list",
|
|
2788
|
-
"data-layout": layout,
|
|
2789
|
-
"aria-busy": state.isLoading || rootProps["aria-busy"],
|
|
2790
|
-
"data-state": getListState(state),
|
|
2791
|
-
"data-loading": state.isLoading ? "true" : void 0,
|
|
2792
|
-
"data-roving-tabindex": "true",
|
|
2793
|
-
role: rootProps.role ?? "group",
|
|
2794
|
-
ref: roving.ref,
|
|
2795
|
-
onKeyDown: (event) => {
|
|
2796
|
-
onKeyDown?.(event);
|
|
2797
|
-
if (!event.defaultPrevented) roving.onKeyDown(event);
|
|
2798
|
-
},
|
|
2799
|
-
onFocusCapture: (event) => {
|
|
2800
|
-
onFocusCapture?.(event);
|
|
2801
|
-
if (!event.defaultPrevented) roving.onFocusCapture(event);
|
|
2802
|
-
}
|
|
2803
|
-
},
|
|
2804
|
-
/* @__PURE__ */ jsx10(KeepSearchQueryProvider, { query: query?.search?.query, children: body }),
|
|
2805
|
-
"KeepList"
|
|
2806
|
-
);
|
|
2807
|
-
}
|
|
2808
|
-
function getListState(state) {
|
|
2809
|
-
if (state.error && state.items.length === 0) return "error";
|
|
2810
|
-
if (state.isLoading && !state.isHydrated) return "loading";
|
|
2811
|
-
if (state.isHydrated && state.items.length === 0) return "empty";
|
|
2812
|
-
return "ready";
|
|
2813
|
-
}
|
|
2814
|
-
function getListBody(state, options) {
|
|
2815
|
-
if (state.error && state.items.length === 0) return resolveContent(options.error, state);
|
|
2816
|
-
if (state.isLoading && !state.isHydrated) {
|
|
2817
|
-
if (options.loading !== void 0) return resolveContent(options.loading, state);
|
|
2818
|
-
const count = Number.isFinite(options.loadingCount) ? Math.max(0, Math.floor(options.loadingCount)) : 6;
|
|
2819
|
-
return /* @__PURE__ */ jsxs6(Fragment5, { children: [
|
|
2820
|
-
/* @__PURE__ */ jsx10("span", { role: "status", "data-keepkit": "loading-label", children: options.loadingLabel }),
|
|
2821
|
-
/* @__PURE__ */ jsx10("ul", { "data-keepkit": "skeleton-list", "data-layout": options.layout, children: Array.from({ length: count }, (_, index) => (
|
|
2822
|
-
// biome-ignore lint/suspicious/noArrayIndexKey: Static loading placeholders never reorder.
|
|
2823
|
-
/* @__PURE__ */ jsx10("li", { children: /* @__PURE__ */ jsx10(KeepItemCardSkeleton, { layout: options.layout }) }, index)
|
|
2824
|
-
)) })
|
|
2825
|
-
] });
|
|
2826
|
-
}
|
|
2827
|
-
if (state.isHydrated && state.items.length === 0) return resolveContent(options.empty, state);
|
|
2828
|
-
if (typeof options.children === "function") return options.children(state);
|
|
2829
|
-
if (options.children !== void 0 && !isValidElement3(options.children)) return options.children;
|
|
2830
|
-
return /* @__PURE__ */ jsx10("ul", { "data-layout": options.layout, children: state.items.map(
|
|
2831
|
-
(item) => options.renderItem ? options.renderItem(item, state) : /* @__PURE__ */ jsx10("li", { children: /* @__PURE__ */ jsx10(KeepItemCard, { item, ...options.itemCardProps }) }, item.id)
|
|
2832
|
-
) });
|
|
2833
|
-
}
|
|
2834
|
-
|
|
2835
|
-
// src/KeepTagFilter.tsx
|
|
2836
|
-
import { isValidElement as isValidElement4 } from "react";
|
|
2837
|
-
|
|
2838
|
-
// src/hooks/useKeepTagFilter.ts
|
|
3032
|
+
// src/features/collection/hooks/useKeepListView.ts
|
|
2839
3033
|
import { useKeepList as useKeepList4 } from "@keepkit/core/react";
|
|
2840
|
-
|
|
2841
|
-
function useKeepTagFilter(options) {
|
|
2842
|
-
const { query, controlledValue, defaultValue, onChange, onValueChange } = options;
|
|
2843
|
-
const [uncontrolledValue, setUncontrolledValue] = useState6(defaultValue);
|
|
2844
|
-
const resolvedValue = controlledValue ?? uncontrolledValue;
|
|
2845
|
-
const list = useKeepList4({
|
|
2846
|
-
...query,
|
|
2847
|
-
tags: resolvedValue ? [...query?.tags ?? [], resolvedValue] : query?.tags
|
|
2848
|
-
});
|
|
2849
|
-
const select = useCallback4(
|
|
2850
|
-
(tag) => {
|
|
2851
|
-
if (controlledValue === void 0) setUncontrolledValue(tag);
|
|
2852
|
-
onChange?.(tag);
|
|
2853
|
-
onValueChange?.(tag);
|
|
2854
|
-
},
|
|
2855
|
-
[controlledValue, onChange, onValueChange]
|
|
2856
|
-
);
|
|
2857
|
-
const state = useMemo3(
|
|
2858
|
-
() => ({ tags: list.tags, tagCounts: list.tagCounts, value: resolvedValue, select }),
|
|
2859
|
-
[list.tagCounts, list.tags, resolvedValue, select]
|
|
2860
|
-
);
|
|
3034
|
+
function useKeepListView(query) {
|
|
2861
3035
|
return {
|
|
2862
|
-
state,
|
|
2863
|
-
|
|
2864
|
-
|
|
3036
|
+
state: useKeepList4(query),
|
|
3037
|
+
labels: {
|
|
3038
|
+
loading: useUiLabel("loadingItems"),
|
|
3039
|
+
empty: useUiLabel("noItems"),
|
|
3040
|
+
error: useUiLabel("errorItems")
|
|
3041
|
+
}
|
|
2865
3042
|
};
|
|
2866
3043
|
}
|
|
2867
3044
|
|
|
2868
|
-
// src/
|
|
2869
|
-
import {
|
|
2870
|
-
function
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
)
|
|
2899
|
-
|
|
2900
|
-
"
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
onClick: () => view.state.select(tag),
|
|
2906
|
-
children: [
|
|
2907
|
-
renderTag ? renderTag(tag, view.state.tagCounts[tag] ?? 0, view.state.value === tag) : tag,
|
|
2908
|
-
/* @__PURE__ */ jsxs7("span", { children: [
|
|
2909
|
-
" (",
|
|
2910
|
-
view.state.tagCounts[tag] ?? 0,
|
|
2911
|
-
")"
|
|
2912
|
-
] })
|
|
2913
|
-
]
|
|
2914
|
-
},
|
|
2915
|
-
tag
|
|
2916
|
-
))
|
|
2917
|
-
] });
|
|
2918
|
-
return renderRoot(
|
|
2919
|
-
asChild,
|
|
2920
|
-
isValidElement4(children) ? children : void 0,
|
|
2921
|
-
{
|
|
2922
|
-
...rootProps,
|
|
2923
|
-
className,
|
|
2924
|
-
"data-keepkit": "tag-filter",
|
|
2925
|
-
"data-state": view.state.value === void 0 ? "all" : "filtered",
|
|
2926
|
-
"data-loading": view.isLoading ? "true" : void 0
|
|
3045
|
+
// src/features/collection/hooks/useRovingTabIndex.ts
|
|
3046
|
+
import { useCallback as useCallback3, useEffect as useEffect6, useRef as useRef4 } from "react";
|
|
3047
|
+
function useRovingTabIndex() {
|
|
3048
|
+
const ref = useRef4(null);
|
|
3049
|
+
const getItems = useCallback3(() => {
|
|
3050
|
+
const root = ref.current;
|
|
3051
|
+
if (!root) return [];
|
|
3052
|
+
return Array.from(root.querySelectorAll('[data-keepkit="card"]')).filter(
|
|
3053
|
+
(item) => item.getAttribute("aria-hidden") !== "true" && item.getAttribute("data-roving-disabled") !== "true"
|
|
3054
|
+
);
|
|
3055
|
+
}, []);
|
|
3056
|
+
const syncTabIndices = useCallback3(() => {
|
|
3057
|
+
const items = getItems();
|
|
3058
|
+
if (items.length === 0) return;
|
|
3059
|
+
const activeElement = document.activeElement;
|
|
3060
|
+
const activeItem = items.find((item) => item === activeElement || item.contains(activeElement));
|
|
3061
|
+
const activeIndex = activeItem ? items.indexOf(activeItem) : 0;
|
|
3062
|
+
items.forEach((item, index) => {
|
|
3063
|
+
item.tabIndex = index === activeIndex ? 0 : -1;
|
|
3064
|
+
});
|
|
3065
|
+
}, [getItems]);
|
|
3066
|
+
useEffect6(() => {
|
|
3067
|
+
const root = ref.current;
|
|
3068
|
+
if (!root) return;
|
|
3069
|
+
syncTabIndices();
|
|
3070
|
+
const observer = new MutationObserver(syncTabIndices);
|
|
3071
|
+
observer.observe(root, { childList: true, subtree: true });
|
|
3072
|
+
return () => observer.disconnect();
|
|
3073
|
+
}, [syncTabIndices]);
|
|
3074
|
+
const onFocusCapture = useCallback3(
|
|
3075
|
+
(event) => {
|
|
3076
|
+
if (!(event.target instanceof HTMLElement)) return;
|
|
3077
|
+
const item = event.target.closest('[data-keepkit="card"]');
|
|
3078
|
+
if (!item || !ref.current?.contains(item)) return;
|
|
3079
|
+
getItems().forEach((candidate) => {
|
|
3080
|
+
candidate.tabIndex = candidate === item ? 0 : -1;
|
|
3081
|
+
});
|
|
2927
3082
|
},
|
|
2928
|
-
|
|
2929
|
-
"KeepTagFilter"
|
|
3083
|
+
[getItems]
|
|
2930
3084
|
);
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
label: useUiLabel("search"),
|
|
2951
|
-
change: (event) => {
|
|
2952
|
-
if (controlledValue === void 0) setUncontrolledValue(event.currentTarget.value);
|
|
2953
|
-
}
|
|
2954
|
-
};
|
|
2955
|
-
}
|
|
2956
|
-
function useKeepSortSelect(options) {
|
|
2957
|
-
const { controlledValue, defaultValue, onValueChange } = options;
|
|
2958
|
-
const [uncontrolledValue, setUncontrolledValue] = useState7(defaultValue);
|
|
2959
|
-
const value = controlledValue ?? uncontrolledValue;
|
|
2960
|
-
return {
|
|
2961
|
-
value,
|
|
2962
|
-
change: (event) => {
|
|
2963
|
-
const nextValue = event.currentTarget.value;
|
|
2964
|
-
if (controlledValue === void 0) setUncontrolledValue(nextValue);
|
|
2965
|
-
const [by, direction] = nextValue.split(":");
|
|
2966
|
-
onValueChange?.(nextValue, { by, direction });
|
|
3085
|
+
const onKeyDown = useCallback3(
|
|
3086
|
+
(event) => {
|
|
3087
|
+
if (event.defaultPrevented) return;
|
|
3088
|
+
const items = getItems();
|
|
3089
|
+
if (!(event.target instanceof HTMLElement)) return;
|
|
3090
|
+
const current = event.target.closest('[data-keepkit="card"]');
|
|
3091
|
+
if (!current || current !== event.target || !ref.current?.contains(current)) return;
|
|
3092
|
+
const currentIndex = items.indexOf(current);
|
|
3093
|
+
if (currentIndex < 0) return;
|
|
3094
|
+
let nextIndex;
|
|
3095
|
+
if (event.key === "Home") nextIndex = 0;
|
|
3096
|
+
if (event.key === "End") nextIndex = items.length - 1;
|
|
3097
|
+
if (event.key === "ArrowRight" || event.key === "ArrowDown")
|
|
3098
|
+
nextIndex = Math.min(currentIndex + 1, items.length - 1);
|
|
3099
|
+
if (event.key === "ArrowLeft" || event.key === "ArrowUp") nextIndex = Math.max(currentIndex - 1, 0);
|
|
3100
|
+
if (nextIndex === void 0) return;
|
|
3101
|
+
event.preventDefault();
|
|
3102
|
+
if (nextIndex === currentIndex) return;
|
|
3103
|
+
items[nextIndex]?.focus();
|
|
2967
3104
|
},
|
|
2968
|
-
|
|
2969
|
-
sort: useUiLabel("sort"),
|
|
2970
|
-
updatedNewest: useUiLabel("updatedNewest"),
|
|
2971
|
-
updatedOldest: useUiLabel("updatedOldest"),
|
|
2972
|
-
savedNewest: useUiLabel("savedNewest"),
|
|
2973
|
-
savedOldest: useUiLabel("savedOldest")
|
|
2974
|
-
}
|
|
2975
|
-
};
|
|
2976
|
-
}
|
|
2977
|
-
function useKeepPagination(options) {
|
|
2978
|
-
const { totalCount, pageSize, page, maxPageButtons, onPageChange } = options;
|
|
2979
|
-
const pageCount = Math.max(1, Math.ceil(totalCount / Math.max(1, pageSize)));
|
|
2980
|
-
const currentPage = Math.min(Math.max(1, page), pageCount);
|
|
2981
|
-
const goToPage = (nextPage) => {
|
|
2982
|
-
const next = Math.min(Math.max(1, nextPage), pageCount);
|
|
2983
|
-
onPageChange?.(next, (next - 1) * pageSize);
|
|
2984
|
-
};
|
|
2985
|
-
return {
|
|
2986
|
-
pageCount,
|
|
2987
|
-
currentPage,
|
|
2988
|
-
goToPage,
|
|
2989
|
-
visiblePages: getVisiblePages(currentPage, pageCount, Math.max(1, maxPageButtons)),
|
|
2990
|
-
labels: {
|
|
2991
|
-
previous: useUiLabel("previousPage"),
|
|
2992
|
-
next: useUiLabel("nextPage"),
|
|
2993
|
-
page: useUiLabel("page"),
|
|
2994
|
-
pagination: useUiLabel("pagination")
|
|
2995
|
-
}
|
|
2996
|
-
};
|
|
2997
|
-
}
|
|
2998
|
-
function getVisiblePages(currentPage, pageCount, maxPageButtons) {
|
|
2999
|
-
if (pageCount <= maxPageButtons) return Array.from({ length: pageCount }, (_, index) => index + 1);
|
|
3000
|
-
const half = Math.floor(maxPageButtons / 2);
|
|
3001
|
-
const start = Math.min(Math.max(1, currentPage - half), pageCount - maxPageButtons + 1);
|
|
3002
|
-
return Array.from({ length: maxPageButtons }, (_, index) => start + index);
|
|
3003
|
-
}
|
|
3004
|
-
|
|
3005
|
-
// src/query-controls.tsx
|
|
3006
|
-
import { Fragment as Fragment6, jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
3007
|
-
function KeepSearchInput({
|
|
3008
|
-
value: controlledValue,
|
|
3009
|
-
defaultValue = "",
|
|
3010
|
-
debounceMs = 300,
|
|
3011
|
-
onValueChange,
|
|
3012
|
-
"aria-label": ariaLabel,
|
|
3013
|
-
placeholder,
|
|
3014
|
-
...props
|
|
3015
|
-
}) {
|
|
3016
|
-
const view = useKeepSearchInput({ controlledValue, defaultValue, debounceMs, onValueChange });
|
|
3017
|
-
return /* @__PURE__ */ jsx12(
|
|
3018
|
-
"input",
|
|
3019
|
-
{
|
|
3020
|
-
...props,
|
|
3021
|
-
"data-keepkit": "search-input",
|
|
3022
|
-
"data-keep-action": "search",
|
|
3023
|
-
type: "search",
|
|
3024
|
-
value: view.value,
|
|
3025
|
-
"data-state": view.value ? "active" : "idle",
|
|
3026
|
-
"data-disabled": props.disabled ? "true" : void 0,
|
|
3027
|
-
"aria-label": ariaLabel ?? view.label,
|
|
3028
|
-
placeholder: placeholder ?? view.label,
|
|
3029
|
-
onChange: view.change
|
|
3030
|
-
}
|
|
3105
|
+
[getItems]
|
|
3031
3106
|
);
|
|
3107
|
+
return { ref, onKeyDown, onFocusCapture };
|
|
3032
3108
|
}
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3109
|
+
|
|
3110
|
+
// src/features/collection/KeepList.tsx
|
|
3111
|
+
import { Fragment as Fragment7, jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
3112
|
+
function KeepList(props) {
|
|
3113
|
+
const { fallback, onBoundaryError, boundaryResetKey, ...listProps } = props;
|
|
3114
|
+
const content = /* @__PURE__ */ jsx13(KeepListContent, { ...listProps });
|
|
3115
|
+
if (fallback === void 0 && onBoundaryError === void 0) return content;
|
|
3116
|
+
return /* @__PURE__ */ jsx13(KeepErrorBoundary, { fallback, onError: onBoundaryError, resetKey: boundaryResetKey, children: content });
|
|
3117
|
+
}
|
|
3118
|
+
function KeepListContent({
|
|
3119
|
+
query,
|
|
3038
3120
|
children,
|
|
3039
|
-
|
|
3121
|
+
renderItem,
|
|
3122
|
+
loading,
|
|
3123
|
+
renderLoading,
|
|
3124
|
+
loadingCount = 6,
|
|
3125
|
+
empty,
|
|
3126
|
+
error: errorContent,
|
|
3127
|
+
itemCardProps,
|
|
3128
|
+
layout = "list",
|
|
3129
|
+
asChild = false,
|
|
3130
|
+
onKeyDown,
|
|
3131
|
+
onFocusCapture,
|
|
3132
|
+
className,
|
|
3133
|
+
...rootProps
|
|
3040
3134
|
}) {
|
|
3041
|
-
const view =
|
|
3042
|
-
const
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3135
|
+
const view = useKeepListView(query);
|
|
3136
|
+
const roving = useRovingTabIndex();
|
|
3137
|
+
const { state } = view;
|
|
3138
|
+
const body = getListBody(state, {
|
|
3139
|
+
children,
|
|
3140
|
+
renderItem,
|
|
3141
|
+
loading: renderLoading !== void 0 ? renderLoading : loading,
|
|
3142
|
+
loadingCount,
|
|
3143
|
+
loadingLabel: view.labels.loading,
|
|
3144
|
+
empty: empty ?? view.labels.empty,
|
|
3145
|
+
error: errorContent ?? view.labels.error,
|
|
3146
|
+
itemCardProps,
|
|
3147
|
+
layout
|
|
3148
|
+
});
|
|
3149
|
+
return renderRoot(
|
|
3150
|
+
asChild,
|
|
3151
|
+
asChild && isValidElement4(children) ? children : void 0,
|
|
3050
3152
|
{
|
|
3051
|
-
...
|
|
3052
|
-
|
|
3053
|
-
"data-
|
|
3054
|
-
|
|
3055
|
-
"
|
|
3056
|
-
"data-
|
|
3057
|
-
"
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
totalCount,
|
|
3065
|
-
pageSize,
|
|
3066
|
-
page = 1,
|
|
3067
|
-
maxPageButtons = 7,
|
|
3068
|
-
onPageChange,
|
|
3069
|
-
render,
|
|
3070
|
-
...props
|
|
3071
|
-
}) {
|
|
3072
|
-
const view = useKeepPagination({ totalCount, pageSize, page, maxPageButtons, onPageChange });
|
|
3073
|
-
const navProps = {
|
|
3074
|
-
...props,
|
|
3075
|
-
"data-keepkit": "pagination",
|
|
3076
|
-
"aria-label": props["aria-label"] ?? view.labels.pagination,
|
|
3077
|
-
"data-state": view.pageCount > 1 ? "active" : "idle"
|
|
3078
|
-
};
|
|
3079
|
-
if (render)
|
|
3080
|
-
return /* @__PURE__ */ jsx12("nav", { ...navProps, children: render({ page: view.currentPage, pageCount: view.pageCount, goToPage: view.goToPage }) });
|
|
3081
|
-
return /* @__PURE__ */ jsxs8("nav", { ...navProps, children: [
|
|
3082
|
-
/* @__PURE__ */ jsx12(
|
|
3083
|
-
"button",
|
|
3084
|
-
{
|
|
3085
|
-
type: "button",
|
|
3086
|
-
"data-keep-action": "previous-page",
|
|
3087
|
-
onClick: () => view.goToPage(view.currentPage - 1),
|
|
3088
|
-
disabled: view.currentPage <= 1,
|
|
3089
|
-
children: view.labels.previous
|
|
3090
|
-
}
|
|
3091
|
-
),
|
|
3092
|
-
view.visiblePages.map((nextPage) => /* @__PURE__ */ jsx12(
|
|
3093
|
-
"button",
|
|
3094
|
-
{
|
|
3095
|
-
type: "button",
|
|
3096
|
-
"data-keep-action": "select-page",
|
|
3097
|
-
"aria-current": nextPage === view.currentPage ? "page" : void 0,
|
|
3098
|
-
"aria-label": `${view.labels.page} ${nextPage}`,
|
|
3099
|
-
onClick: () => view.goToPage(nextPage),
|
|
3100
|
-
children: nextPage
|
|
3153
|
+
...rootProps,
|
|
3154
|
+
className,
|
|
3155
|
+
"data-keepkit": "list",
|
|
3156
|
+
"data-layout": layout,
|
|
3157
|
+
"aria-busy": state.isLoading || rootProps["aria-busy"],
|
|
3158
|
+
"data-state": getListState(state),
|
|
3159
|
+
"data-loading": state.isLoading ? "true" : void 0,
|
|
3160
|
+
"data-roving-tabindex": "true",
|
|
3161
|
+
role: rootProps.role ?? "group",
|
|
3162
|
+
ref: roving.ref,
|
|
3163
|
+
onKeyDown: (event) => {
|
|
3164
|
+
onKeyDown?.(event);
|
|
3165
|
+
if (!event.defaultPrevented) roving.onKeyDown(event);
|
|
3101
3166
|
},
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
"button",
|
|
3106
|
-
{
|
|
3107
|
-
type: "button",
|
|
3108
|
-
"data-keep-action": "next-page",
|
|
3109
|
-
onClick: () => view.goToPage(view.currentPage + 1),
|
|
3110
|
-
disabled: view.currentPage >= view.pageCount,
|
|
3111
|
-
children: view.labels.next
|
|
3167
|
+
onFocusCapture: (event) => {
|
|
3168
|
+
onFocusCapture?.(event);
|
|
3169
|
+
if (!event.defaultPrevented) roving.onFocusCapture(event);
|
|
3112
3170
|
}
|
|
3113
|
-
|
|
3114
|
-
|
|
3171
|
+
},
|
|
3172
|
+
/* @__PURE__ */ jsx13(KeepSearchQueryProvider, { query: query?.search?.query, children: body }),
|
|
3173
|
+
"KeepList"
|
|
3174
|
+
);
|
|
3175
|
+
}
|
|
3176
|
+
function getListState(state) {
|
|
3177
|
+
if (state.error && state.items.length === 0) return "error";
|
|
3178
|
+
if (state.isLoading && !state.isHydrated) return "loading";
|
|
3179
|
+
if (state.isHydrated && state.items.length === 0) return "empty";
|
|
3180
|
+
return "ready";
|
|
3181
|
+
}
|
|
3182
|
+
function getListBody(state, options) {
|
|
3183
|
+
if (state.error && state.items.length === 0) return resolveContent(options.error, state);
|
|
3184
|
+
if (state.isLoading && !state.isHydrated) {
|
|
3185
|
+
if (options.loading !== void 0) return resolveContent(options.loading, state);
|
|
3186
|
+
const count = Number.isFinite(options.loadingCount) ? Math.max(0, Math.floor(options.loadingCount)) : 6;
|
|
3187
|
+
return /* @__PURE__ */ jsxs10(Fragment7, { children: [
|
|
3188
|
+
/* @__PURE__ */ jsx13("span", { role: "status", "data-keepkit": "loading-label", children: options.loadingLabel }),
|
|
3189
|
+
/* @__PURE__ */ jsx13("ul", { "data-keepkit": "skeleton-list", "data-layout": options.layout, children: Array.from({ length: count }, (_, index) => (
|
|
3190
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: Static loading placeholders never reorder.
|
|
3191
|
+
/* @__PURE__ */ jsx13("li", { children: /* @__PURE__ */ jsx13(KeepItemCardSkeleton, { layout: options.layout }) }, index)
|
|
3192
|
+
)) })
|
|
3193
|
+
] });
|
|
3194
|
+
}
|
|
3195
|
+
if (state.isHydrated && state.items.length === 0) return resolveContent(options.empty, state);
|
|
3196
|
+
if (typeof options.children === "function") return options.children(state);
|
|
3197
|
+
if (options.children !== void 0 && !isValidElement4(options.children)) return options.children;
|
|
3198
|
+
return /* @__PURE__ */ jsx13("ul", { "data-layout": options.layout, children: state.items.map(
|
|
3199
|
+
(item) => options.renderItem ? options.renderItem(item, state) : /* @__PURE__ */ jsx13("li", { children: /* @__PURE__ */ jsx13(KeepItemCard, { item, ...options.itemCardProps }) }, item.id)
|
|
3200
|
+
) });
|
|
3115
3201
|
}
|
|
3116
3202
|
|
|
3117
|
-
// src/KeepCollection.tsx
|
|
3118
|
-
import { jsx as
|
|
3203
|
+
// src/features/collection/KeepCollection.tsx
|
|
3204
|
+
import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3119
3205
|
function KeepCollection(props) {
|
|
3120
3206
|
const { fallback, onBoundaryError, boundaryResetKey, ...collectionProps } = props;
|
|
3121
|
-
const content = /* @__PURE__ */
|
|
3207
|
+
const content = /* @__PURE__ */ jsx14(KeepCollectionContent, { ...collectionProps });
|
|
3122
3208
|
if (fallback === void 0 && onBoundaryError === void 0) return content;
|
|
3123
|
-
return /* @__PURE__ */
|
|
3209
|
+
return /* @__PURE__ */ jsx14(KeepErrorBoundary2, { fallback, onError: onBoundaryError, resetKey: boundaryResetKey, children: content });
|
|
3124
3210
|
}
|
|
3125
3211
|
function KeepCollectionContent({
|
|
3126
3212
|
query = {},
|
|
@@ -3140,7 +3226,7 @@ function KeepCollectionContent({
|
|
|
3140
3226
|
...rootProps
|
|
3141
3227
|
}) {
|
|
3142
3228
|
const view = useKeepCollection({ query, pageSize, urlSync, urlAdapter, features });
|
|
3143
|
-
return /* @__PURE__ */
|
|
3229
|
+
return /* @__PURE__ */ jsxs11(
|
|
3144
3230
|
"section",
|
|
3145
3231
|
{
|
|
3146
3232
|
...rootProps,
|
|
@@ -3151,12 +3237,12 @@ function KeepCollectionContent({
|
|
|
3151
3237
|
"data-state": getCollectionState(view.list),
|
|
3152
3238
|
"data-loading": view.list.isLoading || view.list.isMutating ? "true" : void 0,
|
|
3153
3239
|
children: [
|
|
3154
|
-
/* @__PURE__ */
|
|
3155
|
-
view.enabled.search ? /* @__PURE__ */
|
|
3156
|
-
view.enabled.sort ? /* @__PURE__ */
|
|
3157
|
-
view.enabled.tagFilter ? /* @__PURE__ */
|
|
3240
|
+
/* @__PURE__ */ jsxs11("div", { children: [
|
|
3241
|
+
view.enabled.search ? /* @__PURE__ */ jsx14(KeepSearchInput, { value: view.searchValue, onValueChange: view.setSearchValue }) : null,
|
|
3242
|
+
view.enabled.sort ? /* @__PURE__ */ jsx14(KeepSortSelect, { value: view.sortValue, onValueChange: view.setSortValue }) : null,
|
|
3243
|
+
view.enabled.tagFilter ? /* @__PURE__ */ jsx14(KeepTagFilter, { query, value: view.tag, onValueChange: view.setTag }) : null
|
|
3158
3244
|
] }),
|
|
3159
|
-
/* @__PURE__ */
|
|
3245
|
+
/* @__PURE__ */ jsx14(
|
|
3160
3246
|
KeepList,
|
|
3161
3247
|
{
|
|
3162
3248
|
query: view.resolvedQuery,
|
|
@@ -3170,7 +3256,7 @@ function KeepCollectionContent({
|
|
|
3170
3256
|
error
|
|
3171
3257
|
}
|
|
3172
3258
|
),
|
|
3173
|
-
view.enabled.pagination ? /* @__PURE__ */
|
|
3259
|
+
view.enabled.pagination ? /* @__PURE__ */ jsx14(
|
|
3174
3260
|
KeepPagination,
|
|
3175
3261
|
{
|
|
3176
3262
|
totalCount: view.list.totalCount,
|
|
@@ -3179,7 +3265,7 @@ function KeepCollectionContent({
|
|
|
3179
3265
|
onPageChange: view.setPage
|
|
3180
3266
|
}
|
|
3181
3267
|
) : null,
|
|
3182
|
-
view.enabled.bulkActions ? /* @__PURE__ */
|
|
3268
|
+
view.enabled.bulkActions ? /* @__PURE__ */ jsx14(KeepBulkActions, { query: view.resolvedQuery }) : null
|
|
3183
3269
|
]
|
|
3184
3270
|
}
|
|
3185
3271
|
);
|
|
@@ -3190,172 +3276,37 @@ function getCollectionState(list) {
|
|
|
3190
3276
|
if (list.isHydrated && list.items.length === 0) return "empty";
|
|
3191
3277
|
return "ready";
|
|
3192
3278
|
}
|
|
3193
|
-
|
|
3194
|
-
// src/KeepLayout.tsx
|
|
3195
|
-
import { jsx as
|
|
3196
|
-
function KeepLayout({ layout = "list", children, onKeyDown, onFocusCapture, ...props }) {
|
|
3197
|
-
const roving = useRovingTabIndex();
|
|
3198
|
-
return (
|
|
3199
|
-
// biome-ignore lint/a11y/noStaticElementInteractions: The group manages keyboard focus for descendant cards.
|
|
3200
|
-
/* @__PURE__ */
|
|
3201
|
-
"div",
|
|
3202
|
-
{
|
|
3203
|
-
...props,
|
|
3204
|
-
ref: roving.ref,
|
|
3205
|
-
"data-keepkit": "layout",
|
|
3206
|
-
"data-layout": layout,
|
|
3207
|
-
"data-roving-tabindex": "true",
|
|
3208
|
-
role: props.role ?? "group",
|
|
3209
|
-
onKeyDown: (event) => {
|
|
3210
|
-
onKeyDown?.(event);
|
|
3211
|
-
if (!event.defaultPrevented) roving.onKeyDown(event);
|
|
3212
|
-
},
|
|
3213
|
-
onFocusCapture: (event) => {
|
|
3214
|
-
onFocusCapture?.(event);
|
|
3215
|
-
if (!event.defaultPrevented) roving.onFocusCapture(event);
|
|
3216
|
-
},
|
|
3217
|
-
children
|
|
3218
|
-
}
|
|
3219
|
-
)
|
|
3220
|
-
);
|
|
3221
|
-
}
|
|
3222
|
-
|
|
3223
|
-
// src/KeepNoteEditor.tsx
|
|
3224
|
-
import { isValidElement as isValidElement5 } from "react";
|
|
3225
|
-
|
|
3226
|
-
// src/hooks/useKeepNoteEditor.ts
|
|
3227
|
-
import { useKeepItem as useKeepItem3 } from "@keepkit/core/react";
|
|
3228
|
-
import { useCallback as useCallback5, useEffect as useEffect6, useRef as useRef5, useState as useState8 } from "react";
|
|
3229
|
-
function useKeepNoteEditor({ item, debounceMs, onSaved, onSaveError }) {
|
|
3230
|
-
const itemState = useKeepItem3(item);
|
|
3231
|
-
const { error, isMutating, item: savedItem, updateNote } = itemState;
|
|
3232
|
-
const [note, setNote] = useState8(item.note ?? "");
|
|
3233
|
-
const baselineNote = savedItem?.note ?? item.note ?? "";
|
|
3234
|
-
const isDirty = note !== baselineNote;
|
|
3235
|
-
const lastSavedNoteRef = useRef5(void 0);
|
|
3236
|
-
useEffect6(() => setNote(baselineNote), [baselineNote]);
|
|
3237
|
-
const save = useCallback5(async () => {
|
|
3238
|
-
const nextNote = note.trim() || void 0;
|
|
3239
|
-
try {
|
|
3240
|
-
await updateNote(nextNote);
|
|
3241
|
-
lastSavedNoteRef.current = note;
|
|
3242
|
-
onSaved?.(nextNote);
|
|
3243
|
-
} catch (cause) {
|
|
3244
|
-
onSaveError?.(cause);
|
|
3245
|
-
throw cause;
|
|
3246
|
-
}
|
|
3247
|
-
}, [note, onSaveError, onSaved, updateNote]);
|
|
3248
|
-
useEffect6(() => {
|
|
3249
|
-
if (!isDirty || debounceMs <= 0 || lastSavedNoteRef.current === note) return;
|
|
3250
|
-
const timer = window.setTimeout(() => void save().catch(() => void 0), debounceMs);
|
|
3251
|
-
return () => window.clearTimeout(timer);
|
|
3252
|
-
}, [debounceMs, isDirty, note, save]);
|
|
3253
|
-
const state = {
|
|
3254
|
-
item,
|
|
3255
|
-
note,
|
|
3256
|
-
setNote,
|
|
3257
|
-
isDirty,
|
|
3258
|
-
isSaving: isMutating,
|
|
3259
|
-
error,
|
|
3260
|
-
save
|
|
3261
|
-
};
|
|
3262
|
-
const submit = (event) => {
|
|
3263
|
-
event.preventDefault();
|
|
3264
|
-
void save().catch(() => void 0);
|
|
3265
|
-
};
|
|
3266
|
-
return {
|
|
3267
|
-
state,
|
|
3268
|
-
submit,
|
|
3269
|
-
handleKeyDown: (event) => {
|
|
3270
|
-
if (event.key !== "Enter" || !event.ctrlKey && !event.metaKey) return;
|
|
3271
|
-
event.preventDefault();
|
|
3272
|
-
void save().catch(() => void 0);
|
|
3273
|
-
},
|
|
3274
|
-
labels: {
|
|
3275
|
-
note: useUiLabel("note"),
|
|
3276
|
-
save: useUiLabel("saveNote"),
|
|
3277
|
-
error: useUiLabel("error")
|
|
3278
|
-
}
|
|
3279
|
-
};
|
|
3280
|
-
}
|
|
3281
|
-
|
|
3282
|
-
// src/KeepNoteEditor.tsx
|
|
3283
|
-
import { Fragment as Fragment7, jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
3284
|
-
function KeepNoteEditor({
|
|
3285
|
-
item,
|
|
3286
|
-
label,
|
|
3287
|
-
saveLabel,
|
|
3288
|
-
placeholder,
|
|
3289
|
-
debounceMs = 300,
|
|
3290
|
-
onSaved,
|
|
3291
|
-
onSaveError,
|
|
3292
|
-
render,
|
|
3293
|
-
children,
|
|
3294
|
-
asChild = false,
|
|
3295
|
-
className,
|
|
3296
|
-
...formProps
|
|
3297
|
-
}) {
|
|
3298
|
-
const view = useKeepNoteEditor({ item, debounceMs, onSaved, onSaveError });
|
|
3299
|
-
const { error, isDirty, isSaving, note, setNote } = view.state;
|
|
3300
|
-
const contentChildren = asChild && isValidElement5(children) ? void 0 : children;
|
|
3301
|
-
const body = render ? render(view.state) : typeof contentChildren === "function" ? contentChildren(view.state) : contentChildren ?? /* @__PURE__ */ jsxs10(Fragment7, { children: [
|
|
3302
|
-
/* @__PURE__ */ jsxs10("label", { children: [
|
|
3303
|
-
label ?? view.labels.note,
|
|
3304
|
-
/* @__PURE__ */ jsx15(
|
|
3305
|
-
"textarea",
|
|
3306
|
-
{
|
|
3307
|
-
"data-keep-action": "edit-note",
|
|
3308
|
-
value: note,
|
|
3309
|
-
onChange: (event) => setNote(event.currentTarget.value),
|
|
3310
|
-
placeholder,
|
|
3311
|
-
disabled: isSaving,
|
|
3312
|
-
onKeyDown: view.handleKeyDown
|
|
3313
|
-
}
|
|
3314
|
-
)
|
|
3315
|
-
] }),
|
|
3316
|
-
/* @__PURE__ */ jsx15("button", { type: "submit", "data-keep-action": "save-note", disabled: isSaving, "aria-busy": isSaving, children: saveLabel ?? view.labels.save })
|
|
3317
|
-
] });
|
|
3318
|
-
if (!asChild) {
|
|
3319
|
-
return /* @__PURE__ */ jsxs10(
|
|
3320
|
-
"form",
|
|
3279
|
+
|
|
3280
|
+
// src/features/collection/KeepLayout.tsx
|
|
3281
|
+
import { jsx as jsx15 } from "react/jsx-runtime";
|
|
3282
|
+
function KeepLayout({ layout = "list", children, onKeyDown, onFocusCapture, ...props }) {
|
|
3283
|
+
const roving = useRovingTabIndex();
|
|
3284
|
+
return (
|
|
3285
|
+
// biome-ignore lint/a11y/noStaticElementInteractions: The group manages keyboard focus for descendant cards.
|
|
3286
|
+
/* @__PURE__ */ jsx15(
|
|
3287
|
+
"div",
|
|
3321
3288
|
{
|
|
3322
|
-
...
|
|
3323
|
-
|
|
3324
|
-
"data-keepkit": "
|
|
3325
|
-
|
|
3326
|
-
"
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3289
|
+
...props,
|
|
3290
|
+
ref: roving.ref,
|
|
3291
|
+
"data-keepkit": "layout",
|
|
3292
|
+
"data-layout": layout,
|
|
3293
|
+
"data-roving-tabindex": "true",
|
|
3294
|
+
role: props.role ?? "group",
|
|
3295
|
+
onKeyDown: (event) => {
|
|
3296
|
+
onKeyDown?.(event);
|
|
3297
|
+
if (!event.defaultPrevented) roving.onKeyDown(event);
|
|
3298
|
+
},
|
|
3299
|
+
onFocusCapture: (event) => {
|
|
3300
|
+
onFocusCapture?.(event);
|
|
3301
|
+
if (!event.defaultPrevented) roving.onFocusCapture(event);
|
|
3302
|
+
},
|
|
3303
|
+
children
|
|
3334
3304
|
}
|
|
3335
|
-
)
|
|
3336
|
-
}
|
|
3337
|
-
return renderRoot(
|
|
3338
|
-
true,
|
|
3339
|
-
isValidElement5(children) ? children : void 0,
|
|
3340
|
-
{
|
|
3341
|
-
...formProps,
|
|
3342
|
-
className,
|
|
3343
|
-
"data-keepkit": "note-editor",
|
|
3344
|
-
onSubmit: view.submit,
|
|
3345
|
-
"aria-busy": isSaving || formProps["aria-busy"],
|
|
3346
|
-
"data-state": error ? "error" : isDirty ? "dirty" : "clean",
|
|
3347
|
-
"data-loading": isSaving ? "true" : void 0,
|
|
3348
|
-
"data-disabled": isSaving ? "true" : void 0
|
|
3349
|
-
},
|
|
3350
|
-
body,
|
|
3351
|
-
"KeepNoteEditor"
|
|
3305
|
+
)
|
|
3352
3306
|
);
|
|
3353
3307
|
}
|
|
3354
|
-
function getErrorMessage3(error, fallback) {
|
|
3355
|
-
return error instanceof Error ? error.message : fallback;
|
|
3356
|
-
}
|
|
3357
3308
|
|
|
3358
|
-
// src/KeepReorderableList.tsx
|
|
3309
|
+
// src/features/collection/KeepReorderableList.tsx
|
|
3359
3310
|
import { useState as useState9 } from "react";
|
|
3360
3311
|
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
3361
3312
|
function KeepReorderableList({
|
|
@@ -3366,6 +3317,7 @@ function KeepReorderableList({
|
|
|
3366
3317
|
...props
|
|
3367
3318
|
}) {
|
|
3368
3319
|
const [draggedId, setDraggedId] = useState9(null);
|
|
3320
|
+
const [dropTargetIndex, setDropTargetIndex] = useState9(null);
|
|
3369
3321
|
const ids = items.map((item) => item.id);
|
|
3370
3322
|
function commit(nextIds) {
|
|
3371
3323
|
void onReorder(nextIds);
|
|
@@ -3378,6 +3330,18 @@ function KeepReorderableList({
|
|
|
3378
3330
|
next.splice(targetIndex, 0, id);
|
|
3379
3331
|
commit(next);
|
|
3380
3332
|
}
|
|
3333
|
+
function moveToInsertion(index, insertionIndex) {
|
|
3334
|
+
const next = [...ids];
|
|
3335
|
+
const [id] = next.splice(index, 1);
|
|
3336
|
+
if (id === void 0) return;
|
|
3337
|
+
const targetIndex = Math.min(
|
|
3338
|
+
Math.max(0, insertionIndex > index ? insertionIndex - 1 : insertionIndex),
|
|
3339
|
+
next.length
|
|
3340
|
+
);
|
|
3341
|
+
next.splice(targetIndex, 0, id);
|
|
3342
|
+
if (next[index] === id) return;
|
|
3343
|
+
commit(next);
|
|
3344
|
+
}
|
|
3381
3345
|
return /* @__PURE__ */ jsx16("ul", { ...props, "data-keepkit": "reorderable-list", children: items.map((item, index) => {
|
|
3382
3346
|
const moveUp = () => move(index, index - 1);
|
|
3383
3347
|
const moveDown = () => move(index, index + 1);
|
|
@@ -3398,339 +3362,196 @@ function KeepReorderableList({
|
|
|
3398
3362
|
dragHandleProps: {
|
|
3399
3363
|
role: "button",
|
|
3400
3364
|
tabIndex: 0,
|
|
3401
|
-
draggable: true,
|
|
3402
|
-
"
|
|
3403
|
-
"aria-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
return;
|
|
3446
|
-
}
|
|
3447
|
-
if (syncState.status === "synced" && (previous === "pending" || previous === "syncing" || previous === "conflict" || previous === "error")) {
|
|
3448
|
-
emitFeedback({ type: "sync-completed", message: completedMessage });
|
|
3449
|
-
}
|
|
3450
|
-
}, [completedMessage, emitFeedback, failedMessage, syncState.error, syncState.status]);
|
|
3451
|
-
}
|
|
3452
|
-
|
|
3453
|
-
// src/KeepSyncFeedbackObserver.tsx
|
|
3454
|
-
function KeepSyncFeedbackObserver() {
|
|
3455
|
-
useKeepSyncFeedback();
|
|
3456
|
-
return null;
|
|
3457
|
-
}
|
|
3458
|
-
|
|
3459
|
-
// src/hooks/useKeepSyncRecoveryDialog.ts
|
|
3460
|
-
import { useKeepContext as useKeepContext4 } from "@keepkit/core/react";
|
|
3461
|
-
import { useEffect as useEffect8, useState as useState10 } from "react";
|
|
3462
|
-
function useKeepSyncRecoveryDialog(options) {
|
|
3463
|
-
const { open, onOpenChange, conflicts, onManualMerge } = options;
|
|
3464
|
-
const context = useKeepContext4();
|
|
3465
|
-
const conflictList = conflicts ?? context.syncState.conflicts ?? [];
|
|
3466
|
-
const hasRecovery = conflictList.length > 0 || context.syncState.status === "error" || Boolean(context.error);
|
|
3467
|
-
const [dismissed, setDismissed] = useState10(false);
|
|
3468
|
-
const [busyId, setBusyId] = useState10();
|
|
3469
|
-
const [error, setError] = useState10();
|
|
3470
|
-
useEffect8(() => {
|
|
3471
|
-
if (hasRecovery) setDismissed(false);
|
|
3472
|
-
}, [hasRecovery]);
|
|
3473
|
-
return {
|
|
3474
|
-
conflictList,
|
|
3475
|
-
isOpen: open ?? (hasRecovery && !dismissed),
|
|
3476
|
-
busyId,
|
|
3477
|
-
error,
|
|
3478
|
-
showBackupRecovery: context.syncState.status === "error" || Boolean(context.error),
|
|
3479
|
-
close: () => {
|
|
3480
|
-
setDismissed(true);
|
|
3481
|
-
onOpenChange?.(false);
|
|
3482
|
-
},
|
|
3483
|
-
resolve: async (conflict, resolution) => {
|
|
3484
|
-
setError(void 0);
|
|
3485
|
-
setBusyId(conflict.id);
|
|
3486
|
-
try {
|
|
3487
|
-
const merged = resolution === "manual" ? await onManualMerge?.(conflict) : void 0;
|
|
3488
|
-
if (resolution === "manual" && !merged) throw new Error("A manual merge result is required.");
|
|
3489
|
-
await context.resolveSyncConflict(conflict.id, resolution, merged);
|
|
3490
|
-
} catch (cause) {
|
|
3491
|
-
setError(cause);
|
|
3492
|
-
} finally {
|
|
3493
|
-
setBusyId(void 0);
|
|
3494
|
-
}
|
|
3495
|
-
},
|
|
3496
|
-
labels: {
|
|
3497
|
-
close: useUiLabel("close"),
|
|
3498
|
-
title: useUiLabel("resolveSync"),
|
|
3499
|
-
conflict: useUiLabel("syncConflict"),
|
|
3500
|
-
keepLocal: useUiLabel("keepLocal"),
|
|
3501
|
-
useServer: useUiLabel("useServer"),
|
|
3502
|
-
manualMerge: useUiLabel("manualMerge"),
|
|
3503
|
-
localVersion: useUiLabel("localVersion"),
|
|
3504
|
-
remoteVersion: useUiLabel("remoteVersion"),
|
|
3505
|
-
updatedAt: useUiLabel("updatedAt"),
|
|
3506
|
-
note: useUiLabel("note"),
|
|
3507
|
-
backupRecovery: useUiLabel("backupRecovery"),
|
|
3508
|
-
backupRecoveryDescription: useUiLabel("backupRecoveryDescription"),
|
|
3509
|
-
error: useUiLabel("error")
|
|
3510
|
-
}
|
|
3511
|
-
};
|
|
3512
|
-
}
|
|
3513
|
-
|
|
3514
|
-
// src/KeepSyncRecoveryDialog.tsx
|
|
3515
|
-
import { jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3516
|
-
function KeepSyncRecoveryDialog({
|
|
3517
|
-
open,
|
|
3518
|
-
onOpenChange,
|
|
3519
|
-
conflicts,
|
|
3520
|
-
onManualMerge,
|
|
3521
|
-
backup,
|
|
3522
|
-
showBackupControls = true,
|
|
3523
|
-
title,
|
|
3524
|
-
children,
|
|
3525
|
-
className,
|
|
3526
|
-
...props
|
|
3527
|
-
}) {
|
|
3528
|
-
const view = useKeepSyncRecoveryDialog({ open, onOpenChange, conflicts, onManualMerge });
|
|
3529
|
-
if (!view.isOpen) return null;
|
|
3530
|
-
return /* @__PURE__ */ jsxs11(
|
|
3531
|
-
"section",
|
|
3532
|
-
{
|
|
3533
|
-
...props,
|
|
3534
|
-
className,
|
|
3535
|
-
role: "dialog",
|
|
3536
|
-
"aria-modal": "true",
|
|
3537
|
-
"aria-labelledby": "keepkit-sync-recovery-title",
|
|
3538
|
-
"aria-describedby": view.error ? "keepkit-sync-recovery-error" : void 0,
|
|
3539
|
-
"aria-busy": view.busyId !== void 0,
|
|
3540
|
-
"data-keepkit": "sync-recovery",
|
|
3541
|
-
"data-state": view.conflictList.length > 0 ? "conflict" : "error",
|
|
3542
|
-
"data-loading": view.busyId !== void 0 ? "true" : void 0,
|
|
3543
|
-
children: [
|
|
3544
|
-
/* @__PURE__ */ jsxs11("header", { children: [
|
|
3545
|
-
/* @__PURE__ */ jsx17("h2", { id: "keepkit-sync-recovery-title", children: title ?? view.labels.title }),
|
|
3546
|
-
/* @__PURE__ */ jsx17("button", { type: "button", "data-keep-action": "close-dialog", onClick: view.close, "aria-label": view.labels.close, children: view.labels.close })
|
|
3547
|
-
] }),
|
|
3548
|
-
children,
|
|
3549
|
-
view.conflictList.length > 0 ? /* @__PURE__ */ jsxs11("div", { children: [
|
|
3550
|
-
/* @__PURE__ */ jsx17("p", { children: view.labels.conflict }),
|
|
3551
|
-
view.conflictList.map((conflict) => /* @__PURE__ */ jsxs11("article", { "data-conflict-id": conflict.id, children: [
|
|
3552
|
-
/* @__PURE__ */ jsx17("h3", { children: getMetaTitle(conflict.operation.item?.meta) ?? conflict.id }),
|
|
3553
|
-
/* @__PURE__ */ jsxs11("div", { "data-conflict-preview": true, children: [
|
|
3554
|
-
/* @__PURE__ */ jsx17(
|
|
3555
|
-
ConflictPreview,
|
|
3556
|
-
{
|
|
3557
|
-
item: conflict.operation.item,
|
|
3558
|
-
heading: view.labels.localVersion,
|
|
3559
|
-
updatedAtLabel: view.labels.updatedAt,
|
|
3560
|
-
noteLabel: view.labels.note,
|
|
3561
|
-
side: "local"
|
|
3562
|
-
}
|
|
3563
|
-
),
|
|
3564
|
-
/* @__PURE__ */ jsx17(
|
|
3565
|
-
ConflictPreview,
|
|
3566
|
-
{
|
|
3567
|
-
item: conflict.remote,
|
|
3568
|
-
heading: view.labels.remoteVersion,
|
|
3569
|
-
updatedAtLabel: view.labels.updatedAt,
|
|
3570
|
-
noteLabel: view.labels.note,
|
|
3571
|
-
side: "remote"
|
|
3572
|
-
}
|
|
3573
|
-
)
|
|
3574
|
-
] }),
|
|
3575
|
-
/* @__PURE__ */ jsxs11("div", { children: [
|
|
3576
|
-
/* @__PURE__ */ jsx17(
|
|
3577
|
-
"button",
|
|
3578
|
-
{
|
|
3579
|
-
type: "button",
|
|
3580
|
-
"data-keep-action": "keep-local",
|
|
3581
|
-
onClick: () => void view.resolve(conflict, "local"),
|
|
3582
|
-
disabled: view.busyId !== void 0,
|
|
3583
|
-
children: view.labels.keepLocal
|
|
3584
|
-
}
|
|
3585
|
-
),
|
|
3586
|
-
/* @__PURE__ */ jsx17(
|
|
3587
|
-
"button",
|
|
3588
|
-
{
|
|
3589
|
-
type: "button",
|
|
3590
|
-
"data-keep-action": "use-server",
|
|
3591
|
-
onClick: () => void view.resolve(conflict, "remote"),
|
|
3592
|
-
disabled: view.busyId !== void 0,
|
|
3593
|
-
children: view.labels.useServer
|
|
3594
|
-
}
|
|
3595
|
-
),
|
|
3596
|
-
/* @__PURE__ */ jsx17(
|
|
3597
|
-
"button",
|
|
3598
|
-
{
|
|
3599
|
-
type: "button",
|
|
3600
|
-
"data-keep-action": "manual-merge",
|
|
3601
|
-
onClick: () => void view.resolve(conflict, "manual"),
|
|
3602
|
-
disabled: view.busyId !== void 0 || !onManualMerge,
|
|
3603
|
-
children: view.labels.manualMerge
|
|
3604
|
-
}
|
|
3605
|
-
)
|
|
3606
|
-
] })
|
|
3607
|
-
] }, conflict.id))
|
|
3608
|
-
] }) : null,
|
|
3609
|
-
view.showBackupRecovery ? /* @__PURE__ */ jsxs11("section", { "data-recovery": "backup", children: [
|
|
3610
|
-
/* @__PURE__ */ jsx17("h3", { children: view.labels.backupRecovery }),
|
|
3611
|
-
/* @__PURE__ */ jsx17("p", { children: view.labels.backupRecoveryDescription }),
|
|
3612
|
-
backup ?? (showBackupControls ? /* @__PURE__ */ jsx17(KeepBackup, {}) : null)
|
|
3613
|
-
] }) : null,
|
|
3614
|
-
view.error ? /* @__PURE__ */ jsx17("p", { id: "keepkit-sync-recovery-error", role: "alert", "aria-live": "assertive", children: view.error instanceof Error ? view.error.message : view.labels.error }) : null
|
|
3615
|
-
]
|
|
3616
|
-
}
|
|
3617
|
-
);
|
|
3618
|
-
}
|
|
3619
|
-
function ConflictPreview({
|
|
3620
|
-
item,
|
|
3621
|
-
heading,
|
|
3622
|
-
updatedAtLabel,
|
|
3623
|
-
noteLabel,
|
|
3624
|
-
side
|
|
3625
|
-
}) {
|
|
3626
|
-
return /* @__PURE__ */ jsxs11("article", { "data-conflict-version": side, "aria-label": heading, children: [
|
|
3627
|
-
/* @__PURE__ */ jsx17("h4", { children: heading }),
|
|
3628
|
-
/* @__PURE__ */ jsxs11("dl", { children: [
|
|
3629
|
-
/* @__PURE__ */ jsxs11("div", { children: [
|
|
3630
|
-
/* @__PURE__ */ jsx17("dt", { children: updatedAtLabel }),
|
|
3631
|
-
/* @__PURE__ */ jsx17("dd", { children: item ? /* @__PURE__ */ jsx17("time", { dateTime: new Date(item.updatedAt).toISOString(), children: formatConflictDate(item.updatedAt) }) : "\u2014" })
|
|
3632
|
-
] }),
|
|
3633
|
-
/* @__PURE__ */ jsxs11("div", { children: [
|
|
3634
|
-
/* @__PURE__ */ jsx17("dt", { children: noteLabel }),
|
|
3635
|
-
/* @__PURE__ */ jsx17("dd", { children: item?.note || "\u2014" })
|
|
3636
|
-
] })
|
|
3637
|
-
] })
|
|
3638
|
-
] });
|
|
3639
|
-
}
|
|
3640
|
-
function formatConflictDate(timestamp) {
|
|
3641
|
-
return new Date(timestamp).toISOString().slice(0, 10);
|
|
3365
|
+
draggable: true,
|
|
3366
|
+
"data-drag-handle": "true",
|
|
3367
|
+
"aria-label": itemLabel(item, index),
|
|
3368
|
+
"aria-grabbed": draggedId === item.id,
|
|
3369
|
+
onDragStart: () => {
|
|
3370
|
+
setDraggedId(item.id);
|
|
3371
|
+
setDropTargetIndex(index);
|
|
3372
|
+
},
|
|
3373
|
+
onDragEnd: () => {
|
|
3374
|
+
setDraggedId(null);
|
|
3375
|
+
setDropTargetIndex(null);
|
|
3376
|
+
},
|
|
3377
|
+
onKeyDown: onHandleKeyDown
|
|
3378
|
+
}
|
|
3379
|
+
};
|
|
3380
|
+
return /* @__PURE__ */ jsx16(
|
|
3381
|
+
"li",
|
|
3382
|
+
{
|
|
3383
|
+
"data-reorder-index": index,
|
|
3384
|
+
"data-dragging": draggedId === item.id ? "true" : void 0,
|
|
3385
|
+
"data-drop-target": draggedId && draggedId !== item.id ? dropTargetIndex === index ? "before" : dropTargetIndex === index + 1 ? "after" : void 0 : void 0,
|
|
3386
|
+
onDragOver: (event) => {
|
|
3387
|
+
if (!draggedId || draggedId === item.id) return;
|
|
3388
|
+
event.preventDefault();
|
|
3389
|
+
const bounds = event.currentTarget.getBoundingClientRect();
|
|
3390
|
+
const insertionIndex = event.clientY > bounds.top + bounds.height / 2 ? index + 1 : index;
|
|
3391
|
+
setDropTargetIndex(insertionIndex);
|
|
3392
|
+
},
|
|
3393
|
+
onDragLeave: (event) => {
|
|
3394
|
+
if (event.currentTarget === event.target) setDropTargetIndex(null);
|
|
3395
|
+
},
|
|
3396
|
+
onDrop: (event) => {
|
|
3397
|
+
event.preventDefault();
|
|
3398
|
+
if (!draggedId || draggedId === item.id) return;
|
|
3399
|
+
const sourceIndex = ids.indexOf(draggedId);
|
|
3400
|
+
setDraggedId(null);
|
|
3401
|
+
setDropTargetIndex(null);
|
|
3402
|
+
moveToInsertion(sourceIndex, dropTargetIndex ?? index);
|
|
3403
|
+
},
|
|
3404
|
+
children: renderItem(item, state)
|
|
3405
|
+
},
|
|
3406
|
+
item.id
|
|
3407
|
+
);
|
|
3408
|
+
}) });
|
|
3642
3409
|
}
|
|
3643
3410
|
|
|
3644
|
-
// src/
|
|
3645
|
-
import {
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
const
|
|
3652
|
-
const
|
|
3653
|
-
const
|
|
3654
|
-
const
|
|
3655
|
-
const
|
|
3411
|
+
// src/features/editor/KeepNoteEditor.tsx
|
|
3412
|
+
import { isValidElement as isValidElement5 } from "react";
|
|
3413
|
+
|
|
3414
|
+
// src/features/editor/hooks/useKeepNoteEditor.ts
|
|
3415
|
+
import { useKeepItem as useKeepItem3 } from "@keepkit/core/react";
|
|
3416
|
+
import { useCallback as useCallback4, useEffect as useEffect7, useRef as useRef5, useState as useState10 } from "react";
|
|
3417
|
+
function useKeepNoteEditor({ item, debounceMs, onSaved, onSaveError }) {
|
|
3418
|
+
const itemState = useKeepItem3(item);
|
|
3419
|
+
const { error, isMutating, item: savedItem, updateNote } = itemState;
|
|
3420
|
+
const [note, setNote] = useState10(item.note ?? "");
|
|
3421
|
+
const baselineNote = savedItem?.note ?? item.note ?? "";
|
|
3422
|
+
const isDirty = note !== baselineNote;
|
|
3423
|
+
const lastSavedNoteRef = useRef5(void 0);
|
|
3424
|
+
useEffect7(() => setNote(baselineNote), [baselineNote]);
|
|
3425
|
+
const save = useCallback4(async () => {
|
|
3426
|
+
const nextNote = note.trim() || void 0;
|
|
3427
|
+
try {
|
|
3428
|
+
await updateNote(nextNote);
|
|
3429
|
+
lastSavedNoteRef.current = note;
|
|
3430
|
+
onSaved?.(nextNote);
|
|
3431
|
+
} catch (cause) {
|
|
3432
|
+
onSaveError?.(cause);
|
|
3433
|
+
throw cause;
|
|
3434
|
+
}
|
|
3435
|
+
}, [note, onSaveError, onSaved, updateNote]);
|
|
3436
|
+
useEffect7(() => {
|
|
3437
|
+
if (!isDirty || debounceMs <= 0 || lastSavedNoteRef.current === note) return;
|
|
3438
|
+
const timer = window.setTimeout(() => void save().catch(() => void 0), debounceMs);
|
|
3439
|
+
return () => window.clearTimeout(timer);
|
|
3440
|
+
}, [debounceMs, isDirty, note, save]);
|
|
3441
|
+
const state = {
|
|
3442
|
+
item,
|
|
3443
|
+
note,
|
|
3444
|
+
setNote,
|
|
3445
|
+
isDirty,
|
|
3446
|
+
isSaving: isMutating,
|
|
3447
|
+
error,
|
|
3448
|
+
save
|
|
3449
|
+
};
|
|
3450
|
+
const submit = (event) => {
|
|
3451
|
+
event.preventDefault();
|
|
3452
|
+
void save().catch(() => void 0);
|
|
3453
|
+
};
|
|
3656
3454
|
return {
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
return;
|
|
3669
|
-
}
|
|
3670
|
-
await context.flushSync();
|
|
3455
|
+
state,
|
|
3456
|
+
submit,
|
|
3457
|
+
handleKeyDown: (event) => {
|
|
3458
|
+
if (event.key !== "Enter" || !event.ctrlKey && !event.metaKey) return;
|
|
3459
|
+
event.preventDefault();
|
|
3460
|
+
void save().catch(() => void 0);
|
|
3461
|
+
},
|
|
3462
|
+
labels: {
|
|
3463
|
+
note: useUiLabel("note"),
|
|
3464
|
+
save: useUiLabel("saveNote"),
|
|
3465
|
+
error: useUiLabel("error")
|
|
3671
3466
|
}
|
|
3672
3467
|
};
|
|
3673
3468
|
}
|
|
3674
|
-
function getErrorMessage4(error) {
|
|
3675
|
-
return error instanceof Error ? error.message : "Sync failed.";
|
|
3676
|
-
}
|
|
3677
3469
|
|
|
3678
|
-
// src/
|
|
3679
|
-
import { jsx as
|
|
3680
|
-
function
|
|
3681
|
-
|
|
3682
|
-
|
|
3470
|
+
// src/features/editor/KeepNoteEditor.tsx
|
|
3471
|
+
import { Fragment as Fragment8, jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3472
|
+
function KeepNoteEditor({
|
|
3473
|
+
item,
|
|
3474
|
+
label,
|
|
3475
|
+
saveLabel,
|
|
3476
|
+
placeholder,
|
|
3477
|
+
debounceMs = 300,
|
|
3478
|
+
onSaved,
|
|
3479
|
+
onSaveError,
|
|
3480
|
+
render,
|
|
3683
3481
|
children,
|
|
3482
|
+
asChild = false,
|
|
3684
3483
|
className,
|
|
3685
|
-
...
|
|
3484
|
+
...formProps
|
|
3686
3485
|
}) {
|
|
3687
|
-
const view =
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3486
|
+
const view = useKeepNoteEditor({ item, debounceMs, onSaved, onSaveError });
|
|
3487
|
+
const { error, isDirty, isSaving, note, setNote } = view.state;
|
|
3488
|
+
const contentChildren = asChild && isValidElement5(children) ? void 0 : children;
|
|
3489
|
+
const body = render ? render(view.state) : typeof contentChildren === "function" ? contentChildren(view.state) : contentChildren ?? /* @__PURE__ */ jsxs12(Fragment8, { children: [
|
|
3490
|
+
/* @__PURE__ */ jsxs12("label", { children: [
|
|
3491
|
+
label ?? view.labels.note,
|
|
3492
|
+
/* @__PURE__ */ jsx17(
|
|
3493
|
+
"textarea",
|
|
3494
|
+
{
|
|
3495
|
+
"data-keep-action": "edit-note",
|
|
3496
|
+
value: note,
|
|
3497
|
+
onChange: (event) => setNote(event.currentTarget.value),
|
|
3498
|
+
placeholder,
|
|
3499
|
+
disabled: isSaving,
|
|
3500
|
+
onKeyDown: view.handleKeyDown
|
|
3501
|
+
}
|
|
3502
|
+
)
|
|
3503
|
+
] }),
|
|
3504
|
+
/* @__PURE__ */ jsx17("button", { type: "submit", "data-keep-action": "save-note", disabled: isSaving, "aria-busy": isSaving, children: saveLabel ?? view.labels.save })
|
|
3505
|
+
] });
|
|
3506
|
+
if (!asChild) {
|
|
3507
|
+
return /* @__PURE__ */ jsxs12(
|
|
3508
|
+
"form",
|
|
3509
|
+
{
|
|
3510
|
+
...formProps,
|
|
3511
|
+
className,
|
|
3512
|
+
"data-keepkit": "note-editor",
|
|
3513
|
+
onSubmit: view.submit,
|
|
3514
|
+
"aria-busy": isSaving || formProps["aria-busy"],
|
|
3515
|
+
"data-state": error ? "error" : isDirty ? "dirty" : "clean",
|
|
3516
|
+
"data-loading": isSaving ? "true" : void 0,
|
|
3517
|
+
"data-disabled": isSaving ? "true" : void 0,
|
|
3518
|
+
children: [
|
|
3519
|
+
body,
|
|
3520
|
+
error ? /* @__PURE__ */ jsx17("p", { role: "alert", children: getErrorMessage3(error, view.labels.error) }) : null
|
|
3521
|
+
]
|
|
3522
|
+
}
|
|
3523
|
+
);
|
|
3524
|
+
}
|
|
3525
|
+
return renderRoot(
|
|
3526
|
+
true,
|
|
3527
|
+
isValidElement5(children) ? children : void 0,
|
|
3691
3528
|
{
|
|
3692
|
-
...
|
|
3529
|
+
...formProps,
|
|
3693
3530
|
className,
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
"
|
|
3697
|
-
"data-state":
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
type: "button",
|
|
3704
|
-
"data-keep-action": "retry-sync",
|
|
3705
|
-
onClick: () => void view.retry(),
|
|
3706
|
-
disabled: view.isMutating,
|
|
3707
|
-
children: view.retryLabel
|
|
3708
|
-
}
|
|
3709
|
-
) : null,
|
|
3710
|
-
view.hasConflicts ? /* @__PURE__ */ jsx18(
|
|
3711
|
-
"button",
|
|
3712
|
-
{
|
|
3713
|
-
type: "button",
|
|
3714
|
-
"data-keep-action": "resolve-conflicts",
|
|
3715
|
-
onClick: onResolveConflicts,
|
|
3716
|
-
disabled: !onResolveConflicts,
|
|
3717
|
-
children: view.resolveLabel
|
|
3718
|
-
}
|
|
3719
|
-
) : null
|
|
3720
|
-
]
|
|
3721
|
-
}
|
|
3531
|
+
"data-keepkit": "note-editor",
|
|
3532
|
+
onSubmit: view.submit,
|
|
3533
|
+
"aria-busy": isSaving || formProps["aria-busy"],
|
|
3534
|
+
"data-state": error ? "error" : isDirty ? "dirty" : "clean",
|
|
3535
|
+
"data-loading": isSaving ? "true" : void 0,
|
|
3536
|
+
"data-disabled": isSaving ? "true" : void 0
|
|
3537
|
+
},
|
|
3538
|
+
body,
|
|
3539
|
+
"KeepNoteEditor"
|
|
3722
3540
|
);
|
|
3723
3541
|
}
|
|
3542
|
+
function getErrorMessage3(error, fallback) {
|
|
3543
|
+
return error instanceof Error ? error.message : fallback;
|
|
3544
|
+
}
|
|
3724
3545
|
|
|
3725
|
-
// src/hooks/useKeepTagEditor.ts
|
|
3546
|
+
// src/features/editor/hooks/useKeepTagEditor.ts
|
|
3726
3547
|
import { useKeepItem as useKeepItem4 } from "@keepkit/core/react";
|
|
3727
|
-
import { useCallback as
|
|
3548
|
+
import { useCallback as useCallback5, useEffect as useEffect8, useState as useState11 } from "react";
|
|
3728
3549
|
function useKeepTagEditor({ item, onSaved, onSaveError }) {
|
|
3729
3550
|
const itemState = useKeepItem4(item);
|
|
3730
3551
|
const [tags, setTags] = useState11(item.tags ?? []);
|
|
3731
3552
|
const [input, setInput] = useState11("");
|
|
3732
|
-
|
|
3733
|
-
const save =
|
|
3553
|
+
useEffect8(() => setTags(itemState.item?.tags ?? item.tags ?? []), [item.tags, itemState.item?.tags]);
|
|
3554
|
+
const save = useCallback5(async () => {
|
|
3734
3555
|
const nextTags = normalizeUiTags(tags);
|
|
3735
3556
|
try {
|
|
3736
3557
|
await itemState.updateTags(nextTags);
|
|
@@ -3775,8 +3596,8 @@ function useKeepTagEditor({ item, onSaved, onSaveError }) {
|
|
|
3775
3596
|
};
|
|
3776
3597
|
}
|
|
3777
3598
|
|
|
3778
|
-
// src/KeepTagEditor.tsx
|
|
3779
|
-
import { Fragment as
|
|
3599
|
+
// src/features/editor/KeepTagEditor.tsx
|
|
3600
|
+
import { Fragment as Fragment9, jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3780
3601
|
function KeepTagEditor({
|
|
3781
3602
|
item,
|
|
3782
3603
|
availableTags = [],
|
|
@@ -3787,10 +3608,10 @@ function KeepTagEditor({
|
|
|
3787
3608
|
}) {
|
|
3788
3609
|
const view = useKeepTagEditor({ item, onSaved, onSaveError });
|
|
3789
3610
|
const { isSaving, tags } = view.state;
|
|
3790
|
-
const body = render ? render(view.state) : /* @__PURE__ */ jsxs13(
|
|
3611
|
+
const body = render ? render(view.state) : /* @__PURE__ */ jsxs13(Fragment9, { children: [
|
|
3791
3612
|
/* @__PURE__ */ jsxs13("label", { children: [
|
|
3792
3613
|
view.labels.tags,
|
|
3793
|
-
/* @__PURE__ */
|
|
3614
|
+
/* @__PURE__ */ jsx18(
|
|
3794
3615
|
"input",
|
|
3795
3616
|
{
|
|
3796
3617
|
"data-keep-action": "edit-tags",
|
|
@@ -3801,12 +3622,12 @@ function KeepTagEditor({
|
|
|
3801
3622
|
}
|
|
3802
3623
|
)
|
|
3803
3624
|
] }),
|
|
3804
|
-
availableTags.length > 0 ? /* @__PURE__ */
|
|
3805
|
-
/* @__PURE__ */
|
|
3625
|
+
availableTags.length > 0 ? /* @__PURE__ */ jsx18("datalist", { id: `keep-tags-${item.id}`, children: availableTags.map((tag) => /* @__PURE__ */ jsx18("option", { value: tag }, tag)) }) : null,
|
|
3626
|
+
/* @__PURE__ */ jsx18("ul", { "aria-label": view.labels.tags, children: tags.map((tag) => /* @__PURE__ */ jsxs13("li", { children: [
|
|
3806
3627
|
tag,
|
|
3807
|
-
/* @__PURE__ */
|
|
3628
|
+
/* @__PURE__ */ jsx18("button", { type: "button", "data-keep-action": "remove-tag", onClick: () => view.removeTag(tag), children: view.labels.remove })
|
|
3808
3629
|
] }, tag)) }),
|
|
3809
|
-
/* @__PURE__ */
|
|
3630
|
+
/* @__PURE__ */ jsx18("button", { type: "submit", "data-keep-action": "apply-tags", disabled: isSaving, "aria-busy": isSaving, children: view.labels.apply })
|
|
3810
3631
|
] });
|
|
3811
3632
|
return /* @__PURE__ */ jsxs13(
|
|
3812
3633
|
"form",
|
|
@@ -3820,20 +3641,41 @@ function KeepTagEditor({
|
|
|
3820
3641
|
"data-disabled": isSaving ? "true" : void 0,
|
|
3821
3642
|
children: [
|
|
3822
3643
|
body,
|
|
3823
|
-
view.error ? /* @__PURE__ */
|
|
3644
|
+
view.error ? /* @__PURE__ */ jsx18("p", { role: "alert", children: getErrorMessage4(view.error, view.labels.error) }) : null
|
|
3824
3645
|
]
|
|
3825
3646
|
}
|
|
3826
3647
|
);
|
|
3827
3648
|
}
|
|
3828
|
-
function
|
|
3649
|
+
function getErrorMessage4(error, fallback) {
|
|
3829
3650
|
return error instanceof Error ? error.message : fallback;
|
|
3830
3651
|
}
|
|
3831
3652
|
|
|
3832
|
-
// src/
|
|
3653
|
+
// src/features/feedback/useKeepToastFeedback.ts
|
|
3654
|
+
import { useCallback as useCallback6 } from "react";
|
|
3655
|
+
function useKeepToastFeedback(showToast) {
|
|
3656
|
+
return useCallback6(
|
|
3657
|
+
(event) => {
|
|
3658
|
+
if (!("undo" in event)) {
|
|
3659
|
+
showToast(event.message);
|
|
3660
|
+
return;
|
|
3661
|
+
}
|
|
3662
|
+
showToast(event.message, {
|
|
3663
|
+
action: {
|
|
3664
|
+
label: event.undoLabel,
|
|
3665
|
+
onClick: () => void event.undo()
|
|
3666
|
+
}
|
|
3667
|
+
});
|
|
3668
|
+
},
|
|
3669
|
+
[showToast]
|
|
3670
|
+
);
|
|
3671
|
+
}
|
|
3672
|
+
|
|
3673
|
+
// src/features/navigation/KeepTourBar.tsx
|
|
3833
3674
|
import { useKeepNavigator } from "@keepkit/core/react";
|
|
3675
|
+
import { useId } from "react";
|
|
3834
3676
|
|
|
3835
|
-
// src/hooks/useKeepTourShortcuts.ts
|
|
3836
|
-
import { useEffect as
|
|
3677
|
+
// src/features/navigation/hooks/useKeepTourShortcuts.ts
|
|
3678
|
+
import { useEffect as useEffect9 } from "react";
|
|
3837
3679
|
function useKeepTourShortcuts({
|
|
3838
3680
|
onNext,
|
|
3839
3681
|
onPrev,
|
|
@@ -3844,7 +3686,7 @@ function useKeepTourShortcuts({
|
|
|
3844
3686
|
prevKeys = ["k", "["],
|
|
3845
3687
|
onError
|
|
3846
3688
|
}) {
|
|
3847
|
-
|
|
3689
|
+
useEffect9(() => {
|
|
3848
3690
|
if (!enabled) return;
|
|
3849
3691
|
const handleKeyDown = (event) => {
|
|
3850
3692
|
if (!allowInEditable && isEditableTarget(event.target)) return;
|
|
@@ -3864,8 +3706,8 @@ function isEditableTarget(target) {
|
|
|
3864
3706
|
return target.isContentEditable || target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT";
|
|
3865
3707
|
}
|
|
3866
3708
|
|
|
3867
|
-
// src/KeepTourBar.tsx
|
|
3868
|
-
import { jsx as
|
|
3709
|
+
// src/features/navigation/KeepTourBar.tsx
|
|
3710
|
+
import { Fragment as Fragment10, jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
3869
3711
|
function KeepTourBar({
|
|
3870
3712
|
navigation: providedNavigation,
|
|
3871
3713
|
currentId,
|
|
@@ -3883,6 +3725,7 @@ function KeepTourBar({
|
|
|
3883
3725
|
keyboardShortcuts = false,
|
|
3884
3726
|
shortcutOptions,
|
|
3885
3727
|
progress,
|
|
3728
|
+
getItemTitle = (item) => getMetaTitle(item.meta) ?? item.id,
|
|
3886
3729
|
...props
|
|
3887
3730
|
}) {
|
|
3888
3731
|
const ownNavigation = useKeepNavigator({ currentId, initialIndex });
|
|
@@ -3904,84 +3747,89 @@ function KeepTourBar({
|
|
|
3904
3747
|
}
|
|
3905
3748
|
});
|
|
3906
3749
|
return /* @__PURE__ */ jsxs14("nav", { ...props, "data-keepkit": "tour-bar", "aria-label": props["aria-label"] ?? labels.pagination, children: [
|
|
3907
|
-
showProgress ? /* @__PURE__ */
|
|
3908
|
-
/* @__PURE__ */
|
|
3750
|
+
showProgress ? /* @__PURE__ */ jsx19("span", { "data-keepkit": "tour-progress", "aria-live": "polite", children: progress ?? `${navigation.currentPosition ?? 0} / ${navigation.items.length}` }) : null,
|
|
3751
|
+
/* @__PURE__ */ jsx19(
|
|
3909
3752
|
TourAction,
|
|
3910
3753
|
{
|
|
3911
3754
|
href: prevHref,
|
|
3912
3755
|
disabled: !navigation.hasPrev,
|
|
3913
3756
|
onClick: prevHref ? onPrev : resolvedPrev,
|
|
3914
3757
|
"data-keep-action": "tour-prev",
|
|
3758
|
+
preview: navigation.prevItem ? /* @__PURE__ */ jsxs14(Fragment10, { children: [
|
|
3759
|
+
previousLabel,
|
|
3760
|
+
": ",
|
|
3761
|
+
getItemTitle(navigation.prevItem)
|
|
3762
|
+
] }) : void 0,
|
|
3915
3763
|
children: previousLabel
|
|
3916
3764
|
}
|
|
3917
3765
|
),
|
|
3918
|
-
/* @__PURE__ */
|
|
3766
|
+
/* @__PURE__ */ jsx19(
|
|
3919
3767
|
TourAction,
|
|
3920
3768
|
{
|
|
3921
3769
|
href: nextHref,
|
|
3922
3770
|
disabled: !navigation.hasNext,
|
|
3923
3771
|
onClick: nextHref ? onNext : resolvedNext,
|
|
3924
3772
|
"data-keep-action": "tour-next",
|
|
3773
|
+
preview: navigation.nextItem ? /* @__PURE__ */ jsxs14(Fragment10, { children: [
|
|
3774
|
+
nextItemLabel,
|
|
3775
|
+
": ",
|
|
3776
|
+
getItemTitle(navigation.nextItem)
|
|
3777
|
+
] }) : void 0,
|
|
3925
3778
|
children: nextItemLabel
|
|
3926
3779
|
}
|
|
3927
3780
|
),
|
|
3928
|
-
backHref || onBack ? /* @__PURE__ */
|
|
3781
|
+
backHref || onBack ? /* @__PURE__ */ jsx19(TourAction, { href: backHref, onClick: onBack, "data-keep-action": "tour-back", children: listLabel }) : null
|
|
3929
3782
|
] });
|
|
3930
3783
|
}
|
|
3931
|
-
function TourAction({ href, disabled = false, onClick, children, ...props }) {
|
|
3784
|
+
function TourAction({ href, disabled = false, onClick, children, preview, ...props }) {
|
|
3785
|
+
const previewId = useId();
|
|
3786
|
+
const content = /* @__PURE__ */ jsxs14(Fragment10, { children: [
|
|
3787
|
+
/* @__PURE__ */ jsx19("span", { "data-tour-label": "true", children }),
|
|
3788
|
+
preview ? /* @__PURE__ */ jsx19("small", { id: previewId, "data-tour-preview": "true", children: preview }) : null
|
|
3789
|
+
] });
|
|
3932
3790
|
if (href && !disabled) {
|
|
3933
|
-
return /* @__PURE__ */
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
// src/hooks/useKeepUndo.ts
|
|
3944
|
-
import { useKeepContext as useKeepContext6 } from "@keepkit/core/react";
|
|
3945
|
-
function useKeepUndo() {
|
|
3946
|
-
const context = useKeepContext6();
|
|
3947
|
-
const emitFeedback = useKeepUiFeedback();
|
|
3948
|
-
const restoredMessage = useUiLabel("restoredMessage");
|
|
3949
|
-
return {
|
|
3950
|
-
canUndo: context.undo.canUndo,
|
|
3951
|
-
undo: async () => {
|
|
3952
|
-
const items = context.lastChange?.items ?? (context.lastChange?.item ? [context.lastChange.item] : []);
|
|
3953
|
-
await context.undoLastRemoval();
|
|
3954
|
-
if (items.length > 0) {
|
|
3955
|
-
emitFeedback({ type: "item-restored", item: items[0], items, message: restoredMessage });
|
|
3791
|
+
return /* @__PURE__ */ jsx19(
|
|
3792
|
+
"a",
|
|
3793
|
+
{
|
|
3794
|
+
...props,
|
|
3795
|
+
href,
|
|
3796
|
+
onClick: () => void onClick?.(),
|
|
3797
|
+
"aria-label": String(children),
|
|
3798
|
+
"aria-describedby": preview ? previewId : void 0,
|
|
3799
|
+
children: content
|
|
3956
3800
|
}
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3801
|
+
);
|
|
3802
|
+
}
|
|
3803
|
+
return /* @__PURE__ */ jsx19(
|
|
3804
|
+
"button",
|
|
3805
|
+
{
|
|
3806
|
+
...props,
|
|
3807
|
+
type: "button",
|
|
3808
|
+
disabled,
|
|
3809
|
+
onClick: () => void onClick?.(),
|
|
3810
|
+
"aria-label": String(children),
|
|
3811
|
+
"aria-describedby": preview ? previewId : void 0,
|
|
3812
|
+
children: content
|
|
3813
|
+
}
|
|
3814
|
+
);
|
|
3815
|
+
}
|
|
3816
|
+
var KeepNavigator = KeepTourBar;
|
|
3817
|
+
function navigateTo(item, href) {
|
|
3818
|
+
if (!item || !href || typeof window === "undefined") return;
|
|
3819
|
+
window.location.assign(href);
|
|
3972
3820
|
}
|
|
3973
3821
|
|
|
3974
|
-
// src/status.tsx
|
|
3822
|
+
// src/features/status/status.tsx
|
|
3975
3823
|
import { isValidElement as isValidElement6 } from "react";
|
|
3976
3824
|
|
|
3977
|
-
// src/hooks/useStatusViews.ts
|
|
3978
|
-
import { useKeepContext as
|
|
3979
|
-
import { useEffect as
|
|
3825
|
+
// src/features/status/hooks/useStatusViews.ts
|
|
3826
|
+
import { useKeepContext as useKeepContext4 } from "@keepkit/core/react";
|
|
3827
|
+
import { useEffect as useEffect10, useRef as useRef6, useState as useState12 } from "react";
|
|
3980
3828
|
function useKeepEmptyState() {
|
|
3981
3829
|
return useUiLabel("noItems").replace(/\.$/, "");
|
|
3982
3830
|
}
|
|
3983
3831
|
function useKeepStatus(status) {
|
|
3984
|
-
const context =
|
|
3832
|
+
const context = useKeepContext4();
|
|
3985
3833
|
const resolvedStatus = status ?? getDerivedStatus(context);
|
|
3986
3834
|
const state = {
|
|
3987
3835
|
status: resolvedStatus,
|
|
@@ -3992,13 +3840,13 @@ function useKeepStatus(status) {
|
|
|
3992
3840
|
return { state, defaultLabel: useUiLabel(getStatusLabelKey3(resolvedStatus)) };
|
|
3993
3841
|
}
|
|
3994
3842
|
function useKeepAnnouncements(messages) {
|
|
3995
|
-
const context =
|
|
3843
|
+
const context = useKeepContext4();
|
|
3996
3844
|
const savedMessage = useUiLabel("savedMessage", messages?.save);
|
|
3997
3845
|
const removedMessage = useUiLabel("removedMessage", messages?.remove);
|
|
3998
3846
|
const noteSavedMessage = useUiLabel("noteSavedMessage", messages?.note);
|
|
3999
3847
|
const [message, setMessage] = useState12("");
|
|
4000
|
-
const lastChangeRef =
|
|
4001
|
-
|
|
3848
|
+
const lastChangeRef = useRef6(void 0);
|
|
3849
|
+
useEffect10(() => {
|
|
4002
3850
|
const change = context.lastChange;
|
|
4003
3851
|
if (!change || change === lastChangeRef.current) return;
|
|
4004
3852
|
lastChangeRef.current = change;
|
|
@@ -4025,8 +3873,8 @@ function getStatusLabelKey3(status) {
|
|
|
4025
3873
|
return "saved";
|
|
4026
3874
|
}
|
|
4027
3875
|
|
|
4028
|
-
// src/status.tsx
|
|
4029
|
-
import { Fragment as
|
|
3876
|
+
// src/features/status/status.tsx
|
|
3877
|
+
import { Fragment as Fragment11, jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
4030
3878
|
function KeepEmptyState({
|
|
4031
3879
|
title,
|
|
4032
3880
|
description,
|
|
@@ -4038,9 +3886,9 @@ function KeepEmptyState({
|
|
|
4038
3886
|
}) {
|
|
4039
3887
|
const defaultTitle = useKeepEmptyState();
|
|
4040
3888
|
const contentChildren = asChild && isValidElement6(children) ? void 0 : children;
|
|
4041
|
-
const body = contentChildren ?? /* @__PURE__ */
|
|
4042
|
-
/* @__PURE__ */
|
|
4043
|
-
description ? /* @__PURE__ */
|
|
3889
|
+
const body = contentChildren ?? /* @__PURE__ */ jsxs15(Fragment11, { children: [
|
|
3890
|
+
/* @__PURE__ */ jsx20("h2", { children: title ?? defaultTitle }),
|
|
3891
|
+
description ? /* @__PURE__ */ jsx20("p", { children: description }) : null,
|
|
4044
3892
|
action
|
|
4045
3893
|
] });
|
|
4046
3894
|
return renderRoot(
|
|
@@ -4082,7 +3930,7 @@ function KeepStatus({
|
|
|
4082
3930
|
}
|
|
4083
3931
|
function KeepAnnouncements({ messages, ...props }) {
|
|
4084
3932
|
const message = useKeepAnnouncements(messages);
|
|
4085
|
-
return /* @__PURE__ */
|
|
3933
|
+
return /* @__PURE__ */ jsx20(
|
|
4086
3934
|
"div",
|
|
4087
3935
|
{
|
|
4088
3936
|
...props,
|
|
@@ -4097,7 +3945,301 @@ function KeepAnnouncements({ messages, ...props }) {
|
|
|
4097
3945
|
}
|
|
4098
3946
|
var KeepAnnouncer = KeepAnnouncements;
|
|
4099
3947
|
|
|
4100
|
-
// src/
|
|
3948
|
+
// src/features/sync/hooks/useKeepSyncFeedback.ts
|
|
3949
|
+
import { useKeepContext as useKeepContext5 } from "@keepkit/core/react";
|
|
3950
|
+
import { useEffect as useEffect11, useRef as useRef7 } from "react";
|
|
3951
|
+
function useKeepSyncFeedback() {
|
|
3952
|
+
const { syncState } = useKeepContext5();
|
|
3953
|
+
const emitFeedback = useKeepUiFeedback();
|
|
3954
|
+
const completedMessage = useUiLabel("syncSynced");
|
|
3955
|
+
const failedMessage = useUiLabel("syncFailedMessage");
|
|
3956
|
+
const previousStatus = useRef7("idle");
|
|
3957
|
+
useEffect11(() => {
|
|
3958
|
+
const previous = previousStatus.current;
|
|
3959
|
+
previousStatus.current = syncState.status;
|
|
3960
|
+
if (syncState.status === "error" && previous !== "error") {
|
|
3961
|
+
emitFeedback({ type: "sync-failed", error: syncState.error, message: failedMessage });
|
|
3962
|
+
return;
|
|
3963
|
+
}
|
|
3964
|
+
if (syncState.status === "synced" && (previous === "pending" || previous === "syncing" || previous === "conflict" || previous === "error")) {
|
|
3965
|
+
emitFeedback({ type: "sync-completed", message: completedMessage });
|
|
3966
|
+
}
|
|
3967
|
+
}, [completedMessage, emitFeedback, failedMessage, syncState.error, syncState.status]);
|
|
3968
|
+
}
|
|
3969
|
+
|
|
3970
|
+
// src/features/sync/KeepSyncFeedbackObserver.tsx
|
|
3971
|
+
function KeepSyncFeedbackObserver() {
|
|
3972
|
+
useKeepSyncFeedback();
|
|
3973
|
+
return null;
|
|
3974
|
+
}
|
|
3975
|
+
|
|
3976
|
+
// src/features/sync/hooks/useKeepSyncRecoveryDialog.ts
|
|
3977
|
+
import { useKeepContext as useKeepContext6 } from "@keepkit/core/react";
|
|
3978
|
+
import { useEffect as useEffect12, useState as useState13 } from "react";
|
|
3979
|
+
function useKeepSyncRecoveryDialog(options) {
|
|
3980
|
+
const { open, onOpenChange, conflicts, onManualMerge } = options;
|
|
3981
|
+
const context = useKeepContext6();
|
|
3982
|
+
const conflictList = conflicts ?? context.syncState.conflicts ?? [];
|
|
3983
|
+
const hasRecovery = conflictList.length > 0 || context.syncState.status === "error" || Boolean(context.error);
|
|
3984
|
+
const [dismissed, setDismissed] = useState13(false);
|
|
3985
|
+
const [busyId, setBusyId] = useState13();
|
|
3986
|
+
const [error, setError] = useState13();
|
|
3987
|
+
useEffect12(() => {
|
|
3988
|
+
if (hasRecovery) setDismissed(false);
|
|
3989
|
+
}, [hasRecovery]);
|
|
3990
|
+
return {
|
|
3991
|
+
conflictList,
|
|
3992
|
+
isOpen: open ?? (hasRecovery && !dismissed),
|
|
3993
|
+
busyId,
|
|
3994
|
+
error,
|
|
3995
|
+
showBackupRecovery: context.syncState.status === "error" || Boolean(context.error),
|
|
3996
|
+
close: () => {
|
|
3997
|
+
setDismissed(true);
|
|
3998
|
+
onOpenChange?.(false);
|
|
3999
|
+
},
|
|
4000
|
+
resolve: async (conflict, resolution) => {
|
|
4001
|
+
setError(void 0);
|
|
4002
|
+
setBusyId(conflict.id);
|
|
4003
|
+
try {
|
|
4004
|
+
const merged = resolution === "manual" ? await onManualMerge?.(conflict) : void 0;
|
|
4005
|
+
if (resolution === "manual" && !merged) throw new Error("A manual merge result is required.");
|
|
4006
|
+
await context.resolveSyncConflict(conflict.id, resolution, merged);
|
|
4007
|
+
} catch (cause) {
|
|
4008
|
+
setError(cause);
|
|
4009
|
+
} finally {
|
|
4010
|
+
setBusyId(void 0);
|
|
4011
|
+
}
|
|
4012
|
+
},
|
|
4013
|
+
labels: {
|
|
4014
|
+
close: useUiLabel("close"),
|
|
4015
|
+
title: useUiLabel("resolveSync"),
|
|
4016
|
+
conflict: useUiLabel("syncConflict"),
|
|
4017
|
+
keepLocal: useUiLabel("keepLocal"),
|
|
4018
|
+
useServer: useUiLabel("useServer"),
|
|
4019
|
+
manualMerge: useUiLabel("manualMerge"),
|
|
4020
|
+
localVersion: useUiLabel("localVersion"),
|
|
4021
|
+
remoteVersion: useUiLabel("remoteVersion"),
|
|
4022
|
+
updatedAt: useUiLabel("updatedAt"),
|
|
4023
|
+
note: useUiLabel("note"),
|
|
4024
|
+
backupRecovery: useUiLabel("backupRecovery"),
|
|
4025
|
+
backupRecoveryDescription: useUiLabel("backupRecoveryDescription"),
|
|
4026
|
+
error: useUiLabel("error")
|
|
4027
|
+
}
|
|
4028
|
+
};
|
|
4029
|
+
}
|
|
4030
|
+
|
|
4031
|
+
// src/features/sync/KeepSyncRecoveryDialog.tsx
|
|
4032
|
+
import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
4033
|
+
function KeepSyncRecoveryDialog({
|
|
4034
|
+
open,
|
|
4035
|
+
onOpenChange,
|
|
4036
|
+
conflicts,
|
|
4037
|
+
onManualMerge,
|
|
4038
|
+
backup,
|
|
4039
|
+
showBackupControls = true,
|
|
4040
|
+
title,
|
|
4041
|
+
children,
|
|
4042
|
+
className,
|
|
4043
|
+
...props
|
|
4044
|
+
}) {
|
|
4045
|
+
const view = useKeepSyncRecoveryDialog({ open, onOpenChange, conflicts, onManualMerge });
|
|
4046
|
+
if (!view.isOpen) return null;
|
|
4047
|
+
return /* @__PURE__ */ jsxs16(
|
|
4048
|
+
"section",
|
|
4049
|
+
{
|
|
4050
|
+
...props,
|
|
4051
|
+
className,
|
|
4052
|
+
role: "dialog",
|
|
4053
|
+
"aria-modal": "true",
|
|
4054
|
+
"aria-labelledby": "keepkit-sync-recovery-title",
|
|
4055
|
+
"aria-describedby": view.error ? "keepkit-sync-recovery-error" : void 0,
|
|
4056
|
+
"aria-busy": view.busyId !== void 0,
|
|
4057
|
+
"data-keepkit": "sync-recovery",
|
|
4058
|
+
"data-state": view.conflictList.length > 0 ? "conflict" : "error",
|
|
4059
|
+
"data-loading": view.busyId !== void 0 ? "true" : void 0,
|
|
4060
|
+
children: [
|
|
4061
|
+
/* @__PURE__ */ jsxs16("header", { children: [
|
|
4062
|
+
/* @__PURE__ */ jsx21("h2", { id: "keepkit-sync-recovery-title", children: title ?? view.labels.title }),
|
|
4063
|
+
/* @__PURE__ */ jsx21("button", { type: "button", "data-keep-action": "close-dialog", onClick: view.close, "aria-label": view.labels.close, children: view.labels.close })
|
|
4064
|
+
] }),
|
|
4065
|
+
children,
|
|
4066
|
+
view.conflictList.length > 0 ? /* @__PURE__ */ jsxs16("div", { children: [
|
|
4067
|
+
/* @__PURE__ */ jsx21("p", { children: view.labels.conflict }),
|
|
4068
|
+
view.conflictList.map((conflict) => /* @__PURE__ */ jsxs16("article", { "data-conflict-id": conflict.id, children: [
|
|
4069
|
+
/* @__PURE__ */ jsx21("h3", { children: getMetaTitle(conflict.operation.item?.meta) ?? conflict.id }),
|
|
4070
|
+
/* @__PURE__ */ jsxs16("div", { "data-conflict-preview": true, children: [
|
|
4071
|
+
/* @__PURE__ */ jsx21(
|
|
4072
|
+
ConflictPreview,
|
|
4073
|
+
{
|
|
4074
|
+
item: conflict.operation.item,
|
|
4075
|
+
heading: view.labels.localVersion,
|
|
4076
|
+
updatedAtLabel: view.labels.updatedAt,
|
|
4077
|
+
noteLabel: view.labels.note,
|
|
4078
|
+
side: "local"
|
|
4079
|
+
}
|
|
4080
|
+
),
|
|
4081
|
+
/* @__PURE__ */ jsx21(
|
|
4082
|
+
ConflictPreview,
|
|
4083
|
+
{
|
|
4084
|
+
item: conflict.remote,
|
|
4085
|
+
heading: view.labels.remoteVersion,
|
|
4086
|
+
updatedAtLabel: view.labels.updatedAt,
|
|
4087
|
+
noteLabel: view.labels.note,
|
|
4088
|
+
side: "remote"
|
|
4089
|
+
}
|
|
4090
|
+
)
|
|
4091
|
+
] }),
|
|
4092
|
+
/* @__PURE__ */ jsxs16("div", { children: [
|
|
4093
|
+
/* @__PURE__ */ jsx21(
|
|
4094
|
+
"button",
|
|
4095
|
+
{
|
|
4096
|
+
type: "button",
|
|
4097
|
+
"data-keep-action": "keep-local",
|
|
4098
|
+
onClick: () => void view.resolve(conflict, "local"),
|
|
4099
|
+
disabled: view.busyId !== void 0,
|
|
4100
|
+
children: view.labels.keepLocal
|
|
4101
|
+
}
|
|
4102
|
+
),
|
|
4103
|
+
/* @__PURE__ */ jsx21(
|
|
4104
|
+
"button",
|
|
4105
|
+
{
|
|
4106
|
+
type: "button",
|
|
4107
|
+
"data-keep-action": "use-server",
|
|
4108
|
+
onClick: () => void view.resolve(conflict, "remote"),
|
|
4109
|
+
disabled: view.busyId !== void 0,
|
|
4110
|
+
children: view.labels.useServer
|
|
4111
|
+
}
|
|
4112
|
+
),
|
|
4113
|
+
/* @__PURE__ */ jsx21(
|
|
4114
|
+
"button",
|
|
4115
|
+
{
|
|
4116
|
+
type: "button",
|
|
4117
|
+
"data-keep-action": "manual-merge",
|
|
4118
|
+
onClick: () => void view.resolve(conflict, "manual"),
|
|
4119
|
+
disabled: view.busyId !== void 0 || !onManualMerge,
|
|
4120
|
+
children: view.labels.manualMerge
|
|
4121
|
+
}
|
|
4122
|
+
)
|
|
4123
|
+
] })
|
|
4124
|
+
] }, conflict.id))
|
|
4125
|
+
] }) : null,
|
|
4126
|
+
view.showBackupRecovery ? /* @__PURE__ */ jsxs16("section", { "data-recovery": "backup", children: [
|
|
4127
|
+
/* @__PURE__ */ jsx21("h3", { children: view.labels.backupRecovery }),
|
|
4128
|
+
/* @__PURE__ */ jsx21("p", { children: view.labels.backupRecoveryDescription }),
|
|
4129
|
+
backup ?? (showBackupControls ? /* @__PURE__ */ jsx21(KeepBackup, {}) : null)
|
|
4130
|
+
] }) : null,
|
|
4131
|
+
view.error ? /* @__PURE__ */ jsx21("p", { id: "keepkit-sync-recovery-error", role: "alert", "aria-live": "assertive", children: view.error instanceof Error ? view.error.message : view.labels.error }) : null
|
|
4132
|
+
]
|
|
4133
|
+
}
|
|
4134
|
+
);
|
|
4135
|
+
}
|
|
4136
|
+
function ConflictPreview({
|
|
4137
|
+
item,
|
|
4138
|
+
heading,
|
|
4139
|
+
updatedAtLabel,
|
|
4140
|
+
noteLabel,
|
|
4141
|
+
side
|
|
4142
|
+
}) {
|
|
4143
|
+
return /* @__PURE__ */ jsxs16("article", { "data-conflict-version": side, "aria-label": heading, children: [
|
|
4144
|
+
/* @__PURE__ */ jsx21("h4", { children: heading }),
|
|
4145
|
+
/* @__PURE__ */ jsxs16("dl", { children: [
|
|
4146
|
+
/* @__PURE__ */ jsxs16("div", { children: [
|
|
4147
|
+
/* @__PURE__ */ jsx21("dt", { children: updatedAtLabel }),
|
|
4148
|
+
/* @__PURE__ */ jsx21("dd", { children: item ? /* @__PURE__ */ jsx21("time", { dateTime: new Date(item.updatedAt).toISOString(), children: formatConflictDate(item.updatedAt) }) : "\u2014" })
|
|
4149
|
+
] }),
|
|
4150
|
+
/* @__PURE__ */ jsxs16("div", { children: [
|
|
4151
|
+
/* @__PURE__ */ jsx21("dt", { children: noteLabel }),
|
|
4152
|
+
/* @__PURE__ */ jsx21("dd", { children: item?.note || "\u2014" })
|
|
4153
|
+
] })
|
|
4154
|
+
] })
|
|
4155
|
+
] });
|
|
4156
|
+
}
|
|
4157
|
+
function formatConflictDate(timestamp) {
|
|
4158
|
+
return new Date(timestamp).toISOString().slice(0, 10);
|
|
4159
|
+
}
|
|
4160
|
+
|
|
4161
|
+
// src/features/sync/hooks/useKeepSyncStatusBanner.ts
|
|
4162
|
+
import { useKeepContext as useKeepContext7 } from "@keepkit/core/react";
|
|
4163
|
+
function useKeepSyncStatusBanner({ onRetry, children }) {
|
|
4164
|
+
const context = useKeepContext7();
|
|
4165
|
+
const retryLabel = useUiLabel("retrySync");
|
|
4166
|
+
const resolveLabel = useUiLabel("resolveSync");
|
|
4167
|
+
const conflictLabel = useUiLabel("syncConflict");
|
|
4168
|
+
const pendingLabel = useUiLabel("syncPending");
|
|
4169
|
+
const syncedLabel = useUiLabel("syncSynced");
|
|
4170
|
+
const status = context.syncState.status;
|
|
4171
|
+
const hasConflicts = (context.syncState.conflicts?.length ?? 0) > 0 || context.syncState.conflictIds.length > 0;
|
|
4172
|
+
const message = children ?? (status === "error" ? getErrorMessage5(context.syncState.error) : status === "conflict" || hasConflicts ? conflictLabel : status === "pending" || status === "syncing" ? pendingLabel : syncedLabel);
|
|
4173
|
+
return {
|
|
4174
|
+
status,
|
|
4175
|
+
hasConflicts,
|
|
4176
|
+
message,
|
|
4177
|
+
isMutating: context.isMutating,
|
|
4178
|
+
role: status === "error" || status === "conflict" || hasConflicts ? "alert" : "status",
|
|
4179
|
+
showRetry: status === "error" || status === "pending" || status === "syncing",
|
|
4180
|
+
retryLabel,
|
|
4181
|
+
resolveLabel,
|
|
4182
|
+
retry: async () => {
|
|
4183
|
+
if (onRetry) {
|
|
4184
|
+
await onRetry();
|
|
4185
|
+
return;
|
|
4186
|
+
}
|
|
4187
|
+
await context.flushSync();
|
|
4188
|
+
}
|
|
4189
|
+
};
|
|
4190
|
+
}
|
|
4191
|
+
function getErrorMessage5(error) {
|
|
4192
|
+
return error instanceof Error ? error.message : "Sync failed.";
|
|
4193
|
+
}
|
|
4194
|
+
|
|
4195
|
+
// src/features/sync/KeepSyncStatusBanner.tsx
|
|
4196
|
+
import { jsx as jsx22, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
4197
|
+
function KeepSyncStatusBanner({
|
|
4198
|
+
onRetry,
|
|
4199
|
+
onResolveConflicts,
|
|
4200
|
+
children,
|
|
4201
|
+
className,
|
|
4202
|
+
...props
|
|
4203
|
+
}) {
|
|
4204
|
+
const view = useKeepSyncStatusBanner({ onRetry, children });
|
|
4205
|
+
if (view.status === "idle" && !view.hasConflicts) return null;
|
|
4206
|
+
return /* @__PURE__ */ jsxs17(
|
|
4207
|
+
"aside",
|
|
4208
|
+
{
|
|
4209
|
+
...props,
|
|
4210
|
+
className,
|
|
4211
|
+
role: props.role ?? view.role,
|
|
4212
|
+
"aria-live": props["aria-live"] ?? "polite",
|
|
4213
|
+
"data-keepkit": "sync-status",
|
|
4214
|
+
"data-state": view.status,
|
|
4215
|
+
children: [
|
|
4216
|
+
/* @__PURE__ */ jsx22("p", { children: view.message }),
|
|
4217
|
+
view.showRetry ? /* @__PURE__ */ jsx22(
|
|
4218
|
+
"button",
|
|
4219
|
+
{
|
|
4220
|
+
type: "button",
|
|
4221
|
+
"data-keep-action": "retry-sync",
|
|
4222
|
+
onClick: () => void view.retry(),
|
|
4223
|
+
disabled: view.isMutating,
|
|
4224
|
+
children: view.retryLabel
|
|
4225
|
+
}
|
|
4226
|
+
) : null,
|
|
4227
|
+
view.hasConflicts ? /* @__PURE__ */ jsx22(
|
|
4228
|
+
"button",
|
|
4229
|
+
{
|
|
4230
|
+
type: "button",
|
|
4231
|
+
"data-keep-action": "resolve-conflicts",
|
|
4232
|
+
onClick: onResolveConflicts,
|
|
4233
|
+
disabled: !onResolveConflicts,
|
|
4234
|
+
children: view.resolveLabel
|
|
4235
|
+
}
|
|
4236
|
+
) : null
|
|
4237
|
+
]
|
|
4238
|
+
}
|
|
4239
|
+
);
|
|
4240
|
+
}
|
|
4241
|
+
|
|
4242
|
+
// src/foundation/theme.tsx
|
|
4101
4243
|
import { cloneElement as cloneElement2, isValidElement as isValidElement7 } from "react";
|
|
4102
4244
|
import { jsx as jsx23 } from "react/jsx-runtime";
|
|
4103
4245
|
var keepThemeNames = [
|
|
@@ -4182,7 +4324,7 @@ import {
|
|
|
4182
4324
|
LocalStorageSyncQueueAdapter,
|
|
4183
4325
|
SyncStorageAdapter
|
|
4184
4326
|
} from "@keepkit/core/storage";
|
|
4185
|
-
import { jsx as jsx24, jsxs as
|
|
4327
|
+
import { jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
4186
4328
|
function KeepKitProvider({
|
|
4187
4329
|
labels,
|
|
4188
4330
|
locale,
|
|
@@ -4216,7 +4358,7 @@ function KeepKitProvider({
|
|
|
4216
4358
|
className: themeClassName,
|
|
4217
4359
|
style: themeStyle,
|
|
4218
4360
|
asChild: themeAsChild,
|
|
4219
|
-
children: /* @__PURE__ */
|
|
4361
|
+
children: /* @__PURE__ */ jsxs18(CoreKeepProvider, { ...providerProps, children: [
|
|
4220
4362
|
/* @__PURE__ */ jsx24(KeepSyncFeedbackObserver, {}),
|
|
4221
4363
|
children,
|
|
4222
4364
|
/* @__PURE__ */ jsx24(KeepAnnouncements, {})
|