@keepkit/ui 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -11,27 +11,49 @@ import {
11
11
  } from "@keepkit/core/react";
12
12
  import {
13
13
  cloneElement,
14
- createContext,
15
14
  isValidElement,
16
15
  useCallback,
17
- useContext,
18
- useEffect,
19
- useMemo,
16
+ useEffect as useEffect2,
17
+ useMemo as useMemo2,
20
18
  useRef,
21
- useState
19
+ useState as useState2
22
20
  } from "react";
23
- import { KeepProvider, useKeepContext as useKeepContext2, useKeepItem as useKeepItem2, useKeepList as useKeepList2, useKeepShortcut } from "@keepkit/core/react";
24
- import {
25
- createBrowserStorageAdapter,
26
- createStorageAdapter,
27
- FallbackStorageAdapter,
28
- IndexedDBAdapter,
29
- IndexedDBSyncQueueAdapter,
30
- LocalStorageAdapter,
31
- LocalStorageSyncQueueAdapter,
32
- SyncStorageAdapter
33
- } from "@keepkit/core/storage";
34
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
21
+
22
+ // src/KeepItemCheckbox.tsx
23
+ import { jsx } from "react/jsx-runtime";
24
+ function KeepItemCheckbox({
25
+ item,
26
+ checked = false,
27
+ label,
28
+ onCheckedChange,
29
+ "aria-label": ariaLabel,
30
+ ...props
31
+ }) {
32
+ const itemLabel = getItemLabel(item) ?? item.id;
33
+ const accessibleLabel = ariaLabel ?? (typeof label === "string" ? label : itemLabel);
34
+ return /* @__PURE__ */ jsx(
35
+ "input",
36
+ {
37
+ ...props,
38
+ type: "checkbox",
39
+ checked,
40
+ "aria-label": accessibleLabel,
41
+ onChange: (event) => onCheckedChange?.(event.currentTarget.checked)
42
+ }
43
+ );
44
+ }
45
+ function getItemLabel(item) {
46
+ if (typeof item.meta !== "object" || item.meta === null || !("title" in item.meta)) return void 0;
47
+ const title = item.meta.title;
48
+ return typeof title === "string" && title.trim() ? title.trim() : void 0;
49
+ }
50
+
51
+ // src/query-controls.tsx
52
+ import { useEffect, useState } from "react";
53
+
54
+ // src/ui-context.tsx
55
+ import { createContext, useContext, useMemo } from "react";
56
+ import { jsx as jsx2 } from "react/jsx-runtime";
35
57
  var DEFAULT_LABELS = {
36
58
  save: "Save",
37
59
  saved: "Saved",
@@ -51,6 +73,10 @@ var DEFAULT_LABELS = {
51
73
  sort: "Sort saved items",
52
74
  newest: "Newest first",
53
75
  oldest: "Oldest first",
76
+ savedNewest: "Saved newest first",
77
+ savedOldest: "Saved oldest first",
78
+ updatedNewest: "Updated newest first",
79
+ updatedOldest: "Updated oldest first",
54
80
  previousPage: "Previous page",
55
81
  nextPage: "Next page",
56
82
  pagination: "Pagination",
@@ -70,8 +96,149 @@ function KeepUiProvider({ labels, locale, labelResolver, children }) {
70
96
  const resolved = { ...DEFAULT_LABELS, ...labels };
71
97
  return { locale, labels: resolved, labelResolver };
72
98
  }, [labelResolver, labels, locale]);
73
- return /* @__PURE__ */ jsx(KeepUiLabelsContext.Provider, { value, children });
99
+ return /* @__PURE__ */ jsx2(KeepUiLabelsContext.Provider, { value, children });
100
+ }
101
+ function useKeepUiLabels() {
102
+ return useContext(KeepUiLabelsContext);
103
+ }
104
+ function useUiLabel(key, override) {
105
+ const context = useKeepUiLabels();
106
+ return override ?? context.labelResolver?.(key, { locale: context.locale }) ?? context.labels[key];
107
+ }
108
+
109
+ // src/query-controls.tsx
110
+ import { Fragment, jsx as jsx3, jsxs } from "react/jsx-runtime";
111
+ function KeepSearchInput({
112
+ value: controlledValue,
113
+ defaultValue = "",
114
+ debounceMs = 300,
115
+ onValueChange,
116
+ "aria-label": ariaLabel,
117
+ placeholder,
118
+ ...props
119
+ }) {
120
+ const label = useUiLabel("search");
121
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
122
+ const value = controlledValue ?? uncontrolledValue;
123
+ useEffect(() => {
124
+ if (!onValueChange) return;
125
+ if (debounceMs <= 0) {
126
+ onValueChange(value);
127
+ return;
128
+ }
129
+ const timer = window.setTimeout(() => onValueChange(value), debounceMs);
130
+ return () => window.clearTimeout(timer);
131
+ }, [debounceMs, onValueChange, value]);
132
+ return /* @__PURE__ */ jsx3(
133
+ "input",
134
+ {
135
+ ...props,
136
+ type: "search",
137
+ value,
138
+ "aria-label": ariaLabel ?? label,
139
+ placeholder: placeholder ?? label,
140
+ onChange: (event) => {
141
+ const nextValue = event.currentTarget.value;
142
+ if (controlledValue === void 0) setUncontrolledValue(nextValue);
143
+ }
144
+ }
145
+ );
146
+ }
147
+ function KeepSortSelect({
148
+ value: controlledValue,
149
+ defaultValue = "updatedAt:desc",
150
+ onValueChange,
151
+ "aria-label": ariaLabel,
152
+ children,
153
+ ...props
154
+ }) {
155
+ const label = useUiLabel("sort");
156
+ const updatedNewestLabel = useUiLabel("updatedNewest");
157
+ const updatedOldestLabel = useUiLabel("updatedOldest");
158
+ const savedNewestLabel = useUiLabel("savedNewest");
159
+ const savedOldestLabel = useUiLabel("savedOldest");
160
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
161
+ const value = controlledValue ?? uncontrolledValue;
162
+ const options = children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
163
+ /* @__PURE__ */ jsx3("option", { value: "updatedAt:desc", children: updatedNewestLabel }),
164
+ /* @__PURE__ */ jsx3("option", { value: "updatedAt:asc", children: updatedOldestLabel }),
165
+ /* @__PURE__ */ jsx3("option", { value: "savedAt:desc", children: savedNewestLabel }),
166
+ /* @__PURE__ */ jsx3("option", { value: "savedAt:asc", children: savedOldestLabel })
167
+ ] });
168
+ return /* @__PURE__ */ jsx3(
169
+ "select",
170
+ {
171
+ ...props,
172
+ value,
173
+ "aria-label": ariaLabel ?? label,
174
+ onChange: (event) => {
175
+ const nextValue = event.currentTarget.value;
176
+ if (controlledValue === void 0) setUncontrolledValue(nextValue);
177
+ const [by, direction] = nextValue.split(":");
178
+ onValueChange?.(nextValue, { by, direction });
179
+ },
180
+ children: options
181
+ }
182
+ );
183
+ }
184
+ function KeepPagination({
185
+ totalCount,
186
+ pageSize,
187
+ page = 1,
188
+ maxPageButtons = 7,
189
+ onPageChange,
190
+ render,
191
+ ...props
192
+ }) {
193
+ const previousPageLabel = useUiLabel("previousPage");
194
+ const nextPageLabel = useUiLabel("nextPage");
195
+ const pageLabel = useUiLabel("page");
196
+ const paginationLabel = useUiLabel("pagination");
197
+ const pageCount = Math.max(1, Math.ceil(totalCount / Math.max(1, pageSize)));
198
+ const currentPage = Math.min(Math.max(1, page), pageCount);
199
+ const goToPage = (nextPage) => {
200
+ const next = Math.min(Math.max(1, nextPage), pageCount);
201
+ onPageChange?.(next, (next - 1) * pageSize);
202
+ };
203
+ const navProps = { ...props, "aria-label": props["aria-label"] ?? paginationLabel };
204
+ if (render) return /* @__PURE__ */ jsx3("nav", { ...navProps, children: render({ page: currentPage, pageCount, goToPage }) });
205
+ const visiblePages = getVisiblePages(currentPage, pageCount, Math.max(1, maxPageButtons));
206
+ return /* @__PURE__ */ jsxs("nav", { ...navProps, children: [
207
+ /* @__PURE__ */ jsx3("button", { type: "button", onClick: () => goToPage(currentPage - 1), disabled: currentPage <= 1, children: previousPageLabel }),
208
+ visiblePages.map((nextPage) => /* @__PURE__ */ jsx3(
209
+ "button",
210
+ {
211
+ type: "button",
212
+ "aria-current": nextPage === currentPage ? "page" : void 0,
213
+ "aria-label": `${pageLabel} ${nextPage}`,
214
+ onClick: () => goToPage(nextPage),
215
+ children: nextPage
216
+ },
217
+ nextPage
218
+ )),
219
+ /* @__PURE__ */ jsx3("button", { type: "button", onClick: () => goToPage(currentPage + 1), disabled: currentPage >= pageCount, children: nextPageLabel })
220
+ ] });
74
221
  }
222
+ function getVisiblePages(currentPage, pageCount, maxPageButtons) {
223
+ if (pageCount <= maxPageButtons) return Array.from({ length: pageCount }, (_, index) => index + 1);
224
+ const half = Math.floor(maxPageButtons / 2);
225
+ const start = Math.min(Math.max(1, currentPage - half), pageCount - maxPageButtons + 1);
226
+ return Array.from({ length: maxPageButtons }, (_, index) => start + index);
227
+ }
228
+
229
+ // src/index.tsx
230
+ import { KeepProvider, useKeepContext as useKeepContext2, useKeepItem as useKeepItem2, useKeepList as useKeepList2, useKeepShortcut } from "@keepkit/core/react";
231
+ import {
232
+ createBrowserStorageAdapter,
233
+ createStorageAdapter,
234
+ FallbackStorageAdapter,
235
+ IndexedDBAdapter,
236
+ IndexedDBSyncQueueAdapter,
237
+ LocalStorageAdapter,
238
+ LocalStorageSyncQueueAdapter,
239
+ SyncStorageAdapter
240
+ } from "@keepkit/core/storage";
241
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
75
242
  function KeepKitProvider({
76
243
  labels,
77
244
  locale,
@@ -79,20 +246,16 @@ function KeepKitProvider({
79
246
  children,
80
247
  ...providerProps
81
248
  }) {
82
- return /* @__PURE__ */ jsx(KeepUiProvider, { labels, locale, labelResolver, children: /* @__PURE__ */ jsx(CoreKeepProvider, { ...providerProps, children }) });
83
- }
84
- function useKeepUiLabels() {
85
- return useContext(KeepUiLabelsContext);
86
- }
87
- function useUiLabel(key, override) {
88
- const context = useKeepUiLabels();
89
- return override ?? context.labelResolver?.(key, { locale: context.locale }) ?? context.labels[key];
249
+ return /* @__PURE__ */ jsx4(KeepUiProvider, { labels, locale, labelResolver, children: /* @__PURE__ */ jsxs2(CoreKeepProvider, { ...providerProps, children: [
250
+ children,
251
+ /* @__PURE__ */ jsx4(KeepAnnouncements, {})
252
+ ] }) });
90
253
  }
91
254
  function createKeepKit(options = {}) {
92
255
  const { labels, locale, labelResolver, getTitle, getImageProps, ...coreOptions } = options;
93
256
  const coreKit = createCoreKeepKit(coreOptions);
94
257
  return {
95
- Provider: (props) => /* @__PURE__ */ jsx(
258
+ Provider: (props) => /* @__PURE__ */ jsx4(
96
259
  KeepKitProvider,
97
260
  {
98
261
  ...coreOptions,
@@ -102,8 +265,8 @@ function createKeepKit(options = {}) {
102
265
  ...props
103
266
  }
104
267
  ),
105
- Button: (props) => /* @__PURE__ */ jsx(KeepButton, { ...props }),
106
- Collection: (props) => /* @__PURE__ */ jsx(
268
+ Button: (props) => /* @__PURE__ */ jsx4(KeepButton, { ...props }),
269
+ Collection: (props) => /* @__PURE__ */ jsx4(
107
270
  KeepCollection,
108
271
  {
109
272
  ...props,
@@ -143,12 +306,12 @@ function KeepButton({ labels, ...props }) {
143
306
  savedAriaLabel: labels?.savedAriaLabel ?? props.savedAriaLabel,
144
307
  unsavedAriaLabel: labels?.unsavedAriaLabel ?? props.unsavedAriaLabel
145
308
  };
146
- if (!customStateLabel) return /* @__PURE__ */ jsx(CoreKeepButton, { ...sharedProps2 });
309
+ if (!customStateLabel) return /* @__PURE__ */ jsx4(CoreKeepButton, { ...sharedProps2 });
147
310
  const statefulProps2 = {
148
311
  ...sharedProps2,
149
- children: (state) => /* @__PURE__ */ jsx(Fragment, { children: getStateContent(state) })
312
+ children: (state) => /* @__PURE__ */ jsx4(Fragment2, { children: getStateContent(state) })
150
313
  };
151
- return /* @__PURE__ */ jsx(CoreKeepButton, { ...statefulProps2 });
314
+ return /* @__PURE__ */ jsx4(CoreKeepButton, { ...statefulProps2 });
152
315
  }
153
316
  const sharedProps = {
154
317
  ...props,
@@ -158,9 +321,9 @@ function KeepButton({ labels, ...props }) {
158
321
  savedAriaLabel: labels?.savedAriaLabel ?? props.savedAriaLabel,
159
322
  unsavedAriaLabel: labels?.unsavedAriaLabel ?? props.unsavedAriaLabel
160
323
  };
161
- if (!customStateLabel) return /* @__PURE__ */ jsx(CoreKeepButton, { ...sharedProps });
324
+ if (!customStateLabel) return /* @__PURE__ */ jsx4(CoreKeepButton, { ...sharedProps });
162
325
  const statefulProps = { ...sharedProps, children: (state) => getStateContent(state) };
163
- return /* @__PURE__ */ jsx(CoreKeepButton, { ...statefulProps });
326
+ return /* @__PURE__ */ jsx4(CoreKeepButton, { ...statefulProps });
164
327
  }
165
328
  function KeepItemCard({
166
329
  item,
@@ -202,10 +365,10 @@ function KeepItemCard({
202
365
  onRemoveError?.(error);
203
366
  }
204
367
  }
205
- const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs(Fragment, { children: [
206
- 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,
207
- /* @__PURE__ */ jsx("h3", { children: resolvedTitle }),
208
- showSaveButton ? /* @__PURE__ */ jsx(
368
+ const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs2(Fragment2, { children: [
369
+ imageProps ? renderImage?.({ ...imageProps, alt: imageAlt ?? imageProps.alt }, item) ?? (ImageComponent ? /* @__PURE__ */ jsx4(ImageComponent, { ...imageProps, alt: imageAlt ?? imageProps.alt }) : /* @__PURE__ */ jsx4("img", { ...imageProps, alt: imageAlt ?? imageProps.alt })) : null,
370
+ /* @__PURE__ */ jsx4("h3", { children: resolvedTitle }),
371
+ showSaveButton ? /* @__PURE__ */ jsx4(
209
372
  KeepButton,
210
373
  {
211
374
  item: toKeepButtonItem(item),
@@ -213,7 +376,7 @@ function KeepItemCard({
213
376
  getAriaLabel: (buttonState) => `${buttonState.isSaved ? removeActionLabel : saveActionLabel} ${String(resolvedTitle)}`
214
377
  }
215
378
  ) : null,
216
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void handleRemove(), disabled: itemState.isMutating, children: removeLabel ?? removeActionLabel })
379
+ /* @__PURE__ */ jsx4("button", { type: "button", onClick: () => void handleRemove(), disabled: itemState.isMutating, children: removeLabel ?? removeActionLabel })
217
380
  ] });
218
381
  return renderRoot(
219
382
  asChild,
@@ -263,9 +426,9 @@ function getListBody(state, options) {
263
426
  if (typeof options.children === "function") return options.children(state);
264
427
  if (options.children !== void 0 && !isValidElement(options.children)) return options.children;
265
428
  const items = state.items.map(
266
- (item) => options.renderItem ? options.renderItem(item, state) : /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(KeepItemCard, { item, ...options.itemCardProps }) }, item.id)
429
+ (item) => options.renderItem ? options.renderItem(item, state) : /* @__PURE__ */ jsx4("li", { children: /* @__PURE__ */ jsx4(KeepItemCard, { item, ...options.itemCardProps }) }, item.id)
267
430
  );
268
- return /* @__PURE__ */ jsx("ul", { children: items });
431
+ return /* @__PURE__ */ jsx4("ul", { children: items });
269
432
  }
270
433
  function KeepCollection({
271
434
  query = {},
@@ -287,12 +450,12 @@ function KeepCollection({
287
450
  bulkActions: false,
288
451
  ...features
289
452
  };
290
- const [searchValue, setSearchValue] = useState(query.search?.query ?? "");
291
- const [sort, setSort] = useState(query.sort ?? { by: "updatedAt", direction: "desc" });
292
- const [tag, setTag] = useState(query.tags?.[0]);
293
- const [page, setPage] = useState(query.pagination?.page ?? 1);
453
+ const [searchValue, setSearchValue] = useState2(query.search?.query ?? "");
454
+ const [sort, setSort] = useState2(query.sort ?? { by: "updatedAt", direction: "desc" });
455
+ const [tag, setTag] = useState2(query.tags?.[0]);
456
+ const [page, setPage] = useState2(query.pagination?.page ?? 1);
294
457
  const resolvedPageSize = query.pagination?.pageSize ?? pageSize;
295
- const resolvedQuery = useMemo(
458
+ const resolvedQuery = useMemo2(
296
459
  () => ({
297
460
  ...query,
298
461
  search: enabled.search ? { ...query.search, query: searchValue } : query.search,
@@ -303,24 +466,24 @@ function KeepCollection({
303
466
  [enabled.pagination, enabled.search, enabled.sort, page, query, resolvedPageSize, searchValue, sort, tag]
304
467
  );
305
468
  const list = useKeepList(resolvedQuery);
306
- useEffect(() => {
469
+ useEffect2(() => {
307
470
  if (searchValue !== void 0 || sort.by !== void 0 || sort.direction !== void 0 || tag !== void 0) {
308
471
  setPage(1);
309
472
  }
310
473
  }, [searchValue, sort.by, sort.direction, tag]);
311
- return /* @__PURE__ */ jsxs(
474
+ return /* @__PURE__ */ jsxs2(
312
475
  "section",
313
476
  {
314
477
  ...rootProps,
315
478
  className,
316
479
  "aria-busy": list.isLoading || list.isMutating || rootProps["aria-busy"],
317
480
  children: [
318
- /* @__PURE__ */ jsxs("div", { children: [
319
- enabled.search ? /* @__PURE__ */ jsx(KeepSearchInput, { value: searchValue, onValueChange: setSearchValue }) : null,
320
- enabled.sort ? /* @__PURE__ */ jsx(KeepSortSelect, { value: sortToValue(sort), onValueChange: (_value, nextSort) => setSort(nextSort) }) : null,
321
- enabled.tagFilter ? /* @__PURE__ */ jsx(KeepTagFilter, { query, value: tag, onValueChange: setTag }) : null
481
+ /* @__PURE__ */ jsxs2("div", { children: [
482
+ enabled.search ? /* @__PURE__ */ jsx4(KeepSearchInput, { value: searchValue, onValueChange: setSearchValue }) : null,
483
+ enabled.sort ? /* @__PURE__ */ jsx4(KeepSortSelect, { value: sortToValue(sort), onValueChange: (_value, nextSort) => setSort(nextSort) }) : null,
484
+ enabled.tagFilter ? /* @__PURE__ */ jsx4(KeepTagFilter, { query, value: tag, onValueChange: setTag }) : null
322
485
  ] }),
323
- /* @__PURE__ */ jsx(
486
+ /* @__PURE__ */ jsx4(
324
487
  KeepList,
325
488
  {
326
489
  query: resolvedQuery,
@@ -331,7 +494,7 @@ function KeepCollection({
331
494
  error
332
495
  }
333
496
  ),
334
- enabled.pagination ? /* @__PURE__ */ jsx(
497
+ enabled.pagination ? /* @__PURE__ */ jsx4(
335
498
  KeepPagination,
336
499
  {
337
500
  totalCount: list.totalCount,
@@ -340,8 +503,7 @@ function KeepCollection({
340
503
  onPageChange: (_nextPage, nextPageOffset) => setPage(Math.floor(nextPageOffset / resolvedPageSize) + 1)
341
504
  }
342
505
  ) : null,
343
- enabled.bulkActions ? /* @__PURE__ */ jsx(KeepBulkActions, { query: resolvedQuery }) : null,
344
- /* @__PURE__ */ jsx(KeepAnnouncements, {})
506
+ enabled.bulkActions ? /* @__PURE__ */ jsx4(KeepBulkActions, { query: resolvedQuery }) : null
345
507
  ]
346
508
  }
347
509
  );
@@ -366,7 +528,7 @@ function KeepTagFilter({
366
528
  const defaultAllLabel = allLabel ?? uiAllLabel;
367
529
  const defaultAriaLabel = ariaLabel ?? uiAriaLabel;
368
530
  const contentChildren = asChild && isValidElement(children) ? void 0 : children;
369
- const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
531
+ const [uncontrolledValue, setUncontrolledValue] = useState2(defaultValue);
370
532
  const value = controlledValue ?? uncontrolledValue;
371
533
  const list = useKeepList({ ...query, tags: value ? [...query?.tags ?? [], value] : query?.tags });
372
534
  const select = useCallback(
@@ -377,16 +539,16 @@ function KeepTagFilter({
377
539
  },
378
540
  [controlledValue, onChange, onValueChange]
379
541
  );
380
- const state = useMemo(
542
+ const state = useMemo2(
381
543
  () => ({ tags: list.tags, tagCounts: list.tagCounts, value, select }),
382
544
  [list.tagCounts, list.tags, select, value]
383
545
  );
384
- const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs("fieldset", { children: [
385
- /* @__PURE__ */ jsx("legend", { children: defaultAriaLabel }),
386
- /* @__PURE__ */ jsx("button", { type: "button", "aria-pressed": value === void 0, onClick: () => select(), children: defaultAllLabel }),
387
- list.tags.map((tag) => /* @__PURE__ */ jsxs("button", { type: "button", "aria-pressed": value === tag, onClick: () => select(tag), children: [
546
+ const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs2("fieldset", { children: [
547
+ /* @__PURE__ */ jsx4("legend", { children: defaultAriaLabel }),
548
+ /* @__PURE__ */ jsx4("button", { type: "button", "aria-pressed": value === void 0, onClick: () => select(), children: defaultAllLabel }),
549
+ list.tags.map((tag) => /* @__PURE__ */ jsxs2("button", { type: "button", "aria-pressed": value === tag, onClick: () => select(tag), children: [
388
550
  renderTag ? renderTag(tag, list.tagCounts[tag] ?? 0, value === tag) : tag,
389
- /* @__PURE__ */ jsxs("span", { children: [
551
+ /* @__PURE__ */ jsxs2("span", { children: [
390
552
  " (",
391
553
  list.tagCounts[tag] ?? 0,
392
554
  ")"
@@ -418,8 +580,8 @@ function KeepNoteEditor({
418
580
  const defaultSaveLabel = useUiLabel("saveNote");
419
581
  const itemState = useKeepItem(item);
420
582
  const contentChildren = asChild && isValidElement(children) ? void 0 : children;
421
- const [note, setNote] = useState(item.note ?? "");
422
- useEffect(() => setNote(itemState.item?.note ?? item.note ?? ""), [item.note, itemState.item?.note]);
583
+ const [note, setNote] = useState2(item.note ?? "");
584
+ useEffect2(() => setNote(itemState.item?.note ?? item.note ?? ""), [item.note, itemState.item?.note]);
423
585
  const save = useCallback(async () => {
424
586
  const nextNote = note.trim() || void 0;
425
587
  try {
@@ -439,10 +601,10 @@ function KeepNoteEditor({
439
601
  error: itemState.error,
440
602
  save
441
603
  };
442
- const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs(Fragment, { children: [
443
- /* @__PURE__ */ jsxs("label", { children: [
604
+ const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs2(Fragment2, { children: [
605
+ /* @__PURE__ */ jsxs2("label", { children: [
444
606
  label ?? defaultLabel,
445
- /* @__PURE__ */ jsx(
607
+ /* @__PURE__ */ jsx4(
446
608
  "textarea",
447
609
  {
448
610
  value: note,
@@ -452,14 +614,14 @@ function KeepNoteEditor({
452
614
  }
453
615
  )
454
616
  ] }),
455
- /* @__PURE__ */ jsx("button", { type: "submit", disabled: itemState.isMutating, "aria-busy": itemState.isMutating, children: saveLabel ?? defaultSaveLabel })
617
+ /* @__PURE__ */ jsx4("button", { type: "submit", disabled: itemState.isMutating, "aria-busy": itemState.isMutating, children: saveLabel ?? defaultSaveLabel })
456
618
  ] });
457
619
  const handleSubmit = (event) => {
458
620
  event.preventDefault();
459
621
  void save().catch(() => void 0);
460
622
  };
461
623
  if (!asChild) {
462
- return /* @__PURE__ */ jsx(
624
+ return /* @__PURE__ */ jsx4(
463
625
  "form",
464
626
  {
465
627
  ...formProps,
@@ -478,100 +640,6 @@ function KeepNoteEditor({
478
640
  "KeepNoteEditor"
479
641
  );
480
642
  }
481
- function KeepSearchInput({
482
- value: controlledValue,
483
- defaultValue = "",
484
- onValueChange,
485
- "aria-label": ariaLabel,
486
- placeholder,
487
- ...props
488
- }) {
489
- const label = useUiLabel("search");
490
- const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
491
- const value = controlledValue ?? uncontrolledValue;
492
- return /* @__PURE__ */ jsx(
493
- "input",
494
- {
495
- ...props,
496
- type: "search",
497
- value,
498
- "aria-label": ariaLabel ?? label,
499
- placeholder: placeholder ?? label,
500
- onChange: (event) => {
501
- const nextValue = event.currentTarget.value;
502
- if (controlledValue === void 0) setUncontrolledValue(nextValue);
503
- onValueChange?.(nextValue);
504
- }
505
- }
506
- );
507
- }
508
- function KeepSortSelect({
509
- value: controlledValue,
510
- defaultValue = "updatedAt:desc",
511
- onValueChange,
512
- "aria-label": ariaLabel,
513
- children,
514
- ...props
515
- }) {
516
- const label = useUiLabel("sort");
517
- const newestLabel = useUiLabel("newest");
518
- const savedLabel = useUiLabel("saved");
519
- const oldestLabel = useUiLabel("oldest");
520
- const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
521
- const value = controlledValue ?? uncontrolledValue;
522
- const options = children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
523
- /* @__PURE__ */ jsx("option", { value: "updatedAt:desc", children: newestLabel }),
524
- /* @__PURE__ */ jsx("option", { value: "savedAt:desc", children: savedLabel }),
525
- /* @__PURE__ */ jsx("option", { value: "updatedAt:asc", children: oldestLabel }),
526
- /* @__PURE__ */ jsx("option", { value: "savedAt:asc", children: oldestLabel })
527
- ] });
528
- return /* @__PURE__ */ jsx(
529
- "select",
530
- {
531
- ...props,
532
- value,
533
- "aria-label": ariaLabel ?? label,
534
- onChange: (event) => {
535
- const nextValue = event.currentTarget.value;
536
- if (controlledValue === void 0) setUncontrolledValue(nextValue);
537
- const [by, direction] = nextValue.split(":");
538
- onValueChange?.(nextValue, { by, direction });
539
- },
540
- children: options
541
- }
542
- );
543
- }
544
- function KeepPagination({
545
- totalCount,
546
- pageSize,
547
- page = 1,
548
- onPageChange,
549
- render,
550
- ...props
551
- }) {
552
- const previousPageLabel = useUiLabel("previousPage");
553
- const nextPageLabel = useUiLabel("nextPage");
554
- const pageLabel = useUiLabel("page");
555
- const paginationLabel = useUiLabel("pagination");
556
- const pageCount = Math.max(1, Math.ceil(totalCount / Math.max(1, pageSize)));
557
- const currentPage = Math.min(Math.max(1, page), pageCount);
558
- const goToPage = (nextPage) => {
559
- const next = Math.min(Math.max(1, nextPage), pageCount);
560
- onPageChange?.(next, (next - 1) * pageSize);
561
- };
562
- if (render) return /* @__PURE__ */ jsx("nav", { ...props, children: render({ page: currentPage, pageCount, goToPage }) });
563
- return /* @__PURE__ */ jsxs("nav", { ...props, "aria-label": props["aria-label"] ?? paginationLabel, children: [
564
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => goToPage(currentPage - 1), disabled: currentPage <= 1, children: previousPageLabel }),
565
- /* @__PURE__ */ jsxs("span", { "aria-current": "page", children: [
566
- pageLabel,
567
- " ",
568
- currentPage,
569
- " / ",
570
- pageCount
571
- ] }),
572
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => goToPage(currentPage + 1), disabled: currentPage >= pageCount, children: nextPageLabel })
573
- ] });
574
- }
575
643
  function KeepTagEditor({
576
644
  item,
577
645
  availableTags = [],
@@ -584,9 +652,9 @@ function KeepTagEditor({
584
652
  const removeLabel = useUiLabel("remove");
585
653
  const applyTagsLabel = useUiLabel("applyTags");
586
654
  const itemState = useKeepItem(item);
587
- const [tags, setTags] = useState(item.tags ?? []);
588
- const [input, setInput] = useState("");
589
- useEffect(() => setTags(itemState.item?.tags ?? item.tags ?? []), [item.tags, itemState.item?.tags]);
655
+ const [tags, setTags] = useState2(item.tags ?? []);
656
+ const [input, setInput] = useState2("");
657
+ useEffect2(() => setTags(itemState.item?.tags ?? item.tags ?? []), [item.tags, itemState.item?.tags]);
590
658
  const save = useCallback(async () => {
591
659
  const nextTags = normalizeUiTags(tags);
592
660
  try {
@@ -603,10 +671,10 @@ function KeepTagEditor({
603
671
  setTags(next);
604
672
  setInput("");
605
673
  };
606
- const body = render ? render({ tags, setTags, save, isSaving: itemState.isMutating }) : /* @__PURE__ */ jsxs(Fragment, { children: [
607
- /* @__PURE__ */ jsxs("label", { children: [
674
+ const body = render ? render({ tags, setTags, save, isSaving: itemState.isMutating }) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
675
+ /* @__PURE__ */ jsxs2("label", { children: [
608
676
  tagsLabel,
609
- /* @__PURE__ */ jsx(
677
+ /* @__PURE__ */ jsx4(
610
678
  "input",
611
679
  {
612
680
  value: input,
@@ -616,19 +684,22 @@ function KeepTagEditor({
616
684
  if (event.key === "Enter") {
617
685
  event.preventDefault();
618
686
  if (input.trim()) addTag(input);
687
+ } else if (event.key === "Backspace" && input.length === 0 && tags.length > 0) {
688
+ event.preventDefault();
689
+ setTags(tags.slice(0, -1));
619
690
  }
620
691
  }
621
692
  }
622
693
  )
623
694
  ] }),
624
- availableTags.length > 0 ? /* @__PURE__ */ jsx("datalist", { id: `keep-tags-${item.id}`, children: availableTags.map((tag) => /* @__PURE__ */ jsx("option", { value: tag }, tag)) }) : null,
625
- /* @__PURE__ */ jsx("ul", { "aria-label": tagsLabel, children: tags.map((tag) => /* @__PURE__ */ jsxs("li", { children: [
695
+ availableTags.length > 0 ? /* @__PURE__ */ jsx4("datalist", { id: `keep-tags-${item.id}`, children: availableTags.map((tag) => /* @__PURE__ */ jsx4("option", { value: tag }, tag)) }) : null,
696
+ /* @__PURE__ */ jsx4("ul", { "aria-label": tagsLabel, children: tags.map((tag) => /* @__PURE__ */ jsxs2("li", { children: [
626
697
  tag,
627
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setTags(tags.filter((current) => current !== tag)), children: removeLabel })
698
+ /* @__PURE__ */ jsx4("button", { type: "button", onClick: () => setTags(tags.filter((current) => current !== tag)), children: removeLabel })
628
699
  ] }, tag)) }),
629
- /* @__PURE__ */ jsx("button", { type: "submit", disabled: itemState.isMutating, "aria-busy": itemState.isMutating, children: applyTagsLabel })
700
+ /* @__PURE__ */ jsx4("button", { type: "submit", disabled: itemState.isMutating, "aria-busy": itemState.isMutating, children: applyTagsLabel })
630
701
  ] });
631
- return /* @__PURE__ */ jsx(
702
+ return /* @__PURE__ */ jsx4(
632
703
  "form",
633
704
  {
634
705
  ...props,
@@ -648,6 +719,8 @@ function KeepBulkActions({
648
719
  onSelectedIdsChange,
649
720
  renderItem,
650
721
  onCompleted,
722
+ render,
723
+ children,
651
724
  ...props
652
725
  }) {
653
726
  const selectItemsLabel = useUiLabel("selectItems");
@@ -656,10 +729,10 @@ function KeepBulkActions({
656
729
  const tagsLabel = useUiLabel("tagsToApply");
657
730
  const applyTagsLabel = useUiLabel("applyTags");
658
731
  const list = useKeepList(query);
659
- const [uncontrolledSelectedIds, setUncontrolledSelectedIds] = useState(defaultSelectedIds);
732
+ const [uncontrolledSelectedIds, setUncontrolledSelectedIds] = useState2(defaultSelectedIds);
660
733
  const selectedIds = controlledSelectedIds ?? uncontrolledSelectedIds;
661
734
  const selected = new Set(selectedIds);
662
- const [tagsInput, setTagsInput] = useState("");
735
+ const [tagsInput, setTagsInput] = useState2("");
663
736
  const setSelectedIds = (ids) => {
664
737
  if (controlledSelectedIds === void 0) setUncontrolledSelectedIds(ids);
665
738
  onSelectedIdsChange?.(ids);
@@ -678,27 +751,56 @@ function KeepBulkActions({
678
751
  onCompleted?.("tags", selectedIds);
679
752
  setSelectedIds([]);
680
753
  };
681
- return /* @__PURE__ */ jsxs("section", { ...props, "aria-busy": list.isMutating || props["aria-busy"], children: [
682
- /* @__PURE__ */ jsxs("fieldset", { children: [
683
- /* @__PURE__ */ jsx("legend", { children: selectItemsLabel }),
684
- /* @__PURE__ */ jsxs("label", { children: [
685
- /* @__PURE__ */ jsx("input", { type: "checkbox", checked: allSelected, onChange: toggleAll }),
754
+ const state = {
755
+ items: list.items,
756
+ selectedIds,
757
+ selectedCount: selectedIds.length,
758
+ allSelected,
759
+ tagsInput,
760
+ setTagsInput,
761
+ toggle,
762
+ toggleAll,
763
+ remove,
764
+ updateTags,
765
+ isMutating: list.isMutating
766
+ };
767
+ const body = render ? render(state) : typeof children === "function" ? children(state) : children ?? /* @__PURE__ */ jsxs2(Fragment2, { children: [
768
+ /* @__PURE__ */ jsxs2("fieldset", { children: [
769
+ /* @__PURE__ */ jsx4("legend", { children: selectItemsLabel }),
770
+ /* @__PURE__ */ jsxs2("label", { children: [
771
+ /* @__PURE__ */ jsx4("input", { type: "checkbox", checked: allSelected, onChange: toggleAll, "aria-label": selectItemsLabel }),
686
772
  selectedIds.length,
687
773
  " ",
688
774
  selectedCountLabel
689
775
  ] }),
690
- list.items.map((item) => /* @__PURE__ */ jsxs("label", { children: [
691
- /* @__PURE__ */ jsx("input", { type: "checkbox", checked: selected.has(item.id), onChange: () => toggle(item.id) }),
776
+ list.items.map((item) => /* @__PURE__ */ jsxs2("span", { children: [
777
+ /* @__PURE__ */ jsx4(
778
+ KeepItemCheckbox,
779
+ {
780
+ item,
781
+ checked: selected.has(item.id),
782
+ onCheckedChange: () => toggle(item.id)
783
+ }
784
+ ),
692
785
  renderItem ? renderItem(item, selected.has(item.id)) : getMetaTitle(item.meta) ?? item.id
693
786
  ] }, item.id))
694
787
  ] }),
695
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void remove(), disabled: selectedIds.length === 0 || list.isMutating, children: deleteSelectedLabel }),
696
- /* @__PURE__ */ jsxs("label", { children: [
788
+ /* @__PURE__ */ jsx4("button", { type: "button", onClick: () => void remove(), disabled: selectedIds.length === 0 || list.isMutating, children: deleteSelectedLabel }),
789
+ /* @__PURE__ */ jsxs2("label", { children: [
697
790
  tagsLabel,
698
- /* @__PURE__ */ jsx("input", { value: tagsInput, onChange: (event) => setTagsInput(event.currentTarget.value) })
791
+ /* @__PURE__ */ jsx4("input", { value: tagsInput, onChange: (event) => setTagsInput(event.currentTarget.value) })
699
792
  ] }),
700
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void updateTags(), disabled: selectedIds.length === 0 || list.isMutating, children: applyTagsLabel })
793
+ /* @__PURE__ */ jsx4(
794
+ "button",
795
+ {
796
+ type: "button",
797
+ onClick: () => void updateTags(),
798
+ disabled: selectedIds.length === 0 || list.isMutating,
799
+ children: applyTagsLabel
800
+ }
801
+ )
701
802
  ] });
803
+ return /* @__PURE__ */ jsx4("section", { ...props, "aria-busy": list.isMutating || props["aria-busy"], children: body });
702
804
  }
703
805
  function KeepEmptyState({
704
806
  title,
@@ -711,9 +813,9 @@ function KeepEmptyState({
711
813
  }) {
712
814
  const defaultTitle = useUiLabel("noItems").replace(/\.$/, "");
713
815
  const contentChildren = asChild && isValidElement(children) ? void 0 : children;
714
- const body = contentChildren ?? /* @__PURE__ */ jsxs(Fragment, { children: [
715
- /* @__PURE__ */ jsx("h2", { children: title ?? defaultTitle }),
716
- description ? /* @__PURE__ */ jsx("p", { children: description }) : null,
816
+ const body = contentChildren ?? /* @__PURE__ */ jsxs2(Fragment2, { children: [
817
+ /* @__PURE__ */ jsx4("h2", { children: title ?? defaultTitle }),
818
+ description ? /* @__PURE__ */ jsx4("p", { children: description }) : null,
717
819
  action
718
820
  ] });
719
821
  return renderRoot(asChild, children, { ...rootProps, className }, body, "KeepEmptyState");
@@ -752,9 +854,9 @@ function KeepAnnouncements({ messages, ...props }) {
752
854
  const savedMessage = useUiLabel("savedMessage", messages?.save);
753
855
  const removedMessage = useUiLabel("removedMessage", messages?.remove);
754
856
  const noteSavedMessage = useUiLabel("noteSavedMessage", messages?.note);
755
- const [message, setMessage] = useState("");
857
+ const [message, setMessage] = useState2("");
756
858
  const lastChangeRef = useRef(void 0);
757
- useEffect(() => {
859
+ useEffect2(() => {
758
860
  const change = context.lastChange;
759
861
  if (!change || change === lastChangeRef.current) return;
760
862
  lastChangeRef.current = change;
@@ -762,7 +864,7 @@ function KeepAnnouncements({ messages, ...props }) {
762
864
  else if (change.action === "remove" || change.action === "removeBatch") setMessage(removedMessage);
763
865
  else if (change.action === "updateNote") setMessage(noteSavedMessage);
764
866
  }, [context.lastChange, noteSavedMessage, removedMessage, savedMessage]);
765
- return /* @__PURE__ */ jsx("div", { ...props, role: props.role ?? "status", "aria-live": props["aria-live"] ?? "polite", "aria-atomic": "true", children: message });
867
+ return /* @__PURE__ */ jsx4("div", { ...props, role: props.role ?? "status", "aria-live": props["aria-live"] ?? "polite", "aria-atomic": "true", children: message });
766
868
  }
767
869
  function getDerivedStatus(context) {
768
870
  if (context.error) return "error";
@@ -808,7 +910,7 @@ function renderRoot(asChild, child, props, body, componentName) {
808
910
  if (!isValidElement(child)) throw new Error(`${componentName} with asChild requires a single React element child.`);
809
911
  return cloneElement(child, { ...props, children: body });
810
912
  }
811
- return /* @__PURE__ */ jsx("div", { ...props, children: body });
913
+ return /* @__PURE__ */ jsx4("div", { ...props, children: body });
812
914
  }
813
915
  export {
814
916
  FallbackStorageAdapter,
@@ -820,6 +922,7 @@ export {
820
922
  KeepCollection,
821
923
  KeepEmptyState,
822
924
  KeepItemCard,
925
+ KeepItemCheckbox,
823
926
  KeepKitProvider,
824
927
  KeepList,
825
928
  KeepNoteEditor,