@keepkit/ui 0.16.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 +10 -2
- package/dist/index.d.ts +232 -156
- package/dist/index.js +1636 -1117
- package/dist/index.js.map +1 -1
- package/dist/styles/base.css +21 -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,16 +1595,52 @@ 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,
|
|
1602
|
+
isValidElement,
|
|
1603
|
+
useContext as useContext2
|
|
1544
1604
|
} from "react";
|
|
1545
|
-
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
1605
|
+
import { Fragment, jsx as jsx3 } from "react/jsx-runtime";
|
|
1606
|
+
var KeepSearchQueryContext = createContext2(void 0);
|
|
1607
|
+
function KeepSearchQueryProvider({ query, children }) {
|
|
1608
|
+
return /* @__PURE__ */ jsx3(KeepSearchQueryContext.Provider, { value: query, children });
|
|
1609
|
+
}
|
|
1610
|
+
function useKeepSearchQuery() {
|
|
1611
|
+
return useContext2(KeepSearchQueryContext);
|
|
1612
|
+
}
|
|
1613
|
+
function KeepHighlight({ children, query }) {
|
|
1614
|
+
const contextQuery = useKeepSearchQuery();
|
|
1615
|
+
const resolvedQuery = query ?? contextQuery;
|
|
1616
|
+
if (typeof children !== "string" || !resolvedQuery?.trim()) return children ?? null;
|
|
1617
|
+
return highlightText(children, resolvedQuery);
|
|
1618
|
+
}
|
|
1619
|
+
function highlightText(text, query) {
|
|
1620
|
+
const normalizedQuery = query.trim();
|
|
1621
|
+
if (!normalizedQuery) return text;
|
|
1622
|
+
const matcher = new RegExp(escapeRegExp(normalizedQuery), "gi");
|
|
1623
|
+
const parts = [];
|
|
1624
|
+
let lastIndex = 0;
|
|
1625
|
+
let matchIndex = 0;
|
|
1626
|
+
while (true) {
|
|
1627
|
+
const match = matcher.exec(text);
|
|
1628
|
+
if (match === null) break;
|
|
1629
|
+
const index = match.index;
|
|
1630
|
+
if (index > lastIndex) parts.push(text.slice(lastIndex, index));
|
|
1631
|
+
parts.push(
|
|
1632
|
+
/* @__PURE__ */ jsx3("mark", { className: "keep-highlight", "data-highlight": "true", children: match[0] }, `highlight-${matchIndex}`)
|
|
1633
|
+
);
|
|
1634
|
+
lastIndex = index + match[0].length;
|
|
1635
|
+
matchIndex += 1;
|
|
1636
|
+
}
|
|
1637
|
+
if (lastIndex === 0) return text;
|
|
1638
|
+
if (lastIndex < text.length) parts.push(text.slice(lastIndex));
|
|
1639
|
+
return /* @__PURE__ */ jsx3(Fragment, { children: parts });
|
|
1640
|
+
}
|
|
1641
|
+
function escapeRegExp(value) {
|
|
1642
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1643
|
+
}
|
|
1546
1644
|
function toKeepButtonItem(item) {
|
|
1547
1645
|
return {
|
|
1548
1646
|
id: item.id,
|
|
@@ -1574,7 +1672,9 @@ function renderRoot(asChild, child, props, body, componentName) {
|
|
|
1574
1672
|
return /* @__PURE__ */ jsx3("div", { ...props, children: body });
|
|
1575
1673
|
}
|
|
1576
1674
|
|
|
1577
|
-
// 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";
|
|
1578
1678
|
function isAllSelected(items, selectedIds) {
|
|
1579
1679
|
if (items.length === 0) return false;
|
|
1580
1680
|
const selected = new Set(selectedIds);
|
|
@@ -1667,7 +1767,7 @@ function useKeepBulkActions(options) {
|
|
|
1667
1767
|
};
|
|
1668
1768
|
}
|
|
1669
1769
|
|
|
1670
|
-
// src/KeepItemCheckbox.tsx
|
|
1770
|
+
// src/features/actions/KeepItemCheckbox.tsx
|
|
1671
1771
|
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
1672
1772
|
function KeepItemCheckbox({
|
|
1673
1773
|
item,
|
|
@@ -1700,8 +1800,8 @@ function getItemLabel(item) {
|
|
|
1700
1800
|
return typeof title === "string" && title.trim() ? title.trim() : void 0;
|
|
1701
1801
|
}
|
|
1702
1802
|
|
|
1703
|
-
// src/KeepBulkActions.tsx
|
|
1704
|
-
import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1803
|
+
// src/features/actions/KeepBulkActions.tsx
|
|
1804
|
+
import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1705
1805
|
function KeepBulkActions({
|
|
1706
1806
|
query,
|
|
1707
1807
|
selectedIds: controlledSelectedIds,
|
|
@@ -1724,7 +1824,7 @@ function KeepBulkActions({
|
|
|
1724
1824
|
controlledScope,
|
|
1725
1825
|
onSelectionScopeChange
|
|
1726
1826
|
});
|
|
1727
|
-
const body = render ? render(state) : typeof children === "function" ? children(state) : children ?? /* @__PURE__ */ jsxs2(
|
|
1827
|
+
const body = render ? render(state) : typeof children === "function" ? children(state) : children ?? /* @__PURE__ */ jsxs2(Fragment2, { children: [
|
|
1728
1828
|
/* @__PURE__ */ jsxs2("fieldset", { children: [
|
|
1729
1829
|
/* @__PURE__ */ jsx5("legend", { children: labels.selectItems }),
|
|
1730
1830
|
/* @__PURE__ */ jsxs2("label", { children: [
|
|
@@ -1815,13 +1915,13 @@ function KeepBulkActions({
|
|
|
1815
1915
|
);
|
|
1816
1916
|
}
|
|
1817
1917
|
|
|
1818
|
-
// src/KeepButton.tsx
|
|
1918
|
+
// src/features/actions/KeepButton.tsx
|
|
1819
1919
|
import {
|
|
1820
1920
|
KeepButton as CoreKeepButton
|
|
1821
1921
|
} from "@keepkit/core/react";
|
|
1822
|
-
import { createElement, useEffect, useRef as
|
|
1922
|
+
import { createElement, useEffect as useEffect2, useRef as useRef3 } from "react";
|
|
1823
1923
|
|
|
1824
|
-
// src/hooks/useKeepButton.ts
|
|
1924
|
+
// src/features/actions/hooks/useKeepButton.ts
|
|
1825
1925
|
import { useKeepItem } from "@keepkit/core/react";
|
|
1826
1926
|
function useKeepButton({ item, labels, icons, children }) {
|
|
1827
1927
|
return {
|
|
@@ -1841,8 +1941,8 @@ function useKeepButton({ item, labels, icons, children }) {
|
|
|
1841
1941
|
};
|
|
1842
1942
|
}
|
|
1843
1943
|
|
|
1844
|
-
// src/KeepButton.tsx
|
|
1845
|
-
import { Fragment as
|
|
1944
|
+
// src/features/actions/KeepButton.tsx
|
|
1945
|
+
import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1846
1946
|
function KeepButton({
|
|
1847
1947
|
labels,
|
|
1848
1948
|
icons,
|
|
@@ -1854,8 +1954,8 @@ function KeepButton({
|
|
|
1854
1954
|
...props
|
|
1855
1955
|
}) {
|
|
1856
1956
|
const view = useKeepButton({ item: props.item, labels, icons, children: props.children });
|
|
1857
|
-
const pendingToggle =
|
|
1858
|
-
|
|
1957
|
+
const pendingToggle = useRef3(null);
|
|
1958
|
+
useEffect2(() => {
|
|
1859
1959
|
const pending = pendingToggle.current;
|
|
1860
1960
|
if (!pending || view.buttonState.isMutating || pending.wasSaved === view.buttonState.isSaved) return;
|
|
1861
1961
|
pendingToggle.current = null;
|
|
@@ -1887,7 +1987,7 @@ function KeepButton({
|
|
|
1887
1987
|
const label = state.error ? labels?.error ?? view.labels.error : state.isMutating ? labels?.loading ?? view.labels.loading : state.isSaved ? labels?.saved ?? view.labels.saved : labels?.unsaved ?? view.labels.save;
|
|
1888
1988
|
if (!icons) return label;
|
|
1889
1989
|
const icon = state.error ? icons.error : state.isMutating ? icons.loading : state.isSaved ? icons.remove ?? icons.saved : icons.save;
|
|
1890
|
-
return /* @__PURE__ */ jsxs3(
|
|
1990
|
+
return /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
1891
1991
|
renderIcon(icon, iconClassName),
|
|
1892
1992
|
showLabel && !iconOnly ? label : null
|
|
1893
1993
|
] });
|
|
@@ -1915,128 +2015,388 @@ function KeepButton({
|
|
|
1915
2015
|
unsavedAriaLabel: labels?.unsavedAriaLabel ?? props.unsavedAriaLabel
|
|
1916
2016
|
};
|
|
1917
2017
|
if (!view.customStateLabel) return /* @__PURE__ */ jsx6(CoreKeepButton, { ...sharedProps });
|
|
1918
|
-
return /* @__PURE__ */ jsx6(CoreKeepButton, { ...sharedProps, children: (state) => /* @__PURE__ */ jsx6(
|
|
2018
|
+
return /* @__PURE__ */ jsx6(CoreKeepButton, { ...sharedProps, children: (state) => /* @__PURE__ */ jsx6(Fragment3, { children: getStateContent(state) }) });
|
|
1919
2019
|
}
|
|
1920
2020
|
function renderIcon(icon, className) {
|
|
1921
2021
|
if (typeof icon === "function") return createElement(icon, { "aria-hidden": true, className });
|
|
1922
2022
|
return icon ?? null;
|
|
1923
2023
|
}
|
|
1924
2024
|
|
|
1925
|
-
// src/
|
|
1926
|
-
import {
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
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));
|
|
1941
2044
|
return {
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
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")
|
|
1950
2059
|
};
|
|
1951
2060
|
}
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
...decoded.pagination ? { pagination: { ...previousQuery.pagination, ...decoded.pagination } } : {}
|
|
1977
|
-
}));
|
|
1978
|
-
};
|
|
1979
|
-
read();
|
|
1980
|
-
return adapter.subscribe?.(read);
|
|
1981
|
-
}, [adapter, enabled, params]);
|
|
1982
|
-
useEffect2(() => {
|
|
1983
|
-
if (!enabled) return;
|
|
1984
|
-
if (skipWriteRef.current) {
|
|
1985
|
-
skipWriteRef.current = false;
|
|
1986
|
-
return;
|
|
1987
|
-
}
|
|
1988
|
-
const currentUrl = new URL(adapter.getUrl(), "http://keepkit.invalid");
|
|
1989
|
-
const urlParams = { ...DEFAULT_KEEP_URL_PARAMS, ...params };
|
|
1990
|
-
for (const key of Object.values(urlParams)) currentUrl.searchParams.delete(key);
|
|
1991
|
-
const nextParams = encodeKeepListQuery(query, { params });
|
|
1992
|
-
nextParams.forEach((value, key) => {
|
|
1993
|
-
currentUrl.searchParams.append(key, value);
|
|
1994
|
-
});
|
|
1995
|
-
const nextUrl = `${currentUrl.pathname}${currentUrl.search}${currentUrl.hash}`;
|
|
1996
|
-
adapter.navigate(nextUrl, options.history ?? "push");
|
|
1997
|
-
}, [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
|
+
] });
|
|
1998
2085
|
}
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
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);
|
|
2006
2109
|
},
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
}
|
|
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") }
|
|
2011
2120
|
};
|
|
2012
2121
|
}
|
|
2013
2122
|
|
|
2014
|
-
// src/
|
|
2015
|
-
|
|
2123
|
+
// src/features/query/KeepTagFilter.tsx
|
|
2124
|
+
import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
2125
|
+
function KeepTagFilter({
|
|
2016
2126
|
query,
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
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
|
|
2021
2139
|
}) {
|
|
2022
|
-
const
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
() => (
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
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,
|
|
2040
2400
|
tags: tag ? [.../* @__PURE__ */ new Set([...query.tags ?? [], tag])] : query.tags,
|
|
2041
2401
|
pagination: enabled.pagination ? { ...query.pagination, page, pageSize: resolvedPageSize } : query.pagination
|
|
2042
2402
|
}),
|
|
@@ -2055,7 +2415,7 @@ function useKeepCollection({
|
|
|
2055
2415
|
options: typeof urlSync === "object" ? urlSync : {},
|
|
2056
2416
|
adapter: urlAdapter
|
|
2057
2417
|
});
|
|
2058
|
-
const list =
|
|
2418
|
+
const list = useKeepList3(resolvedQuery);
|
|
2059
2419
|
return {
|
|
2060
2420
|
enabled,
|
|
2061
2421
|
searchValue,
|
|
@@ -2080,108 +2440,38 @@ function useKeepCollection({
|
|
|
2080
2440
|
};
|
|
2081
2441
|
}
|
|
2082
2442
|
|
|
2083
|
-
// src/KeepList.tsx
|
|
2443
|
+
// src/features/collection/KeepList.tsx
|
|
2084
2444
|
import { KeepErrorBoundary } from "@keepkit/core/react";
|
|
2085
|
-
import { isValidElement as
|
|
2086
|
-
|
|
2087
|
-
// src/hooks/useKeepListView.ts
|
|
2088
|
-
import { useKeepList as useKeepList3 } from "@keepkit/core/react";
|
|
2089
|
-
function useKeepListView(query) {
|
|
2090
|
-
return {
|
|
2091
|
-
state: useKeepList3(query),
|
|
2092
|
-
labels: {
|
|
2093
|
-
loading: useUiLabel("loadingItems"),
|
|
2094
|
-
empty: useUiLabel("noItems"),
|
|
2095
|
-
error: useUiLabel("errorItems")
|
|
2096
|
-
}
|
|
2097
|
-
};
|
|
2098
|
-
}
|
|
2445
|
+
import { isValidElement as isValidElement4 } from "react";
|
|
2099
2446
|
|
|
2100
|
-
// src/KeepItemCard.tsx
|
|
2447
|
+
// src/features/item/KeepItemCard.tsx
|
|
2101
2448
|
import {
|
|
2102
|
-
createContext as
|
|
2449
|
+
createContext as createContext3,
|
|
2103
2450
|
createElement as createElement2,
|
|
2104
|
-
isValidElement as
|
|
2105
|
-
useContext as
|
|
2451
|
+
isValidElement as isValidElement3,
|
|
2452
|
+
useContext as useContext3,
|
|
2453
|
+
useEffect as useEffect5,
|
|
2454
|
+
useState as useState8
|
|
2106
2455
|
} from "react";
|
|
2107
2456
|
|
|
2108
|
-
// src/hooks/
|
|
2109
|
-
|
|
2110
|
-
function useKeepItemCard(options) {
|
|
2111
|
-
const {
|
|
2112
|
-
item,
|
|
2113
|
-
title,
|
|
2114
|
-
getTitle,
|
|
2115
|
-
getImageProps,
|
|
2116
|
-
href: hrefOption,
|
|
2117
|
-
linkTargetAttribute,
|
|
2118
|
-
linkRel,
|
|
2119
|
-
onRemoveError,
|
|
2120
|
-
onRemoved
|
|
2121
|
-
} = options;
|
|
2122
|
-
const itemState = useKeepItem2(item);
|
|
2123
|
-
const emitFeedback = useKeepUiFeedback();
|
|
2124
|
-
const removedMessage = useUiLabel("removedMessage");
|
|
2125
|
-
const restoredMessage = useUiLabel("restoredMessage");
|
|
2126
|
-
const undoLabel = useUiLabel("undo");
|
|
2127
|
-
const resolvedTitle = typeof title === "function" ? title(item) : title ?? getTitle?.(item) ?? getMetaTitle(item.meta) ?? item.id;
|
|
2128
|
-
const imageProps = getImageProps?.(item, resolvedTitle);
|
|
2129
|
-
const href = typeof hrefOption === "function" ? hrefOption(item) : hrefOption;
|
|
2130
|
-
const isAvailable = item.status === void 0 || item.status === "available";
|
|
2131
|
-
const isExternalLink = href ? /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href) : false;
|
|
2132
|
-
const statusLabelKey = item.status && item.status !== "available" ? getStatusLabelKey(item.status) : "statusUnknown";
|
|
2133
|
-
const unavailableLabel = useUiLabel(statusLabelKey);
|
|
2134
|
-
const remove = async () => {
|
|
2135
|
-
const wasSaved = itemState.isSaved;
|
|
2136
|
-
try {
|
|
2137
|
-
await itemState.removeWithUndo();
|
|
2138
|
-
onRemoved?.(item);
|
|
2139
|
-
if (!wasSaved) return;
|
|
2140
|
-
emitFeedback({
|
|
2141
|
-
type: "item-removed",
|
|
2142
|
-
item,
|
|
2143
|
-
message: removedMessage,
|
|
2144
|
-
undoLabel,
|
|
2145
|
-
undo: async () => {
|
|
2146
|
-
await itemState.undo();
|
|
2147
|
-
emitFeedback({ type: "item-restored", item, items: [item], message: restoredMessage });
|
|
2148
|
-
}
|
|
2149
|
-
});
|
|
2150
|
-
} catch (cause) {
|
|
2151
|
-
onRemoveError?.(cause);
|
|
2152
|
-
}
|
|
2153
|
-
};
|
|
2154
|
-
const state = {
|
|
2155
|
-
item,
|
|
2156
|
-
isSaved: itemState.isSaved,
|
|
2157
|
-
isMutating: itemState.isMutating,
|
|
2158
|
-
error: itemState.error,
|
|
2159
|
-
remove,
|
|
2160
|
-
status: item.status
|
|
2161
|
-
};
|
|
2457
|
+
// src/features/item/hooks/useKeepItemStatusBadge.ts
|
|
2458
|
+
function useKeepItemStatusBadge(status) {
|
|
2162
2459
|
return {
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
imageProps,
|
|
2167
|
-
href,
|
|
2168
|
-
isAvailable,
|
|
2169
|
-
displayStatus: getDisplayStatus(item.status),
|
|
2170
|
-
resolvedLinkTarget: linkTargetAttribute ?? (isExternalLink ? "_blank" : void 0),
|
|
2171
|
-
resolvedLinkRel: linkRel ?? (isExternalLink ? "noreferrer" : void 0),
|
|
2172
|
-
statusLabel: item.status && item.status !== "available" ? unavailableLabel : void 0,
|
|
2173
|
-
remove,
|
|
2174
|
-
labels: {
|
|
2175
|
-
save: useUiLabel("save"),
|
|
2176
|
-
savedAt: useUiLabel("saved"),
|
|
2177
|
-
error: useUiLabel("error"),
|
|
2178
|
-
remove: useUiLabel("remove"),
|
|
2179
|
-
tags: useUiLabel("tags")
|
|
2180
|
-
}
|
|
2460
|
+
resolvedStatus: getDisplayStatus(status),
|
|
2461
|
+
statusLabel: useUiLabel(getStatusLabelKey(status)),
|
|
2462
|
+
icon: getStatusIcon(status)
|
|
2181
2463
|
};
|
|
2182
2464
|
}
|
|
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";
|
|
2470
|
+
}
|
|
2183
2471
|
function getStatusLabelKey(status) {
|
|
2184
2472
|
switch (status) {
|
|
2473
|
+
case "available":
|
|
2474
|
+
return "statusAvailable";
|
|
2185
2475
|
case "expired":
|
|
2186
2476
|
return "statusExpired";
|
|
2187
2477
|
case "removed":
|
|
@@ -2190,28 +2480,85 @@ function getStatusLabelKey(status) {
|
|
|
2190
2480
|
return "statusDeleted";
|
|
2191
2481
|
case "private":
|
|
2192
2482
|
return "statusPrivate";
|
|
2193
|
-
|
|
2483
|
+
case "unknown":
|
|
2194
2484
|
return "statusUnknown";
|
|
2485
|
+
case "restricted":
|
|
2486
|
+
return "statusPrivate";
|
|
2195
2487
|
}
|
|
2196
2488
|
}
|
|
2197
2489
|
function getDisplayStatus(status) {
|
|
2198
|
-
if (status ===
|
|
2490
|
+
if (status === "available") return "available";
|
|
2199
2491
|
if (status === "expired") return "expired";
|
|
2200
|
-
if (status === "removed") return "removed";
|
|
2492
|
+
if (status === "removed" || status === "deleted") return "removed";
|
|
2201
2493
|
return "restricted";
|
|
2202
2494
|
}
|
|
2203
2495
|
|
|
2204
|
-
// src/
|
|
2205
|
-
import {
|
|
2206
|
-
|
|
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";
|
|
2207
2554
|
function useKeepStaleNotice({ item, onRetry, onRemoved }) {
|
|
2208
|
-
const context =
|
|
2555
|
+
const context = useKeepContext3();
|
|
2209
2556
|
const emitFeedback = useKeepUiFeedback();
|
|
2210
2557
|
const removedMessage = useUiLabel("removedMessage");
|
|
2211
2558
|
const restoredMessage = useUiLabel("restoredMessage");
|
|
2212
2559
|
const undoLabel = useUiLabel("undo");
|
|
2213
|
-
const [isRetrying, setIsRetrying] =
|
|
2214
|
-
const [error, setError] =
|
|
2560
|
+
const [isRetrying, setIsRetrying] = useState7(false);
|
|
2561
|
+
const [error, setError] = useState7(null);
|
|
2215
2562
|
async function retry() {
|
|
2216
2563
|
setError(null);
|
|
2217
2564
|
setIsRetrying(true);
|
|
@@ -2257,7 +2604,7 @@ function useKeepStaleNotice({ item, onRetry, onRemoved }) {
|
|
|
2257
2604
|
};
|
|
2258
2605
|
}
|
|
2259
2606
|
function useKeepPruneStale({ statuses, onPruned }) {
|
|
2260
|
-
const context =
|
|
2607
|
+
const context = useKeepContext3();
|
|
2261
2608
|
const emitFeedback = useKeepUiFeedback();
|
|
2262
2609
|
const staleItems = context.items.filter((item) => item.status && statuses.includes(item.status));
|
|
2263
2610
|
const staleIds = staleItems.map((item) => item.id);
|
|
@@ -2290,54 +2637,8 @@ function useKeepPruneStale({ statuses, onPruned }) {
|
|
|
2290
2637
|
};
|
|
2291
2638
|
}
|
|
2292
2639
|
|
|
2293
|
-
// src/
|
|
2294
|
-
|
|
2295
|
-
return { resolvedStatus: getDisplayStatus2(status), statusLabel: useUiLabel(getStatusLabelKey2(status)) };
|
|
2296
|
-
}
|
|
2297
|
-
function getStatusLabelKey2(status) {
|
|
2298
|
-
switch (status) {
|
|
2299
|
-
case "available":
|
|
2300
|
-
return "statusAvailable";
|
|
2301
|
-
case "expired":
|
|
2302
|
-
return "statusExpired";
|
|
2303
|
-
case "removed":
|
|
2304
|
-
return "statusRemoved";
|
|
2305
|
-
case "deleted":
|
|
2306
|
-
return "statusDeleted";
|
|
2307
|
-
case "private":
|
|
2308
|
-
return "statusPrivate";
|
|
2309
|
-
case "unknown":
|
|
2310
|
-
return "statusUnknown";
|
|
2311
|
-
case "restricted":
|
|
2312
|
-
return "statusPrivate";
|
|
2313
|
-
}
|
|
2314
|
-
}
|
|
2315
|
-
function getDisplayStatus2(status) {
|
|
2316
|
-
if (status === "available") return "available";
|
|
2317
|
-
if (status === "expired") return "expired";
|
|
2318
|
-
if (status === "removed") return "removed";
|
|
2319
|
-
return "restricted";
|
|
2320
|
-
}
|
|
2321
|
-
|
|
2322
|
-
// src/KeepItemStatusBadge.tsx
|
|
2323
|
-
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
2324
|
-
function KeepItemStatusBadge({ status = "available", label, className, ...props }) {
|
|
2325
|
-
const view = useKeepItemStatusBadge(status);
|
|
2326
|
-
return /* @__PURE__ */ jsx7(
|
|
2327
|
-
"span",
|
|
2328
|
-
{
|
|
2329
|
-
...props,
|
|
2330
|
-
className,
|
|
2331
|
-
"data-keepkit": "status-badge",
|
|
2332
|
-
"data-status": status,
|
|
2333
|
-
"data-item-status": view.resolvedStatus,
|
|
2334
|
-
children: label ?? view.statusLabel
|
|
2335
|
-
}
|
|
2336
|
-
);
|
|
2337
|
-
}
|
|
2338
|
-
|
|
2339
|
-
// src/KeepStaleNotice.tsx
|
|
2340
|
-
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";
|
|
2341
2642
|
function KeepStaleNotice({
|
|
2342
2643
|
item,
|
|
2343
2644
|
onRetry,
|
|
@@ -2349,11 +2650,11 @@ function KeepStaleNotice({
|
|
|
2349
2650
|
...props
|
|
2350
2651
|
}) {
|
|
2351
2652
|
const view = useKeepStaleNotice({ item, onRetry, onRemoved });
|
|
2352
|
-
return /* @__PURE__ */
|
|
2353
|
-
/* @__PURE__ */
|
|
2354
|
-
children ?? (item.statusReason ? /* @__PURE__ */
|
|
2355
|
-
/* @__PURE__ */
|
|
2356
|
-
/* @__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(
|
|
2357
2658
|
"button",
|
|
2358
2659
|
{
|
|
2359
2660
|
type: "button",
|
|
@@ -2363,7 +2664,7 @@ function KeepStaleNotice({
|
|
|
2363
2664
|
children: retryLabel ?? view.labels.retry
|
|
2364
2665
|
}
|
|
2365
2666
|
),
|
|
2366
|
-
/* @__PURE__ */
|
|
2667
|
+
/* @__PURE__ */ jsx11(
|
|
2367
2668
|
"button",
|
|
2368
2669
|
{
|
|
2369
2670
|
type: "button",
|
|
@@ -2374,7 +2675,7 @@ function KeepStaleNotice({
|
|
|
2374
2675
|
}
|
|
2375
2676
|
)
|
|
2376
2677
|
] }),
|
|
2377
|
-
view.error ? /* @__PURE__ */
|
|
2678
|
+
view.error ? /* @__PURE__ */ jsx11("p", { role: "alert", children: view.error instanceof Error ? view.error.message : view.labels.error }) : null
|
|
2378
2679
|
] });
|
|
2379
2680
|
}
|
|
2380
2681
|
function KeepPruneStaleButton({
|
|
@@ -2386,7 +2687,7 @@ function KeepPruneStaleButton({
|
|
|
2386
2687
|
...props
|
|
2387
2688
|
}) {
|
|
2388
2689
|
const view = useKeepPruneStale({ statuses, onPruned });
|
|
2389
|
-
return /* @__PURE__ */
|
|
2690
|
+
return /* @__PURE__ */ jsx11(
|
|
2390
2691
|
"button",
|
|
2391
2692
|
{
|
|
2392
2693
|
...props,
|
|
@@ -2402,11 +2703,107 @@ function KeepPruneStaleButton({
|
|
|
2402
2703
|
);
|
|
2403
2704
|
}
|
|
2404
2705
|
|
|
2405
|
-
// src/
|
|
2406
|
-
import {
|
|
2407
|
-
|
|
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);
|
|
2408
2805
|
function useKeepItemCardCompound(part) {
|
|
2409
|
-
const context =
|
|
2806
|
+
const context = useContext3(KeepItemCardContext);
|
|
2410
2807
|
if (!context) throw new Error(`KeepItemCard.${part} must be rendered inside KeepItemCard.`);
|
|
2411
2808
|
return context;
|
|
2412
2809
|
}
|
|
@@ -2436,6 +2833,7 @@ function KeepItemCardRoot({
|
|
|
2436
2833
|
linkComponent: LinkComponent,
|
|
2437
2834
|
linkTargetAttribute,
|
|
2438
2835
|
linkRel,
|
|
2836
|
+
highlightQuery,
|
|
2439
2837
|
className,
|
|
2440
2838
|
...rootProps
|
|
2441
2839
|
}) {
|
|
@@ -2450,7 +2848,9 @@ function KeepItemCardRoot({
|
|
|
2450
2848
|
onRemoveError,
|
|
2451
2849
|
onRemoved
|
|
2452
2850
|
});
|
|
2453
|
-
const
|
|
2851
|
+
const contextQuery = useKeepSearchQuery();
|
|
2852
|
+
const searchQuery = highlightQuery ?? contextQuery;
|
|
2853
|
+
const contentChildren = asChild && isValidElement3(children) ? void 0 : children;
|
|
2454
2854
|
function renderLink(content) {
|
|
2455
2855
|
if (!view.href || !view.isAvailable) return content;
|
|
2456
2856
|
const linkProps = {
|
|
@@ -2460,28 +2860,49 @@ function KeepItemCardRoot({
|
|
|
2460
2860
|
onClick: (event) => onOpen?.(item, event),
|
|
2461
2861
|
children: content
|
|
2462
2862
|
};
|
|
2463
|
-
return LinkComponent ? /* @__PURE__ */
|
|
2863
|
+
return LinkComponent ? /* @__PURE__ */ jsx12(LinkComponent, { ...linkProps }) : /* @__PURE__ */ jsx12("a", { ...linkProps });
|
|
2464
2864
|
}
|
|
2465
2865
|
function renderTitle(content) {
|
|
2466
2866
|
if (linkTarget !== "title") return content;
|
|
2467
2867
|
if (view.isAvailable) return renderLink(content);
|
|
2468
|
-
return view.href ? /* @__PURE__ */
|
|
2868
|
+
return view.href ? /* @__PURE__ */ jsx12("span", { "aria-disabled": "true", "data-link-disabled": "true", children: content }) : content;
|
|
2469
2869
|
}
|
|
2470
2870
|
const resolvedImageProps = view.imageProps ? { ...view.imageProps, alt: imageAlt ?? view.imageProps.alt } : void 0;
|
|
2471
|
-
const
|
|
2871
|
+
const imageSource = resolvedImageProps?.src;
|
|
2872
|
+
const [imageStatus, setImageStatus] = useState8(imageSource ? "loading" : "error");
|
|
2873
|
+
useEffect5(() => {
|
|
2874
|
+
setImageStatus(imageSource ? "loading" : "error");
|
|
2875
|
+
}, [imageSource]);
|
|
2876
|
+
let image = null;
|
|
2877
|
+
if (resolvedImageProps) {
|
|
2878
|
+
const imagePropsWithHandlers = {
|
|
2879
|
+
...resolvedImageProps,
|
|
2880
|
+
src: resolvedImageProps.src,
|
|
2881
|
+
alt: imageAlt ?? resolvedImageProps.alt,
|
|
2882
|
+
onLoad: (event) => {
|
|
2883
|
+
resolvedImageProps.onLoad?.(event);
|
|
2884
|
+
setImageStatus("loaded");
|
|
2885
|
+
},
|
|
2886
|
+
onError: (event) => {
|
|
2887
|
+
resolvedImageProps.onError?.(event);
|
|
2888
|
+
setImageStatus("error");
|
|
2889
|
+
}
|
|
2890
|
+
};
|
|
2891
|
+
image = renderImage?.(imagePropsWithHandlers, item) ?? (ImageComponent ? /* @__PURE__ */ jsx12(ImageComponent, { ...imagePropsWithHandlers }) : /* @__PURE__ */ jsx12("img", { ...imagePropsWithHandlers, alt: imagePropsWithHandlers.alt }));
|
|
2892
|
+
}
|
|
2472
2893
|
const tags = showTags ? item.tags ?? [] : [];
|
|
2473
2894
|
const renderedTags = showTags && tags.length > 0 ? renderTags?.(tags, item) ?? null : null;
|
|
2474
|
-
const meta = showSavedAt ? /* @__PURE__ */
|
|
2475
|
-
/* @__PURE__ */
|
|
2895
|
+
const meta = showSavedAt ? /* @__PURE__ */ jsxs9("div", { "data-card-meta": true, children: [
|
|
2896
|
+
/* @__PURE__ */ jsxs9("span", { children: [
|
|
2476
2897
|
view.labels.savedAt,
|
|
2477
2898
|
":"
|
|
2478
2899
|
] }),
|
|
2479
2900
|
" ",
|
|
2480
|
-
/* @__PURE__ */
|
|
2901
|
+
/* @__PURE__ */ jsx12("time", { dateTime: new Date(item.savedAt).toISOString(), children: formatSavedAt(item.savedAt) })
|
|
2481
2902
|
] }) : null;
|
|
2482
|
-
const error = view.itemState.error ? /* @__PURE__ */
|
|
2483
|
-
const actions = view.statusLabel ? /* @__PURE__ */
|
|
2484
|
-
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(
|
|
2485
2906
|
KeepButton,
|
|
2486
2907
|
{
|
|
2487
2908
|
item: toKeepButtonItem(item),
|
|
@@ -2489,7 +2910,7 @@ function KeepItemCardRoot({
|
|
|
2489
2910
|
getAriaLabel: (buttonState) => `${buttonState.isSaved ? view.labels.remove : view.labels.save} ${String(view.resolvedTitle)}`
|
|
2490
2911
|
}
|
|
2491
2912
|
) : null,
|
|
2492
|
-
/* @__PURE__ */
|
|
2913
|
+
/* @__PURE__ */ jsx12(
|
|
2493
2914
|
"button",
|
|
2494
2915
|
{
|
|
2495
2916
|
type: "button",
|
|
@@ -2503,25 +2924,27 @@ function KeepItemCardRoot({
|
|
|
2503
2924
|
const compoundValue = {
|
|
2504
2925
|
resolvedTitle: view.resolvedTitle,
|
|
2505
2926
|
renderTitle,
|
|
2506
|
-
image,
|
|
2927
|
+
image: imageStatus === "error" ? null : image,
|
|
2928
|
+
imageStatus,
|
|
2507
2929
|
fallbackLabel: String(view.resolvedTitle),
|
|
2508
2930
|
tags,
|
|
2509
2931
|
tagsLabel: view.labels.tags,
|
|
2510
2932
|
renderedTags,
|
|
2511
2933
|
meta,
|
|
2512
2934
|
error,
|
|
2513
|
-
actions
|
|
2935
|
+
actions,
|
|
2936
|
+
renderText: (content) => /* @__PURE__ */ jsx12(KeepHighlight, { query: searchQuery, children: content })
|
|
2514
2937
|
};
|
|
2515
|
-
const defaultBody = /* @__PURE__ */
|
|
2516
|
-
/* @__PURE__ */
|
|
2517
|
-
/* @__PURE__ */
|
|
2518
|
-
/* @__PURE__ */
|
|
2938
|
+
const defaultBody = /* @__PURE__ */ jsxs9(Fragment6, { children: [
|
|
2939
|
+
/* @__PURE__ */ jsx12(KeepItemCardMedia, {}),
|
|
2940
|
+
/* @__PURE__ */ jsx12(KeepItemCardContent, {}),
|
|
2941
|
+
/* @__PURE__ */ jsx12(KeepItemCardActions, {})
|
|
2519
2942
|
] });
|
|
2520
2943
|
const body = render ? render(view.state) : typeof contentChildren === "function" ? contentChildren(view.state) : contentChildren ?? defaultBody;
|
|
2521
2944
|
const linkedBody = linkTarget === "card" && view.isAvailable ? renderLink(body) : body;
|
|
2522
2945
|
const root = renderRoot(
|
|
2523
2946
|
asChild,
|
|
2524
|
-
|
|
2947
|
+
isValidElement3(children) ? children : void 0,
|
|
2525
2948
|
{
|
|
2526
2949
|
...rootProps,
|
|
2527
2950
|
className,
|
|
@@ -2536,38 +2959,52 @@ function KeepItemCardRoot({
|
|
|
2536
2959
|
linkedBody,
|
|
2537
2960
|
"KeepItemCard"
|
|
2538
2961
|
);
|
|
2539
|
-
return /* @__PURE__ */
|
|
2962
|
+
return /* @__PURE__ */ jsx12(KeepItemCardContext.Provider, { value: compoundValue, children: root });
|
|
2540
2963
|
}
|
|
2541
2964
|
function KeepItemCardMedia({ children, fallback, ...props }) {
|
|
2542
2965
|
const context = useKeepItemCardCompound("Media");
|
|
2543
|
-
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, {}) }) });
|
|
2544
2967
|
}
|
|
2545
2968
|
function KeepItemCardContent({ children, ...props }) {
|
|
2546
2969
|
const context = useKeepItemCardCompound("Content");
|
|
2547
|
-
return /* @__PURE__ */
|
|
2548
|
-
/* @__PURE__ */
|
|
2970
|
+
return /* @__PURE__ */ jsx12("div", { ...props, "data-keep-card-part": "content", children: children === void 0 ? /* @__PURE__ */ jsxs9(Fragment6, { children: [
|
|
2971
|
+
/* @__PURE__ */ jsx12(KeepItemCardTitle, {}),
|
|
2549
2972
|
context.meta,
|
|
2550
|
-
/* @__PURE__ */
|
|
2973
|
+
/* @__PURE__ */ jsx12(KeepItemCardTags, {}),
|
|
2551
2974
|
context.error
|
|
2552
|
-
] }) });
|
|
2975
|
+
] }) : context.renderText(children) });
|
|
2553
2976
|
}
|
|
2554
2977
|
function KeepItemCardTitle({ as = "h3", children, ...props }) {
|
|
2555
2978
|
const context = useKeepItemCardCompound("Title");
|
|
2556
2979
|
return createElement2(
|
|
2557
2980
|
as,
|
|
2558
|
-
{ ...props, "data-keep-card-part": "title" },
|
|
2559
|
-
context.renderTitle(children ?? context.resolvedTitle)
|
|
2981
|
+
{ ...props, "data-keep-card-part": "title", "data-line-clamp": "2" },
|
|
2982
|
+
context.renderTitle(context.renderText(children ?? context.resolvedTitle))
|
|
2560
2983
|
);
|
|
2561
2984
|
}
|
|
2985
|
+
function KeepMediaPlaceholderIcon() {
|
|
2986
|
+
return /* @__PURE__ */ jsxs9("svg", { "data-media-fallback-icon": "true", viewBox: "0 0 24 24", "aria-hidden": "true", children: [
|
|
2987
|
+
/* @__PURE__ */ jsx12(
|
|
2988
|
+
"path",
|
|
2989
|
+
{
|
|
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",
|
|
2991
|
+
fill: "none",
|
|
2992
|
+
stroke: "currentColor"
|
|
2993
|
+
}
|
|
2994
|
+
),
|
|
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" })
|
|
2997
|
+
] });
|
|
2998
|
+
}
|
|
2562
2999
|
function KeepItemCardTags({ children, ...props }) {
|
|
2563
3000
|
const context = useKeepItemCardCompound("Tags");
|
|
2564
3001
|
if (children === void 0 && context.renderedTags) return context.renderedTags;
|
|
2565
3002
|
if (children === void 0 && context.tags.length === 0) return null;
|
|
2566
|
-
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)) });
|
|
2567
3004
|
}
|
|
2568
3005
|
function KeepItemCardActions({ children, ...props }) {
|
|
2569
3006
|
const context = useKeepItemCardCompound("Actions");
|
|
2570
|
-
return /* @__PURE__ */
|
|
3007
|
+
return /* @__PURE__ */ jsx12("div", { ...props, "data-keep-card-part": "actions", children: children ?? context.actions });
|
|
2571
3008
|
}
|
|
2572
3009
|
var KeepItemCard = Object.assign(KeepItemCardRoot, {
|
|
2573
3010
|
Media: KeepItemCardMedia,
|
|
@@ -2577,12 +3014,12 @@ var KeepItemCard = Object.assign(KeepItemCardRoot, {
|
|
|
2577
3014
|
Actions: KeepItemCardActions
|
|
2578
3015
|
});
|
|
2579
3016
|
function KeepItemCardSkeleton({ layout = "list", ...props }) {
|
|
2580
|
-
return /* @__PURE__ */
|
|
2581
|
-
/* @__PURE__ */
|
|
2582
|
-
/* @__PURE__ */
|
|
2583
|
-
/* @__PURE__ */
|
|
2584
|
-
/* @__PURE__ */
|
|
2585
|
-
/* @__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" })
|
|
2586
3023
|
] });
|
|
2587
3024
|
}
|
|
2588
3025
|
function formatSavedAt(timestamp) {
|
|
@@ -2592,19 +3029,97 @@ function getErrorMessage2(error, fallback) {
|
|
|
2592
3029
|
return error instanceof Error ? error.message : fallback;
|
|
2593
3030
|
}
|
|
2594
3031
|
|
|
2595
|
-
// src/
|
|
2596
|
-
import {
|
|
2597
|
-
function
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
3032
|
+
// src/features/collection/hooks/useKeepListView.ts
|
|
3033
|
+
import { useKeepList as useKeepList4 } from "@keepkit/core/react";
|
|
3034
|
+
function useKeepListView(query) {
|
|
3035
|
+
return {
|
|
3036
|
+
state: useKeepList4(query),
|
|
3037
|
+
labels: {
|
|
3038
|
+
loading: useUiLabel("loadingItems"),
|
|
3039
|
+
empty: useUiLabel("noItems"),
|
|
3040
|
+
error: useUiLabel("errorItems")
|
|
3041
|
+
}
|
|
3042
|
+
};
|
|
3043
|
+
}
|
|
3044
|
+
|
|
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
|
+
});
|
|
3082
|
+
},
|
|
3083
|
+
[getItems]
|
|
3084
|
+
);
|
|
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();
|
|
3104
|
+
},
|
|
3105
|
+
[getItems]
|
|
3106
|
+
);
|
|
3107
|
+
return { ref, onKeyDown, onFocusCapture };
|
|
3108
|
+
}
|
|
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,
|
|
3120
|
+
children,
|
|
3121
|
+
renderItem,
|
|
3122
|
+
loading,
|
|
2608
3123
|
renderLoading,
|
|
2609
3124
|
loadingCount = 6,
|
|
2610
3125
|
empty,
|
|
@@ -2612,10 +3127,13 @@ function KeepListContent({
|
|
|
2612
3127
|
itemCardProps,
|
|
2613
3128
|
layout = "list",
|
|
2614
3129
|
asChild = false,
|
|
3130
|
+
onKeyDown,
|
|
3131
|
+
onFocusCapture,
|
|
2615
3132
|
className,
|
|
2616
3133
|
...rootProps
|
|
2617
3134
|
}) {
|
|
2618
3135
|
const view = useKeepListView(query);
|
|
3136
|
+
const roving = useRovingTabIndex();
|
|
2619
3137
|
const { state } = view;
|
|
2620
3138
|
const body = getListBody(state, {
|
|
2621
3139
|
children,
|
|
@@ -2630,7 +3148,7 @@ function KeepListContent({
|
|
|
2630
3148
|
});
|
|
2631
3149
|
return renderRoot(
|
|
2632
3150
|
asChild,
|
|
2633
|
-
asChild &&
|
|
3151
|
+
asChild && isValidElement4(children) ? children : void 0,
|
|
2634
3152
|
{
|
|
2635
3153
|
...rootProps,
|
|
2636
3154
|
className,
|
|
@@ -2638,9 +3156,20 @@ function KeepListContent({
|
|
|
2638
3156
|
"data-layout": layout,
|
|
2639
3157
|
"aria-busy": state.isLoading || rootProps["aria-busy"],
|
|
2640
3158
|
"data-state": getListState(state),
|
|
2641
|
-
"data-loading": state.isLoading ? "true" : void 0
|
|
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);
|
|
3166
|
+
},
|
|
3167
|
+
onFocusCapture: (event) => {
|
|
3168
|
+
onFocusCapture?.(event);
|
|
3169
|
+
if (!event.defaultPrevented) roving.onFocusCapture(event);
|
|
3170
|
+
}
|
|
2642
3171
|
},
|
|
2643
|
-
body,
|
|
3172
|
+
/* @__PURE__ */ jsx13(KeepSearchQueryProvider, { query: query?.search?.query, children: body }),
|
|
2644
3173
|
"KeepList"
|
|
2645
3174
|
);
|
|
2646
3175
|
}
|
|
@@ -2655,437 +3184,280 @@ function getListBody(state, options) {
|
|
|
2655
3184
|
if (state.isLoading && !state.isHydrated) {
|
|
2656
3185
|
if (options.loading !== void 0) return resolveContent(options.loading, state);
|
|
2657
3186
|
const count = Number.isFinite(options.loadingCount) ? Math.max(0, Math.floor(options.loadingCount)) : 6;
|
|
2658
|
-
return /* @__PURE__ */
|
|
2659
|
-
/* @__PURE__ */
|
|
2660
|
-
/* @__PURE__ */
|
|
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) => (
|
|
2661
3190
|
// biome-ignore lint/suspicious/noArrayIndexKey: Static loading placeholders never reorder.
|
|
2662
|
-
/* @__PURE__ */
|
|
3191
|
+
/* @__PURE__ */ jsx13("li", { children: /* @__PURE__ */ jsx13(KeepItemCardSkeleton, { layout: options.layout }) }, index)
|
|
2663
3192
|
)) })
|
|
2664
3193
|
] });
|
|
2665
3194
|
}
|
|
2666
3195
|
if (state.isHydrated && state.items.length === 0) return resolveContent(options.empty, state);
|
|
2667
3196
|
if (typeof options.children === "function") return options.children(state);
|
|
2668
|
-
if (options.children !== void 0 && !
|
|
2669
|
-
return /* @__PURE__ */
|
|
2670
|
-
(item) => options.renderItem ? options.renderItem(item, state) : /* @__PURE__ */
|
|
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)
|
|
2671
3200
|
) });
|
|
2672
3201
|
}
|
|
2673
3202
|
|
|
2674
|
-
// src/
|
|
2675
|
-
import {
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
const { query, controlledValue, defaultValue, onChange, onValueChange } = options;
|
|
2682
|
-
const [uncontrolledValue, setUncontrolledValue] = useState5(defaultValue);
|
|
2683
|
-
const resolvedValue = controlledValue ?? uncontrolledValue;
|
|
2684
|
-
const list = useKeepList4({
|
|
2685
|
-
...query,
|
|
2686
|
-
tags: resolvedValue ? [...query?.tags ?? [], resolvedValue] : query?.tags
|
|
2687
|
-
});
|
|
2688
|
-
const select = useCallback3(
|
|
2689
|
-
(tag) => {
|
|
2690
|
-
if (controlledValue === void 0) setUncontrolledValue(tag);
|
|
2691
|
-
onChange?.(tag);
|
|
2692
|
-
onValueChange?.(tag);
|
|
2693
|
-
},
|
|
2694
|
-
[controlledValue, onChange, onValueChange]
|
|
2695
|
-
);
|
|
2696
|
-
const state = useMemo3(
|
|
2697
|
-
() => ({ tags: list.tags, tagCounts: list.tagCounts, value: resolvedValue, select }),
|
|
2698
|
-
[list.tagCounts, list.tags, resolvedValue, select]
|
|
2699
|
-
);
|
|
2700
|
-
return {
|
|
2701
|
-
state,
|
|
2702
|
-
isLoading: list.isLoading,
|
|
2703
|
-
labels: { all: useUiLabel("allTags"), aria: useUiLabel("filterTags") }
|
|
2704
|
-
};
|
|
3203
|
+
// src/features/collection/KeepCollection.tsx
|
|
3204
|
+
import { jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3205
|
+
function KeepCollection(props) {
|
|
3206
|
+
const { fallback, onBoundaryError, boundaryResetKey, ...collectionProps } = props;
|
|
3207
|
+
const content = /* @__PURE__ */ jsx14(KeepCollectionContent, { ...collectionProps });
|
|
3208
|
+
if (fallback === void 0 && onBoundaryError === void 0) return content;
|
|
3209
|
+
return /* @__PURE__ */ jsx14(KeepErrorBoundary2, { fallback, onError: onBoundaryError, resetKey: boundaryResetKey, children: content });
|
|
2705
3210
|
}
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
asChild = false,
|
|
3211
|
+
function KeepCollectionContent({
|
|
3212
|
+
query = {},
|
|
3213
|
+
pageSize = 20,
|
|
3214
|
+
layout = "list",
|
|
3215
|
+
urlSync = false,
|
|
3216
|
+
urlAdapter,
|
|
3217
|
+
features,
|
|
3218
|
+
renderItem,
|
|
3219
|
+
itemCardProps,
|
|
3220
|
+
loading,
|
|
3221
|
+
renderLoading,
|
|
3222
|
+
loadingCount,
|
|
3223
|
+
empty,
|
|
3224
|
+
error,
|
|
2721
3225
|
className,
|
|
2722
3226
|
...rootProps
|
|
2723
3227
|
}) {
|
|
2724
|
-
const view =
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
/* @__PURE__ */ jsx11("legend", { children: ariaLabel ?? view.labels.aria }),
|
|
2728
|
-
/* @__PURE__ */ jsx11(
|
|
2729
|
-
"button",
|
|
2730
|
-
{
|
|
2731
|
-
type: "button",
|
|
2732
|
-
"data-keep-action": "filter-all-tags",
|
|
2733
|
-
"aria-pressed": view.state.value === void 0,
|
|
2734
|
-
onClick: () => view.state.select(),
|
|
2735
|
-
children: allLabel ?? view.labels.all
|
|
2736
|
-
}
|
|
2737
|
-
),
|
|
2738
|
-
view.state.tags.map((tag) => /* @__PURE__ */ jsxs7(
|
|
2739
|
-
"button",
|
|
2740
|
-
{
|
|
2741
|
-
type: "button",
|
|
2742
|
-
"data-keep-action": "filter-tag",
|
|
2743
|
-
"aria-pressed": view.state.value === tag,
|
|
2744
|
-
onClick: () => view.state.select(tag),
|
|
2745
|
-
children: [
|
|
2746
|
-
renderTag ? renderTag(tag, view.state.tagCounts[tag] ?? 0, view.state.value === tag) : tag,
|
|
2747
|
-
/* @__PURE__ */ jsxs7("span", { children: [
|
|
2748
|
-
" (",
|
|
2749
|
-
view.state.tagCounts[tag] ?? 0,
|
|
2750
|
-
")"
|
|
2751
|
-
] })
|
|
2752
|
-
]
|
|
2753
|
-
},
|
|
2754
|
-
tag
|
|
2755
|
-
))
|
|
2756
|
-
] });
|
|
2757
|
-
return renderRoot(
|
|
2758
|
-
asChild,
|
|
2759
|
-
isValidElement4(children) ? children : void 0,
|
|
3228
|
+
const view = useKeepCollection({ query, pageSize, urlSync, urlAdapter, features });
|
|
3229
|
+
return /* @__PURE__ */ jsxs11(
|
|
3230
|
+
"section",
|
|
2760
3231
|
{
|
|
2761
3232
|
...rootProps,
|
|
2762
3233
|
className,
|
|
2763
|
-
"data-keepkit": "
|
|
2764
|
-
"data-
|
|
2765
|
-
"
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
3234
|
+
"data-keepkit": "collection",
|
|
3235
|
+
"data-layout": layout,
|
|
3236
|
+
"aria-busy": view.list.isLoading || view.list.isMutating || rootProps["aria-busy"],
|
|
3237
|
+
"data-state": getCollectionState(view.list),
|
|
3238
|
+
"data-loading": view.list.isLoading || view.list.isMutating ? "true" : void 0,
|
|
3239
|
+
children: [
|
|
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
|
|
3244
|
+
] }),
|
|
3245
|
+
/* @__PURE__ */ jsx14(
|
|
3246
|
+
KeepList,
|
|
3247
|
+
{
|
|
3248
|
+
query: view.resolvedQuery,
|
|
3249
|
+
renderItem,
|
|
3250
|
+
itemCardProps,
|
|
3251
|
+
layout,
|
|
3252
|
+
loading,
|
|
3253
|
+
renderLoading,
|
|
3254
|
+
loadingCount,
|
|
3255
|
+
empty,
|
|
3256
|
+
error
|
|
3257
|
+
}
|
|
3258
|
+
),
|
|
3259
|
+
view.enabled.pagination ? /* @__PURE__ */ jsx14(
|
|
3260
|
+
KeepPagination,
|
|
3261
|
+
{
|
|
3262
|
+
totalCount: view.list.totalCount,
|
|
3263
|
+
pageSize: view.resolvedPageSize,
|
|
3264
|
+
page: view.list.page,
|
|
3265
|
+
onPageChange: view.setPage
|
|
3266
|
+
}
|
|
3267
|
+
) : null,
|
|
3268
|
+
view.enabled.bulkActions ? /* @__PURE__ */ jsx14(KeepBulkActions, { query: view.resolvedQuery }) : null
|
|
3269
|
+
]
|
|
3270
|
+
}
|
|
3271
|
+
);
|
|
3272
|
+
}
|
|
3273
|
+
function getCollectionState(list) {
|
|
3274
|
+
if (list.error && list.items.length === 0) return "error";
|
|
3275
|
+
if (list.isLoading && !list.isHydrated) return "loading";
|
|
3276
|
+
if (list.isHydrated && list.items.length === 0) return "empty";
|
|
3277
|
+
return "ready";
|
|
3278
|
+
}
|
|
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",
|
|
3288
|
+
{
|
|
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
|
|
3304
|
+
}
|
|
3305
|
+
)
|
|
2769
3306
|
);
|
|
2770
3307
|
}
|
|
2771
3308
|
|
|
2772
|
-
// src/
|
|
2773
|
-
import {
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
3309
|
+
// src/features/collection/KeepReorderableList.tsx
|
|
3310
|
+
import { useState as useState9 } from "react";
|
|
3311
|
+
import { jsx as jsx16 } from "react/jsx-runtime";
|
|
3312
|
+
function KeepReorderableList({
|
|
3313
|
+
items,
|
|
3314
|
+
onReorder,
|
|
3315
|
+
renderItem,
|
|
3316
|
+
itemLabel = (_item, index) => `Move item ${index + 1}`,
|
|
3317
|
+
...props
|
|
3318
|
+
}) {
|
|
3319
|
+
const [draggedId, setDraggedId] = useState9(null);
|
|
3320
|
+
const [dropTargetIndex, setDropTargetIndex] = useState9(null);
|
|
3321
|
+
const ids = items.map((item) => item.id);
|
|
3322
|
+
function commit(nextIds) {
|
|
3323
|
+
void onReorder(nextIds);
|
|
3324
|
+
}
|
|
3325
|
+
function move(index, targetIndex) {
|
|
3326
|
+
if (targetIndex < 0 || targetIndex >= ids.length || targetIndex === index) return;
|
|
3327
|
+
const next = [...ids];
|
|
3328
|
+
const [id] = next.splice(index, 1);
|
|
3329
|
+
if (id === void 0) return;
|
|
3330
|
+
next.splice(targetIndex, 0, id);
|
|
3331
|
+
commit(next);
|
|
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
|
+
}
|
|
3345
|
+
return /* @__PURE__ */ jsx16("ul", { ...props, "data-keepkit": "reorderable-list", children: items.map((item, index) => {
|
|
3346
|
+
const moveUp = () => move(index, index - 1);
|
|
3347
|
+
const moveDown = () => move(index, index + 1);
|
|
3348
|
+
const onHandleKeyDown = (event) => {
|
|
3349
|
+
if (event.key === "ArrowUp") {
|
|
3350
|
+
event.preventDefault();
|
|
3351
|
+
moveUp();
|
|
3352
|
+
} else if (event.key === "ArrowDown") {
|
|
3353
|
+
event.preventDefault();
|
|
3354
|
+
moveDown();
|
|
3355
|
+
}
|
|
3356
|
+
};
|
|
3357
|
+
const state = {
|
|
3358
|
+
index,
|
|
3359
|
+
isDragging: draggedId === item.id,
|
|
3360
|
+
moveUp,
|
|
3361
|
+
moveDown,
|
|
3362
|
+
dragHandleProps: {
|
|
3363
|
+
role: "button",
|
|
3364
|
+
tabIndex: 0,
|
|
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
|
+
}) });
|
|
3409
|
+
}
|
|
3410
|
+
|
|
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;
|
|
2783
3434
|
}
|
|
2784
|
-
|
|
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);
|
|
2785
3439
|
return () => window.clearTimeout(timer);
|
|
2786
|
-
}, [debounceMs,
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
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);
|
|
2793
3453
|
};
|
|
2794
|
-
}
|
|
2795
|
-
function useKeepSortSelect(options) {
|
|
2796
|
-
const { controlledValue, defaultValue, onValueChange } = options;
|
|
2797
|
-
const [uncontrolledValue, setUncontrolledValue] = useState6(defaultValue);
|
|
2798
|
-
const value = controlledValue ?? uncontrolledValue;
|
|
2799
3454
|
return {
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
if (
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
},
|
|
2807
|
-
labels: {
|
|
2808
|
-
sort: useUiLabel("sort"),
|
|
2809
|
-
updatedNewest: useUiLabel("updatedNewest"),
|
|
2810
|
-
updatedOldest: useUiLabel("updatedOldest"),
|
|
2811
|
-
savedNewest: useUiLabel("savedNewest"),
|
|
2812
|
-
savedOldest: useUiLabel("savedOldest")
|
|
2813
|
-
}
|
|
2814
|
-
};
|
|
2815
|
-
}
|
|
2816
|
-
function useKeepPagination(options) {
|
|
2817
|
-
const { totalCount, pageSize, page, maxPageButtons, onPageChange } = options;
|
|
2818
|
-
const pageCount = Math.max(1, Math.ceil(totalCount / Math.max(1, pageSize)));
|
|
2819
|
-
const currentPage = Math.min(Math.max(1, page), pageCount);
|
|
2820
|
-
const goToPage = (nextPage) => {
|
|
2821
|
-
const next = Math.min(Math.max(1, nextPage), pageCount);
|
|
2822
|
-
onPageChange?.(next, (next - 1) * pageSize);
|
|
2823
|
-
};
|
|
2824
|
-
return {
|
|
2825
|
-
pageCount,
|
|
2826
|
-
currentPage,
|
|
2827
|
-
goToPage,
|
|
2828
|
-
visiblePages: getVisiblePages(currentPage, pageCount, Math.max(1, maxPageButtons)),
|
|
2829
|
-
labels: {
|
|
2830
|
-
previous: useUiLabel("previousPage"),
|
|
2831
|
-
next: useUiLabel("nextPage"),
|
|
2832
|
-
page: useUiLabel("page"),
|
|
2833
|
-
pagination: useUiLabel("pagination")
|
|
2834
|
-
}
|
|
2835
|
-
};
|
|
2836
|
-
}
|
|
2837
|
-
function getVisiblePages(currentPage, pageCount, maxPageButtons) {
|
|
2838
|
-
if (pageCount <= maxPageButtons) return Array.from({ length: pageCount }, (_, index) => index + 1);
|
|
2839
|
-
const half = Math.floor(maxPageButtons / 2);
|
|
2840
|
-
const start = Math.min(Math.max(1, currentPage - half), pageCount - maxPageButtons + 1);
|
|
2841
|
-
return Array.from({ length: maxPageButtons }, (_, index) => start + index);
|
|
2842
|
-
}
|
|
2843
|
-
|
|
2844
|
-
// src/query-controls.tsx
|
|
2845
|
-
import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2846
|
-
function KeepSearchInput({
|
|
2847
|
-
value: controlledValue,
|
|
2848
|
-
defaultValue = "",
|
|
2849
|
-
debounceMs = 300,
|
|
2850
|
-
onValueChange,
|
|
2851
|
-
"aria-label": ariaLabel,
|
|
2852
|
-
placeholder,
|
|
2853
|
-
...props
|
|
2854
|
-
}) {
|
|
2855
|
-
const view = useKeepSearchInput({ controlledValue, defaultValue, debounceMs, onValueChange });
|
|
2856
|
-
return /* @__PURE__ */ jsx12(
|
|
2857
|
-
"input",
|
|
2858
|
-
{
|
|
2859
|
-
...props,
|
|
2860
|
-
"data-keepkit": "search-input",
|
|
2861
|
-
"data-keep-action": "search",
|
|
2862
|
-
type: "search",
|
|
2863
|
-
value: view.value,
|
|
2864
|
-
"data-state": view.value ? "active" : "idle",
|
|
2865
|
-
"data-disabled": props.disabled ? "true" : void 0,
|
|
2866
|
-
"aria-label": ariaLabel ?? view.label,
|
|
2867
|
-
placeholder: placeholder ?? view.label,
|
|
2868
|
-
onChange: view.change
|
|
2869
|
-
}
|
|
2870
|
-
);
|
|
2871
|
-
}
|
|
2872
|
-
function KeepSortSelect({
|
|
2873
|
-
value: controlledValue,
|
|
2874
|
-
defaultValue = "updatedAt:desc",
|
|
2875
|
-
onValueChange,
|
|
2876
|
-
"aria-label": ariaLabel,
|
|
2877
|
-
children,
|
|
2878
|
-
...props
|
|
2879
|
-
}) {
|
|
2880
|
-
const view = useKeepSortSelect({ controlledValue, defaultValue, onValueChange });
|
|
2881
|
-
const options = children ?? /* @__PURE__ */ jsxs8(Fragment5, { children: [
|
|
2882
|
-
/* @__PURE__ */ jsx12("option", { value: "updatedAt:desc", children: view.labels.updatedNewest }),
|
|
2883
|
-
/* @__PURE__ */ jsx12("option", { value: "updatedAt:asc", children: view.labels.updatedOldest }),
|
|
2884
|
-
/* @__PURE__ */ jsx12("option", { value: "savedAt:desc", children: view.labels.savedNewest }),
|
|
2885
|
-
/* @__PURE__ */ jsx12("option", { value: "savedAt:asc", children: view.labels.savedOldest })
|
|
2886
|
-
] });
|
|
2887
|
-
return /* @__PURE__ */ jsx12(
|
|
2888
|
-
"select",
|
|
2889
|
-
{
|
|
2890
|
-
...props,
|
|
2891
|
-
"data-keepkit": "sort-select",
|
|
2892
|
-
"data-keep-action": "sort",
|
|
2893
|
-
value: view.value,
|
|
2894
|
-
"data-state": "selected",
|
|
2895
|
-
"data-disabled": props.disabled ? "true" : void 0,
|
|
2896
|
-
"aria-label": ariaLabel ?? view.labels.sort,
|
|
2897
|
-
onChange: view.change,
|
|
2898
|
-
children: options
|
|
2899
|
-
}
|
|
2900
|
-
);
|
|
2901
|
-
}
|
|
2902
|
-
function KeepPagination({
|
|
2903
|
-
totalCount,
|
|
2904
|
-
pageSize,
|
|
2905
|
-
page = 1,
|
|
2906
|
-
maxPageButtons = 7,
|
|
2907
|
-
onPageChange,
|
|
2908
|
-
render,
|
|
2909
|
-
...props
|
|
2910
|
-
}) {
|
|
2911
|
-
const view = useKeepPagination({ totalCount, pageSize, page, maxPageButtons, onPageChange });
|
|
2912
|
-
const navProps = {
|
|
2913
|
-
...props,
|
|
2914
|
-
"data-keepkit": "pagination",
|
|
2915
|
-
"aria-label": props["aria-label"] ?? view.labels.pagination,
|
|
2916
|
-
"data-state": view.pageCount > 1 ? "active" : "idle"
|
|
2917
|
-
};
|
|
2918
|
-
if (render)
|
|
2919
|
-
return /* @__PURE__ */ jsx12("nav", { ...navProps, children: render({ page: view.currentPage, pageCount: view.pageCount, goToPage: view.goToPage }) });
|
|
2920
|
-
return /* @__PURE__ */ jsxs8("nav", { ...navProps, children: [
|
|
2921
|
-
/* @__PURE__ */ jsx12(
|
|
2922
|
-
"button",
|
|
2923
|
-
{
|
|
2924
|
-
type: "button",
|
|
2925
|
-
"data-keep-action": "previous-page",
|
|
2926
|
-
onClick: () => view.goToPage(view.currentPage - 1),
|
|
2927
|
-
disabled: view.currentPage <= 1,
|
|
2928
|
-
children: view.labels.previous
|
|
2929
|
-
}
|
|
2930
|
-
),
|
|
2931
|
-
view.visiblePages.map((nextPage) => /* @__PURE__ */ jsx12(
|
|
2932
|
-
"button",
|
|
2933
|
-
{
|
|
2934
|
-
type: "button",
|
|
2935
|
-
"data-keep-action": "select-page",
|
|
2936
|
-
"aria-current": nextPage === view.currentPage ? "page" : void 0,
|
|
2937
|
-
"aria-label": `${view.labels.page} ${nextPage}`,
|
|
2938
|
-
onClick: () => view.goToPage(nextPage),
|
|
2939
|
-
children: nextPage
|
|
2940
|
-
},
|
|
2941
|
-
nextPage
|
|
2942
|
-
)),
|
|
2943
|
-
/* @__PURE__ */ jsx12(
|
|
2944
|
-
"button",
|
|
2945
|
-
{
|
|
2946
|
-
type: "button",
|
|
2947
|
-
"data-keep-action": "next-page",
|
|
2948
|
-
onClick: () => view.goToPage(view.currentPage + 1),
|
|
2949
|
-
disabled: view.currentPage >= view.pageCount,
|
|
2950
|
-
children: view.labels.next
|
|
2951
|
-
}
|
|
2952
|
-
)
|
|
2953
|
-
] });
|
|
2954
|
-
}
|
|
2955
|
-
|
|
2956
|
-
// src/KeepCollection.tsx
|
|
2957
|
-
import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2958
|
-
function KeepCollection(props) {
|
|
2959
|
-
const { fallback, onBoundaryError, boundaryResetKey, ...collectionProps } = props;
|
|
2960
|
-
const content = /* @__PURE__ */ jsx13(KeepCollectionContent, { ...collectionProps });
|
|
2961
|
-
if (fallback === void 0 && onBoundaryError === void 0) return content;
|
|
2962
|
-
return /* @__PURE__ */ jsx13(KeepErrorBoundary2, { fallback, onError: onBoundaryError, resetKey: boundaryResetKey, children: content });
|
|
2963
|
-
}
|
|
2964
|
-
function KeepCollectionContent({
|
|
2965
|
-
query = {},
|
|
2966
|
-
pageSize = 20,
|
|
2967
|
-
layout = "list",
|
|
2968
|
-
urlSync = false,
|
|
2969
|
-
urlAdapter,
|
|
2970
|
-
features,
|
|
2971
|
-
renderItem,
|
|
2972
|
-
itemCardProps,
|
|
2973
|
-
loading,
|
|
2974
|
-
renderLoading,
|
|
2975
|
-
loadingCount,
|
|
2976
|
-
empty,
|
|
2977
|
-
error,
|
|
2978
|
-
className,
|
|
2979
|
-
...rootProps
|
|
2980
|
-
}) {
|
|
2981
|
-
const view = useKeepCollection({ query, pageSize, urlSync, urlAdapter, features });
|
|
2982
|
-
return /* @__PURE__ */ jsxs9(
|
|
2983
|
-
"section",
|
|
2984
|
-
{
|
|
2985
|
-
...rootProps,
|
|
2986
|
-
className,
|
|
2987
|
-
"data-keepkit": "collection",
|
|
2988
|
-
"data-layout": layout,
|
|
2989
|
-
"aria-busy": view.list.isLoading || view.list.isMutating || rootProps["aria-busy"],
|
|
2990
|
-
"data-state": getCollectionState(view.list),
|
|
2991
|
-
"data-loading": view.list.isLoading || view.list.isMutating ? "true" : void 0,
|
|
2992
|
-
children: [
|
|
2993
|
-
/* @__PURE__ */ jsxs9("div", { children: [
|
|
2994
|
-
view.enabled.search ? /* @__PURE__ */ jsx13(KeepSearchInput, { value: view.searchValue, onValueChange: view.setSearchValue }) : null,
|
|
2995
|
-
view.enabled.sort ? /* @__PURE__ */ jsx13(KeepSortSelect, { value: view.sortValue, onValueChange: view.setSortValue }) : null,
|
|
2996
|
-
view.enabled.tagFilter ? /* @__PURE__ */ jsx13(KeepTagFilter, { query, value: view.tag, onValueChange: view.setTag }) : null
|
|
2997
|
-
] }),
|
|
2998
|
-
/* @__PURE__ */ jsx13(
|
|
2999
|
-
KeepList,
|
|
3000
|
-
{
|
|
3001
|
-
query: view.resolvedQuery,
|
|
3002
|
-
renderItem,
|
|
3003
|
-
itemCardProps,
|
|
3004
|
-
layout,
|
|
3005
|
-
loading,
|
|
3006
|
-
renderLoading,
|
|
3007
|
-
loadingCount,
|
|
3008
|
-
empty,
|
|
3009
|
-
error
|
|
3010
|
-
}
|
|
3011
|
-
),
|
|
3012
|
-
view.enabled.pagination ? /* @__PURE__ */ jsx13(
|
|
3013
|
-
KeepPagination,
|
|
3014
|
-
{
|
|
3015
|
-
totalCount: view.list.totalCount,
|
|
3016
|
-
pageSize: view.resolvedPageSize,
|
|
3017
|
-
page: view.list.page,
|
|
3018
|
-
onPageChange: view.setPage
|
|
3019
|
-
}
|
|
3020
|
-
) : null,
|
|
3021
|
-
view.enabled.bulkActions ? /* @__PURE__ */ jsx13(KeepBulkActions, { query: view.resolvedQuery }) : null
|
|
3022
|
-
]
|
|
3023
|
-
}
|
|
3024
|
-
);
|
|
3025
|
-
}
|
|
3026
|
-
function getCollectionState(list) {
|
|
3027
|
-
if (list.error && list.items.length === 0) return "error";
|
|
3028
|
-
if (list.isLoading && !list.isHydrated) return "loading";
|
|
3029
|
-
if (list.isHydrated && list.items.length === 0) return "empty";
|
|
3030
|
-
return "ready";
|
|
3031
|
-
}
|
|
3032
|
-
|
|
3033
|
-
// src/KeepLayout.tsx
|
|
3034
|
-
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
3035
|
-
function KeepLayout({ layout = "list", children, ...props }) {
|
|
3036
|
-
return /* @__PURE__ */ jsx14("div", { ...props, "data-keepkit": "layout", "data-layout": layout, children });
|
|
3037
|
-
}
|
|
3038
|
-
|
|
3039
|
-
// src/KeepNoteEditor.tsx
|
|
3040
|
-
import { isValidElement as isValidElement5 } from "react";
|
|
3041
|
-
|
|
3042
|
-
// src/hooks/useKeepNoteEditor.ts
|
|
3043
|
-
import { useKeepItem as useKeepItem3 } from "@keepkit/core/react";
|
|
3044
|
-
import { useCallback as useCallback4, useEffect as useEffect4, useRef as useRef4, useState as useState7 } from "react";
|
|
3045
|
-
function useKeepNoteEditor({ item, debounceMs, onSaved, onSaveError }) {
|
|
3046
|
-
const itemState = useKeepItem3(item);
|
|
3047
|
-
const { error, isMutating, item: savedItem, updateNote } = itemState;
|
|
3048
|
-
const [note, setNote] = useState7(item.note ?? "");
|
|
3049
|
-
const baselineNote = savedItem?.note ?? item.note ?? "";
|
|
3050
|
-
const isDirty = note !== baselineNote;
|
|
3051
|
-
const lastSavedNoteRef = useRef4(void 0);
|
|
3052
|
-
useEffect4(() => setNote(baselineNote), [baselineNote]);
|
|
3053
|
-
const save = useCallback4(async () => {
|
|
3054
|
-
const nextNote = note.trim() || void 0;
|
|
3055
|
-
try {
|
|
3056
|
-
await updateNote(nextNote);
|
|
3057
|
-
lastSavedNoteRef.current = note;
|
|
3058
|
-
onSaved?.(nextNote);
|
|
3059
|
-
} catch (cause) {
|
|
3060
|
-
onSaveError?.(cause);
|
|
3061
|
-
throw cause;
|
|
3062
|
-
}
|
|
3063
|
-
}, [note, onSaveError, onSaved, updateNote]);
|
|
3064
|
-
useEffect4(() => {
|
|
3065
|
-
if (!isDirty || debounceMs <= 0 || lastSavedNoteRef.current === note) return;
|
|
3066
|
-
const timer = window.setTimeout(() => void save().catch(() => void 0), debounceMs);
|
|
3067
|
-
return () => window.clearTimeout(timer);
|
|
3068
|
-
}, [debounceMs, isDirty, note, save]);
|
|
3069
|
-
const state = {
|
|
3070
|
-
item,
|
|
3071
|
-
note,
|
|
3072
|
-
setNote,
|
|
3073
|
-
isDirty,
|
|
3074
|
-
isSaving: isMutating,
|
|
3075
|
-
error,
|
|
3076
|
-
save
|
|
3077
|
-
};
|
|
3078
|
-
const submit = (event) => {
|
|
3079
|
-
event.preventDefault();
|
|
3080
|
-
void save().catch(() => void 0);
|
|
3081
|
-
};
|
|
3082
|
-
return {
|
|
3083
|
-
state,
|
|
3084
|
-
submit,
|
|
3085
|
-
handleKeyDown: (event) => {
|
|
3086
|
-
if (event.key !== "Enter" || !event.ctrlKey && !event.metaKey) return;
|
|
3087
|
-
event.preventDefault();
|
|
3088
|
-
void save().catch(() => void 0);
|
|
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);
|
|
3089
3461
|
},
|
|
3090
3462
|
labels: {
|
|
3091
3463
|
note: useUiLabel("note"),
|
|
@@ -3095,8 +3467,8 @@ function useKeepNoteEditor({ item, debounceMs, onSaved, onSaveError }) {
|
|
|
3095
3467
|
};
|
|
3096
3468
|
}
|
|
3097
3469
|
|
|
3098
|
-
// src/KeepNoteEditor.tsx
|
|
3099
|
-
import { Fragment as
|
|
3470
|
+
// src/features/editor/KeepNoteEditor.tsx
|
|
3471
|
+
import { Fragment as Fragment8, jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3100
3472
|
function KeepNoteEditor({
|
|
3101
3473
|
item,
|
|
3102
3474
|
label,
|
|
@@ -3114,10 +3486,10 @@ function KeepNoteEditor({
|
|
|
3114
3486
|
const view = useKeepNoteEditor({ item, debounceMs, onSaved, onSaveError });
|
|
3115
3487
|
const { error, isDirty, isSaving, note, setNote } = view.state;
|
|
3116
3488
|
const contentChildren = asChild && isValidElement5(children) ? void 0 : children;
|
|
3117
|
-
const body = render ? render(view.state) : typeof contentChildren === "function" ? contentChildren(view.state) : contentChildren ?? /* @__PURE__ */
|
|
3118
|
-
/* @__PURE__ */
|
|
3489
|
+
const body = render ? render(view.state) : typeof contentChildren === "function" ? contentChildren(view.state) : contentChildren ?? /* @__PURE__ */ jsxs12(Fragment8, { children: [
|
|
3490
|
+
/* @__PURE__ */ jsxs12("label", { children: [
|
|
3119
3491
|
label ?? view.labels.note,
|
|
3120
|
-
/* @__PURE__ */
|
|
3492
|
+
/* @__PURE__ */ jsx17(
|
|
3121
3493
|
"textarea",
|
|
3122
3494
|
{
|
|
3123
3495
|
"data-keep-action": "edit-note",
|
|
@@ -3129,10 +3501,10 @@ function KeepNoteEditor({
|
|
|
3129
3501
|
}
|
|
3130
3502
|
)
|
|
3131
3503
|
] }),
|
|
3132
|
-
/* @__PURE__ */
|
|
3504
|
+
/* @__PURE__ */ jsx17("button", { type: "submit", "data-keep-action": "save-note", disabled: isSaving, "aria-busy": isSaving, children: saveLabel ?? view.labels.save })
|
|
3133
3505
|
] });
|
|
3134
3506
|
if (!asChild) {
|
|
3135
|
-
return /* @__PURE__ */
|
|
3507
|
+
return /* @__PURE__ */ jsxs12(
|
|
3136
3508
|
"form",
|
|
3137
3509
|
{
|
|
3138
3510
|
...formProps,
|
|
@@ -3145,7 +3517,7 @@ function KeepNoteEditor({
|
|
|
3145
3517
|
"data-disabled": isSaving ? "true" : void 0,
|
|
3146
3518
|
children: [
|
|
3147
3519
|
body,
|
|
3148
|
-
error ? /* @__PURE__ */
|
|
3520
|
+
error ? /* @__PURE__ */ jsx17("p", { role: "alert", children: getErrorMessage3(error, view.labels.error) }) : null
|
|
3149
3521
|
]
|
|
3150
3522
|
}
|
|
3151
3523
|
);
|
|
@@ -3167,20 +3539,422 @@ function KeepNoteEditor({
|
|
|
3167
3539
|
"KeepNoteEditor"
|
|
3168
3540
|
);
|
|
3169
3541
|
}
|
|
3170
|
-
function getErrorMessage3(error, fallback) {
|
|
3171
|
-
return error instanceof Error ? error.message : fallback;
|
|
3172
|
-
}
|
|
3542
|
+
function getErrorMessage3(error, fallback) {
|
|
3543
|
+
return error instanceof Error ? error.message : fallback;
|
|
3544
|
+
}
|
|
3545
|
+
|
|
3546
|
+
// src/features/editor/hooks/useKeepTagEditor.ts
|
|
3547
|
+
import { useKeepItem as useKeepItem4 } from "@keepkit/core/react";
|
|
3548
|
+
import { useCallback as useCallback5, useEffect as useEffect8, useState as useState11 } from "react";
|
|
3549
|
+
function useKeepTagEditor({ item, onSaved, onSaveError }) {
|
|
3550
|
+
const itemState = useKeepItem4(item);
|
|
3551
|
+
const [tags, setTags] = useState11(item.tags ?? []);
|
|
3552
|
+
const [input, setInput] = useState11("");
|
|
3553
|
+
useEffect8(() => setTags(itemState.item?.tags ?? item.tags ?? []), [item.tags, itemState.item?.tags]);
|
|
3554
|
+
const save = useCallback5(async () => {
|
|
3555
|
+
const nextTags = normalizeUiTags(tags);
|
|
3556
|
+
try {
|
|
3557
|
+
await itemState.updateTags(nextTags);
|
|
3558
|
+
setTags(nextTags);
|
|
3559
|
+
onSaved?.(nextTags);
|
|
3560
|
+
} catch (cause) {
|
|
3561
|
+
onSaveError?.(cause);
|
|
3562
|
+
throw cause;
|
|
3563
|
+
}
|
|
3564
|
+
}, [itemState, onSaveError, onSaved, tags]);
|
|
3565
|
+
const addTag = (tag) => {
|
|
3566
|
+
setTags(normalizeUiTags([...tags, tag]));
|
|
3567
|
+
setInput("");
|
|
3568
|
+
};
|
|
3569
|
+
const state = { tags, setTags, save, isSaving: itemState.isMutating };
|
|
3570
|
+
return {
|
|
3571
|
+
state,
|
|
3572
|
+
input,
|
|
3573
|
+
setInput,
|
|
3574
|
+
error: itemState.error,
|
|
3575
|
+
handleInputKeyDown: (event) => {
|
|
3576
|
+
if (event.key === "Enter") {
|
|
3577
|
+
if (event.nativeEvent.isComposing) return;
|
|
3578
|
+
event.preventDefault();
|
|
3579
|
+
if (input.trim()) addTag(input);
|
|
3580
|
+
} else if (event.key === "Backspace" && input.length === 0 && tags.length > 0) {
|
|
3581
|
+
event.preventDefault();
|
|
3582
|
+
setTags(tags.slice(0, -1));
|
|
3583
|
+
}
|
|
3584
|
+
},
|
|
3585
|
+
removeTag: (tag) => setTags(tags.filter((current) => current !== tag)),
|
|
3586
|
+
submit: (event) => {
|
|
3587
|
+
event.preventDefault();
|
|
3588
|
+
void save().catch(() => void 0);
|
|
3589
|
+
},
|
|
3590
|
+
labels: {
|
|
3591
|
+
tags: useUiLabel("tagsToApply"),
|
|
3592
|
+
remove: useUiLabel("remove"),
|
|
3593
|
+
apply: useUiLabel("applyTags"),
|
|
3594
|
+
error: useUiLabel("error")
|
|
3595
|
+
}
|
|
3596
|
+
};
|
|
3597
|
+
}
|
|
3598
|
+
|
|
3599
|
+
// src/features/editor/KeepTagEditor.tsx
|
|
3600
|
+
import { Fragment as Fragment9, jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3601
|
+
function KeepTagEditor({
|
|
3602
|
+
item,
|
|
3603
|
+
availableTags = [],
|
|
3604
|
+
onSaved,
|
|
3605
|
+
onSaveError,
|
|
3606
|
+
render,
|
|
3607
|
+
...props
|
|
3608
|
+
}) {
|
|
3609
|
+
const view = useKeepTagEditor({ item, onSaved, onSaveError });
|
|
3610
|
+
const { isSaving, tags } = view.state;
|
|
3611
|
+
const body = render ? render(view.state) : /* @__PURE__ */ jsxs13(Fragment9, { children: [
|
|
3612
|
+
/* @__PURE__ */ jsxs13("label", { children: [
|
|
3613
|
+
view.labels.tags,
|
|
3614
|
+
/* @__PURE__ */ jsx18(
|
|
3615
|
+
"input",
|
|
3616
|
+
{
|
|
3617
|
+
"data-keep-action": "edit-tags",
|
|
3618
|
+
value: view.input,
|
|
3619
|
+
list: availableTags.length > 0 ? `keep-tags-${item.id}` : void 0,
|
|
3620
|
+
onChange: (event) => view.setInput(event.currentTarget.value),
|
|
3621
|
+
onKeyDown: view.handleInputKeyDown
|
|
3622
|
+
}
|
|
3623
|
+
)
|
|
3624
|
+
] }),
|
|
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: [
|
|
3627
|
+
tag,
|
|
3628
|
+
/* @__PURE__ */ jsx18("button", { type: "button", "data-keep-action": "remove-tag", onClick: () => view.removeTag(tag), children: view.labels.remove })
|
|
3629
|
+
] }, tag)) }),
|
|
3630
|
+
/* @__PURE__ */ jsx18("button", { type: "submit", "data-keep-action": "apply-tags", disabled: isSaving, "aria-busy": isSaving, children: view.labels.apply })
|
|
3631
|
+
] });
|
|
3632
|
+
return /* @__PURE__ */ jsxs13(
|
|
3633
|
+
"form",
|
|
3634
|
+
{
|
|
3635
|
+
...props,
|
|
3636
|
+
onSubmit: view.submit,
|
|
3637
|
+
"aria-busy": isSaving || props["aria-busy"],
|
|
3638
|
+
"data-keepkit": "tag-editor",
|
|
3639
|
+
"data-state": view.error ? "error" : isSaving ? "saving" : "idle",
|
|
3640
|
+
"data-loading": isSaving ? "true" : void 0,
|
|
3641
|
+
"data-disabled": isSaving ? "true" : void 0,
|
|
3642
|
+
children: [
|
|
3643
|
+
body,
|
|
3644
|
+
view.error ? /* @__PURE__ */ jsx18("p", { role: "alert", children: getErrorMessage4(view.error, view.labels.error) }) : null
|
|
3645
|
+
]
|
|
3646
|
+
}
|
|
3647
|
+
);
|
|
3648
|
+
}
|
|
3649
|
+
function getErrorMessage4(error, fallback) {
|
|
3650
|
+
return error instanceof Error ? error.message : fallback;
|
|
3651
|
+
}
|
|
3652
|
+
|
|
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
|
|
3674
|
+
import { useKeepNavigator } from "@keepkit/core/react";
|
|
3675
|
+
import { useId } from "react";
|
|
3676
|
+
|
|
3677
|
+
// src/features/navigation/hooks/useKeepTourShortcuts.ts
|
|
3678
|
+
import { useEffect as useEffect9 } from "react";
|
|
3679
|
+
function useKeepTourShortcuts({
|
|
3680
|
+
onNext,
|
|
3681
|
+
onPrev,
|
|
3682
|
+
enabled = true,
|
|
3683
|
+
allowInEditable = false,
|
|
3684
|
+
preventDefault = true,
|
|
3685
|
+
nextKeys = ["j", "]"],
|
|
3686
|
+
prevKeys = ["k", "["],
|
|
3687
|
+
onError
|
|
3688
|
+
}) {
|
|
3689
|
+
useEffect9(() => {
|
|
3690
|
+
if (!enabled) return;
|
|
3691
|
+
const handleKeyDown = (event) => {
|
|
3692
|
+
if (!allowInEditable && isEditableTarget(event.target)) return;
|
|
3693
|
+
if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return;
|
|
3694
|
+
const key = event.key.toLocaleLowerCase();
|
|
3695
|
+
const action = nextKeys.some((candidate) => candidate.toLocaleLowerCase() === key) ? onNext : prevKeys.some((candidate) => candidate.toLocaleLowerCase() === key) ? onPrev : void 0;
|
|
3696
|
+
if (!action) return;
|
|
3697
|
+
if (preventDefault) event.preventDefault();
|
|
3698
|
+
void Promise.resolve(action()).catch((error) => onError?.(error));
|
|
3699
|
+
};
|
|
3700
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
3701
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
3702
|
+
}, [allowInEditable, enabled, nextKeys, onError, onNext, onPrev, preventDefault, prevKeys]);
|
|
3703
|
+
}
|
|
3704
|
+
function isEditableTarget(target) {
|
|
3705
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
3706
|
+
return target.isContentEditable || target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT";
|
|
3707
|
+
}
|
|
3708
|
+
|
|
3709
|
+
// src/features/navigation/KeepTourBar.tsx
|
|
3710
|
+
import { Fragment as Fragment10, jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
3711
|
+
function KeepTourBar({
|
|
3712
|
+
navigation: providedNavigation,
|
|
3713
|
+
currentId,
|
|
3714
|
+
initialIndex,
|
|
3715
|
+
showProgress = true,
|
|
3716
|
+
prevHref,
|
|
3717
|
+
nextHref,
|
|
3718
|
+
backHref,
|
|
3719
|
+
onPrev,
|
|
3720
|
+
onNext,
|
|
3721
|
+
onBack,
|
|
3722
|
+
prevLabel,
|
|
3723
|
+
nextLabel,
|
|
3724
|
+
backLabel,
|
|
3725
|
+
keyboardShortcuts = false,
|
|
3726
|
+
shortcutOptions,
|
|
3727
|
+
progress,
|
|
3728
|
+
getItemTitle = (item) => getMetaTitle(item.meta) ?? item.id,
|
|
3729
|
+
...props
|
|
3730
|
+
}) {
|
|
3731
|
+
const ownNavigation = useKeepNavigator({ currentId, initialIndex });
|
|
3732
|
+
const navigation = providedNavigation ?? ownNavigation;
|
|
3733
|
+
const previousLabel = useUiLabel("previousPage", prevLabel);
|
|
3734
|
+
const nextItemLabel = useUiLabel("nextPage", nextLabel);
|
|
3735
|
+
const listLabel = useUiLabel("allItems", backLabel);
|
|
3736
|
+
const { labels } = useKeepUiLabels();
|
|
3737
|
+
const resolvedPrev = onPrev ?? (() => navigateTo(navigation.goToPrev(), prevHref));
|
|
3738
|
+
const resolvedNext = onNext ?? (() => navigateTo(navigation.goToNext(), nextHref));
|
|
3739
|
+
useKeepTourShortcuts({
|
|
3740
|
+
...shortcutOptions,
|
|
3741
|
+
enabled: keyboardShortcuts && (shortcutOptions?.enabled ?? true),
|
|
3742
|
+
onNext: () => {
|
|
3743
|
+
return resolvedNext();
|
|
3744
|
+
},
|
|
3745
|
+
onPrev: () => {
|
|
3746
|
+
return resolvedPrev();
|
|
3747
|
+
}
|
|
3748
|
+
});
|
|
3749
|
+
return /* @__PURE__ */ jsxs14("nav", { ...props, "data-keepkit": "tour-bar", "aria-label": props["aria-label"] ?? labels.pagination, children: [
|
|
3750
|
+
showProgress ? /* @__PURE__ */ jsx19("span", { "data-keepkit": "tour-progress", "aria-live": "polite", children: progress ?? `${navigation.currentPosition ?? 0} / ${navigation.items.length}` }) : null,
|
|
3751
|
+
/* @__PURE__ */ jsx19(
|
|
3752
|
+
TourAction,
|
|
3753
|
+
{
|
|
3754
|
+
href: prevHref,
|
|
3755
|
+
disabled: !navigation.hasPrev,
|
|
3756
|
+
onClick: prevHref ? onPrev : resolvedPrev,
|
|
3757
|
+
"data-keep-action": "tour-prev",
|
|
3758
|
+
preview: navigation.prevItem ? /* @__PURE__ */ jsxs14(Fragment10, { children: [
|
|
3759
|
+
previousLabel,
|
|
3760
|
+
": ",
|
|
3761
|
+
getItemTitle(navigation.prevItem)
|
|
3762
|
+
] }) : void 0,
|
|
3763
|
+
children: previousLabel
|
|
3764
|
+
}
|
|
3765
|
+
),
|
|
3766
|
+
/* @__PURE__ */ jsx19(
|
|
3767
|
+
TourAction,
|
|
3768
|
+
{
|
|
3769
|
+
href: nextHref,
|
|
3770
|
+
disabled: !navigation.hasNext,
|
|
3771
|
+
onClick: nextHref ? onNext : resolvedNext,
|
|
3772
|
+
"data-keep-action": "tour-next",
|
|
3773
|
+
preview: navigation.nextItem ? /* @__PURE__ */ jsxs14(Fragment10, { children: [
|
|
3774
|
+
nextItemLabel,
|
|
3775
|
+
": ",
|
|
3776
|
+
getItemTitle(navigation.nextItem)
|
|
3777
|
+
] }) : void 0,
|
|
3778
|
+
children: nextItemLabel
|
|
3779
|
+
}
|
|
3780
|
+
),
|
|
3781
|
+
backHref || onBack ? /* @__PURE__ */ jsx19(TourAction, { href: backHref, onClick: onBack, "data-keep-action": "tour-back", children: listLabel }) : null
|
|
3782
|
+
] });
|
|
3783
|
+
}
|
|
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
|
+
] });
|
|
3790
|
+
if (href && !disabled) {
|
|
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
|
|
3800
|
+
}
|
|
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);
|
|
3820
|
+
}
|
|
3821
|
+
|
|
3822
|
+
// src/features/status/status.tsx
|
|
3823
|
+
import { isValidElement as isValidElement6 } from "react";
|
|
3824
|
+
|
|
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";
|
|
3828
|
+
function useKeepEmptyState() {
|
|
3829
|
+
return useUiLabel("noItems").replace(/\.$/, "");
|
|
3830
|
+
}
|
|
3831
|
+
function useKeepStatus(status) {
|
|
3832
|
+
const context = useKeepContext4();
|
|
3833
|
+
const resolvedStatus = status ?? getDerivedStatus(context);
|
|
3834
|
+
const state = {
|
|
3835
|
+
status: resolvedStatus,
|
|
3836
|
+
error: context.error,
|
|
3837
|
+
pendingCount: context.syncState.pendingCount,
|
|
3838
|
+
items: context.items
|
|
3839
|
+
};
|
|
3840
|
+
return { state, defaultLabel: useUiLabel(getStatusLabelKey3(resolvedStatus)) };
|
|
3841
|
+
}
|
|
3842
|
+
function useKeepAnnouncements(messages) {
|
|
3843
|
+
const context = useKeepContext4();
|
|
3844
|
+
const savedMessage = useUiLabel("savedMessage", messages?.save);
|
|
3845
|
+
const removedMessage = useUiLabel("removedMessage", messages?.remove);
|
|
3846
|
+
const noteSavedMessage = useUiLabel("noteSavedMessage", messages?.note);
|
|
3847
|
+
const [message, setMessage] = useState12("");
|
|
3848
|
+
const lastChangeRef = useRef6(void 0);
|
|
3849
|
+
useEffect10(() => {
|
|
3850
|
+
const change = context.lastChange;
|
|
3851
|
+
if (!change || change === lastChangeRef.current) return;
|
|
3852
|
+
lastChangeRef.current = change;
|
|
3853
|
+
if (change.action === "save") setMessage(savedMessage);
|
|
3854
|
+
else if (change.action === "remove" || change.action === "removeBatch") setMessage(removedMessage);
|
|
3855
|
+
else if (change.action === "updateNote") setMessage(noteSavedMessage);
|
|
3856
|
+
}, [context.lastChange, noteSavedMessage, removedMessage, savedMessage]);
|
|
3857
|
+
return message;
|
|
3858
|
+
}
|
|
3859
|
+
function getDerivedStatus(context) {
|
|
3860
|
+
if (context.error) return "error";
|
|
3861
|
+
if (context.syncState.status === "pending" || context.syncState.status === "syncing") return "syncing";
|
|
3862
|
+
if (context.isMutating) return "saving";
|
|
3863
|
+
if (context.isLoading && !context.isHydrated) return "loading";
|
|
3864
|
+
if (context.isHydrated && context.items.length === 0) return "empty";
|
|
3865
|
+
return "idle";
|
|
3866
|
+
}
|
|
3867
|
+
function getStatusLabelKey3(status) {
|
|
3868
|
+
if (status === "empty") return "noItems";
|
|
3869
|
+
if (status === "loading") return "loadingItems";
|
|
3870
|
+
if (status === "error") return "error";
|
|
3871
|
+
if (status === "saving") return "saving";
|
|
3872
|
+
if (status === "syncing") return "syncing";
|
|
3873
|
+
return "saved";
|
|
3874
|
+
}
|
|
3875
|
+
|
|
3876
|
+
// src/features/status/status.tsx
|
|
3877
|
+
import { Fragment as Fragment11, jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
3878
|
+
function KeepEmptyState({
|
|
3879
|
+
title,
|
|
3880
|
+
description,
|
|
3881
|
+
action,
|
|
3882
|
+
children,
|
|
3883
|
+
asChild = false,
|
|
3884
|
+
className,
|
|
3885
|
+
...rootProps
|
|
3886
|
+
}) {
|
|
3887
|
+
const defaultTitle = useKeepEmptyState();
|
|
3888
|
+
const contentChildren = asChild && isValidElement6(children) ? void 0 : children;
|
|
3889
|
+
const body = contentChildren ?? /* @__PURE__ */ jsxs15(Fragment11, { children: [
|
|
3890
|
+
/* @__PURE__ */ jsx20("h2", { children: title ?? defaultTitle }),
|
|
3891
|
+
description ? /* @__PURE__ */ jsx20("p", { children: description }) : null,
|
|
3892
|
+
action
|
|
3893
|
+
] });
|
|
3894
|
+
return renderRoot(
|
|
3895
|
+
asChild,
|
|
3896
|
+
children,
|
|
3897
|
+
{ ...rootProps, className, "data-keepkit": "empty-state", "data-state": "empty" },
|
|
3898
|
+
body,
|
|
3899
|
+
"KeepEmptyState"
|
|
3900
|
+
);
|
|
3901
|
+
}
|
|
3902
|
+
function KeepStatus({
|
|
3903
|
+
status,
|
|
3904
|
+
labels,
|
|
3905
|
+
children,
|
|
3906
|
+
render,
|
|
3907
|
+
asChild = false,
|
|
3908
|
+
className,
|
|
3909
|
+
...rootProps
|
|
3910
|
+
}) {
|
|
3911
|
+
const view = useKeepStatus(status);
|
|
3912
|
+
const contentChildren = asChild && isValidElement6(children) ? void 0 : children;
|
|
3913
|
+
const body = render ? render(view.state) : typeof contentChildren === "function" ? contentChildren(view.state) : contentChildren ?? labels?.[view.state.status] ?? view.defaultLabel;
|
|
3914
|
+
const role = rootProps.role ?? (view.state.status === "error" ? "alert" : "status");
|
|
3915
|
+
return renderRoot(
|
|
3916
|
+
asChild,
|
|
3917
|
+
isValidElement6(children) ? children : void 0,
|
|
3918
|
+
{
|
|
3919
|
+
...rootProps,
|
|
3920
|
+
className,
|
|
3921
|
+
"data-keepkit": "status",
|
|
3922
|
+
role,
|
|
3923
|
+
"aria-live": rootProps["aria-live"] ?? "polite",
|
|
3924
|
+
"data-state": view.state.status,
|
|
3925
|
+
"data-loading": view.state.status === "loading" || view.state.status === "saving" || view.state.status === "syncing" ? "true" : void 0
|
|
3926
|
+
},
|
|
3927
|
+
body,
|
|
3928
|
+
"KeepStatus"
|
|
3929
|
+
);
|
|
3930
|
+
}
|
|
3931
|
+
function KeepAnnouncements({ messages, ...props }) {
|
|
3932
|
+
const message = useKeepAnnouncements(messages);
|
|
3933
|
+
return /* @__PURE__ */ jsx20(
|
|
3934
|
+
"div",
|
|
3935
|
+
{
|
|
3936
|
+
...props,
|
|
3937
|
+
role: props.role ?? "status",
|
|
3938
|
+
"aria-live": props["aria-live"] ?? "polite",
|
|
3939
|
+
"aria-atomic": "true",
|
|
3940
|
+
"data-keepkit": "announcements",
|
|
3941
|
+
"data-state": "announcing",
|
|
3942
|
+
children: message
|
|
3943
|
+
}
|
|
3944
|
+
);
|
|
3945
|
+
}
|
|
3946
|
+
var KeepAnnouncer = KeepAnnouncements;
|
|
3173
3947
|
|
|
3174
|
-
// src/hooks/useKeepSyncFeedback.ts
|
|
3175
|
-
import { useKeepContext as
|
|
3176
|
-
import { useEffect as
|
|
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";
|
|
3177
3951
|
function useKeepSyncFeedback() {
|
|
3178
|
-
const { syncState } =
|
|
3952
|
+
const { syncState } = useKeepContext5();
|
|
3179
3953
|
const emitFeedback = useKeepUiFeedback();
|
|
3180
3954
|
const completedMessage = useUiLabel("syncSynced");
|
|
3181
3955
|
const failedMessage = useUiLabel("syncFailedMessage");
|
|
3182
|
-
const previousStatus =
|
|
3183
|
-
|
|
3956
|
+
const previousStatus = useRef7("idle");
|
|
3957
|
+
useEffect11(() => {
|
|
3184
3958
|
const previous = previousStatus.current;
|
|
3185
3959
|
previousStatus.current = syncState.status;
|
|
3186
3960
|
if (syncState.status === "error" && previous !== "error") {
|
|
@@ -3193,24 +3967,24 @@ function useKeepSyncFeedback() {
|
|
|
3193
3967
|
}, [completedMessage, emitFeedback, failedMessage, syncState.error, syncState.status]);
|
|
3194
3968
|
}
|
|
3195
3969
|
|
|
3196
|
-
// src/KeepSyncFeedbackObserver.tsx
|
|
3970
|
+
// src/features/sync/KeepSyncFeedbackObserver.tsx
|
|
3197
3971
|
function KeepSyncFeedbackObserver() {
|
|
3198
3972
|
useKeepSyncFeedback();
|
|
3199
3973
|
return null;
|
|
3200
3974
|
}
|
|
3201
3975
|
|
|
3202
|
-
// src/hooks/useKeepSyncRecoveryDialog.ts
|
|
3203
|
-
import { useKeepContext as
|
|
3204
|
-
import { useEffect as
|
|
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";
|
|
3205
3979
|
function useKeepSyncRecoveryDialog(options) {
|
|
3206
3980
|
const { open, onOpenChange, conflicts, onManualMerge } = options;
|
|
3207
|
-
const context =
|
|
3981
|
+
const context = useKeepContext6();
|
|
3208
3982
|
const conflictList = conflicts ?? context.syncState.conflicts ?? [];
|
|
3209
3983
|
const hasRecovery = conflictList.length > 0 || context.syncState.status === "error" || Boolean(context.error);
|
|
3210
|
-
const [dismissed, setDismissed] =
|
|
3211
|
-
const [busyId, setBusyId] =
|
|
3212
|
-
const [error, setError] =
|
|
3213
|
-
|
|
3984
|
+
const [dismissed, setDismissed] = useState13(false);
|
|
3985
|
+
const [busyId, setBusyId] = useState13();
|
|
3986
|
+
const [error, setError] = useState13();
|
|
3987
|
+
useEffect12(() => {
|
|
3214
3988
|
if (hasRecovery) setDismissed(false);
|
|
3215
3989
|
}, [hasRecovery]);
|
|
3216
3990
|
return {
|
|
@@ -3254,8 +4028,8 @@ function useKeepSyncRecoveryDialog(options) {
|
|
|
3254
4028
|
};
|
|
3255
4029
|
}
|
|
3256
4030
|
|
|
3257
|
-
// src/KeepSyncRecoveryDialog.tsx
|
|
3258
|
-
import { jsx as
|
|
4031
|
+
// src/features/sync/KeepSyncRecoveryDialog.tsx
|
|
4032
|
+
import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
3259
4033
|
function KeepSyncRecoveryDialog({
|
|
3260
4034
|
open,
|
|
3261
4035
|
onOpenChange,
|
|
@@ -3270,7 +4044,7 @@ function KeepSyncRecoveryDialog({
|
|
|
3270
4044
|
}) {
|
|
3271
4045
|
const view = useKeepSyncRecoveryDialog({ open, onOpenChange, conflicts, onManualMerge });
|
|
3272
4046
|
if (!view.isOpen) return null;
|
|
3273
|
-
return /* @__PURE__ */
|
|
4047
|
+
return /* @__PURE__ */ jsxs16(
|
|
3274
4048
|
"section",
|
|
3275
4049
|
{
|
|
3276
4050
|
...props,
|
|
@@ -3284,17 +4058,17 @@ function KeepSyncRecoveryDialog({
|
|
|
3284
4058
|
"data-state": view.conflictList.length > 0 ? "conflict" : "error",
|
|
3285
4059
|
"data-loading": view.busyId !== void 0 ? "true" : void 0,
|
|
3286
4060
|
children: [
|
|
3287
|
-
/* @__PURE__ */
|
|
3288
|
-
/* @__PURE__ */
|
|
3289
|
-
/* @__PURE__ */
|
|
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 })
|
|
3290
4064
|
] }),
|
|
3291
4065
|
children,
|
|
3292
|
-
view.conflictList.length > 0 ? /* @__PURE__ */
|
|
3293
|
-
/* @__PURE__ */
|
|
3294
|
-
view.conflictList.map((conflict) => /* @__PURE__ */
|
|
3295
|
-
/* @__PURE__ */
|
|
3296
|
-
/* @__PURE__ */
|
|
3297
|
-
/* @__PURE__ */
|
|
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(
|
|
3298
4072
|
ConflictPreview,
|
|
3299
4073
|
{
|
|
3300
4074
|
item: conflict.operation.item,
|
|
@@ -3304,7 +4078,7 @@ function KeepSyncRecoveryDialog({
|
|
|
3304
4078
|
side: "local"
|
|
3305
4079
|
}
|
|
3306
4080
|
),
|
|
3307
|
-
/* @__PURE__ */
|
|
4081
|
+
/* @__PURE__ */ jsx21(
|
|
3308
4082
|
ConflictPreview,
|
|
3309
4083
|
{
|
|
3310
4084
|
item: conflict.remote,
|
|
@@ -3315,8 +4089,8 @@ function KeepSyncRecoveryDialog({
|
|
|
3315
4089
|
}
|
|
3316
4090
|
)
|
|
3317
4091
|
] }),
|
|
3318
|
-
/* @__PURE__ */
|
|
3319
|
-
/* @__PURE__ */
|
|
4092
|
+
/* @__PURE__ */ jsxs16("div", { children: [
|
|
4093
|
+
/* @__PURE__ */ jsx21(
|
|
3320
4094
|
"button",
|
|
3321
4095
|
{
|
|
3322
4096
|
type: "button",
|
|
@@ -3326,7 +4100,7 @@ function KeepSyncRecoveryDialog({
|
|
|
3326
4100
|
children: view.labels.keepLocal
|
|
3327
4101
|
}
|
|
3328
4102
|
),
|
|
3329
|
-
/* @__PURE__ */
|
|
4103
|
+
/* @__PURE__ */ jsx21(
|
|
3330
4104
|
"button",
|
|
3331
4105
|
{
|
|
3332
4106
|
type: "button",
|
|
@@ -3336,7 +4110,7 @@ function KeepSyncRecoveryDialog({
|
|
|
3336
4110
|
children: view.labels.useServer
|
|
3337
4111
|
}
|
|
3338
4112
|
),
|
|
3339
|
-
/* @__PURE__ */
|
|
4113
|
+
/* @__PURE__ */ jsx21(
|
|
3340
4114
|
"button",
|
|
3341
4115
|
{
|
|
3342
4116
|
type: "button",
|
|
@@ -3349,12 +4123,12 @@ function KeepSyncRecoveryDialog({
|
|
|
3349
4123
|
] })
|
|
3350
4124
|
] }, conflict.id))
|
|
3351
4125
|
] }) : null,
|
|
3352
|
-
view.showBackupRecovery ? /* @__PURE__ */
|
|
3353
|
-
/* @__PURE__ */
|
|
3354
|
-
/* @__PURE__ */
|
|
3355
|
-
backup ?? (showBackupControls ? /* @__PURE__ */
|
|
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)
|
|
3356
4130
|
] }) : null,
|
|
3357
|
-
view.error ? /* @__PURE__ */
|
|
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
|
|
3358
4132
|
]
|
|
3359
4133
|
}
|
|
3360
4134
|
);
|
|
@@ -3366,16 +4140,16 @@ function ConflictPreview({
|
|
|
3366
4140
|
noteLabel,
|
|
3367
4141
|
side
|
|
3368
4142
|
}) {
|
|
3369
|
-
return /* @__PURE__ */
|
|
3370
|
-
/* @__PURE__ */
|
|
3371
|
-
/* @__PURE__ */
|
|
3372
|
-
/* @__PURE__ */
|
|
3373
|
-
/* @__PURE__ */
|
|
3374
|
-
/* @__PURE__ */
|
|
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" })
|
|
3375
4149
|
] }),
|
|
3376
|
-
/* @__PURE__ */
|
|
3377
|
-
/* @__PURE__ */
|
|
3378
|
-
/* @__PURE__ */
|
|
4150
|
+
/* @__PURE__ */ jsxs16("div", { children: [
|
|
4151
|
+
/* @__PURE__ */ jsx21("dt", { children: noteLabel }),
|
|
4152
|
+
/* @__PURE__ */ jsx21("dd", { children: item?.note || "\u2014" })
|
|
3379
4153
|
] })
|
|
3380
4154
|
] })
|
|
3381
4155
|
] });
|
|
@@ -3384,10 +4158,10 @@ function formatConflictDate(timestamp) {
|
|
|
3384
4158
|
return new Date(timestamp).toISOString().slice(0, 10);
|
|
3385
4159
|
}
|
|
3386
4160
|
|
|
3387
|
-
// src/hooks/useKeepSyncStatusBanner.ts
|
|
3388
|
-
import { useKeepContext as
|
|
4161
|
+
// src/features/sync/hooks/useKeepSyncStatusBanner.ts
|
|
4162
|
+
import { useKeepContext as useKeepContext7 } from "@keepkit/core/react";
|
|
3389
4163
|
function useKeepSyncStatusBanner({ onRetry, children }) {
|
|
3390
|
-
const context =
|
|
4164
|
+
const context = useKeepContext7();
|
|
3391
4165
|
const retryLabel = useUiLabel("retrySync");
|
|
3392
4166
|
const resolveLabel = useUiLabel("resolveSync");
|
|
3393
4167
|
const conflictLabel = useUiLabel("syncConflict");
|
|
@@ -3395,7 +4169,7 @@ function useKeepSyncStatusBanner({ onRetry, children }) {
|
|
|
3395
4169
|
const syncedLabel = useUiLabel("syncSynced");
|
|
3396
4170
|
const status = context.syncState.status;
|
|
3397
4171
|
const hasConflicts = (context.syncState.conflicts?.length ?? 0) > 0 || context.syncState.conflictIds.length > 0;
|
|
3398
|
-
const message = children ?? (status === "error" ?
|
|
4172
|
+
const message = children ?? (status === "error" ? getErrorMessage5(context.syncState.error) : status === "conflict" || hasConflicts ? conflictLabel : status === "pending" || status === "syncing" ? pendingLabel : syncedLabel);
|
|
3399
4173
|
return {
|
|
3400
4174
|
status,
|
|
3401
4175
|
hasConflicts,
|
|
@@ -3414,12 +4188,12 @@ function useKeepSyncStatusBanner({ onRetry, children }) {
|
|
|
3414
4188
|
}
|
|
3415
4189
|
};
|
|
3416
4190
|
}
|
|
3417
|
-
function
|
|
4191
|
+
function getErrorMessage5(error) {
|
|
3418
4192
|
return error instanceof Error ? error.message : "Sync failed.";
|
|
3419
4193
|
}
|
|
3420
4194
|
|
|
3421
|
-
// src/KeepSyncStatusBanner.tsx
|
|
3422
|
-
import { jsx as
|
|
4195
|
+
// src/features/sync/KeepSyncStatusBanner.tsx
|
|
4196
|
+
import { jsx as jsx22, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
3423
4197
|
function KeepSyncStatusBanner({
|
|
3424
4198
|
onRetry,
|
|
3425
4199
|
onResolveConflicts,
|
|
@@ -3429,7 +4203,7 @@ function KeepSyncStatusBanner({
|
|
|
3429
4203
|
}) {
|
|
3430
4204
|
const view = useKeepSyncStatusBanner({ onRetry, children });
|
|
3431
4205
|
if (view.status === "idle" && !view.hasConflicts) return null;
|
|
3432
|
-
return /* @__PURE__ */
|
|
4206
|
+
return /* @__PURE__ */ jsxs17(
|
|
3433
4207
|
"aside",
|
|
3434
4208
|
{
|
|
3435
4209
|
...props,
|
|
@@ -3439,8 +4213,8 @@ function KeepSyncStatusBanner({
|
|
|
3439
4213
|
"data-keepkit": "sync-status",
|
|
3440
4214
|
"data-state": view.status,
|
|
3441
4215
|
children: [
|
|
3442
|
-
/* @__PURE__ */
|
|
3443
|
-
view.showRetry ? /* @__PURE__ */
|
|
4216
|
+
/* @__PURE__ */ jsx22("p", { children: view.message }),
|
|
4217
|
+
view.showRetry ? /* @__PURE__ */ jsx22(
|
|
3444
4218
|
"button",
|
|
3445
4219
|
{
|
|
3446
4220
|
type: "button",
|
|
@@ -3450,7 +4224,7 @@ function KeepSyncStatusBanner({
|
|
|
3450
4224
|
children: view.retryLabel
|
|
3451
4225
|
}
|
|
3452
4226
|
) : null,
|
|
3453
|
-
view.hasConflicts ? /* @__PURE__ */
|
|
4227
|
+
view.hasConflicts ? /* @__PURE__ */ jsx22(
|
|
3454
4228
|
"button",
|
|
3455
4229
|
{
|
|
3456
4230
|
type: "button",
|
|
@@ -3465,273 +4239,9 @@ function KeepSyncStatusBanner({
|
|
|
3465
4239
|
);
|
|
3466
4240
|
}
|
|
3467
4241
|
|
|
3468
|
-
// src/
|
|
3469
|
-
import { useKeepItem as useKeepItem4 } from "@keepkit/core/react";
|
|
3470
|
-
import { useCallback as useCallback5, useEffect as useEffect7, useState as useState9 } from "react";
|
|
3471
|
-
function useKeepTagEditor({ item, onSaved, onSaveError }) {
|
|
3472
|
-
const itemState = useKeepItem4(item);
|
|
3473
|
-
const [tags, setTags] = useState9(item.tags ?? []);
|
|
3474
|
-
const [input, setInput] = useState9("");
|
|
3475
|
-
useEffect7(() => setTags(itemState.item?.tags ?? item.tags ?? []), [item.tags, itemState.item?.tags]);
|
|
3476
|
-
const save = useCallback5(async () => {
|
|
3477
|
-
const nextTags = normalizeUiTags(tags);
|
|
3478
|
-
try {
|
|
3479
|
-
await itemState.updateTags(nextTags);
|
|
3480
|
-
setTags(nextTags);
|
|
3481
|
-
onSaved?.(nextTags);
|
|
3482
|
-
} catch (cause) {
|
|
3483
|
-
onSaveError?.(cause);
|
|
3484
|
-
throw cause;
|
|
3485
|
-
}
|
|
3486
|
-
}, [itemState, onSaveError, onSaved, tags]);
|
|
3487
|
-
const addTag = (tag) => {
|
|
3488
|
-
setTags(normalizeUiTags([...tags, tag]));
|
|
3489
|
-
setInput("");
|
|
3490
|
-
};
|
|
3491
|
-
const state = { tags, setTags, save, isSaving: itemState.isMutating };
|
|
3492
|
-
return {
|
|
3493
|
-
state,
|
|
3494
|
-
input,
|
|
3495
|
-
setInput,
|
|
3496
|
-
error: itemState.error,
|
|
3497
|
-
handleInputKeyDown: (event) => {
|
|
3498
|
-
if (event.key === "Enter") {
|
|
3499
|
-
if (event.nativeEvent.isComposing) return;
|
|
3500
|
-
event.preventDefault();
|
|
3501
|
-
if (input.trim()) addTag(input);
|
|
3502
|
-
} else if (event.key === "Backspace" && input.length === 0 && tags.length > 0) {
|
|
3503
|
-
event.preventDefault();
|
|
3504
|
-
setTags(tags.slice(0, -1));
|
|
3505
|
-
}
|
|
3506
|
-
},
|
|
3507
|
-
removeTag: (tag) => setTags(tags.filter((current) => current !== tag)),
|
|
3508
|
-
submit: (event) => {
|
|
3509
|
-
event.preventDefault();
|
|
3510
|
-
void save().catch(() => void 0);
|
|
3511
|
-
},
|
|
3512
|
-
labels: {
|
|
3513
|
-
tags: useUiLabel("tagsToApply"),
|
|
3514
|
-
remove: useUiLabel("remove"),
|
|
3515
|
-
apply: useUiLabel("applyTags"),
|
|
3516
|
-
error: useUiLabel("error")
|
|
3517
|
-
}
|
|
3518
|
-
};
|
|
3519
|
-
}
|
|
3520
|
-
|
|
3521
|
-
// src/KeepTagEditor.tsx
|
|
3522
|
-
import { Fragment as Fragment7, jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3523
|
-
function KeepTagEditor({
|
|
3524
|
-
item,
|
|
3525
|
-
availableTags = [],
|
|
3526
|
-
onSaved,
|
|
3527
|
-
onSaveError,
|
|
3528
|
-
render,
|
|
3529
|
-
...props
|
|
3530
|
-
}) {
|
|
3531
|
-
const view = useKeepTagEditor({ item, onSaved, onSaveError });
|
|
3532
|
-
const { isSaving, tags } = view.state;
|
|
3533
|
-
const body = render ? render(view.state) : /* @__PURE__ */ jsxs13(Fragment7, { children: [
|
|
3534
|
-
/* @__PURE__ */ jsxs13("label", { children: [
|
|
3535
|
-
view.labels.tags,
|
|
3536
|
-
/* @__PURE__ */ jsx18(
|
|
3537
|
-
"input",
|
|
3538
|
-
{
|
|
3539
|
-
"data-keep-action": "edit-tags",
|
|
3540
|
-
value: view.input,
|
|
3541
|
-
list: availableTags.length > 0 ? `keep-tags-${item.id}` : void 0,
|
|
3542
|
-
onChange: (event) => view.setInput(event.currentTarget.value),
|
|
3543
|
-
onKeyDown: view.handleInputKeyDown
|
|
3544
|
-
}
|
|
3545
|
-
)
|
|
3546
|
-
] }),
|
|
3547
|
-
availableTags.length > 0 ? /* @__PURE__ */ jsx18("datalist", { id: `keep-tags-${item.id}`, children: availableTags.map((tag) => /* @__PURE__ */ jsx18("option", { value: tag }, tag)) }) : null,
|
|
3548
|
-
/* @__PURE__ */ jsx18("ul", { "aria-label": view.labels.tags, children: tags.map((tag) => /* @__PURE__ */ jsxs13("li", { children: [
|
|
3549
|
-
tag,
|
|
3550
|
-
/* @__PURE__ */ jsx18("button", { type: "button", "data-keep-action": "remove-tag", onClick: () => view.removeTag(tag), children: view.labels.remove })
|
|
3551
|
-
] }, tag)) }),
|
|
3552
|
-
/* @__PURE__ */ jsx18("button", { type: "submit", "data-keep-action": "apply-tags", disabled: isSaving, "aria-busy": isSaving, children: view.labels.apply })
|
|
3553
|
-
] });
|
|
3554
|
-
return /* @__PURE__ */ jsxs13(
|
|
3555
|
-
"form",
|
|
3556
|
-
{
|
|
3557
|
-
...props,
|
|
3558
|
-
onSubmit: view.submit,
|
|
3559
|
-
"aria-busy": isSaving || props["aria-busy"],
|
|
3560
|
-
"data-keepkit": "tag-editor",
|
|
3561
|
-
"data-state": view.error ? "error" : isSaving ? "saving" : "idle",
|
|
3562
|
-
"data-loading": isSaving ? "true" : void 0,
|
|
3563
|
-
"data-disabled": isSaving ? "true" : void 0,
|
|
3564
|
-
children: [
|
|
3565
|
-
body,
|
|
3566
|
-
view.error ? /* @__PURE__ */ jsx18("p", { role: "alert", children: getErrorMessage5(view.error, view.labels.error) }) : null
|
|
3567
|
-
]
|
|
3568
|
-
}
|
|
3569
|
-
);
|
|
3570
|
-
}
|
|
3571
|
-
function getErrorMessage5(error, fallback) {
|
|
3572
|
-
return error instanceof Error ? error.message : fallback;
|
|
3573
|
-
}
|
|
3574
|
-
|
|
3575
|
-
// src/hooks/useKeepUndo.ts
|
|
3576
|
-
import { useKeepContext as useKeepContext6 } from "@keepkit/core/react";
|
|
3577
|
-
function useKeepUndo() {
|
|
3578
|
-
const context = useKeepContext6();
|
|
3579
|
-
const emitFeedback = useKeepUiFeedback();
|
|
3580
|
-
const restoredMessage = useUiLabel("restoredMessage");
|
|
3581
|
-
return {
|
|
3582
|
-
canUndo: context.undo.canUndo,
|
|
3583
|
-
undo: async () => {
|
|
3584
|
-
const items = context.lastChange?.items ?? (context.lastChange?.item ? [context.lastChange.item] : []);
|
|
3585
|
-
await context.undoLastRemoval();
|
|
3586
|
-
if (items.length > 0) {
|
|
3587
|
-
emitFeedback({ type: "item-restored", item: items[0], items, message: restoredMessage });
|
|
3588
|
-
}
|
|
3589
|
-
},
|
|
3590
|
-
message: useUiLabel("undoAvailable"),
|
|
3591
|
-
label: useUiLabel("undo")
|
|
3592
|
-
};
|
|
3593
|
-
}
|
|
3594
|
-
|
|
3595
|
-
// src/KeepUndo.tsx
|
|
3596
|
-
import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
3597
|
-
function KeepUndo({ children, label, ...props }) {
|
|
3598
|
-
const view = useKeepUndo();
|
|
3599
|
-
if (!view.canUndo) return null;
|
|
3600
|
-
return /* @__PURE__ */ jsxs14("div", { ...props, role: "status", "aria-live": "polite", "data-keepkit": "undo", "data-state": "available", children: [
|
|
3601
|
-
children ?? view.message,
|
|
3602
|
-
/* @__PURE__ */ jsx19("button", { type: "button", "data-keep-action": "undo", onClick: () => void view.undo(), children: label ?? view.label })
|
|
3603
|
-
] });
|
|
3604
|
-
}
|
|
3605
|
-
|
|
3606
|
-
// src/status.tsx
|
|
3607
|
-
import { isValidElement as isValidElement6 } from "react";
|
|
3608
|
-
|
|
3609
|
-
// src/hooks/useStatusViews.ts
|
|
3610
|
-
import { useKeepContext as useKeepContext7 } from "@keepkit/core/react";
|
|
3611
|
-
import { useEffect as useEffect8, useRef as useRef6, useState as useState10 } from "react";
|
|
3612
|
-
function useKeepEmptyState() {
|
|
3613
|
-
return useUiLabel("noItems").replace(/\.$/, "");
|
|
3614
|
-
}
|
|
3615
|
-
function useKeepStatus(status) {
|
|
3616
|
-
const context = useKeepContext7();
|
|
3617
|
-
const resolvedStatus = status ?? getDerivedStatus(context);
|
|
3618
|
-
const state = {
|
|
3619
|
-
status: resolvedStatus,
|
|
3620
|
-
error: context.error,
|
|
3621
|
-
pendingCount: context.syncState.pendingCount,
|
|
3622
|
-
items: context.items
|
|
3623
|
-
};
|
|
3624
|
-
return { state, defaultLabel: useUiLabel(getStatusLabelKey3(resolvedStatus)) };
|
|
3625
|
-
}
|
|
3626
|
-
function useKeepAnnouncements(messages) {
|
|
3627
|
-
const context = useKeepContext7();
|
|
3628
|
-
const savedMessage = useUiLabel("savedMessage", messages?.save);
|
|
3629
|
-
const removedMessage = useUiLabel("removedMessage", messages?.remove);
|
|
3630
|
-
const noteSavedMessage = useUiLabel("noteSavedMessage", messages?.note);
|
|
3631
|
-
const [message, setMessage] = useState10("");
|
|
3632
|
-
const lastChangeRef = useRef6(void 0);
|
|
3633
|
-
useEffect8(() => {
|
|
3634
|
-
const change = context.lastChange;
|
|
3635
|
-
if (!change || change === lastChangeRef.current) return;
|
|
3636
|
-
lastChangeRef.current = change;
|
|
3637
|
-
if (change.action === "save") setMessage(savedMessage);
|
|
3638
|
-
else if (change.action === "remove" || change.action === "removeBatch") setMessage(removedMessage);
|
|
3639
|
-
else if (change.action === "updateNote") setMessage(noteSavedMessage);
|
|
3640
|
-
}, [context.lastChange, noteSavedMessage, removedMessage, savedMessage]);
|
|
3641
|
-
return message;
|
|
3642
|
-
}
|
|
3643
|
-
function getDerivedStatus(context) {
|
|
3644
|
-
if (context.error) return "error";
|
|
3645
|
-
if (context.syncState.status === "pending" || context.syncState.status === "syncing") return "syncing";
|
|
3646
|
-
if (context.isMutating) return "saving";
|
|
3647
|
-
if (context.isLoading && !context.isHydrated) return "loading";
|
|
3648
|
-
if (context.isHydrated && context.items.length === 0) return "empty";
|
|
3649
|
-
return "idle";
|
|
3650
|
-
}
|
|
3651
|
-
function getStatusLabelKey3(status) {
|
|
3652
|
-
if (status === "empty") return "noItems";
|
|
3653
|
-
if (status === "loading") return "loadingItems";
|
|
3654
|
-
if (status === "error") return "error";
|
|
3655
|
-
if (status === "saving") return "saving";
|
|
3656
|
-
if (status === "syncing") return "syncing";
|
|
3657
|
-
return "saved";
|
|
3658
|
-
}
|
|
3659
|
-
|
|
3660
|
-
// src/status.tsx
|
|
3661
|
-
import { Fragment as Fragment8, jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
3662
|
-
function KeepEmptyState({
|
|
3663
|
-
title,
|
|
3664
|
-
description,
|
|
3665
|
-
action,
|
|
3666
|
-
children,
|
|
3667
|
-
asChild = false,
|
|
3668
|
-
className,
|
|
3669
|
-
...rootProps
|
|
3670
|
-
}) {
|
|
3671
|
-
const defaultTitle = useKeepEmptyState();
|
|
3672
|
-
const contentChildren = asChild && isValidElement6(children) ? void 0 : children;
|
|
3673
|
-
const body = contentChildren ?? /* @__PURE__ */ jsxs15(Fragment8, { children: [
|
|
3674
|
-
/* @__PURE__ */ jsx20("h2", { children: title ?? defaultTitle }),
|
|
3675
|
-
description ? /* @__PURE__ */ jsx20("p", { children: description }) : null,
|
|
3676
|
-
action
|
|
3677
|
-
] });
|
|
3678
|
-
return renderRoot(
|
|
3679
|
-
asChild,
|
|
3680
|
-
children,
|
|
3681
|
-
{ ...rootProps, className, "data-keepkit": "empty-state", "data-state": "empty" },
|
|
3682
|
-
body,
|
|
3683
|
-
"KeepEmptyState"
|
|
3684
|
-
);
|
|
3685
|
-
}
|
|
3686
|
-
function KeepStatus({
|
|
3687
|
-
status,
|
|
3688
|
-
labels,
|
|
3689
|
-
children,
|
|
3690
|
-
render,
|
|
3691
|
-
asChild = false,
|
|
3692
|
-
className,
|
|
3693
|
-
...rootProps
|
|
3694
|
-
}) {
|
|
3695
|
-
const view = useKeepStatus(status);
|
|
3696
|
-
const contentChildren = asChild && isValidElement6(children) ? void 0 : children;
|
|
3697
|
-
const body = render ? render(view.state) : typeof contentChildren === "function" ? contentChildren(view.state) : contentChildren ?? labels?.[view.state.status] ?? view.defaultLabel;
|
|
3698
|
-
const role = rootProps.role ?? (view.state.status === "error" ? "alert" : "status");
|
|
3699
|
-
return renderRoot(
|
|
3700
|
-
asChild,
|
|
3701
|
-
isValidElement6(children) ? children : void 0,
|
|
3702
|
-
{
|
|
3703
|
-
...rootProps,
|
|
3704
|
-
className,
|
|
3705
|
-
"data-keepkit": "status",
|
|
3706
|
-
role,
|
|
3707
|
-
"aria-live": rootProps["aria-live"] ?? "polite",
|
|
3708
|
-
"data-state": view.state.status,
|
|
3709
|
-
"data-loading": view.state.status === "loading" || view.state.status === "saving" || view.state.status === "syncing" ? "true" : void 0
|
|
3710
|
-
},
|
|
3711
|
-
body,
|
|
3712
|
-
"KeepStatus"
|
|
3713
|
-
);
|
|
3714
|
-
}
|
|
3715
|
-
function KeepAnnouncements({ messages, ...props }) {
|
|
3716
|
-
const message = useKeepAnnouncements(messages);
|
|
3717
|
-
return /* @__PURE__ */ jsx20(
|
|
3718
|
-
"div",
|
|
3719
|
-
{
|
|
3720
|
-
...props,
|
|
3721
|
-
role: props.role ?? "status",
|
|
3722
|
-
"aria-live": props["aria-live"] ?? "polite",
|
|
3723
|
-
"aria-atomic": "true",
|
|
3724
|
-
"data-keepkit": "announcements",
|
|
3725
|
-
"data-state": "announcing",
|
|
3726
|
-
children: message
|
|
3727
|
-
}
|
|
3728
|
-
);
|
|
3729
|
-
}
|
|
3730
|
-
var KeepAnnouncer = KeepAnnouncements;
|
|
3731
|
-
|
|
3732
|
-
// src/theme.tsx
|
|
4242
|
+
// src/foundation/theme.tsx
|
|
3733
4243
|
import { cloneElement as cloneElement2, isValidElement as isValidElement7 } from "react";
|
|
3734
|
-
import { jsx as
|
|
4244
|
+
import { jsx as jsx23 } from "react/jsx-runtime";
|
|
3735
4245
|
var keepThemeNames = [
|
|
3736
4246
|
"default",
|
|
3737
4247
|
"ocean",
|
|
@@ -3790,7 +4300,7 @@ function KeepThemeProvider({
|
|
|
3790
4300
|
throw new Error("KeepThemeProvider with asChild requires a single React element child.");
|
|
3791
4301
|
return cloneElement2(children, rootProps);
|
|
3792
4302
|
}
|
|
3793
|
-
return /* @__PURE__ */
|
|
4303
|
+
return /* @__PURE__ */ jsx23("div", { ...rootProps, children });
|
|
3794
4304
|
}
|
|
3795
4305
|
|
|
3796
4306
|
// src/index.tsx
|
|
@@ -3801,6 +4311,7 @@ import {
|
|
|
3801
4311
|
useKeepContext as useKeepContext8,
|
|
3802
4312
|
useKeepItem as useKeepItem5,
|
|
3803
4313
|
useKeepList as useKeepList5,
|
|
4314
|
+
useKeepNavigator as useKeepNavigator2,
|
|
3804
4315
|
useKeepShortcut
|
|
3805
4316
|
} from "@keepkit/core/react";
|
|
3806
4317
|
import {
|
|
@@ -3813,7 +4324,7 @@ import {
|
|
|
3813
4324
|
LocalStorageSyncQueueAdapter,
|
|
3814
4325
|
SyncStorageAdapter
|
|
3815
4326
|
} from "@keepkit/core/storage";
|
|
3816
|
-
import { jsx as
|
|
4327
|
+
import { jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
3817
4328
|
function KeepKitProvider({
|
|
3818
4329
|
labels,
|
|
3819
4330
|
locale,
|
|
@@ -3833,7 +4344,7 @@ function KeepKitProvider({
|
|
|
3833
4344
|
children,
|
|
3834
4345
|
...providerProps
|
|
3835
4346
|
}) {
|
|
3836
|
-
return /* @__PURE__ */
|
|
4347
|
+
return /* @__PURE__ */ jsx24(KeepUiProvider, { labels, locale, labelResolver, onFeedback, children: /* @__PURE__ */ jsx24(
|
|
3837
4348
|
KeepThemeProvider,
|
|
3838
4349
|
{
|
|
3839
4350
|
theme,
|
|
@@ -3847,10 +4358,10 @@ function KeepKitProvider({
|
|
|
3847
4358
|
className: themeClassName,
|
|
3848
4359
|
style: themeStyle,
|
|
3849
4360
|
asChild: themeAsChild,
|
|
3850
|
-
children: /* @__PURE__ */
|
|
3851
|
-
/* @__PURE__ */
|
|
4361
|
+
children: /* @__PURE__ */ jsxs18(CoreKeepProvider, { ...providerProps, children: [
|
|
4362
|
+
/* @__PURE__ */ jsx24(KeepSyncFeedbackObserver, {}),
|
|
3852
4363
|
children,
|
|
3853
|
-
/* @__PURE__ */
|
|
4364
|
+
/* @__PURE__ */ jsx24(KeepAnnouncements, {})
|
|
3854
4365
|
] })
|
|
3855
4366
|
}
|
|
3856
4367
|
) });
|
|
@@ -3877,7 +4388,7 @@ function createKeepKit(options = {}) {
|
|
|
3877
4388
|
} = options;
|
|
3878
4389
|
const coreKit = createCoreKeepKit(coreOptions);
|
|
3879
4390
|
return {
|
|
3880
|
-
Provider: (props) => /* @__PURE__ */
|
|
4391
|
+
Provider: (props) => /* @__PURE__ */ jsx24(
|
|
3881
4392
|
KeepKitProvider,
|
|
3882
4393
|
{
|
|
3883
4394
|
...coreOptions,
|
|
@@ -3898,9 +4409,9 @@ function createKeepKit(options = {}) {
|
|
|
3898
4409
|
...props
|
|
3899
4410
|
}
|
|
3900
4411
|
),
|
|
3901
|
-
Button: (props) => /* @__PURE__ */
|
|
3902
|
-
Backup: (props) => /* @__PURE__ */
|
|
3903
|
-
Collection: (props) => /* @__PURE__ */
|
|
4412
|
+
Button: (props) => /* @__PURE__ */ jsx24(KeepButton, { ...props }),
|
|
4413
|
+
Backup: (props) => /* @__PURE__ */ jsx24(KeepBackup, { ...props }),
|
|
4414
|
+
Collection: (props) => /* @__PURE__ */ jsx24(
|
|
3904
4415
|
KeepCollection,
|
|
3905
4416
|
{
|
|
3906
4417
|
...props,
|
|
@@ -3914,6 +4425,7 @@ function createKeepKit(options = {}) {
|
|
|
3914
4425
|
useContext: () => coreKit.useContext(),
|
|
3915
4426
|
useItem: (item) => coreKit.useItem(item),
|
|
3916
4427
|
useList: (query) => coreKit.useList(query),
|
|
4428
|
+
useNavigator: (navigatorOptions) => coreKit.useNavigator(navigatorOptions),
|
|
3917
4429
|
useShortcut: (shortcutOptions) => coreKit.useShortcut(shortcutOptions)
|
|
3918
4430
|
};
|
|
3919
4431
|
}
|
|
@@ -3929,6 +4441,7 @@ export {
|
|
|
3929
4441
|
KeepCollection,
|
|
3930
4442
|
KeepEmptyState,
|
|
3931
4443
|
KeepErrorBoundary3 as KeepErrorBoundary,
|
|
4444
|
+
KeepHighlight,
|
|
3932
4445
|
KeepItemCard,
|
|
3933
4446
|
KeepItemCardSkeleton,
|
|
3934
4447
|
KeepItemCheckbox,
|
|
@@ -3936,10 +4449,12 @@ export {
|
|
|
3936
4449
|
KeepKitProvider,
|
|
3937
4450
|
KeepLayout,
|
|
3938
4451
|
KeepList,
|
|
4452
|
+
KeepNavigator,
|
|
3939
4453
|
KeepNoteEditor,
|
|
3940
4454
|
KeepPagination,
|
|
3941
4455
|
KeepProvider,
|
|
3942
4456
|
KeepPruneStaleButton,
|
|
4457
|
+
KeepReorderableList,
|
|
3943
4458
|
KeepSearchInput,
|
|
3944
4459
|
KeepSortSelect,
|
|
3945
4460
|
KeepStaleNotice,
|
|
@@ -3949,6 +4464,7 @@ export {
|
|
|
3949
4464
|
KeepTagEditor,
|
|
3950
4465
|
KeepTagFilter,
|
|
3951
4466
|
KeepThemeProvider,
|
|
4467
|
+
KeepTourBar,
|
|
3952
4468
|
KeepUiProvider,
|
|
3953
4469
|
KeepUndo,
|
|
3954
4470
|
LocalStorageAdapter,
|
|
@@ -3960,14 +4476,17 @@ export {
|
|
|
3960
4476
|
createNextPagesRouterAdapter,
|
|
3961
4477
|
createStorageAdapter,
|
|
3962
4478
|
getKeepLocaleLabels,
|
|
4479
|
+
highlightText,
|
|
3963
4480
|
isAllSelected,
|
|
3964
4481
|
keepThemeNames,
|
|
3965
4482
|
toggleSelectAll,
|
|
3966
4483
|
useKeepContext8 as useKeepContext,
|
|
3967
4484
|
useKeepItem5 as useKeepItem,
|
|
3968
4485
|
useKeepList5 as useKeepList,
|
|
4486
|
+
useKeepNavigator2 as useKeepNavigator,
|
|
3969
4487
|
useKeepShortcut,
|
|
3970
4488
|
useKeepToastFeedback,
|
|
4489
|
+
useKeepTourShortcuts,
|
|
3971
4490
|
useKeepUiLabels,
|
|
3972
4491
|
useKeepUrlSync
|
|
3973
4492
|
};
|