@keepkit/core 0.10.0 → 0.11.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/core.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  queryKeepItems,
14
14
  reconcileKeepItems,
15
15
  revalidateKeepItems
16
- } from "./chunk-XF5VI6O5.js";
16
+ } from "./chunk-MDTL5L64.js";
17
17
  import {
18
18
  KeepSchemaValidationError,
19
19
  parseKeepMeta,
@@ -36,11 +36,16 @@ import {
36
36
  KeepStorageQuotaError,
37
37
  LocalStorageAdapter,
38
38
  LocalStorageSyncQueueAdapter,
39
+ ScopedStorageAdapter,
40
+ ScopedSyncQueueAdapter,
39
41
  SyncStorageAdapter,
40
42
  createBrowserStorageAdapter,
43
+ createScopedStorageAdapter,
41
44
  createStorageAdapter,
45
+ getKeepScopeKey,
46
+ isSameKeepScope,
42
47
  normalizeKeepTags
43
- } from "./chunk-EXE4E3GA.js";
48
+ } from "./chunk-36YIELZE.js";
44
49
 
45
50
  // src/integrations.ts
46
51
  function createKeepInvalidationPlugin(options) {
@@ -52,9 +57,97 @@ function createKeepInvalidationPlugin(options) {
52
57
  }
53
58
  };
54
59
  }
60
+
61
+ // src/presets.ts
62
+ function createKeepKitPreset(options = {}) {
63
+ const mode = options.mode ?? "local";
64
+ const local = options.storage ? options.scope ? createScopedStorageAdapter(options.storage, options.scope) : options.storage : createBrowserStorageAdapter({ key: options.key, scope: options.scope });
65
+ if (mode === "sync" && !options.remote) {
66
+ throw new Error('createKeepKitPreset({ mode: "sync" }) requires a remote driver.');
67
+ }
68
+ let storage = local;
69
+ if (mode === "sync") {
70
+ const remote = options.remote;
71
+ if (!remote) throw new Error('createKeepKitPreset({ mode: "sync" }) requires a remote driver.');
72
+ storage = new SyncStorageAdapter({
73
+ local,
74
+ remote,
75
+ userId: options.scope?.userId,
76
+ tenantId: options.scope?.tenantId
77
+ });
78
+ }
79
+ return {
80
+ mode,
81
+ scope: options.scope,
82
+ storage,
83
+ exportBackup: () => exportItems(storage)
84
+ };
85
+ }
86
+ var createKeepKitSetup = createKeepKitPreset;
87
+
88
+ // src/url.ts
89
+ var DEFAULT_KEEP_URL_PARAMS = {
90
+ search: "q",
91
+ tags: "tag",
92
+ sort: "sort",
93
+ page: "page"
94
+ };
95
+ function encodeKeepListQuery(query = {}, options = {}) {
96
+ const params = { ...DEFAULT_KEEP_URL_PARAMS, ...options.params };
97
+ const result = new URLSearchParams();
98
+ const search = query.search?.query?.trim();
99
+ if (search) result.set(params.search, search);
100
+ for (const tag of query.tags ?? []) {
101
+ const normalized = tag.trim();
102
+ if (normalized) result.append(params.tags, normalized);
103
+ }
104
+ if (query.sort?.by) result.set(params.sort, `${query.sort.by}:${query.sort.direction ?? "desc"}`);
105
+ const page = query.pagination?.page;
106
+ if (page !== void 0 && Number.isFinite(page) && page > 1) result.set(params.page, String(Math.floor(page)));
107
+ return result;
108
+ }
109
+ function decodeKeepListQuery(input, options = {}) {
110
+ const params = { ...DEFAULT_KEEP_URL_PARAMS, ...options.params };
111
+ const searchParams = input instanceof URLSearchParams ? input : new URL(input, "http://keepkit.invalid").searchParams;
112
+ const search = searchParams.get(params.search)?.trim();
113
+ const tags = [
114
+ ...new Set(
115
+ searchParams.getAll(params.tags).map((tag) => tag.trim()).filter(Boolean)
116
+ )
117
+ ];
118
+ const sortValue = searchParams.get(params.sort)?.split(":");
119
+ const sort = sortValue?.[0] === "savedAt" || sortValue?.[0] === "updatedAt" ? {
120
+ by: sortValue[0],
121
+ direction: sortValue[1] === "asc" ? "asc" : "desc"
122
+ } : void 0;
123
+ const rawPage = Number(searchParams.get(params.page));
124
+ const page = Number.isInteger(rawPage) && rawPage > 0 ? rawPage : void 0;
125
+ return {
126
+ ...search ? { search: { query: search } } : {},
127
+ ...tags.length > 0 ? { tags } : {},
128
+ ...sort ? { sort } : {},
129
+ ...page ? { pagination: { page } } : {}
130
+ };
131
+ }
132
+ function serializeKeepListQuery(query = {}, options = {}) {
133
+ const value = encodeKeepListQuery(query, options).toString();
134
+ return value ? `?${value}` : "";
135
+ }
136
+ function mergeKeepListQueryFromUrl(query, input, options = {}) {
137
+ const decoded = decodeKeepListQuery(input, options);
138
+ return {
139
+ ...query,
140
+ ...decoded,
141
+ search: decoded.search ?? query.search,
142
+ tags: decoded.tags ?? query.tags,
143
+ sort: decoded.sort ?? query.sort,
144
+ pagination: decoded.pagination ? { ...query.pagination, ...decoded.pagination } : query.pagination
145
+ };
146
+ }
55
147
  export {
56
148
  DEFAULT_INDEXEDDB_DATABASE,
57
149
  DEFAULT_INDEXEDDB_STORE,
150
+ DEFAULT_KEEP_URL_PARAMS,
58
151
  DEFAULT_STORAGE_KEY,
59
152
  DEFAULT_SYNC_QUEUE_DATABASE,
60
153
  DEFAULT_SYNC_QUEUE_KEY,
@@ -75,21 +168,32 @@ export {
75
168
  KeepStore,
76
169
  LocalStorageAdapter,
77
170
  LocalStorageSyncQueueAdapter,
171
+ ScopedStorageAdapter,
172
+ ScopedSyncQueueAdapter,
78
173
  SyncStorageAdapter,
79
174
  createBrowserStorageAdapter,
80
175
  createKeepInvalidationPlugin,
176
+ createKeepKitPreset,
177
+ createKeepKitSetup,
178
+ createScopedStorageAdapter,
81
179
  createStorageAdapter,
180
+ decodeKeepListQuery,
181
+ encodeKeepListQuery,
82
182
  exportItems,
183
+ getKeepScopeKey,
83
184
  getTagCounts,
84
185
  importItems,
85
186
  isKeepItemMetadataStale,
187
+ isSameKeepScope,
86
188
  mergeKeepItems,
189
+ mergeKeepListQueryFromUrl,
87
190
  migrateKeepItems,
88
191
  normalizeKeepTags,
89
192
  parseKeepMeta,
90
193
  queryKeepItems,
91
194
  reconcileKeepItems,
92
195
  revalidateKeepItems,
196
+ serializeKeepListQuery,
93
197
  validateKeepItem
94
198
  };
95
199
  //# sourceMappingURL=core.js.map
package/dist/core.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/integrations.ts"],"sourcesContent":["import type { KeepPlugin, KeepPluginContext } from \"./types\";\n\nexport type KeepInvalidationPluginOptions<TMeta = Record<string, unknown>> = {\n /** Query keys to invalidate after a successful local KeepKit mutation. */\n queryKeys: readonly unknown[] | ((context: KeepPluginContext<TMeta>) => readonly (readonly unknown[])[]);\n /** Connect this callback to queryClient.invalidateQueries or SWR mutate. */\n invalidate: (queryKey: readonly unknown[], context: KeepPluginContext<TMeta>) => void | Promise<void>;\n name?: string;\n};\n\n/** Framework-neutral bridge for TanStack Query, SWR, and similar caches. */\nexport function createKeepInvalidationPlugin<TMeta = Record<string, unknown>>(\n options: KeepInvalidationPluginOptions<TMeta>,\n): KeepPlugin<TMeta> {\n return {\n name: options.name ?? \"keepkit-cache-invalidation\",\n after: async (context) => {\n const keys = typeof options.queryKeys === \"function\" ? options.queryKeys(context) : [options.queryKeys];\n await Promise.all(keys.map((queryKey) => options.invalidate(queryKey, context)));\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAWO,SAAS,6BACd,SACmB;AACnB,SAAO;AAAA,IACL,MAAM,QAAQ,QAAQ;AAAA,IACtB,OAAO,OAAO,YAAY;AACxB,YAAM,OAAO,OAAO,QAAQ,cAAc,aAAa,QAAQ,UAAU,OAAO,IAAI,CAAC,QAAQ,SAAS;AACtG,YAAM,QAAQ,IAAI,KAAK,IAAI,CAAC,aAAa,QAAQ,WAAW,UAAU,OAAO,CAAC,CAAC;AAAA,IACjF;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/integrations.ts","../src/presets.ts","../src/url.ts"],"sourcesContent":["import type { KeepPlugin, KeepPluginContext } from \"./types\";\n\nexport type KeepInvalidationPluginOptions<TMeta = Record<string, unknown>> = {\n /** Query keys to invalidate after a successful local KeepKit mutation. */\n queryKeys: readonly unknown[] | ((context: KeepPluginContext<TMeta>) => readonly (readonly unknown[])[]);\n /** Connect this callback to queryClient.invalidateQueries or SWR mutate. */\n invalidate: (queryKey: readonly unknown[], context: KeepPluginContext<TMeta>) => void | Promise<void>;\n name?: string;\n};\n\n/** Framework-neutral bridge for TanStack Query, SWR, and similar caches. */\nexport function createKeepInvalidationPlugin<TMeta = Record<string, unknown>>(\n options: KeepInvalidationPluginOptions<TMeta>,\n): KeepPlugin<TMeta> {\n return {\n name: options.name ?? \"keepkit-cache-invalidation\",\n after: async (context) => {\n const keys = typeof options.queryKeys === \"function\" ? options.queryKeys(context) : [options.queryKeys];\n await Promise.all(keys.map((queryKey) => options.invalidate(queryKey, context)));\n },\n };\n}\n","import { exportItems } from \"./backup\";\nimport { createScopedStorageAdapter, type KeepScope } from \"./scope\";\nimport { createBrowserStorageAdapter } from \"./storage/index\";\nimport { SyncStorageAdapter } from \"./storage/sync\";\nimport type { RemoteSyncDriver, StorageAdapter } from \"./types\";\n\nexport type KeepKitPresetMode = \"local\" | \"sync\" | \"backup\";\n\nexport type KeepKitPresetOptions<TMeta = Record<string, unknown>> = {\n mode?: KeepKitPresetMode;\n key?: string;\n scope?: KeepScope;\n remote?: RemoteSyncDriver<TMeta>;\n storage?: StorageAdapter<TMeta>;\n};\n\nexport type KeepKitSetup<TMeta = Record<string, unknown>> = {\n mode: KeepKitPresetMode;\n scope?: KeepScope;\n storage: StorageAdapter<TMeta>;\n exportBackup: () => Promise<string>;\n};\n\n/**\n * Build the recommended local/sync/backup wiring without imposing an auth or\n * API client. Pass the current user and tenant scope whenever the account changes.\n */\nexport function createKeepKitPreset<TMeta = Record<string, unknown>>(\n options: KeepKitPresetOptions<TMeta> = {},\n): KeepKitSetup<TMeta> {\n const mode = options.mode ?? \"local\";\n const local = options.storage\n ? options.scope\n ? createScopedStorageAdapter(options.storage, options.scope)\n : options.storage\n : createBrowserStorageAdapter<TMeta>({ key: options.key, scope: options.scope });\n if (mode === \"sync\" && !options.remote) {\n throw new Error('createKeepKitPreset({ mode: \"sync\" }) requires a remote driver.');\n }\n let storage: StorageAdapter<TMeta> = local;\n if (mode === \"sync\") {\n const remote = options.remote;\n if (!remote) throw new Error('createKeepKitPreset({ mode: \"sync\" }) requires a remote driver.');\n storage = new SyncStorageAdapter<TMeta>({\n local,\n remote,\n userId: options.scope?.userId,\n tenantId: options.scope?.tenantId,\n });\n }\n return {\n mode,\n scope: options.scope,\n storage,\n exportBackup: () => exportItems(storage),\n };\n}\n\nexport const createKeepKitSetup = createKeepKitPreset;\n","import type { KeepListQuery } from \"./query\";\n\nexport type KeepUrlParamNames = {\n search: string;\n tags: string;\n sort: string;\n page: string;\n};\n\nexport type KeepUrlSyncOptions = {\n /** Parameters are intentionally short so shared collection URLs stay readable. */\n params?: Partial<KeepUrlParamNames>;\n /** Push is the default so browser back/forward restores collection states. */\n history?: \"replace\" | \"push\";\n /** URL to read/write. Defaults to the current browser URL. */\n url?: string;\n};\n\nexport const DEFAULT_KEEP_URL_PARAMS: KeepUrlParamNames = {\n search: \"q\",\n tags: \"tag\",\n sort: \"sort\",\n page: \"page\",\n};\n\nexport type KeepUrlState = Pick<KeepListQuery, \"search\" | \"tags\" | \"sort\" | \"pagination\">;\n\n/** Convert a list query to stable URLSearchParams without serializing functions or unsupported filters. */\nexport function encodeKeepListQuery<TMeta = Record<string, unknown>>(\n query: KeepListQuery<TMeta> = {},\n options: Pick<KeepUrlSyncOptions, \"params\"> = {},\n): URLSearchParams {\n const params = { ...DEFAULT_KEEP_URL_PARAMS, ...options.params };\n const result = new URLSearchParams();\n const search = query.search?.query?.trim();\n if (search) result.set(params.search, search);\n for (const tag of query.tags ?? []) {\n const normalized = tag.trim();\n if (normalized) result.append(params.tags, normalized);\n }\n if (query.sort?.by) result.set(params.sort, `${query.sort.by}:${query.sort.direction ?? \"desc\"}`);\n const page = query.pagination?.page;\n if (page !== undefined && Number.isFinite(page) && page > 1) result.set(params.page, String(Math.floor(page)));\n return result;\n}\n\n/** Parse a URL into the query fields supported by KeepCollection. Invalid values are ignored. */\nexport function decodeKeepListQuery(\n input: string | URL | URLSearchParams,\n options: Pick<KeepUrlSyncOptions, \"params\"> = {},\n): KeepUrlState {\n const params = { ...DEFAULT_KEEP_URL_PARAMS, ...options.params };\n const searchParams = input instanceof URLSearchParams ? input : new URL(input, \"http://keepkit.invalid\").searchParams;\n const search = searchParams.get(params.search)?.trim();\n const tags = [\n ...new Set(\n searchParams\n .getAll(params.tags)\n .map((tag) => tag.trim())\n .filter(Boolean),\n ),\n ];\n const sortValue = searchParams.get(params.sort)?.split(\":\");\n const sort: KeepListQuery[\"sort\"] =\n sortValue?.[0] === \"savedAt\" || sortValue?.[0] === \"updatedAt\"\n ? {\n by: sortValue[0],\n direction: sortValue[1] === \"asc\" ? (\"asc\" as const) : (\"desc\" as const),\n }\n : undefined;\n const rawPage = Number(searchParams.get(params.page));\n const page = Number.isInteger(rawPage) && rawPage > 0 ? rawPage : undefined;\n return {\n ...(search ? { search: { query: search } } : {}),\n ...(tags.length > 0 ? { tags } : {}),\n ...(sort ? { sort } : {}),\n ...(page ? { pagination: { page } } : {}),\n };\n}\n\nexport function serializeKeepListQuery<TMeta = Record<string, unknown>>(\n query: KeepListQuery<TMeta> = {},\n options: Pick<KeepUrlSyncOptions, \"params\"> = {},\n): string {\n const value = encodeKeepListQuery(query, options).toString();\n return value ? `?${value}` : \"\";\n}\n\nexport function mergeKeepListQueryFromUrl<TMeta = Record<string, unknown>>(\n query: KeepListQuery<TMeta>,\n input: string | URL | URLSearchParams,\n options: Pick<KeepUrlSyncOptions, \"params\"> = {},\n): KeepListQuery<TMeta> {\n const decoded = decodeKeepListQuery(input, options);\n return {\n ...query,\n ...decoded,\n search: decoded.search ?? query.search,\n tags: decoded.tags ?? query.tags,\n sort: decoded.sort ?? query.sort,\n pagination: decoded.pagination ? { ...query.pagination, ...decoded.pagination } : query.pagination,\n };\n}\n"],"mappings":"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","names":[]}
package/dist/react.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, ButtonHTMLAttributes, MouseEvent, HTMLAttributes, ReactElement, ErrorInfo, Component, PropsWithChildren, ComponentType } from 'react';
3
- import { f as KeepItemMetadataRefresher, k as KeepItemRevalidator, R as RevalidateKeepItemsOptions, j as KeepItemRevalidationSummary, l as KeepListQuery, g as KeepItemResolver, I as ImportItemsOptions, a as ImportItemsResult, m as KeepStore, n as KeepStoreActions } from './store-Cgqx7_xd.js';
4
- export { i as KeepItemRevalidationResult, s as isKeepItemMetadataStale } from './store-Cgqx7_xd.js';
5
- import { m as KeepItemInput, a as KeepItem, k as KeepEventHandlers, S as StorageAdapter, c as KeepPlugin, K as KeepSchema, l as KeepInvalidItemPolicy, e as KeepChangeContext, u as KeepSyncState } from './types--YahIoEB.js';
6
- export { n as KeepItemStatus } from './types--YahIoEB.js';
3
+ import { g as KeepItemMetadataRefresher, l as KeepItemRevalidator, R as RevalidateKeepItemsOptions, k as KeepItemRevalidationSummary, m as KeepListQuery, h as KeepItemResolver, c as KeepAutoRevalidationOptions, I as ImportItemsOptions, a as ImportItemsResult, n as KeepStore, o as KeepStoreActions } from './url-BYFEfulm.js';
4
+ export { j as KeepItemRevalidationResult, q as KeepUrlParamNames, r as KeepUrlState, s as KeepUrlSyncOptions, y as isKeepItemMetadataStale } from './url-BYFEfulm.js';
5
+ import { p as KeepItemInput, a as KeepItem, n as KeepEventHandlers, b as StorageAdapter, f as KeepPlugin, K as KeepSchema, o as KeepInvalidItemPolicy, h as KeepChangeContext, x as KeepSyncState, z as KeepUndoState } from './types-D-xRiz1Y.js';
6
+ export { q as KeepItemStatus } from './types-D-xRiz1Y.js';
7
+ export { K as KeepScope, S as ScopedStorageAdapter } from './scope-CtLSlZhq.js';
7
8
 
8
9
  type UseKeepItemResult<TMeta = Record<string, unknown>> = {
9
10
  item: KeepItem<TMeta> | undefined;
@@ -13,6 +14,8 @@ type UseKeepItemResult<TMeta = Record<string, unknown>> = {
13
14
  error: unknown | null;
14
15
  save: () => Promise<void>;
15
16
  remove: () => Promise<void>;
17
+ removeWithUndo: () => Promise<void>;
18
+ undo: () => Promise<void>;
16
19
  toggle: () => Promise<void>;
17
20
  updateNote: (note?: string) => Promise<void>;
18
21
  updateTags: (tags?: string[]) => Promise<void>;
@@ -36,6 +39,8 @@ type UseKeepListResult<TMeta = Record<string, unknown>> = {
36
39
  error: unknown | null;
37
40
  remove: (id: string) => Promise<void>;
38
41
  removeBatch: (ids: string[]) => Promise<void>;
42
+ removeWithUndo: (id: string) => Promise<void>;
43
+ removeBatchWithUndo: (ids: string[]) => Promise<void>;
39
44
  updateTagsBatch: (ids: string[], tags?: string[]) => Promise<void>;
40
45
  addTagsBatch: (ids: string[], tags: string[]) => Promise<void>;
41
46
  removeTagsBatch: (ids: string[], tags: string[]) => Promise<void>;
@@ -122,6 +127,7 @@ type KeepContextValue<TMeta = Record<string, unknown>> = {
122
127
  error: unknown | null;
123
128
  lastChange?: KeepChangeContext<TMeta>;
124
129
  syncState: KeepSyncState;
130
+ undo: KeepUndoState;
125
131
  saveItem: (item: KeepItem<TMeta>) => Promise<void>;
126
132
  updateNote: (id: string, note?: string) => Promise<void>;
127
133
  updateTags: (id: string, tags?: string[]) => Promise<void>;
@@ -130,6 +136,9 @@ type KeepContextValue<TMeta = Record<string, unknown>> = {
130
136
  removeTagsBatch: (ids: string[], tags: string[]) => Promise<void>;
131
137
  removeItem: (id: string) => Promise<void>;
132
138
  removeItems: (ids: string[]) => Promise<void>;
139
+ removeItemWithUndo: (id: string) => Promise<void>;
140
+ removeItemsWithUndo: (ids: string[]) => Promise<void>;
141
+ undoLastRemoval: () => Promise<void>;
133
142
  clear: () => Promise<void>;
134
143
  refresh: () => Promise<void>;
135
144
  flushSync: () => Promise<void>;
@@ -158,12 +167,14 @@ type KeepProviderProps<TMeta = Record<string, unknown>> = PropsWithChildren<Keep
158
167
  boundaryResetKey?: unknown;
159
168
  validateItem?: KeepItemRevalidator<TMeta>;
160
169
  resolveItem?: KeepItemResolver<TMeta>;
170
+ autoRevalidation?: KeepAutoRevalidationOptions<TMeta>;
171
+ undoTimeoutMs?: number;
161
172
  }>;
162
173
  type KeepStoreAccess<TMeta> = {
163
174
  store: KeepStore<TMeta>;
164
175
  actions: KeepStoreActions<TMeta>;
165
176
  };
166
- declare function KeepProvider<TMeta = Record<string, unknown>>({ storage, initialItems, onSave, onRemove, onNoteUpdate, onTagsUpdate, onChange, onError, plugins, schemaVersion, schema, invalidItemPolicy, onInvalidItem, migrateMeta, fallback, onBoundaryError, boundaryResetKey, validateItem, resolveItem, children, }: KeepProviderProps<TMeta>): react.JSX.Element;
177
+ declare function KeepProvider<TMeta = Record<string, unknown>>({ storage, initialItems, onSave, onRemove, onNoteUpdate, onTagsUpdate, onChange, onUndo, onError, plugins, schemaVersion, schema, invalidItemPolicy, onInvalidItem, migrateMeta, fallback, onBoundaryError, boundaryResetKey, validateItem, resolveItem, autoRevalidation, undoTimeoutMs, children, }: KeepProviderProps<TMeta>): react.JSX.Element;
167
178
  declare function useKeepContext<TMeta = Record<string, unknown>>(): KeepContextValue<TMeta>;
168
179
  declare function useKeepStore<TMeta = Record<string, unknown>>(): KeepStoreAccess<TMeta>;
169
180
 
@@ -179,4 +190,4 @@ type KeepKit<TMeta> = {
179
190
  /** Create an app-specific, fully typed set of KeepKit components and hooks. */
180
191
  declare function createKeepKit<TMeta = Record<string, unknown>>(options?: CreateKeepKitOptions<TMeta>): KeepKit<TMeta>;
181
192
 
182
- export { type CreateKeepKitOptions, KeepButton, type KeepButtonItem, type KeepButtonProps, type KeepButtonState, type KeepContextValue, KeepErrorBoundary, type KeepErrorBoundaryProps, KeepItemMetadataRefresher, KeepItemResolver, KeepItemRevalidationSummary, KeepItemRevalidator, type KeepKit, KeepListQuery, KeepProvider, type KeepProviderProps, type KeepShortcutModifier, type KeepShortcutOptions, RevalidateKeepItemsOptions, type UseKeepItemResult, type UseKeepListResult, createKeepKit, useKeepContext, useKeepItem, useKeepList, useKeepShortcut, useKeepStore };
193
+ export { type CreateKeepKitOptions, KeepAutoRevalidationOptions, KeepButton, type KeepButtonItem, type KeepButtonProps, type KeepButtonState, type KeepContextValue, KeepErrorBoundary, type KeepErrorBoundaryProps, KeepItemMetadataRefresher, KeepItemResolver, KeepItemRevalidationSummary, KeepItemRevalidator, type KeepKit, KeepListQuery, KeepProvider, type KeepProviderProps, type KeepShortcutModifier, type KeepShortcutOptions, KeepUndoState, RevalidateKeepItemsOptions, type UseKeepItemResult, type UseKeepListResult, createKeepKit, useKeepContext, useKeepItem, useKeepList, useKeepShortcut, useKeepStore };
package/dist/react.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  isKeepItemMetadataStale,
7
7
  queryKeepItems,
8
8
  revalidateKeepItems
9
- } from "./chunk-XF5VI6O5.js";
9
+ } from "./chunk-MDTL5L64.js";
10
10
  import {
11
11
  parseKeepMeta
12
12
  } from "./chunk-THZ3ACR2.js";
@@ -14,7 +14,7 @@ import "./chunk-5QSZP6MT.js";
14
14
  import {
15
15
  createBrowserStorageAdapter,
16
16
  normalizeKeepTags
17
- } from "./chunk-EXE4E3GA.js";
17
+ } from "./chunk-36YIELZE.js";
18
18
 
19
19
  // src/hooks/useKeepItem.ts
20
20
  import { useCallback as useCallback3 } from "react";
@@ -69,6 +69,7 @@ function KeepProvider({
69
69
  onNoteUpdate,
70
70
  onTagsUpdate,
71
71
  onChange,
72
+ onUndo,
72
73
  onError,
73
74
  plugins = [],
74
75
  schemaVersion,
@@ -81,6 +82,8 @@ function KeepProvider({
81
82
  boundaryResetKey,
82
83
  validateItem,
83
84
  resolveItem,
85
+ autoRevalidation,
86
+ undoTimeoutMs,
84
87
  children
85
88
  }) {
86
89
  const content = /* @__PURE__ */ jsx(
@@ -93,6 +96,7 @@ function KeepProvider({
93
96
  onNoteUpdate,
94
97
  onTagsUpdate,
95
98
  onChange,
99
+ onUndo,
96
100
  onError,
97
101
  plugins,
98
102
  schemaVersion,
@@ -102,6 +106,8 @@ function KeepProvider({
102
106
  migrateMeta,
103
107
  validateItem,
104
108
  resolveItem,
109
+ autoRevalidation,
110
+ undoTimeoutMs,
105
111
  children
106
112
  }
107
113
  );
@@ -116,6 +122,7 @@ function KeepProviderContent({
116
122
  onNoteUpdate,
117
123
  onTagsUpdate,
118
124
  onChange,
125
+ onUndo,
119
126
  onError,
120
127
  plugins = [],
121
128
  schemaVersion,
@@ -125,6 +132,8 @@ function KeepProviderContent({
125
132
  migrateMeta,
126
133
  validateItem,
127
134
  resolveItem,
135
+ autoRevalidation,
136
+ undoTimeoutMs = 5e3,
128
137
  children
129
138
  }) {
130
139
  const storeRef = useRef(null);
@@ -135,17 +144,19 @@ function KeepProviderContent({
135
144
  isHydrated: false,
136
145
  isMutating: false,
137
146
  error: null,
138
- lastChange: void 0
147
+ lastChange: void 0,
148
+ undo: { canUndo: false, ids: [] }
139
149
  });
140
150
  }
141
151
  const store = storeRef.current;
142
152
  const state = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
143
- const { items, isLoading, isHydrated, isMutating, error, lastChange } = state;
153
+ const { items, isLoading, isHydrated, isMutating, error, lastChange, undo: storedUndo } = state;
154
+ const undo = storedUndo ?? EMPTY_UNDO_STATE;
144
155
  const itemsRef = useRef(items);
145
156
  const pluginsRef = useRef(plugins);
146
157
  pluginsRef.current = plugins;
147
- const handlersRef = useRef({ onSave, onRemove, onNoteUpdate, onTagsUpdate, onChange, onError });
148
- handlersRef.current = { onSave, onRemove, onNoteUpdate, onTagsUpdate, onChange, onError };
158
+ const handlersRef = useRef({ onSave, onRemove, onNoteUpdate, onTagsUpdate, onChange, onUndo, onError });
159
+ handlersRef.current = { onSave, onRemove, onNoteUpdate, onTagsUpdate, onChange, onUndo, onError };
149
160
  const migrationRef = useRef({
150
161
  schemaVersion,
151
162
  migrateMeta,
@@ -157,6 +168,10 @@ function KeepProviderContent({
157
168
  const operationTailRef = useRef(Promise.resolve());
158
169
  const pendingRefreshesRef = useRef(0);
159
170
  const pendingMutationsRef = useRef(0);
171
+ const undoRef = useRef(void 0);
172
+ const autoRevalidationRef = useRef(autoRevalidation);
173
+ autoRevalidationRef.current = autoRevalidation;
174
+ const didMountRevalidateRef = useRef(false);
160
175
  const syncStorage = isSyncCapableStorage(storage) ? storage : void 0;
161
176
  const getSyncState = useCallback(() => syncStorage?.getSyncState() ?? IDLE_SYNC_STATE, [syncStorage]);
162
177
  const subscribeSync = useCallback(
@@ -474,6 +489,69 @@ function KeepProviderContent({
474
489
  },
475
490
  [runMutation, storage]
476
491
  );
492
+ const rememberUndo = useCallback(
493
+ (removedItems) => {
494
+ undoRef.current?.timer && clearTimeout(undoRef.current.timer);
495
+ const expiresAt = Date.now() + Math.max(0, undoTimeoutMs);
496
+ const timer = setTimeout(
497
+ () => {
498
+ undoRef.current = void 0;
499
+ store.setState({ undo: { canUndo: false, ids: [] } });
500
+ },
501
+ Math.max(0, undoTimeoutMs)
502
+ );
503
+ undoRef.current = { items: removedItems, expiresAt, timer };
504
+ store.setState({ undo: { canUndo: true, ids: removedItems.map((item) => item.id), expiresAt } });
505
+ },
506
+ [store, undoTimeoutMs]
507
+ );
508
+ const removeItemWithUndo = useCallback(
509
+ async (id) => {
510
+ const item = itemsRef.current.find((current) => current.id === id);
511
+ await removeItem(id);
512
+ if (item) rememberUndo([item]);
513
+ },
514
+ [rememberUndo, removeItem]
515
+ );
516
+ const removeItemsWithUndo = useCallback(
517
+ async (ids) => {
518
+ const idSet = new Set(ids);
519
+ const removedItems = itemsRef.current.filter((item) => idSet.has(item.id));
520
+ await removeItems(ids);
521
+ if (removedItems.length > 0) rememberUndo(removedItems);
522
+ },
523
+ [rememberUndo, removeItems]
524
+ );
525
+ const undoLastRemoval = useCallback(async () => {
526
+ const pending = undoRef.current;
527
+ if (!pending || pending.expiresAt < Date.now()) {
528
+ undoRef.current = void 0;
529
+ store.setState({ undo: { canUndo: false, ids: [] } });
530
+ return;
531
+ }
532
+ if (pending.timer) clearTimeout(pending.timer);
533
+ undoRef.current = void 0;
534
+ store.setState({ undo: { canUndo: false, ids: [] } });
535
+ await runMutation("undo", void 0, (previous) => {
536
+ const restored = new Map(pending.items.map((item) => [item.id, item]));
537
+ const next = [...previous.filter((item) => !restored.has(item.id)), ...pending.items].sort(
538
+ (a, b) => b.updatedAt - a.updatedAt
539
+ );
540
+ return {
541
+ next,
542
+ persist: async () => {
543
+ if (storage.setMany) await storage.setMany(pending.items);
544
+ else for (const item of pending.items) await storage.set(item);
545
+ },
546
+ onSuccess: () => handlersRef.current.onUndo?.(pending.items),
547
+ pluginContext: { action: "undo", items: pending.items }
548
+ };
549
+ });
550
+ }, [runMutation, storage, store]);
551
+ useEffect(() => {
552
+ if (syncState.status !== "error" || !undoRef.current) return;
553
+ void undoLastRemoval();
554
+ }, [syncState.status, undoLastRemoval]);
477
555
  const clear = useCallback(
478
556
  () => runMutation("clear", void 0, (_previous) => ({
479
557
  next: [],
@@ -544,6 +622,27 @@ function KeepProviderContent({
544
622
  validateItem
545
623
  ]
546
624
  );
625
+ useEffect(() => {
626
+ const settings = autoRevalidationRef.current;
627
+ const activeRevalidator = settings?.revalidator ?? validateItem;
628
+ if (!settings || !activeRevalidator) return;
629
+ const run = () => void revalidateItems(activeRevalidator, { removeStatuses: settings.removeStatuses }).catch(() => void 0);
630
+ if (isHydrated && settings.onMount !== false && !didMountRevalidateRef.current) {
631
+ didMountRevalidateRef.current = true;
632
+ run();
633
+ }
634
+ const interval = settings.intervalMs && settings.intervalMs > 0 ? setInterval(run, settings.intervalMs) : void 0;
635
+ const onOnline = () => {
636
+ if (settings.onReconnect !== false) run();
637
+ };
638
+ if (typeof window !== "undefined" && settings.onReconnect !== false) {
639
+ window.addEventListener("online", onOnline);
640
+ }
641
+ return () => {
642
+ if (interval) clearInterval(interval);
643
+ if (typeof window !== "undefined") window.removeEventListener("online", onOnline);
644
+ };
645
+ }, [isHydrated, revalidateItems, validateItem]);
547
646
  const refreshItemMetadata = useCallback(
548
647
  async (id, refresh2) => {
549
648
  if (!itemsRef.current.some((item) => item.id === id)) {
@@ -594,6 +693,7 @@ function KeepProviderContent({
594
693
  error,
595
694
  lastChange,
596
695
  syncState,
696
+ undo,
597
697
  saveItem,
598
698
  updateNote,
599
699
  updateTags,
@@ -602,6 +702,9 @@ function KeepProviderContent({
602
702
  removeTagsBatch,
603
703
  removeItem,
604
704
  removeItems,
705
+ removeItemWithUndo,
706
+ removeItemsWithUndo,
707
+ undoLastRemoval,
605
708
  clear,
606
709
  refresh,
607
710
  flushSync,
@@ -620,9 +723,13 @@ function KeepProviderContent({
620
723
  isMutating,
621
724
  items,
622
725
  syncState,
726
+ undo,
623
727
  refresh,
624
728
  removeItem,
625
729
  saveItem,
730
+ removeItemWithUndo,
731
+ removeItemsWithUndo,
732
+ undoLastRemoval,
626
733
  updateNote,
627
734
  updateTags,
628
735
  updateTagsBatch,
@@ -645,6 +752,9 @@ function KeepProviderContent({
645
752
  removeTagsBatch,
646
753
  removeItem,
647
754
  removeItems,
755
+ removeItemWithUndo,
756
+ removeItemsWithUndo,
757
+ undoLastRemoval,
648
758
  clear,
649
759
  refresh,
650
760
  refreshItemMetadata,
@@ -662,7 +772,10 @@ function KeepProviderContent({
662
772
  updateTags,
663
773
  updateTagsBatch,
664
774
  refreshItemMetadata,
665
- revalidateItems
775
+ revalidateItems,
776
+ removeItemWithUndo,
777
+ removeItemsWithUndo,
778
+ undoLastRemoval
666
779
  ]
667
780
  );
668
781
  const storeAccess = useMemo(() => ({ store, actions }), [actions, store]);
@@ -673,6 +786,7 @@ var IDLE_SYNC_STATE = Object.freeze({
673
786
  pendingCount: 0,
674
787
  conflictIds: []
675
788
  });
789
+ var EMPTY_UNDO_STATE = Object.freeze({ canUndo: false, ids: [] });
676
790
  function isSyncCapableStorage(storage) {
677
791
  return "getSyncState" in storage && typeof storage.getSyncState === "function" && "subscribeSync" in storage && typeof storage.subscribeSync === "function" && "flushSync" in storage && typeof storage.flushSync === "function";
678
792
  }
@@ -745,6 +859,7 @@ function useKeepItem(input) {
745
859
  });
746
860
  }, [actions, input, item?.savedAt]);
747
861
  const remove = useCallback3(() => actions.removeItem(id), [actions, id]);
862
+ const removeWithUndo = useCallback3(() => actions.removeItemWithUndo(id), [actions, id]);
748
863
  const toggle = useCallback3(() => item ? remove() : save(), [item, remove, save]);
749
864
  const updateNote = useCallback3((note) => actions.updateNote(id, note), [actions, id]);
750
865
  const updateTags = useCallback3((tags) => actions.updateTags(id, tags), [actions, id]);
@@ -760,6 +875,8 @@ function useKeepItem(input) {
760
875
  error,
761
876
  save,
762
877
  remove,
878
+ removeWithUndo,
879
+ undo: actions.undoLastRemoval,
763
880
  toggle,
764
881
  updateNote,
765
882
  updateTags,
@@ -814,6 +931,8 @@ function useKeepList(query = {}) {
814
931
  const allTags = useKeepStoreSelector(store, tagsSelector);
815
932
  const remove = useCallback4((id) => actions.removeItem(id), [actions]);
816
933
  const removeBatch = useCallback4((ids) => actions.removeItems(ids), [actions]);
934
+ const removeWithUndo = useCallback4((id) => actions.removeItemWithUndo(id), [actions]);
935
+ const removeBatchWithUndo = useCallback4((ids) => actions.removeItemsWithUndo(ids), [actions]);
817
936
  const updateTagsBatch = useCallback4(
818
937
  (ids, nextTags) => actions.updateTagsBatch(ids, nextTags),
819
938
  [actions]
@@ -841,6 +960,8 @@ function useKeepList(query = {}) {
841
960
  error,
842
961
  remove,
843
962
  removeBatch,
963
+ removeWithUndo,
964
+ removeBatchWithUndo,
844
965
  updateTagsBatch,
845
966
  addTagsBatch,
846
967
  removeTagsBatch,