@keepkit/ui 0.12.0 → 0.13.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
@@ -1312,37 +1312,46 @@ function KeepBackup({
1312
1312
  setError(cause);
1313
1313
  }
1314
1314
  }
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
- ] });
1315
+ return /* @__PURE__ */ jsxs(
1316
+ "section",
1317
+ {
1318
+ ...props,
1319
+ "data-keepkit": "backup",
1320
+ "data-state": error ? "error" : result ? "complete" : "idle",
1321
+ "data-loading": context.isMutating ? "true" : void 0,
1322
+ children: [
1323
+ /* @__PURE__ */ jsx2("button", { type: "button", onClick: () => void handleExport(), disabled: context.isMutating, children: exportLabel }),
1324
+ /* @__PURE__ */ jsxs("label", { children: [
1325
+ importModeLabel,
1326
+ /* @__PURE__ */ jsxs("select", { value: mode, onChange: (event) => setMode(event.currentTarget.value), children: [
1327
+ /* @__PURE__ */ jsx2("option", { value: "merge", children: mergeLabel }),
1328
+ /* @__PURE__ */ jsx2("option", { value: "replace", children: replaceLabel })
1329
+ ] })
1330
+ ] }),
1331
+ /* @__PURE__ */ jsx2("button", { type: "button", onClick: () => inputRef.current?.click(), disabled: context.isMutating, children: importLabel }),
1332
+ /* @__PURE__ */ jsx2(
1333
+ "input",
1334
+ {
1335
+ ref: inputRef,
1336
+ type: "file",
1337
+ accept: "application/json,.json",
1338
+ "aria-label": importLabel,
1339
+ onChange: (event) => void handleImport(event)
1340
+ }
1341
+ ),
1342
+ result ? /* @__PURE__ */ jsxs("p", { role: "status", children: [
1343
+ result.imported,
1344
+ " ",
1345
+ importedCountLabel,
1346
+ "; ",
1347
+ result.failed,
1348
+ " ",
1349
+ failedCountLabel
1350
+ ] }) : null,
1351
+ error ? /* @__PURE__ */ jsx2("p", { role: "alert", children: isQuotaError(error) ? quotaErrorLabel : getErrorMessage(error) }) : null
1352
+ ]
1353
+ }
1354
+ );
1346
1355
  }
1347
1356
  function isQuotaError(error) {
1348
1357
  if (error instanceof Error && error.name === "KeepStorageQuotaError") return true;
@@ -1375,6 +1384,7 @@ function KeepItemCheckbox({
1375
1384
  ...props,
1376
1385
  type: "checkbox",
1377
1386
  checked,
1387
+ "data-keepkit": "item-checkbox",
1378
1388
  "data-state": checked ? "checked" : "unchecked",
1379
1389
  "data-disabled": props.disabled ? "true" : void 0,
1380
1390
  "aria-label": accessibleLabel,
@@ -1568,6 +1578,7 @@ function KeepBulkActions({
1568
1578
  "section",
1569
1579
  {
1570
1580
  ...props,
1581
+ "data-keepkit": "bulk-actions",
1571
1582
  "aria-busy": list.isMutating || props["aria-busy"],
1572
1583
  "data-state": selectedIds.length > 0 ? "selected" : "idle",
1573
1584
  "data-loading": list.isLoading || list.isMutating ? "true" : void 0,
@@ -1581,23 +1592,37 @@ import {
1581
1592
  KeepButton as CoreKeepButton,
1582
1593
  useKeepItem
1583
1594
  } from "@keepkit/core/react";
1584
- import { Fragment as Fragment2, jsx as jsx6 } from "react/jsx-runtime";
1585
- function KeepButton({ labels, ...props }) {
1595
+ import { createElement } from "react";
1596
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
1597
+ function KeepButton({
1598
+ labels,
1599
+ icons,
1600
+ iconOnly = false,
1601
+ showLabel = true,
1602
+ iconClassName,
1603
+ ...props
1604
+ }) {
1586
1605
  const saveLabel = useUiLabel("save", typeof labels?.unsaved === "string" ? labels.unsaved : void 0);
1587
1606
  const savedLabel = useUiLabel("saved", typeof labels?.saved === "string" ? labels.saved : void 0);
1588
1607
  const loadingLabel = useUiLabel("loading", typeof labels?.loading === "string" ? labels.loading : void 0);
1589
1608
  const errorLabel = useUiLabel("error", typeof labels?.error === "string" ? labels.error : void 0);
1590
1609
  const buttonState = useKeepItem(props.item);
1591
- const customStateLabel = labels?.loading !== void 0 || labels?.error !== void 0;
1610
+ const customStateLabel = labels?.loading !== void 0 || labels?.error !== void 0 || icons !== void 0 && props.children === void 0;
1592
1611
  const getStateContent = (state) => {
1593
- if (state.error) return labels?.error ?? errorLabel;
1594
- if (state.isMutating) return labels?.loading ?? loadingLabel;
1595
1612
  if (typeof props.children === "function") return props.children(state);
1596
1613
  if (props.children !== void 0) return props.children;
1597
- return state.isSaved ? labels?.saved ?? savedLabel : labels?.unsaved ?? saveLabel;
1614
+ const label = state.error ? labels?.error ?? errorLabel : state.isMutating ? labels?.loading ?? loadingLabel : state.isSaved ? labels?.saved ?? savedLabel : labels?.unsaved ?? saveLabel;
1615
+ if (!icons) return label;
1616
+ const icon = state.error ? icons.error : state.isMutating ? icons.loading : state.isSaved ? icons.remove ?? icons.saved : icons.save;
1617
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
1618
+ renderIcon(icon, iconClassName),
1619
+ showLabel && !iconOnly ? label : null
1620
+ ] });
1598
1621
  };
1599
1622
  const sharedProps = {
1600
1623
  ...props,
1624
+ "data-keepkit": "button",
1625
+ "data-icon-only": iconOnly ? "true" : void 0,
1601
1626
  "aria-busy": props["aria-busy"] ?? (buttonState.isLoading || buttonState.isMutating),
1602
1627
  savedLabel: labels?.saved ?? props.savedLabel ?? savedLabel,
1603
1628
  unsavedLabel: labels?.unsaved ?? props.unsavedLabel ?? saveLabel,
@@ -1607,6 +1632,10 @@ function KeepButton({ labels, ...props }) {
1607
1632
  if (!customStateLabel) return /* @__PURE__ */ jsx6(CoreKeepButton, { ...sharedProps });
1608
1633
  return /* @__PURE__ */ jsx6(CoreKeepButton, { ...sharedProps, children: (state) => /* @__PURE__ */ jsx6(Fragment2, { children: getStateContent(state) }) });
1609
1634
  }
1635
+ function renderIcon(icon, className) {
1636
+ if (typeof icon === "function") return createElement(icon, { "aria-hidden": true, className });
1637
+ return icon ?? null;
1638
+ }
1610
1639
 
1611
1640
  // src/KeepCollection.tsx
1612
1641
  import { KeepErrorBoundary as KeepErrorBoundary2, useKeepList as useKeepList4 } from "@keepkit/core/react";
@@ -1653,7 +1682,7 @@ function getStatusLabelKey(status) {
1653
1682
  }
1654
1683
 
1655
1684
  // src/KeepStaleNotice.tsx
1656
- import { jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
1685
+ import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
1657
1686
  function KeepStaleNotice({
1658
1687
  item,
1659
1688
  onRetry,
@@ -1691,10 +1720,10 @@ function KeepStaleNotice({
1691
1720
  setError(cause);
1692
1721
  }
1693
1722
  }
1694
- return /* @__PURE__ */ jsxs3("aside", { ...props, className, "data-keepkit": "stale-notice", "data-state": error ? "error" : "stale", children: [
1723
+ return /* @__PURE__ */ jsxs4("aside", { ...props, className, "data-keepkit": "stale-notice", "data-state": error ? "error" : "stale", children: [
1695
1724
  /* @__PURE__ */ jsx8(KeepItemStatusBadge, { status }),
1696
1725
  children ?? (item.statusReason ? /* @__PURE__ */ jsx8("p", { children: item.statusReason }) : null),
1697
- /* @__PURE__ */ jsxs3("div", { children: [
1726
+ /* @__PURE__ */ jsxs4("div", { children: [
1698
1727
  /* @__PURE__ */ jsx8("button", { type: "button", onClick: () => void handleRetry(), disabled: isRetrying || context.isMutating, children: retryLabel ?? retryText }),
1699
1728
  /* @__PURE__ */ jsx8("button", { type: "button", onClick: () => void handleRemove(), disabled: context.isMutating, children: removeLabel ?? removeText })
1700
1729
  ] }),
@@ -1733,7 +1762,7 @@ function KeepPruneStaleButton({
1733
1762
  }
1734
1763
 
1735
1764
  // src/KeepItemCard.tsx
1736
- import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
1765
+ import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
1737
1766
  function KeepItemCard({
1738
1767
  item,
1739
1768
  title,
@@ -1743,6 +1772,7 @@ function KeepItemCard({
1743
1772
  renderImage,
1744
1773
  renderTags,
1745
1774
  showTags = true,
1775
+ showSavedAt = true,
1746
1776
  imageAlt,
1747
1777
  render,
1748
1778
  children,
@@ -1763,6 +1793,8 @@ function KeepItemCard({
1763
1793
  ...rootProps
1764
1794
  }) {
1765
1795
  const saveActionLabel = useUiLabel("save");
1796
+ const savedAtLabel = useUiLabel("saved");
1797
+ const errorLabel = useUiLabel("error");
1766
1798
  const removeActionLabel = useUiLabel("remove");
1767
1799
  const itemState = useKeepItem2(item);
1768
1800
  const contentChildren = asChild && isValidElement2(children) ? void 0 : children;
@@ -1801,10 +1833,19 @@ function KeepItemCard({
1801
1833
  onRemoveError?.(error);
1802
1834
  }
1803
1835
  }
1804
- const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs4(Fragment3, { children: [
1836
+ const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs5(Fragment3, { children: [
1805
1837
  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
1838
  /* @__PURE__ */ jsx9("h3", { children: linkTarget === "title" ? renderLink(resolvedTitle) : resolvedTitle }),
1839
+ showSavedAt ? /* @__PURE__ */ jsxs5("div", { "data-card-meta": true, children: [
1840
+ /* @__PURE__ */ jsxs5("span", { children: [
1841
+ savedAtLabel,
1842
+ ":"
1843
+ ] }),
1844
+ " ",
1845
+ /* @__PURE__ */ jsx9("time", { dateTime: new Date(item.savedAt).toISOString(), children: formatSavedAt(item.savedAt) })
1846
+ ] }) : null,
1807
1847
  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,
1848
+ itemState.error ? /* @__PURE__ */ jsx9("p", { role: "alert", children: getErrorMessage2(itemState.error, errorLabel) }) : null,
1808
1849
  statusLabel ? /* @__PURE__ */ jsx9(KeepStaleNotice, { item, onRetry, onRemoved }) : null,
1809
1850
  showSaveButton && !statusLabel ? /* @__PURE__ */ jsx9(
1810
1851
  KeepButton,
@@ -1823,8 +1864,9 @@ function KeepItemCard({
1823
1864
  {
1824
1865
  ...rootProps,
1825
1866
  className,
1867
+ "data-keepkit": "card",
1826
1868
  "aria-busy": itemState.isMutating || rootProps["aria-busy"],
1827
- "data-state": itemState.isSaved ? "saved" : "unsaved",
1869
+ "data-state": itemState.error ? "error" : itemState.isSaved ? "saved" : "unsaved",
1828
1870
  "data-status": item.status ?? "available",
1829
1871
  "data-loading": itemState.isMutating ? "true" : void 0
1830
1872
  },
@@ -1846,6 +1888,12 @@ function getStatusLabelKey2(status) {
1846
1888
  return "statusUnknown";
1847
1889
  }
1848
1890
  }
1891
+ function formatSavedAt(timestamp) {
1892
+ return new Date(timestamp).toISOString().slice(0, 10);
1893
+ }
1894
+ function getErrorMessage2(error, fallback) {
1895
+ return error instanceof Error ? error.message : fallback;
1896
+ }
1849
1897
 
1850
1898
  // src/KeepList.tsx
1851
1899
  import { jsx as jsx10 } from "react/jsx-runtime";
@@ -1887,6 +1935,7 @@ function KeepListContent({
1887
1935
  {
1888
1936
  ...rootProps,
1889
1937
  className,
1938
+ "data-keepkit": "list",
1890
1939
  "aria-busy": state.isLoading || rootProps["aria-busy"],
1891
1940
  "data-state": getListState(state),
1892
1941
  "data-loading": state.isLoading ? "true" : void 0
@@ -1922,7 +1971,7 @@ function getListBody(state, options) {
1922
1971
  // src/KeepTagFilter.tsx
1923
1972
  import { useKeepList as useKeepList3 } from "@keepkit/core/react";
1924
1973
  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";
1974
+ import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
1926
1975
  function KeepTagFilter({
1927
1976
  query,
1928
1977
  value: controlledValue,
@@ -1959,12 +2008,12 @@ function KeepTagFilter({
1959
2008
  [list.tagCounts, list.tags, resolvedValue, select]
1960
2009
  );
1961
2010
  const contentChildren = asChild && isValidElement4(children) ? void 0 : children;
1962
- const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs5("fieldset", { children: [
2011
+ const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs6("fieldset", { children: [
1963
2012
  /* @__PURE__ */ jsx11("legend", { children: ariaLabel ?? uiAriaLabel }),
1964
2013
  /* @__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: [
2014
+ list.tags.map((tag) => /* @__PURE__ */ jsxs6("button", { type: "button", "aria-pressed": resolvedValue === tag, onClick: () => select(tag), children: [
1966
2015
  renderTag ? renderTag(tag, list.tagCounts[tag] ?? 0, resolvedValue === tag) : tag,
1967
- /* @__PURE__ */ jsxs5("span", { children: [
2016
+ /* @__PURE__ */ jsxs6("span", { children: [
1968
2017
  " (",
1969
2018
  list.tagCounts[tag] ?? 0,
1970
2019
  ")"
@@ -1977,6 +2026,7 @@ function KeepTagFilter({
1977
2026
  {
1978
2027
  ...rootProps,
1979
2028
  className,
2029
+ "data-keepkit": "tag-filter",
1980
2030
  "data-state": resolvedValue === void 0 ? "all" : "filtered",
1981
2031
  "data-loading": list.isLoading ? "true" : void 0
1982
2032
  },
@@ -1987,7 +2037,7 @@ function KeepTagFilter({
1987
2037
 
1988
2038
  // src/query-controls.tsx
1989
2039
  import { useEffect, useState as useState5 } from "react";
1990
- import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs6 } from "react/jsx-runtime";
2040
+ import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
1991
2041
  function KeepSearchInput({
1992
2042
  value: controlledValue,
1993
2043
  defaultValue = "",
@@ -2013,6 +2063,7 @@ function KeepSearchInput({
2013
2063
  "input",
2014
2064
  {
2015
2065
  ...props,
2066
+ "data-keepkit": "search-input",
2016
2067
  type: "search",
2017
2068
  value,
2018
2069
  "data-state": value ? "active" : "idle",
@@ -2041,7 +2092,7 @@ function KeepSortSelect({
2041
2092
  const savedOldestLabel = useUiLabel("savedOldest");
2042
2093
  const [uncontrolledValue, setUncontrolledValue] = useState5(defaultValue);
2043
2094
  const value = controlledValue ?? uncontrolledValue;
2044
- const options = children ?? /* @__PURE__ */ jsxs6(Fragment4, { children: [
2095
+ const options = children ?? /* @__PURE__ */ jsxs7(Fragment4, { children: [
2045
2096
  /* @__PURE__ */ jsx12("option", { value: "updatedAt:desc", children: updatedNewestLabel }),
2046
2097
  /* @__PURE__ */ jsx12("option", { value: "updatedAt:asc", children: updatedOldestLabel }),
2047
2098
  /* @__PURE__ */ jsx12("option", { value: "savedAt:desc", children: savedNewestLabel }),
@@ -2051,6 +2102,7 @@ function KeepSortSelect({
2051
2102
  "select",
2052
2103
  {
2053
2104
  ...props,
2105
+ "data-keepkit": "sort-select",
2054
2106
  value,
2055
2107
  "data-state": "selected",
2056
2108
  "data-disabled": props.disabled ? "true" : void 0,
@@ -2086,12 +2138,13 @@ function KeepPagination({
2086
2138
  };
2087
2139
  const navProps = {
2088
2140
  ...props,
2141
+ "data-keepkit": "pagination",
2089
2142
  "aria-label": props["aria-label"] ?? paginationLabel,
2090
2143
  "data-state": pageCount > 1 ? "active" : "idle"
2091
2144
  };
2092
2145
  if (render) return /* @__PURE__ */ jsx12("nav", { ...navProps, children: render({ page: currentPage, pageCount, goToPage }) });
2093
2146
  const visiblePages = getVisiblePages(currentPage, pageCount, Math.max(1, maxPageButtons));
2094
- return /* @__PURE__ */ jsxs6("nav", { ...navProps, children: [
2147
+ return /* @__PURE__ */ jsxs7("nav", { ...navProps, children: [
2095
2148
  /* @__PURE__ */ jsx12("button", { type: "button", onClick: () => goToPage(currentPage - 1), disabled: currentPage <= 1, children: previousPageLabel }),
2096
2149
  visiblePages.map((nextPage) => /* @__PURE__ */ jsx12(
2097
2150
  "button",
@@ -2197,7 +2250,7 @@ function getBrowserAdapter() {
2197
2250
  }
2198
2251
 
2199
2252
  // src/KeepCollection.tsx
2200
- import { jsx as jsx13, jsxs as jsxs7 } from "react/jsx-runtime";
2253
+ import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
2201
2254
  function KeepCollection(props) {
2202
2255
  const { fallback, onBoundaryError, boundaryResetKey, ...collectionProps } = props;
2203
2256
  const content = /* @__PURE__ */ jsx13(KeepCollectionContent, { ...collectionProps });
@@ -2256,16 +2309,17 @@ function KeepCollectionContent({
2256
2309
  adapter: urlAdapter
2257
2310
  });
2258
2311
  const list = useKeepList4(resolvedQuery);
2259
- return /* @__PURE__ */ jsxs7(
2312
+ return /* @__PURE__ */ jsxs8(
2260
2313
  "section",
2261
2314
  {
2262
2315
  ...rootProps,
2263
2316
  className,
2317
+ "data-keepkit": "collection",
2264
2318
  "aria-busy": list.isLoading || list.isMutating || rootProps["aria-busy"],
2265
2319
  "data-state": getCollectionState(list),
2266
2320
  "data-loading": list.isLoading || list.isMutating ? "true" : void 0,
2267
2321
  children: [
2268
- /* @__PURE__ */ jsxs7("div", { children: [
2322
+ /* @__PURE__ */ jsxs8("div", { children: [
2269
2323
  enabled.search ? /* @__PURE__ */ jsx13(
2270
2324
  KeepSearchInput,
2271
2325
  {
@@ -2335,7 +2389,7 @@ function getCollectionState(list) {
2335
2389
  import { jsx as jsx14 } from "react/jsx-runtime";
2336
2390
  function KeepLayout({ layout = "list", children, style, ...props }) {
2337
2391
  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 });
2392
+ return /* @__PURE__ */ jsx14("div", { ...props, style: { ...layoutStyle, ...style }, "data-keepkit": "layout", "data-layout": layout, children });
2339
2393
  }
2340
2394
 
2341
2395
  // src/KeepNoteEditor.tsx
@@ -2347,7 +2401,7 @@ import {
2347
2401
  useRef as useRef3,
2348
2402
  useState as useState7
2349
2403
  } from "react";
2350
- import { Fragment as Fragment5, jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
2404
+ import { Fragment as Fragment5, jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
2351
2405
  function KeepNoteEditor({
2352
2406
  item,
2353
2407
  label,
@@ -2364,6 +2418,7 @@ function KeepNoteEditor({
2364
2418
  }) {
2365
2419
  const defaultLabel = useUiLabel("note");
2366
2420
  const defaultSaveLabel = useUiLabel("saveNote");
2421
+ const errorLabel = useUiLabel("error");
2367
2422
  const itemState = useKeepItem3(item);
2368
2423
  const { error, isMutating, item: savedItem, updateNote } = itemState;
2369
2424
  const contentChildren = asChild && isValidElement5(children) ? void 0 : children;
@@ -2397,8 +2452,8 @@ function KeepNoteEditor({
2397
2452
  error,
2398
2453
  save
2399
2454
  };
2400
- const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs8(Fragment5, { children: [
2401
- /* @__PURE__ */ jsxs8("label", { children: [
2455
+ const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs9(Fragment5, { children: [
2456
+ /* @__PURE__ */ jsxs9("label", { children: [
2402
2457
  label ?? defaultLabel,
2403
2458
  /* @__PURE__ */ jsx15(
2404
2459
  "textarea",
@@ -2423,17 +2478,21 @@ function KeepNoteEditor({
2423
2478
  void save().catch(() => void 0);
2424
2479
  };
2425
2480
  if (!asChild) {
2426
- return /* @__PURE__ */ jsx15(
2481
+ return /* @__PURE__ */ jsxs9(
2427
2482
  "form",
2428
2483
  {
2429
2484
  ...formProps,
2430
2485
  className,
2486
+ "data-keepkit": "note-editor",
2431
2487
  onSubmit: handleSubmit,
2432
2488
  "aria-busy": isMutating || formProps["aria-busy"],
2433
- "data-state": isDirty ? "dirty" : "clean",
2489
+ "data-state": error ? "error" : isDirty ? "dirty" : "clean",
2434
2490
  "data-loading": isMutating ? "true" : void 0,
2435
2491
  "data-disabled": isMutating ? "true" : void 0,
2436
- children: body
2492
+ children: [
2493
+ body,
2494
+ error ? /* @__PURE__ */ jsx15("p", { role: "alert", children: getErrorMessage3(error, errorLabel) }) : null
2495
+ ]
2437
2496
  }
2438
2497
  );
2439
2498
  }
@@ -2443,9 +2502,10 @@ function KeepNoteEditor({
2443
2502
  {
2444
2503
  ...formProps,
2445
2504
  className,
2505
+ "data-keepkit": "note-editor",
2446
2506
  onSubmit: handleSubmit,
2447
2507
  "aria-busy": isMutating || formProps["aria-busy"],
2448
- "data-state": isDirty ? "dirty" : "clean",
2508
+ "data-state": error ? "error" : isDirty ? "dirty" : "clean",
2449
2509
  "data-loading": isMutating ? "true" : void 0,
2450
2510
  "data-disabled": isMutating ? "true" : void 0
2451
2511
  },
@@ -2453,11 +2513,14 @@ function KeepNoteEditor({
2453
2513
  "KeepNoteEditor"
2454
2514
  );
2455
2515
  }
2516
+ function getErrorMessage3(error, fallback) {
2517
+ return error instanceof Error ? error.message : fallback;
2518
+ }
2456
2519
 
2457
2520
  // src/KeepSyncRecoveryDialog.tsx
2458
2521
  import { useKeepContext as useKeepContext3 } from "@keepkit/core/react";
2459
2522
  import { useEffect as useEffect4, useState as useState8 } from "react";
2460
- import { jsx as jsx16, jsxs as jsxs9 } from "react/jsx-runtime";
2523
+ import { jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
2461
2524
  function KeepSyncRecoveryDialog({
2462
2525
  open,
2463
2526
  onOpenChange,
@@ -2508,7 +2571,7 @@ function KeepSyncRecoveryDialog({
2508
2571
  setBusyId(void 0);
2509
2572
  }
2510
2573
  }
2511
- return /* @__PURE__ */ jsxs9(
2574
+ return /* @__PURE__ */ jsxs10(
2512
2575
  "section",
2513
2576
  {
2514
2577
  ...props,
@@ -2518,17 +2581,18 @@ function KeepSyncRecoveryDialog({
2518
2581
  "aria-labelledby": "keepkit-sync-recovery-title",
2519
2582
  "data-keepkit": "sync-recovery",
2520
2583
  "data-state": conflictList.length > 0 ? "conflict" : "error",
2584
+ "data-loading": busyId !== void 0 ? "true" : void 0,
2521
2585
  children: [
2522
- /* @__PURE__ */ jsxs9("header", { children: [
2586
+ /* @__PURE__ */ jsxs10("header", { children: [
2523
2587
  /* @__PURE__ */ jsx16("h2", { id: "keepkit-sync-recovery-title", children: dialogTitle }),
2524
2588
  /* @__PURE__ */ jsx16("button", { type: "button", onClick: close, "aria-label": closeLabel, children: closeLabel })
2525
2589
  ] }),
2526
2590
  children,
2527
- conflictList.length > 0 ? /* @__PURE__ */ jsxs9("div", { children: [
2591
+ conflictList.length > 0 ? /* @__PURE__ */ jsxs10("div", { children: [
2528
2592
  /* @__PURE__ */ jsx16("p", { children: conflictLabel }),
2529
- conflictList.map((conflict) => /* @__PURE__ */ jsxs9("article", { "data-conflict-id": conflict.id, children: [
2593
+ conflictList.map((conflict) => /* @__PURE__ */ jsxs10("article", { "data-conflict-id": conflict.id, children: [
2530
2594
  /* @__PURE__ */ jsx16("h3", { children: getMetaTitle(conflict.operation.item?.meta) ?? conflict.id }),
2531
- /* @__PURE__ */ jsxs9("div", { children: [
2595
+ /* @__PURE__ */ jsxs10("div", { children: [
2532
2596
  /* @__PURE__ */ jsx16("button", { type: "button", onClick: () => void resolve(conflict, "local"), disabled: busyId !== void 0, children: keepLocalLabel }),
2533
2597
  /* @__PURE__ */ jsx16("button", { type: "button", onClick: () => void resolve(conflict, "remote"), disabled: busyId !== void 0, children: useServerLabel }),
2534
2598
  /* @__PURE__ */ jsx16(
@@ -2543,7 +2607,7 @@ function KeepSyncRecoveryDialog({
2543
2607
  ] })
2544
2608
  ] }, conflict.id))
2545
2609
  ] }) : null,
2546
- context.syncState.status === "error" || context.error ? /* @__PURE__ */ jsxs9("section", { "data-recovery": "backup", children: [
2610
+ context.syncState.status === "error" || context.error ? /* @__PURE__ */ jsxs10("section", { "data-recovery": "backup", children: [
2547
2611
  /* @__PURE__ */ jsx16("h3", { children: backupRecoveryLabel }),
2548
2612
  /* @__PURE__ */ jsx16("p", { children: backupRecoveryDescription }),
2549
2613
  backup ?? (showBackupControls ? /* @__PURE__ */ jsx16(KeepBackup, {}) : null)
@@ -2556,7 +2620,7 @@ function KeepSyncRecoveryDialog({
2556
2620
 
2557
2621
  // src/KeepSyncStatusBanner.tsx
2558
2622
  import { useKeepContext as useKeepContext4 } from "@keepkit/core/react";
2559
- import { jsx as jsx17, jsxs as jsxs10 } from "react/jsx-runtime";
2623
+ import { jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
2560
2624
  function KeepSyncStatusBanner({
2561
2625
  onRetry,
2562
2626
  onResolveConflicts,
@@ -2573,7 +2637,7 @@ function KeepSyncStatusBanner({
2573
2637
  const status = context.syncState.status;
2574
2638
  const hasConflicts = (context.syncState.conflicts?.length ?? 0) > 0 || context.syncState.conflictIds.length > 0;
2575
2639
  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);
2640
+ const message = children ?? (status === "error" ? getErrorMessage4(context.syncState.error) : status === "conflict" || hasConflicts ? conflictLabel : status === "pending" || status === "syncing" ? pendingLabel : syncedLabel);
2577
2641
  const role = status === "error" || status === "conflict" || hasConflicts ? "alert" : "status";
2578
2642
  const retry = async () => {
2579
2643
  if (onRetry) {
@@ -2582,7 +2646,7 @@ function KeepSyncStatusBanner({
2582
2646
  }
2583
2647
  await context.flushSync();
2584
2648
  };
2585
- return /* @__PURE__ */ jsxs10(
2649
+ return /* @__PURE__ */ jsxs11(
2586
2650
  "aside",
2587
2651
  {
2588
2652
  ...props,
@@ -2599,14 +2663,14 @@ function KeepSyncStatusBanner({
2599
2663
  }
2600
2664
  );
2601
2665
  }
2602
- function getErrorMessage2(error) {
2666
+ function getErrorMessage4(error) {
2603
2667
  return error instanceof Error ? error.message : "Sync failed.";
2604
2668
  }
2605
2669
 
2606
2670
  // src/KeepTagEditor.tsx
2607
2671
  import { useKeepItem as useKeepItem4 } from "@keepkit/core/react";
2608
2672
  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";
2673
+ import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
2610
2674
  function KeepTagEditor({
2611
2675
  item,
2612
2676
  availableTags = [],
@@ -2618,6 +2682,7 @@ function KeepTagEditor({
2618
2682
  const tagsLabel = useUiLabel("tagsToApply");
2619
2683
  const removeLabel = useUiLabel("remove");
2620
2684
  const applyTagsLabel = useUiLabel("applyTags");
2685
+ const errorLabel = useUiLabel("error");
2621
2686
  const itemState = useKeepItem4(item);
2622
2687
  const [tags, setTags] = useState9(item.tags ?? []);
2623
2688
  const [input, setInput] = useState9("");
@@ -2637,8 +2702,8 @@ function KeepTagEditor({
2637
2702
  setTags(normalizeUiTags([...tags, tag]));
2638
2703
  setInput("");
2639
2704
  };
2640
- const body = render ? render({ tags, setTags, save, isSaving: itemState.isMutating }) : /* @__PURE__ */ jsxs11(Fragment6, { children: [
2641
- /* @__PURE__ */ jsxs11("label", { children: [
2705
+ const body = render ? render({ tags, setTags, save, isSaving: itemState.isMutating }) : /* @__PURE__ */ jsxs12(Fragment6, { children: [
2706
+ /* @__PURE__ */ jsxs12("label", { children: [
2642
2707
  tagsLabel,
2643
2708
  /* @__PURE__ */ jsx18(
2644
2709
  "input",
@@ -2660,13 +2725,13 @@ function KeepTagEditor({
2660
2725
  )
2661
2726
  ] }),
2662
2727
  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: [
2728
+ /* @__PURE__ */ jsx18("ul", { "aria-label": tagsLabel, children: tags.map((tag) => /* @__PURE__ */ jsxs12("li", { children: [
2664
2729
  tag,
2665
2730
  /* @__PURE__ */ jsx18("button", { type: "button", onClick: () => setTags(tags.filter((current) => current !== tag)), children: removeLabel })
2666
2731
  ] }, tag)) }),
2667
2732
  /* @__PURE__ */ jsx18("button", { type: "submit", disabled: itemState.isMutating, "aria-busy": itemState.isMutating, children: applyTagsLabel })
2668
2733
  ] });
2669
- return /* @__PURE__ */ jsx18(
2734
+ return /* @__PURE__ */ jsxs12(
2670
2735
  "form",
2671
2736
  {
2672
2737
  ...props,
@@ -2675,23 +2740,30 @@ function KeepTagEditor({
2675
2740
  void save().catch(() => void 0);
2676
2741
  },
2677
2742
  "aria-busy": itemState.isMutating || props["aria-busy"],
2678
- "data-state": itemState.isMutating ? "saving" : "idle",
2743
+ "data-keepkit": "tag-editor",
2744
+ "data-state": itemState.error ? "error" : itemState.isMutating ? "saving" : "idle",
2679
2745
  "data-loading": itemState.isMutating ? "true" : void 0,
2680
2746
  "data-disabled": itemState.isMutating ? "true" : void 0,
2681
- children: body
2747
+ children: [
2748
+ body,
2749
+ itemState.error ? /* @__PURE__ */ jsx18("p", { role: "alert", children: getErrorMessage5(itemState.error, errorLabel) }) : null
2750
+ ]
2682
2751
  }
2683
2752
  );
2684
2753
  }
2754
+ function getErrorMessage5(error, fallback) {
2755
+ return error instanceof Error ? error.message : fallback;
2756
+ }
2685
2757
 
2686
2758
  // src/KeepUndo.tsx
2687
2759
  import { useKeepContext as useKeepContext5 } from "@keepkit/core/react";
2688
- import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
2760
+ import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
2689
2761
  function KeepUndo({ children, label, ...props }) {
2690
2762
  const context = useKeepContext5();
2691
2763
  const message = useUiLabel("undoAvailable");
2692
2764
  const undoLabel = useUiLabel("undo");
2693
2765
  if (!context.undo.canUndo) return null;
2694
- return /* @__PURE__ */ jsxs12("div", { ...props, role: "status", "aria-live": "polite", "data-state": "available", children: [
2766
+ return /* @__PURE__ */ jsxs13("div", { ...props, role: "status", "aria-live": "polite", "data-keepkit": "undo", "data-state": "available", children: [
2695
2767
  children ?? message,
2696
2768
  /* @__PURE__ */ jsx19("button", { type: "button", onClick: () => void context.undoLastRemoval(), children: label ?? undoLabel })
2697
2769
  ] });
@@ -2700,7 +2772,7 @@ function KeepUndo({ children, label, ...props }) {
2700
2772
  // src/status.tsx
2701
2773
  import { useKeepContext as useKeepContext6 } from "@keepkit/core/react";
2702
2774
  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";
2775
+ import { Fragment as Fragment7, jsx as jsx20, jsxs as jsxs14 } from "react/jsx-runtime";
2704
2776
  function KeepEmptyState({
2705
2777
  title,
2706
2778
  description,
@@ -2712,12 +2784,18 @@ function KeepEmptyState({
2712
2784
  }) {
2713
2785
  const defaultTitle = useUiLabel("noItems").replace(/\.$/, "");
2714
2786
  const contentChildren = asChild && isValidElement6(children) ? void 0 : children;
2715
- const body = contentChildren ?? /* @__PURE__ */ jsxs13(Fragment7, { children: [
2787
+ const body = contentChildren ?? /* @__PURE__ */ jsxs14(Fragment7, { children: [
2716
2788
  /* @__PURE__ */ jsx20("h2", { children: title ?? defaultTitle }),
2717
2789
  description ? /* @__PURE__ */ jsx20("p", { children: description }) : null,
2718
2790
  action
2719
2791
  ] });
2720
- return renderRoot(asChild, children, { ...rootProps, className, "data-state": "empty" }, body, "KeepEmptyState");
2792
+ return renderRoot(
2793
+ asChild,
2794
+ children,
2795
+ { ...rootProps, className, "data-keepkit": "empty-state", "data-state": "empty" },
2796
+ body,
2797
+ "KeepEmptyState"
2798
+ );
2721
2799
  }
2722
2800
  function KeepStatus({
2723
2801
  status,
@@ -2746,6 +2824,7 @@ function KeepStatus({
2746
2824
  {
2747
2825
  ...rootProps,
2748
2826
  className,
2827
+ "data-keepkit": "status",
2749
2828
  role,
2750
2829
  "aria-live": rootProps["aria-live"] ?? "polite",
2751
2830
  "data-state": resolvedStatus,
@@ -2777,6 +2856,7 @@ function KeepAnnouncements({ messages, ...props }) {
2777
2856
  role: props.role ?? "status",
2778
2857
  "aria-live": props["aria-live"] ?? "polite",
2779
2858
  "aria-atomic": "true",
2859
+ "data-keepkit": "announcements",
2780
2860
  "data-state": "announcing",
2781
2861
  children: message
2782
2862
  }
@@ -2800,6 +2880,58 @@ function getStatusLabelKey3(status) {
2800
2880
  return "saved";
2801
2881
  }
2802
2882
 
2883
+ // src/theme.tsx
2884
+ import { cloneElement as cloneElement2, isValidElement as isValidElement7 } from "react";
2885
+ import { jsx as jsx21 } from "react/jsx-runtime";
2886
+ function KeepThemeProvider({
2887
+ children,
2888
+ theme = "default",
2889
+ mode = "system",
2890
+ density = "comfortable",
2891
+ radius = "medium",
2892
+ accentColor,
2893
+ highContrast = false,
2894
+ reducedMotion = false,
2895
+ variables,
2896
+ style,
2897
+ className,
2898
+ asChild = false,
2899
+ ...props
2900
+ }) {
2901
+ const themeClassName = [
2902
+ "keep-theme",
2903
+ `keep-theme--${theme}`,
2904
+ `keep-theme--${mode}`,
2905
+ `keep-theme--density-${density}`,
2906
+ `keep-theme--radius-${radius}`,
2907
+ className
2908
+ ].filter(Boolean).join(" ");
2909
+ const themeStyle = {
2910
+ ...style,
2911
+ ...accentColor && !highContrast ? { "--keep-accent": accentColor, "--keep-primary": accentColor } : {},
2912
+ ...variables
2913
+ };
2914
+ const rootProps = {
2915
+ ...props,
2916
+ className: themeClassName,
2917
+ style: themeStyle,
2918
+ "data-keep-theme": theme,
2919
+ "data-theme": theme,
2920
+ "data-mode": mode,
2921
+ "data-density": density,
2922
+ "data-radius": radius,
2923
+ "data-accent-color": accentColor,
2924
+ "data-high-contrast": highContrast ? "true" : void 0,
2925
+ "data-reduced-motion": reducedMotion ? "true" : void 0
2926
+ };
2927
+ if (asChild) {
2928
+ if (!isValidElement7(children))
2929
+ throw new Error("KeepThemeProvider with asChild requires a single React element child.");
2930
+ return cloneElement2(children, rootProps);
2931
+ }
2932
+ return /* @__PURE__ */ jsx21("div", { ...rootProps, children });
2933
+ }
2934
+
2803
2935
  // src/index.tsx
2804
2936
  import { createAuthenticatedSyncKit } from "@keepkit/core/core";
2805
2937
  import {
@@ -2820,36 +2952,90 @@ import {
2820
2952
  LocalStorageSyncQueueAdapter,
2821
2953
  SyncStorageAdapter
2822
2954
  } from "@keepkit/core/storage";
2823
- import { jsx as jsx21, jsxs as jsxs14 } from "react/jsx-runtime";
2955
+ import { jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
2824
2956
  function KeepKitProvider({
2825
2957
  labels,
2826
2958
  locale,
2827
2959
  labelResolver,
2960
+ theme,
2961
+ mode,
2962
+ density,
2963
+ radius,
2964
+ accentColor,
2965
+ highContrast,
2966
+ reducedMotion,
2967
+ variables,
2968
+ className: themeClassName,
2969
+ style: themeStyle,
2970
+ asChild: themeAsChild,
2828
2971
  children,
2829
2972
  ...providerProps
2830
2973
  }) {
2831
- return /* @__PURE__ */ jsx21(KeepUiProvider, { labels, locale, labelResolver, children: /* @__PURE__ */ jsxs14(CoreKeepProvider, { ...providerProps, children: [
2832
- children,
2833
- /* @__PURE__ */ jsx21(KeepAnnouncements, {})
2834
- ] }) });
2974
+ return /* @__PURE__ */ jsx22(KeepUiProvider, { labels, locale, labelResolver, children: /* @__PURE__ */ jsx22(
2975
+ KeepThemeProvider,
2976
+ {
2977
+ theme,
2978
+ mode,
2979
+ density,
2980
+ radius,
2981
+ accentColor,
2982
+ highContrast,
2983
+ reducedMotion,
2984
+ variables,
2985
+ className: themeClassName,
2986
+ style: themeStyle,
2987
+ asChild: themeAsChild,
2988
+ children: /* @__PURE__ */ jsxs15(CoreKeepProvider, { ...providerProps, children: [
2989
+ children,
2990
+ /* @__PURE__ */ jsx22(KeepAnnouncements, {})
2991
+ ] })
2992
+ }
2993
+ ) });
2835
2994
  }
2836
2995
  function createKeepKit(options = {}) {
2837
- const { labels, locale, labelResolver, getTitle, getImageProps, ...coreOptions } = options;
2996
+ const {
2997
+ labels,
2998
+ locale,
2999
+ labelResolver,
3000
+ theme,
3001
+ mode,
3002
+ density,
3003
+ radius,
3004
+ accentColor,
3005
+ highContrast,
3006
+ reducedMotion,
3007
+ variables,
3008
+ themeClassName,
3009
+ themeStyle,
3010
+ getTitle,
3011
+ getImageProps,
3012
+ ...coreOptions
3013
+ } = options;
2838
3014
  const coreKit = createCoreKeepKit(coreOptions);
2839
3015
  return {
2840
- Provider: (props) => /* @__PURE__ */ jsx21(
3016
+ Provider: (props) => /* @__PURE__ */ jsx22(
2841
3017
  KeepKitProvider,
2842
3018
  {
2843
3019
  ...coreOptions,
2844
3020
  labels,
2845
3021
  locale,
2846
3022
  labelResolver,
3023
+ theme,
3024
+ mode,
3025
+ density,
3026
+ radius,
3027
+ accentColor,
3028
+ highContrast,
3029
+ reducedMotion,
3030
+ variables,
3031
+ className: themeClassName,
3032
+ style: themeStyle,
2847
3033
  ...props
2848
3034
  }
2849
3035
  ),
2850
- Button: (props) => /* @__PURE__ */ jsx21(KeepButton, { ...props }),
2851
- Backup: (props) => /* @__PURE__ */ jsx21(KeepBackup, { ...props }),
2852
- Collection: (props) => /* @__PURE__ */ jsx21(
3036
+ Button: (props) => /* @__PURE__ */ jsx22(KeepButton, { ...props }),
3037
+ Backup: (props) => /* @__PURE__ */ jsx22(KeepBackup, { ...props }),
3038
+ Collection: (props) => /* @__PURE__ */ jsx22(
2853
3039
  KeepCollection,
2854
3040
  {
2855
3041
  ...props,
@@ -2896,6 +3082,7 @@ export {
2896
3082
  KeepSyncStatusBanner,
2897
3083
  KeepTagEditor,
2898
3084
  KeepTagFilter,
3085
+ KeepThemeProvider,
2899
3086
  KeepUiProvider,
2900
3087
  KeepUndo,
2901
3088
  LocalStorageAdapter,