@keepkit/ui 0.12.0 → 0.14.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
@@ -55,6 +55,9 @@ var DE_LABELS = {
55
55
  currentPage: "Aktuelle Seite",
56
56
  searchResults: "Aktuelle Suchergebnisse",
57
57
  allItems: "Alle gespeicherten Elemente",
58
+ localVersion: "Lokal",
59
+ remoteVersion: "Server",
60
+ updatedAt: "Aktualisiert",
58
61
  statusAvailable: "Verf\xFCgbar",
59
62
  noteSavedMessage: "Notiz gespeichert.",
60
63
  exportData: "JSON exportieren",
@@ -128,6 +131,9 @@ var EN_LABELS = {
128
131
  currentPage: "Current page",
129
132
  searchResults: "Current search results",
130
133
  allItems: "All saved items",
134
+ localVersion: "Local",
135
+ remoteVersion: "Remote",
136
+ updatedAt: "Updated",
131
137
  statusAvailable: "Available",
132
138
  noteSavedMessage: "Note saved.",
133
139
  exportData: "Export JSON",
@@ -201,6 +207,9 @@ var ES_LABELS = {
201
207
  currentPage: "P\xE1gina actual",
202
208
  searchResults: "Resultados de b\xFAsqueda actuales",
203
209
  allItems: "Todos los elementos guardados",
210
+ localVersion: "Local",
211
+ remoteVersion: "Servidor",
212
+ updatedAt: "Actualizado",
204
213
  statusAvailable: "Disponible",
205
214
  noteSavedMessage: "Nota guardada.",
206
215
  exportData: "Exportar JSON",
@@ -274,6 +283,9 @@ var FIL_LABELS = {
274
283
  currentPage: "Kasalukuyang pahina",
275
284
  searchResults: "Kasalukuyang resulta ng paghahanap",
276
285
  allItems: "Lahat ng naka-save na item",
286
+ localVersion: "Lokal",
287
+ remoteVersion: "Remote",
288
+ updatedAt: "Na-update",
277
289
  statusAvailable: "Available",
278
290
  noteSavedMessage: "Na-save ang tala.",
279
291
  exportData: "I-export ang JSON",
@@ -347,6 +359,9 @@ var FR_LABELS = {
347
359
  currentPage: "Page actuelle",
348
360
  searchResults: "R\xE9sultats de recherche actuels",
349
361
  allItems: "Tous les \xE9l\xE9ments enregistr\xE9s",
362
+ localVersion: "Local",
363
+ remoteVersion: "Distant",
364
+ updatedAt: "Mis \xE0 jour",
350
365
  statusAvailable: "Disponible",
351
366
  noteSavedMessage: "Note enregistr\xE9e.",
352
367
  exportData: "Exporter JSON",
@@ -420,6 +435,9 @@ var ID_LABELS = {
420
435
  currentPage: "Halaman saat ini",
421
436
  searchResults: "Hasil pencarian saat ini",
422
437
  allItems: "Semua item tersimpan",
438
+ localVersion: "Lokal",
439
+ remoteVersion: "Server",
440
+ updatedAt: "Diperbarui",
423
441
  statusAvailable: "Tersedia",
424
442
  noteSavedMessage: "Catatan tersimpan.",
425
443
  exportData: "Ekspor JSON",
@@ -493,6 +511,9 @@ var IT_LABELS = {
493
511
  currentPage: "Pagina corrente",
494
512
  searchResults: "Risultati di ricerca correnti",
495
513
  allItems: "Tutti gli elementi salvati",
514
+ localVersion: "Locale",
515
+ remoteVersion: "Server",
516
+ updatedAt: "Aggiornato",
496
517
  statusAvailable: "Disponibile",
497
518
  noteSavedMessage: "Nota salvata.",
498
519
  exportData: "Esporta JSON",
@@ -566,6 +587,9 @@ var JA_LABELS = {
566
587
  currentPage: "\u8868\u793A\u4E2D\u306E\u30DA\u30FC\u30B8",
567
588
  searchResults: "\u73FE\u5728\u306E\u691C\u7D22\u7D50\u679C\u5168\u4F53",
568
589
  allItems: "\u3059\u3079\u3066\u306E\u4FDD\u5B58\u30A2\u30A4\u30C6\u30E0",
590
+ localVersion: "\u30ED\u30FC\u30AB\u30EB",
591
+ remoteVersion: "\u30EA\u30E2\u30FC\u30C8",
592
+ updatedAt: "\u66F4\u65B0\u65E5\u6642",
569
593
  statusAvailable: "\u5229\u7528\u53EF\u80FD",
570
594
  noteSavedMessage: "\u30E1\u30E2\u3092\u4FDD\u5B58\u3057\u307E\u3057\u305F\u3002",
571
595
  exportData: "JSON\u3092\u30A8\u30AF\u30B9\u30DD\u30FC\u30C8",
@@ -639,6 +663,9 @@ var KO_LABELS = {
639
663
  currentPage: "\uD604\uC7AC \uD398\uC774\uC9C0",
640
664
  searchResults: "\uD604\uC7AC \uAC80\uC0C9 \uACB0\uACFC \uC804\uCCB4",
641
665
  allItems: "\uBAA8\uB4E0 \uC800\uC7A5 \uD56D\uBAA9",
666
+ localVersion: "\uB85C\uCEEC",
667
+ remoteVersion: "\uC6D0\uACA9",
668
+ updatedAt: "\uC5C5\uB370\uC774\uD2B8 \uC2DC\uAC04",
642
669
  statusAvailable: "\uC0AC\uC6A9 \uAC00\uB2A5",
643
670
  noteSavedMessage: "\uBA54\uBAA8\uB97C \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4.",
644
671
  exportData: "JSON \uB0B4\uBCF4\uB0B4\uAE30",
@@ -712,6 +739,9 @@ var MS_LABELS = {
712
739
  currentPage: "Halaman semasa",
713
740
  searchResults: "Hasil carian semasa",
714
741
  allItems: "Semua item yang disimpan",
742
+ localVersion: "Tempatan",
743
+ remoteVersion: "Pelayan",
744
+ updatedAt: "Dikemas kini",
715
745
  statusAvailable: "Tersedia",
716
746
  noteSavedMessage: "Nota disimpan.",
717
747
  exportData: "Eksport JSON",
@@ -785,6 +815,9 @@ var PT_BR_LABELS = {
785
815
  currentPage: "P\xE1gina atual",
786
816
  searchResults: "Resultados da pesquisa atual",
787
817
  allItems: "Todos os itens salvos",
818
+ localVersion: "Local",
819
+ remoteVersion: "Servidor",
820
+ updatedAt: "Atualizado",
788
821
  statusAvailable: "Dispon\xEDvel",
789
822
  noteSavedMessage: "Nota salva.",
790
823
  exportData: "Exportar JSON",
@@ -858,6 +891,9 @@ var RU_LABELS = {
858
891
  currentPage: "\u0422\u0435\u043A\u0443\u0449\u0430\u044F \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0430",
859
892
  searchResults: "\u0422\u0435\u043A\u0443\u0449\u0438\u0435 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u044B \u043F\u043E\u0438\u0441\u043A\u0430",
860
893
  allItems: "\u0412\u0441\u0435 \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D\u043D\u044B\u0435 \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u044B",
894
+ localVersion: "\u041B\u043E\u043A\u0430\u043B\u044C\u043D\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F",
895
+ remoteVersion: "\u0421\u0435\u0440\u0432\u0435\u0440\u043D\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F",
896
+ updatedAt: "\u041E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u043E",
861
897
  statusAvailable: "\u0414\u043E\u0441\u0442\u0443\u043F\u0435\u043D",
862
898
  noteSavedMessage: "\u0417\u0430\u043C\u0435\u0442\u043A\u0430 \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0430.",
863
899
  exportData: "\u042D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C JSON",
@@ -931,6 +967,9 @@ var TH_LABELS = {
931
967
  currentPage: "\u0E2B\u0E19\u0E49\u0E32\u0E1B\u0E31\u0E08\u0E08\u0E38\u0E1A\u0E31\u0E19",
932
968
  searchResults: "\u0E1C\u0E25\u0E01\u0E32\u0E23\u0E04\u0E49\u0E19\u0E2B\u0E32\u0E1B\u0E31\u0E08\u0E08\u0E38\u0E1A\u0E31\u0E19",
933
969
  allItems: "\u0E23\u0E32\u0E22\u0E01\u0E32\u0E23\u0E17\u0E35\u0E48\u0E1A\u0E31\u0E19\u0E17\u0E36\u0E01\u0E17\u0E31\u0E49\u0E07\u0E2B\u0E21\u0E14",
970
+ localVersion: "\u0E43\u0E19\u0E40\u0E04\u0E23\u0E37\u0E48\u0E2D\u0E07",
971
+ remoteVersion: "\u0E23\u0E30\u0E22\u0E30\u0E44\u0E01\u0E25",
972
+ updatedAt: "\u0E2D\u0E31\u0E1B\u0E40\u0E14\u0E15\u0E40\u0E21\u0E37\u0E48\u0E2D",
934
973
  statusAvailable: "\u0E1E\u0E23\u0E49\u0E2D\u0E21\u0E43\u0E0A\u0E49\u0E07\u0E32\u0E19",
935
974
  noteSavedMessage: "\u0E1A\u0E31\u0E19\u0E17\u0E36\u0E01\u0E22\u0E48\u0E2D\u0E41\u0E25\u0E49\u0E27",
936
975
  exportData: "\u0E2A\u0E48\u0E07\u0E2D\u0E2D\u0E01 JSON",
@@ -1004,6 +1043,9 @@ var VI_LABELS = {
1004
1043
  currentPage: "Trang hi\u1EC7n t\u1EA1i",
1005
1044
  searchResults: "K\u1EBFt qu\u1EA3 t\xECm ki\u1EBFm hi\u1EC7n t\u1EA1i",
1006
1045
  allItems: "T\u1EA5t c\u1EA3 m\u1EE5c \u0111\xE3 l\u01B0u",
1046
+ localVersion: "C\u1EE5c b\u1ED9",
1047
+ remoteVersion: "M\xE1y ch\u1EE7",
1048
+ updatedAt: "\u0110\xE3 c\u1EADp nh\u1EADt",
1007
1049
  statusAvailable: "Kh\u1EA3 d\u1EE5ng",
1008
1050
  noteSavedMessage: "\u0110\xE3 l\u01B0u ghi ch\xFA.",
1009
1051
  exportData: "Xu\u1EA5t JSON",
@@ -1077,6 +1119,9 @@ var ZH_HANS_LABELS = {
1077
1119
  currentPage: "\u5F53\u524D\u9875",
1078
1120
  searchResults: "\u5F53\u524D\u5168\u90E8\u641C\u7D22\u7ED3\u679C",
1079
1121
  allItems: "\u5168\u90E8\u5DF2\u4FDD\u5B58\u9879\u76EE",
1122
+ localVersion: "\u672C\u5730",
1123
+ remoteVersion: "\u8FDC\u7A0B",
1124
+ updatedAt: "\u66F4\u65B0\u65F6\u95F4",
1080
1125
  statusAvailable: "\u53EF\u7528",
1081
1126
  noteSavedMessage: "\u5907\u6CE8\u5DF2\u4FDD\u5B58\u3002",
1082
1127
  exportData: "\u5BFC\u51FAJSON",
@@ -1150,6 +1195,9 @@ var ZH_HANT_LABELS = {
1150
1195
  currentPage: "\u76EE\u524D\u9801\u9762",
1151
1196
  searchResults: "\u76EE\u524D\u5168\u90E8\u641C\u5C0B\u7D50\u679C",
1152
1197
  allItems: "\u6240\u6709\u5DF2\u5132\u5B58\u9805\u76EE",
1198
+ localVersion: "\u672C\u6A5F",
1199
+ remoteVersion: "\u9060\u7AEF",
1200
+ updatedAt: "\u66F4\u65B0\u6642\u9593",
1153
1201
  statusAvailable: "\u53EF\u7528",
1154
1202
  noteSavedMessage: "\u5099\u8A3B\u5DF2\u5132\u5B58\u3002",
1155
1203
  exportData: "\u532F\u51FAJSON",
@@ -1312,37 +1360,46 @@ function KeepBackup({
1312
1360
  setError(cause);
1313
1361
  }
1314
1362
  }
1315
- return /* @__PURE__ */ jsxs("section", { ...props, "data-state": error ? "error" : result ? "complete" : "idle", children: [
1316
- /* @__PURE__ */ jsx2("button", { type: "button", onClick: () => void handleExport(), disabled: context.isMutating, children: exportLabel }),
1317
- /* @__PURE__ */ jsxs("label", { children: [
1318
- importModeLabel,
1319
- /* @__PURE__ */ jsxs("select", { value: mode, onChange: (event) => setMode(event.currentTarget.value), children: [
1320
- /* @__PURE__ */ jsx2("option", { value: "merge", children: mergeLabel }),
1321
- /* @__PURE__ */ jsx2("option", { value: "replace", children: replaceLabel })
1322
- ] })
1323
- ] }),
1324
- /* @__PURE__ */ jsx2("button", { type: "button", onClick: () => inputRef.current?.click(), disabled: context.isMutating, children: importLabel }),
1325
- /* @__PURE__ */ jsx2(
1326
- "input",
1327
- {
1328
- ref: inputRef,
1329
- type: "file",
1330
- accept: "application/json,.json",
1331
- "aria-label": importLabel,
1332
- onChange: (event) => void handleImport(event)
1333
- }
1334
- ),
1335
- result ? /* @__PURE__ */ jsxs("p", { role: "status", children: [
1336
- result.imported,
1337
- " ",
1338
- importedCountLabel,
1339
- "; ",
1340
- result.failed,
1341
- " ",
1342
- failedCountLabel
1343
- ] }) : null,
1344
- error ? /* @__PURE__ */ jsx2("p", { role: "alert", children: isQuotaError(error) ? quotaErrorLabel : getErrorMessage(error) }) : null
1345
- ] });
1363
+ return /* @__PURE__ */ jsxs(
1364
+ "section",
1365
+ {
1366
+ ...props,
1367
+ "data-keepkit": "backup",
1368
+ "data-state": error ? "error" : result ? "complete" : "idle",
1369
+ "data-loading": context.isMutating ? "true" : void 0,
1370
+ children: [
1371
+ /* @__PURE__ */ jsx2("button", { type: "button", onClick: () => void handleExport(), disabled: context.isMutating, children: exportLabel }),
1372
+ /* @__PURE__ */ jsxs("label", { children: [
1373
+ importModeLabel,
1374
+ /* @__PURE__ */ jsxs("select", { value: mode, onChange: (event) => setMode(event.currentTarget.value), children: [
1375
+ /* @__PURE__ */ jsx2("option", { value: "merge", children: mergeLabel }),
1376
+ /* @__PURE__ */ jsx2("option", { value: "replace", children: replaceLabel })
1377
+ ] })
1378
+ ] }),
1379
+ /* @__PURE__ */ jsx2("button", { type: "button", onClick: () => inputRef.current?.click(), disabled: context.isMutating, children: importLabel }),
1380
+ /* @__PURE__ */ jsx2(
1381
+ "input",
1382
+ {
1383
+ ref: inputRef,
1384
+ type: "file",
1385
+ accept: "application/json,.json",
1386
+ "aria-label": importLabel,
1387
+ onChange: (event) => void handleImport(event)
1388
+ }
1389
+ ),
1390
+ result ? /* @__PURE__ */ jsxs("p", { role: "status", children: [
1391
+ result.imported,
1392
+ " ",
1393
+ importedCountLabel,
1394
+ "; ",
1395
+ result.failed,
1396
+ " ",
1397
+ failedCountLabel
1398
+ ] }) : null,
1399
+ error ? /* @__PURE__ */ jsx2("p", { role: "alert", children: isQuotaError(error) ? quotaErrorLabel : getErrorMessage(error) }) : null
1400
+ ]
1401
+ }
1402
+ );
1346
1403
  }
1347
1404
  function isQuotaError(error) {
1348
1405
  if (error instanceof Error && error.name === "KeepStorageQuotaError") return true;
@@ -1375,6 +1432,7 @@ function KeepItemCheckbox({
1375
1432
  ...props,
1376
1433
  type: "checkbox",
1377
1434
  checked,
1435
+ "data-keepkit": "item-checkbox",
1378
1436
  "data-state": checked ? "checked" : "unchecked",
1379
1437
  "data-disabled": props.disabled ? "true" : void 0,
1380
1438
  "aria-label": accessibleLabel,
@@ -1568,6 +1626,7 @@ function KeepBulkActions({
1568
1626
  "section",
1569
1627
  {
1570
1628
  ...props,
1629
+ "data-keepkit": "bulk-actions",
1571
1630
  "aria-busy": list.isMutating || props["aria-busy"],
1572
1631
  "data-state": selectedIds.length > 0 ? "selected" : "idle",
1573
1632
  "data-loading": list.isLoading || list.isMutating ? "true" : void 0,
@@ -1581,23 +1640,37 @@ import {
1581
1640
  KeepButton as CoreKeepButton,
1582
1641
  useKeepItem
1583
1642
  } from "@keepkit/core/react";
1584
- import { Fragment as Fragment2, jsx as jsx6 } from "react/jsx-runtime";
1585
- function KeepButton({ labels, ...props }) {
1643
+ import { createElement } from "react";
1644
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
1645
+ function KeepButton({
1646
+ labels,
1647
+ icons,
1648
+ iconOnly = false,
1649
+ showLabel = true,
1650
+ iconClassName,
1651
+ ...props
1652
+ }) {
1586
1653
  const saveLabel = useUiLabel("save", typeof labels?.unsaved === "string" ? labels.unsaved : void 0);
1587
1654
  const savedLabel = useUiLabel("saved", typeof labels?.saved === "string" ? labels.saved : void 0);
1588
1655
  const loadingLabel = useUiLabel("loading", typeof labels?.loading === "string" ? labels.loading : void 0);
1589
1656
  const errorLabel = useUiLabel("error", typeof labels?.error === "string" ? labels.error : void 0);
1590
1657
  const buttonState = useKeepItem(props.item);
1591
- const customStateLabel = labels?.loading !== void 0 || labels?.error !== void 0;
1658
+ const customStateLabel = labels?.loading !== void 0 || labels?.error !== void 0 || icons !== void 0 && props.children === void 0;
1592
1659
  const getStateContent = (state) => {
1593
- if (state.error) return labels?.error ?? errorLabel;
1594
- if (state.isMutating) return labels?.loading ?? loadingLabel;
1595
1660
  if (typeof props.children === "function") return props.children(state);
1596
1661
  if (props.children !== void 0) return props.children;
1597
- return state.isSaved ? labels?.saved ?? savedLabel : labels?.unsaved ?? saveLabel;
1662
+ const label = state.error ? labels?.error ?? errorLabel : state.isMutating ? labels?.loading ?? loadingLabel : state.isSaved ? labels?.saved ?? savedLabel : labels?.unsaved ?? saveLabel;
1663
+ if (!icons) return label;
1664
+ const icon = state.error ? icons.error : state.isMutating ? icons.loading : state.isSaved ? icons.remove ?? icons.saved : icons.save;
1665
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
1666
+ renderIcon(icon, iconClassName),
1667
+ showLabel && !iconOnly ? label : null
1668
+ ] });
1598
1669
  };
1599
1670
  const sharedProps = {
1600
1671
  ...props,
1672
+ "data-keepkit": "button",
1673
+ "data-icon-only": iconOnly ? "true" : void 0,
1601
1674
  "aria-busy": props["aria-busy"] ?? (buttonState.isLoading || buttonState.isMutating),
1602
1675
  savedLabel: labels?.saved ?? props.savedLabel ?? savedLabel,
1603
1676
  unsavedLabel: labels?.unsaved ?? props.unsavedLabel ?? saveLabel,
@@ -1607,6 +1680,10 @@ function KeepButton({ labels, ...props }) {
1607
1680
  if (!customStateLabel) return /* @__PURE__ */ jsx6(CoreKeepButton, { ...sharedProps });
1608
1681
  return /* @__PURE__ */ jsx6(CoreKeepButton, { ...sharedProps, children: (state) => /* @__PURE__ */ jsx6(Fragment2, { children: getStateContent(state) }) });
1609
1682
  }
1683
+ function renderIcon(icon, className) {
1684
+ if (typeof icon === "function") return createElement(icon, { "aria-hidden": true, className });
1685
+ return icon ?? null;
1686
+ }
1610
1687
 
1611
1688
  // src/KeepCollection.tsx
1612
1689
  import { KeepErrorBoundary as KeepErrorBoundary2, useKeepList as useKeepList4 } from "@keepkit/core/react";
@@ -1632,9 +1709,19 @@ import { useState as useState3 } from "react";
1632
1709
  // src/KeepItemStatusBadge.tsx
1633
1710
  import { jsx as jsx7 } from "react/jsx-runtime";
1634
1711
  function KeepItemStatusBadge({ status = "available", label, className, ...props }) {
1635
- const resolvedStatus = status === "available" ? "available" : status;
1636
- const statusLabel = useUiLabel(getStatusLabelKey(resolvedStatus));
1637
- return /* @__PURE__ */ jsx7("span", { ...props, className, "data-keepkit": "status-badge", "data-status": resolvedStatus, children: label ?? statusLabel });
1712
+ const resolvedStatus = getDisplayStatus(status);
1713
+ const statusLabel = useUiLabel(getStatusLabelKey(status));
1714
+ return /* @__PURE__ */ jsx7(
1715
+ "span",
1716
+ {
1717
+ ...props,
1718
+ className,
1719
+ "data-keepkit": "status-badge",
1720
+ "data-status": status,
1721
+ "data-item-status": resolvedStatus,
1722
+ children: label ?? statusLabel
1723
+ }
1724
+ );
1638
1725
  }
1639
1726
  function getStatusLabelKey(status) {
1640
1727
  switch (status) {
@@ -1647,13 +1734,22 @@ function getStatusLabelKey(status) {
1647
1734
  case "deleted":
1648
1735
  return "statusDeleted";
1649
1736
  case "private":
1737
+ return "statusPrivate";
1650
1738
  case "unknown":
1651
- return status === "private" ? "statusPrivate" : "statusUnknown";
1739
+ return "statusUnknown";
1740
+ case "restricted":
1741
+ return "statusPrivate";
1652
1742
  }
1653
1743
  }
1744
+ function getDisplayStatus(status) {
1745
+ if (status === "available") return "available";
1746
+ if (status === "expired") return "expired";
1747
+ if (status === "removed") return "removed";
1748
+ return "restricted";
1749
+ }
1654
1750
 
1655
1751
  // src/KeepStaleNotice.tsx
1656
- import { jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
1752
+ import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
1657
1753
  function KeepStaleNotice({
1658
1754
  item,
1659
1755
  onRetry,
@@ -1691,10 +1787,10 @@ function KeepStaleNotice({
1691
1787
  setError(cause);
1692
1788
  }
1693
1789
  }
1694
- return /* @__PURE__ */ jsxs3("aside", { ...props, className, "data-keepkit": "stale-notice", "data-state": error ? "error" : "stale", children: [
1790
+ return /* @__PURE__ */ jsxs4("aside", { ...props, className, "data-keepkit": "stale-notice", "data-state": error ? "error" : "stale", children: [
1695
1791
  /* @__PURE__ */ jsx8(KeepItemStatusBadge, { status }),
1696
1792
  children ?? (item.statusReason ? /* @__PURE__ */ jsx8("p", { children: item.statusReason }) : null),
1697
- /* @__PURE__ */ jsxs3("div", { children: [
1793
+ /* @__PURE__ */ jsxs4("div", { children: [
1698
1794
  /* @__PURE__ */ jsx8("button", { type: "button", onClick: () => void handleRetry(), disabled: isRetrying || context.isMutating, children: retryLabel ?? retryText }),
1699
1795
  /* @__PURE__ */ jsx8("button", { type: "button", onClick: () => void handleRemove(), disabled: context.isMutating, children: removeLabel ?? removeText })
1700
1796
  ] }),
@@ -1733,7 +1829,7 @@ function KeepPruneStaleButton({
1733
1829
  }
1734
1830
 
1735
1831
  // src/KeepItemCard.tsx
1736
- import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
1832
+ import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
1737
1833
  function KeepItemCard({
1738
1834
  item,
1739
1835
  title,
@@ -1743,6 +1839,7 @@ function KeepItemCard({
1743
1839
  renderImage,
1744
1840
  renderTags,
1745
1841
  showTags = true,
1842
+ showSavedAt = true,
1746
1843
  imageAlt,
1747
1844
  render,
1748
1845
  children,
@@ -1763,6 +1860,8 @@ function KeepItemCard({
1763
1860
  ...rootProps
1764
1861
  }) {
1765
1862
  const saveActionLabel = useUiLabel("save");
1863
+ const savedAtLabel = useUiLabel("saved");
1864
+ const errorLabel = useUiLabel("error");
1766
1865
  const removeActionLabel = useUiLabel("remove");
1767
1866
  const itemState = useKeepItem2(item);
1768
1867
  const contentChildren = asChild && isValidElement2(children) ? void 0 : children;
@@ -1778,6 +1877,10 @@ function KeepItemCard({
1778
1877
  const imageProps = getImageProps?.(item, resolvedTitle);
1779
1878
  const href = typeof hrefOption === "function" ? hrefOption(item) : hrefOption;
1780
1879
  const isAvailable = item.status === void 0 || item.status === "available";
1880
+ const displayStatus = getDisplayStatus2(item.status);
1881
+ const isExternalLink = href ? isExternalHref(href) : false;
1882
+ const resolvedLinkTarget = linkTargetAttribute ?? (isExternalLink ? "_blank" : void 0);
1883
+ const resolvedLinkRel = linkRel ?? (isExternalLink ? "noreferrer" : void 0);
1781
1884
  const statusLabelKey = item.status && item.status !== "available" ? getStatusLabelKey2(item.status) : "statusUnknown";
1782
1885
  const unavailableLabel = useUiLabel(statusLabelKey);
1783
1886
  const tagsLabel = useUiLabel("tags");
@@ -1786,8 +1889,8 @@ function KeepItemCard({
1786
1889
  if (!href || !isAvailable) return content;
1787
1890
  const linkProps = {
1788
1891
  href,
1789
- target: linkTargetAttribute,
1790
- rel: linkRel,
1892
+ target: resolvedLinkTarget,
1893
+ rel: resolvedLinkRel,
1791
1894
  onClick: (event) => onOpen?.(item, event),
1792
1895
  children: content
1793
1896
  };
@@ -1801,10 +1904,19 @@ function KeepItemCard({
1801
1904
  onRemoveError?.(error);
1802
1905
  }
1803
1906
  }
1804
- const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs4(Fragment3, { children: [
1907
+ const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs5(Fragment3, { children: [
1805
1908
  imageProps ? renderImage?.({ ...imageProps, alt: imageAlt ?? imageProps.alt }, item) ?? (ImageComponent ? /* @__PURE__ */ jsx9(ImageComponent, { ...imageProps, alt: imageAlt ?? imageProps.alt }) : /* @__PURE__ */ jsx9("img", { ...imageProps, alt: imageAlt ?? imageProps.alt })) : null,
1806
- /* @__PURE__ */ jsx9("h3", { children: linkTarget === "title" ? renderLink(resolvedTitle) : resolvedTitle }),
1909
+ /* @__PURE__ */ jsx9("h3", { children: linkTarget === "title" ? isAvailable ? renderLink(resolvedTitle) : href ? /* @__PURE__ */ jsx9("span", { "aria-disabled": "true", "data-link-disabled": "true", children: resolvedTitle }) : resolvedTitle : resolvedTitle }),
1910
+ showSavedAt ? /* @__PURE__ */ jsxs5("div", { "data-card-meta": true, children: [
1911
+ /* @__PURE__ */ jsxs5("span", { children: [
1912
+ savedAtLabel,
1913
+ ":"
1914
+ ] }),
1915
+ " ",
1916
+ /* @__PURE__ */ jsx9("time", { dateTime: new Date(item.savedAt).toISOString(), children: formatSavedAt(item.savedAt) })
1917
+ ] }) : null,
1807
1918
  showTags && (item.tags?.length ?? 0) > 0 ? renderTags ? renderTags(item.tags ?? [], item) : /* @__PURE__ */ jsx9("ul", { "aria-label": tagsLabel, children: item.tags?.map((tag) => /* @__PURE__ */ jsx9("li", { children: tag }, tag)) }) : null,
1919
+ itemState.error ? /* @__PURE__ */ jsx9("p", { role: "alert", children: getErrorMessage2(itemState.error, errorLabel) }) : null,
1808
1920
  statusLabel ? /* @__PURE__ */ jsx9(KeepStaleNotice, { item, onRetry, onRemoved }) : null,
1809
1921
  showSaveButton && !statusLabel ? /* @__PURE__ */ jsx9(
1810
1922
  KeepButton,
@@ -1816,16 +1928,19 @@ function KeepItemCard({
1816
1928
  ) : null,
1817
1929
  !statusLabel ? /* @__PURE__ */ jsx9("button", { type: "button", onClick: () => void handleRemove(), disabled: itemState.isMutating, children: removeLabel ?? removeActionLabel }) : null
1818
1930
  ] });
1819
- const linkedBody = linkTarget === "card" ? renderLink(body) : body;
1931
+ const linkedBody = linkTarget === "card" && isAvailable ? renderLink(body) : body;
1820
1932
  return renderRoot(
1821
1933
  asChild,
1822
1934
  isValidElement2(children) ? children : void 0,
1823
1935
  {
1824
1936
  ...rootProps,
1825
1937
  className,
1938
+ "data-keepkit": "card",
1826
1939
  "aria-busy": itemState.isMutating || rootProps["aria-busy"],
1827
- "data-state": itemState.isSaved ? "saved" : "unsaved",
1940
+ "aria-disabled": rootProps["aria-disabled"] ?? (!isAvailable ? "true" : void 0),
1941
+ "data-state": itemState.error ? "error" : itemState.isSaved ? "saved" : "unsaved",
1828
1942
  "data-status": item.status ?? "available",
1943
+ "data-item-status": displayStatus,
1829
1944
  "data-loading": itemState.isMutating ? "true" : void 0
1830
1945
  },
1831
1946
  linkedBody,
@@ -1846,6 +1961,21 @@ function getStatusLabelKey2(status) {
1846
1961
  return "statusUnknown";
1847
1962
  }
1848
1963
  }
1964
+ function getDisplayStatus2(status) {
1965
+ if (status === void 0 || status === "available") return "available";
1966
+ if (status === "expired") return "expired";
1967
+ if (status === "removed") return "removed";
1968
+ return "restricted";
1969
+ }
1970
+ function isExternalHref(href) {
1971
+ return /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href);
1972
+ }
1973
+ function formatSavedAt(timestamp) {
1974
+ return new Date(timestamp).toISOString().slice(0, 10);
1975
+ }
1976
+ function getErrorMessage2(error, fallback) {
1977
+ return error instanceof Error ? error.message : fallback;
1978
+ }
1849
1979
 
1850
1980
  // src/KeepList.tsx
1851
1981
  import { jsx as jsx10 } from "react/jsx-runtime";
@@ -1887,6 +2017,8 @@ function KeepListContent({
1887
2017
  {
1888
2018
  ...rootProps,
1889
2019
  className,
2020
+ "data-keepkit": "list",
2021
+ "data-layout": layout,
1890
2022
  "aria-busy": state.isLoading || rootProps["aria-busy"],
1891
2023
  "data-state": getListState(state),
1892
2024
  "data-loading": state.isLoading ? "true" : void 0
@@ -1907,22 +2039,15 @@ function getListBody(state, options) {
1907
2039
  if (state.isHydrated && state.items.length === 0) return resolveContent(options.empty, state);
1908
2040
  if (typeof options.children === "function") return options.children(state);
1909
2041
  if (options.children !== void 0 && !isValidElement3(options.children)) return options.children;
1910
- return /* @__PURE__ */ jsx10(
1911
- "ul",
1912
- {
1913
- "data-layout": options.layout,
1914
- style: options.layout === "grid" ? { display: "grid", gap: "1rem", gridTemplateColumns: "repeat(auto-fit, minmax(16rem, 1fr))" } : options.layout === "compact" ? { display: "grid", gap: "0.5rem", gridTemplateColumns: "repeat(auto-fit, minmax(12rem, 1fr))" } : { display: "flex", flexDirection: "column", gap: "1rem" },
1915
- children: state.items.map(
1916
- (item) => options.renderItem ? options.renderItem(item, state) : /* @__PURE__ */ jsx10("li", { children: /* @__PURE__ */ jsx10(KeepItemCard, { item, ...options.itemCardProps }) }, item.id)
1917
- )
1918
- }
1919
- );
2042
+ return /* @__PURE__ */ jsx10("ul", { "data-layout": options.layout, children: state.items.map(
2043
+ (item) => options.renderItem ? options.renderItem(item, state) : /* @__PURE__ */ jsx10("li", { children: /* @__PURE__ */ jsx10(KeepItemCard, { item, ...options.itemCardProps }) }, item.id)
2044
+ ) });
1920
2045
  }
1921
2046
 
1922
2047
  // src/KeepTagFilter.tsx
1923
2048
  import { useKeepList as useKeepList3 } from "@keepkit/core/react";
1924
2049
  import { isValidElement as isValidElement4, useCallback, useMemo as useMemo2, useState as useState4 } from "react";
1925
- import { jsx as jsx11, jsxs as jsxs5 } from "react/jsx-runtime";
2050
+ import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
1926
2051
  function KeepTagFilter({
1927
2052
  query,
1928
2053
  value: controlledValue,
@@ -1959,12 +2084,12 @@ function KeepTagFilter({
1959
2084
  [list.tagCounts, list.tags, resolvedValue, select]
1960
2085
  );
1961
2086
  const contentChildren = asChild && isValidElement4(children) ? void 0 : children;
1962
- const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs5("fieldset", { children: [
2087
+ const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs6("fieldset", { children: [
1963
2088
  /* @__PURE__ */ jsx11("legend", { children: ariaLabel ?? uiAriaLabel }),
1964
2089
  /* @__PURE__ */ jsx11("button", { type: "button", "aria-pressed": resolvedValue === void 0, onClick: () => select(), children: allLabel ?? uiAllLabel }),
1965
- list.tags.map((tag) => /* @__PURE__ */ jsxs5("button", { type: "button", "aria-pressed": resolvedValue === tag, onClick: () => select(tag), children: [
2090
+ list.tags.map((tag) => /* @__PURE__ */ jsxs6("button", { type: "button", "aria-pressed": resolvedValue === tag, onClick: () => select(tag), children: [
1966
2091
  renderTag ? renderTag(tag, list.tagCounts[tag] ?? 0, resolvedValue === tag) : tag,
1967
- /* @__PURE__ */ jsxs5("span", { children: [
2092
+ /* @__PURE__ */ jsxs6("span", { children: [
1968
2093
  " (",
1969
2094
  list.tagCounts[tag] ?? 0,
1970
2095
  ")"
@@ -1977,6 +2102,7 @@ function KeepTagFilter({
1977
2102
  {
1978
2103
  ...rootProps,
1979
2104
  className,
2105
+ "data-keepkit": "tag-filter",
1980
2106
  "data-state": resolvedValue === void 0 ? "all" : "filtered",
1981
2107
  "data-loading": list.isLoading ? "true" : void 0
1982
2108
  },
@@ -1987,7 +2113,7 @@ function KeepTagFilter({
1987
2113
 
1988
2114
  // src/query-controls.tsx
1989
2115
  import { useEffect, useState as useState5 } from "react";
1990
- import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
2116
+ import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
1991
2117
  function KeepSearchInput({
1992
2118
  value: controlledValue,
1993
2119
  defaultValue = "",
@@ -2013,6 +2139,7 @@ function KeepSearchInput({
2013
2139
  "input",
2014
2140
  {
2015
2141
  ...props,
2142
+ "data-keepkit": "search-input",
2016
2143
  type: "search",
2017
2144
  value,
2018
2145
  "data-state": value ? "active" : "idle",
@@ -2041,7 +2168,7 @@ function KeepSortSelect({
2041
2168
  const savedOldestLabel = useUiLabel("savedOldest");
2042
2169
  const [uncontrolledValue, setUncontrolledValue] = useState5(defaultValue);
2043
2170
  const value = controlledValue ?? uncontrolledValue;
2044
- const options = children ?? /* @__PURE__ */ jsxs6(Fragment4, { children: [
2171
+ const options = children ?? /* @__PURE__ */ jsxs7(Fragment4, { children: [
2045
2172
  /* @__PURE__ */ jsx12("option", { value: "updatedAt:desc", children: updatedNewestLabel }),
2046
2173
  /* @__PURE__ */ jsx12("option", { value: "updatedAt:asc", children: updatedOldestLabel }),
2047
2174
  /* @__PURE__ */ jsx12("option", { value: "savedAt:desc", children: savedNewestLabel }),
@@ -2051,6 +2178,7 @@ function KeepSortSelect({
2051
2178
  "select",
2052
2179
  {
2053
2180
  ...props,
2181
+ "data-keepkit": "sort-select",
2054
2182
  value,
2055
2183
  "data-state": "selected",
2056
2184
  "data-disabled": props.disabled ? "true" : void 0,
@@ -2086,12 +2214,13 @@ function KeepPagination({
2086
2214
  };
2087
2215
  const navProps = {
2088
2216
  ...props,
2217
+ "data-keepkit": "pagination",
2089
2218
  "aria-label": props["aria-label"] ?? paginationLabel,
2090
2219
  "data-state": pageCount > 1 ? "active" : "idle"
2091
2220
  };
2092
2221
  if (render) return /* @__PURE__ */ jsx12("nav", { ...navProps, children: render({ page: currentPage, pageCount, goToPage }) });
2093
2222
  const visiblePages = getVisiblePages(currentPage, pageCount, Math.max(1, maxPageButtons));
2094
- return /* @__PURE__ */ jsxs6("nav", { ...navProps, children: [
2223
+ return /* @__PURE__ */ jsxs7("nav", { ...navProps, children: [
2095
2224
  /* @__PURE__ */ jsx12("button", { type: "button", onClick: () => goToPage(currentPage - 1), disabled: currentPage <= 1, children: previousPageLabel }),
2096
2225
  visiblePages.map((nextPage) => /* @__PURE__ */ jsx12(
2097
2226
  "button",
@@ -2197,7 +2326,7 @@ function getBrowserAdapter() {
2197
2326
  }
2198
2327
 
2199
2328
  // src/KeepCollection.tsx
2200
- import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
2329
+ import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
2201
2330
  function KeepCollection(props) {
2202
2331
  const { fallback, onBoundaryError, boundaryResetKey, ...collectionProps } = props;
2203
2332
  const content = /* @__PURE__ */ jsx13(KeepCollectionContent, { ...collectionProps });
@@ -2256,16 +2385,18 @@ function KeepCollectionContent({
2256
2385
  adapter: urlAdapter
2257
2386
  });
2258
2387
  const list = useKeepList4(resolvedQuery);
2259
- return /* @__PURE__ */ jsxs7(
2388
+ return /* @__PURE__ */ jsxs8(
2260
2389
  "section",
2261
2390
  {
2262
2391
  ...rootProps,
2263
2392
  className,
2393
+ "data-keepkit": "collection",
2394
+ "data-layout": layout,
2264
2395
  "aria-busy": list.isLoading || list.isMutating || rootProps["aria-busy"],
2265
2396
  "data-state": getCollectionState(list),
2266
2397
  "data-loading": list.isLoading || list.isMutating ? "true" : void 0,
2267
2398
  children: [
2268
- /* @__PURE__ */ jsxs7("div", { children: [
2399
+ /* @__PURE__ */ jsxs8("div", { children: [
2269
2400
  enabled.search ? /* @__PURE__ */ jsx13(
2270
2401
  KeepSearchInput,
2271
2402
  {
@@ -2333,9 +2464,8 @@ function getCollectionState(list) {
2333
2464
 
2334
2465
  // src/KeepLayout.tsx
2335
2466
  import { jsx as jsx14 } from "react/jsx-runtime";
2336
- function KeepLayout({ layout = "list", children, style, ...props }) {
2337
- const layoutStyle = layout === "grid" ? { display: "grid", gap: "1rem", gridTemplateColumns: "repeat(auto-fit, minmax(16rem, 1fr))" } : layout === "compact" ? { display: "grid", gap: "0.5rem", gridTemplateColumns: "repeat(auto-fit, minmax(12rem, 1fr))" } : { display: "flex", flexDirection: "column", gap: "1rem" };
2338
- return /* @__PURE__ */ jsx14("div", { ...props, style: { ...layoutStyle, ...style }, "data-layout": layout, children });
2467
+ function KeepLayout({ layout = "list", children, ...props }) {
2468
+ return /* @__PURE__ */ jsx14("div", { ...props, "data-keepkit": "layout", "data-layout": layout, children });
2339
2469
  }
2340
2470
 
2341
2471
  // src/KeepNoteEditor.tsx
@@ -2347,7 +2477,7 @@ import {
2347
2477
  useRef as useRef3,
2348
2478
  useState as useState7
2349
2479
  } from "react";
2350
- import { Fragment as Fragment5, jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
2480
+ import { Fragment as Fragment5, jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
2351
2481
  function KeepNoteEditor({
2352
2482
  item,
2353
2483
  label,
@@ -2364,6 +2494,7 @@ function KeepNoteEditor({
2364
2494
  }) {
2365
2495
  const defaultLabel = useUiLabel("note");
2366
2496
  const defaultSaveLabel = useUiLabel("saveNote");
2497
+ const errorLabel = useUiLabel("error");
2367
2498
  const itemState = useKeepItem3(item);
2368
2499
  const { error, isMutating, item: savedItem, updateNote } = itemState;
2369
2500
  const contentChildren = asChild && isValidElement5(children) ? void 0 : children;
@@ -2397,8 +2528,8 @@ function KeepNoteEditor({
2397
2528
  error,
2398
2529
  save
2399
2530
  };
2400
- const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs8(Fragment5, { children: [
2401
- /* @__PURE__ */ jsxs8("label", { children: [
2531
+ const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs9(Fragment5, { children: [
2532
+ /* @__PURE__ */ jsxs9("label", { children: [
2402
2533
  label ?? defaultLabel,
2403
2534
  /* @__PURE__ */ jsx15(
2404
2535
  "textarea",
@@ -2423,17 +2554,21 @@ function KeepNoteEditor({
2423
2554
  void save().catch(() => void 0);
2424
2555
  };
2425
2556
  if (!asChild) {
2426
- return /* @__PURE__ */ jsx15(
2557
+ return /* @__PURE__ */ jsxs9(
2427
2558
  "form",
2428
2559
  {
2429
2560
  ...formProps,
2430
2561
  className,
2562
+ "data-keepkit": "note-editor",
2431
2563
  onSubmit: handleSubmit,
2432
2564
  "aria-busy": isMutating || formProps["aria-busy"],
2433
- "data-state": isDirty ? "dirty" : "clean",
2565
+ "data-state": error ? "error" : isDirty ? "dirty" : "clean",
2434
2566
  "data-loading": isMutating ? "true" : void 0,
2435
2567
  "data-disabled": isMutating ? "true" : void 0,
2436
- children: body
2568
+ children: [
2569
+ body,
2570
+ error ? /* @__PURE__ */ jsx15("p", { role: "alert", children: getErrorMessage3(error, errorLabel) }) : null
2571
+ ]
2437
2572
  }
2438
2573
  );
2439
2574
  }
@@ -2443,9 +2578,10 @@ function KeepNoteEditor({
2443
2578
  {
2444
2579
  ...formProps,
2445
2580
  className,
2581
+ "data-keepkit": "note-editor",
2446
2582
  onSubmit: handleSubmit,
2447
2583
  "aria-busy": isMutating || formProps["aria-busy"],
2448
- "data-state": isDirty ? "dirty" : "clean",
2584
+ "data-state": error ? "error" : isDirty ? "dirty" : "clean",
2449
2585
  "data-loading": isMutating ? "true" : void 0,
2450
2586
  "data-disabled": isMutating ? "true" : void 0
2451
2587
  },
@@ -2453,11 +2589,14 @@ function KeepNoteEditor({
2453
2589
  "KeepNoteEditor"
2454
2590
  );
2455
2591
  }
2592
+ function getErrorMessage3(error, fallback) {
2593
+ return error instanceof Error ? error.message : fallback;
2594
+ }
2456
2595
 
2457
2596
  // src/KeepSyncRecoveryDialog.tsx
2458
2597
  import { useKeepContext as useKeepContext3 } from "@keepkit/core/react";
2459
2598
  import { useEffect as useEffect4, useState as useState8 } from "react";
2460
- import { jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
2599
+ import { jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
2461
2600
  function KeepSyncRecoveryDialog({
2462
2601
  open,
2463
2602
  onOpenChange,
@@ -2478,6 +2617,10 @@ function KeepSyncRecoveryDialog({
2478
2617
  const keepLocalLabel = useUiLabel("keepLocal");
2479
2618
  const useServerLabel = useUiLabel("useServer");
2480
2619
  const manualMergeLabel = useUiLabel("manualMerge");
2620
+ const localVersionLabel = useUiLabel("localVersion");
2621
+ const remoteVersionLabel = useUiLabel("remoteVersion");
2622
+ const updatedAtLabel = useUiLabel("updatedAt");
2623
+ const noteLabel = useUiLabel("note");
2481
2624
  const backupRecoveryLabel = useUiLabel("backupRecovery");
2482
2625
  const backupRecoveryDescription = useUiLabel("backupRecoveryDescription");
2483
2626
  const errorLabel = useUiLabel("error");
@@ -2508,7 +2651,7 @@ function KeepSyncRecoveryDialog({
2508
2651
  setBusyId(void 0);
2509
2652
  }
2510
2653
  }
2511
- return /* @__PURE__ */ jsxs9(
2654
+ return /* @__PURE__ */ jsxs10(
2512
2655
  "section",
2513
2656
  {
2514
2657
  ...props,
@@ -2516,19 +2659,44 @@ function KeepSyncRecoveryDialog({
2516
2659
  role: "dialog",
2517
2660
  "aria-modal": "true",
2518
2661
  "aria-labelledby": "keepkit-sync-recovery-title",
2662
+ "aria-describedby": error ? "keepkit-sync-recovery-error" : void 0,
2663
+ "aria-busy": busyId !== void 0,
2519
2664
  "data-keepkit": "sync-recovery",
2520
2665
  "data-state": conflictList.length > 0 ? "conflict" : "error",
2666
+ "data-loading": busyId !== void 0 ? "true" : void 0,
2521
2667
  children: [
2522
- /* @__PURE__ */ jsxs9("header", { children: [
2668
+ /* @__PURE__ */ jsxs10("header", { children: [
2523
2669
  /* @__PURE__ */ jsx16("h2", { id: "keepkit-sync-recovery-title", children: dialogTitle }),
2524
2670
  /* @__PURE__ */ jsx16("button", { type: "button", onClick: close, "aria-label": closeLabel, children: closeLabel })
2525
2671
  ] }),
2526
2672
  children,
2527
- conflictList.length > 0 ? /* @__PURE__ */ jsxs9("div", { children: [
2673
+ conflictList.length > 0 ? /* @__PURE__ */ jsxs10("div", { children: [
2528
2674
  /* @__PURE__ */ jsx16("p", { children: conflictLabel }),
2529
- conflictList.map((conflict) => /* @__PURE__ */ jsxs9("article", { "data-conflict-id": conflict.id, children: [
2675
+ conflictList.map((conflict) => /* @__PURE__ */ jsxs10("article", { "data-conflict-id": conflict.id, children: [
2530
2676
  /* @__PURE__ */ jsx16("h3", { children: getMetaTitle(conflict.operation.item?.meta) ?? conflict.id }),
2531
- /* @__PURE__ */ jsxs9("div", { children: [
2677
+ /* @__PURE__ */ jsxs10("div", { "data-conflict-preview": true, children: [
2678
+ /* @__PURE__ */ jsx16(
2679
+ ConflictPreview,
2680
+ {
2681
+ item: conflict.operation.item,
2682
+ heading: localVersionLabel,
2683
+ updatedAtLabel,
2684
+ noteLabel,
2685
+ side: "local"
2686
+ }
2687
+ ),
2688
+ /* @__PURE__ */ jsx16(
2689
+ ConflictPreview,
2690
+ {
2691
+ item: conflict.remote,
2692
+ heading: remoteVersionLabel,
2693
+ updatedAtLabel,
2694
+ noteLabel,
2695
+ side: "remote"
2696
+ }
2697
+ )
2698
+ ] }),
2699
+ /* @__PURE__ */ jsxs10("div", { children: [
2532
2700
  /* @__PURE__ */ jsx16("button", { type: "button", onClick: () => void resolve(conflict, "local"), disabled: busyId !== void 0, children: keepLocalLabel }),
2533
2701
  /* @__PURE__ */ jsx16("button", { type: "button", onClick: () => void resolve(conflict, "remote"), disabled: busyId !== void 0, children: useServerLabel }),
2534
2702
  /* @__PURE__ */ jsx16(
@@ -2543,20 +2711,44 @@ function KeepSyncRecoveryDialog({
2543
2711
  ] })
2544
2712
  ] }, conflict.id))
2545
2713
  ] }) : null,
2546
- context.syncState.status === "error" || context.error ? /* @__PURE__ */ jsxs9("section", { "data-recovery": "backup", children: [
2714
+ context.syncState.status === "error" || context.error ? /* @__PURE__ */ jsxs10("section", { "data-recovery": "backup", children: [
2547
2715
  /* @__PURE__ */ jsx16("h3", { children: backupRecoveryLabel }),
2548
2716
  /* @__PURE__ */ jsx16("p", { children: backupRecoveryDescription }),
2549
2717
  backup ?? (showBackupControls ? /* @__PURE__ */ jsx16(KeepBackup, {}) : null)
2550
2718
  ] }) : null,
2551
- error ? /* @__PURE__ */ jsx16("p", { role: "alert", children: error instanceof Error ? error.message : errorLabel }) : null
2719
+ error ? /* @__PURE__ */ jsx16("p", { id: "keepkit-sync-recovery-error", role: "alert", "aria-live": "assertive", children: error instanceof Error ? error.message : errorLabel }) : null
2552
2720
  ]
2553
2721
  }
2554
2722
  );
2555
2723
  }
2724
+ function ConflictPreview({
2725
+ item,
2726
+ heading,
2727
+ updatedAtLabel,
2728
+ noteLabel,
2729
+ side
2730
+ }) {
2731
+ return /* @__PURE__ */ jsxs10("article", { "data-conflict-version": side, "aria-label": heading, children: [
2732
+ /* @__PURE__ */ jsx16("h4", { children: heading }),
2733
+ /* @__PURE__ */ jsxs10("dl", { children: [
2734
+ /* @__PURE__ */ jsxs10("div", { children: [
2735
+ /* @__PURE__ */ jsx16("dt", { children: updatedAtLabel }),
2736
+ /* @__PURE__ */ jsx16("dd", { children: item ? /* @__PURE__ */ jsx16("time", { dateTime: new Date(item.updatedAt).toISOString(), children: formatConflictDate(item.updatedAt) }) : "\u2014" })
2737
+ ] }),
2738
+ /* @__PURE__ */ jsxs10("div", { children: [
2739
+ /* @__PURE__ */ jsx16("dt", { children: noteLabel }),
2740
+ /* @__PURE__ */ jsx16("dd", { children: item?.note || "\u2014" })
2741
+ ] })
2742
+ ] })
2743
+ ] });
2744
+ }
2745
+ function formatConflictDate(timestamp) {
2746
+ return new Date(timestamp).toISOString().slice(0, 10);
2747
+ }
2556
2748
 
2557
2749
  // src/KeepSyncStatusBanner.tsx
2558
2750
  import { useKeepContext as useKeepContext4 } from "@keepkit/core/react";
2559
- import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
2751
+ import { jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
2560
2752
  function KeepSyncStatusBanner({
2561
2753
  onRetry,
2562
2754
  onResolveConflicts,
@@ -2573,7 +2765,7 @@ function KeepSyncStatusBanner({
2573
2765
  const status = context.syncState.status;
2574
2766
  const hasConflicts = (context.syncState.conflicts?.length ?? 0) > 0 || context.syncState.conflictIds.length > 0;
2575
2767
  if (status === "idle" && !hasConflicts) return null;
2576
- const message = children ?? (status === "error" ? getErrorMessage2(context.syncState.error) : status === "conflict" || hasConflicts ? conflictLabel : status === "pending" || status === "syncing" ? pendingLabel : syncedLabel);
2768
+ const message = children ?? (status === "error" ? getErrorMessage4(context.syncState.error) : status === "conflict" || hasConflicts ? conflictLabel : status === "pending" || status === "syncing" ? pendingLabel : syncedLabel);
2577
2769
  const role = status === "error" || status === "conflict" || hasConflicts ? "alert" : "status";
2578
2770
  const retry = async () => {
2579
2771
  if (onRetry) {
@@ -2582,7 +2774,7 @@ function KeepSyncStatusBanner({
2582
2774
  }
2583
2775
  await context.flushSync();
2584
2776
  };
2585
- return /* @__PURE__ */ jsxs10(
2777
+ return /* @__PURE__ */ jsxs11(
2586
2778
  "aside",
2587
2779
  {
2588
2780
  ...props,
@@ -2599,14 +2791,14 @@ function KeepSyncStatusBanner({
2599
2791
  }
2600
2792
  );
2601
2793
  }
2602
- function getErrorMessage2(error) {
2794
+ function getErrorMessage4(error) {
2603
2795
  return error instanceof Error ? error.message : "Sync failed.";
2604
2796
  }
2605
2797
 
2606
2798
  // src/KeepTagEditor.tsx
2607
2799
  import { useKeepItem as useKeepItem4 } from "@keepkit/core/react";
2608
2800
  import { useCallback as useCallback3, useEffect as useEffect5, useState as useState9 } from "react";
2609
- import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
2801
+ import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
2610
2802
  function KeepTagEditor({
2611
2803
  item,
2612
2804
  availableTags = [],
@@ -2618,6 +2810,7 @@ function KeepTagEditor({
2618
2810
  const tagsLabel = useUiLabel("tagsToApply");
2619
2811
  const removeLabel = useUiLabel("remove");
2620
2812
  const applyTagsLabel = useUiLabel("applyTags");
2813
+ const errorLabel = useUiLabel("error");
2621
2814
  const itemState = useKeepItem4(item);
2622
2815
  const [tags, setTags] = useState9(item.tags ?? []);
2623
2816
  const [input, setInput] = useState9("");
@@ -2637,8 +2830,8 @@ function KeepTagEditor({
2637
2830
  setTags(normalizeUiTags([...tags, tag]));
2638
2831
  setInput("");
2639
2832
  };
2640
- const body = render ? render({ tags, setTags, save, isSaving: itemState.isMutating }) : /* @__PURE__ */ jsxs11(Fragment6, { children: [
2641
- /* @__PURE__ */ jsxs11("label", { children: [
2833
+ const body = render ? render({ tags, setTags, save, isSaving: itemState.isMutating }) : /* @__PURE__ */ jsxs12(Fragment6, { children: [
2834
+ /* @__PURE__ */ jsxs12("label", { children: [
2642
2835
  tagsLabel,
2643
2836
  /* @__PURE__ */ jsx18(
2644
2837
  "input",
@@ -2660,13 +2853,13 @@ function KeepTagEditor({
2660
2853
  )
2661
2854
  ] }),
2662
2855
  availableTags.length > 0 ? /* @__PURE__ */ jsx18("datalist", { id: `keep-tags-${item.id}`, children: availableTags.map((tag) => /* @__PURE__ */ jsx18("option", { value: tag }, tag)) }) : null,
2663
- /* @__PURE__ */ jsx18("ul", { "aria-label": tagsLabel, children: tags.map((tag) => /* @__PURE__ */ jsxs11("li", { children: [
2856
+ /* @__PURE__ */ jsx18("ul", { "aria-label": tagsLabel, children: tags.map((tag) => /* @__PURE__ */ jsxs12("li", { children: [
2664
2857
  tag,
2665
2858
  /* @__PURE__ */ jsx18("button", { type: "button", onClick: () => setTags(tags.filter((current) => current !== tag)), children: removeLabel })
2666
2859
  ] }, tag)) }),
2667
2860
  /* @__PURE__ */ jsx18("button", { type: "submit", disabled: itemState.isMutating, "aria-busy": itemState.isMutating, children: applyTagsLabel })
2668
2861
  ] });
2669
- return /* @__PURE__ */ jsx18(
2862
+ return /* @__PURE__ */ jsxs12(
2670
2863
  "form",
2671
2864
  {
2672
2865
  ...props,
@@ -2675,23 +2868,30 @@ function KeepTagEditor({
2675
2868
  void save().catch(() => void 0);
2676
2869
  },
2677
2870
  "aria-busy": itemState.isMutating || props["aria-busy"],
2678
- "data-state": itemState.isMutating ? "saving" : "idle",
2871
+ "data-keepkit": "tag-editor",
2872
+ "data-state": itemState.error ? "error" : itemState.isMutating ? "saving" : "idle",
2679
2873
  "data-loading": itemState.isMutating ? "true" : void 0,
2680
2874
  "data-disabled": itemState.isMutating ? "true" : void 0,
2681
- children: body
2875
+ children: [
2876
+ body,
2877
+ itemState.error ? /* @__PURE__ */ jsx18("p", { role: "alert", children: getErrorMessage5(itemState.error, errorLabel) }) : null
2878
+ ]
2682
2879
  }
2683
2880
  );
2684
2881
  }
2882
+ function getErrorMessage5(error, fallback) {
2883
+ return error instanceof Error ? error.message : fallback;
2884
+ }
2685
2885
 
2686
2886
  // src/KeepUndo.tsx
2687
2887
  import { useKeepContext as useKeepContext5 } from "@keepkit/core/react";
2688
- import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
2888
+ import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
2689
2889
  function KeepUndo({ children, label, ...props }) {
2690
2890
  const context = useKeepContext5();
2691
2891
  const message = useUiLabel("undoAvailable");
2692
2892
  const undoLabel = useUiLabel("undo");
2693
2893
  if (!context.undo.canUndo) return null;
2694
- return /* @__PURE__ */ jsxs12("div", { ...props, role: "status", "aria-live": "polite", "data-state": "available", children: [
2894
+ return /* @__PURE__ */ jsxs13("div", { ...props, role: "status", "aria-live": "polite", "data-keepkit": "undo", "data-state": "available", children: [
2695
2895
  children ?? message,
2696
2896
  /* @__PURE__ */ jsx19("button", { type: "button", onClick: () => void context.undoLastRemoval(), children: label ?? undoLabel })
2697
2897
  ] });
@@ -2700,7 +2900,7 @@ function KeepUndo({ children, label, ...props }) {
2700
2900
  // src/status.tsx
2701
2901
  import { useKeepContext as useKeepContext6 } from "@keepkit/core/react";
2702
2902
  import { isValidElement as isValidElement6, useEffect as useEffect6, useRef as useRef4, useState as useState10 } from "react";
2703
- import { Fragment as Fragment7, jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
2903
+ import { Fragment as Fragment7, jsx as jsx20, jsxs as jsxs14 } from "react/jsx-runtime";
2704
2904
  function KeepEmptyState({
2705
2905
  title,
2706
2906
  description,
@@ -2712,12 +2912,18 @@ function KeepEmptyState({
2712
2912
  }) {
2713
2913
  const defaultTitle = useUiLabel("noItems").replace(/\.$/, "");
2714
2914
  const contentChildren = asChild && isValidElement6(children) ? void 0 : children;
2715
- const body = contentChildren ?? /* @__PURE__ */ jsxs13(Fragment7, { children: [
2915
+ const body = contentChildren ?? /* @__PURE__ */ jsxs14(Fragment7, { children: [
2716
2916
  /* @__PURE__ */ jsx20("h2", { children: title ?? defaultTitle }),
2717
2917
  description ? /* @__PURE__ */ jsx20("p", { children: description }) : null,
2718
2918
  action
2719
2919
  ] });
2720
- return renderRoot(asChild, children, { ...rootProps, className, "data-state": "empty" }, body, "KeepEmptyState");
2920
+ return renderRoot(
2921
+ asChild,
2922
+ children,
2923
+ { ...rootProps, className, "data-keepkit": "empty-state", "data-state": "empty" },
2924
+ body,
2925
+ "KeepEmptyState"
2926
+ );
2721
2927
  }
2722
2928
  function KeepStatus({
2723
2929
  status,
@@ -2746,6 +2952,7 @@ function KeepStatus({
2746
2952
  {
2747
2953
  ...rootProps,
2748
2954
  className,
2955
+ "data-keepkit": "status",
2749
2956
  role,
2750
2957
  "aria-live": rootProps["aria-live"] ?? "polite",
2751
2958
  "data-state": resolvedStatus,
@@ -2777,6 +2984,7 @@ function KeepAnnouncements({ messages, ...props }) {
2777
2984
  role: props.role ?? "status",
2778
2985
  "aria-live": props["aria-live"] ?? "polite",
2779
2986
  "aria-atomic": "true",
2987
+ "data-keepkit": "announcements",
2780
2988
  "data-state": "announcing",
2781
2989
  children: message
2782
2990
  }
@@ -2800,6 +3008,58 @@ function getStatusLabelKey3(status) {
2800
3008
  return "saved";
2801
3009
  }
2802
3010
 
3011
+ // src/theme.tsx
3012
+ import { cloneElement as cloneElement2, isValidElement as isValidElement7 } from "react";
3013
+ import { jsx as jsx21 } from "react/jsx-runtime";
3014
+ function KeepThemeProvider({
3015
+ children,
3016
+ theme = "default",
3017
+ mode = "system",
3018
+ density = "comfortable",
3019
+ radius = "medium",
3020
+ accentColor,
3021
+ highContrast = false,
3022
+ reducedMotion = false,
3023
+ variables,
3024
+ style,
3025
+ className,
3026
+ asChild = false,
3027
+ ...props
3028
+ }) {
3029
+ const themeClassName = [
3030
+ "keep-theme",
3031
+ `keep-theme--${theme}`,
3032
+ `keep-theme--${mode}`,
3033
+ `keep-theme--density-${density}`,
3034
+ `keep-theme--radius-${radius}`,
3035
+ className
3036
+ ].filter(Boolean).join(" ");
3037
+ const themeStyle = {
3038
+ ...style,
3039
+ ...accentColor && !highContrast ? { "--keep-accent": accentColor, "--keep-primary": accentColor } : {},
3040
+ ...variables
3041
+ };
3042
+ const rootProps = {
3043
+ ...props,
3044
+ className: themeClassName,
3045
+ style: themeStyle,
3046
+ "data-keep-theme": theme,
3047
+ "data-theme": theme,
3048
+ "data-mode": mode,
3049
+ "data-density": density,
3050
+ "data-radius": radius,
3051
+ "data-accent-color": accentColor,
3052
+ "data-high-contrast": highContrast ? "true" : void 0,
3053
+ "data-reduced-motion": reducedMotion ? "true" : void 0
3054
+ };
3055
+ if (asChild) {
3056
+ if (!isValidElement7(children))
3057
+ throw new Error("KeepThemeProvider with asChild requires a single React element child.");
3058
+ return cloneElement2(children, rootProps);
3059
+ }
3060
+ return /* @__PURE__ */ jsx21("div", { ...rootProps, children });
3061
+ }
3062
+
2803
3063
  // src/index.tsx
2804
3064
  import { createAuthenticatedSyncKit } from "@keepkit/core/core";
2805
3065
  import {
@@ -2820,36 +3080,90 @@ import {
2820
3080
  LocalStorageSyncQueueAdapter,
2821
3081
  SyncStorageAdapter
2822
3082
  } from "@keepkit/core/storage";
2823
- import { jsx as jsx21, jsxs as jsxs14 } from "react/jsx-runtime";
3083
+ import { jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
2824
3084
  function KeepKitProvider({
2825
3085
  labels,
2826
3086
  locale,
2827
3087
  labelResolver,
3088
+ theme,
3089
+ mode,
3090
+ density,
3091
+ radius,
3092
+ accentColor,
3093
+ highContrast,
3094
+ reducedMotion,
3095
+ variables,
3096
+ className: themeClassName,
3097
+ style: themeStyle,
3098
+ asChild: themeAsChild,
2828
3099
  children,
2829
3100
  ...providerProps
2830
3101
  }) {
2831
- return /* @__PURE__ */ jsx21(KeepUiProvider, { labels, locale, labelResolver, children: /* @__PURE__ */ jsxs14(CoreKeepProvider, { ...providerProps, children: [
2832
- children,
2833
- /* @__PURE__ */ jsx21(KeepAnnouncements, {})
2834
- ] }) });
3102
+ return /* @__PURE__ */ jsx22(KeepUiProvider, { labels, locale, labelResolver, children: /* @__PURE__ */ jsx22(
3103
+ KeepThemeProvider,
3104
+ {
3105
+ theme,
3106
+ mode,
3107
+ density,
3108
+ radius,
3109
+ accentColor,
3110
+ highContrast,
3111
+ reducedMotion,
3112
+ variables,
3113
+ className: themeClassName,
3114
+ style: themeStyle,
3115
+ asChild: themeAsChild,
3116
+ children: /* @__PURE__ */ jsxs15(CoreKeepProvider, { ...providerProps, children: [
3117
+ children,
3118
+ /* @__PURE__ */ jsx22(KeepAnnouncements, {})
3119
+ ] })
3120
+ }
3121
+ ) });
2835
3122
  }
2836
3123
  function createKeepKit(options = {}) {
2837
- const { labels, locale, labelResolver, getTitle, getImageProps, ...coreOptions } = options;
3124
+ const {
3125
+ labels,
3126
+ locale,
3127
+ labelResolver,
3128
+ theme,
3129
+ mode,
3130
+ density,
3131
+ radius,
3132
+ accentColor,
3133
+ highContrast,
3134
+ reducedMotion,
3135
+ variables,
3136
+ themeClassName,
3137
+ themeStyle,
3138
+ getTitle,
3139
+ getImageProps,
3140
+ ...coreOptions
3141
+ } = options;
2838
3142
  const coreKit = createCoreKeepKit(coreOptions);
2839
3143
  return {
2840
- Provider: (props) => /* @__PURE__ */ jsx21(
3144
+ Provider: (props) => /* @__PURE__ */ jsx22(
2841
3145
  KeepKitProvider,
2842
3146
  {
2843
3147
  ...coreOptions,
2844
3148
  labels,
2845
3149
  locale,
2846
3150
  labelResolver,
3151
+ theme,
3152
+ mode,
3153
+ density,
3154
+ radius,
3155
+ accentColor,
3156
+ highContrast,
3157
+ reducedMotion,
3158
+ variables,
3159
+ className: themeClassName,
3160
+ style: themeStyle,
2847
3161
  ...props
2848
3162
  }
2849
3163
  ),
2850
- Button: (props) => /* @__PURE__ */ jsx21(KeepButton, { ...props }),
2851
- Backup: (props) => /* @__PURE__ */ jsx21(KeepBackup, { ...props }),
2852
- Collection: (props) => /* @__PURE__ */ jsx21(
3164
+ Button: (props) => /* @__PURE__ */ jsx22(KeepButton, { ...props }),
3165
+ Backup: (props) => /* @__PURE__ */ jsx22(KeepBackup, { ...props }),
3166
+ Collection: (props) => /* @__PURE__ */ jsx22(
2853
3167
  KeepCollection,
2854
3168
  {
2855
3169
  ...props,
@@ -2896,6 +3210,7 @@ export {
2896
3210
  KeepSyncStatusBanner,
2897
3211
  KeepTagEditor,
2898
3212
  KeepTagFilter,
3213
+ KeepThemeProvider,
2899
3214
  KeepUiProvider,
2900
3215
  KeepUndo,
2901
3216
  LocalStorageAdapter,