@lapismd/design-core 0.1.0 → 0.2.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.
Files changed (124) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/LICENSE.md +3 -3
  3. package/README.md +21 -1
  4. package/package.json +34 -15
  5. package/spec/src/styling-and-themes.md +11 -9
  6. package/src/shared/ai/chat.css +25 -1
  7. package/src/shared/ai/chat.ts +12 -0
  8. package/src/shared/ai/composer-input/ComposerInput.svelte +3 -2
  9. package/src/shared/ai/context.svelte.ts +2 -0
  10. package/src/shared/ai/experimental/reaction-bar/ReactionBar.svelte +1 -1
  11. package/src/shared/ai/layout/Layout.svelte +116 -10
  12. package/src/shared/ai/message-list/MessageList.svelte +123 -43
  13. package/src/shared/ai/message-list/MessageListHistoryFixture.svelte +112 -0
  14. package/src/shared/ai/message-list/MessageListLegacyFixture.svelte +52 -0
  15. package/src/shared/ai/message-list/VirtualizedMessageRows.svelte +299 -0
  16. package/src/shared/ai/message-list/position.ts +7 -0
  17. package/src/shared/ai/new-messages.svelte.ts +24 -5
  18. package/src/shared/ai/stream-scroll.svelte.ts +135 -6
  19. package/src/shared/ai/tool-call-detail/ToolCallDetail.svelte +71 -0
  20. package/src/shared/ai/tool-call-detail/tool-call-detail.ts +209 -0
  21. package/src/shared/ai/tool-calls/ToolCalls.svelte +1 -1
  22. package/src/shared/ai/trigger-menu.ts +2 -3
  23. package/src/shared/diff/core/merge/actions.ts +4 -3
  24. package/src/shared/diff/core/merge/render-model.ts +14 -10
  25. package/src/shared/diff/file-diff/FileDiff.css +68 -1
  26. package/src/shared/diff/file-diff/FileDiff.svelte +27 -14
  27. package/src/shared/diff/file-diff/FileDiffComposer.svelte +98 -16
  28. package/src/shared/diff/file-diff/index.ts +2 -0
  29. package/src/shared/diff/file-diff/types.ts +13 -0
  30. package/src/shared/diff/file-listing/FileListingViewModeToggle.css +42 -5
  31. package/src/shared/diff/index.ts +2 -0
  32. package/src/shared/diff/merge-editor/MergeEditor.svelte +2 -7
  33. package/src/shared/filter/filter-query/filters.ts +2 -2
  34. package/src/shared/filter/filter-query/parser.ts +9 -8
  35. package/src/shared/filter/power-search/PowerSearch.svelte +1 -1
  36. package/src/shared/filter/search-filter-bar/SearchFilterBar.svelte +2 -2
  37. package/src/shared/filter/search-filter-bar/SearchFilterInput.svelte +1 -1
  38. package/src/shared/filter/search-filter-bar/SearchFilterPredicateEditor.svelte +1 -1
  39. package/src/shared/filter/search-filter-bar/search-filter-syntax.ts +1 -1
  40. package/src/shared/forms/autocomplete-input/AutocompleteInput.svelte +19 -7
  41. package/src/shared/forms/confirm-button/ConfirmButton.svelte +38 -0
  42. package/src/shared/forms/confirm-button/ConfirmButtonExample.svelte +14 -0
  43. package/src/shared/forms/confirm-button/index.ts +1 -0
  44. package/src/shared/forms/confirm-dialog/ConfirmDialog.css +21 -0
  45. package/src/shared/forms/confirm-dialog/ConfirmDialog.svelte +116 -0
  46. package/src/shared/forms/confirm-dialog/ConfirmDialogExample.svelte +17 -0
  47. package/src/shared/forms/confirm-dialog/index.ts +2 -0
  48. package/src/shared/forms/dialog-frame/DialogFrame.css +130 -0
  49. package/src/shared/forms/dialog-frame/DialogFrame.svelte +108 -0
  50. package/src/shared/forms/dialog-frame/DialogFrameExample.svelte +37 -0
  51. package/src/shared/forms/dialog-frame/index.ts +6 -0
  52. package/src/shared/forms/dialog.ts +8 -0
  53. package/src/shared/forms/index.ts +11 -0
  54. package/src/shared/shadcn/button/button.svelte +4 -0
  55. package/src/shared/shadcn/code-block/highlightRanges.ts +10 -6
  56. package/src/shared/shadcn/code-block/syntax/defineSyntaxTheme.ts +4 -2
  57. package/src/shared/shadcn/code-block/tokenizer.ts +3 -3
  58. package/src/shared/shadcn/command/command-dialog.svelte +1 -1
  59. package/src/shared/shadcn/command/command-group.svelte +3 -1
  60. package/src/shared/shadcn/command-view/command-view.svelte +1 -6
  61. package/src/shared/shadcn/dialog/dialog-content.svelte +3 -4
  62. package/src/shared/shadcn/dialog/dialog-portal.svelte +10 -2
  63. package/src/shared/shadcn/dropdown-menu/dropdown-menu-checkbox-item.svelte +0 -691
  64. package/src/shared/shadcn/dropdown-menu/dropdown-menu-content.svelte +691 -0
  65. package/src/shared/shadcn/dropdown-menu/dropdown-menu-radio-group.svelte +1 -1
  66. package/src/shared/shadcn/scroll-area/scroll-area.svelte +2 -0
  67. package/src/shared/shadcn/spinner/spinner.svelte +3 -3
  68. package/src/shared/shadcn/tooltip/tooltip-content.svelte +10 -4
  69. package/src/shared/shadcn/tooltip/tooltip.tokens.ts +4 -0
  70. package/src/shared/shell/app-shell/AppShell.css +112 -32
  71. package/src/shared/shell/app-shell/AppShell.demo.css +37 -0
  72. package/src/shared/shell/app-shell/AppShellBodyContent.svelte +5 -1
  73. package/src/shared/shell/app-shell/AppShellBodySidebar.svelte +5 -1
  74. package/src/shared/shell/app-shell/AppShellMain.svelte +12 -1
  75. package/src/shared/shell/app-shell/AppShellMobilePanelSelector.svelte +1 -1
  76. package/src/shared/shell/app-shell/AppShellRoot.svelte +27 -13
  77. package/src/shared/shell/app-shell/AppShellSidebar.svelte +121 -19
  78. package/src/shared/shell/app-shell/AppShellSidebarClose.svelte +17 -17
  79. package/src/shared/shell/app-shell/AppShellSidebarToggle.svelte +38 -58
  80. package/src/shared/shell/app-shell/AppShellSurfaceLayer.svelte +173 -0
  81. package/src/shared/shell/app-shell/AppShellSurfaceLayerBody.svelte +32 -0
  82. package/src/shared/shell/app-shell/AppShellSurfaceLayerHeader.svelte +26 -0
  83. package/src/shared/shell/app-shell/app-shell-context.svelte.ts +6 -3
  84. package/src/shared/shell/app-shell/app-shell-controller.svelte.ts +269 -11
  85. package/src/shared/shell/app-shell/app-shell-sidebar-context.svelte.ts +1 -0
  86. package/src/shared/shell/app-shell/examples/AppShellBodyDemo.svelte +4 -0
  87. package/src/shared/shell/app-shell/examples/AppShellCompleteDemo.svelte +1 -0
  88. package/src/shared/shell/app-shell/examples/AppShellConversationDemo.svelte +1 -0
  89. package/src/shared/shell/app-shell/examples/AppShellExamples.css +624 -0
  90. package/src/shared/shell/app-shell/examples/AppShellFilesSidebarDemo.svelte +1 -0
  91. package/src/shared/shell/app-shell/examples/AppShellMarkdownDocumentDemo.svelte +6 -1
  92. package/src/shared/shell/app-shell/examples/AppShellMarkdownFilesDemo.svelte +1 -0
  93. package/src/shared/shell/app-shell/examples/AppShellModeSwitchDemo.svelte +1 -0
  94. package/src/shared/shell/app-shell/examples/AppShellMultiPanelDemo.svelte +187 -0
  95. package/src/shared/shell/app-shell/examples/AppShellProjectSidebarDemo.svelte +1 -0
  96. package/src/shared/shell/app-shell/examples/AppShellSidebarDemo.svelte +1 -0
  97. package/src/shared/shell/app-shell/examples/AppShellToolbarDemo.svelte +1 -0
  98. package/src/shared/shell/app-shell/index.ts +14 -0
  99. package/src/shared/shell/shell.tokens.css +2 -0
  100. package/src/shared/shell/shell.tokens.ts +5 -0
  101. package/src/shared/workspace/menu/WorkspaceContextMenuItems.svelte +2 -1
  102. package/src/shared/workspace/menu/WorkspaceMenuItems.svelte +2 -1
  103. package/src/shared/workspace/problems/ProblemsQuickFix.svelte +1 -4
  104. package/src/shared/workspace/settings/AppSettingsContent.svelte +6 -0
  105. package/src/shared/workspace/settings/AppSettingsNavigation.svelte +7 -0
  106. package/src/shared/workspace/settings/CopyableValue.svelte +81 -0
  107. package/src/shared/workspace/settings/DeclarativeSettingsSection.svelte +20 -0
  108. package/src/shared/workspace/settings/WorkspaceSettingField.svelte +110 -41
  109. package/src/shared/workspace/settings/WorkspaceSettingMultiSelect.svelte +7 -6
  110. package/src/shared/workspace/settings/WorkspaceSettingObjectTable.svelte +187 -23
  111. package/src/shared/workspace/settings/WorkspaceSettingSelect.svelte +29 -29
  112. package/src/shared/workspace/settings/WorkspaceSettings.css +190 -4
  113. package/src/shared/workspace/settings/WorkspaceSettingsStoryCustomField.svelte +2 -2
  114. package/src/shared/workspace/settings/WorkspaceSettingsStoryCustomPage.svelte +13 -0
  115. package/src/shared/workspace/settings/WorkspaceSettingsSurface.svelte +50 -0
  116. package/src/shared/workspace/settings/configuration.ts +5 -0
  117. package/src/shared/workspace/settings/copyable-value.ts +10 -0
  118. package/src/shared/workspace/settings/index.ts +5 -0
  119. package/src/shared/workspace/settings/settings-controller.svelte.ts +349 -15
  120. package/src/shared/workspace/settings/types.ts +90 -8
  121. package/src/storybook/app-shell-reference/AppShellTwoExpandedSidebarsReference.css +25 -0
  122. package/src/storybook/app-shell-reference/AppShellTwoExpandedSidebarsReference.svelte +48 -0
  123. package/src/storybook/app-shell-reference/index.ts +1 -0
  124. package/src/theme.css +10 -0
@@ -26,12 +26,41 @@
26
26
  }
27
27
 
28
28
  .ui-diff-file-diff__row {
29
+ position: relative;
29
30
  display: flex;
30
31
  align-items: stretch;
31
32
  min-width: 0;
32
33
  width: 100%;
33
34
  }
34
35
 
36
+ .ui-diff-file-diff__accessory {
37
+ position: sticky;
38
+ left: 0.125rem;
39
+ z-index: 2;
40
+ display: flex;
41
+ width: 0;
42
+ min-width: 0;
43
+ align-items: center;
44
+ align-self: stretch;
45
+ overflow: visible;
46
+ opacity: 0;
47
+ pointer-events: none;
48
+ transition: opacity 120ms ease;
49
+ }
50
+
51
+ .ui-diff-file-diff__row:hover .ui-diff-file-diff__accessory,
52
+ .ui-diff-file-diff__row:focus-within .ui-diff-file-diff__accessory {
53
+ opacity: 1;
54
+ pointer-events: auto;
55
+ }
56
+
57
+ @media (hover: none) {
58
+ .ui-diff-file-diff__accessory {
59
+ opacity: 1;
60
+ pointer-events: auto;
61
+ }
62
+ }
63
+
35
64
  .ui-diff-file-diff__row[data-variant="added"] {
36
65
  background: var(--ui-diff-added-background);
37
66
  }
@@ -223,12 +252,50 @@
223
252
  .ui-diff-file-diff-composer__file {
224
253
  border: var(--ui-diff-frame-border);
225
254
  border-radius: var(--ui-diff-frame-radius);
226
- overflow: hidden;
255
+ overflow: clip;
256
+ scroll-margin-top: var(--ui-diff-file-sticky-offset, 3.5rem);
257
+ }
258
+
259
+ .ui-diff-file-diff-composer__file[data-selected="true"] {
260
+ outline: 2px solid var(--ring);
261
+ outline-offset: 1px;
227
262
  }
228
263
 
229
264
  .ui-diff-file-diff-composer__header {
265
+ display: flex;
266
+ min-height: 2.25rem;
267
+ align-items: center;
268
+ justify-content: space-between;
269
+ gap: 0.75rem;
230
270
  padding: 0.375rem 0.75rem;
231
271
  border-bottom: 1px solid var(--ui-diff-border);
232
272
  background: var(--muted);
233
273
  font-weight: 650;
234
274
  }
275
+
276
+ .ui-diff-file-diff-composer__file[data-collapsed="true"]
277
+ .ui-diff-file-diff-composer__header {
278
+ border-bottom: 0;
279
+ }
280
+
281
+ .ui-diff-file-diff-composer[data-sticky-headers="true"]
282
+ .ui-diff-file-diff-composer__header {
283
+ position: sticky;
284
+ top: var(--ui-diff-file-sticky-offset, 0);
285
+ z-index: 3;
286
+ }
287
+
288
+ .ui-diff-file-diff-composer__header-start,
289
+ .ui-diff-file-diff-composer__header-leading,
290
+ .ui-diff-file-diff-composer__header-trailing {
291
+ display: flex;
292
+ min-width: 0;
293
+ align-items: center;
294
+ gap: 0.5rem;
295
+ }
296
+
297
+ .ui-diff-file-diff-composer__header-start > span {
298
+ overflow: hidden;
299
+ text-overflow: ellipsis;
300
+ white-space: nowrap;
301
+ }
@@ -40,6 +40,7 @@
40
40
  language,
41
41
  scrollTo,
42
42
  lineAccessory,
43
+ lineAnnotation,
43
44
  }: {
44
45
  path: string;
45
46
  oldText?: string | null;
@@ -51,6 +52,8 @@
51
52
  language?: string;
52
53
  scrollTo?: FileDiffScrollTarget | null;
53
54
  lineAccessory?: Snippet<[FileDiffLineContext]>;
55
+ /** Host-owned content inserted after each matching unified diff row. */
56
+ lineAnnotation?: Snippet<[FileDiffLineContext]>;
54
57
  } = $props();
55
58
 
56
59
  let expandedBlocks = $state<ExpandedBlockState>({});
@@ -107,10 +110,7 @@
107
110
  if (candidate.scrollTop !== target.scrollTop) {
108
111
  candidate.scrollTop = target.scrollTop;
109
112
  }
110
- if (
111
- syncHorizontal &&
112
- candidate.scrollLeft !== target.scrollLeft
113
- ) {
113
+ if (syncHorizontal && candidate.scrollLeft !== target.scrollLeft) {
114
114
  candidate.scrollLeft = target.scrollLeft;
115
115
  }
116
116
  }
@@ -200,6 +200,14 @@
200
200
  data-diff-line-variant={row.variant}
201
201
  data-variant={row.variant}
202
202
  >
203
+ {#if lineAccessory}
204
+ <div
205
+ class="ui-diff-file-diff__accessory"
206
+ data-ui-part="diff-row-accessory"
207
+ >
208
+ {@render lineAccessory(lineContext(row))}
209
+ </div>
210
+ {/if}
203
211
  <span class="ui-diff-file-diff__gutter" aria-hidden="true">
204
212
  {row.lineNumber ?? ""}
205
213
  </span>
@@ -207,17 +215,17 @@
207
215
  {row.variant === "added" ? "+" : row.variant === "removed" ? "-" : " "}
208
216
  </span>
209
217
  {@render highlightedText(row)}
210
- {#if lineAccessory}
211
- {@render lineAccessory(lineContext(row))}
212
- {/if}
213
218
  </div>
219
+ {#if lineAnnotation}
220
+ {@render lineAnnotation(lineContext(row))}
221
+ {/if}
214
222
  {/snippet}
215
223
 
216
224
  {#snippet splitCell(
217
- row: UnifiedDiffRow | null,
218
- side: "left" | "right",
219
- pairKey: string,
220
- )}
225
+ row: UnifiedDiffRow | null,
226
+ side: "left" | "right",
227
+ pairKey: string,
228
+ )}
221
229
  {#if row}
222
230
  <div
223
231
  class="ui-diff-file-diff__row"
@@ -229,13 +237,18 @@
229
237
  data-side={side}
230
238
  data-pair-key={pairKey}
231
239
  >
240
+ {#if lineAccessory}
241
+ <div
242
+ class="ui-diff-file-diff__accessory"
243
+ data-ui-part="diff-row-accessory"
244
+ >
245
+ {@render lineAccessory(lineContext(row))}
246
+ </div>
247
+ {/if}
232
248
  <span class="ui-diff-file-diff__gutter" aria-hidden="true">
233
249
  {lineNumberForSplitSide(row, side) ?? ""}
234
250
  </span>
235
251
  {@render highlightedText(row)}
236
- {#if lineAccessory}
237
- {@render lineAccessory(lineContext(row))}
238
- {/if}
239
252
  </div>
240
253
  {:else}
241
254
  <div
@@ -1,9 +1,15 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from "svelte";
3
+ import { tick } from "svelte";
4
+ import ChevronDownIcon from "@lucide/svelte/icons/chevron-down";
5
+ import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
6
+ import { Button } from "../../shadcn/button/index.js";
3
7
  import { truncatePathMiddle } from "../core/unified-diff.js";
4
8
  import FileDiff from "./FileDiff.svelte";
5
9
  import type {
10
+ FileDiffComposerFileContext,
6
11
  FileDiffFile,
12
+ FileDiffFileScrollTarget,
7
13
  FileDiffLineContext,
8
14
  FileDiffScrollTarget,
9
15
  FileDiffViewMode,
@@ -13,48 +19,124 @@
13
19
  let {
14
20
  files,
15
21
  viewMode = "unified",
22
+ selectedPath,
23
+ collapsedFilePaths = [],
24
+ scrollToFile,
25
+ stickyHeaders = false,
16
26
  scrollTo,
27
+ onFileToggle,
28
+ fileHeaderLeading,
29
+ fileHeaderTrailing,
17
30
  lineAccessory,
31
+ lineAnnotation,
18
32
  }: {
19
33
  files: FileDiffFile[];
20
34
  viewMode?: FileDiffViewMode;
35
+ selectedPath?: string;
36
+ collapsedFilePaths?: readonly string[];
37
+ scrollToFile?: FileDiffFileScrollTarget | null;
38
+ stickyHeaders?: boolean;
21
39
  scrollTo?: FileDiffScrollTarget | null;
40
+ onFileToggle?: (path: string, collapsed: boolean) => void;
41
+ fileHeaderLeading?: Snippet<[FileDiffComposerFileContext]>;
42
+ fileHeaderTrailing?: Snippet<[FileDiffComposerFileContext]>;
22
43
  lineAccessory?: Snippet<[FileDiffLineContext]>;
44
+ /** Host-owned content inserted after each matching unified diff row. */
45
+ lineAnnotation?: Snippet<[FileDiffLineContext]>;
23
46
  } = $props();
47
+
48
+ let rootEl: HTMLElement | null = $state(null);
49
+
50
+ function fileContext(file: FileDiffFile): FileDiffComposerFileContext {
51
+ return {
52
+ file,
53
+ path: file.path,
54
+ collapsed: collapsedFilePaths.includes(file.path),
55
+ selected: selectedPath === file.path,
56
+ };
57
+ }
58
+
59
+ $effect(() => {
60
+ const target = scrollToFile;
61
+ if (!target || !rootEl) return;
62
+ target.requestId;
63
+ void tick().then(() => {
64
+ const section = [
65
+ ...(rootEl?.querySelectorAll<HTMLElement>("[data-diff-file-path]") ??
66
+ []),
67
+ ].find((candidate) => candidate.dataset.diffFilePath === target.path);
68
+ section?.scrollIntoView({ block: "start", inline: "nearest" });
69
+ });
70
+ });
24
71
  </script>
25
72
 
26
73
  <div
74
+ bind:this={rootEl}
27
75
  class="ui-diff-file-diff-composer"
28
76
  data-ui-component="file-diff-composer"
29
77
  data-ui-part="file-diff-composer"
78
+ data-sticky-headers={stickyHeaders}
30
79
  >
31
80
  {#if files.length === 0}
32
81
  <p class="ui-diff-file-diff__empty">No file diffs</p>
33
82
  {:else}
34
83
  {#each files as file (file.path)}
84
+ {@const context = fileContext(file)}
35
85
  <section
36
86
  class="ui-diff-file-diff-composer__file"
37
87
  data-ui-part="file-section"
38
88
  data-diff-file-path={file.path}
89
+ data-selected={context.selected}
90
+ data-collapsed={context.collapsed}
39
91
  >
40
92
  <header class="ui-diff-file-diff-composer__header">
41
- <span title={file.path}>{truncatePathMiddle(file.path)}</span>
93
+ <div class="ui-diff-file-diff-composer__header-start">
94
+ {#if fileHeaderLeading}
95
+ <div class="ui-diff-file-diff-composer__header-leading">
96
+ {@render fileHeaderLeading(context)}
97
+ </div>
98
+ {/if}
99
+ {#if onFileToggle}
100
+ <Button
101
+ type="button"
102
+ variant="ghost"
103
+ size="icon-xs"
104
+ aria-label={`${context.collapsed ? "Expand" : "Collapse"} ${file.path}`}
105
+ onclick={() => onFileToggle?.(file.path, !context.collapsed)}
106
+ >
107
+ {#if context.collapsed}
108
+ <ChevronRightIcon aria-hidden="true" />
109
+ {:else}
110
+ <ChevronDownIcon aria-hidden="true" />
111
+ {/if}
112
+ </Button>
113
+ {/if}
114
+ <span title={file.path}>{truncatePathMiddle(file.path)}</span>
115
+ </div>
116
+ {#if fileHeaderTrailing}
117
+ <div class="ui-diff-file-diff-composer__header-trailing">
118
+ {@render fileHeaderTrailing(context)}
119
+ </div>
120
+ {/if}
42
121
  </header>
43
- <FileDiff
44
- path={file.path}
45
- oldText={file.oldText}
46
- newText={file.newText}
47
- patch={file.patch}
48
- language={file.language}
49
- {viewMode}
50
- scrollTo={scrollTo?.path === file.path ||
51
- (scrollTo != null &&
52
- scrollTo.path == null &&
53
- files[0]?.path === file.path)
54
- ? scrollTo
55
- : null}
56
- {lineAccessory}
57
- />
122
+ {#if !context.collapsed}
123
+ <FileDiff
124
+ path={file.path}
125
+ oldText={file.oldText}
126
+ newText={file.newText}
127
+ patch={file.patch}
128
+ language={file.language}
129
+ {viewMode}
130
+ scrollTo={scrollTo?.path === file.path ||
131
+ (scrollTo != null &&
132
+ scrollTo.path == null &&
133
+ files[0]?.path === file.path)
134
+ ? scrollTo
135
+ : null}
136
+ {lineAccessory}
137
+ {lineAnnotation}
138
+ />
139
+ {/if}
58
140
  </section>
59
141
  {/each}
60
142
  {/if}
@@ -2,7 +2,9 @@ export { default as FileDiff } from "./FileDiff.svelte";
2
2
  export { default as FileDiffComposer } from "./FileDiffComposer.svelte";
3
3
  export { resolveDiffLanguage } from "./language.js";
4
4
  export type {
5
+ FileDiffComposerFileContext,
5
6
  FileDiffFile,
7
+ FileDiffFileScrollTarget,
6
8
  FileDiffLineContext,
7
9
  FileDiffScrollTarget,
8
10
  FileDiffViewMode,
@@ -17,6 +17,19 @@ export interface FileDiffFile {
17
17
  language?: string;
18
18
  }
19
19
 
20
+ export interface FileDiffComposerFileContext {
21
+ file: FileDiffFile;
22
+ path: string;
23
+ collapsed: boolean;
24
+ selected: boolean;
25
+ }
26
+
27
+ export interface FileDiffFileScrollTarget {
28
+ path: string;
29
+ /** Changes for every scroll request, including repeated requests for one path. */
30
+ requestId: string | number;
31
+ }
32
+
20
33
  export interface FileDiffScrollTarget {
21
34
  path?: string;
22
35
  lineNumber: number;
@@ -1,14 +1,51 @@
1
1
  @import "../diff.tokens.css";
2
2
 
3
- [data-ui-component="file-listing-view-mode-toggle"] svg {
4
- width: 0.875rem;
5
- height: 0.875rem;
3
+ [data-ui-component="file-listing-view-mode-toggle"][data-ui-part="toggle-group"] {
4
+ display: inline-flex;
5
+ align-items: stretch;
6
+ width: fit-content;
7
+ height: 1.375rem;
8
+ gap: 0;
9
+ overflow: hidden;
10
+ border: 1px solid var(--ui-diff-border, var(--border, #d4d4d4));
11
+ border-radius: calc(var(--radius, 0.5rem) * 0.6);
12
+ background: var(--ui-diff-surface, var(--background, #fff));
13
+ box-shadow: none;
6
14
  }
7
15
 
8
16
  [data-ui-component="file-listing-view-mode-toggle"]
9
- [data-ui-part="toggle-group-item"][data-state="on"],
17
+ [data-ui-component="toggle-group"][data-ui-part="toggle-group-item"][data-size="sm"],
10
18
  [data-ui-component="file-listing-view-mode-toggle"]
11
- [data-ui-part="toggle-group-item"][data-state="on"]:hover {
19
+ [data-ui-component="toggle-group"][data-ui-part="toggle-group-item"][data-variant="outline"] {
20
+ box-sizing: border-box;
21
+ height: 100%;
22
+ min-height: 0;
23
+ width: 1.375rem;
24
+ min-width: 1.375rem;
25
+ padding: 0;
26
+ border: none;
27
+ border-radius: 0;
28
+ box-shadow: none;
29
+ background-color: transparent;
30
+ }
31
+
32
+ [data-ui-component="file-listing-view-mode-toggle"]
33
+ [data-ui-component="toggle-group"][data-ui-part="toggle-group-item"]
34
+ + [data-ui-component="toggle-group"][data-ui-part="toggle-group-item"] {
35
+ border-left: 1px solid var(--ui-diff-border, var(--border, #d4d4d4));
36
+ }
37
+
38
+ [data-ui-component="file-listing-view-mode-toggle"]
39
+ [data-ui-component="toggle-group"][data-ui-part="toggle-group-item"]
40
+ svg {
41
+ width: 0.75rem;
42
+ height: 0.75rem;
43
+ }
44
+
45
+ [data-ui-component="file-listing-view-mode-toggle"]
46
+ [data-ui-component="toggle-group"][data-ui-part="toggle-group-item"][data-state="on"],
47
+ [data-ui-component="file-listing-view-mode-toggle"]
48
+ [data-ui-component="toggle-group"][data-ui-part="toggle-group-item"][data-state="on"]:hover {
12
49
  background-color: var(--ui-diff-selected);
13
50
  color: inherit;
14
51
  }
@@ -14,7 +14,9 @@ export {
14
14
  FileDiff,
15
15
  FileDiffComposer,
16
16
  resolveDiffLanguage,
17
+ type FileDiffComposerFileContext,
17
18
  type FileDiffFile,
19
+ type FileDiffFileScrollTarget,
18
20
  type FileDiffLineContext,
19
21
  type FileDiffScrollTarget,
20
22
  type FileDiffViewMode,
@@ -187,9 +187,7 @@
187
187
  const viewport = view.querySelector<HTMLElement>(
188
188
  "[data-ui-part='scroll-area-viewport']",
189
189
  );
190
- return viewport
191
- ? [{ side: view.dataset.mergeSide, viewport }]
192
- : [];
190
+ return viewport ? [{ side: view.dataset.mergeSide, viewport }] : [];
193
191
  });
194
192
  if (targets.length === 0) return;
195
193
  let syncing = false;
@@ -202,10 +200,7 @@
202
200
  if (candidate.viewport.scrollTop !== target.viewport.scrollTop) {
203
201
  candidate.viewport.scrollTop = target.viewport.scrollTop;
204
202
  }
205
- if (
206
- !syncAcross &&
207
- candidate.side !== target.side
208
- ) {
203
+ if (!syncAcross && candidate.side !== target.side) {
209
204
  continue;
210
205
  }
211
206
  if (candidate.viewport.scrollLeft !== target.viewport.scrollLeft) {
@@ -350,14 +350,14 @@ export class BooleanFilter extends Filter {
350
350
  const size = this.args.length;
351
351
  if (this.op == FilterType.NOT) {
352
352
  if (size == 1) {
353
- return `-${this.args[0]}`;
353
+ return `-${this.args[0]!}`;
354
354
  }
355
355
  return `-(${this.args.map((it) => it.toString()).join("")})`;
356
356
  }
357
357
 
358
358
  const separator = this.op === FilterType.AND ? " " : ",";
359
359
  return size == 1
360
- ? this.args[0].toString()
360
+ ? this.args[0]!.toString()
361
361
  : `(${this.args.map((it) => it.toString()).join(separator)})`;
362
362
  }
363
363
 
@@ -310,13 +310,14 @@ export class DateExpression {
310
310
 
311
311
  resolve(): DateExpressionInput | DateRange {
312
312
  if (this.#operands.length == 1) {
313
- if (this.#operands[0] instanceof DateExpression) {
314
- return this.#operands[0].resolve();
313
+ const operand = this.#operands[0]!;
314
+ if (operand instanceof DateExpression) {
315
+ return operand.resolve();
315
316
  } else {
316
- return this.#operands[0];
317
+ return operand;
317
318
  }
318
319
  }
319
- return this.eval(this.#operands[0], this.#operands[1]);
320
+ return this.eval(this.#operands[0]!, this.#operands[1]!);
320
321
  }
321
322
 
322
323
  private eval(left: DateExpressionInput, right: DateExpressionInput) {
@@ -510,9 +511,9 @@ export class Parser {
510
511
  }, this.leave.bind(this));
511
512
 
512
513
  if (this.stack.length == 1) {
513
- const filter = this.stack[0];
514
+ const filter = this.stack[0]!;
514
515
  if (filter instanceof BooleanFilter) {
515
- this.predicate = filter.args.length == 1 ? filter.args[0] : filter;
516
+ this.predicate = filter.args.length == 1 ? filter.args[0]! : filter;
516
517
  } else {
517
518
  this.errors.push(
518
519
  `Expected {${filter}} to be instance of BooleanFilter`,
@@ -658,7 +659,7 @@ export class Parser {
658
659
  return new NumberFilter(this.content(node));
659
660
  } else if (node.name === "Regex") {
660
661
  const value = this.content(node).match(/\/(.+)\/([a-z]+)?/) || [];
661
- return new PatternFilter(new RegExp(value[1], value[2] ?? ""));
662
+ return new PatternFilter(new RegExp(value[1] ?? "", value[2] ?? ""));
662
663
  } else if (node.name === "AccountName") {
663
664
  return new AccountFilter(this.content(node));
664
665
  }
@@ -704,7 +705,7 @@ export class Parser {
704
705
  private dateExpression(node: SyntaxNode | null | undefined) {
705
706
  return dateExpression(node, {
706
707
  content: this.content.bind(this),
707
- now: this.options.now,
708
+ ...(this.options.now === undefined ? {} : { now: this.options.now }),
708
709
  });
709
710
  }
710
711
  }
@@ -207,7 +207,7 @@
207
207
  {disabled}
208
208
  ariaLabel="Add filter field"
209
209
  onSelectField={openAddForField}
210
- onCommitText={contentSearchFieldKey ? handleCommitText : undefined}
210
+ {...contentSearchFieldKey ? { onCommitText: handleCommitText } : {}}
211
211
  />
212
212
  </div>
213
213
 
@@ -112,12 +112,12 @@
112
112
  {ariaLabel}
113
113
  shortcut={!showFilterToggle || filtersExpanded ? shortcut : ""}
114
114
  {disabled}
115
- {inputId}
115
+ {...inputId === undefined ? {} : { inputId }}
116
116
  {inputMode}
117
117
  {clearSearchLabel}
118
118
  {error}
119
119
  {editorExtensions}
120
- {filterSyntax}
120
+ {...filterSyntax === undefined ? {} : { filterSyntax }}
121
121
  {onValueChange}
122
122
  {onClearSearch}
123
123
  >
@@ -403,7 +403,7 @@
403
403
  {#key `${chipEditSession.from}:${chipEditSession.to}:${chipEditSession.field}`}
404
404
  <SearchFilterPredicateEditor
405
405
  session={chipEditSession}
406
- {filterSyntax}
406
+ {...filterSyntax === undefined ? {} : { filterSyntax }}
407
407
  {disabled}
408
408
  onCancel={closePredicateChipEditor}
409
409
  onApply={applyPredicateChipEdit}
@@ -158,7 +158,7 @@
158
158
  <div class="cv-search-filter-bar__predicate-editor-row">
159
159
  <SearchFilterFieldAutocomplete
160
160
  value={field}
161
- {filterSyntax}
161
+ {...filterSyntax === undefined ? {} : { filterSyntax }}
162
162
  {disabled}
163
163
  onValueChange={handleFieldChange}
164
164
  />
@@ -261,7 +261,7 @@ function operatorDescription(operator: string) {
261
261
  function valueCompletions(field: SearchFilterField): Completion[] {
262
262
  return uniqueValues(field).map((value) => ({
263
263
  label: value.label ?? value.value,
264
- detail: value.description,
264
+ ...(value.description === undefined ? {} : { detail: value.description }),
265
265
  type: "constant",
266
266
  apply: value.apply ?? value.value,
267
267
  }));
@@ -52,6 +52,7 @@
52
52
  let editing = $state(false);
53
53
  let filterValue = $state("");
54
54
  let activeIndex = $state(0);
55
+ let blurFocusTarget: HTMLElement | null = null;
55
56
  const listId = `autocomplete-list-${Math.random().toString(36).slice(2)}`;
56
57
 
57
58
  const normalizedSuggestions = $derived.by(() => {
@@ -115,10 +116,10 @@
115
116
  void onCommit(suggestion);
116
117
  }
117
118
 
118
- function commitValue() {
119
+ function commitValue(close = true) {
119
120
  const committed = value.trim();
120
121
  if (!committed) return;
121
- if (!forceOpen) open = false;
122
+ if (close && !forceOpen) open = false;
122
123
  editing = false;
123
124
  filterValue = "";
124
125
  void onCommit(committed);
@@ -132,6 +133,13 @@
132
133
  activeIndex = 0;
133
134
  }
134
135
 
136
+ function handleCloseAutoFocus(event: Event) {
137
+ event.preventDefault();
138
+ const target = blurFocusTarget;
139
+ blurFocusTarget = null;
140
+ target?.focus({ preventScroll: true });
141
+ }
142
+
135
143
  function handleKeydown(event: KeyboardEvent) {
136
144
  if (event.key === "ArrowDown") {
137
145
  event.preventDefault();
@@ -166,9 +174,8 @@
166
174
  }
167
175
  if (event.key === "Tab") {
168
176
  if (commitOnTab && value.trim()) {
169
- commitValue();
177
+ commitValue(false);
170
178
  }
171
- closeList();
172
179
  return;
173
180
  }
174
181
  if (event.key === "Backspace" && !value) {
@@ -213,11 +220,16 @@
213
220
  activeIndex = 0;
214
221
  }}
215
222
  onkeydown={handleKeydown}
216
- onblur={() => {
223
+ onblur={(event) => {
224
+ blurFocusTarget =
225
+ event.relatedTarget instanceof HTMLElement
226
+ ? event.relatedTarget
227
+ : null;
217
228
  if (commitOnBlur) {
218
229
  commitValue();
230
+ } else {
231
+ closeList();
219
232
  }
220
- closeList();
221
233
  }}
222
234
  />
223
235
  <Popover.Content
@@ -227,7 +239,7 @@
227
239
  customAnchor={input}
228
240
  {portalProps}
229
241
  onOpenAutoFocus={(event) => event.preventDefault()}
230
- onCloseAutoFocus={(event) => event.preventDefault()}
242
+ onCloseAutoFocus={handleCloseAutoFocus}
231
243
  >
232
244
  <div id={listId} class="ui-autocomplete-input__list" role="listbox">
233
245
  {#each normalizedSuggestions as suggestion, index (suggestion)}