@keepkit/core 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.d.ts CHANGED
@@ -1,10 +1,10 @@
1
+ import { g as KeepItemMetadataRefresher, l as KeepItemRevalidator, h as KeepItemResolver, c as KeepAutoRevalidationOptions, R as RevalidateKeepItemsOptions, k as KeepItemRevalidationSummary, I as ImportItemsOptions, a as ImportItemsResult, o as KeepStore, p as KeepStoreActions, m as KeepListQuery, n as KeepNavigationState } from './store-Cm35R_ho.js';
2
+ export { j as KeepItemRevalidationResult, r as KeepUrlParamNames, s as KeepUrlState, t as KeepUrlSyncOptions, A as isKeepItemMetadataStale } from './store-Cm35R_ho.js';
3
+ import { r as KeepItemInput, K as KeepItem, p as KeepEventHandlers, a as StorageAdapter, e as KeepPlugin, t as KeepSchema, q as KeepInvalidItemPolicy, j as KeepChangeContext, D as KeepSyncState, F as KeepUndoState } from './types-Aoc0Eyvk.js';
4
+ export { s as KeepItemStatus } from './types-Aoc0Eyvk.js';
5
+ export { K as KeepScope, S as ScopedStorageAdapter } from './scope-Dk5FTVz8.js';
1
6
  import * as react from 'react';
2
7
  import { ReactNode, ButtonHTMLAttributes, MouseEvent, HTMLAttributes, ReactElement, ErrorInfo, Component, PropsWithChildren, ComponentType } from 'react';
3
- import { g as KeepItemMetadataRefresher, l as KeepItemRevalidator, R as RevalidateKeepItemsOptions, k as KeepItemRevalidationSummary, m as KeepListQuery, n as KeepNavigationState, h as KeepItemResolver, c as KeepAutoRevalidationOptions, I as ImportItemsOptions, a as ImportItemsResult, o as KeepStore, p as KeepStoreActions } from './url-Blx4SPKD.js';
4
- export { j as KeepItemRevalidationResult, r as KeepUrlParamNames, s as KeepUrlState, t as KeepUrlSyncOptions, A as isKeepItemMetadataStale } from './url-Blx4SPKD.js';
5
- import { s as KeepItemInput, a as KeepItem, q as KeepEventHandlers, b as StorageAdapter, f as KeepPlugin, K as KeepSchema, r as KeepInvalidItemPolicy, k as KeepChangeContext, D as KeepSyncState, F as KeepUndoState } from './types-B3xc8-Pi.js';
6
- export { t as KeepItemStatus } from './types-B3xc8-Pi.js';
7
- export { K as KeepScope, S as ScopedStorageAdapter } from './scope-D52NbAWR.js';
8
8
 
9
9
  type UseKeepItemResult<TMeta = Record<string, unknown>> = {
10
10
  item: KeepItem<TMeta> | undefined;
@@ -24,63 +24,6 @@ type UseKeepItemResult<TMeta = Record<string, unknown>> = {
24
24
  /** Read and mutate one saved item from its complete minimal input description. */
25
25
  declare function useKeepItem<TMeta = Record<string, unknown>>(input?: KeepItemInput<TMeta>): UseKeepItemResult<TMeta>;
26
26
 
27
- type UseKeepListResult<TMeta = Record<string, unknown>> = {
28
- items: KeepItem<TMeta>[];
29
- totalCount: number;
30
- tags: string[];
31
- tagCounts: Record<string, number>;
32
- page: number;
33
- pageCount: number;
34
- hasNextPage: boolean;
35
- hasPreviousPage: boolean;
36
- isLoading: boolean;
37
- isHydrated: boolean;
38
- isMutating: boolean;
39
- error: unknown | null;
40
- remove: (id: string) => Promise<void>;
41
- removeBatch: (ids: string[]) => Promise<void>;
42
- removeWithUndo: (id: string) => Promise<void>;
43
- removeBatchWithUndo: (ids: string[]) => Promise<void>;
44
- updateTagsBatch: (ids: string[], tags?: string[]) => Promise<void>;
45
- addTagsBatch: (ids: string[], tags: string[]) => Promise<void>;
46
- removeTagsBatch: (ids: string[], tags: string[]) => Promise<void>;
47
- reorder: (orderedIds: string[]) => Promise<void>;
48
- move: (id: string, targetIndex: number) => Promise<void>;
49
- clear: () => Promise<void>;
50
- refresh: () => Promise<void>;
51
- revalidate: (revalidator: KeepItemRevalidator<TMeta>, options?: RevalidateKeepItemsOptions<TMeta>) => Promise<KeepItemRevalidationSummary<TMeta>>;
52
- };
53
- declare function useKeepList<TMeta = Record<string, unknown>>(query?: KeepListQuery<TMeta>): UseKeepListResult<TMeta>;
54
-
55
- type UseKeepNavigatorOptions<TMeta = Record<string, unknown>> = {
56
- currentId?: string;
57
- initialIndex?: number;
58
- query?: Omit<KeepListQuery<TMeta>, "pagination">;
59
- };
60
- type UseKeepNavigatorResult<TMeta = Record<string, unknown>> = KeepNavigationState<TMeta> & {
61
- goToNext: () => KeepItem<TMeta> | null;
62
- goToPrev: () => KeepItem<TMeta> | null;
63
- goToIndex: (index: number) => KeepItem<TMeta> | null;
64
- goToItem: (id: string) => KeepItem<TMeta> | null;
65
- };
66
- /** Derive a stable previous/current/next view and pointer actions from the provider store. */
67
- declare function useKeepNavigator<TMeta = Record<string, unknown>>(options?: UseKeepNavigatorOptions<TMeta>): UseKeepNavigatorResult<TMeta>;
68
-
69
- type KeepShortcutModifier = "meta" | "ctrl" | "alt" | "shift";
70
- type KeepShortcutOptions<TMeta = Record<string, unknown>> = {
71
- key: string;
72
- modifier?: KeepShortcutModifier;
73
- item?: KeepItemInput<TMeta>;
74
- action?: "toggle" | "save" | "remove";
75
- enabled?: boolean;
76
- preventDefault?: boolean;
77
- allowInEditable?: boolean;
78
- onTrigger?: (event: KeyboardEvent) => void | Promise<void>;
79
- onError?: (error: unknown) => void;
80
- };
81
- /** Bind a keyboard shortcut to a Keep action or an arbitrary command. */
82
- declare function useKeepShortcut<TMeta = Record<string, unknown>>(options: KeepShortcutOptions<TMeta>): void;
83
-
84
27
  type KeepButtonItem<TMeta = Record<string, unknown>> = KeepItemInput<TMeta>;
85
28
  type KeepButtonSharedProps<TMeta> = {
86
29
  item: KeepButtonItem<TMeta>;
@@ -197,6 +140,63 @@ declare function KeepProvider<TMeta = Record<string, unknown>>({ storage, initia
197
140
  declare function useKeepContext<TMeta = Record<string, unknown>>(): KeepContextValue<TMeta>;
198
141
  declare function useKeepStore<TMeta = Record<string, unknown>>(): KeepStoreAccess<TMeta>;
199
142
 
143
+ type UseKeepListResult<TMeta = Record<string, unknown>> = {
144
+ items: KeepItem<TMeta>[];
145
+ totalCount: number;
146
+ tags: string[];
147
+ tagCounts: Record<string, number>;
148
+ page: number;
149
+ pageCount: number;
150
+ hasNextPage: boolean;
151
+ hasPreviousPage: boolean;
152
+ isLoading: boolean;
153
+ isHydrated: boolean;
154
+ isMutating: boolean;
155
+ error: unknown | null;
156
+ remove: (id: string) => Promise<void>;
157
+ removeBatch: (ids: string[]) => Promise<void>;
158
+ removeWithUndo: (id: string) => Promise<void>;
159
+ removeBatchWithUndo: (ids: string[]) => Promise<void>;
160
+ updateTagsBatch: (ids: string[], tags?: string[]) => Promise<void>;
161
+ addTagsBatch: (ids: string[], tags: string[]) => Promise<void>;
162
+ removeTagsBatch: (ids: string[], tags: string[]) => Promise<void>;
163
+ reorder: (orderedIds: string[]) => Promise<void>;
164
+ move: (id: string, targetIndex: number) => Promise<void>;
165
+ clear: () => Promise<void>;
166
+ refresh: () => Promise<void>;
167
+ revalidate: (revalidator: KeepItemRevalidator<TMeta>, options?: RevalidateKeepItemsOptions<TMeta>) => Promise<KeepItemRevalidationSummary<TMeta>>;
168
+ };
169
+ declare function useKeepList<TMeta = Record<string, unknown>>(query?: KeepListQuery<TMeta>): UseKeepListResult<TMeta>;
170
+
171
+ type UseKeepNavigatorOptions<TMeta = Record<string, unknown>> = {
172
+ currentId?: string;
173
+ initialIndex?: number;
174
+ query?: Omit<KeepListQuery<TMeta>, "pagination">;
175
+ };
176
+ type UseKeepNavigatorResult<TMeta = Record<string, unknown>> = KeepNavigationState<TMeta> & {
177
+ goToNext: () => KeepItem<TMeta> | null;
178
+ goToPrev: () => KeepItem<TMeta> | null;
179
+ goToIndex: (index: number) => KeepItem<TMeta> | null;
180
+ goToItem: (id: string) => KeepItem<TMeta> | null;
181
+ };
182
+ /** Derive a stable previous/current/next view and pointer actions from the provider store. */
183
+ declare function useKeepNavigator<TMeta = Record<string, unknown>>(options?: UseKeepNavigatorOptions<TMeta>): UseKeepNavigatorResult<TMeta>;
184
+
185
+ type KeepShortcutModifier = "meta" | "ctrl" | "alt" | "shift";
186
+ type KeepShortcutOptions<TMeta = Record<string, unknown>> = {
187
+ key: string;
188
+ modifier?: KeepShortcutModifier;
189
+ item?: KeepItemInput<TMeta>;
190
+ action?: "toggle" | "save" | "remove";
191
+ enabled?: boolean;
192
+ preventDefault?: boolean;
193
+ allowInEditable?: boolean;
194
+ onTrigger?: (event: KeyboardEvent) => void | Promise<void>;
195
+ onError?: (error: unknown) => void;
196
+ };
197
+ /** Bind a keyboard shortcut to a Keep action or an arbitrary command. */
198
+ declare function useKeepShortcut<TMeta = Record<string, unknown>>(options: KeepShortcutOptions<TMeta>): void;
199
+
200
200
  type CreateKeepKitOptions<TMeta = Record<string, unknown>> = Omit<KeepProviderProps<TMeta>, "children">;
201
201
  type KeepKit<TMeta> = {
202
202
  Provider: ComponentType<KeepProviderProps<TMeta>>;
package/dist/react.js CHANGED
@@ -10,19 +10,26 @@ import {
10
10
  queryKeepItems,
11
11
  reorderKeepItems,
12
12
  revalidateKeepItems
13
- } from "./chunk-XWZ6GRD4.js";
13
+ } from "./chunk-62I2YZYI.js";
14
14
  import {
15
15
  parseKeepMeta
16
- } from "./chunk-THZ3ACR2.js";
16
+ } from "./chunk-5W4QSJHV.js";
17
17
  import {
18
18
  createBrowserStorageAdapter,
19
19
  normalizeKeepTags
20
- } from "./chunk-XIBTMJ4R.js";
20
+ } from "./chunk-PNP7OALR.js";
21
21
 
22
- // src/hooks/useKeepItem.ts
22
+ // src/react/components/KeepButton.tsx
23
+ import {
24
+ Children,
25
+ cloneElement,
26
+ isValidElement
27
+ } from "react";
28
+
29
+ // src/react/hooks/useKeepItem.ts
23
30
  import { useCallback as useCallback3 } from "react";
24
31
 
25
- // src/KeepProvider.tsx
32
+ // src/react/components/KeepProvider.tsx
26
33
  import {
27
34
  createContext,
28
35
  useCallback,
@@ -33,7 +40,7 @@ import {
33
40
  useSyncExternalStore
34
41
  } from "react";
35
42
 
36
- // src/KeepErrorBoundary.tsx
43
+ // src/react/components/KeepErrorBoundary.tsx
37
44
  import { Component } from "react";
38
45
  var KeepErrorBoundary = class extends Component {
39
46
  constructor() {
@@ -59,7 +66,7 @@ var KeepErrorBoundary = class extends Component {
59
66
  }
60
67
  };
61
68
 
62
- // src/KeepProvider.tsx
69
+ // src/react/components/KeepProvider.tsx
63
70
  import { jsx } from "react/jsx-runtime";
64
71
  var defaultStorage = createBrowserStorageAdapter();
65
72
  var KeepContext = createContext(null);
@@ -565,7 +572,8 @@ function KeepProviderContent({
565
572
  const rememberUndo = useCallback(
566
573
  (removedItems) => {
567
574
  undoRef.current?.timer && clearTimeout(undoRef.current.timer);
568
- const expiresAt = Date.now() + Math.max(0, undoTimeoutMs);
575
+ const startedAt = Date.now();
576
+ const expiresAt = startedAt + Math.max(0, undoTimeoutMs);
569
577
  const timer = setTimeout(
570
578
  () => {
571
579
  undoRef.current = void 0;
@@ -574,7 +582,7 @@ function KeepProviderContent({
574
582
  Math.max(0, undoTimeoutMs)
575
583
  );
576
584
  undoRef.current = { items: removedItems, expiresAt, timer };
577
- 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 } });
578
586
  },
579
587
  [store, undoTimeoutMs]
580
588
  );
@@ -910,7 +918,7 @@ function useKeepStore() {
910
918
  return context;
911
919
  }
912
920
 
913
- // src/hooks/useKeepStoreSelector.ts
921
+ // src/react/hooks/useKeepStoreSelector.ts
914
922
  import { useCallback as useCallback2, useRef as useRef2, useSyncExternalStore as useSyncExternalStore2 } from "react";
915
923
  function useKeepStoreSelector(store, selector) {
916
924
  const cacheRef = useRef2(null);
@@ -925,7 +933,7 @@ function useKeepStoreSelector(store, selector) {
925
933
  return useSyncExternalStore2(store.subscribe, getSelectedSnapshot, getSelectedSnapshot);
926
934
  }
927
935
 
928
- // src/hooks/useKeepItem.ts
936
+ // src/react/hooks/useKeepItem.ts
929
937
  function useKeepItem(input) {
930
938
  const { store, actions } = useKeepStore();
931
939
  const id = input?.id ?? "";
@@ -982,7 +990,96 @@ function useKeepItem(input) {
982
990
  };
983
991
  }
984
992
 
985
- // src/hooks/useKeepList.ts
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
986
1083
  import { useCallback as useCallback4, useMemo as useMemo2 } from "react";
987
1084
  function useKeepList(query = {}) {
988
1085
  const { store, actions } = useKeepStore();
@@ -1082,7 +1179,7 @@ function sameCounts(left, right) {
1082
1179
  });
1083
1180
  }
1084
1181
 
1085
- // src/hooks/useKeepNavigator.ts
1182
+ // src/react/hooks/useKeepNavigator.ts
1086
1183
  import { useCallback as useCallback5, useMemo as useMemo3, useState } from "react";
1087
1184
  function useKeepNavigator(options = {}) {
1088
1185
  const { store } = useKeepStore();
@@ -1138,7 +1235,7 @@ function useKeepNavigator(options = {}) {
1138
1235
  return { ...navigation, goToNext, goToPrev, goToIndex, goToItem };
1139
1236
  }
1140
1237
 
1141
- // src/hooks/useKeepShortcut.ts
1238
+ // src/react/hooks/useKeepShortcut.ts
1142
1239
  import { useEffect as useEffect2 } from "react";
1143
1240
  function useKeepShortcut(options) {
1144
1241
  const item = useKeepItem(options.item);
@@ -1194,101 +1291,7 @@ function isEditableTarget(target) {
1194
1291
  return target.isContentEditable || target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.tagName === "SELECT";
1195
1292
  }
1196
1293
 
1197
- // src/KeepButton.tsx
1198
- import {
1199
- Children,
1200
- cloneElement,
1201
- isValidElement
1202
- } from "react";
1203
- import { jsx as jsx2 } from "react/jsx-runtime";
1204
- function KeepButton({
1205
- item,
1206
- children,
1207
- savedLabel = "Saved",
1208
- unsavedLabel = "Save",
1209
- savedAriaLabel,
1210
- unsavedAriaLabel,
1211
- getAriaLabel,
1212
- asChild = false,
1213
- onToggleError,
1214
- onClick,
1215
- disabled,
1216
- ...buttonProps
1217
- }) {
1218
- const state = useKeepItem(item);
1219
- const { isSaved, toggle } = state;
1220
- const isDisabled = disabled ?? state.isMutating;
1221
- async function handleClick(event) {
1222
- if (isDisabled) return;
1223
- if (asChild) {
1224
- onClick?.(event);
1225
- } else {
1226
- onClick?.(
1227
- event
1228
- );
1229
- }
1230
- if (event.defaultPrevented) return;
1231
- try {
1232
- await toggle();
1233
- } catch (error) {
1234
- onToggleError?.(error);
1235
- }
1236
- }
1237
- const content = typeof children === "function" ? children(state) : children ?? (isSaved ? savedLabel : unsavedLabel);
1238
- function handleElementClick(event) {
1239
- if (isDisabled) return;
1240
- if (asChild && isValidElement(content)) {
1241
- content.props.onClick?.(event);
1242
- }
1243
- if (!event.defaultPrevented) void handleClick(event);
1244
- }
1245
- function handleKeyDown(event) {
1246
- if (asChild && isValidElement(content)) {
1247
- content.props.onKeyDown?.(event);
1248
- }
1249
- buttonProps.onKeyDown?.(event);
1250
- if (!event.defaultPrevented && !isDisabled && asChild && (event.key === "Enter" || event.key === " ")) {
1251
- void handleClick(event);
1252
- event.preventDefault();
1253
- }
1254
- }
1255
- const child = asChild ? Children.only(content) : void 0;
1256
- if (asChild && !isValidElement(child)) {
1257
- throw new Error("KeepButton with asChild requires a single React element child.");
1258
- }
1259
- const commonProps = {
1260
- ...buttonProps,
1261
- "aria-pressed": isSaved,
1262
- "data-state": state.error ? "error" : isSaved ? "saved" : "unsaved",
1263
- "data-loading": state.isLoading || state.isMutating ? "true" : void 0,
1264
- "data-disabled": isDisabled ? "true" : void 0,
1265
- "aria-label": ("aria-label" in buttonProps ? buttonProps["aria-label"] : void 0) ?? getAriaLabel?.(state) ?? (isSaved ? savedAriaLabel : unsavedAriaLabel) ?? getAccessibleLabel(isSaved, item, asChild),
1266
- ...asChild ? {
1267
- "aria-disabled": isDisabled,
1268
- role: buttonProps.role ?? "button",
1269
- tabIndex: isDisabled ? -1 : buttonProps.tabIndex ?? 0
1270
- } : { disabled: isDisabled },
1271
- onClick: handleElementClick,
1272
- onKeyDown: handleKeyDown
1273
- };
1274
- if (asChild) {
1275
- return cloneElement(child, commonProps);
1276
- }
1277
- return /* @__PURE__ */ jsx2("button", { ...commonProps, type: "type" in buttonProps ? buttonProps.type ?? "button" : "button", children: content });
1278
- }
1279
- function getAccessibleLabel(isSaved, item, asChild) {
1280
- if (!asChild) return isSaved ? "Remove saved item" : "Save item";
1281
- const title = getMetaTitle(item.meta);
1282
- const subject = title ? `${item.targetType ?? "item"}: ${title}` : item.targetType ?? "item";
1283
- return `${isSaved ? "Remove" : "Save"} ${subject}`;
1284
- }
1285
- function getMetaTitle(meta) {
1286
- if (typeof meta !== "object" || meta === null || !("title" in meta)) return void 0;
1287
- const title = meta.title;
1288
- return typeof title === "string" && title.trim() ? title.trim() : void 0;
1289
- }
1290
-
1291
- // src/createKeepKit.tsx
1294
+ // src/react/createKeepKit.tsx
1292
1295
  import { jsx as jsx3 } from "react/jsx-runtime";
1293
1296
  function createKeepKit(options = {}) {
1294
1297
  return {