@keepkit/ui 0.8.0 → 0.10.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/index.js CHANGED
@@ -6,12 +6,176 @@ import {
6
6
  createKeepKit as createCoreKeepKit
7
7
  } from "@keepkit/core/react";
8
8
 
9
+ // src/KeepBackup.tsx
10
+ import { useKeepContext } from "@keepkit/core/react";
11
+ import { useRef, useState } from "react";
12
+
13
+ // src/ui-context.tsx
14
+ import { createContext, useContext, useMemo } from "react";
15
+ import { jsx } from "react/jsx-runtime";
16
+ var DEFAULT_LABELS = {
17
+ save: "Save",
18
+ saved: "Saved",
19
+ remove: "Remove",
20
+ loading: "Loading\u2026",
21
+ saving: "Saving\u2026",
22
+ syncing: "Syncing\u2026",
23
+ error: "Something went wrong.",
24
+ allTags: "All",
25
+ filterTags: "Filter saved items by tag",
26
+ note: "Note",
27
+ saveNote: "Save note",
28
+ noItems: "No saved items.",
29
+ loadingItems: "Loading saved items\u2026",
30
+ errorItems: "Could not load saved items.",
31
+ search: "Search saved items",
32
+ sort: "Sort saved items",
33
+ newest: "Newest first",
34
+ oldest: "Oldest first",
35
+ savedNewest: "Saved newest first",
36
+ savedOldest: "Saved oldest first",
37
+ updatedNewest: "Updated newest first",
38
+ updatedOldest: "Updated oldest first",
39
+ previousPage: "Previous page",
40
+ nextPage: "Next page",
41
+ pagination: "Pagination",
42
+ page: "Page",
43
+ selectItems: "Select saved items",
44
+ selectedCount: "selected",
45
+ deleteSelected: "Delete selected",
46
+ tagsToApply: "Tags to apply",
47
+ applyTags: "Apply tags",
48
+ savedMessage: "Item saved.",
49
+ removedMessage: "Item removed.",
50
+ noteSavedMessage: "Note saved.",
51
+ exportData: "Export JSON",
52
+ importData: "Import JSON",
53
+ importMode: "Import mode",
54
+ merge: "Merge",
55
+ replace: "Replace",
56
+ importedCount: "items imported",
57
+ failedCount: "items failed",
58
+ storageQuotaError: "Storage is full. Free space and try again.",
59
+ statusExpired: "Expired",
60
+ statusRemoved: "Removed",
61
+ statusDeleted: "Deleted",
62
+ statusPrivate: "Private",
63
+ statusUnknown: "Unavailable"
64
+ };
65
+ var KeepUiLabelsContext = createContext({ labels: DEFAULT_LABELS });
66
+ function KeepUiProvider({ labels, locale, labelResolver, children }) {
67
+ const value = useMemo(() => {
68
+ const resolved = { ...DEFAULT_LABELS, ...labels };
69
+ return { locale, labels: resolved, labelResolver };
70
+ }, [labelResolver, labels, locale]);
71
+ return /* @__PURE__ */ jsx(KeepUiLabelsContext.Provider, { value, children });
72
+ }
73
+ function useKeepUiLabels() {
74
+ return useContext(KeepUiLabelsContext);
75
+ }
76
+ function useUiLabel(key, override) {
77
+ const context = useKeepUiLabels();
78
+ return override ?? context.labelResolver?.(key, { locale: context.locale }) ?? context.labels[key];
79
+ }
80
+
81
+ // src/KeepBackup.tsx
82
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
83
+ function KeepBackup({
84
+ filename = "keepkit-backup.json",
85
+ onExport,
86
+ onImported,
87
+ ...props
88
+ }) {
89
+ const context = useKeepContext();
90
+ const exportLabel = useUiLabel("exportData");
91
+ const importLabel = useUiLabel("importData");
92
+ const importModeLabel = useUiLabel("importMode");
93
+ const mergeLabel = useUiLabel("merge");
94
+ const replaceLabel = useUiLabel("replace");
95
+ const importedCountLabel = useUiLabel("importedCount");
96
+ const failedCountLabel = useUiLabel("failedCount");
97
+ const quotaErrorLabel = useUiLabel("storageQuotaError");
98
+ const inputRef = useRef(null);
99
+ const [mode, setMode] = useState("merge");
100
+ const [result, setResult] = useState();
101
+ const [error, setError] = useState();
102
+ async function handleExport() {
103
+ setError(void 0);
104
+ try {
105
+ const data = await context.exportBackup();
106
+ onExport?.(data);
107
+ if (typeof document === "undefined") return;
108
+ const url = URL.createObjectURL(new Blob([data], { type: "application/json" }));
109
+ const anchor = document.createElement("a");
110
+ anchor.href = url;
111
+ anchor.download = filename;
112
+ anchor.click();
113
+ URL.revokeObjectURL(url);
114
+ } catch (cause) {
115
+ setError(cause);
116
+ }
117
+ }
118
+ async function handleImport(event) {
119
+ const file = event.currentTarget.files?.[0];
120
+ event.currentTarget.value = "";
121
+ if (!file) return;
122
+ setError(void 0);
123
+ setResult(void 0);
124
+ try {
125
+ const imported = await context.importBackup(await file.text(), { mode });
126
+ setResult(imported);
127
+ onImported?.(imported);
128
+ } catch (cause) {
129
+ setError(cause);
130
+ }
131
+ }
132
+ return /* @__PURE__ */ jsxs("section", { ...props, "data-state": error ? "error" : result ? "complete" : "idle", children: [
133
+ /* @__PURE__ */ jsx2("button", { type: "button", onClick: () => void handleExport(), disabled: context.isMutating, children: exportLabel }),
134
+ /* @__PURE__ */ jsxs("label", { children: [
135
+ importModeLabel,
136
+ /* @__PURE__ */ jsxs("select", { value: mode, onChange: (event) => setMode(event.currentTarget.value), children: [
137
+ /* @__PURE__ */ jsx2("option", { value: "merge", children: mergeLabel }),
138
+ /* @__PURE__ */ jsx2("option", { value: "replace", children: replaceLabel })
139
+ ] })
140
+ ] }),
141
+ /* @__PURE__ */ jsx2("button", { type: "button", onClick: () => inputRef.current?.click(), disabled: context.isMutating, children: importLabel }),
142
+ /* @__PURE__ */ jsx2(
143
+ "input",
144
+ {
145
+ ref: inputRef,
146
+ type: "file",
147
+ accept: "application/json,.json",
148
+ "aria-label": importLabel,
149
+ onChange: (event) => void handleImport(event)
150
+ }
151
+ ),
152
+ result ? /* @__PURE__ */ jsxs("p", { role: "status", children: [
153
+ result.imported,
154
+ " ",
155
+ importedCountLabel,
156
+ "; ",
157
+ result.failed,
158
+ " ",
159
+ failedCountLabel
160
+ ] }) : null,
161
+ error ? /* @__PURE__ */ jsx2("p", { role: "alert", children: isQuotaError(error) ? quotaErrorLabel : getErrorMessage(error) }) : null
162
+ ] });
163
+ }
164
+ function isQuotaError(error) {
165
+ if (error instanceof Error && error.name === "KeepStorageQuotaError") return true;
166
+ if (error && typeof error === "object" && "cause" in error) return isQuotaError(error.cause);
167
+ return false;
168
+ }
169
+ function getErrorMessage(error) {
170
+ return error instanceof Error ? error.message : "Something went wrong.";
171
+ }
172
+
9
173
  // src/KeepBulkActions.tsx
10
174
  import { useKeepList } from "@keepkit/core/react";
11
- import { useState } from "react";
175
+ import { useState as useState2 } from "react";
12
176
 
13
177
  // src/KeepItemCheckbox.tsx
14
- import { jsx } from "react/jsx-runtime";
178
+ import { jsx as jsx3 } from "react/jsx-runtime";
15
179
  function KeepItemCheckbox({
16
180
  item,
17
181
  checked = false,
@@ -22,12 +186,14 @@ function KeepItemCheckbox({
22
186
  }) {
23
187
  const itemLabel = getItemLabel(item) ?? item.id;
24
188
  const accessibleLabel = ariaLabel ?? (typeof label === "string" ? label : itemLabel);
25
- return /* @__PURE__ */ jsx(
189
+ return /* @__PURE__ */ jsx3(
26
190
  "input",
27
191
  {
28
192
  ...props,
29
193
  type: "checkbox",
30
194
  checked,
195
+ "data-state": checked ? "checked" : "unchecked",
196
+ "data-disabled": props.disabled ? "true" : void 0,
31
197
  "aria-label": accessibleLabel,
32
198
  onChange: (event) => onCheckedChange?.(event.currentTarget.checked)
33
199
  }
@@ -44,7 +210,7 @@ import {
44
210
  cloneElement,
45
211
  isValidElement
46
212
  } from "react";
47
- import { jsx as jsx2 } from "react/jsx-runtime";
213
+ import { jsx as jsx4 } from "react/jsx-runtime";
48
214
  function toKeepButtonItem(item) {
49
215
  return {
50
216
  id: item.id,
@@ -73,66 +239,11 @@ function renderRoot(asChild, child, props, body, componentName) {
73
239
  if (!isValidElement(child)) throw new Error(`${componentName} with asChild requires a single React element child.`);
74
240
  return cloneElement(child, { ...props, children: body });
75
241
  }
76
- return /* @__PURE__ */ jsx2("div", { ...props, children: body });
77
- }
78
-
79
- // src/ui-context.tsx
80
- import { createContext, useContext, useMemo } from "react";
81
- import { jsx as jsx3 } from "react/jsx-runtime";
82
- var DEFAULT_LABELS = {
83
- save: "Save",
84
- saved: "Saved",
85
- remove: "Remove",
86
- loading: "Loading\u2026",
87
- saving: "Saving\u2026",
88
- syncing: "Syncing\u2026",
89
- error: "Something went wrong.",
90
- allTags: "All",
91
- filterTags: "Filter saved items by tag",
92
- note: "Note",
93
- saveNote: "Save note",
94
- noItems: "No saved items.",
95
- loadingItems: "Loading saved items\u2026",
96
- errorItems: "Could not load saved items.",
97
- search: "Search saved items",
98
- sort: "Sort saved items",
99
- newest: "Newest first",
100
- oldest: "Oldest first",
101
- savedNewest: "Saved newest first",
102
- savedOldest: "Saved oldest first",
103
- updatedNewest: "Updated newest first",
104
- updatedOldest: "Updated oldest first",
105
- previousPage: "Previous page",
106
- nextPage: "Next page",
107
- pagination: "Pagination",
108
- page: "Page",
109
- selectItems: "Select saved items",
110
- selectedCount: "selected",
111
- deleteSelected: "Delete selected",
112
- tagsToApply: "Tags to apply",
113
- applyTags: "Apply tags",
114
- savedMessage: "Item saved.",
115
- removedMessage: "Item removed.",
116
- noteSavedMessage: "Note saved."
117
- };
118
- var KeepUiLabelsContext = createContext({ labels: DEFAULT_LABELS });
119
- function KeepUiProvider({ labels, locale, labelResolver, children }) {
120
- const value = useMemo(() => {
121
- const resolved = { ...DEFAULT_LABELS, ...labels };
122
- return { locale, labels: resolved, labelResolver };
123
- }, [labelResolver, labels, locale]);
124
- return /* @__PURE__ */ jsx3(KeepUiLabelsContext.Provider, { value, children });
125
- }
126
- function useKeepUiLabels() {
127
- return useContext(KeepUiLabelsContext);
128
- }
129
- function useUiLabel(key, override) {
130
- const context = useKeepUiLabels();
131
- return override ?? context.labelResolver?.(key, { locale: context.locale }) ?? context.labels[key];
242
+ return /* @__PURE__ */ jsx4("div", { ...props, children: body });
132
243
  }
133
244
 
134
245
  // src/KeepBulkActions.tsx
135
- import { Fragment, jsx as jsx4, jsxs } from "react/jsx-runtime";
246
+ import { Fragment, jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
136
247
  function isAllSelected(items, selectedIds) {
137
248
  if (items.length === 0) return false;
138
249
  const selected = new Set(selectedIds);
@@ -164,10 +275,10 @@ function KeepBulkActions({
164
275
  const tagsLabel = useUiLabel("tagsToApply");
165
276
  const applyTagsLabel = useUiLabel("applyTags");
166
277
  const list = useKeepList(query);
167
- const [uncontrolledSelectedIds, setUncontrolledSelectedIds] = useState(defaultSelectedIds);
278
+ const [uncontrolledSelectedIds, setUncontrolledSelectedIds] = useState2(defaultSelectedIds);
168
279
  const selectedIds = controlledSelectedIds ?? uncontrolledSelectedIds;
169
280
  const selected = new Set(selectedIds);
170
- const [tagsInput, setTagsInput] = useState("");
281
+ const [tagsInput, setTagsInput] = useState2("");
171
282
  const setSelectedIds = (ids) => {
172
283
  if (controlledSelectedIds === void 0) setUncontrolledSelectedIds(ids);
173
284
  onSelectedIdsChange?.(ids);
@@ -202,17 +313,17 @@ function KeepBulkActions({
202
313
  updateTags,
203
314
  isMutating: list.isMutating
204
315
  };
205
- const body = render ? render(state) : typeof children === "function" ? children(state) : children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
206
- /* @__PURE__ */ jsxs("fieldset", { children: [
207
- /* @__PURE__ */ jsx4("legend", { children: selectItemsLabel }),
208
- /* @__PURE__ */ jsxs("label", { children: [
209
- /* @__PURE__ */ jsx4("input", { type: "checkbox", checked: allSelected, onChange: toggleAll, "aria-label": selectItemsLabel }),
316
+ const body = render ? render(state) : typeof children === "function" ? children(state) : children ?? /* @__PURE__ */ jsxs2(Fragment, { children: [
317
+ /* @__PURE__ */ jsxs2("fieldset", { children: [
318
+ /* @__PURE__ */ jsx5("legend", { children: selectItemsLabel }),
319
+ /* @__PURE__ */ jsxs2("label", { children: [
320
+ /* @__PURE__ */ jsx5("input", { type: "checkbox", checked: allSelected, onChange: toggleAll, "aria-label": selectItemsLabel }),
210
321
  selectedIds.length,
211
322
  " ",
212
323
  selectedCountLabel
213
324
  ] }),
214
- list.items.map((item) => /* @__PURE__ */ jsxs("span", { children: [
215
- /* @__PURE__ */ jsx4(
325
+ list.items.map((item) => /* @__PURE__ */ jsxs2("span", { children: [
326
+ /* @__PURE__ */ jsx5(
216
327
  KeepItemCheckbox,
217
328
  {
218
329
  item,
@@ -223,12 +334,12 @@ function KeepBulkActions({
223
334
  renderItem ? renderItem(item, selected.has(item.id)) : getMetaTitle(item.meta) ?? item.id
224
335
  ] }, item.id))
225
336
  ] }),
226
- /* @__PURE__ */ jsx4("button", { type: "button", onClick: () => void remove(), disabled: selectedIds.length === 0 || list.isMutating, children: deleteSelectedLabel }),
227
- /* @__PURE__ */ jsxs("label", { children: [
337
+ /* @__PURE__ */ jsx5("button", { type: "button", onClick: () => void remove(), disabled: selectedIds.length === 0 || list.isMutating, children: deleteSelectedLabel }),
338
+ /* @__PURE__ */ jsxs2("label", { children: [
228
339
  tagsLabel,
229
- /* @__PURE__ */ jsx4("input", { value: tagsInput, onChange: (event) => setTagsInput(event.currentTarget.value) })
340
+ /* @__PURE__ */ jsx5("input", { value: tagsInput, onChange: (event) => setTagsInput(event.currentTarget.value) })
230
341
  ] }),
231
- /* @__PURE__ */ jsx4(
342
+ /* @__PURE__ */ jsx5(
232
343
  "button",
233
344
  {
234
345
  type: "button",
@@ -238,7 +349,16 @@ function KeepBulkActions({
238
349
  }
239
350
  )
240
351
  ] });
241
- return /* @__PURE__ */ jsx4("section", { ...props, "aria-busy": list.isMutating || props["aria-busy"], children: body });
352
+ return /* @__PURE__ */ jsx5(
353
+ "section",
354
+ {
355
+ ...props,
356
+ "aria-busy": list.isMutating || props["aria-busy"],
357
+ "data-state": selectedIds.length > 0 ? "selected" : "idle",
358
+ "data-loading": list.isLoading || list.isMutating ? "true" : void 0,
359
+ children: body
360
+ }
361
+ );
242
362
  }
243
363
 
244
364
  // src/KeepButton.tsx
@@ -246,7 +366,7 @@ import {
246
366
  KeepButton as CoreKeepButton,
247
367
  useKeepItem
248
368
  } from "@keepkit/core/react";
249
- import { Fragment as Fragment2, jsx as jsx5 } from "react/jsx-runtime";
369
+ import { Fragment as Fragment2, jsx as jsx6 } from "react/jsx-runtime";
250
370
  function KeepButton({ labels, ...props }) {
251
371
  const saveLabel = useUiLabel("save", typeof labels?.unsaved === "string" ? labels.unsaved : void 0);
252
372
  const savedLabel = useUiLabel("saved", typeof labels?.saved === "string" ? labels.saved : void 0);
@@ -269,22 +389,27 @@ function KeepButton({ labels, ...props }) {
269
389
  savedAriaLabel: labels?.savedAriaLabel ?? props.savedAriaLabel,
270
390
  unsavedAriaLabel: labels?.unsavedAriaLabel ?? props.unsavedAriaLabel
271
391
  };
272
- if (!customStateLabel) return /* @__PURE__ */ jsx5(CoreKeepButton, { ...sharedProps });
273
- return /* @__PURE__ */ jsx5(CoreKeepButton, { ...sharedProps, children: (state) => /* @__PURE__ */ jsx5(Fragment2, { children: getStateContent(state) }) });
392
+ if (!customStateLabel) return /* @__PURE__ */ jsx6(CoreKeepButton, { ...sharedProps });
393
+ return /* @__PURE__ */ jsx6(CoreKeepButton, { ...sharedProps, children: (state) => /* @__PURE__ */ jsx6(Fragment2, { children: getStateContent(state) }) });
274
394
  }
275
395
 
276
396
  // src/KeepCollection.tsx
277
- import { useKeepList as useKeepList4 } from "@keepkit/core/react";
278
- import { useMemo as useMemo3, useState as useState4 } from "react";
397
+ import { KeepErrorBoundary as KeepErrorBoundary2, useKeepList as useKeepList4 } from "@keepkit/core/react";
398
+ import { useMemo as useMemo3, useState as useState5 } from "react";
279
399
 
280
400
  // src/KeepList.tsx
281
- import { useKeepList as useKeepList2 } from "@keepkit/core/react";
401
+ import {
402
+ KeepErrorBoundary,
403
+ useKeepList as useKeepList2
404
+ } from "@keepkit/core/react";
282
405
  import { isValidElement as isValidElement3 } from "react";
283
406
 
284
407
  // src/KeepItemCard.tsx
285
408
  import { useKeepItem as useKeepItem2 } from "@keepkit/core/react";
286
- import { isValidElement as isValidElement2 } from "react";
287
- import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
409
+ import {
410
+ isValidElement as isValidElement2
411
+ } from "react";
412
+ import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
288
413
  function KeepItemCard({
289
414
  item,
290
415
  title,
@@ -301,6 +426,12 @@ function KeepItemCard({
301
426
  showSaveButton = true,
302
427
  saveButtonLabels,
303
428
  asChild = false,
429
+ href: hrefOption,
430
+ onOpen,
431
+ linkTarget = "title",
432
+ linkComponent: LinkComponent,
433
+ linkTargetAttribute,
434
+ linkRel,
304
435
  className,
305
436
  ...rootProps
306
437
  }) {
@@ -313,10 +444,27 @@ function KeepItemCard({
313
444
  isSaved: itemState.isSaved,
314
445
  isMutating: itemState.isMutating,
315
446
  error: itemState.error,
316
- remove: itemState.remove
447
+ remove: itemState.remove,
448
+ status: item.status
317
449
  };
318
450
  const resolvedTitle = typeof title === "function" ? title(item) : title ?? getTitle?.(item) ?? getMetaTitle(item.meta) ?? item.id;
319
451
  const imageProps = getImageProps?.(item, resolvedTitle);
452
+ const href = typeof hrefOption === "function" ? hrefOption(item) : hrefOption;
453
+ const isAvailable = item.status === void 0 || item.status === "available";
454
+ const statusLabelKey = item.status && item.status !== "available" ? getStatusLabelKey(item.status) : "statusUnknown";
455
+ const unavailableLabel = useUiLabel(statusLabelKey);
456
+ const statusLabel = item.status && item.status !== "available" ? unavailableLabel : void 0;
457
+ function renderLink(content) {
458
+ if (!href || !isAvailable) return content;
459
+ const linkProps = {
460
+ href,
461
+ target: linkTargetAttribute,
462
+ rel: linkRel,
463
+ onClick: (event) => onOpen?.(item, event),
464
+ children: content
465
+ };
466
+ return LinkComponent ? /* @__PURE__ */ jsx7(LinkComponent, { ...linkProps }) : /* @__PURE__ */ jsx7("a", { ...linkProps });
467
+ }
320
468
  async function handleRemove() {
321
469
  try {
322
470
  await itemState.remove();
@@ -325,10 +473,11 @@ function KeepItemCard({
325
473
  onRemoveError?.(error);
326
474
  }
327
475
  }
328
- const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs2(Fragment3, { children: [
329
- imageProps ? renderImage?.({ ...imageProps, alt: imageAlt ?? imageProps.alt }, item) ?? (ImageComponent ? /* @__PURE__ */ jsx6(ImageComponent, { ...imageProps, alt: imageAlt ?? imageProps.alt }) : /* @__PURE__ */ jsx6("img", { ...imageProps, alt: imageAlt ?? imageProps.alt })) : null,
330
- /* @__PURE__ */ jsx6("h3", { children: resolvedTitle }),
331
- showSaveButton ? /* @__PURE__ */ jsx6(
476
+ const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs3(Fragment3, { children: [
477
+ imageProps ? renderImage?.({ ...imageProps, alt: imageAlt ?? imageProps.alt }, item) ?? (ImageComponent ? /* @__PURE__ */ jsx7(ImageComponent, { ...imageProps, alt: imageAlt ?? imageProps.alt }) : /* @__PURE__ */ jsx7("img", { ...imageProps, alt: imageAlt ?? imageProps.alt })) : null,
478
+ /* @__PURE__ */ jsx7("h3", { children: linkTarget === "title" ? renderLink(resolvedTitle) : resolvedTitle }),
479
+ statusLabel ? /* @__PURE__ */ jsx7("p", { "data-status": item.status, children: statusLabel }) : null,
480
+ showSaveButton ? /* @__PURE__ */ jsx7(
332
481
  KeepButton,
333
482
  {
334
483
  item: toKeepButtonItem(item),
@@ -336,20 +485,48 @@ function KeepItemCard({
336
485
  getAriaLabel: (buttonState) => `${buttonState.isSaved ? removeActionLabel : saveActionLabel} ${String(resolvedTitle)}`
337
486
  }
338
487
  ) : null,
339
- /* @__PURE__ */ jsx6("button", { type: "button", onClick: () => void handleRemove(), disabled: itemState.isMutating, children: removeLabel ?? removeActionLabel })
488
+ /* @__PURE__ */ jsx7("button", { type: "button", onClick: () => void handleRemove(), disabled: itemState.isMutating, children: removeLabel ?? removeActionLabel })
340
489
  ] });
490
+ const linkedBody = linkTarget === "card" ? renderLink(body) : body;
341
491
  return renderRoot(
342
492
  asChild,
343
493
  isValidElement2(children) ? children : void 0,
344
- { ...rootProps, className, "aria-busy": itemState.isMutating || rootProps["aria-busy"] },
345
- body,
494
+ {
495
+ ...rootProps,
496
+ className,
497
+ "aria-busy": itemState.isMutating || rootProps["aria-busy"],
498
+ "data-state": itemState.isSaved ? "saved" : "unsaved",
499
+ "data-status": item.status ?? "available",
500
+ "data-loading": itemState.isMutating ? "true" : void 0
501
+ },
502
+ linkedBody,
346
503
  "KeepItemCard"
347
504
  );
348
505
  }
506
+ function getStatusLabelKey(status) {
507
+ switch (status) {
508
+ case "expired":
509
+ return "statusExpired";
510
+ case "removed":
511
+ return "statusRemoved";
512
+ case "deleted":
513
+ return "statusDeleted";
514
+ case "private":
515
+ return "statusPrivate";
516
+ default:
517
+ return "statusUnknown";
518
+ }
519
+ }
349
520
 
350
521
  // src/KeepList.tsx
351
- import { jsx as jsx7 } from "react/jsx-runtime";
352
- function KeepList({
522
+ import { jsx as jsx8 } from "react/jsx-runtime";
523
+ function KeepList(props) {
524
+ const { fallback, onBoundaryError, boundaryResetKey, ...listProps } = props;
525
+ const content = /* @__PURE__ */ jsx8(KeepListContent, { ...listProps });
526
+ if (fallback === void 0 && onBoundaryError === void 0) return content;
527
+ return /* @__PURE__ */ jsx8(KeepErrorBoundary, { fallback, onError: onBoundaryError, resetKey: boundaryResetKey, children: content });
528
+ }
529
+ function KeepListContent({
353
530
  query,
354
531
  children,
355
532
  renderItem,
@@ -376,26 +553,38 @@ function KeepList({
376
553
  return renderRoot(
377
554
  asChild,
378
555
  asChild && isValidElement3(children) ? children : void 0,
379
- { ...rootProps, className, "aria-busy": state.isLoading || rootProps["aria-busy"] },
556
+ {
557
+ ...rootProps,
558
+ className,
559
+ "aria-busy": state.isLoading || rootProps["aria-busy"],
560
+ "data-state": getListState(state),
561
+ "data-loading": state.isLoading ? "true" : void 0
562
+ },
380
563
  body,
381
564
  "KeepList"
382
565
  );
383
566
  }
567
+ function getListState(state) {
568
+ if (state.error && state.items.length === 0) return "error";
569
+ if (state.isLoading && !state.isHydrated) return "loading";
570
+ if (state.isHydrated && state.items.length === 0) return "empty";
571
+ return "ready";
572
+ }
384
573
  function getListBody(state, options) {
385
574
  if (state.error && state.items.length === 0) return resolveContent(options.error, state);
386
575
  if (state.isLoading && !state.isHydrated) return resolveContent(options.loading, state);
387
576
  if (state.isHydrated && state.items.length === 0) return resolveContent(options.empty, state);
388
577
  if (typeof options.children === "function") return options.children(state);
389
578
  if (options.children !== void 0 && !isValidElement3(options.children)) return options.children;
390
- return /* @__PURE__ */ jsx7("ul", { children: state.items.map(
391
- (item) => options.renderItem ? options.renderItem(item, state) : /* @__PURE__ */ jsx7("li", { children: /* @__PURE__ */ jsx7(KeepItemCard, { item, ...options.itemCardProps }) }, item.id)
579
+ return /* @__PURE__ */ jsx8("ul", { children: state.items.map(
580
+ (item) => options.renderItem ? options.renderItem(item, state) : /* @__PURE__ */ jsx8("li", { children: /* @__PURE__ */ jsx8(KeepItemCard, { item, ...options.itemCardProps }) }, item.id)
392
581
  ) });
393
582
  }
394
583
 
395
584
  // src/KeepTagFilter.tsx
396
585
  import { useKeepList as useKeepList3 } from "@keepkit/core/react";
397
- import { isValidElement as isValidElement4, useCallback, useMemo as useMemo2, useState as useState2 } from "react";
398
- import { jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
586
+ import { isValidElement as isValidElement4, useCallback, useMemo as useMemo2, useState as useState3 } from "react";
587
+ import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
399
588
  function KeepTagFilter({
400
589
  query,
401
590
  value: controlledValue,
@@ -413,7 +602,7 @@ function KeepTagFilter({
413
602
  }) {
414
603
  const uiAllLabel = useUiLabel("allTags");
415
604
  const uiAriaLabel = useUiLabel("filterTags");
416
- const [uncontrolledValue, setUncontrolledValue] = useState2(defaultValue);
605
+ const [uncontrolledValue, setUncontrolledValue] = useState3(defaultValue);
417
606
  const resolvedValue = controlledValue ?? uncontrolledValue;
418
607
  const list = useKeepList3({
419
608
  ...query,
@@ -432,12 +621,12 @@ function KeepTagFilter({
432
621
  [list.tagCounts, list.tags, resolvedValue, select]
433
622
  );
434
623
  const contentChildren = asChild && isValidElement4(children) ? void 0 : children;
435
- const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs3("fieldset", { children: [
436
- /* @__PURE__ */ jsx8("legend", { children: ariaLabel ?? uiAriaLabel }),
437
- /* @__PURE__ */ jsx8("button", { type: "button", "aria-pressed": resolvedValue === void 0, onClick: () => select(), children: allLabel ?? uiAllLabel }),
438
- list.tags.map((tag) => /* @__PURE__ */ jsxs3("button", { type: "button", "aria-pressed": resolvedValue === tag, onClick: () => select(tag), children: [
624
+ const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs4("fieldset", { children: [
625
+ /* @__PURE__ */ jsx9("legend", { children: ariaLabel ?? uiAriaLabel }),
626
+ /* @__PURE__ */ jsx9("button", { type: "button", "aria-pressed": resolvedValue === void 0, onClick: () => select(), children: allLabel ?? uiAllLabel }),
627
+ list.tags.map((tag) => /* @__PURE__ */ jsxs4("button", { type: "button", "aria-pressed": resolvedValue === tag, onClick: () => select(tag), children: [
439
628
  renderTag ? renderTag(tag, list.tagCounts[tag] ?? 0, resolvedValue === tag) : tag,
440
- /* @__PURE__ */ jsxs3("span", { children: [
629
+ /* @__PURE__ */ jsxs4("span", { children: [
441
630
  " (",
442
631
  list.tagCounts[tag] ?? 0,
443
632
  ")"
@@ -447,15 +636,20 @@ function KeepTagFilter({
447
636
  return renderRoot(
448
637
  asChild,
449
638
  isValidElement4(children) ? children : void 0,
450
- { ...rootProps, className },
639
+ {
640
+ ...rootProps,
641
+ className,
642
+ "data-state": resolvedValue === void 0 ? "all" : "filtered",
643
+ "data-loading": list.isLoading ? "true" : void 0
644
+ },
451
645
  body,
452
646
  "KeepTagFilter"
453
647
  );
454
648
  }
455
649
 
456
650
  // src/query-controls.tsx
457
- import { useEffect, useState as useState3 } from "react";
458
- import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
651
+ import { useEffect, useState as useState4 } from "react";
652
+ import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
459
653
  function KeepSearchInput({
460
654
  value: controlledValue,
461
655
  defaultValue = "",
@@ -466,7 +660,7 @@ function KeepSearchInput({
466
660
  ...props
467
661
  }) {
468
662
  const label = useUiLabel("search");
469
- const [uncontrolledValue, setUncontrolledValue] = useState3(defaultValue);
663
+ const [uncontrolledValue, setUncontrolledValue] = useState4(defaultValue);
470
664
  const value = controlledValue ?? uncontrolledValue;
471
665
  useEffect(() => {
472
666
  if (!onValueChange) return;
@@ -477,12 +671,14 @@ function KeepSearchInput({
477
671
  const timer = window.setTimeout(() => onValueChange(value), debounceMs);
478
672
  return () => window.clearTimeout(timer);
479
673
  }, [debounceMs, onValueChange, value]);
480
- return /* @__PURE__ */ jsx9(
674
+ return /* @__PURE__ */ jsx10(
481
675
  "input",
482
676
  {
483
677
  ...props,
484
678
  type: "search",
485
679
  value,
680
+ "data-state": value ? "active" : "idle",
681
+ "data-disabled": props.disabled ? "true" : void 0,
486
682
  "aria-label": ariaLabel ?? label,
487
683
  placeholder: placeholder ?? label,
488
684
  onChange: (event) => {
@@ -505,19 +701,21 @@ function KeepSortSelect({
505
701
  const updatedOldestLabel = useUiLabel("updatedOldest");
506
702
  const savedNewestLabel = useUiLabel("savedNewest");
507
703
  const savedOldestLabel = useUiLabel("savedOldest");
508
- const [uncontrolledValue, setUncontrolledValue] = useState3(defaultValue);
704
+ const [uncontrolledValue, setUncontrolledValue] = useState4(defaultValue);
509
705
  const value = controlledValue ?? uncontrolledValue;
510
- const options = children ?? /* @__PURE__ */ jsxs4(Fragment4, { children: [
511
- /* @__PURE__ */ jsx9("option", { value: "updatedAt:desc", children: updatedNewestLabel }),
512
- /* @__PURE__ */ jsx9("option", { value: "updatedAt:asc", children: updatedOldestLabel }),
513
- /* @__PURE__ */ jsx9("option", { value: "savedAt:desc", children: savedNewestLabel }),
514
- /* @__PURE__ */ jsx9("option", { value: "savedAt:asc", children: savedOldestLabel })
706
+ const options = children ?? /* @__PURE__ */ jsxs5(Fragment4, { children: [
707
+ /* @__PURE__ */ jsx10("option", { value: "updatedAt:desc", children: updatedNewestLabel }),
708
+ /* @__PURE__ */ jsx10("option", { value: "updatedAt:asc", children: updatedOldestLabel }),
709
+ /* @__PURE__ */ jsx10("option", { value: "savedAt:desc", children: savedNewestLabel }),
710
+ /* @__PURE__ */ jsx10("option", { value: "savedAt:asc", children: savedOldestLabel })
515
711
  ] });
516
- return /* @__PURE__ */ jsx9(
712
+ return /* @__PURE__ */ jsx10(
517
713
  "select",
518
714
  {
519
715
  ...props,
520
716
  value,
717
+ "data-state": "selected",
718
+ "data-disabled": props.disabled ? "true" : void 0,
521
719
  "aria-label": ariaLabel ?? label,
522
720
  onChange: (event) => {
523
721
  const nextValue = event.currentTarget.value;
@@ -548,12 +746,16 @@ function KeepPagination({
548
746
  const next = Math.min(Math.max(1, nextPage), pageCount);
549
747
  onPageChange?.(next, (next - 1) * pageSize);
550
748
  };
551
- const navProps = { ...props, "aria-label": props["aria-label"] ?? paginationLabel };
552
- if (render) return /* @__PURE__ */ jsx9("nav", { ...navProps, children: render({ page: currentPage, pageCount, goToPage }) });
749
+ const navProps = {
750
+ ...props,
751
+ "aria-label": props["aria-label"] ?? paginationLabel,
752
+ "data-state": pageCount > 1 ? "active" : "idle"
753
+ };
754
+ if (render) return /* @__PURE__ */ jsx10("nav", { ...navProps, children: render({ page: currentPage, pageCount, goToPage }) });
553
755
  const visiblePages = getVisiblePages(currentPage, pageCount, Math.max(1, maxPageButtons));
554
- return /* @__PURE__ */ jsxs4("nav", { ...navProps, children: [
555
- /* @__PURE__ */ jsx9("button", { type: "button", onClick: () => goToPage(currentPage - 1), disabled: currentPage <= 1, children: previousPageLabel }),
556
- visiblePages.map((nextPage) => /* @__PURE__ */ jsx9(
756
+ return /* @__PURE__ */ jsxs5("nav", { ...navProps, children: [
757
+ /* @__PURE__ */ jsx10("button", { type: "button", onClick: () => goToPage(currentPage - 1), disabled: currentPage <= 1, children: previousPageLabel }),
758
+ visiblePages.map((nextPage) => /* @__PURE__ */ jsx10(
557
759
  "button",
558
760
  {
559
761
  type: "button",
@@ -564,7 +766,7 @@ function KeepPagination({
564
766
  },
565
767
  nextPage
566
768
  )),
567
- /* @__PURE__ */ jsx9("button", { type: "button", onClick: () => goToPage(currentPage + 1), disabled: currentPage >= pageCount, children: nextPageLabel })
769
+ /* @__PURE__ */ jsx10("button", { type: "button", onClick: () => goToPage(currentPage + 1), disabled: currentPage >= pageCount, children: nextPageLabel })
568
770
  ] });
569
771
  }
570
772
  function getVisiblePages(currentPage, pageCount, maxPageButtons) {
@@ -575,8 +777,14 @@ function getVisiblePages(currentPage, pageCount, maxPageButtons) {
575
777
  }
576
778
 
577
779
  // src/KeepCollection.tsx
578
- import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
579
- function KeepCollection({
780
+ import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
781
+ function KeepCollection(props) {
782
+ const { fallback, onBoundaryError, boundaryResetKey, ...collectionProps } = props;
783
+ const content = /* @__PURE__ */ jsx11(KeepCollectionContent, { ...collectionProps });
784
+ if (fallback === void 0 && onBoundaryError === void 0) return content;
785
+ return /* @__PURE__ */ jsx11(KeepErrorBoundary2, { fallback, onError: onBoundaryError, resetKey: boundaryResetKey, children: content });
786
+ }
787
+ function KeepCollectionContent({
580
788
  query = {},
581
789
  pageSize = 20,
582
790
  features,
@@ -596,10 +804,10 @@ function KeepCollection({
596
804
  bulkActions: false,
597
805
  ...features
598
806
  };
599
- const [searchValue, setSearchValue] = useState4(query.search?.query ?? "");
600
- const [sort, setSort] = useState4(query.sort ?? { by: "updatedAt", direction: "desc" });
601
- const [tag, setTag] = useState4(query.tags?.[0]);
602
- const [page, setPage] = useState4(query.pagination?.page ?? 1);
807
+ const [searchValue, setSearchValue] = useState5(query.search?.query ?? "");
808
+ const [sort, setSort] = useState5(query.sort ?? { by: "updatedAt", direction: "desc" });
809
+ const [tag, setTag] = useState5(query.tags?.[0]);
810
+ const [page, setPage] = useState5(query.pagination?.page ?? 1);
603
811
  const resolvedPageSize = query.pagination?.pageSize ?? pageSize;
604
812
  const resolvedQuery = useMemo3(
605
813
  () => ({
@@ -612,15 +820,17 @@ function KeepCollection({
612
820
  [enabled.pagination, enabled.search, enabled.sort, page, query, resolvedPageSize, searchValue, sort, tag]
613
821
  );
614
822
  const list = useKeepList4(resolvedQuery);
615
- return /* @__PURE__ */ jsxs5(
823
+ return /* @__PURE__ */ jsxs6(
616
824
  "section",
617
825
  {
618
826
  ...rootProps,
619
827
  className,
620
828
  "aria-busy": list.isLoading || list.isMutating || rootProps["aria-busy"],
829
+ "data-state": getCollectionState(list),
830
+ "data-loading": list.isLoading || list.isMutating ? "true" : void 0,
621
831
  children: [
622
- /* @__PURE__ */ jsxs5("div", { children: [
623
- enabled.search ? /* @__PURE__ */ jsx10(
832
+ /* @__PURE__ */ jsxs6("div", { children: [
833
+ enabled.search ? /* @__PURE__ */ jsx11(
624
834
  KeepSearchInput,
625
835
  {
626
836
  value: searchValue,
@@ -630,7 +840,7 @@ function KeepCollection({
630
840
  }
631
841
  }
632
842
  ) : null,
633
- enabled.sort ? /* @__PURE__ */ jsx10(
843
+ enabled.sort ? /* @__PURE__ */ jsx11(
634
844
  KeepSortSelect,
635
845
  {
636
846
  value: sortToValue(sort),
@@ -640,7 +850,7 @@ function KeepCollection({
640
850
  }
641
851
  }
642
852
  ) : null,
643
- enabled.tagFilter ? /* @__PURE__ */ jsx10(
853
+ enabled.tagFilter ? /* @__PURE__ */ jsx11(
644
854
  KeepTagFilter,
645
855
  {
646
856
  query,
@@ -652,7 +862,7 @@ function KeepCollection({
652
862
  }
653
863
  ) : null
654
864
  ] }),
655
- /* @__PURE__ */ jsx10(
865
+ /* @__PURE__ */ jsx11(
656
866
  KeepList,
657
867
  {
658
868
  query: resolvedQuery,
@@ -663,7 +873,7 @@ function KeepCollection({
663
873
  error
664
874
  }
665
875
  ),
666
- enabled.pagination ? /* @__PURE__ */ jsx10(
876
+ enabled.pagination ? /* @__PURE__ */ jsx11(
667
877
  KeepPagination,
668
878
  {
669
879
  totalCount: list.totalCount,
@@ -672,11 +882,17 @@ function KeepCollection({
672
882
  onPageChange: (nextPage) => setPage(nextPage)
673
883
  }
674
884
  ) : null,
675
- enabled.bulkActions ? /* @__PURE__ */ jsx10(KeepBulkActions, { query: resolvedQuery }) : null
885
+ enabled.bulkActions ? /* @__PURE__ */ jsx11(KeepBulkActions, { query: resolvedQuery }) : null
676
886
  ]
677
887
  }
678
888
  );
679
889
  }
890
+ function getCollectionState(list) {
891
+ if (list.error && list.items.length === 0) return "error";
892
+ if (list.isLoading && !list.isHydrated) return "loading";
893
+ if (list.isHydrated && list.items.length === 0) return "empty";
894
+ return "ready";
895
+ }
680
896
 
681
897
  // src/KeepNoteEditor.tsx
682
898
  import { useKeepItem as useKeepItem3 } from "@keepkit/core/react";
@@ -684,10 +900,10 @@ import {
684
900
  isValidElement as isValidElement5,
685
901
  useCallback as useCallback2,
686
902
  useEffect as useEffect2,
687
- useRef,
688
- useState as useState5
903
+ useRef as useRef2,
904
+ useState as useState6
689
905
  } from "react";
690
- import { Fragment as Fragment5, jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
906
+ import { Fragment as Fragment5, jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
691
907
  function KeepNoteEditor({
692
908
  item,
693
909
  label,
@@ -707,10 +923,10 @@ function KeepNoteEditor({
707
923
  const itemState = useKeepItem3(item);
708
924
  const { error, isMutating, item: savedItem, updateNote } = itemState;
709
925
  const contentChildren = asChild && isValidElement5(children) ? void 0 : children;
710
- const [note, setNote] = useState5(item.note ?? "");
926
+ const [note, setNote] = useState6(item.note ?? "");
711
927
  const baselineNote = savedItem?.note ?? item.note ?? "";
712
928
  const isDirty = note !== baselineNote;
713
- const lastSavedNoteRef = useRef(void 0);
929
+ const lastSavedNoteRef = useRef2(void 0);
714
930
  useEffect2(() => setNote(baselineNote), [baselineNote]);
715
931
  const save = useCallback2(async () => {
716
932
  const nextNote = note.trim() || void 0;
@@ -737,10 +953,10 @@ function KeepNoteEditor({
737
953
  error,
738
954
  save
739
955
  };
740
- const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs6(Fragment5, { children: [
741
- /* @__PURE__ */ jsxs6("label", { children: [
956
+ const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs7(Fragment5, { children: [
957
+ /* @__PURE__ */ jsxs7("label", { children: [
742
958
  label ?? defaultLabel,
743
- /* @__PURE__ */ jsx11(
959
+ /* @__PURE__ */ jsx12(
744
960
  "textarea",
745
961
  {
746
962
  value: note,
@@ -756,20 +972,23 @@ function KeepNoteEditor({
756
972
  }
757
973
  )
758
974
  ] }),
759
- /* @__PURE__ */ jsx11("button", { type: "submit", disabled: isMutating, "aria-busy": isMutating, children: saveLabel ?? defaultSaveLabel })
975
+ /* @__PURE__ */ jsx12("button", { type: "submit", disabled: isMutating, "aria-busy": isMutating, children: saveLabel ?? defaultSaveLabel })
760
976
  ] });
761
977
  const handleSubmit = (event) => {
762
978
  event.preventDefault();
763
979
  void save().catch(() => void 0);
764
980
  };
765
981
  if (!asChild) {
766
- return /* @__PURE__ */ jsx11(
982
+ return /* @__PURE__ */ jsx12(
767
983
  "form",
768
984
  {
769
985
  ...formProps,
770
986
  className,
771
987
  onSubmit: handleSubmit,
772
988
  "aria-busy": isMutating || formProps["aria-busy"],
989
+ "data-state": isDirty ? "dirty" : "clean",
990
+ "data-loading": isMutating ? "true" : void 0,
991
+ "data-disabled": isMutating ? "true" : void 0,
773
992
  children: body
774
993
  }
775
994
  );
@@ -777,7 +996,15 @@ function KeepNoteEditor({
777
996
  return renderRoot(
778
997
  true,
779
998
  isValidElement5(children) ? children : void 0,
780
- { ...formProps, className, onSubmit: handleSubmit, "aria-busy": isMutating || formProps["aria-busy"] },
999
+ {
1000
+ ...formProps,
1001
+ className,
1002
+ onSubmit: handleSubmit,
1003
+ "aria-busy": isMutating || formProps["aria-busy"],
1004
+ "data-state": isDirty ? "dirty" : "clean",
1005
+ "data-loading": isMutating ? "true" : void 0,
1006
+ "data-disabled": isMutating ? "true" : void 0
1007
+ },
781
1008
  body,
782
1009
  "KeepNoteEditor"
783
1010
  );
@@ -785,8 +1012,8 @@ function KeepNoteEditor({
785
1012
 
786
1013
  // src/KeepTagEditor.tsx
787
1014
  import { useKeepItem as useKeepItem4 } from "@keepkit/core/react";
788
- import { useCallback as useCallback3, useEffect as useEffect3, useState as useState6 } from "react";
789
- import { Fragment as Fragment6, jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
1015
+ import { useCallback as useCallback3, useEffect as useEffect3, useState as useState7 } from "react";
1016
+ import { Fragment as Fragment6, jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
790
1017
  function KeepTagEditor({
791
1018
  item,
792
1019
  availableTags = [],
@@ -799,8 +1026,8 @@ function KeepTagEditor({
799
1026
  const removeLabel = useUiLabel("remove");
800
1027
  const applyTagsLabel = useUiLabel("applyTags");
801
1028
  const itemState = useKeepItem4(item);
802
- const [tags, setTags] = useState6(item.tags ?? []);
803
- const [input, setInput] = useState6("");
1029
+ const [tags, setTags] = useState7(item.tags ?? []);
1030
+ const [input, setInput] = useState7("");
804
1031
  useEffect3(() => setTags(itemState.item?.tags ?? item.tags ?? []), [item.tags, itemState.item?.tags]);
805
1032
  const save = useCallback3(async () => {
806
1033
  const nextTags = normalizeUiTags(tags);
@@ -817,10 +1044,10 @@ function KeepTagEditor({
817
1044
  setTags(normalizeUiTags([...tags, tag]));
818
1045
  setInput("");
819
1046
  };
820
- const body = render ? render({ tags, setTags, save, isSaving: itemState.isMutating }) : /* @__PURE__ */ jsxs7(Fragment6, { children: [
821
- /* @__PURE__ */ jsxs7("label", { children: [
1047
+ const body = render ? render({ tags, setTags, save, isSaving: itemState.isMutating }) : /* @__PURE__ */ jsxs8(Fragment6, { children: [
1048
+ /* @__PURE__ */ jsxs8("label", { children: [
822
1049
  tagsLabel,
823
- /* @__PURE__ */ jsx12(
1050
+ /* @__PURE__ */ jsx13(
824
1051
  "input",
825
1052
  {
826
1053
  value: input,
@@ -839,14 +1066,14 @@ function KeepTagEditor({
839
1066
  }
840
1067
  )
841
1068
  ] }),
842
- availableTags.length > 0 ? /* @__PURE__ */ jsx12("datalist", { id: `keep-tags-${item.id}`, children: availableTags.map((tag) => /* @__PURE__ */ jsx12("option", { value: tag }, tag)) }) : null,
843
- /* @__PURE__ */ jsx12("ul", { "aria-label": tagsLabel, children: tags.map((tag) => /* @__PURE__ */ jsxs7("li", { children: [
1069
+ availableTags.length > 0 ? /* @__PURE__ */ jsx13("datalist", { id: `keep-tags-${item.id}`, children: availableTags.map((tag) => /* @__PURE__ */ jsx13("option", { value: tag }, tag)) }) : null,
1070
+ /* @__PURE__ */ jsx13("ul", { "aria-label": tagsLabel, children: tags.map((tag) => /* @__PURE__ */ jsxs8("li", { children: [
844
1071
  tag,
845
- /* @__PURE__ */ jsx12("button", { type: "button", onClick: () => setTags(tags.filter((current) => current !== tag)), children: removeLabel })
1072
+ /* @__PURE__ */ jsx13("button", { type: "button", onClick: () => setTags(tags.filter((current) => current !== tag)), children: removeLabel })
846
1073
  ] }, tag)) }),
847
- /* @__PURE__ */ jsx12("button", { type: "submit", disabled: itemState.isMutating, "aria-busy": itemState.isMutating, children: applyTagsLabel })
1074
+ /* @__PURE__ */ jsx13("button", { type: "submit", disabled: itemState.isMutating, "aria-busy": itemState.isMutating, children: applyTagsLabel })
848
1075
  ] });
849
- return /* @__PURE__ */ jsx12(
1076
+ return /* @__PURE__ */ jsx13(
850
1077
  "form",
851
1078
  {
852
1079
  ...props,
@@ -855,15 +1082,18 @@ function KeepTagEditor({
855
1082
  void save().catch(() => void 0);
856
1083
  },
857
1084
  "aria-busy": itemState.isMutating || props["aria-busy"],
1085
+ "data-state": itemState.isMutating ? "saving" : "idle",
1086
+ "data-loading": itemState.isMutating ? "true" : void 0,
1087
+ "data-disabled": itemState.isMutating ? "true" : void 0,
858
1088
  children: body
859
1089
  }
860
1090
  );
861
1091
  }
862
1092
 
863
1093
  // src/status.tsx
864
- import { useKeepContext } from "@keepkit/core/react";
865
- import { isValidElement as isValidElement6, useEffect as useEffect4, useRef as useRef2, useState as useState7 } from "react";
866
- import { Fragment as Fragment7, jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
1094
+ import { useKeepContext as useKeepContext2 } from "@keepkit/core/react";
1095
+ import { isValidElement as isValidElement6, useEffect as useEffect4, useRef as useRef3, useState as useState8 } from "react";
1096
+ import { Fragment as Fragment7, jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
867
1097
  function KeepEmptyState({
868
1098
  title,
869
1099
  description,
@@ -875,12 +1105,12 @@ function KeepEmptyState({
875
1105
  }) {
876
1106
  const defaultTitle = useUiLabel("noItems").replace(/\.$/, "");
877
1107
  const contentChildren = asChild && isValidElement6(children) ? void 0 : children;
878
- const body = contentChildren ?? /* @__PURE__ */ jsxs8(Fragment7, { children: [
879
- /* @__PURE__ */ jsx13("h2", { children: title ?? defaultTitle }),
880
- description ? /* @__PURE__ */ jsx13("p", { children: description }) : null,
1108
+ const body = contentChildren ?? /* @__PURE__ */ jsxs9(Fragment7, { children: [
1109
+ /* @__PURE__ */ jsx14("h2", { children: title ?? defaultTitle }),
1110
+ description ? /* @__PURE__ */ jsx14("p", { children: description }) : null,
881
1111
  action
882
1112
  ] });
883
- return renderRoot(asChild, children, { ...rootProps, className }, body, "KeepEmptyState");
1113
+ return renderRoot(asChild, children, { ...rootProps, className, "data-state": "empty" }, body, "KeepEmptyState");
884
1114
  }
885
1115
  function KeepStatus({
886
1116
  status,
@@ -891,10 +1121,10 @@ function KeepStatus({
891
1121
  className,
892
1122
  ...rootProps
893
1123
  }) {
894
- const context = useKeepContext();
1124
+ const context = useKeepContext2();
895
1125
  const contentChildren = asChild && isValidElement6(children) ? void 0 : children;
896
1126
  const resolvedStatus = status ?? getDerivedStatus(context);
897
- const defaultLabel = useUiLabel(getStatusLabelKey(resolvedStatus));
1127
+ const defaultLabel = useUiLabel(getStatusLabelKey2(resolvedStatus));
898
1128
  const state = {
899
1129
  status: resolvedStatus,
900
1130
  error: context.error,
@@ -906,18 +1136,25 @@ function KeepStatus({
906
1136
  return renderRoot(
907
1137
  asChild,
908
1138
  isValidElement6(children) ? children : void 0,
909
- { ...rootProps, className, role, "aria-live": rootProps["aria-live"] ?? "polite" },
1139
+ {
1140
+ ...rootProps,
1141
+ className,
1142
+ role,
1143
+ "aria-live": rootProps["aria-live"] ?? "polite",
1144
+ "data-state": resolvedStatus,
1145
+ "data-loading": resolvedStatus === "loading" || resolvedStatus === "saving" || resolvedStatus === "syncing" ? "true" : void 0
1146
+ },
910
1147
  body,
911
1148
  "KeepStatus"
912
1149
  );
913
1150
  }
914
1151
  function KeepAnnouncements({ messages, ...props }) {
915
- const context = useKeepContext();
1152
+ const context = useKeepContext2();
916
1153
  const savedMessage = useUiLabel("savedMessage", messages?.save);
917
1154
  const removedMessage = useUiLabel("removedMessage", messages?.remove);
918
1155
  const noteSavedMessage = useUiLabel("noteSavedMessage", messages?.note);
919
- const [message, setMessage] = useState7("");
920
- const lastChangeRef = useRef2(void 0);
1156
+ const [message, setMessage] = useState8("");
1157
+ const lastChangeRef = useRef3(void 0);
921
1158
  useEffect4(() => {
922
1159
  const change = context.lastChange;
923
1160
  if (!change || change === lastChangeRef.current) return;
@@ -926,7 +1163,17 @@ function KeepAnnouncements({ messages, ...props }) {
926
1163
  else if (change.action === "remove" || change.action === "removeBatch") setMessage(removedMessage);
927
1164
  else if (change.action === "updateNote") setMessage(noteSavedMessage);
928
1165
  }, [context.lastChange, noteSavedMessage, removedMessage, savedMessage]);
929
- return /* @__PURE__ */ jsx13("div", { ...props, role: props.role ?? "status", "aria-live": props["aria-live"] ?? "polite", "aria-atomic": "true", children: message });
1166
+ return /* @__PURE__ */ jsx14(
1167
+ "div",
1168
+ {
1169
+ ...props,
1170
+ role: props.role ?? "status",
1171
+ "aria-live": props["aria-live"] ?? "polite",
1172
+ "aria-atomic": "true",
1173
+ "data-state": "announcing",
1174
+ children: message
1175
+ }
1176
+ );
930
1177
  }
931
1178
  var KeepAnnouncer = KeepAnnouncements;
932
1179
  function getDerivedStatus(context) {
@@ -937,7 +1184,7 @@ function getDerivedStatus(context) {
937
1184
  if (context.isHydrated && context.items.length === 0) return "empty";
938
1185
  return "idle";
939
1186
  }
940
- function getStatusLabelKey(status) {
1187
+ function getStatusLabelKey2(status) {
941
1188
  if (status === "empty") return "noItems";
942
1189
  if (status === "loading") return "loadingItems";
943
1190
  if (status === "error") return "error";
@@ -947,7 +1194,14 @@ function getStatusLabelKey(status) {
947
1194
  }
948
1195
 
949
1196
  // src/index.tsx
950
- import { KeepProvider, useKeepContext as useKeepContext2, useKeepItem as useKeepItem5, useKeepList as useKeepList5, useKeepShortcut } from "@keepkit/core/react";
1197
+ import {
1198
+ KeepErrorBoundary as KeepErrorBoundary3,
1199
+ KeepProvider,
1200
+ useKeepContext as useKeepContext3,
1201
+ useKeepItem as useKeepItem5,
1202
+ useKeepList as useKeepList5,
1203
+ useKeepShortcut
1204
+ } from "@keepkit/core/react";
951
1205
  import {
952
1206
  createBrowserStorageAdapter,
953
1207
  createStorageAdapter,
@@ -958,7 +1212,7 @@ import {
958
1212
  LocalStorageSyncQueueAdapter,
959
1213
  SyncStorageAdapter
960
1214
  } from "@keepkit/core/storage";
961
- import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
1215
+ import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
962
1216
  function KeepKitProvider({
963
1217
  labels,
964
1218
  locale,
@@ -966,16 +1220,16 @@ function KeepKitProvider({
966
1220
  children,
967
1221
  ...providerProps
968
1222
  }) {
969
- return /* @__PURE__ */ jsx14(KeepUiProvider, { labels, locale, labelResolver, children: /* @__PURE__ */ jsxs9(CoreKeepProvider, { ...providerProps, children: [
1223
+ return /* @__PURE__ */ jsx15(KeepUiProvider, { labels, locale, labelResolver, children: /* @__PURE__ */ jsxs10(CoreKeepProvider, { ...providerProps, children: [
970
1224
  children,
971
- /* @__PURE__ */ jsx14(KeepAnnouncements, {})
1225
+ /* @__PURE__ */ jsx15(KeepAnnouncements, {})
972
1226
  ] }) });
973
1227
  }
974
1228
  function createKeepKit(options = {}) {
975
1229
  const { labels, locale, labelResolver, getTitle, getImageProps, ...coreOptions } = options;
976
1230
  const coreKit = createCoreKeepKit(coreOptions);
977
1231
  return {
978
- Provider: (props) => /* @__PURE__ */ jsx14(
1232
+ Provider: (props) => /* @__PURE__ */ jsx15(
979
1233
  KeepKitProvider,
980
1234
  {
981
1235
  ...coreOptions,
@@ -985,8 +1239,9 @@ function createKeepKit(options = {}) {
985
1239
  ...props
986
1240
  }
987
1241
  ),
988
- Button: (props) => /* @__PURE__ */ jsx14(KeepButton, { ...props }),
989
- Collection: (props) => /* @__PURE__ */ jsx14(
1242
+ Button: (props) => /* @__PURE__ */ jsx15(KeepButton, { ...props }),
1243
+ Backup: (props) => /* @__PURE__ */ jsx15(KeepBackup, { ...props }),
1244
+ Collection: (props) => /* @__PURE__ */ jsx15(
990
1245
  KeepCollection,
991
1246
  {
992
1247
  ...props,
@@ -1009,10 +1264,12 @@ export {
1009
1264
  IndexedDBSyncQueueAdapter,
1010
1265
  KeepAnnouncements,
1011
1266
  KeepAnnouncer,
1267
+ KeepBackup,
1012
1268
  KeepBulkActions,
1013
1269
  KeepButton,
1014
1270
  KeepCollection,
1015
1271
  KeepEmptyState,
1272
+ KeepErrorBoundary3 as KeepErrorBoundary,
1016
1273
  KeepItemCard,
1017
1274
  KeepItemCheckbox,
1018
1275
  KeepKitProvider,
@@ -1034,7 +1291,7 @@ export {
1034
1291
  createStorageAdapter,
1035
1292
  isAllSelected,
1036
1293
  toggleSelectAll,
1037
- useKeepContext2 as useKeepContext,
1294
+ useKeepContext3 as useKeepContext,
1038
1295
  useKeepItem5 as useKeepItem,
1039
1296
  useKeepList5 as useKeepList,
1040
1297
  useKeepShortcut,