@keepkit/core 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 +8 -4
- package/dist/{chunk-THZ3ACR2.js → chunk-5W4QSJHV.js} +2 -2
- package/dist/chunk-5W4QSJHV.js.map +1 -0
- package/dist/{chunk-MDTL5L64.js → chunk-62I2YZYI.js} +72 -9
- package/dist/chunk-62I2YZYI.js.map +1 -0
- package/dist/{chunk-VJDO3GFH.js → chunk-PNP7OALR.js} +55 -55
- package/dist/chunk-PNP7OALR.js.map +1 -0
- package/dist/core.d.ts +20 -20
- package/dist/core.js +84 -76
- package/dist/core.js.map +1 -1
- package/dist/react.d.ts +66 -47
- package/dist/react.js +255 -118
- package/dist/react.js.map +1 -1
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +1 -1
- package/dist/{scope-y_aBm363.d.ts → scope-Dk5FTVz8.d.ts} +1 -1
- package/dist/storage.d.ts +3 -3
- package/dist/storage.js +1 -1
- package/dist/{url-VhisKZCR.d.ts → store-Cm35R_ho.d.ts} +87 -66
- package/dist/{types-iMK12pmy.d.ts → types-Aoc0Eyvk.d.ts} +6 -2
- package/package.json +1 -1
- package/dist/chunk-MDTL5L64.js.map +0 -1
- package/dist/chunk-THZ3ACR2.js.map +0 -1
- package/dist/chunk-VJDO3GFH.js.map +0 -1
package/dist/react.js
CHANGED
|
@@ -2,23 +2,34 @@
|
|
|
2
2
|
import {
|
|
3
3
|
KeepStore,
|
|
4
4
|
exportItems,
|
|
5
|
+
getKeepNavigationState,
|
|
5
6
|
importItems,
|
|
6
7
|
isKeepItemMetadataStale,
|
|
8
|
+
moveKeepItem,
|
|
9
|
+
orderKeepItems,
|
|
7
10
|
queryKeepItems,
|
|
11
|
+
reorderKeepItems,
|
|
8
12
|
revalidateKeepItems
|
|
9
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-62I2YZYI.js";
|
|
10
14
|
import {
|
|
11
15
|
parseKeepMeta
|
|
12
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-5W4QSJHV.js";
|
|
13
17
|
import {
|
|
14
18
|
createBrowserStorageAdapter,
|
|
15
19
|
normalizeKeepTags
|
|
16
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-PNP7OALR.js";
|
|
17
21
|
|
|
18
|
-
// src/
|
|
22
|
+
// src/react/components/KeepButton.tsx
|
|
23
|
+
import {
|
|
24
|
+
Children,
|
|
25
|
+
cloneElement,
|
|
26
|
+
isValidElement
|
|
27
|
+
} from "react";
|
|
28
|
+
|
|
29
|
+
// src/react/hooks/useKeepItem.ts
|
|
19
30
|
import { useCallback as useCallback3 } from "react";
|
|
20
31
|
|
|
21
|
-
// src/KeepProvider.tsx
|
|
32
|
+
// src/react/components/KeepProvider.tsx
|
|
22
33
|
import {
|
|
23
34
|
createContext,
|
|
24
35
|
useCallback,
|
|
@@ -29,7 +40,7 @@ import {
|
|
|
29
40
|
useSyncExternalStore
|
|
30
41
|
} from "react";
|
|
31
42
|
|
|
32
|
-
// src/KeepErrorBoundary.tsx
|
|
43
|
+
// src/react/components/KeepErrorBoundary.tsx
|
|
33
44
|
import { Component } from "react";
|
|
34
45
|
var KeepErrorBoundary = class extends Component {
|
|
35
46
|
constructor() {
|
|
@@ -55,7 +66,7 @@ var KeepErrorBoundary = class extends Component {
|
|
|
55
66
|
}
|
|
56
67
|
};
|
|
57
68
|
|
|
58
|
-
// src/KeepProvider.tsx
|
|
69
|
+
// src/react/components/KeepProvider.tsx
|
|
59
70
|
import { jsx } from "react/jsx-runtime";
|
|
60
71
|
var defaultStorage = createBrowserStorageAdapter();
|
|
61
72
|
var KeepContext = createContext(null);
|
|
@@ -331,17 +342,77 @@ function KeepProviderContent({
|
|
|
331
342
|
reportError(cause, { action: "save", id: item.id });
|
|
332
343
|
throw cause;
|
|
333
344
|
}
|
|
334
|
-
await runMutation("save", normalizedItem.id, (previous) =>
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
345
|
+
await runMutation("save", normalizedItem.id, (previous) => {
|
|
346
|
+
const current = previous.find((item2) => item2.id === normalizedItem.id);
|
|
347
|
+
const hasCustomOrder = previous.some((item2) => item2.order !== void 0);
|
|
348
|
+
const nextItem = {
|
|
349
|
+
...normalizedItem,
|
|
350
|
+
...normalizedItem.order === void 0 && current?.order !== void 0 ? { order: current.order } : {},
|
|
351
|
+
...normalizedItem.order === void 0 && !current && hasCustomOrder ? { order: previous.reduce((max, item2) => Math.max(max, item2.order ?? -1), -1) + 1 } : {}
|
|
352
|
+
};
|
|
353
|
+
const withoutCurrent = previous.filter((item2) => item2.id !== nextItem.id);
|
|
354
|
+
const next = hasCustomOrder ? reorderKeepItems(
|
|
355
|
+
[...withoutCurrent, nextItem],
|
|
356
|
+
orderKeepItems(previous).map((item2) => item2.id === nextItem.id ? nextItem.id : item2.id)
|
|
357
|
+
) : [...withoutCurrent, nextItem].sort((a, b) => b.updatedAt - a.updatedAt);
|
|
358
|
+
return {
|
|
359
|
+
next,
|
|
360
|
+
persist: () => storage.set(nextItem),
|
|
361
|
+
onSuccess: () => handlersRef.current.onSave?.(nextItem),
|
|
362
|
+
pluginContext: { action: "save", id: nextItem.id, item: nextItem }
|
|
363
|
+
};
|
|
364
|
+
});
|
|
342
365
|
},
|
|
343
366
|
[reportError, runMutation, storage]
|
|
344
367
|
);
|
|
368
|
+
const reorderItems = useCallback(
|
|
369
|
+
async (orderedIds) => {
|
|
370
|
+
await runMutation("reorder", void 0, (previous) => {
|
|
371
|
+
const next = reorderKeepItems(previous, orderedIds);
|
|
372
|
+
const changedItems = next.filter(
|
|
373
|
+
(item, index) => item.id !== previous[index]?.id || item.order !== previous[index]?.order
|
|
374
|
+
);
|
|
375
|
+
return {
|
|
376
|
+
next,
|
|
377
|
+
persist: async () => {
|
|
378
|
+
try {
|
|
379
|
+
if (storage.setMany) await storage.setMany(changedItems);
|
|
380
|
+
else for (const item of changedItems) await storage.set(item);
|
|
381
|
+
} catch (cause) {
|
|
382
|
+
await restoreItems(storage, previous);
|
|
383
|
+
throw cause;
|
|
384
|
+
}
|
|
385
|
+
},
|
|
386
|
+
pluginContext: { action: "reorder", items: changedItems }
|
|
387
|
+
};
|
|
388
|
+
});
|
|
389
|
+
},
|
|
390
|
+
[runMutation, storage]
|
|
391
|
+
);
|
|
392
|
+
const moveItem = useCallback(
|
|
393
|
+
async (id, targetIndex) => {
|
|
394
|
+
await runMutation("reorder", id, (previous) => {
|
|
395
|
+
const next = moveKeepItem(previous, id, targetIndex);
|
|
396
|
+
const changedItems = next.filter(
|
|
397
|
+
(item, index) => item.id !== previous[index]?.id || item.order !== previous[index]?.order
|
|
398
|
+
);
|
|
399
|
+
return {
|
|
400
|
+
next,
|
|
401
|
+
persist: async () => {
|
|
402
|
+
try {
|
|
403
|
+
if (storage.setMany) await storage.setMany(changedItems);
|
|
404
|
+
else for (const item of changedItems) await storage.set(item);
|
|
405
|
+
} catch (cause) {
|
|
406
|
+
await restoreItems(storage, previous);
|
|
407
|
+
throw cause;
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
pluginContext: { action: "reorder", id, items: changedItems }
|
|
411
|
+
};
|
|
412
|
+
});
|
|
413
|
+
},
|
|
414
|
+
[runMutation, storage]
|
|
415
|
+
);
|
|
345
416
|
const updateNote = useCallback(
|
|
346
417
|
async (id, note) => {
|
|
347
418
|
const nextNote = note?.trim() || void 0;
|
|
@@ -501,7 +572,8 @@ function KeepProviderContent({
|
|
|
501
572
|
const rememberUndo = useCallback(
|
|
502
573
|
(removedItems) => {
|
|
503
574
|
undoRef.current?.timer && clearTimeout(undoRef.current.timer);
|
|
504
|
-
const
|
|
575
|
+
const startedAt = Date.now();
|
|
576
|
+
const expiresAt = startedAt + Math.max(0, undoTimeoutMs);
|
|
505
577
|
const timer = setTimeout(
|
|
506
578
|
() => {
|
|
507
579
|
undoRef.current = void 0;
|
|
@@ -510,7 +582,7 @@ function KeepProviderContent({
|
|
|
510
582
|
Math.max(0, undoTimeoutMs)
|
|
511
583
|
);
|
|
512
584
|
undoRef.current = { items: removedItems, expiresAt, timer };
|
|
513
|
-
store.setState({ undo: { canUndo: true, ids: removedItems.map((item) => item.id), expiresAt } });
|
|
585
|
+
store.setState({ undo: { canUndo: true, ids: removedItems.map((item) => item.id), startedAt, expiresAt } });
|
|
514
586
|
},
|
|
515
587
|
[store, undoTimeoutMs]
|
|
516
588
|
);
|
|
@@ -729,6 +801,8 @@ function KeepProviderContent({
|
|
|
729
801
|
resolveSyncConflict,
|
|
730
802
|
refreshItemMetadata,
|
|
731
803
|
revalidateItems,
|
|
804
|
+
reorderItems,
|
|
805
|
+
moveItem,
|
|
732
806
|
exportBackup,
|
|
733
807
|
importBackup
|
|
734
808
|
}),
|
|
@@ -758,6 +832,8 @@ function KeepProviderContent({
|
|
|
758
832
|
removeItems,
|
|
759
833
|
refreshItemMetadata,
|
|
760
834
|
revalidateItems,
|
|
835
|
+
reorderItems,
|
|
836
|
+
moveItem,
|
|
761
837
|
exportBackup,
|
|
762
838
|
importBackup
|
|
763
839
|
]
|
|
@@ -778,7 +854,9 @@ function KeepProviderContent({
|
|
|
778
854
|
clear,
|
|
779
855
|
refresh,
|
|
780
856
|
refreshItemMetadata,
|
|
781
|
-
revalidateItems
|
|
857
|
+
revalidateItems,
|
|
858
|
+
reorderItems,
|
|
859
|
+
moveItem
|
|
782
860
|
}),
|
|
783
861
|
[
|
|
784
862
|
addTagsBatch,
|
|
@@ -793,6 +871,8 @@ function KeepProviderContent({
|
|
|
793
871
|
updateTagsBatch,
|
|
794
872
|
refreshItemMetadata,
|
|
795
873
|
revalidateItems,
|
|
874
|
+
reorderItems,
|
|
875
|
+
moveItem,
|
|
796
876
|
removeItemWithUndo,
|
|
797
877
|
removeItemsWithUndo,
|
|
798
878
|
undoLastRemoval
|
|
@@ -838,7 +918,7 @@ function useKeepStore() {
|
|
|
838
918
|
return context;
|
|
839
919
|
}
|
|
840
920
|
|
|
841
|
-
// src/hooks/useKeepStoreSelector.ts
|
|
921
|
+
// src/react/hooks/useKeepStoreSelector.ts
|
|
842
922
|
import { useCallback as useCallback2, useRef as useRef2, useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
843
923
|
function useKeepStoreSelector(store, selector) {
|
|
844
924
|
const cacheRef = useRef2(null);
|
|
@@ -853,7 +933,7 @@ function useKeepStoreSelector(store, selector) {
|
|
|
853
933
|
return useSyncExternalStore2(store.subscribe, getSelectedSnapshot, getSelectedSnapshot);
|
|
854
934
|
}
|
|
855
935
|
|
|
856
|
-
// src/hooks/useKeepItem.ts
|
|
936
|
+
// src/react/hooks/useKeepItem.ts
|
|
857
937
|
function useKeepItem(input) {
|
|
858
938
|
const { store, actions } = useKeepStore();
|
|
859
939
|
const id = input?.id ?? "";
|
|
@@ -873,15 +953,17 @@ function useKeepItem(input) {
|
|
|
873
953
|
store,
|
|
874
954
|
useCallback3((state) => state.error, [])
|
|
875
955
|
);
|
|
956
|
+
const currentOrder = item?.order;
|
|
876
957
|
const save = useCallback3(async () => {
|
|
877
958
|
if (!input) throw new Error("An item input is required to save an item.");
|
|
878
959
|
const now = Date.now();
|
|
879
960
|
await actions.saveItem({
|
|
880
961
|
...input,
|
|
962
|
+
...currentOrder === void 0 ? {} : { order: currentOrder },
|
|
881
963
|
savedAt: item?.savedAt ?? now,
|
|
882
964
|
updatedAt: now
|
|
883
965
|
});
|
|
884
|
-
}, [actions, input, item?.savedAt]);
|
|
966
|
+
}, [actions, currentOrder, input, item?.savedAt]);
|
|
885
967
|
const remove = useCallback3(() => actions.removeItem(id), [actions, id]);
|
|
886
968
|
const removeWithUndo = useCallback3(() => actions.removeItemWithUndo(id), [actions, id]);
|
|
887
969
|
const toggle = useCallback3(() => item ? remove() : save(), [item, remove, save]);
|
|
@@ -908,7 +990,96 @@ function useKeepItem(input) {
|
|
|
908
990
|
};
|
|
909
991
|
}
|
|
910
992
|
|
|
911
|
-
// src/
|
|
993
|
+
// src/react/components/KeepButton.tsx
|
|
994
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
995
|
+
function KeepButton({
|
|
996
|
+
item,
|
|
997
|
+
children,
|
|
998
|
+
savedLabel = "Saved",
|
|
999
|
+
unsavedLabel = "Save",
|
|
1000
|
+
savedAriaLabel,
|
|
1001
|
+
unsavedAriaLabel,
|
|
1002
|
+
getAriaLabel,
|
|
1003
|
+
asChild = false,
|
|
1004
|
+
onToggleError,
|
|
1005
|
+
onClick,
|
|
1006
|
+
disabled,
|
|
1007
|
+
...buttonProps
|
|
1008
|
+
}) {
|
|
1009
|
+
const state = useKeepItem(item);
|
|
1010
|
+
const { isSaved, toggle } = state;
|
|
1011
|
+
const isDisabled = disabled ?? state.isMutating;
|
|
1012
|
+
async function handleClick(event) {
|
|
1013
|
+
if (isDisabled) return;
|
|
1014
|
+
if (asChild) {
|
|
1015
|
+
onClick?.(event);
|
|
1016
|
+
} else {
|
|
1017
|
+
onClick?.(
|
|
1018
|
+
event
|
|
1019
|
+
);
|
|
1020
|
+
}
|
|
1021
|
+
if (event.defaultPrevented) return;
|
|
1022
|
+
try {
|
|
1023
|
+
await toggle();
|
|
1024
|
+
} catch (error) {
|
|
1025
|
+
onToggleError?.(error);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
const content = typeof children === "function" ? children(state) : children ?? (isSaved ? savedLabel : unsavedLabel);
|
|
1029
|
+
function handleElementClick(event) {
|
|
1030
|
+
if (isDisabled) return;
|
|
1031
|
+
if (asChild && isValidElement(content)) {
|
|
1032
|
+
content.props.onClick?.(event);
|
|
1033
|
+
}
|
|
1034
|
+
if (!event.defaultPrevented) void handleClick(event);
|
|
1035
|
+
}
|
|
1036
|
+
function handleKeyDown(event) {
|
|
1037
|
+
if (asChild && isValidElement(content)) {
|
|
1038
|
+
content.props.onKeyDown?.(event);
|
|
1039
|
+
}
|
|
1040
|
+
buttonProps.onKeyDown?.(event);
|
|
1041
|
+
if (!event.defaultPrevented && !isDisabled && asChild && (event.key === "Enter" || event.key === " ")) {
|
|
1042
|
+
void handleClick(event);
|
|
1043
|
+
event.preventDefault();
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
const child = asChild ? Children.only(content) : void 0;
|
|
1047
|
+
if (asChild && !isValidElement(child)) {
|
|
1048
|
+
throw new Error("KeepButton with asChild requires a single React element child.");
|
|
1049
|
+
}
|
|
1050
|
+
const commonProps = {
|
|
1051
|
+
...buttonProps,
|
|
1052
|
+
"aria-pressed": isSaved,
|
|
1053
|
+
"data-state": state.error ? "error" : isSaved ? "saved" : "unsaved",
|
|
1054
|
+
"data-loading": state.isLoading || state.isMutating ? "true" : void 0,
|
|
1055
|
+
"data-disabled": isDisabled ? "true" : void 0,
|
|
1056
|
+
"aria-label": ("aria-label" in buttonProps ? buttonProps["aria-label"] : void 0) ?? getAriaLabel?.(state) ?? (isSaved ? savedAriaLabel : unsavedAriaLabel) ?? getAccessibleLabel(isSaved, item, asChild),
|
|
1057
|
+
...asChild ? {
|
|
1058
|
+
"aria-disabled": isDisabled,
|
|
1059
|
+
role: buttonProps.role ?? "button",
|
|
1060
|
+
tabIndex: isDisabled ? -1 : buttonProps.tabIndex ?? 0
|
|
1061
|
+
} : { disabled: isDisabled },
|
|
1062
|
+
onClick: handleElementClick,
|
|
1063
|
+
onKeyDown: handleKeyDown
|
|
1064
|
+
};
|
|
1065
|
+
if (asChild) {
|
|
1066
|
+
return cloneElement(child, commonProps);
|
|
1067
|
+
}
|
|
1068
|
+
return /* @__PURE__ */ jsx2("button", { ...commonProps, type: "type" in buttonProps ? buttonProps.type ?? "button" : "button", children: content });
|
|
1069
|
+
}
|
|
1070
|
+
function getAccessibleLabel(isSaved, item, asChild) {
|
|
1071
|
+
if (!asChild) return isSaved ? "Remove saved item" : "Save item";
|
|
1072
|
+
const title = getMetaTitle(item.meta);
|
|
1073
|
+
const subject = title ? `${item.targetType ?? "item"}: ${title}` : item.targetType ?? "item";
|
|
1074
|
+
return `${isSaved ? "Remove" : "Save"} ${subject}`;
|
|
1075
|
+
}
|
|
1076
|
+
function getMetaTitle(meta) {
|
|
1077
|
+
if (typeof meta !== "object" || meta === null || !("title" in meta)) return void 0;
|
|
1078
|
+
const title = meta.title;
|
|
1079
|
+
return typeof title === "string" && title.trim() ? title.trim() : void 0;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// src/react/hooks/useKeepList.ts
|
|
912
1083
|
import { useCallback as useCallback4, useMemo as useMemo2 } from "react";
|
|
913
1084
|
function useKeepList(query = {}) {
|
|
914
1085
|
const { store, actions } = useKeepStore();
|
|
@@ -989,6 +1160,8 @@ function useKeepList(query = {}) {
|
|
|
989
1160
|
updateTagsBatch,
|
|
990
1161
|
addTagsBatch,
|
|
991
1162
|
removeTagsBatch,
|
|
1163
|
+
reorder: actions.reorderItems,
|
|
1164
|
+
move: actions.moveItem,
|
|
992
1165
|
clear: actions.clear,
|
|
993
1166
|
refresh: actions.refresh,
|
|
994
1167
|
revalidate: actions.revalidateItems
|
|
@@ -1006,7 +1179,63 @@ function sameCounts(left, right) {
|
|
|
1006
1179
|
});
|
|
1007
1180
|
}
|
|
1008
1181
|
|
|
1009
|
-
// src/hooks/
|
|
1182
|
+
// src/react/hooks/useKeepNavigator.ts
|
|
1183
|
+
import { useCallback as useCallback5, useMemo as useMemo3, useState } from "react";
|
|
1184
|
+
function useKeepNavigator(options = {}) {
|
|
1185
|
+
const { store } = useKeepStore();
|
|
1186
|
+
const { currentId, initialIndex = 0, query } = options;
|
|
1187
|
+
const [activeIndex, setActiveIndex] = useState(() => Math.max(0, Math.floor(initialIndex)));
|
|
1188
|
+
const queryOptions = useMemo3(() => ({ ...query, pagination: void 0 }), [query]);
|
|
1189
|
+
const selector = useMemo3(() => {
|
|
1190
|
+
let previousSource;
|
|
1191
|
+
let previousItems;
|
|
1192
|
+
let previousPointer;
|
|
1193
|
+
let previousResult;
|
|
1194
|
+
return (state) => {
|
|
1195
|
+
if (previousSource !== state.items) {
|
|
1196
|
+
previousSource = state.items;
|
|
1197
|
+
previousItems = queryKeepItems(state.items, queryOptions).items;
|
|
1198
|
+
previousResult = void 0;
|
|
1199
|
+
}
|
|
1200
|
+
const items = previousItems ?? [];
|
|
1201
|
+
const pointer = currentId ?? activeIndex;
|
|
1202
|
+
if (previousResult && previousPointer === pointer) return previousResult;
|
|
1203
|
+
previousPointer = pointer;
|
|
1204
|
+
previousResult = getKeepNavigationState(items, pointer);
|
|
1205
|
+
return previousResult;
|
|
1206
|
+
};
|
|
1207
|
+
}, [activeIndex, currentId, queryOptions]);
|
|
1208
|
+
const navigation = useKeepStoreSelector(store, selector);
|
|
1209
|
+
const goToIndex = useCallback5(
|
|
1210
|
+
(index) => {
|
|
1211
|
+
const item = navigation.items[index];
|
|
1212
|
+
if (!item) return null;
|
|
1213
|
+
setActiveIndex(index);
|
|
1214
|
+
return item;
|
|
1215
|
+
},
|
|
1216
|
+
[navigation.items]
|
|
1217
|
+
);
|
|
1218
|
+
const goToNext = useCallback5(() => {
|
|
1219
|
+
if (!navigation.nextItem) return null;
|
|
1220
|
+
setActiveIndex(navigation.currentIndex + 1);
|
|
1221
|
+
return navigation.nextItem;
|
|
1222
|
+
}, [navigation.currentIndex, navigation.nextItem]);
|
|
1223
|
+
const goToPrev = useCallback5(() => {
|
|
1224
|
+
if (!navigation.prevItem) return null;
|
|
1225
|
+
setActiveIndex(navigation.currentIndex - 1);
|
|
1226
|
+
return navigation.prevItem;
|
|
1227
|
+
}, [navigation.currentIndex, navigation.prevItem]);
|
|
1228
|
+
const goToItem = useCallback5(
|
|
1229
|
+
(id) => {
|
|
1230
|
+
const index = navigation.items.findIndex((item) => item.id === id);
|
|
1231
|
+
return index < 0 ? null : goToIndex(index);
|
|
1232
|
+
},
|
|
1233
|
+
[goToIndex, navigation.items]
|
|
1234
|
+
);
|
|
1235
|
+
return { ...navigation, goToNext, goToPrev, goToIndex, goToItem };
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
// src/react/hooks/useKeepShortcut.ts
|
|
1010
1239
|
import { useEffect as useEffect2 } from "react";
|
|
1011
1240
|
function useKeepShortcut(options) {
|
|
1012
1241
|
const item = useKeepItem(options.item);
|
|
@@ -1062,101 +1291,7 @@ function isEditableTarget(target) {
|
|
|
1062
1291
|
return target.isContentEditable || target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT";
|
|
1063
1292
|
}
|
|
1064
1293
|
|
|
1065
|
-
// src/
|
|
1066
|
-
import {
|
|
1067
|
-
Children,
|
|
1068
|
-
cloneElement,
|
|
1069
|
-
isValidElement
|
|
1070
|
-
} from "react";
|
|
1071
|
-
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
1072
|
-
function KeepButton({
|
|
1073
|
-
item,
|
|
1074
|
-
children,
|
|
1075
|
-
savedLabel = "Saved",
|
|
1076
|
-
unsavedLabel = "Save",
|
|
1077
|
-
savedAriaLabel,
|
|
1078
|
-
unsavedAriaLabel,
|
|
1079
|
-
getAriaLabel,
|
|
1080
|
-
asChild = false,
|
|
1081
|
-
onToggleError,
|
|
1082
|
-
onClick,
|
|
1083
|
-
disabled,
|
|
1084
|
-
...buttonProps
|
|
1085
|
-
}) {
|
|
1086
|
-
const state = useKeepItem(item);
|
|
1087
|
-
const { isSaved, toggle } = state;
|
|
1088
|
-
const isDisabled = disabled ?? state.isMutating;
|
|
1089
|
-
async function handleClick(event) {
|
|
1090
|
-
if (isDisabled) return;
|
|
1091
|
-
if (asChild) {
|
|
1092
|
-
onClick?.(event);
|
|
1093
|
-
} else {
|
|
1094
|
-
onClick?.(
|
|
1095
|
-
event
|
|
1096
|
-
);
|
|
1097
|
-
}
|
|
1098
|
-
if (event.defaultPrevented) return;
|
|
1099
|
-
try {
|
|
1100
|
-
await toggle();
|
|
1101
|
-
} catch (error) {
|
|
1102
|
-
onToggleError?.(error);
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
const content = typeof children === "function" ? children(state) : children ?? (isSaved ? savedLabel : unsavedLabel);
|
|
1106
|
-
function handleElementClick(event) {
|
|
1107
|
-
if (isDisabled) return;
|
|
1108
|
-
if (asChild && isValidElement(content)) {
|
|
1109
|
-
content.props.onClick?.(event);
|
|
1110
|
-
}
|
|
1111
|
-
if (!event.defaultPrevented) void handleClick(event);
|
|
1112
|
-
}
|
|
1113
|
-
function handleKeyDown(event) {
|
|
1114
|
-
if (asChild && isValidElement(content)) {
|
|
1115
|
-
content.props.onKeyDown?.(event);
|
|
1116
|
-
}
|
|
1117
|
-
buttonProps.onKeyDown?.(event);
|
|
1118
|
-
if (!event.defaultPrevented && !isDisabled && asChild && (event.key === "Enter" || event.key === " ")) {
|
|
1119
|
-
void handleClick(event);
|
|
1120
|
-
event.preventDefault();
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
const child = asChild ? Children.only(content) : void 0;
|
|
1124
|
-
if (asChild && !isValidElement(child)) {
|
|
1125
|
-
throw new Error("KeepButton with asChild requires a single React element child.");
|
|
1126
|
-
}
|
|
1127
|
-
const commonProps = {
|
|
1128
|
-
...buttonProps,
|
|
1129
|
-
"aria-pressed": isSaved,
|
|
1130
|
-
"data-state": state.error ? "error" : isSaved ? "saved" : "unsaved",
|
|
1131
|
-
"data-loading": state.isLoading || state.isMutating ? "true" : void 0,
|
|
1132
|
-
"data-disabled": isDisabled ? "true" : void 0,
|
|
1133
|
-
"aria-label": ("aria-label" in buttonProps ? buttonProps["aria-label"] : void 0) ?? getAriaLabel?.(state) ?? (isSaved ? savedAriaLabel : unsavedAriaLabel) ?? getAccessibleLabel(isSaved, item, asChild),
|
|
1134
|
-
...asChild ? {
|
|
1135
|
-
"aria-disabled": isDisabled,
|
|
1136
|
-
role: buttonProps.role ?? "button",
|
|
1137
|
-
tabIndex: isDisabled ? -1 : buttonProps.tabIndex ?? 0
|
|
1138
|
-
} : { disabled: isDisabled },
|
|
1139
|
-
onClick: handleElementClick,
|
|
1140
|
-
onKeyDown: handleKeyDown
|
|
1141
|
-
};
|
|
1142
|
-
if (asChild) {
|
|
1143
|
-
return cloneElement(child, commonProps);
|
|
1144
|
-
}
|
|
1145
|
-
return /* @__PURE__ */ jsx2("button", { ...commonProps, type: "type" in buttonProps ? buttonProps.type ?? "button" : "button", children: content });
|
|
1146
|
-
}
|
|
1147
|
-
function getAccessibleLabel(isSaved, item, asChild) {
|
|
1148
|
-
if (!asChild) return isSaved ? "Remove saved item" : "Save item";
|
|
1149
|
-
const title = getMetaTitle(item.meta);
|
|
1150
|
-
const subject = title ? `${item.targetType ?? "item"}: ${title}` : item.targetType ?? "item";
|
|
1151
|
-
return `${isSaved ? "Remove" : "Save"} ${subject}`;
|
|
1152
|
-
}
|
|
1153
|
-
function getMetaTitle(meta) {
|
|
1154
|
-
if (typeof meta !== "object" || meta === null || !("title" in meta)) return void 0;
|
|
1155
|
-
const title = meta.title;
|
|
1156
|
-
return typeof title === "string" && title.trim() ? title.trim() : void 0;
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
// src/createKeepKit.tsx
|
|
1294
|
+
// src/react/createKeepKit.tsx
|
|
1160
1295
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
1161
1296
|
function createKeepKit(options = {}) {
|
|
1162
1297
|
return {
|
|
@@ -1165,6 +1300,7 @@ function createKeepKit(options = {}) {
|
|
|
1165
1300
|
useContext: () => useKeepContext(),
|
|
1166
1301
|
useItem: (item) => useKeepItem(item),
|
|
1167
1302
|
useList: (query) => useKeepList(query),
|
|
1303
|
+
useNavigator: (navigatorOptions) => useKeepNavigator(navigatorOptions),
|
|
1168
1304
|
useShortcut: (shortcutOptions) => useKeepShortcut(shortcutOptions)
|
|
1169
1305
|
};
|
|
1170
1306
|
}
|
|
@@ -1177,6 +1313,7 @@ export {
|
|
|
1177
1313
|
useKeepContext,
|
|
1178
1314
|
useKeepItem,
|
|
1179
1315
|
useKeepList,
|
|
1316
|
+
useKeepNavigator,
|
|
1180
1317
|
useKeepShortcut,
|
|
1181
1318
|
useKeepStore
|
|
1182
1319
|
};
|