@keepkit/ui 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -56,8 +56,9 @@ function SavedArticles() {
56
56
 
57
57
  `keep.Collection`は検索、ソート、ページング、loading / empty / error、ARIA live通知を標準で提供します。検索は既定で300msデバウンスされます。`features={{ tagFilter: true, bulkActions: true }}`でタグフィルターと一括操作も有効にできます。個別の`KeepList`、`KeepSearchInput`、`KeepSortSelect`、`KeepPagination`、`KeepItemCheckbox`、`KeepTagEditor`などは高度なレイアウト用に利用できます。`KeepBulkActions`はrender propsで操作UIを差し替えられ、`isAllSelected` / `toggleSelectAll`で表示中アイテムを一括操作できます。`KeepNoteEditor`は既定300msのデバウンス保存に対応し、`debounceMs={0}`でフォーム送信のみへ戻せます。通知領域だけを明示的に置く場合は`KeepAnnouncer`(`KeepAnnouncements`のalias)を使えます。
58
58
  `KeepItemCard`は`href`、`onOpen`、`linkTarget`、`linkComponent`に対応し、保存アイテムから詳細ページへ遷移できます。非公開・期限切れ等の`status`を持つアイテムは自動的にリンクを無効化します。`KeepBackup`はJSONのエクスポート、merge / replaceインポート、結果件数、容量エラー表示を提供します。
59
+ 外部URLの詳細リンクには`target="_blank"`と`rel="noreferrer"`が既定で補完され、利用できないカードには`aria-disabled="true"`と`data-item-status`が付与されます。同期競合ダイアログではローカルとリモートの更新日時・メモを並べて確認できます。
59
60
 
60
- v0.13.0では`KeepCollection urlSync layout="list" | "grid" | "compact"`、`KeepBulkActions selectionScope="page" | "query" | "all"`、`KeepUndo`を利用できます。`createNextPagesRouterAdapter`はNext.js Pages Router用の注入adapterです。`locale`は英語、日本語、韓国語、中国語簡体字・繁体字、タイ語、フランス語、スペイン語、ポルトガル語、イタリア語、ドイツ語、ロシア語、フィリピン語、ベトナム語、インドネシア語、マレー語の16言語に対応し、`labels`で上書きできます。認証付き同期は`createAuthenticatedSyncKit`から利用できます。
61
+ v0.14.0では`KeepCollection urlSync layout="list" | "grid" | "compact"`、`KeepBulkActions selectionScope="page" | "query" | "all"`、`KeepUndo`を利用できます。`createNextPagesRouterAdapter`はNext.js Pages Router用の注入adapterです。`locale`は英語、日本語、韓国語、中国語簡体字・繁体字、タイ語、フランス語、スペイン語、ポルトガル語、イタリア語、ドイツ語、ロシア語、フィリピン語、ベトナム語、インドネシア語、マレー語の16言語に対応し、`labels`で上書きできます。認証付き同期は`createAuthenticatedSyncKit`から利用できます。
61
62
  Phase 4の状態UIとして`KeepItemStatusBadge`、`KeepStaleNotice`、`KeepPruneStaleButton`、`KeepSyncStatusBanner`、`KeepSyncRecoveryDialog`を利用できます。`import "@keepkit/ui/theme.css"`でテーマCSSを有効にできます。
62
63
 
63
64
  ### Tailwind/shadcnテーマ
@@ -142,8 +143,9 @@ function SavedArticles() {
142
143
 
143
144
  `keep.Collection` includes search, sorting, pagination, loading/empty/error states, and polite live announcements. Search is debounced by 300ms by default. Enable `features={{ tagFilter: true, bulkActions: true }}` for tag filtering and bulk operations. Use the individual `KeepList`, `KeepSearchInput`, `KeepSortSelect`, `KeepPagination`, `KeepItemCheckbox`, and `KeepTagEditor` primitives when you need a custom layout. `KeepBulkActions` supports render props and exposes `isAllSelected` / `toggleSelectAll` for visible-item selection. `KeepNoteEditor` auto-saves dirty notes after 300ms by default; set `debounceMs={0}` to use form submission only. Mount `KeepAnnouncer` (`KeepAnnouncements` alias) when you need the live region explicitly.
144
145
  `KeepItemCard` accepts `href`, `onOpen`, `linkTarget`, and `linkComponent` for detail-page navigation. Links are disabled for unavailable `status` values such as private or expired. `KeepBackup` provides JSON export, merge/replace import, result counts, and quota-error messaging.
146
+ External detail URLs receive `target="_blank"` and `rel="noreferrer"` defaults. Unavailable cards expose `aria-disabled="true"` and normalized `data-item-status` values, and the sync recovery dialog compares local and remote updated dates and notes side by side.
145
147
 
146
- In v0.13.0, use `KeepCollection urlSync` with `layout="list" | "grid" | "compact"`, `KeepBulkActions selectionScope="page" | "query" | "all"`, and `KeepUndo`. `createNextPagesRouterAdapter` is the injected adapter for Next.js Pages Router. `locale` includes complete dictionaries for 16 built-in locales; `labels` overrides individual entries. Authenticated sync is available through `createAuthenticatedSyncKit`.
148
+ In v0.14.0, use `KeepCollection urlSync` with `layout="list" | "grid" | "compact"`, `KeepBulkActions selectionScope="page" | "query" | "all"`, and `KeepUndo`. `createNextPagesRouterAdapter` is the injected adapter for Next.js Pages Router. `locale` includes complete dictionaries for 16 built-in locales; `labels` overrides individual entries. Authenticated sync is available through `createAuthenticatedSyncKit`.
147
149
  Phase 4 adds `KeepItemStatusBadge`, `KeepStaleNotice`, `KeepPruneStaleButton`, `KeepSyncStatusBanner`, and `KeepSyncRecoveryDialog` for unavailable items, sync failures, conflict resolution, and backup recovery. Import `@keepkit/ui/theme.css` or `@keepkit/ui/tailwind.css` for the opt-in theme layer.
148
150
 
149
151
  ### Tailwind and shadcn theme
package/dist/index.d.ts CHANGED
@@ -211,9 +211,10 @@ type KeepItemCheckboxProps<TMeta = Record<string, unknown>> = Omit<InputHTMLAttr
211
211
  declare function KeepItemCheckbox<TMeta = Record<string, unknown>>({ item, checked, label, onCheckedChange, "aria-label": ariaLabel, ...props }: KeepItemCheckboxProps<TMeta>): react.JSX.Element;
212
212
 
213
213
  type KeepItemStatusBadgeProps = Omit<HTMLAttributes<HTMLSpanElement>, "children"> & {
214
- status?: KeepItemStatus;
214
+ status?: KeepItemStatus | "restricted";
215
215
  label?: ReactNode;
216
216
  };
217
+ type KeepDisplayStatus = "available" | "expired" | "removed" | "restricted";
217
218
  /** Displays the normalized availability state of a saved item. */
218
219
  declare function KeepItemStatusBadge({ status, label, className, ...props }: KeepItemStatusBadgeProps): react.JSX.Element;
219
220
 
@@ -221,8 +222,8 @@ type KeepLayoutProps = Omit<HTMLAttributes<HTMLElement>, "children"> & {
221
222
  layout?: KeepLayoutPreset;
222
223
  children?: ReactNode;
223
224
  };
224
- /** A CSS-free layout wrapper with responsive grid presets and data attributes for host styling. */
225
- declare function KeepLayout({ layout, children, style, ...props }: KeepLayoutProps): react.JSX.Element;
225
+ /** A style-free layout wrapper with a stable preset attribute for host or theme CSS. */
226
+ declare function KeepLayout({ layout, children, ...props }: KeepLayoutProps): react.JSX.Element;
226
227
 
227
228
  type KeepNoteEditorState<TMeta = Record<string, unknown>> = {
228
229
  item: KeepItem<TMeta>;
@@ -432,7 +433,7 @@ type KeepThemeProviderProps = {
432
433
  /** Scopes KeepKit tokens and opt-in component styles to one subtree. */
433
434
  declare function KeepThemeProvider({ children, theme, mode, density, radius, accentColor, highContrast, reducedMotion, variables, style, className, asChild, ...props }: KeepThemeProviderProps): react.JSX.Element;
434
435
 
435
- type KeepUiLabelKey = "save" | "saved" | "remove" | "loading" | "saving" | "syncing" | "error" | "allTags" | "tags" | "filterTags" | "note" | "saveNote" | "noItems" | "loadingItems" | "errorItems" | "search" | "sort" | "newest" | "oldest" | "savedNewest" | "savedOldest" | "updatedNewest" | "updatedOldest" | "previousPage" | "nextPage" | "pagination" | "page" | "selectItems" | "selectedCount" | "deleteSelected" | "tagsToApply" | "applyTags" | "savedMessage" | "removedMessage" | "undo" | "undoAvailable" | "selectionScope" | "currentPage" | "searchResults" | "allItems" | "statusAvailable" | "noteSavedMessage" | "exportData" | "importData" | "importMode" | "merge" | "replace" | "importedCount" | "failedCount" | "storageQuotaError" | "statusExpired" | "statusRemoved" | "statusDeleted" | "statusPrivate" | "statusUnknown" | "retry" | "removeFromList" | "pruneStale" | "retrySync" | "resolveSync" | "keepLocal" | "useServer" | "manualMerge" | "close" | "backupRecovery" | "backupRecoveryDescription" | "syncPending" | "syncSynced" | "syncConflict";
436
+ type KeepUiLabelKey = "save" | "saved" | "remove" | "loading" | "saving" | "syncing" | "error" | "allTags" | "tags" | "filterTags" | "note" | "saveNote" | "noItems" | "loadingItems" | "errorItems" | "search" | "sort" | "newest" | "oldest" | "savedNewest" | "savedOldest" | "updatedNewest" | "updatedOldest" | "previousPage" | "nextPage" | "pagination" | "page" | "selectItems" | "selectedCount" | "deleteSelected" | "tagsToApply" | "applyTags" | "savedMessage" | "removedMessage" | "undo" | "undoAvailable" | "selectionScope" | "currentPage" | "searchResults" | "allItems" | "localVersion" | "remoteVersion" | "updatedAt" | "statusAvailable" | "noteSavedMessage" | "exportData" | "importData" | "importMode" | "merge" | "replace" | "importedCount" | "failedCount" | "storageQuotaError" | "statusExpired" | "statusRemoved" | "statusDeleted" | "statusPrivate" | "statusUnknown" | "retry" | "removeFromList" | "pruneStale" | "retrySync" | "resolveSync" | "keepLocal" | "useServer" | "manualMerge" | "close" | "backupRecovery" | "backupRecoveryDescription" | "syncPending" | "syncSynced" | "syncConflict";
436
437
  type KeepUiLabels = Partial<Record<KeepUiLabelKey, string>>;
437
438
  type KeepUiLocaleLabels = Record<KeepUiLabelKey, string>;
438
439
 
@@ -491,4 +492,4 @@ type KeepKit<TMeta = Record<string, unknown>> = {
491
492
  /** Create one typed application API for the core and standard UI layer. */
492
493
  declare function createKeepKit<TMeta = Record<string, unknown>>(options?: CreateKeepKitOptions<TMeta>): KeepKit<TMeta>;
493
494
 
494
- export { type CreateKeepKitOptions, KeepAnnouncements, type KeepAnnouncementsProps, KeepAnnouncer, KeepBackup, type KeepBackupProps, KeepBulkActions, type KeepBulkActionsProps, type KeepBulkActionsState, KeepButton, type KeepButtonIcon, type KeepButtonIconProps, type KeepButtonIcons, type KeepButtonLabels, type KeepButtonProps, KeepCollection, type KeepCollectionFeature, type KeepCollectionProps, KeepEmptyState, type KeepEmptyStateProps, type KeepImageProps, KeepItemCard, type KeepItemCardLinkProps, type KeepItemCardProps, type KeepItemCardState, KeepItemCheckbox, type KeepItemCheckboxProps, KeepItemStatusBadge, type KeepItemStatusBadgeProps, type KeepKit, KeepKitProvider, type KeepKitProviderProps, KeepLayout, type KeepLayoutPreset, type KeepLayoutProps, KeepList, type KeepListProps, type KeepListState, KeepNoteEditor, type KeepNoteEditorProps, type KeepNoteEditorState, type KeepPagesRouterLike, KeepPagination, type KeepPaginationProps, type KeepPaginationState, KeepPruneStaleButton, type KeepPruneStaleButtonProps, KeepSearchInput, type KeepSearchInputProps, type KeepSelectionScope, KeepSortSelect, type KeepSortSelectProps, type KeepSortValue, KeepStaleNotice, type KeepStaleNoticeProps, KeepStatus, type KeepStatusLabels, type KeepStatusProps, type KeepStatusState, type KeepStatusValue, KeepSyncRecoveryDialog, type KeepSyncRecoveryDialogProps, KeepSyncStatusBanner, type KeepSyncStatusBannerProps, KeepTagEditor, type KeepTagEditorProps, type KeepTagEditorState, KeepTagFilter, type KeepTagFilterProps, type KeepTagFilterState, type KeepThemeDensity, type KeepThemeMode, type KeepThemeName, KeepThemeProvider, type KeepThemeProviderProps, type KeepThemeRadius, type KeepThemeVariables, type KeepUiLabelContext, type KeepUiLabelKey, type KeepUiLabels, type KeepUiLocale, type KeepUiLocaleLabels, KeepUiProvider, type KeepUiProviderProps, KeepUndo, type KeepUndoProps, type KeepUrlAdapter, type RenderProp, createKeepKit, createNextPagesRouterAdapter, getKeepLocaleLabels, isAllSelected, toggleSelectAll, useKeepUiLabels, useKeepUrlSync };
495
+ export { type CreateKeepKitOptions, KeepAnnouncements, type KeepAnnouncementsProps, KeepAnnouncer, KeepBackup, type KeepBackupProps, KeepBulkActions, type KeepBulkActionsProps, type KeepBulkActionsState, KeepButton, type KeepButtonIcon, type KeepButtonIconProps, type KeepButtonIcons, type KeepButtonLabels, type KeepButtonProps, KeepCollection, type KeepCollectionFeature, type KeepCollectionProps, type KeepDisplayStatus, KeepEmptyState, type KeepEmptyStateProps, type KeepImageProps, KeepItemCard, type KeepItemCardLinkProps, type KeepItemCardProps, type KeepItemCardState, KeepItemCheckbox, type KeepItemCheckboxProps, KeepItemStatusBadge, type KeepItemStatusBadgeProps, type KeepKit, KeepKitProvider, type KeepKitProviderProps, KeepLayout, type KeepLayoutPreset, type KeepLayoutProps, KeepList, type KeepListProps, type KeepListState, KeepNoteEditor, type KeepNoteEditorProps, type KeepNoteEditorState, type KeepPagesRouterLike, KeepPagination, type KeepPaginationProps, type KeepPaginationState, KeepPruneStaleButton, type KeepPruneStaleButtonProps, KeepSearchInput, type KeepSearchInputProps, type KeepSelectionScope, KeepSortSelect, type KeepSortSelectProps, type KeepSortValue, KeepStaleNotice, type KeepStaleNoticeProps, KeepStatus, type KeepStatusLabels, type KeepStatusProps, type KeepStatusState, type KeepStatusValue, KeepSyncRecoveryDialog, type KeepSyncRecoveryDialogProps, KeepSyncStatusBanner, type KeepSyncStatusBannerProps, KeepTagEditor, type KeepTagEditorProps, type KeepTagEditorState, KeepTagFilter, type KeepTagFilterProps, type KeepTagFilterState, type KeepThemeDensity, type KeepThemeMode, type KeepThemeName, KeepThemeProvider, type KeepThemeProviderProps, type KeepThemeRadius, type KeepThemeVariables, type KeepUiLabelContext, type KeepUiLabelKey, type KeepUiLabels, type KeepUiLocale, type KeepUiLocaleLabels, KeepUiProvider, type KeepUiProviderProps, KeepUndo, type KeepUndoProps, type KeepUrlAdapter, type RenderProp, createKeepKit, createNextPagesRouterAdapter, getKeepLocaleLabels, isAllSelected, toggleSelectAll, useKeepUiLabels, useKeepUrlSync };
package/dist/index.js CHANGED
@@ -55,6 +55,9 @@ var DE_LABELS = {
55
55
  currentPage: "Aktuelle Seite",
56
56
  searchResults: "Aktuelle Suchergebnisse",
57
57
  allItems: "Alle gespeicherten Elemente",
58
+ localVersion: "Lokal",
59
+ remoteVersion: "Server",
60
+ updatedAt: "Aktualisiert",
58
61
  statusAvailable: "Verf\xFCgbar",
59
62
  noteSavedMessage: "Notiz gespeichert.",
60
63
  exportData: "JSON exportieren",
@@ -128,6 +131,9 @@ var EN_LABELS = {
128
131
  currentPage: "Current page",
129
132
  searchResults: "Current search results",
130
133
  allItems: "All saved items",
134
+ localVersion: "Local",
135
+ remoteVersion: "Remote",
136
+ updatedAt: "Updated",
131
137
  statusAvailable: "Available",
132
138
  noteSavedMessage: "Note saved.",
133
139
  exportData: "Export JSON",
@@ -201,6 +207,9 @@ var ES_LABELS = {
201
207
  currentPage: "P\xE1gina actual",
202
208
  searchResults: "Resultados de b\xFAsqueda actuales",
203
209
  allItems: "Todos los elementos guardados",
210
+ localVersion: "Local",
211
+ remoteVersion: "Servidor",
212
+ updatedAt: "Actualizado",
204
213
  statusAvailable: "Disponible",
205
214
  noteSavedMessage: "Nota guardada.",
206
215
  exportData: "Exportar JSON",
@@ -274,6 +283,9 @@ var FIL_LABELS = {
274
283
  currentPage: "Kasalukuyang pahina",
275
284
  searchResults: "Kasalukuyang resulta ng paghahanap",
276
285
  allItems: "Lahat ng naka-save na item",
286
+ localVersion: "Lokal",
287
+ remoteVersion: "Remote",
288
+ updatedAt: "Na-update",
277
289
  statusAvailable: "Available",
278
290
  noteSavedMessage: "Na-save ang tala.",
279
291
  exportData: "I-export ang JSON",
@@ -347,6 +359,9 @@ var FR_LABELS = {
347
359
  currentPage: "Page actuelle",
348
360
  searchResults: "R\xE9sultats de recherche actuels",
349
361
  allItems: "Tous les \xE9l\xE9ments enregistr\xE9s",
362
+ localVersion: "Local",
363
+ remoteVersion: "Distant",
364
+ updatedAt: "Mis \xE0 jour",
350
365
  statusAvailable: "Disponible",
351
366
  noteSavedMessage: "Note enregistr\xE9e.",
352
367
  exportData: "Exporter JSON",
@@ -420,6 +435,9 @@ var ID_LABELS = {
420
435
  currentPage: "Halaman saat ini",
421
436
  searchResults: "Hasil pencarian saat ini",
422
437
  allItems: "Semua item tersimpan",
438
+ localVersion: "Lokal",
439
+ remoteVersion: "Server",
440
+ updatedAt: "Diperbarui",
423
441
  statusAvailable: "Tersedia",
424
442
  noteSavedMessage: "Catatan tersimpan.",
425
443
  exportData: "Ekspor JSON",
@@ -493,6 +511,9 @@ var IT_LABELS = {
493
511
  currentPage: "Pagina corrente",
494
512
  searchResults: "Risultati di ricerca correnti",
495
513
  allItems: "Tutti gli elementi salvati",
514
+ localVersion: "Locale",
515
+ remoteVersion: "Server",
516
+ updatedAt: "Aggiornato",
496
517
  statusAvailable: "Disponibile",
497
518
  noteSavedMessage: "Nota salvata.",
498
519
  exportData: "Esporta JSON",
@@ -566,6 +587,9 @@ var JA_LABELS = {
566
587
  currentPage: "\u8868\u793A\u4E2D\u306E\u30DA\u30FC\u30B8",
567
588
  searchResults: "\u73FE\u5728\u306E\u691C\u7D22\u7D50\u679C\u5168\u4F53",
568
589
  allItems: "\u3059\u3079\u3066\u306E\u4FDD\u5B58\u30A2\u30A4\u30C6\u30E0",
590
+ localVersion: "\u30ED\u30FC\u30AB\u30EB",
591
+ remoteVersion: "\u30EA\u30E2\u30FC\u30C8",
592
+ updatedAt: "\u66F4\u65B0\u65E5\u6642",
569
593
  statusAvailable: "\u5229\u7528\u53EF\u80FD",
570
594
  noteSavedMessage: "\u30E1\u30E2\u3092\u4FDD\u5B58\u3057\u307E\u3057\u305F\u3002",
571
595
  exportData: "JSON\u3092\u30A8\u30AF\u30B9\u30DD\u30FC\u30C8",
@@ -639,6 +663,9 @@ var KO_LABELS = {
639
663
  currentPage: "\uD604\uC7AC \uD398\uC774\uC9C0",
640
664
  searchResults: "\uD604\uC7AC \uAC80\uC0C9 \uACB0\uACFC \uC804\uCCB4",
641
665
  allItems: "\uBAA8\uB4E0 \uC800\uC7A5 \uD56D\uBAA9",
666
+ localVersion: "\uB85C\uCEEC",
667
+ remoteVersion: "\uC6D0\uACA9",
668
+ updatedAt: "\uC5C5\uB370\uC774\uD2B8 \uC2DC\uAC04",
642
669
  statusAvailable: "\uC0AC\uC6A9 \uAC00\uB2A5",
643
670
  noteSavedMessage: "\uBA54\uBAA8\uB97C \uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4.",
644
671
  exportData: "JSON \uB0B4\uBCF4\uB0B4\uAE30",
@@ -712,6 +739,9 @@ var MS_LABELS = {
712
739
  currentPage: "Halaman semasa",
713
740
  searchResults: "Hasil carian semasa",
714
741
  allItems: "Semua item yang disimpan",
742
+ localVersion: "Tempatan",
743
+ remoteVersion: "Pelayan",
744
+ updatedAt: "Dikemas kini",
715
745
  statusAvailable: "Tersedia",
716
746
  noteSavedMessage: "Nota disimpan.",
717
747
  exportData: "Eksport JSON",
@@ -785,6 +815,9 @@ var PT_BR_LABELS = {
785
815
  currentPage: "P\xE1gina atual",
786
816
  searchResults: "Resultados da pesquisa atual",
787
817
  allItems: "Todos os itens salvos",
818
+ localVersion: "Local",
819
+ remoteVersion: "Servidor",
820
+ updatedAt: "Atualizado",
788
821
  statusAvailable: "Dispon\xEDvel",
789
822
  noteSavedMessage: "Nota salva.",
790
823
  exportData: "Exportar JSON",
@@ -858,6 +891,9 @@ var RU_LABELS = {
858
891
  currentPage: "\u0422\u0435\u043A\u0443\u0449\u0430\u044F \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0430",
859
892
  searchResults: "\u0422\u0435\u043A\u0443\u0449\u0438\u0435 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u044B \u043F\u043E\u0438\u0441\u043A\u0430",
860
893
  allItems: "\u0412\u0441\u0435 \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D\u043D\u044B\u0435 \u044D\u043B\u0435\u043C\u0435\u043D\u0442\u044B",
894
+ localVersion: "\u041B\u043E\u043A\u0430\u043B\u044C\u043D\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F",
895
+ remoteVersion: "\u0421\u0435\u0440\u0432\u0435\u0440\u043D\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F",
896
+ updatedAt: "\u041E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u043E",
861
897
  statusAvailable: "\u0414\u043E\u0441\u0442\u0443\u043F\u0435\u043D",
862
898
  noteSavedMessage: "\u0417\u0430\u043C\u0435\u0442\u043A\u0430 \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0430.",
863
899
  exportData: "\u042D\u043A\u0441\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u0442\u044C JSON",
@@ -931,6 +967,9 @@ var TH_LABELS = {
931
967
  currentPage: "\u0E2B\u0E19\u0E49\u0E32\u0E1B\u0E31\u0E08\u0E08\u0E38\u0E1A\u0E31\u0E19",
932
968
  searchResults: "\u0E1C\u0E25\u0E01\u0E32\u0E23\u0E04\u0E49\u0E19\u0E2B\u0E32\u0E1B\u0E31\u0E08\u0E08\u0E38\u0E1A\u0E31\u0E19",
933
969
  allItems: "\u0E23\u0E32\u0E22\u0E01\u0E32\u0E23\u0E17\u0E35\u0E48\u0E1A\u0E31\u0E19\u0E17\u0E36\u0E01\u0E17\u0E31\u0E49\u0E07\u0E2B\u0E21\u0E14",
970
+ localVersion: "\u0E43\u0E19\u0E40\u0E04\u0E23\u0E37\u0E48\u0E2D\u0E07",
971
+ remoteVersion: "\u0E23\u0E30\u0E22\u0E30\u0E44\u0E01\u0E25",
972
+ updatedAt: "\u0E2D\u0E31\u0E1B\u0E40\u0E14\u0E15\u0E40\u0E21\u0E37\u0E48\u0E2D",
934
973
  statusAvailable: "\u0E1E\u0E23\u0E49\u0E2D\u0E21\u0E43\u0E0A\u0E49\u0E07\u0E32\u0E19",
935
974
  noteSavedMessage: "\u0E1A\u0E31\u0E19\u0E17\u0E36\u0E01\u0E22\u0E48\u0E2D\u0E41\u0E25\u0E49\u0E27",
936
975
  exportData: "\u0E2A\u0E48\u0E07\u0E2D\u0E2D\u0E01 JSON",
@@ -1004,6 +1043,9 @@ var VI_LABELS = {
1004
1043
  currentPage: "Trang hi\u1EC7n t\u1EA1i",
1005
1044
  searchResults: "K\u1EBFt qu\u1EA3 t\xECm ki\u1EBFm hi\u1EC7n t\u1EA1i",
1006
1045
  allItems: "T\u1EA5t c\u1EA3 m\u1EE5c \u0111\xE3 l\u01B0u",
1046
+ localVersion: "C\u1EE5c b\u1ED9",
1047
+ remoteVersion: "M\xE1y ch\u1EE7",
1048
+ updatedAt: "\u0110\xE3 c\u1EADp nh\u1EADt",
1007
1049
  statusAvailable: "Kh\u1EA3 d\u1EE5ng",
1008
1050
  noteSavedMessage: "\u0110\xE3 l\u01B0u ghi ch\xFA.",
1009
1051
  exportData: "Xu\u1EA5t JSON",
@@ -1077,6 +1119,9 @@ var ZH_HANS_LABELS = {
1077
1119
  currentPage: "\u5F53\u524D\u9875",
1078
1120
  searchResults: "\u5F53\u524D\u5168\u90E8\u641C\u7D22\u7ED3\u679C",
1079
1121
  allItems: "\u5168\u90E8\u5DF2\u4FDD\u5B58\u9879\u76EE",
1122
+ localVersion: "\u672C\u5730",
1123
+ remoteVersion: "\u8FDC\u7A0B",
1124
+ updatedAt: "\u66F4\u65B0\u65F6\u95F4",
1080
1125
  statusAvailable: "\u53EF\u7528",
1081
1126
  noteSavedMessage: "\u5907\u6CE8\u5DF2\u4FDD\u5B58\u3002",
1082
1127
  exportData: "\u5BFC\u51FAJSON",
@@ -1150,6 +1195,9 @@ var ZH_HANT_LABELS = {
1150
1195
  currentPage: "\u76EE\u524D\u9801\u9762",
1151
1196
  searchResults: "\u76EE\u524D\u5168\u90E8\u641C\u5C0B\u7D50\u679C",
1152
1197
  allItems: "\u6240\u6709\u5DF2\u5132\u5B58\u9805\u76EE",
1198
+ localVersion: "\u672C\u6A5F",
1199
+ remoteVersion: "\u9060\u7AEF",
1200
+ updatedAt: "\u66F4\u65B0\u6642\u9593",
1153
1201
  statusAvailable: "\u53EF\u7528",
1154
1202
  noteSavedMessage: "\u5099\u8A3B\u5DF2\u5132\u5B58\u3002",
1155
1203
  exportData: "\u532F\u51FAJSON",
@@ -1661,9 +1709,19 @@ import { useState as useState3 } from "react";
1661
1709
  // src/KeepItemStatusBadge.tsx
1662
1710
  import { jsx as jsx7 } from "react/jsx-runtime";
1663
1711
  function KeepItemStatusBadge({ status = "available", label, className, ...props }) {
1664
- const resolvedStatus = status === "available" ? "available" : status;
1665
- const statusLabel = useUiLabel(getStatusLabelKey(resolvedStatus));
1666
- return /* @__PURE__ */ jsx7("span", { ...props, className, "data-keepkit": "status-badge", "data-status": resolvedStatus, children: label ?? statusLabel });
1712
+ const resolvedStatus = getDisplayStatus(status);
1713
+ const statusLabel = useUiLabel(getStatusLabelKey(status));
1714
+ return /* @__PURE__ */ jsx7(
1715
+ "span",
1716
+ {
1717
+ ...props,
1718
+ className,
1719
+ "data-keepkit": "status-badge",
1720
+ "data-status": status,
1721
+ "data-item-status": resolvedStatus,
1722
+ children: label ?? statusLabel
1723
+ }
1724
+ );
1667
1725
  }
1668
1726
  function getStatusLabelKey(status) {
1669
1727
  switch (status) {
@@ -1676,10 +1734,19 @@ function getStatusLabelKey(status) {
1676
1734
  case "deleted":
1677
1735
  return "statusDeleted";
1678
1736
  case "private":
1737
+ return "statusPrivate";
1679
1738
  case "unknown":
1680
- return status === "private" ? "statusPrivate" : "statusUnknown";
1739
+ return "statusUnknown";
1740
+ case "restricted":
1741
+ return "statusPrivate";
1681
1742
  }
1682
1743
  }
1744
+ function getDisplayStatus(status) {
1745
+ if (status === "available") return "available";
1746
+ if (status === "expired") return "expired";
1747
+ if (status === "removed") return "removed";
1748
+ return "restricted";
1749
+ }
1683
1750
 
1684
1751
  // src/KeepStaleNotice.tsx
1685
1752
  import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
@@ -1810,6 +1877,10 @@ function KeepItemCard({
1810
1877
  const imageProps = getImageProps?.(item, resolvedTitle);
1811
1878
  const href = typeof hrefOption === "function" ? hrefOption(item) : hrefOption;
1812
1879
  const isAvailable = item.status === void 0 || item.status === "available";
1880
+ const displayStatus = getDisplayStatus2(item.status);
1881
+ const isExternalLink = href ? isExternalHref(href) : false;
1882
+ const resolvedLinkTarget = linkTargetAttribute ?? (isExternalLink ? "_blank" : void 0);
1883
+ const resolvedLinkRel = linkRel ?? (isExternalLink ? "noreferrer" : void 0);
1813
1884
  const statusLabelKey = item.status && item.status !== "available" ? getStatusLabelKey2(item.status) : "statusUnknown";
1814
1885
  const unavailableLabel = useUiLabel(statusLabelKey);
1815
1886
  const tagsLabel = useUiLabel("tags");
@@ -1818,8 +1889,8 @@ function KeepItemCard({
1818
1889
  if (!href || !isAvailable) return content;
1819
1890
  const linkProps = {
1820
1891
  href,
1821
- target: linkTargetAttribute,
1822
- rel: linkRel,
1892
+ target: resolvedLinkTarget,
1893
+ rel: resolvedLinkRel,
1823
1894
  onClick: (event) => onOpen?.(item, event),
1824
1895
  children: content
1825
1896
  };
@@ -1835,7 +1906,7 @@ function KeepItemCard({
1835
1906
  }
1836
1907
  const body = render ? render(state) : typeof contentChildren === "function" ? contentChildren(state) : contentChildren ?? /* @__PURE__ */ jsxs5(Fragment3, { children: [
1837
1908
  imageProps ? renderImage?.({ ...imageProps, alt: imageAlt ?? imageProps.alt }, item) ?? (ImageComponent ? /* @__PURE__ */ jsx9(ImageComponent, { ...imageProps, alt: imageAlt ?? imageProps.alt }) : /* @__PURE__ */ jsx9("img", { ...imageProps, alt: imageAlt ?? imageProps.alt })) : null,
1838
- /* @__PURE__ */ jsx9("h3", { children: linkTarget === "title" ? renderLink(resolvedTitle) : resolvedTitle }),
1909
+ /* @__PURE__ */ jsx9("h3", { children: linkTarget === "title" ? isAvailable ? renderLink(resolvedTitle) : href ? /* @__PURE__ */ jsx9("span", { "aria-disabled": "true", "data-link-disabled": "true", children: resolvedTitle }) : resolvedTitle : resolvedTitle }),
1839
1910
  showSavedAt ? /* @__PURE__ */ jsxs5("div", { "data-card-meta": true, children: [
1840
1911
  /* @__PURE__ */ jsxs5("span", { children: [
1841
1912
  savedAtLabel,
@@ -1857,7 +1928,7 @@ function KeepItemCard({
1857
1928
  ) : null,
1858
1929
  !statusLabel ? /* @__PURE__ */ jsx9("button", { type: "button", onClick: () => void handleRemove(), disabled: itemState.isMutating, children: removeLabel ?? removeActionLabel }) : null
1859
1930
  ] });
1860
- const linkedBody = linkTarget === "card" ? renderLink(body) : body;
1931
+ const linkedBody = linkTarget === "card" && isAvailable ? renderLink(body) : body;
1861
1932
  return renderRoot(
1862
1933
  asChild,
1863
1934
  isValidElement2(children) ? children : void 0,
@@ -1866,8 +1937,10 @@ function KeepItemCard({
1866
1937
  className,
1867
1938
  "data-keepkit": "card",
1868
1939
  "aria-busy": itemState.isMutating || rootProps["aria-busy"],
1940
+ "aria-disabled": rootProps["aria-disabled"] ?? (!isAvailable ? "true" : void 0),
1869
1941
  "data-state": itemState.error ? "error" : itemState.isSaved ? "saved" : "unsaved",
1870
1942
  "data-status": item.status ?? "available",
1943
+ "data-item-status": displayStatus,
1871
1944
  "data-loading": itemState.isMutating ? "true" : void 0
1872
1945
  },
1873
1946
  linkedBody,
@@ -1888,6 +1961,15 @@ function getStatusLabelKey2(status) {
1888
1961
  return "statusUnknown";
1889
1962
  }
1890
1963
  }
1964
+ function getDisplayStatus2(status) {
1965
+ if (status === void 0 || status === "available") return "available";
1966
+ if (status === "expired") return "expired";
1967
+ if (status === "removed") return "removed";
1968
+ return "restricted";
1969
+ }
1970
+ function isExternalHref(href) {
1971
+ return /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href);
1972
+ }
1891
1973
  function formatSavedAt(timestamp) {
1892
1974
  return new Date(timestamp).toISOString().slice(0, 10);
1893
1975
  }
@@ -1936,6 +2018,7 @@ function KeepListContent({
1936
2018
  ...rootProps,
1937
2019
  className,
1938
2020
  "data-keepkit": "list",
2021
+ "data-layout": layout,
1939
2022
  "aria-busy": state.isLoading || rootProps["aria-busy"],
1940
2023
  "data-state": getListState(state),
1941
2024
  "data-loading": state.isLoading ? "true" : void 0
@@ -1956,16 +2039,9 @@ function getListBody(state, options) {
1956
2039
  if (state.isHydrated && state.items.length === 0) return resolveContent(options.empty, state);
1957
2040
  if (typeof options.children === "function") return options.children(state);
1958
2041
  if (options.children !== void 0 && !isValidElement3(options.children)) return options.children;
1959
- return /* @__PURE__ */ jsx10(
1960
- "ul",
1961
- {
1962
- "data-layout": options.layout,
1963
- style: options.layout === "grid" ? { display: "grid", gap: "1rem", gridTemplateColumns: "repeat(auto-fit, minmax(16rem, 1fr))" } : options.layout === "compact" ? { display: "grid", gap: "0.5rem", gridTemplateColumns: "repeat(auto-fit, minmax(12rem, 1fr))" } : { display: "flex", flexDirection: "column", gap: "1rem" },
1964
- children: state.items.map(
1965
- (item) => options.renderItem ? options.renderItem(item, state) : /* @__PURE__ */ jsx10("li", { children: /* @__PURE__ */ jsx10(KeepItemCard, { item, ...options.itemCardProps }) }, item.id)
1966
- )
1967
- }
1968
- );
2042
+ return /* @__PURE__ */ jsx10("ul", { "data-layout": options.layout, children: state.items.map(
2043
+ (item) => options.renderItem ? options.renderItem(item, state) : /* @__PURE__ */ jsx10("li", { children: /* @__PURE__ */ jsx10(KeepItemCard, { item, ...options.itemCardProps }) }, item.id)
2044
+ ) });
1969
2045
  }
1970
2046
 
1971
2047
  // src/KeepTagFilter.tsx
@@ -2315,6 +2391,7 @@ function KeepCollectionContent({
2315
2391
  ...rootProps,
2316
2392
  className,
2317
2393
  "data-keepkit": "collection",
2394
+ "data-layout": layout,
2318
2395
  "aria-busy": list.isLoading || list.isMutating || rootProps["aria-busy"],
2319
2396
  "data-state": getCollectionState(list),
2320
2397
  "data-loading": list.isLoading || list.isMutating ? "true" : void 0,
@@ -2387,9 +2464,8 @@ function getCollectionState(list) {
2387
2464
 
2388
2465
  // src/KeepLayout.tsx
2389
2466
  import { jsx as jsx14 } from "react/jsx-runtime";
2390
- function KeepLayout({ layout = "list", children, style, ...props }) {
2391
- const layoutStyle = layout === "grid" ? { display: "grid", gap: "1rem", gridTemplateColumns: "repeat(auto-fit, minmax(16rem, 1fr))" } : layout === "compact" ? { display: "grid", gap: "0.5rem", gridTemplateColumns: "repeat(auto-fit, minmax(12rem, 1fr))" } : { display: "flex", flexDirection: "column", gap: "1rem" };
2392
- return /* @__PURE__ */ jsx14("div", { ...props, style: { ...layoutStyle, ...style }, "data-keepkit": "layout", "data-layout": layout, children });
2467
+ function KeepLayout({ layout = "list", children, ...props }) {
2468
+ return /* @__PURE__ */ jsx14("div", { ...props, "data-keepkit": "layout", "data-layout": layout, children });
2393
2469
  }
2394
2470
 
2395
2471
  // src/KeepNoteEditor.tsx
@@ -2541,6 +2617,10 @@ function KeepSyncRecoveryDialog({
2541
2617
  const keepLocalLabel = useUiLabel("keepLocal");
2542
2618
  const useServerLabel = useUiLabel("useServer");
2543
2619
  const manualMergeLabel = useUiLabel("manualMerge");
2620
+ const localVersionLabel = useUiLabel("localVersion");
2621
+ const remoteVersionLabel = useUiLabel("remoteVersion");
2622
+ const updatedAtLabel = useUiLabel("updatedAt");
2623
+ const noteLabel = useUiLabel("note");
2544
2624
  const backupRecoveryLabel = useUiLabel("backupRecovery");
2545
2625
  const backupRecoveryDescription = useUiLabel("backupRecoveryDescription");
2546
2626
  const errorLabel = useUiLabel("error");
@@ -2579,6 +2659,8 @@ function KeepSyncRecoveryDialog({
2579
2659
  role: "dialog",
2580
2660
  "aria-modal": "true",
2581
2661
  "aria-labelledby": "keepkit-sync-recovery-title",
2662
+ "aria-describedby": error ? "keepkit-sync-recovery-error" : void 0,
2663
+ "aria-busy": busyId !== void 0,
2582
2664
  "data-keepkit": "sync-recovery",
2583
2665
  "data-state": conflictList.length > 0 ? "conflict" : "error",
2584
2666
  "data-loading": busyId !== void 0 ? "true" : void 0,
@@ -2592,6 +2674,28 @@ function KeepSyncRecoveryDialog({
2592
2674
  /* @__PURE__ */ jsx16("p", { children: conflictLabel }),
2593
2675
  conflictList.map((conflict) => /* @__PURE__ */ jsxs10("article", { "data-conflict-id": conflict.id, children: [
2594
2676
  /* @__PURE__ */ jsx16("h3", { children: getMetaTitle(conflict.operation.item?.meta) ?? conflict.id }),
2677
+ /* @__PURE__ */ jsxs10("div", { "data-conflict-preview": true, children: [
2678
+ /* @__PURE__ */ jsx16(
2679
+ ConflictPreview,
2680
+ {
2681
+ item: conflict.operation.item,
2682
+ heading: localVersionLabel,
2683
+ updatedAtLabel,
2684
+ noteLabel,
2685
+ side: "local"
2686
+ }
2687
+ ),
2688
+ /* @__PURE__ */ jsx16(
2689
+ ConflictPreview,
2690
+ {
2691
+ item: conflict.remote,
2692
+ heading: remoteVersionLabel,
2693
+ updatedAtLabel,
2694
+ noteLabel,
2695
+ side: "remote"
2696
+ }
2697
+ )
2698
+ ] }),
2595
2699
  /* @__PURE__ */ jsxs10("div", { children: [
2596
2700
  /* @__PURE__ */ jsx16("button", { type: "button", onClick: () => void resolve(conflict, "local"), disabled: busyId !== void 0, children: keepLocalLabel }),
2597
2701
  /* @__PURE__ */ jsx16("button", { type: "button", onClick: () => void resolve(conflict, "remote"), disabled: busyId !== void 0, children: useServerLabel }),
@@ -2612,11 +2716,35 @@ function KeepSyncRecoveryDialog({
2612
2716
  /* @__PURE__ */ jsx16("p", { children: backupRecoveryDescription }),
2613
2717
  backup ?? (showBackupControls ? /* @__PURE__ */ jsx16(KeepBackup, {}) : null)
2614
2718
  ] }) : null,
2615
- error ? /* @__PURE__ */ jsx16("p", { role: "alert", children: error instanceof Error ? error.message : errorLabel }) : null
2719
+ error ? /* @__PURE__ */ jsx16("p", { id: "keepkit-sync-recovery-error", role: "alert", "aria-live": "assertive", children: error instanceof Error ? error.message : errorLabel }) : null
2616
2720
  ]
2617
2721
  }
2618
2722
  );
2619
2723
  }
2724
+ function ConflictPreview({
2725
+ item,
2726
+ heading,
2727
+ updatedAtLabel,
2728
+ noteLabel,
2729
+ side
2730
+ }) {
2731
+ return /* @__PURE__ */ jsxs10("article", { "data-conflict-version": side, "aria-label": heading, children: [
2732
+ /* @__PURE__ */ jsx16("h4", { children: heading }),
2733
+ /* @__PURE__ */ jsxs10("dl", { children: [
2734
+ /* @__PURE__ */ jsxs10("div", { children: [
2735
+ /* @__PURE__ */ jsx16("dt", { children: updatedAtLabel }),
2736
+ /* @__PURE__ */ jsx16("dd", { children: item ? /* @__PURE__ */ jsx16("time", { dateTime: new Date(item.updatedAt).toISOString(), children: formatConflictDate(item.updatedAt) }) : "\u2014" })
2737
+ ] }),
2738
+ /* @__PURE__ */ jsxs10("div", { children: [
2739
+ /* @__PURE__ */ jsx16("dt", { children: noteLabel }),
2740
+ /* @__PURE__ */ jsx16("dd", { children: item?.note || "\u2014" })
2741
+ ] })
2742
+ ] })
2743
+ ] });
2744
+ }
2745
+ function formatConflictDate(timestamp) {
2746
+ return new Date(timestamp).toISOString().slice(0, 10);
2747
+ }
2620
2748
 
2621
2749
  // src/KeepSyncStatusBanner.tsx
2622
2750
  import { useKeepContext as useKeepContext4 } from "@keepkit/core/react";