@keepkit/ui 0.3.0 → 0.4.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/README.md CHANGED
@@ -27,13 +27,28 @@ function SavedItems() {
27
27
 
28
28
  - `KeepButton`: 保存済み状態、`aria-pressed`、ローディング・エラー状態、`asChild`、render props。`labels` または既存の `savedAriaLabel` / `unsavedAriaLabel` で翻訳できます。
29
29
  - `KeepList`: 保存一覧、読み込み中・空・エラー状態、標準カード、削除操作。`renderItem` で行単位を置き換えられます。
30
- - `KeepItemCard`: タイトル、画像、保存ボタン、削除ボタン。`getImageUrl` とrender propsで表示を差し替えられます。
30
+ - `KeepItemCard`: タイトル、画像、保存ボタン、削除ボタン。`getImageProps`、`imageComponent`、`renderImage` とrender propsで表示を差し替えられます。
31
31
  - `KeepTagFilter`: タグと件数を表示するキーボード操作可能なフィルター。`value` / `onValueChange` のcontrolled APIに対応します。
32
32
  - `KeepNoteEditor`: ノートの編集・保存フォーム。`render` で完全なカスタムUIにできます。
33
+ - `KeepSearchInput` / `KeepSortSelect` / `KeepPagination`: `useKeepList` に渡す検索、ソート、offsetページング用のプリミティブ。
34
+ - `KeepTagEditor` / `KeepBulkActions`: タグ編集、複数選択、一括削除、一括タグ更新。
35
+ - `KeepUiProvider`: `labels`、`locale`、`labelResolver` をUI全体に適用するラベルコンテキスト。
36
+ - `KeepAnnouncements`: 保存、削除、ノート保存の成功を `aria-live` で通知します。
33
37
  - `KeepEmptyState` / `KeepStatus`: 空、読み込み中、保存中、同期中、エラー状態の表示。
34
38
 
35
39
  すべてのルート要素で `className` を利用でき、対応コンポーネントでは `asChild` で既存要素をルートとして利用できます。コンポーネントの標準HTMLは意味的な要素、ネイティブボタン、フォーム、`aria-live` を利用します。
36
40
 
41
+ ### カスタマイズ契約
42
+
43
+ - `render` / render props はstateを受け取り、標準内部マークアップを置き換えます。`KeepList.renderItem` は各アイテムだけを置き換えます。
44
+ - `KeepItemCard` の画像は `getImageProps` で `src`、`alt`、`width`、`height`、`srcSet` を作り、`imageComponent` または `renderImage` で描画します。
45
+ - `asChild` は単一のReact要素を必要とし、ルート要素だけを置き換えます。内部のアクセシビリティ属性とイベントは保持してください。
46
+ - 標準構造は `KeepList` が `div > ul > li`、`KeepTagFilter` が `fieldset`、`KeepNoteEditor` が `form`、一括操作が `section > fieldset` です。
47
+ - CSSはルートと渡された標準要素の `className` に適用します。コンポーネントはstyleを持ちません。
48
+ - `onClick`、`onSubmit`、`onChange` は利用者のハンドラーを先に実行し、`preventDefault()` された場合は既定操作を実行しません。
49
+
50
+ ラベルと画像の拡張ポイントは `KeepUiProvider` と `getImageProps`/`imageComponent`/`renderImage` に集約しています。既存の個別label propsも明示的な上書き用途として利用できます。
51
+
37
52
  ## English
38
53
 
39
54
  `@keepkit/ui` provides low-dependency, style-free React UI primitives that connect `@keepkit/core` state to an app's own CSS or design system. It has no direct dependency on Tailwind CSS, shadcn/ui, or another component library.
@@ -57,4 +72,8 @@ function SavedItems() {
57
72
 
58
73
  Components include `KeepButton`, `KeepList`, `KeepItemCard`, `KeepTagFilter`, `KeepNoteEditor`, `KeepEmptyState`, and `KeepStatus`. They support `className`, render props, localized labels, and `asChild` where a root element can be replaced. The defaults use semantic HTML, native keyboard behavior, `aria-pressed`, and live-region status announcements. `KeepList` includes loading, empty, error, and removal states, while `KeepTagFilter` exposes controlled `value` / `onValueChange` state.
59
74
 
60
- The package re-exports `KeepProvider` and the core React hooks for convenient composition. The state and storage implementation remains in `@keepkit/core`; this package only adds the UI layer.
75
+ The package also exports `KeepSearchInput`, `KeepSortSelect`, `KeepPagination`, `KeepTagEditor`, `KeepBulkActions`, `KeepUiProvider`, and `KeepAnnouncements`. Configure labels once with `KeepUiProvider`, replace card images with `getImageProps` plus `imageComponent` or `renderImage`, and use `KeepAnnouncements` for success messages in a polite live region.
76
+
77
+ Customization is explicit: render props replace component markup, `KeepList.renderItem` replaces one item, `asChild` replaces only a single root element, and standard HTML structure is stable (`div > ul > li`, `fieldset`, `form`, and `section > fieldset`). User handlers run before default behavior and `preventDefault()` cancels it. The old URL-only image entry point is replaced by the structured image API; app-wide labels belong in `KeepUiProvider`.
78
+
79
+ The package re-exports `KeepProvider` and the core React hooks for convenient composition. The state and storage implementation remains in `@keepkit/core`; this package only adds the UI layer. Existing component label props remain available as explicit overrides, while app-wide defaults belong in `KeepUiProvider`.
package/dist/index.d.ts CHANGED
@@ -1,10 +1,29 @@
1
1
  import * as react from 'react';
2
- import { ReactNode, HTMLAttributes, ReactElement, FormHTMLAttributes } from 'react';
3
- import { KeepItem, KeepListOptions } from '@keepkit/core/core';
2
+ import { HTMLAttributes, ReactNode, ReactElement, ImgHTMLAttributes, ComponentType, FormHTMLAttributes, InputHTMLAttributes, SelectHTMLAttributes } from 'react';
3
+ import { KeepListOptions, KeepItem } from '@keepkit/core/core';
4
4
  export { KeepItem, KeepItemInput, KeepListOptions, KeepSyncStatus } from '@keepkit/core/core';
5
5
  import { KeepButtonProps as KeepButtonProps$1, UseKeepListResult } from '@keepkit/core/react';
6
6
  export { KeepContextValue, KeepProvider, KeepProviderProps, useKeepContext, useKeepItem, useKeepList } from '@keepkit/core/react';
7
7
 
8
+ type KeepUiLabelKey = "save" | "saved" | "remove" | "loading" | "saving" | "syncing" | "error" | "allTags" | "filterTags" | "note" | "saveNote" | "noItems" | "loadingItems" | "errorItems" | "search" | "sort" | "newest" | "oldest" | "previousPage" | "nextPage" | "pagination" | "page" | "selectItems" | "selectedCount" | "deleteSelected" | "tagsToApply" | "applyTags" | "savedMessage" | "removedMessage" | "noteSavedMessage";
9
+ type KeepUiLabels = Partial<Record<KeepUiLabelKey, string>>;
10
+ type KeepUiLabelContext = {
11
+ locale?: string;
12
+ labels: Readonly<Record<KeepUiLabelKey, string>>;
13
+ labelResolver?: (key: KeepUiLabelKey, context: {
14
+ locale?: string;
15
+ }) => string | undefined;
16
+ };
17
+ type KeepUiProviderProps = {
18
+ labels?: KeepUiLabels;
19
+ locale?: string;
20
+ labelResolver?: KeepUiLabelContext["labelResolver"];
21
+ children?: ReactNode;
22
+ };
23
+ /** Provides one locale-aware label source to every UI primitive. */
24
+ declare function KeepUiProvider({ labels, locale, labelResolver, children }: KeepUiProviderProps): react.JSX.Element;
25
+ declare function useKeepUiLabels(): KeepUiLabelContext;
26
+
8
27
  type RenderProp<TState> = (state: TState) => ReactNode;
9
28
  type KeepButtonLabels = {
10
29
  saved?: ReactNode;
@@ -26,10 +45,16 @@ type KeepItemCardState<TMeta = Record<string, unknown>> = {
26
45
  error: unknown | null;
27
46
  remove: () => Promise<void>;
28
47
  };
48
+ type KeepImageProps = ImgHTMLAttributes<HTMLImageElement> & {
49
+ src: string;
50
+ alt: string;
51
+ };
29
52
  type KeepItemCardProps<TMeta = Record<string, unknown>> = Omit<HTMLAttributes<HTMLElement>, "children" | "title"> & {
30
53
  item: KeepItem<TMeta>;
31
54
  title?: ReactNode | ((item: KeepItem<TMeta>) => ReactNode);
32
- getImageUrl?: (item: KeepItem<TMeta>) => string | undefined;
55
+ getImageProps?: (item: KeepItem<TMeta>, title: ReactNode) => KeepImageProps | undefined;
56
+ imageComponent?: ComponentType<KeepImageProps>;
57
+ renderImage?: (props: KeepImageProps, item: KeepItem<TMeta>) => ReactNode;
33
58
  imageAlt?: string;
34
59
  render?: RenderProp<KeepItemCardState<TMeta>>;
35
60
  children?: ReactNode | RenderProp<KeepItemCardState<TMeta>>;
@@ -41,7 +66,7 @@ type KeepItemCardProps<TMeta = Record<string, unknown>> = Omit<HTMLAttributes<HT
41
66
  asChild?: boolean;
42
67
  };
43
68
  /** A low-dependency item presentation primitive. The default markup can be replaced with render props. */
44
- declare function KeepItemCard<TMeta = Record<string, unknown>>({ item, title, getImageUrl, imageAlt, render, children, removeLabel, onRemoveError, onRemoved, showSaveButton, saveButtonLabels, asChild, className, ...rootProps }: KeepItemCardProps<TMeta>): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
69
+ declare function KeepItemCard<TMeta = Record<string, unknown>>({ item, title, getImageProps, imageComponent: ImageComponent, renderImage, imageAlt, render, children, removeLabel, onRemoveError, onRemoved, showSaveButton, saveButtonLabels, asChild, className, ...rootProps }: KeepItemCardProps<TMeta>): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
45
70
  type KeepListState<TMeta = Record<string, unknown>> = UseKeepListResult<TMeta>;
46
71
  type KeepListProps<TMeta = Record<string, unknown>> = Omit<HTMLAttributes<HTMLElement>, "children"> & {
47
72
  options?: KeepListOptions<TMeta>;
@@ -97,7 +122,59 @@ type KeepNoteEditorProps<TMeta = Record<string, unknown>> = Omit<FormHTMLAttribu
97
122
  asChild?: boolean;
98
123
  };
99
124
  /** A controlled-by-default note editor that persists through useKeepItem. */
100
- declare function KeepNoteEditor<TMeta = Record<string, unknown>>({ item, label, saveLabel, placeholder, onSaved, onSaveError, render, children, asChild, className, ...formProps }: KeepNoteEditorProps<TMeta>): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
125
+ declare function KeepNoteEditor<TMeta = Record<string, unknown>>({ item, label, saveLabel, placeholder, onSaved, onSaveError, render, children, asChild, className, ...formProps }: KeepNoteEditorProps<TMeta>): react.JSX.Element;
126
+ type KeepSearchInputProps = Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "defaultValue" | "onChange"> & {
127
+ value?: string;
128
+ defaultValue?: string;
129
+ onValueChange?: (value: string) => void;
130
+ };
131
+ /** A controlled/uncontrolled search field for wiring into useKeepList.searchQuery. */
132
+ declare function KeepSearchInput({ value: controlledValue, defaultValue, onValueChange, "aria-label": ariaLabel, placeholder, ...props }: KeepSearchInputProps): react.JSX.Element;
133
+ type KeepSortValue = "savedAt:desc" | "savedAt:asc" | "updatedAt:desc" | "updatedAt:asc";
134
+ type KeepSortSelectProps = Omit<SelectHTMLAttributes<HTMLSelectElement>, "value" | "defaultValue" | "onChange"> & {
135
+ value?: KeepSortValue;
136
+ defaultValue?: KeepSortValue;
137
+ onValueChange?: (value: KeepSortValue, sort: NonNullable<KeepListOptions["sort"]>) => void;
138
+ };
139
+ /** Emits a normalized sort descriptor that can be passed directly to useKeepList. */
140
+ declare function KeepSortSelect({ value: controlledValue, defaultValue, onValueChange, "aria-label": ariaLabel, children, ...props }: KeepSortSelectProps): react.JSX.Element;
141
+ type KeepPaginationProps = Omit<HTMLAttributes<HTMLElement>, "children"> & {
142
+ totalCount: number;
143
+ pageSize: number;
144
+ page?: number;
145
+ onPageChange?: (page: number, offset: number) => void;
146
+ render?: RenderProp<{
147
+ page: number;
148
+ pageCount: number;
149
+ goToPage: (page: number) => void;
150
+ }>;
151
+ };
152
+ /** A small accessible pagination control using one-based pages and zero-based offsets. */
153
+ declare function KeepPagination({ totalCount, pageSize, page, onPageChange, render, ...props }: KeepPaginationProps): react.JSX.Element;
154
+ type KeepTagEditorProps<TMeta = Record<string, unknown>> = Omit<FormHTMLAttributes<HTMLFormElement>, "children" | "onSubmit"> & {
155
+ item: KeepItem<TMeta>;
156
+ availableTags?: string[];
157
+ onSaved?: (tags?: string[]) => void;
158
+ onSaveError?: (error: unknown) => void;
159
+ render?: RenderProp<{
160
+ tags: string[];
161
+ setTags: (tags: string[]) => void;
162
+ save: () => Promise<void>;
163
+ isSaving: boolean;
164
+ }>;
165
+ };
166
+ /** Edits one item's normalized tag set and exposes a render-prop escape hatch. */
167
+ declare function KeepTagEditor<TMeta = Record<string, unknown>>({ item, availableTags, onSaved, onSaveError, render, ...props }: KeepTagEditorProps<TMeta>): react.JSX.Element;
168
+ type KeepBulkActionsProps<TMeta = Record<string, unknown>> = Omit<HTMLAttributes<HTMLElement>, "children"> & {
169
+ listOptions?: KeepListOptions<TMeta>;
170
+ selectedIds?: string[];
171
+ defaultSelectedIds?: string[];
172
+ onSelectedIdsChange?: (ids: string[]) => void;
173
+ renderItem?: (item: KeepItem<TMeta>, selected: boolean) => ReactNode;
174
+ onCompleted?: (action: "remove" | "tags", ids: string[]) => void;
175
+ };
176
+ /** Selects visible items and performs one storage operation for the whole selection. */
177
+ declare function KeepBulkActions<TMeta = Record<string, unknown>>({ listOptions, selectedIds: controlledSelectedIds, defaultSelectedIds, onSelectedIdsChange, renderItem, onCompleted, ...props }: KeepBulkActionsProps<TMeta>): react.JSX.Element;
101
178
  type KeepEmptyStateProps = Omit<HTMLAttributes<HTMLElement>, "children" | "title"> & {
102
179
  title?: ReactNode;
103
180
  description?: ReactNode;
@@ -124,5 +201,10 @@ type KeepStatusProps<TMeta = Record<string, unknown>> = Omit<HTMLAttributes<HTML
124
201
  };
125
202
  /** Presents provider loading, mutation, synchronization, empty, and error states accessibly. */
126
203
  declare function KeepStatus<TMeta = Record<string, unknown>>({ status, labels, children, render, asChild, className, ...rootProps }: KeepStatusProps<TMeta>): ReactElement<unknown, string | react.JSXElementConstructor<any>>;
204
+ type KeepAnnouncementsProps = Omit<HTMLAttributes<HTMLElement>, "children"> & {
205
+ messages?: Partial<Record<"save" | "remove" | "note", string>>;
206
+ };
207
+ /** Announces successful save, remove, and note operations in a polite live region. */
208
+ declare function KeepAnnouncements<TMeta = Record<string, unknown>>({ messages, ...props }: KeepAnnouncementsProps): react.JSX.Element;
127
209
 
128
- export { KeepButton, type KeepButtonLabels, type KeepButtonProps, KeepEmptyState, type KeepEmptyStateProps, KeepItemCard, type KeepItemCardProps, type KeepItemCardState, KeepList, type KeepListProps, type KeepListState, KeepNoteEditor, type KeepNoteEditorProps, type KeepNoteEditorState, KeepStatus, type KeepStatusLabels, type KeepStatusProps, type KeepStatusState, type KeepStatusValue, KeepTagFilter, type KeepTagFilterProps, type KeepTagFilterState, type RenderProp };
210
+ export { KeepAnnouncements, type KeepAnnouncementsProps, KeepBulkActions, type KeepBulkActionsProps, KeepButton, type KeepButtonLabels, type KeepButtonProps, KeepEmptyState, type KeepEmptyStateProps, type KeepImageProps, KeepItemCard, type KeepItemCardProps, type KeepItemCardState, KeepList, type KeepListProps, type KeepListState, KeepNoteEditor, type KeepNoteEditorProps, type KeepNoteEditorState, KeepPagination, type KeepPaginationProps, KeepSearchInput, type KeepSearchInputProps, KeepSortSelect, type KeepSortSelectProps, type KeepSortValue, KeepStatus, type KeepStatusLabels, type KeepStatusProps, type KeepStatusState, type KeepStatusValue, KeepTagEditor, type KeepTagEditorProps, KeepTagFilter, type KeepTagFilterProps, type KeepTagFilterState, type KeepUiLabelContext, type KeepUiLabelKey, type KeepUiLabels, KeepUiProvider, type KeepUiProviderProps, type RenderProp, useKeepUiLabels };
package/dist/index.js CHANGED
@@ -9,15 +9,69 @@ import {
9
9
  } from "@keepkit/core/react";
10
10
  import {
11
11
  cloneElement,
12
+ createContext,
12
13
  isValidElement,
13
14
  useCallback,
15
+ useContext,
14
16
  useEffect,
15
17
  useMemo,
18
+ useRef,
16
19
  useState
17
20
  } from "react";
18
21
  import { KeepProvider, useKeepContext as useKeepContext2, useKeepItem as useKeepItem2, useKeepList as useKeepList2 } from "@keepkit/core/react";
19
22
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
23
+ var DEFAULT_LABELS = {
24
+ save: "Save",
25
+ saved: "Saved",
26
+ remove: "Remove",
27
+ loading: "Loading\u2026",
28
+ saving: "Saving\u2026",
29
+ syncing: "Syncing\u2026",
30
+ error: "Something went wrong.",
31
+ allTags: "All",
32
+ filterTags: "Filter saved items by tag",
33
+ note: "Note",
34
+ saveNote: "Save note",
35
+ noItems: "No saved items.",
36
+ loadingItems: "Loading saved items\u2026",
37
+ errorItems: "Could not load saved items.",
38
+ search: "Search saved items",
39
+ sort: "Sort saved items",
40
+ newest: "Newest first",
41
+ oldest: "Oldest first",
42
+ previousPage: "Previous page",
43
+ nextPage: "Next page",
44
+ pagination: "Pagination",
45
+ page: "Page",
46
+ selectItems: "Select saved items",
47
+ selectedCount: "selected",
48
+ deleteSelected: "Delete selected",
49
+ tagsToApply: "Tags to apply",
50
+ applyTags: "Apply tags",
51
+ savedMessage: "Item saved.",
52
+ removedMessage: "Item removed.",
53
+ noteSavedMessage: "Note saved."
54
+ };
55
+ var KeepUiLabelsContext = createContext({ labels: DEFAULT_LABELS });
56
+ function KeepUiProvider({ labels, locale, labelResolver, children }) {
57
+ const value = useMemo(() => {
58
+ const resolved = { ...DEFAULT_LABELS, ...labels };
59
+ return { locale, labels: resolved, labelResolver };
60
+ }, [labelResolver, labels, locale]);
61
+ return /* @__PURE__ */ jsx(KeepUiLabelsContext.Provider, { value, children });
62
+ }
63
+ function useKeepUiLabels() {
64
+ return useContext(KeepUiLabelsContext);
65
+ }
66
+ function useUiLabel(key, override) {
67
+ const context = useKeepUiLabels();
68
+ return override ?? context.labelResolver?.(key, { locale: context.locale }) ?? context.labels[key];
69
+ }
20
70
  function KeepButton({ labels, ...props }) {
71
+ const saveLabel = useUiLabel("save", typeof labels?.unsaved === "string" ? labels.unsaved : void 0);
72
+ const savedLabel = useUiLabel("saved", typeof labels?.saved === "string" ? labels.saved : void 0);
73
+ const loadingLabel = useUiLabel("loading", typeof labels?.loading === "string" ? labels.loading : void 0);
74
+ const errorLabel = useUiLabel("error", typeof labels?.error === "string" ? labels.error : void 0);
21
75
  const buttonState = useKeepItem(props.item.id, {
22
76
  meta: props.item.meta,
23
77
  targetType: props.item.targetType,
@@ -26,18 +80,18 @@ function KeepButton({ labels, ...props }) {
26
80
  });
27
81
  const customStateLabel = labels?.loading !== void 0 || labels?.error !== void 0;
28
82
  const getStateContent = (state) => {
29
- if (state.error && labels?.error !== void 0) return labels.error;
30
- if (state.isMutating && labels?.loading !== void 0) return labels.loading;
83
+ if (state.error) return labels?.error ?? errorLabel;
84
+ if (state.isMutating) return labels?.loading ?? loadingLabel;
31
85
  if (typeof props.children === "function") return props.children(state);
32
86
  if (props.children !== void 0) return props.children;
33
- return state.isSaved ? labels?.saved ?? "Saved" : labels?.unsaved ?? "Save";
87
+ return state.isSaved ? labels?.saved ?? savedLabel : labels?.unsaved ?? saveLabel;
34
88
  };
35
89
  if (props.asChild === true) {
36
90
  const sharedProps2 = {
37
91
  ...props,
38
92
  "aria-busy": props["aria-busy"] ?? (buttonState.isLoading || buttonState.isMutating),
39
- savedLabel: labels?.saved ?? props.savedLabel,
40
- unsavedLabel: labels?.unsaved ?? props.unsavedLabel,
93
+ savedLabel: labels?.saved ?? savedLabel,
94
+ unsavedLabel: labels?.unsaved ?? saveLabel,
41
95
  savedAriaLabel: labels?.savedAriaLabel ?? props.savedAriaLabel,
42
96
  unsavedAriaLabel: labels?.unsavedAriaLabel ?? props.unsavedAriaLabel
43
97
  };
@@ -51,8 +105,8 @@ function KeepButton({ labels, ...props }) {
51
105
  const sharedProps = {
52
106
  ...props,
53
107
  "aria-busy": props["aria-busy"] ?? (buttonState.isLoading || buttonState.isMutating),
54
- savedLabel: labels?.saved ?? props.savedLabel,
55
- unsavedLabel: labels?.unsaved ?? props.unsavedLabel,
108
+ savedLabel: labels?.saved ?? savedLabel,
109
+ unsavedLabel: labels?.unsaved ?? saveLabel,
56
110
  savedAriaLabel: labels?.savedAriaLabel ?? props.savedAriaLabel,
57
111
  unsavedAriaLabel: labels?.unsavedAriaLabel ?? props.unsavedAriaLabel
58
112
  };
@@ -63,11 +117,13 @@ function KeepButton({ labels, ...props }) {
63
117
  function KeepItemCard({
64
118
  item,
65
119
  title,
66
- getImageUrl,
120
+ getImageProps,
121
+ imageComponent: ImageComponent,
122
+ renderImage,
67
123
  imageAlt,
68
124
  render,
69
125
  children,
70
- removeLabel = "Remove",
126
+ removeLabel,
71
127
  onRemoveError,
72
128
  onRemoved,
73
129
  showSaveButton = true,
@@ -76,6 +132,8 @@ function KeepItemCard({
76
132
  className,
77
133
  ...rootProps
78
134
  }) {
135
+ const saveActionLabel = useUiLabel("save");
136
+ const removeActionLabel = useUiLabel("remove");
79
137
  const itemState = useKeepItem(item.id);
80
138
  const contentChildren = asChild && isValidElement(children) ? void 0 : children;
81
139
  const state = {
@@ -86,7 +144,7 @@ function KeepItemCard({
86
144
  remove: itemState.remove
87
145
  };
88
146
  const resolvedTitle = typeof title === "function" ? title(item) : title ?? getMetaTitle(item.meta) ?? item.id;
89
- const imageUrl = getImageUrl?.(item);
147
+ const imageProps = getImageProps?.(item, resolvedTitle);
90
148
  async function handleRemove() {
91
149
  try {
92
150
  await itemState.remove();
@@ -96,17 +154,17 @@ function KeepItemCard({
96
154
  }
97
155
  }
98
156
  const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs(Fragment, { children: [
99
- imageUrl ? /* @__PURE__ */ jsx("img", { src: imageUrl, alt: imageAlt ?? String(resolvedTitle) }) : null,
157
+ imageProps ? renderImage?.({ ...imageProps, alt: imageAlt ?? imageProps.alt }, item) ?? (ImageComponent ? /* @__PURE__ */ jsx(ImageComponent, { ...imageProps, alt: imageAlt ?? imageProps.alt }) : /* @__PURE__ */ jsx("img", { ...imageProps, alt: imageAlt ?? imageProps.alt })) : null,
100
158
  /* @__PURE__ */ jsx("h3", { children: resolvedTitle }),
101
159
  showSaveButton ? /* @__PURE__ */ jsx(
102
160
  KeepButton,
103
161
  {
104
162
  item: toKeepButtonItem(item),
105
163
  labels: saveButtonLabels,
106
- getAriaLabel: (buttonState) => `${buttonState.isSaved ? "Remove" : "Save"} ${String(resolvedTitle)}`
164
+ getAriaLabel: (buttonState) => `${buttonState.isSaved ? removeActionLabel : saveActionLabel} ${String(resolvedTitle)}`
107
165
  }
108
166
  ) : null,
109
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void handleRemove(), disabled: itemState.isMutating, children: removeLabel })
167
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void handleRemove(), disabled: itemState.isMutating, children: removeLabel ?? removeActionLabel })
110
168
  ] });
111
169
  return renderRoot(
112
170
  asChild,
@@ -120,16 +178,26 @@ function KeepList({
120
178
  options,
121
179
  children,
122
180
  renderItem,
123
- loading = "Loading saved items\u2026",
124
- empty = "No saved items.",
125
- error: errorContent = "Could not load saved items.",
181
+ loading,
182
+ empty,
183
+ error: errorContent,
126
184
  itemCardProps,
127
185
  asChild = false,
128
186
  className,
129
187
  ...rootProps
130
188
  }) {
189
+ const defaultLoading = useUiLabel("loadingItems");
190
+ const defaultEmpty = useUiLabel("noItems");
191
+ const defaultError = useUiLabel("errorItems");
131
192
  const state = useKeepList(options);
132
- const body = getListBody(state, { children, renderItem, loading, empty, error: errorContent, itemCardProps });
193
+ const body = getListBody(state, {
194
+ children,
195
+ renderItem,
196
+ loading: loading ?? defaultLoading,
197
+ empty: empty ?? defaultEmpty,
198
+ error: errorContent ?? defaultError,
199
+ itemCardProps
200
+ });
133
201
  const root = asChild && isValidElement(children) ? children : void 0;
134
202
  return renderRoot(
135
203
  asChild,
@@ -156,8 +224,8 @@ function KeepTagFilter({
156
224
  defaultValue,
157
225
  onChange,
158
226
  onValueChange,
159
- allLabel = "All",
160
- ariaLabel = "Filter saved items by tag",
227
+ allLabel,
228
+ ariaLabel,
161
229
  renderTag,
162
230
  render,
163
231
  children,
@@ -165,6 +233,10 @@ function KeepTagFilter({
165
233
  className,
166
234
  ...rootProps
167
235
  }) {
236
+ const uiAllLabel = useUiLabel("allTags");
237
+ const uiAriaLabel = useUiLabel("filterTags");
238
+ const defaultAllLabel = allLabel ?? uiAllLabel;
239
+ const defaultAriaLabel = ariaLabel ?? uiAriaLabel;
168
240
  const list = useKeepList(listOptions);
169
241
  const contentChildren = asChild && isValidElement(children) ? void 0 : children;
170
242
  const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
@@ -182,8 +254,8 @@ function KeepTagFilter({
182
254
  [list.tagCounts, list.tags, select, value]
183
255
  );
184
256
  const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs("fieldset", { children: [
185
- /* @__PURE__ */ jsx("legend", { children: ariaLabel }),
186
- /* @__PURE__ */ jsx("button", { type: "button", "aria-pressed": value === void 0, onClick: () => select(), children: allLabel }),
257
+ /* @__PURE__ */ jsx("legend", { children: defaultAriaLabel }),
258
+ /* @__PURE__ */ jsx("button", { type: "button", "aria-pressed": value === void 0, onClick: () => select(), children: defaultAllLabel }),
187
259
  list.tags.map((tag) => /* @__PURE__ */ jsxs("button", { type: "button", "aria-pressed": value === tag, onClick: () => select(tag), children: [
188
260
  renderTag ? renderTag(tag, list.tagCounts[tag] ?? 0, value === tag) : tag,
189
261
  /* @__PURE__ */ jsxs("span", { children: [
@@ -203,8 +275,8 @@ function KeepTagFilter({
203
275
  }
204
276
  function KeepNoteEditor({
205
277
  item,
206
- label = "Note",
207
- saveLabel = "Save note",
278
+ label,
279
+ saveLabel,
208
280
  placeholder,
209
281
  onSaved,
210
282
  onSaveError,
@@ -214,6 +286,8 @@ function KeepNoteEditor({
214
286
  className,
215
287
  ...formProps
216
288
  }) {
289
+ const defaultLabel = useUiLabel("note");
290
+ const defaultSaveLabel = useUiLabel("saveNote");
217
291
  const itemState = useKeepItem(item.id);
218
292
  const contentChildren = asChild && isValidElement(children) ? void 0 : children;
219
293
  const [note, setNote] = useState(item.note ?? "");
@@ -239,7 +313,7 @@ function KeepNoteEditor({
239
313
  };
240
314
  const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs(Fragment, { children: [
241
315
  /* @__PURE__ */ jsxs("label", { children: [
242
- label,
316
+ label ?? defaultLabel,
243
317
  /* @__PURE__ */ jsx(
244
318
  "textarea",
245
319
  {
@@ -250,22 +324,256 @@ function KeepNoteEditor({
250
324
  }
251
325
  )
252
326
  ] }),
253
- /* @__PURE__ */ jsx("button", { type: "submit", disabled: itemState.isMutating, "aria-busy": itemState.isMutating, children: saveLabel })
327
+ /* @__PURE__ */ jsx("button", { type: "submit", disabled: itemState.isMutating, "aria-busy": itemState.isMutating, children: saveLabel ?? defaultSaveLabel })
254
328
  ] });
255
329
  const handleSubmit = (event) => {
256
330
  event.preventDefault();
257
331
  void save().catch(() => void 0);
258
332
  };
333
+ if (!asChild) {
334
+ return /* @__PURE__ */ jsx(
335
+ "form",
336
+ {
337
+ ...formProps,
338
+ className,
339
+ onSubmit: handleSubmit,
340
+ "aria-busy": itemState.isMutating || formProps["aria-busy"],
341
+ children: body
342
+ }
343
+ );
344
+ }
259
345
  return renderRoot(
260
- asChild,
346
+ true,
261
347
  isValidElement(children) ? children : void 0,
262
348
  { ...formProps, className, onSubmit: handleSubmit, "aria-busy": itemState.isMutating || formProps["aria-busy"] },
263
349
  body,
264
350
  "KeepNoteEditor"
265
351
  );
266
352
  }
353
+ function KeepSearchInput({
354
+ value: controlledValue,
355
+ defaultValue = "",
356
+ onValueChange,
357
+ "aria-label": ariaLabel,
358
+ placeholder,
359
+ ...props
360
+ }) {
361
+ const label = useUiLabel("search");
362
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
363
+ const value = controlledValue ?? uncontrolledValue;
364
+ return /* @__PURE__ */ jsx(
365
+ "input",
366
+ {
367
+ ...props,
368
+ type: "search",
369
+ value,
370
+ "aria-label": ariaLabel ?? label,
371
+ placeholder: placeholder ?? label,
372
+ onChange: (event) => {
373
+ const nextValue = event.currentTarget.value;
374
+ if (controlledValue === void 0) setUncontrolledValue(nextValue);
375
+ onValueChange?.(nextValue);
376
+ }
377
+ }
378
+ );
379
+ }
380
+ function KeepSortSelect({
381
+ value: controlledValue,
382
+ defaultValue = "updatedAt:desc",
383
+ onValueChange,
384
+ "aria-label": ariaLabel,
385
+ children,
386
+ ...props
387
+ }) {
388
+ const label = useUiLabel("sort");
389
+ const newestLabel = useUiLabel("newest");
390
+ const savedLabel = useUiLabel("saved");
391
+ const oldestLabel = useUiLabel("oldest");
392
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
393
+ const value = controlledValue ?? uncontrolledValue;
394
+ const options = children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
395
+ /* @__PURE__ */ jsx("option", { value: "updatedAt:desc", children: newestLabel }),
396
+ /* @__PURE__ */ jsx("option", { value: "savedAt:desc", children: savedLabel }),
397
+ /* @__PURE__ */ jsx("option", { value: "updatedAt:asc", children: oldestLabel }),
398
+ /* @__PURE__ */ jsx("option", { value: "savedAt:asc", children: oldestLabel })
399
+ ] });
400
+ return /* @__PURE__ */ jsx(
401
+ "select",
402
+ {
403
+ ...props,
404
+ value,
405
+ "aria-label": ariaLabel ?? label,
406
+ onChange: (event) => {
407
+ const nextValue = event.currentTarget.value;
408
+ if (controlledValue === void 0) setUncontrolledValue(nextValue);
409
+ const [by, direction] = nextValue.split(":");
410
+ onValueChange?.(nextValue, { by, direction });
411
+ },
412
+ children: options
413
+ }
414
+ );
415
+ }
416
+ function KeepPagination({
417
+ totalCount,
418
+ pageSize,
419
+ page = 1,
420
+ onPageChange,
421
+ render,
422
+ ...props
423
+ }) {
424
+ const previousPageLabel = useUiLabel("previousPage");
425
+ const nextPageLabel = useUiLabel("nextPage");
426
+ const pageLabel = useUiLabel("page");
427
+ const paginationLabel = useUiLabel("pagination");
428
+ const pageCount = Math.max(1, Math.ceil(totalCount / Math.max(1, pageSize)));
429
+ const currentPage = Math.min(Math.max(1, page), pageCount);
430
+ const goToPage = (nextPage) => {
431
+ const next = Math.min(Math.max(1, nextPage), pageCount);
432
+ onPageChange?.(next, (next - 1) * pageSize);
433
+ };
434
+ if (render) return /* @__PURE__ */ jsx("nav", { ...props, children: render({ page: currentPage, pageCount, goToPage }) });
435
+ return /* @__PURE__ */ jsxs("nav", { ...props, "aria-label": props["aria-label"] ?? paginationLabel, children: [
436
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => goToPage(currentPage - 1), disabled: currentPage <= 1, children: previousPageLabel }),
437
+ /* @__PURE__ */ jsxs("span", { "aria-current": "page", children: [
438
+ pageLabel,
439
+ " ",
440
+ currentPage,
441
+ " / ",
442
+ pageCount
443
+ ] }),
444
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => goToPage(currentPage + 1), disabled: currentPage >= pageCount, children: nextPageLabel })
445
+ ] });
446
+ }
447
+ function KeepTagEditor({
448
+ item,
449
+ availableTags = [],
450
+ onSaved,
451
+ onSaveError,
452
+ render,
453
+ ...props
454
+ }) {
455
+ const tagsLabel = useUiLabel("tagsToApply");
456
+ const removeLabel = useUiLabel("remove");
457
+ const applyTagsLabel = useUiLabel("applyTags");
458
+ const itemState = useKeepItem(item.id);
459
+ const [tags, setTags] = useState(item.tags ?? []);
460
+ const [input, setInput] = useState("");
461
+ useEffect(() => setTags(itemState.item?.tags ?? item.tags ?? []), [item.tags, itemState.item?.tags]);
462
+ const save = useCallback(async () => {
463
+ const nextTags = normalizeUiTags(tags);
464
+ try {
465
+ await itemState.updateTags(nextTags);
466
+ setTags(nextTags);
467
+ onSaved?.(nextTags);
468
+ } catch (error) {
469
+ onSaveError?.(error);
470
+ throw error;
471
+ }
472
+ }, [itemState, onSaveError, onSaved, tags]);
473
+ const addTag = (tag) => {
474
+ const next = normalizeUiTags([...tags, tag]);
475
+ setTags(next);
476
+ setInput("");
477
+ };
478
+ const body = render ? render({ tags, setTags, save, isSaving: itemState.isMutating }) : /* @__PURE__ */ jsxs(Fragment, { children: [
479
+ /* @__PURE__ */ jsxs("label", { children: [
480
+ tagsLabel,
481
+ /* @__PURE__ */ jsx(
482
+ "input",
483
+ {
484
+ value: input,
485
+ list: availableTags.length > 0 ? `keep-tags-${item.id}` : void 0,
486
+ onChange: (event) => setInput(event.currentTarget.value),
487
+ onKeyDown: (event) => {
488
+ if (event.key === "Enter") {
489
+ event.preventDefault();
490
+ if (input.trim()) addTag(input);
491
+ }
492
+ }
493
+ }
494
+ )
495
+ ] }),
496
+ availableTags.length > 0 ? /* @__PURE__ */ jsx("datalist", { id: `keep-tags-${item.id}`, children: availableTags.map((tag) => /* @__PURE__ */ jsx("option", { value: tag }, tag)) }) : null,
497
+ /* @__PURE__ */ jsx("ul", { "aria-label": tagsLabel, children: tags.map((tag) => /* @__PURE__ */ jsxs("li", { children: [
498
+ tag,
499
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setTags(tags.filter((current) => current !== tag)), children: removeLabel })
500
+ ] }, tag)) }),
501
+ /* @__PURE__ */ jsx("button", { type: "submit", disabled: itemState.isMutating, "aria-busy": itemState.isMutating, children: applyTagsLabel })
502
+ ] });
503
+ return /* @__PURE__ */ jsx(
504
+ "form",
505
+ {
506
+ ...props,
507
+ onSubmit: (event) => {
508
+ event.preventDefault();
509
+ void save().catch(() => void 0);
510
+ },
511
+ "aria-busy": itemState.isMutating || props["aria-busy"],
512
+ children: body
513
+ }
514
+ );
515
+ }
516
+ function KeepBulkActions({
517
+ listOptions,
518
+ selectedIds: controlledSelectedIds,
519
+ defaultSelectedIds = [],
520
+ onSelectedIdsChange,
521
+ renderItem,
522
+ onCompleted,
523
+ ...props
524
+ }) {
525
+ const selectItemsLabel = useUiLabel("selectItems");
526
+ const selectedCountLabel = useUiLabel("selectedCount");
527
+ const deleteSelectedLabel = useUiLabel("deleteSelected");
528
+ const tagsLabel = useUiLabel("tagsToApply");
529
+ const applyTagsLabel = useUiLabel("applyTags");
530
+ const list = useKeepList(listOptions);
531
+ const [uncontrolledSelectedIds, setUncontrolledSelectedIds] = useState(defaultSelectedIds);
532
+ const selectedIds = controlledSelectedIds ?? uncontrolledSelectedIds;
533
+ const selected = new Set(selectedIds);
534
+ const [tagsInput, setTagsInput] = useState("");
535
+ const setSelectedIds = (ids) => {
536
+ if (controlledSelectedIds === void 0) setUncontrolledSelectedIds(ids);
537
+ onSelectedIdsChange?.(ids);
538
+ };
539
+ const toggle = (id) => setSelectedIds(selected.has(id) ? selectedIds.filter((current) => current !== id) : [...selectedIds, id]);
540
+ const allSelected = list.items.length > 0 && list.items.every((item) => selected.has(item.id));
541
+ const toggleAll = () => setSelectedIds(allSelected ? [] : list.items.map((item) => item.id));
542
+ const remove = async () => {
543
+ await list.removeBatch(selectedIds);
544
+ onCompleted?.("remove", selectedIds);
545
+ setSelectedIds([]);
546
+ };
547
+ const updateTags = async () => {
548
+ const tags = normalizeUiTags(tagsInput.split(","));
549
+ await list.updateTagsBatch(selectedIds, tags);
550
+ onCompleted?.("tags", selectedIds);
551
+ setSelectedIds([]);
552
+ };
553
+ return /* @__PURE__ */ jsxs("section", { ...props, "aria-busy": list.isMutating || props["aria-busy"], children: [
554
+ /* @__PURE__ */ jsxs("fieldset", { children: [
555
+ /* @__PURE__ */ jsx("legend", { children: selectItemsLabel }),
556
+ /* @__PURE__ */ jsxs("label", { children: [
557
+ /* @__PURE__ */ jsx("input", { type: "checkbox", checked: allSelected, onChange: toggleAll }),
558
+ selectedIds.length,
559
+ " ",
560
+ selectedCountLabel
561
+ ] }),
562
+ list.items.map((item) => /* @__PURE__ */ jsxs("label", { children: [
563
+ /* @__PURE__ */ jsx("input", { type: "checkbox", checked: selected.has(item.id), onChange: () => toggle(item.id) }),
564
+ renderItem ? renderItem(item, selected.has(item.id)) : getMetaTitle(item.meta) ?? item.id
565
+ ] }, item.id))
566
+ ] }),
567
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void remove(), disabled: selectedIds.length === 0 || list.isMutating, children: deleteSelectedLabel }),
568
+ /* @__PURE__ */ jsxs("label", { children: [
569
+ tagsLabel,
570
+ /* @__PURE__ */ jsx("input", { value: tagsInput, onChange: (event) => setTagsInput(event.currentTarget.value) })
571
+ ] }),
572
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void updateTags(), disabled: selectedIds.length === 0 || list.isMutating, children: applyTagsLabel })
573
+ ] });
574
+ }
267
575
  function KeepEmptyState({
268
- title = "No saved items",
576
+ title,
269
577
  description,
270
578
  action,
271
579
  children,
@@ -273,9 +581,10 @@ function KeepEmptyState({
273
581
  className,
274
582
  ...rootProps
275
583
  }) {
584
+ const defaultTitle = useUiLabel("noItems").replace(/\.$/, "");
276
585
  const contentChildren = asChild && isValidElement(children) ? void 0 : children;
277
586
  const body = contentChildren ?? /* @__PURE__ */ jsxs(Fragment, { children: [
278
- /* @__PURE__ */ jsx("h2", { children: title }),
587
+ /* @__PURE__ */ jsx("h2", { children: title ?? defaultTitle }),
279
588
  description ? /* @__PURE__ */ jsx("p", { children: description }) : null,
280
589
  action
281
590
  ] });
@@ -293,13 +602,14 @@ function KeepStatus({
293
602
  const context = useKeepContext();
294
603
  const contentChildren = asChild && isValidElement(children) ? void 0 : children;
295
604
  const resolvedStatus = status ?? getDerivedStatus(context);
605
+ const defaultLabel = useUiLabel(getStatusLabelKey(resolvedStatus));
296
606
  const state = {
297
607
  status: resolvedStatus,
298
608
  error: context.error,
299
609
  pendingCount: context.syncState.pendingCount,
300
610
  items: context.items
301
611
  };
302
- const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? labels?.[resolvedStatus] ?? getDefaultStatusLabel(resolvedStatus);
612
+ const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? labels?.[resolvedStatus] ?? defaultLabel;
303
613
  const role = rootProps.role ?? (resolvedStatus === "error" ? "alert" : "status");
304
614
  return renderRoot(
305
615
  asChild,
@@ -309,6 +619,23 @@ function KeepStatus({
309
619
  "KeepStatus"
310
620
  );
311
621
  }
622
+ function KeepAnnouncements({ messages, ...props }) {
623
+ const context = useKeepContext();
624
+ const savedMessage = useUiLabel("savedMessage", messages?.save);
625
+ const removedMessage = useUiLabel("removedMessage", messages?.remove);
626
+ const noteSavedMessage = useUiLabel("noteSavedMessage", messages?.note);
627
+ const [message, setMessage] = useState("");
628
+ const lastChangeRef = useRef(void 0);
629
+ useEffect(() => {
630
+ const change = context.lastChange;
631
+ if (!change || change === lastChangeRef.current) return;
632
+ lastChangeRef.current = change;
633
+ if (change.action === "save") setMessage(savedMessage);
634
+ else if (change.action === "remove" || change.action === "removeBatch") setMessage(removedMessage);
635
+ else if (change.action === "updateNote") setMessage(noteSavedMessage);
636
+ }, [context.lastChange, noteSavedMessage, removedMessage, savedMessage]);
637
+ return /* @__PURE__ */ jsx("div", { ...props, role: props.role ?? "status", "aria-live": props["aria-live"] ?? "polite", "aria-atomic": "true", children: message });
638
+ }
312
639
  function getDerivedStatus(context) {
313
640
  if (context.error) return "error";
314
641
  if (context.syncState.status === "pending" || context.syncState.status === "syncing") return "syncing";
@@ -317,13 +644,13 @@ function getDerivedStatus(context) {
317
644
  if (context.isHydrated && context.items.length === 0) return "empty";
318
645
  return "idle";
319
646
  }
320
- function getDefaultStatusLabel(status) {
321
- if (status === "empty") return "No saved items.";
322
- if (status === "loading") return "Loading saved items\u2026";
323
- if (status === "saving") return "Saving\u2026";
324
- if (status === "syncing") return "Syncing\u2026";
325
- if (status === "error") return "Something went wrong.";
326
- return "";
647
+ function getStatusLabelKey(status) {
648
+ if (status === "empty") return "noItems";
649
+ if (status === "loading") return "loadingItems";
650
+ if (status === "error") return "error";
651
+ if (status === "saving") return "saving";
652
+ if (status === "syncing") return "syncing";
653
+ return "saved";
327
654
  }
328
655
  function resolveContent(content, state) {
329
656
  return typeof content === "function" ? content(state) : content;
@@ -342,6 +669,9 @@ function getMetaTitle(meta) {
342
669
  const title = meta.title;
343
670
  return typeof title === "string" && title.trim() ? title.trim() : void 0;
344
671
  }
672
+ function normalizeUiTags(tags) {
673
+ return [...new Set(tags.map((tag) => tag.trim()).filter(Boolean))];
674
+ }
345
675
  function renderRoot(asChild, child, props, body, componentName) {
346
676
  if (asChild) {
347
677
  if (!isValidElement(child)) throw new Error(`${componentName} with asChild requires a single React element child.`);
@@ -350,16 +680,24 @@ function renderRoot(asChild, child, props, body, componentName) {
350
680
  return /* @__PURE__ */ jsx("div", { ...props, children: body });
351
681
  }
352
682
  export {
683
+ KeepAnnouncements,
684
+ KeepBulkActions,
353
685
  KeepButton,
354
686
  KeepEmptyState,
355
687
  KeepItemCard,
356
688
  KeepList,
357
689
  KeepNoteEditor,
690
+ KeepPagination,
358
691
  KeepProvider,
692
+ KeepSearchInput,
693
+ KeepSortSelect,
359
694
  KeepStatus,
695
+ KeepTagEditor,
360
696
  KeepTagFilter,
697
+ KeepUiProvider,
361
698
  useKeepContext2 as useKeepContext,
362
699
  useKeepItem2 as useKeepItem,
363
- useKeepList2 as useKeepList
700
+ useKeepList2 as useKeepList,
701
+ useKeepUiLabels
364
702
  };
365
703
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.tsx"],"sourcesContent":["\"use client\";\n\nimport type { KeepItem, KeepItemInput, KeepListOptions, KeepSyncStatus } from \"@keepkit/core/core\";\nimport type { UseKeepListResult } from \"@keepkit/core/react\";\nimport {\n KeepButton as CoreKeepButton,\n type KeepButtonProps as CoreKeepButtonProps,\n type KeepButtonState,\n useKeepContext,\n useKeepItem,\n useKeepList,\n} from \"@keepkit/core/react\";\nimport {\n cloneElement,\n type FormHTMLAttributes,\n type HTMLAttributes,\n isValidElement,\n type ReactElement,\n type ReactNode,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from \"react\";\n\nexport type { KeepContextValue, KeepProviderProps } from \"@keepkit/core/react\";\nexport { KeepProvider, useKeepContext, useKeepItem, useKeepList } from \"@keepkit/core/react\";\nexport type { KeepItem, KeepItemInput, KeepListOptions, KeepSyncStatus };\n\nexport type RenderProp<TState> = (state: TState) => ReactNode;\n\nexport type KeepButtonLabels = {\n saved?: ReactNode;\n unsaved?: ReactNode;\n loading?: ReactNode;\n error?: ReactNode;\n savedAriaLabel?: string;\n unsavedAriaLabel?: string;\n};\n\nexport type KeepButtonProps<TMeta = Record<string, unknown>> = CoreKeepButtonProps<TMeta> & {\n labels?: KeepButtonLabels;\n};\n\n/** A style-free save toggle with localized labels and the core button's ARIA/asChild behavior. */\nexport function KeepButton<TMeta = Record<string, unknown>>({ labels, ...props }: KeepButtonProps<TMeta>) {\n const buttonState = useKeepItem<TMeta>(props.item.id, {\n meta: props.item.meta,\n targetType: props.item.targetType,\n note: props.item.note,\n tags: props.item.tags,\n });\n const customStateLabel = labels?.loading !== undefined || labels?.error !== undefined;\n const getStateContent = (state: KeepButtonState<TMeta>): ReactNode => {\n if (state.error && labels?.error !== undefined) return labels.error;\n if (state.isMutating && labels?.loading !== undefined) return labels.loading;\n if (typeof props.children === \"function\") return props.children(state);\n if (props.children !== undefined) return props.children;\n return state.isSaved ? (labels?.saved ?? \"Saved\") : (labels?.unsaved ?? \"Save\");\n };\n if (props.asChild === true) {\n const sharedProps = {\n ...props,\n \"aria-busy\": props[\"aria-busy\"] ?? (buttonState.isLoading || buttonState.isMutating),\n savedLabel: labels?.saved ?? props.savedLabel,\n unsavedLabel: labels?.unsaved ?? props.unsavedLabel,\n savedAriaLabel: labels?.savedAriaLabel ?? props.savedAriaLabel,\n unsavedAriaLabel: labels?.unsavedAriaLabel ?? props.unsavedAriaLabel,\n };\n if (!customStateLabel) return <CoreKeepButton<TMeta> {...sharedProps} />;\n const statefulProps = {\n ...sharedProps,\n children: (state: KeepButtonState<TMeta>) => <>{getStateContent(state)}</>,\n };\n return <CoreKeepButton<TMeta> {...statefulProps} />;\n }\n const sharedProps = {\n ...props,\n \"aria-busy\": props[\"aria-busy\"] ?? (buttonState.isLoading || buttonState.isMutating),\n savedLabel: labels?.saved ?? props.savedLabel,\n unsavedLabel: labels?.unsaved ?? props.unsavedLabel,\n savedAriaLabel: labels?.savedAriaLabel ?? props.savedAriaLabel,\n unsavedAriaLabel: labels?.unsavedAriaLabel ?? props.unsavedAriaLabel,\n };\n if (!customStateLabel) return <CoreKeepButton<TMeta> {...sharedProps} />;\n const statefulProps = { ...sharedProps, children: (state: KeepButtonState<TMeta>) => getStateContent(state) };\n return <CoreKeepButton<TMeta> {...statefulProps} />;\n}\n\nexport type KeepItemCardState<TMeta = Record<string, unknown>> = {\n item: KeepItem<TMeta>;\n isSaved: boolean;\n isMutating: boolean;\n error: unknown | null;\n remove: () => Promise<void>;\n};\n\nexport type KeepItemCardProps<TMeta = Record<string, unknown>> = Omit<\n HTMLAttributes<HTMLElement>,\n \"children\" | \"title\"\n> & {\n item: KeepItem<TMeta>;\n title?: ReactNode | ((item: KeepItem<TMeta>) => ReactNode);\n getImageUrl?: (item: KeepItem<TMeta>) => string | undefined;\n imageAlt?: string;\n render?: RenderProp<KeepItemCardState<TMeta>>;\n children?: ReactNode | RenderProp<KeepItemCardState<TMeta>>;\n removeLabel?: string;\n onRemoveError?: (error: unknown) => void;\n onRemoved?: (item: KeepItem<TMeta>) => void;\n showSaveButton?: boolean;\n saveButtonLabels?: KeepButtonLabels;\n asChild?: boolean;\n};\n\n/** A low-dependency item presentation primitive. The default markup can be replaced with render props. */\nexport function KeepItemCard<TMeta = Record<string, unknown>>({\n item,\n title,\n getImageUrl,\n imageAlt,\n render,\n children,\n removeLabel = \"Remove\",\n onRemoveError,\n onRemoved,\n showSaveButton = true,\n saveButtonLabels,\n asChild = false,\n className,\n ...rootProps\n}: KeepItemCardProps<TMeta>) {\n const itemState = useKeepItem<TMeta>(item.id);\n const contentChildren = asChild && isValidElement(children) ? undefined : children;\n const state: KeepItemCardState<TMeta> = {\n item,\n isSaved: itemState.isSaved,\n isMutating: itemState.isMutating,\n error: itemState.error,\n remove: itemState.remove,\n };\n const resolvedTitle = typeof title === \"function\" ? title(item) : (title ?? getMetaTitle(item.meta) ?? item.id);\n const imageUrl = getImageUrl?.(item);\n\n async function handleRemove() {\n try {\n await itemState.remove();\n onRemoved?.(item);\n } catch (error) {\n onRemoveError?.(error);\n }\n }\n\n const body = render\n ? render(state)\n : typeof contentChildren === \"function\"\n ? contentChildren(state)\n : (contentChildren ?? (\n <>\n {imageUrl ? <img src={imageUrl} alt={imageAlt ?? String(resolvedTitle)} /> : null}\n <h3>{resolvedTitle}</h3>\n {showSaveButton ? (\n <KeepButton\n item={toKeepButtonItem(item)}\n labels={saveButtonLabels}\n getAriaLabel={(buttonState) => `${buttonState.isSaved ? \"Remove\" : \"Save\"} ${String(resolvedTitle)}`}\n />\n ) : null}\n <button type=\"button\" onClick={() => void handleRemove()} disabled={itemState.isMutating}>\n {removeLabel}\n </button>\n </>\n ));\n\n return renderRoot(\n asChild,\n isValidElement(children) ? children : undefined,\n { ...rootProps, className, \"aria-busy\": itemState.isMutating || rootProps[\"aria-busy\"] },\n body,\n \"KeepItemCard\",\n );\n}\n\nexport type KeepListState<TMeta = Record<string, unknown>> = UseKeepListResult<TMeta>;\n\nexport type KeepListProps<TMeta = Record<string, unknown>> = Omit<HTMLAttributes<HTMLElement>, \"children\"> & {\n options?: KeepListOptions<TMeta>;\n children?: ReactNode | RenderProp<KeepListState<TMeta>>;\n renderItem?: (item: KeepItem<TMeta>, state: KeepListState<TMeta>) => ReactNode;\n loading?: ReactNode | RenderProp<KeepListState<TMeta>>;\n empty?: ReactNode | RenderProp<KeepListState<TMeta>>;\n error?: ReactNode | RenderProp<KeepListState<TMeta>>;\n itemCardProps?: Omit<KeepItemCardProps<TMeta>, \"item\" | \"children\" | \"render\">;\n asChild?: boolean;\n};\n\n/** A list primitive with built-in loading, empty, error, and removal states. */\nexport function KeepList<TMeta = Record<string, unknown>>({\n options,\n children,\n renderItem,\n loading = \"Loading saved items…\",\n empty = \"No saved items.\",\n error: errorContent = \"Could not load saved items.\",\n itemCardProps,\n asChild = false,\n className,\n ...rootProps\n}: KeepListProps<TMeta>) {\n const state = useKeepList<TMeta>(options);\n const body = getListBody(state, { children, renderItem, loading, empty, error: errorContent, itemCardProps });\n const root = asChild && isValidElement(children) ? children : undefined;\n return renderRoot(\n asChild,\n root,\n { ...rootProps, className, \"aria-busy\": state.isLoading || rootProps[\"aria-busy\"] },\n body,\n \"KeepList\",\n );\n}\n\nfunction getListBody<TMeta>(\n state: KeepListState<TMeta>,\n options: {\n children?: ReactNode | RenderProp<KeepListState<TMeta>>;\n renderItem?: (item: KeepItem<TMeta>, state: KeepListState<TMeta>) => ReactNode;\n loading: ReactNode | RenderProp<KeepListState<TMeta>>;\n empty: ReactNode | RenderProp<KeepListState<TMeta>>;\n error: ReactNode | RenderProp<KeepListState<TMeta>>;\n itemCardProps?: Omit<KeepItemCardProps<TMeta>, \"item\" | \"children\" | \"render\">;\n },\n): ReactNode {\n if (state.error && state.items.length === 0) return resolveContent(options.error, state);\n if (state.isLoading && !state.isHydrated) return resolveContent(options.loading, state);\n if (state.isHydrated && state.items.length === 0) return resolveContent(options.empty, state);\n if (typeof options.children === \"function\") return options.children(state);\n if (options.children !== undefined && !isValidElement(options.children)) return options.children;\n const items = state.items.map((item) =>\n options.renderItem ? (\n options.renderItem(item, state)\n ) : (\n <li key={item.id}>\n <KeepItemCard item={item} {...options.itemCardProps} />\n </li>\n ),\n );\n return <ul>{items}</ul>;\n}\n\nexport type KeepTagFilterState = {\n tags: string[];\n tagCounts: Record<string, number>;\n value?: string;\n select: (tag?: string) => void;\n};\n\nexport type KeepTagFilterProps<TMeta = Record<string, unknown>> = Omit<\n HTMLAttributes<HTMLElement>,\n \"children\" | \"onChange\"\n> & {\n listOptions?: KeepListOptions<TMeta>;\n value?: string;\n defaultValue?: string;\n onChange?: (tag?: string) => void;\n onValueChange?: (tag?: string) => void;\n allLabel?: ReactNode;\n ariaLabel?: string;\n renderTag?: (tag: string, count: number, selected: boolean) => ReactNode;\n render?: RenderProp<KeepTagFilterState>;\n children?: ReactNode | RenderProp<KeepTagFilterState>;\n asChild?: boolean;\n};\n\n/** An ARIA button group for tag filtering; consumers can connect value to KeepList options. */\nexport function KeepTagFilter<TMeta = Record<string, unknown>>({\n listOptions,\n value: controlledValue,\n defaultValue,\n onChange,\n onValueChange,\n allLabel = \"All\",\n ariaLabel = \"Filter saved items by tag\",\n renderTag,\n render,\n children,\n asChild = false,\n className,\n ...rootProps\n}: KeepTagFilterProps<TMeta>) {\n const list = useKeepList<TMeta>(listOptions);\n const contentChildren = asChild && isValidElement(children) ? undefined : children;\n const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);\n const value = controlledValue ?? uncontrolledValue;\n const select = useCallback(\n (tag?: string) => {\n if (controlledValue === undefined) setUncontrolledValue(tag);\n onChange?.(tag);\n onValueChange?.(tag);\n },\n [controlledValue, onChange, onValueChange],\n );\n const state = useMemo<KeepTagFilterState>(\n () => ({ tags: list.tags, tagCounts: list.tagCounts, value, select }),\n [list.tagCounts, list.tags, select, value],\n );\n const body = render\n ? render(state)\n : typeof contentChildren === \"function\"\n ? contentChildren(state)\n : (contentChildren ?? (\n <fieldset>\n <legend>{ariaLabel}</legend>\n <button type=\"button\" aria-pressed={value === undefined} onClick={() => select()}>\n {allLabel}\n </button>\n {list.tags.map((tag) => (\n <button key={tag} type=\"button\" aria-pressed={value === tag} onClick={() => select(tag)}>\n {renderTag ? renderTag(tag, list.tagCounts[tag] ?? 0, value === tag) : tag}\n <span> ({list.tagCounts[tag] ?? 0})</span>\n </button>\n ))}\n </fieldset>\n ));\n return renderRoot(\n asChild,\n isValidElement(children) ? children : undefined,\n { ...rootProps, className },\n body,\n \"KeepTagFilter\",\n );\n}\n\nexport type KeepNoteEditorState<TMeta = Record<string, unknown>> = {\n item: KeepItem<TMeta>;\n note: string;\n setNote: (note: string) => void;\n isDirty: boolean;\n isSaving: boolean;\n error: unknown | null;\n save: () => Promise<void>;\n};\n\nexport type KeepNoteEditorProps<TMeta = Record<string, unknown>> = Omit<\n FormHTMLAttributes<HTMLFormElement>,\n \"children\" | \"onSubmit\"\n> & {\n item: KeepItem<TMeta>;\n label?: ReactNode;\n saveLabel?: ReactNode;\n placeholder?: string;\n onSaved?: (note?: string) => void;\n onSaveError?: (error: unknown) => void;\n render?: RenderProp<KeepNoteEditorState<TMeta>>;\n children?: ReactNode | RenderProp<KeepNoteEditorState<TMeta>>;\n asChild?: boolean;\n};\n\n/** A controlled-by-default note editor that persists through useKeepItem. */\nexport function KeepNoteEditor<TMeta = Record<string, unknown>>({\n item,\n label = \"Note\",\n saveLabel = \"Save note\",\n placeholder,\n onSaved,\n onSaveError,\n render,\n children,\n asChild = false,\n className,\n ...formProps\n}: KeepNoteEditorProps<TMeta>) {\n const itemState = useKeepItem<TMeta>(item.id);\n const contentChildren = asChild && isValidElement(children) ? undefined : children;\n const [note, setNote] = useState(item.note ?? \"\");\n useEffect(() => setNote(itemState.item?.note ?? item.note ?? \"\"), [item.note, itemState.item?.note]);\n const save = useCallback(async () => {\n const nextNote = note.trim() || undefined;\n try {\n await itemState.updateNote(nextNote);\n onSaved?.(nextNote);\n } catch (error) {\n onSaveError?.(error);\n throw error;\n }\n }, [itemState, note, onSaveError, onSaved]);\n const state: KeepNoteEditorState<TMeta> = {\n item,\n note,\n setNote,\n isDirty: note !== (itemState.item?.note ?? item.note ?? \"\"),\n isSaving: itemState.isMutating,\n error: itemState.error,\n save,\n };\n const body = render\n ? render(state)\n : typeof contentChildren === \"function\"\n ? contentChildren(state)\n : (contentChildren ?? (\n <>\n <label>\n {label}\n <textarea\n value={note}\n onChange={(event) => setNote(event.currentTarget.value)}\n placeholder={placeholder}\n disabled={itemState.isMutating}\n />\n </label>\n <button type=\"submit\" disabled={itemState.isMutating} aria-busy={itemState.isMutating}>\n {saveLabel}\n </button>\n </>\n ));\n const handleSubmit: FormHTMLAttributes<HTMLFormElement>[\"onSubmit\"] = (event) => {\n event.preventDefault();\n void save().catch(() => undefined);\n };\n return renderRoot(\n asChild,\n isValidElement(children) ? children : undefined,\n { ...formProps, className, onSubmit: handleSubmit, \"aria-busy\": itemState.isMutating || formProps[\"aria-busy\"] },\n body,\n \"KeepNoteEditor\",\n );\n}\n\nexport type KeepEmptyStateProps = Omit<HTMLAttributes<HTMLElement>, \"children\" | \"title\"> & {\n title?: ReactNode;\n description?: ReactNode;\n action?: ReactNode;\n children?: ReactNode;\n asChild?: boolean;\n};\n\n/** A style-free, reusable empty collection state. */\nexport function KeepEmptyState({\n title = \"No saved items\",\n description,\n action,\n children,\n asChild = false,\n className,\n ...rootProps\n}: KeepEmptyStateProps) {\n const contentChildren = asChild && isValidElement(children) ? undefined : children;\n const body = contentChildren ?? (\n <>\n <h2>{title}</h2>\n {description ? <p>{description}</p> : null}\n {action}\n </>\n );\n return renderRoot(asChild, children, { ...rootProps, className }, body, \"KeepEmptyState\");\n}\n\nexport type KeepStatusValue = \"idle\" | \"empty\" | \"loading\" | \"saving\" | \"syncing\" | \"error\";\n\nexport type KeepStatusLabels = Partial<Record<KeepStatusValue, ReactNode>>;\n\nexport type KeepStatusState<TMeta = Record<string, unknown>> = {\n status: KeepStatusValue;\n error: unknown | null;\n pendingCount: number;\n items: KeepItem<TMeta>[];\n};\n\nexport type KeepStatusProps<TMeta = Record<string, unknown>> = Omit<HTMLAttributes<HTMLElement>, \"children\"> & {\n status?: KeepStatusValue;\n labels?: KeepStatusLabels;\n children?: ReactNode | RenderProp<KeepStatusState<TMeta>>;\n render?: RenderProp<KeepStatusState<TMeta>>;\n asChild?: boolean;\n};\n\n/** Presents provider loading, mutation, synchronization, empty, and error states accessibly. */\nexport function KeepStatus<TMeta = Record<string, unknown>>({\n status,\n labels,\n children,\n render,\n asChild = false,\n className,\n ...rootProps\n}: KeepStatusProps<TMeta>) {\n const context = useKeepContext<TMeta>();\n const contentChildren = asChild && isValidElement(children) ? undefined : children;\n const resolvedStatus = status ?? getDerivedStatus(context);\n const state: KeepStatusState<TMeta> = {\n status: resolvedStatus,\n error: context.error,\n pendingCount: context.syncState.pendingCount,\n items: context.items,\n };\n const body = render\n ? render(state)\n : typeof contentChildren === \"function\"\n ? contentChildren(state)\n : (contentChildren ?? labels?.[resolvedStatus] ?? getDefaultStatusLabel(resolvedStatus));\n const role = rootProps.role ?? (resolvedStatus === \"error\" ? \"alert\" : \"status\");\n return renderRoot(\n asChild,\n isValidElement(children) ? children : undefined,\n { ...rootProps, className, role, \"aria-live\": rootProps[\"aria-live\"] ?? \"polite\" },\n body,\n \"KeepStatus\",\n );\n}\n\nfunction getDerivedStatus<TMeta>(context: ReturnType<typeof useKeepContext<TMeta>>): KeepStatusValue {\n if (context.error) return \"error\";\n if (context.syncState.status === \"pending\" || context.syncState.status === \"syncing\") return \"syncing\";\n if (context.isMutating) return \"saving\";\n if (context.isLoading && !context.isHydrated) return \"loading\";\n if (context.isHydrated && context.items.length === 0) return \"empty\";\n return \"idle\";\n}\n\nfunction getDefaultStatusLabel(status: KeepStatusValue): string {\n if (status === \"empty\") return \"No saved items.\";\n if (status === \"loading\") return \"Loading saved items…\";\n if (status === \"saving\") return \"Saving…\";\n if (status === \"syncing\") return \"Syncing…\";\n if (status === \"error\") return \"Something went wrong.\";\n return \"\";\n}\n\nfunction resolveContent<TState>(content: ReactNode | RenderProp<TState>, state: TState): ReactNode {\n return typeof content === \"function\" ? content(state) : content;\n}\n\nfunction toKeepButtonItem<TMeta>(item: KeepItem<TMeta>): KeepItemInput<TMeta> & { id: string } {\n return {\n id: item.id,\n meta: item.meta,\n targetType: item.targetType,\n note: item.note,\n tags: item.tags,\n };\n}\n\nfunction getMetaTitle<TMeta>(meta: TMeta): string | undefined {\n if (typeof meta !== \"object\" || meta === null || !(\"title\" in meta)) return undefined;\n const title = (meta as { title?: unknown }).title;\n return typeof title === \"string\" && title.trim() ? title.trim() : undefined;\n}\n\nfunction renderRoot(\n asChild: boolean,\n child: ReactNode,\n props: HTMLAttributes<HTMLElement> & Record<string, unknown>,\n body: ReactNode,\n componentName: string,\n): ReactElement {\n if (asChild) {\n if (!isValidElement(child)) throw new Error(`${componentName} with asChild requires a single React element child.`);\n return cloneElement(child as ReactElement<Record<string, unknown>>, { ...props, children: body });\n }\n return <div {...props}>{body}</div>;\n}\n"],"mappings":"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","names":["useKeepContext","useKeepItem","useKeepList","sharedProps","statefulProps"]}
1
+ {"version":3,"sources":["../src/index.tsx"],"sourcesContent":["\"use client\";\n\nimport type { KeepChangeContext, KeepItem, KeepItemInput, KeepListOptions, KeepSyncStatus } from \"@keepkit/core/core\";\nimport type { UseKeepListResult } from \"@keepkit/core/react\";\nimport {\n KeepButton as CoreKeepButton,\n type KeepButtonProps as CoreKeepButtonProps,\n type KeepButtonState,\n useKeepContext,\n useKeepItem,\n useKeepList,\n} from \"@keepkit/core/react\";\nimport {\n type ComponentType,\n cloneElement,\n createContext,\n type FormHTMLAttributes,\n type HTMLAttributes,\n type ImgHTMLAttributes,\n type InputHTMLAttributes,\n isValidElement,\n type ReactElement,\n type ReactNode,\n type SelectHTMLAttributes,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nexport type KeepUiLabelKey =\n | \"save\"\n | \"saved\"\n | \"remove\"\n | \"loading\"\n | \"saving\"\n | \"syncing\"\n | \"error\"\n | \"allTags\"\n | \"filterTags\"\n | \"note\"\n | \"saveNote\"\n | \"noItems\"\n | \"loadingItems\"\n | \"errorItems\"\n | \"search\"\n | \"sort\"\n | \"newest\"\n | \"oldest\"\n | \"previousPage\"\n | \"nextPage\"\n | \"pagination\"\n | \"page\"\n | \"selectItems\"\n | \"selectedCount\"\n | \"deleteSelected\"\n | \"tagsToApply\"\n | \"applyTags\"\n | \"savedMessage\"\n | \"removedMessage\"\n | \"noteSavedMessage\";\n\nexport type KeepUiLabels = Partial<Record<KeepUiLabelKey, string>>;\n\nexport type KeepUiLabelContext = {\n locale?: string;\n labels: Readonly<Record<KeepUiLabelKey, string>>;\n labelResolver?: (key: KeepUiLabelKey, context: { locale?: string }) => string | undefined;\n};\n\nconst DEFAULT_LABELS: Record<KeepUiLabelKey, string> = {\n save: \"Save\",\n saved: \"Saved\",\n remove: \"Remove\",\n loading: \"Loading…\",\n saving: \"Saving…\",\n syncing: \"Syncing…\",\n error: \"Something went wrong.\",\n allTags: \"All\",\n filterTags: \"Filter saved items by tag\",\n note: \"Note\",\n saveNote: \"Save note\",\n noItems: \"No saved items.\",\n loadingItems: \"Loading saved items…\",\n errorItems: \"Could not load saved items.\",\n search: \"Search saved items\",\n sort: \"Sort saved items\",\n newest: \"Newest first\",\n oldest: \"Oldest first\",\n previousPage: \"Previous page\",\n nextPage: \"Next page\",\n pagination: \"Pagination\",\n page: \"Page\",\n selectItems: \"Select saved items\",\n selectedCount: \"selected\",\n deleteSelected: \"Delete selected\",\n tagsToApply: \"Tags to apply\",\n applyTags: \"Apply tags\",\n savedMessage: \"Item saved.\",\n removedMessage: \"Item removed.\",\n noteSavedMessage: \"Note saved.\",\n};\n\nconst KeepUiLabelsContext = createContext<KeepUiLabelContext>({ labels: DEFAULT_LABELS });\n\nexport type KeepUiProviderProps = {\n labels?: KeepUiLabels;\n locale?: string;\n labelResolver?: KeepUiLabelContext[\"labelResolver\"];\n children?: ReactNode;\n};\n\n/** Provides one locale-aware label source to every UI primitive. */\nexport function KeepUiProvider({ labels, locale, labelResolver, children }: KeepUiProviderProps) {\n const value = useMemo<KeepUiLabelContext>(() => {\n const resolved = { ...DEFAULT_LABELS, ...labels };\n return { locale, labels: resolved, labelResolver };\n }, [labelResolver, labels, locale]);\n return <KeepUiLabelsContext.Provider value={value}>{children}</KeepUiLabelsContext.Provider>;\n}\n\nexport function useKeepUiLabels(): KeepUiLabelContext {\n return useContext(KeepUiLabelsContext);\n}\n\nfunction useUiLabel(key: KeepUiLabelKey, override?: string): string {\n const context = useKeepUiLabels();\n return override ?? context.labelResolver?.(key, { locale: context.locale }) ?? context.labels[key];\n}\n\nexport type { KeepContextValue, KeepProviderProps } from \"@keepkit/core/react\";\nexport { KeepProvider, useKeepContext, useKeepItem, useKeepList } from \"@keepkit/core/react\";\nexport type { KeepItem, KeepItemInput, KeepListOptions, KeepSyncStatus };\n\nexport type RenderProp<TState> = (state: TState) => ReactNode;\n\nexport type KeepButtonLabels = {\n saved?: ReactNode;\n unsaved?: ReactNode;\n loading?: ReactNode;\n error?: ReactNode;\n savedAriaLabel?: string;\n unsavedAriaLabel?: string;\n};\n\nexport type KeepButtonProps<TMeta = Record<string, unknown>> = CoreKeepButtonProps<TMeta> & {\n labels?: KeepButtonLabels;\n};\n\n/** A style-free save toggle with localized labels and the core button's ARIA/asChild behavior. */\nexport function KeepButton<TMeta = Record<string, unknown>>({ labels, ...props }: KeepButtonProps<TMeta>) {\n const saveLabel = useUiLabel(\"save\", typeof labels?.unsaved === \"string\" ? labels.unsaved : undefined);\n const savedLabel = useUiLabel(\"saved\", typeof labels?.saved === \"string\" ? labels.saved : undefined);\n const loadingLabel = useUiLabel(\"loading\", typeof labels?.loading === \"string\" ? labels.loading : undefined);\n const errorLabel = useUiLabel(\"error\", typeof labels?.error === \"string\" ? labels.error : undefined);\n const buttonState = useKeepItem<TMeta>(props.item.id, {\n meta: props.item.meta,\n targetType: props.item.targetType,\n note: props.item.note,\n tags: props.item.tags,\n });\n const customStateLabel = labels?.loading !== undefined || labels?.error !== undefined;\n const getStateContent = (state: KeepButtonState<TMeta>): ReactNode => {\n if (state.error) return labels?.error ?? errorLabel;\n if (state.isMutating) return labels?.loading ?? loadingLabel;\n if (typeof props.children === \"function\") return props.children(state);\n if (props.children !== undefined) return props.children;\n return state.isSaved ? (labels?.saved ?? savedLabel) : (labels?.unsaved ?? saveLabel);\n };\n if (props.asChild === true) {\n const sharedProps = {\n ...props,\n \"aria-busy\": props[\"aria-busy\"] ?? (buttonState.isLoading || buttonState.isMutating),\n savedLabel: labels?.saved ?? savedLabel,\n unsavedLabel: labels?.unsaved ?? saveLabel,\n savedAriaLabel: labels?.savedAriaLabel ?? props.savedAriaLabel,\n unsavedAriaLabel: labels?.unsavedAriaLabel ?? props.unsavedAriaLabel,\n };\n if (!customStateLabel) return <CoreKeepButton<TMeta> {...sharedProps} />;\n const statefulProps = {\n ...sharedProps,\n children: (state: KeepButtonState<TMeta>) => <>{getStateContent(state)}</>,\n };\n return <CoreKeepButton<TMeta> {...statefulProps} />;\n }\n const sharedProps = {\n ...props,\n \"aria-busy\": props[\"aria-busy\"] ?? (buttonState.isLoading || buttonState.isMutating),\n savedLabel: labels?.saved ?? savedLabel,\n unsavedLabel: labels?.unsaved ?? saveLabel,\n savedAriaLabel: labels?.savedAriaLabel ?? props.savedAriaLabel,\n unsavedAriaLabel: labels?.unsavedAriaLabel ?? props.unsavedAriaLabel,\n };\n if (!customStateLabel) return <CoreKeepButton<TMeta> {...sharedProps} />;\n const statefulProps = { ...sharedProps, children: (state: KeepButtonState<TMeta>) => getStateContent(state) };\n return <CoreKeepButton<TMeta> {...statefulProps} />;\n}\n\nexport type KeepItemCardState<TMeta = Record<string, unknown>> = {\n item: KeepItem<TMeta>;\n isSaved: boolean;\n isMutating: boolean;\n error: unknown | null;\n remove: () => Promise<void>;\n};\n\nexport type KeepImageProps = ImgHTMLAttributes<HTMLImageElement> & {\n src: string;\n alt: string;\n};\n\nexport type KeepItemCardProps<TMeta = Record<string, unknown>> = Omit<\n HTMLAttributes<HTMLElement>,\n \"children\" | \"title\"\n> & {\n item: KeepItem<TMeta>;\n title?: ReactNode | ((item: KeepItem<TMeta>) => ReactNode);\n getImageProps?: (item: KeepItem<TMeta>, title: ReactNode) => KeepImageProps | undefined;\n imageComponent?: ComponentType<KeepImageProps>;\n renderImage?: (props: KeepImageProps, item: KeepItem<TMeta>) => ReactNode;\n imageAlt?: string;\n render?: RenderProp<KeepItemCardState<TMeta>>;\n children?: ReactNode | RenderProp<KeepItemCardState<TMeta>>;\n removeLabel?: string;\n onRemoveError?: (error: unknown) => void;\n onRemoved?: (item: KeepItem<TMeta>) => void;\n showSaveButton?: boolean;\n saveButtonLabels?: KeepButtonLabels;\n asChild?: boolean;\n};\n\n/** A low-dependency item presentation primitive. The default markup can be replaced with render props. */\nexport function KeepItemCard<TMeta = Record<string, unknown>>({\n item,\n title,\n getImageProps,\n imageComponent: ImageComponent,\n renderImage,\n imageAlt,\n render,\n children,\n removeLabel,\n onRemoveError,\n onRemoved,\n showSaveButton = true,\n saveButtonLabels,\n asChild = false,\n className,\n ...rootProps\n}: KeepItemCardProps<TMeta>) {\n const saveActionLabel = useUiLabel(\"save\");\n const removeActionLabel = useUiLabel(\"remove\");\n const itemState = useKeepItem<TMeta>(item.id);\n const contentChildren = asChild && isValidElement(children) ? undefined : children;\n const state: KeepItemCardState<TMeta> = {\n item,\n isSaved: itemState.isSaved,\n isMutating: itemState.isMutating,\n error: itemState.error,\n remove: itemState.remove,\n };\n const resolvedTitle = typeof title === \"function\" ? title(item) : (title ?? getMetaTitle(item.meta) ?? item.id);\n const imageProps = getImageProps?.(item, resolvedTitle);\n\n async function handleRemove() {\n try {\n await itemState.remove();\n onRemoved?.(item);\n } catch (error) {\n onRemoveError?.(error);\n }\n }\n\n const body = render\n ? render(state)\n : typeof contentChildren === \"function\"\n ? contentChildren(state)\n : (contentChildren ?? (\n <>\n {imageProps\n ? (renderImage?.({ ...imageProps, alt: imageAlt ?? imageProps.alt }, item) ??\n (ImageComponent ? (\n <ImageComponent {...imageProps} alt={imageAlt ?? imageProps.alt} />\n ) : (\n <img {...imageProps} alt={imageAlt ?? imageProps.alt} />\n )))\n : null}\n <h3>{resolvedTitle}</h3>\n {showSaveButton ? (\n <KeepButton\n item={toKeepButtonItem(item)}\n labels={saveButtonLabels}\n getAriaLabel={(buttonState) =>\n `${buttonState.isSaved ? removeActionLabel : saveActionLabel} ${String(resolvedTitle)}`\n }\n />\n ) : null}\n <button type=\"button\" onClick={() => void handleRemove()} disabled={itemState.isMutating}>\n {removeLabel ?? removeActionLabel}\n </button>\n </>\n ));\n\n return renderRoot(\n asChild,\n isValidElement(children) ? children : undefined,\n { ...rootProps, className, \"aria-busy\": itemState.isMutating || rootProps[\"aria-busy\"] },\n body,\n \"KeepItemCard\",\n );\n}\n\nexport type KeepListState<TMeta = Record<string, unknown>> = UseKeepListResult<TMeta>;\n\nexport type KeepListProps<TMeta = Record<string, unknown>> = Omit<HTMLAttributes<HTMLElement>, \"children\"> & {\n options?: KeepListOptions<TMeta>;\n children?: ReactNode | RenderProp<KeepListState<TMeta>>;\n renderItem?: (item: KeepItem<TMeta>, state: KeepListState<TMeta>) => ReactNode;\n loading?: ReactNode | RenderProp<KeepListState<TMeta>>;\n empty?: ReactNode | RenderProp<KeepListState<TMeta>>;\n error?: ReactNode | RenderProp<KeepListState<TMeta>>;\n itemCardProps?: Omit<KeepItemCardProps<TMeta>, \"item\" | \"children\" | \"render\">;\n asChild?: boolean;\n};\n\n/** A list primitive with built-in loading, empty, error, and removal states. */\nexport function KeepList<TMeta = Record<string, unknown>>({\n options,\n children,\n renderItem,\n loading,\n empty,\n error: errorContent,\n itemCardProps,\n asChild = false,\n className,\n ...rootProps\n}: KeepListProps<TMeta>) {\n const defaultLoading = useUiLabel(\"loadingItems\");\n const defaultEmpty = useUiLabel(\"noItems\");\n const defaultError = useUiLabel(\"errorItems\");\n const state = useKeepList<TMeta>(options);\n const body = getListBody(state, {\n children,\n renderItem,\n loading: loading ?? defaultLoading,\n empty: empty ?? defaultEmpty,\n error: errorContent ?? defaultError,\n itemCardProps,\n });\n const root = asChild && isValidElement(children) ? children : undefined;\n return renderRoot(\n asChild,\n root,\n { ...rootProps, className, \"aria-busy\": state.isLoading || rootProps[\"aria-busy\"] },\n body,\n \"KeepList\",\n );\n}\n\nfunction getListBody<TMeta>(\n state: KeepListState<TMeta>,\n options: {\n children?: ReactNode | RenderProp<KeepListState<TMeta>>;\n renderItem?: (item: KeepItem<TMeta>, state: KeepListState<TMeta>) => ReactNode;\n loading: ReactNode | RenderProp<KeepListState<TMeta>>;\n empty: ReactNode | RenderProp<KeepListState<TMeta>>;\n error: ReactNode | RenderProp<KeepListState<TMeta>>;\n itemCardProps?: Omit<KeepItemCardProps<TMeta>, \"item\" | \"children\" | \"render\">;\n },\n): ReactNode {\n if (state.error && state.items.length === 0) return resolveContent(options.error, state);\n if (state.isLoading && !state.isHydrated) return resolveContent(options.loading, state);\n if (state.isHydrated && state.items.length === 0) return resolveContent(options.empty, state);\n if (typeof options.children === \"function\") return options.children(state);\n if (options.children !== undefined && !isValidElement(options.children)) return options.children;\n const items = state.items.map((item) =>\n options.renderItem ? (\n options.renderItem(item, state)\n ) : (\n <li key={item.id}>\n <KeepItemCard item={item} {...options.itemCardProps} />\n </li>\n ),\n );\n return <ul>{items}</ul>;\n}\n\nexport type KeepTagFilterState = {\n tags: string[];\n tagCounts: Record<string, number>;\n value?: string;\n select: (tag?: string) => void;\n};\n\nexport type KeepTagFilterProps<TMeta = Record<string, unknown>> = Omit<\n HTMLAttributes<HTMLElement>,\n \"children\" | \"onChange\"\n> & {\n listOptions?: KeepListOptions<TMeta>;\n value?: string;\n defaultValue?: string;\n onChange?: (tag?: string) => void;\n onValueChange?: (tag?: string) => void;\n allLabel?: ReactNode;\n ariaLabel?: string;\n renderTag?: (tag: string, count: number, selected: boolean) => ReactNode;\n render?: RenderProp<KeepTagFilterState>;\n children?: ReactNode | RenderProp<KeepTagFilterState>;\n asChild?: boolean;\n};\n\n/** An ARIA button group for tag filtering; consumers can connect value to KeepList options. */\nexport function KeepTagFilter<TMeta = Record<string, unknown>>({\n listOptions,\n value: controlledValue,\n defaultValue,\n onChange,\n onValueChange,\n allLabel,\n ariaLabel,\n renderTag,\n render,\n children,\n asChild = false,\n className,\n ...rootProps\n}: KeepTagFilterProps<TMeta>) {\n const uiAllLabel = useUiLabel(\"allTags\");\n const uiAriaLabel = useUiLabel(\"filterTags\");\n const defaultAllLabel = allLabel ?? uiAllLabel;\n const defaultAriaLabel = ariaLabel ?? uiAriaLabel;\n const list = useKeepList<TMeta>(listOptions);\n const contentChildren = asChild && isValidElement(children) ? undefined : children;\n const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);\n const value = controlledValue ?? uncontrolledValue;\n const select = useCallback(\n (tag?: string) => {\n if (controlledValue === undefined) setUncontrolledValue(tag);\n onChange?.(tag);\n onValueChange?.(tag);\n },\n [controlledValue, onChange, onValueChange],\n );\n const state = useMemo<KeepTagFilterState>(\n () => ({ tags: list.tags, tagCounts: list.tagCounts, value, select }),\n [list.tagCounts, list.tags, select, value],\n );\n const body = render\n ? render(state)\n : typeof contentChildren === \"function\"\n ? contentChildren(state)\n : (contentChildren ?? (\n <fieldset>\n <legend>{defaultAriaLabel}</legend>\n <button type=\"button\" aria-pressed={value === undefined} onClick={() => select()}>\n {defaultAllLabel}\n </button>\n {list.tags.map((tag) => (\n <button key={tag} type=\"button\" aria-pressed={value === tag} onClick={() => select(tag)}>\n {renderTag ? renderTag(tag, list.tagCounts[tag] ?? 0, value === tag) : tag}\n <span> ({list.tagCounts[tag] ?? 0})</span>\n </button>\n ))}\n </fieldset>\n ));\n return renderRoot(\n asChild,\n isValidElement(children) ? children : undefined,\n { ...rootProps, className },\n body,\n \"KeepTagFilter\",\n );\n}\n\nexport type KeepNoteEditorState<TMeta = Record<string, unknown>> = {\n item: KeepItem<TMeta>;\n note: string;\n setNote: (note: string) => void;\n isDirty: boolean;\n isSaving: boolean;\n error: unknown | null;\n save: () => Promise<void>;\n};\n\nexport type KeepNoteEditorProps<TMeta = Record<string, unknown>> = Omit<\n FormHTMLAttributes<HTMLFormElement>,\n \"children\" | \"onSubmit\"\n> & {\n item: KeepItem<TMeta>;\n label?: ReactNode;\n saveLabel?: ReactNode;\n placeholder?: string;\n onSaved?: (note?: string) => void;\n onSaveError?: (error: unknown) => void;\n render?: RenderProp<KeepNoteEditorState<TMeta>>;\n children?: ReactNode | RenderProp<KeepNoteEditorState<TMeta>>;\n asChild?: boolean;\n};\n\n/** A controlled-by-default note editor that persists through useKeepItem. */\nexport function KeepNoteEditor<TMeta = Record<string, unknown>>({\n item,\n label,\n saveLabel,\n placeholder,\n onSaved,\n onSaveError,\n render,\n children,\n asChild = false,\n className,\n ...formProps\n}: KeepNoteEditorProps<TMeta>) {\n const defaultLabel = useUiLabel(\"note\");\n const defaultSaveLabel = useUiLabel(\"saveNote\");\n const itemState = useKeepItem<TMeta>(item.id);\n const contentChildren = asChild && isValidElement(children) ? undefined : children;\n const [note, setNote] = useState(item.note ?? \"\");\n useEffect(() => setNote(itemState.item?.note ?? item.note ?? \"\"), [item.note, itemState.item?.note]);\n const save = useCallback(async () => {\n const nextNote = note.trim() || undefined;\n try {\n await itemState.updateNote(nextNote);\n onSaved?.(nextNote);\n } catch (error) {\n onSaveError?.(error);\n throw error;\n }\n }, [itemState, note, onSaveError, onSaved]);\n const state: KeepNoteEditorState<TMeta> = {\n item,\n note,\n setNote,\n isDirty: note !== (itemState.item?.note ?? item.note ?? \"\"),\n isSaving: itemState.isMutating,\n error: itemState.error,\n save,\n };\n const body = render\n ? render(state)\n : typeof contentChildren === \"function\"\n ? contentChildren(state)\n : (contentChildren ?? (\n <>\n <label>\n {label ?? defaultLabel}\n <textarea\n value={note}\n onChange={(event) => setNote(event.currentTarget.value)}\n placeholder={placeholder}\n disabled={itemState.isMutating}\n />\n </label>\n <button type=\"submit\" disabled={itemState.isMutating} aria-busy={itemState.isMutating}>\n {saveLabel ?? defaultSaveLabel}\n </button>\n </>\n ));\n const handleSubmit: FormHTMLAttributes<HTMLFormElement>[\"onSubmit\"] = (event) => {\n event.preventDefault();\n void save().catch(() => undefined);\n };\n if (!asChild) {\n return (\n <form\n {...formProps}\n className={className}\n onSubmit={handleSubmit}\n aria-busy={itemState.isMutating || formProps[\"aria-busy\"]}\n >\n {body}\n </form>\n );\n }\n return renderRoot(\n true,\n isValidElement(children) ? children : undefined,\n { ...formProps, className, onSubmit: handleSubmit, \"aria-busy\": itemState.isMutating || formProps[\"aria-busy\"] },\n body,\n \"KeepNoteEditor\",\n );\n}\n\nexport type KeepSearchInputProps = Omit<\n InputHTMLAttributes<HTMLInputElement>,\n \"value\" | \"defaultValue\" | \"onChange\"\n> & {\n value?: string;\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n};\n\n/** A controlled/uncontrolled search field for wiring into useKeepList.searchQuery. */\nexport function KeepSearchInput({\n value: controlledValue,\n defaultValue = \"\",\n onValueChange,\n \"aria-label\": ariaLabel,\n placeholder,\n ...props\n}: KeepSearchInputProps) {\n const label = useUiLabel(\"search\");\n const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);\n const value = controlledValue ?? uncontrolledValue;\n return (\n <input\n {...props}\n type=\"search\"\n value={value}\n aria-label={ariaLabel ?? label}\n placeholder={placeholder ?? label}\n onChange={(event) => {\n const nextValue = event.currentTarget.value;\n if (controlledValue === undefined) setUncontrolledValue(nextValue);\n onValueChange?.(nextValue);\n }}\n />\n );\n}\n\nexport type KeepSortValue = \"savedAt:desc\" | \"savedAt:asc\" | \"updatedAt:desc\" | \"updatedAt:asc\";\n\nexport type KeepSortSelectProps = Omit<\n SelectHTMLAttributes<HTMLSelectElement>,\n \"value\" | \"defaultValue\" | \"onChange\"\n> & {\n value?: KeepSortValue;\n defaultValue?: KeepSortValue;\n onValueChange?: (value: KeepSortValue, sort: NonNullable<KeepListOptions[\"sort\"]>) => void;\n};\n\n/** Emits a normalized sort descriptor that can be passed directly to useKeepList. */\nexport function KeepSortSelect({\n value: controlledValue,\n defaultValue = \"updatedAt:desc\",\n onValueChange,\n \"aria-label\": ariaLabel,\n children,\n ...props\n}: KeepSortSelectProps) {\n const label = useUiLabel(\"sort\");\n const newestLabel = useUiLabel(\"newest\");\n const savedLabel = useUiLabel(\"saved\");\n const oldestLabel = useUiLabel(\"oldest\");\n const [uncontrolledValue, setUncontrolledValue] = useState<KeepSortValue>(defaultValue);\n const value = controlledValue ?? uncontrolledValue;\n const options = children ?? (\n <>\n <option value=\"updatedAt:desc\">{newestLabel}</option>\n <option value=\"savedAt:desc\">{savedLabel}</option>\n <option value=\"updatedAt:asc\">{oldestLabel}</option>\n <option value=\"savedAt:asc\">{oldestLabel}</option>\n </>\n );\n return (\n <select\n {...props}\n value={value}\n aria-label={ariaLabel ?? label}\n onChange={(event) => {\n const nextValue = event.currentTarget.value as KeepSortValue;\n if (controlledValue === undefined) setUncontrolledValue(nextValue);\n const [by, direction] = nextValue.split(\":\") as [\"savedAt\" | \"updatedAt\", \"asc\" | \"desc\"];\n onValueChange?.(nextValue, { by, direction });\n }}\n >\n {options}\n </select>\n );\n}\n\nexport type KeepPaginationProps = Omit<HTMLAttributes<HTMLElement>, \"children\"> & {\n totalCount: number;\n pageSize: number;\n page?: number;\n onPageChange?: (page: number, offset: number) => void;\n render?: RenderProp<{ page: number; pageCount: number; goToPage: (page: number) => void }>;\n};\n\n/** A small accessible pagination control using one-based pages and zero-based offsets. */\nexport function KeepPagination({\n totalCount,\n pageSize,\n page = 1,\n onPageChange,\n render,\n ...props\n}: KeepPaginationProps) {\n const previousPageLabel = useUiLabel(\"previousPage\");\n const nextPageLabel = useUiLabel(\"nextPage\");\n const pageLabel = useUiLabel(\"page\");\n const paginationLabel = useUiLabel(\"pagination\");\n const pageCount = Math.max(1, Math.ceil(totalCount / Math.max(1, pageSize)));\n const currentPage = Math.min(Math.max(1, page), pageCount);\n const goToPage = (nextPage: number) => {\n const next = Math.min(Math.max(1, nextPage), pageCount);\n onPageChange?.(next, (next - 1) * pageSize);\n };\n if (render) return <nav {...props}>{render({ page: currentPage, pageCount, goToPage })}</nav>;\n return (\n <nav {...props} aria-label={props[\"aria-label\"] ?? paginationLabel}>\n <button type=\"button\" onClick={() => goToPage(currentPage - 1)} disabled={currentPage <= 1}>\n {previousPageLabel}\n </button>\n <span aria-current=\"page\">\n {pageLabel} {currentPage} / {pageCount}\n </span>\n <button type=\"button\" onClick={() => goToPage(currentPage + 1)} disabled={currentPage >= pageCount}>\n {nextPageLabel}\n </button>\n </nav>\n );\n}\n\nexport type KeepTagEditorProps<TMeta = Record<string, unknown>> = Omit<\n FormHTMLAttributes<HTMLFormElement>,\n \"children\" | \"onSubmit\"\n> & {\n item: KeepItem<TMeta>;\n availableTags?: string[];\n onSaved?: (tags?: string[]) => void;\n onSaveError?: (error: unknown) => void;\n render?: RenderProp<{\n tags: string[];\n setTags: (tags: string[]) => void;\n save: () => Promise<void>;\n isSaving: boolean;\n }>;\n};\n\n/** Edits one item's normalized tag set and exposes a render-prop escape hatch. */\nexport function KeepTagEditor<TMeta = Record<string, unknown>>({\n item,\n availableTags = [],\n onSaved,\n onSaveError,\n render,\n ...props\n}: KeepTagEditorProps<TMeta>) {\n const tagsLabel = useUiLabel(\"tagsToApply\");\n const removeLabel = useUiLabel(\"remove\");\n const applyTagsLabel = useUiLabel(\"applyTags\");\n const itemState = useKeepItem<TMeta>(item.id);\n const [tags, setTags] = useState(item.tags ?? []);\n const [input, setInput] = useState(\"\");\n useEffect(() => setTags(itemState.item?.tags ?? item.tags ?? []), [item.tags, itemState.item?.tags]);\n const save = useCallback(async () => {\n const nextTags = normalizeUiTags(tags);\n try {\n await itemState.updateTags(nextTags);\n setTags(nextTags);\n onSaved?.(nextTags);\n } catch (error) {\n onSaveError?.(error);\n throw error;\n }\n }, [itemState, onSaveError, onSaved, tags]);\n const addTag = (tag: string) => {\n const next = normalizeUiTags([...tags, tag]);\n setTags(next);\n setInput(\"\");\n };\n const body = render ? (\n render({ tags, setTags, save, isSaving: itemState.isMutating })\n ) : (\n <>\n <label>\n {tagsLabel}\n <input\n value={input}\n list={availableTags.length > 0 ? `keep-tags-${item.id}` : undefined}\n onChange={(event) => setInput(event.currentTarget.value)}\n onKeyDown={(event) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n if (input.trim()) addTag(input);\n }\n }}\n />\n </label>\n {availableTags.length > 0 ? (\n <datalist id={`keep-tags-${item.id}`}>\n {availableTags.map((tag) => (\n <option key={tag} value={tag} />\n ))}\n </datalist>\n ) : null}\n <ul aria-label={tagsLabel}>\n {tags.map((tag) => (\n <li key={tag}>\n {tag}\n <button type=\"button\" onClick={() => setTags(tags.filter((current) => current !== tag))}>\n {removeLabel}\n </button>\n </li>\n ))}\n </ul>\n <button type=\"submit\" disabled={itemState.isMutating} aria-busy={itemState.isMutating}>\n {applyTagsLabel}\n </button>\n </>\n );\n return (\n <form\n {...props}\n onSubmit={(event) => {\n event.preventDefault();\n void save().catch(() => undefined);\n }}\n aria-busy={itemState.isMutating || props[\"aria-busy\"]}\n >\n {body}\n </form>\n );\n}\n\nexport type KeepBulkActionsProps<TMeta = Record<string, unknown>> = Omit<HTMLAttributes<HTMLElement>, \"children\"> & {\n listOptions?: KeepListOptions<TMeta>;\n selectedIds?: string[];\n defaultSelectedIds?: string[];\n onSelectedIdsChange?: (ids: string[]) => void;\n renderItem?: (item: KeepItem<TMeta>, selected: boolean) => ReactNode;\n onCompleted?: (action: \"remove\" | \"tags\", ids: string[]) => void;\n};\n\n/** Selects visible items and performs one storage operation for the whole selection. */\nexport function KeepBulkActions<TMeta = Record<string, unknown>>({\n listOptions,\n selectedIds: controlledSelectedIds,\n defaultSelectedIds = [],\n onSelectedIdsChange,\n renderItem,\n onCompleted,\n ...props\n}: KeepBulkActionsProps<TMeta>) {\n const selectItemsLabel = useUiLabel(\"selectItems\");\n const selectedCountLabel = useUiLabel(\"selectedCount\");\n const deleteSelectedLabel = useUiLabel(\"deleteSelected\");\n const tagsLabel = useUiLabel(\"tagsToApply\");\n const applyTagsLabel = useUiLabel(\"applyTags\");\n const list = useKeepList<TMeta>(listOptions);\n const [uncontrolledSelectedIds, setUncontrolledSelectedIds] = useState(defaultSelectedIds);\n const selectedIds = controlledSelectedIds ?? uncontrolledSelectedIds;\n const selected = new Set(selectedIds);\n const [tagsInput, setTagsInput] = useState(\"\");\n const setSelectedIds = (ids: string[]) => {\n if (controlledSelectedIds === undefined) setUncontrolledSelectedIds(ids);\n onSelectedIdsChange?.(ids);\n };\n const toggle = (id: string) =>\n setSelectedIds(selected.has(id) ? selectedIds.filter((current) => current !== id) : [...selectedIds, id]);\n const allSelected = list.items.length > 0 && list.items.every((item) => selected.has(item.id));\n const toggleAll = () => setSelectedIds(allSelected ? [] : list.items.map((item) => item.id));\n const remove = async () => {\n await list.removeBatch(selectedIds);\n onCompleted?.(\"remove\", selectedIds);\n setSelectedIds([]);\n };\n const updateTags = async () => {\n const tags = normalizeUiTags(tagsInput.split(\",\"));\n await list.updateTagsBatch(selectedIds, tags);\n onCompleted?.(\"tags\", selectedIds);\n setSelectedIds([]);\n };\n return (\n <section {...props} aria-busy={list.isMutating || props[\"aria-busy\"]}>\n <fieldset>\n <legend>{selectItemsLabel}</legend>\n <label>\n <input type=\"checkbox\" checked={allSelected} onChange={toggleAll} />\n {selectedIds.length} {selectedCountLabel}\n </label>\n {list.items.map((item) => (\n <label key={item.id}>\n <input type=\"checkbox\" checked={selected.has(item.id)} onChange={() => toggle(item.id)} />\n {renderItem ? renderItem(item, selected.has(item.id)) : (getMetaTitle(item.meta) ?? item.id)}\n </label>\n ))}\n </fieldset>\n <button type=\"button\" onClick={() => void remove()} disabled={selectedIds.length === 0 || list.isMutating}>\n {deleteSelectedLabel}\n </button>\n <label>\n {tagsLabel}\n <input value={tagsInput} onChange={(event) => setTagsInput(event.currentTarget.value)} />\n </label>\n <button type=\"button\" onClick={() => void updateTags()} disabled={selectedIds.length === 0 || list.isMutating}>\n {applyTagsLabel}\n </button>\n </section>\n );\n}\n\nexport type KeepEmptyStateProps = Omit<HTMLAttributes<HTMLElement>, \"children\" | \"title\"> & {\n title?: ReactNode;\n description?: ReactNode;\n action?: ReactNode;\n children?: ReactNode;\n asChild?: boolean;\n};\n\n/** A style-free, reusable empty collection state. */\nexport function KeepEmptyState({\n title,\n description,\n action,\n children,\n asChild = false,\n className,\n ...rootProps\n}: KeepEmptyStateProps) {\n const defaultTitle = useUiLabel(\"noItems\").replace(/\\.$/, \"\");\n const contentChildren = asChild && isValidElement(children) ? undefined : children;\n const body = contentChildren ?? (\n <>\n <h2>{title ?? defaultTitle}</h2>\n {description ? <p>{description}</p> : null}\n {action}\n </>\n );\n return renderRoot(asChild, children, { ...rootProps, className }, body, \"KeepEmptyState\");\n}\n\nexport type KeepStatusValue = \"idle\" | \"empty\" | \"loading\" | \"saving\" | \"syncing\" | \"error\";\n\nexport type KeepStatusLabels = Partial<Record<KeepStatusValue, ReactNode>>;\n\nexport type KeepStatusState<TMeta = Record<string, unknown>> = {\n status: KeepStatusValue;\n error: unknown | null;\n pendingCount: number;\n items: KeepItem<TMeta>[];\n};\n\nexport type KeepStatusProps<TMeta = Record<string, unknown>> = Omit<HTMLAttributes<HTMLElement>, \"children\"> & {\n status?: KeepStatusValue;\n labels?: KeepStatusLabels;\n children?: ReactNode | RenderProp<KeepStatusState<TMeta>>;\n render?: RenderProp<KeepStatusState<TMeta>>;\n asChild?: boolean;\n};\n\n/** Presents provider loading, mutation, synchronization, empty, and error states accessibly. */\nexport function KeepStatus<TMeta = Record<string, unknown>>({\n status,\n labels,\n children,\n render,\n asChild = false,\n className,\n ...rootProps\n}: KeepStatusProps<TMeta>) {\n const context = useKeepContext<TMeta>();\n const contentChildren = asChild && isValidElement(children) ? undefined : children;\n const resolvedStatus = status ?? getDerivedStatus(context);\n const defaultLabel = useUiLabel(getStatusLabelKey(resolvedStatus));\n const state: KeepStatusState<TMeta> = {\n status: resolvedStatus,\n error: context.error,\n pendingCount: context.syncState.pendingCount,\n items: context.items,\n };\n const body = render\n ? render(state)\n : typeof contentChildren === \"function\"\n ? contentChildren(state)\n : (contentChildren ?? labels?.[resolvedStatus] ?? defaultLabel);\n const role = rootProps.role ?? (resolvedStatus === \"error\" ? \"alert\" : \"status\");\n return renderRoot(\n asChild,\n isValidElement(children) ? children : undefined,\n { ...rootProps, className, role, \"aria-live\": rootProps[\"aria-live\"] ?? \"polite\" },\n body,\n \"KeepStatus\",\n );\n}\n\nexport type KeepAnnouncementsProps = Omit<HTMLAttributes<HTMLElement>, \"children\"> & {\n messages?: Partial<Record<\"save\" | \"remove\" | \"note\", string>>;\n};\n\n/** Announces successful save, remove, and note operations in a polite live region. */\nexport function KeepAnnouncements<TMeta = Record<string, unknown>>({ messages, ...props }: KeepAnnouncementsProps) {\n const context = useKeepContext<TMeta>();\n const savedMessage = useUiLabel(\"savedMessage\", messages?.save);\n const removedMessage = useUiLabel(\"removedMessage\", messages?.remove);\n const noteSavedMessage = useUiLabel(\"noteSavedMessage\", messages?.note);\n const [message, setMessage] = useState(\"\");\n const lastChangeRef = useRef<KeepChangeContext<TMeta> | undefined>(undefined);\n useEffect(() => {\n const change = context.lastChange;\n if (!change || change === lastChangeRef.current) return;\n lastChangeRef.current = change;\n if (change.action === \"save\") setMessage(savedMessage);\n else if (change.action === \"remove\" || change.action === \"removeBatch\") setMessage(removedMessage);\n else if (change.action === \"updateNote\") setMessage(noteSavedMessage);\n }, [context.lastChange, noteSavedMessage, removedMessage, savedMessage]);\n return (\n <div {...props} role={props.role ?? \"status\"} aria-live={props[\"aria-live\"] ?? \"polite\"} aria-atomic=\"true\">\n {message}\n </div>\n );\n}\n\nfunction getDerivedStatus<TMeta>(context: ReturnType<typeof useKeepContext<TMeta>>): KeepStatusValue {\n if (context.error) return \"error\";\n if (context.syncState.status === \"pending\" || context.syncState.status === \"syncing\") return \"syncing\";\n if (context.isMutating) return \"saving\";\n if (context.isLoading && !context.isHydrated) return \"loading\";\n if (context.isHydrated && context.items.length === 0) return \"empty\";\n return \"idle\";\n}\n\nfunction getStatusLabelKey(status: KeepStatusValue): KeepUiLabelKey {\n if (status === \"empty\") return \"noItems\";\n if (status === \"loading\") return \"loadingItems\";\n if (status === \"error\") return \"error\";\n if (status === \"saving\") return \"saving\";\n if (status === \"syncing\") return \"syncing\";\n return \"saved\";\n}\n\nfunction resolveContent<TState>(content: ReactNode | RenderProp<TState>, state: TState): ReactNode {\n return typeof content === \"function\" ? content(state) : content;\n}\n\nfunction toKeepButtonItem<TMeta>(item: KeepItem<TMeta>): KeepItemInput<TMeta> & { id: string } {\n return {\n id: item.id,\n meta: item.meta,\n targetType: item.targetType,\n note: item.note,\n tags: item.tags,\n };\n}\n\nfunction getMetaTitle<TMeta>(meta: TMeta): string | undefined {\n if (typeof meta !== \"object\" || meta === null || !(\"title\" in meta)) return undefined;\n const title = (meta as { title?: unknown }).title;\n return typeof title === \"string\" && title.trim() ? title.trim() : undefined;\n}\n\nfunction normalizeUiTags(tags: string[]): string[] {\n return [...new Set(tags.map((tag) => tag.trim()).filter(Boolean))];\n}\n\nfunction renderRoot(\n asChild: boolean,\n child: ReactNode,\n props: HTMLAttributes<HTMLElement> & Record<string, unknown>,\n body: ReactNode,\n componentName: string,\n): ReactElement {\n if (asChild) {\n if (!isValidElement(child)) throw new Error(`${componentName} with asChild requires a single React element child.`);\n return cloneElement(child as ReactElement<Record<string, unknown>>, { ...props, children: body });\n }\n return <div {...props}>{body}</div>;\n}\n"],"mappings":"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","names":["useKeepContext","useKeepItem","useKeepList","sharedProps","statefulProps"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keepkit/ui",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Headless, accessible React UI components for KeepKit.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -27,11 +27,11 @@
27
27
  "access": "public"
28
28
  },
29
29
  "peerDependencies": {
30
- "@keepkit/core": ">=0.2.0",
30
+ "@keepkit/core": ">=0.4.0",
31
31
  "react": ">=18"
32
32
  },
33
33
  "devDependencies": {
34
- "@keepkit/core": "0.3.0",
34
+ "@keepkit/core": "0.4.0",
35
35
  "@types/react": "^19.2.18",
36
36
  "react": "^19.2.8",
37
37
  "tsup": "^8.5.1",