@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
@@ -3,7 +3,7 @@
3
3
 
4
4
  let {
5
5
  ref = $bindable(null),
6
- value = $bindable(),
6
+ value = $bindable(""),
7
7
  ...restProps
8
8
  }: DropdownMenuPrimitive.RadioGroupProps = $props();
9
9
  </script>
@@ -292,6 +292,7 @@
292
292
  width: 0.25rem;
293
293
  flex: 0 0 0.25rem;
294
294
  margin-inline-start: auto;
295
+ margin-inline-end: 1px;
295
296
  }
296
297
 
297
298
  [data-ui-component="scroll-area"][data-ui-part="scroll-area-scrollbar"][data-orientation="vertical"]
@@ -308,6 +309,7 @@
308
309
  height: 0.25rem;
309
310
  flex: 0 0 0.25rem;
310
311
  margin-block-start: auto;
312
+ margin-block-end: 1px;
311
313
  }
312
314
 
313
315
  [data-ui-component="scroll-area"][data-ui-part="scroll-area-scrollbar"][data-orientation="horizontal"]
@@ -18,9 +18,9 @@
18
18
  <Loader2Icon
19
19
  {...omitDataUiComponent(restProps)}
20
20
  {role}
21
- name={name === null ? undefined : name}
22
- color={color === null ? undefined : color}
23
- stroke={stroke === null ? undefined : stroke}
21
+ {...name == null ? {} : { name }}
22
+ {...color == null ? {} : { color }}
23
+ {...stroke == null ? {} : { stroke }}
24
24
  aria-label={ariaLabel}
25
25
  data-ui-component="spinner"
26
26
  class={className}
@@ -119,14 +119,20 @@
119
119
  fill: var(--foreground);
120
120
  }
121
121
  [data-ui-component="tooltip"][data-ui-part="tooltip-content"] {
122
- padding-inline: calc(var(--spacing) * 3);
122
+ padding-inline: var(
123
+ --ui-tooltip-padding-inline,
124
+ calc(var(--spacing) * 2.5)
125
+ );
123
126
  }
124
127
  [data-ui-component="tooltip"][data-ui-part="tooltip-content"] {
125
- padding-block: calc(var(--spacing) * 1.5);
128
+ padding-block: var(
129
+ --ui-tooltip-padding-block,
130
+ calc(var(--spacing) * 1)
131
+ );
126
132
  }
127
133
  [data-ui-component="tooltip"][data-ui-part="tooltip-content"] {
128
- font-size: var(--text-xs);
129
- line-height: var(--tw-leading, var(--text-xs--line-height));
134
+ font-size: var(--ui-tooltip-font-size, 0.6875rem);
135
+ line-height: var(--ui-tooltip-line-height, 1rem);
130
136
  }
131
137
  [data-ui-component="tooltip"][data-ui-part="tooltip-content"] {
132
138
  color: var(--ui-tooltip-foreground, var(--background));
@@ -4,6 +4,10 @@ export const tooltipTokenNames = {
4
4
  borderColor: "--ui-tooltip-border-color",
5
5
  radius: "--ui-tooltip-radius",
6
6
  focusRingColor: "--ui-tooltip-focus-ring-color",
7
+ fontSize: "--ui-tooltip-font-size",
8
+ lineHeight: "--ui-tooltip-line-height",
9
+ paddingInline: "--ui-tooltip-padding-inline",
10
+ paddingBlock: "--ui-tooltip-padding-block",
7
11
  } as const;
8
12
 
9
13
  export type TooltipToken =
@@ -64,6 +64,10 @@
64
64
  background: transparent;
65
65
  }
66
66
 
67
+ .ui-minimal-app-shell__sidebar[hidden] {
68
+ display: none !important;
69
+ }
70
+
67
71
  .ui-minimal-app-shell__sidebar[data-side="left"] {
68
72
  width: var(--ui-shell-sidebar-size, var(--ui-shell-left-sidebar-width));
69
73
  }
@@ -78,12 +82,19 @@
78
82
  background: var(--background);
79
83
  }
80
84
 
85
+ .ui-minimal-app-shell__sidebar[data-variant="rail"][data-presentation="inline"] {
86
+ margin-block: var(--ui-shell-main-block-inset);
87
+ border: 0;
88
+ border-radius: 0;
89
+ background: transparent;
90
+ box-shadow: none;
91
+ }
92
+
81
93
  .ui-minimal-app-shell__sidebar[data-variant="outer"][data-side="left"][data-presentation="inline"] {
82
94
  border-inline-end: 1px solid var(--border);
83
95
  }
84
96
 
85
- .ui-minimal-app-shell__sidebar[data-presentation="overlay"],
86
- .ui-minimal-app-shell__sidebar[data-desktop-overlay-preview] {
97
+ .ui-minimal-app-shell__sidebar[data-presentation="overlay"] {
87
98
  position: absolute;
88
99
  z-index: 31;
89
100
  inset-block: 0;
@@ -92,14 +103,12 @@
92
103
  color-mix(in srgb, var(--foreground) 18%, transparent);
93
104
  }
94
105
 
95
- .ui-minimal-app-shell__sidebar[data-presentation="overlay"][data-side="left"],
96
- .ui-minimal-app-shell__sidebar[data-desktop-overlay-preview][data-side="left"] {
106
+ .ui-minimal-app-shell__sidebar[data-presentation="overlay"][data-side="left"] {
97
107
  inset-inline-start: 0;
98
108
  border-inline-end: 1px solid var(--border);
99
109
  }
100
110
 
101
- .ui-minimal-app-shell__sidebar[data-presentation="overlay"][data-side="right"],
102
- .ui-minimal-app-shell__sidebar[data-desktop-overlay-preview][data-side="right"] {
111
+ .ui-minimal-app-shell__sidebar[data-presentation="overlay"][data-side="right"] {
103
112
  inset-inline-end: 0;
104
113
  margin-inline-start: 0;
105
114
  border-inline-start: 1px solid var(--border);
@@ -139,24 +148,27 @@
139
148
  display: block;
140
149
  }
141
150
 
142
- .ui-minimal-app-shell[data-desktop-overlay-right]
143
- > .ui-minimal-app-shell__composition
144
- > .ui-minimal-app-shell__sidebar[data-side="right"]:not(
145
- [data-presentation="overlay"]
146
- ):not([data-desktop-overlay-preview]),
147
- .ui-minimal-app-shell[data-desktop-overlay-left]
148
- > .ui-minimal-app-shell__composition
149
- > .ui-minimal-app-shell__sidebar[data-mobile-panel-id="left"]:not(
150
- [data-presentation="overlay"]
151
- ):not([data-desktop-overlay-preview]),
152
- .ui-minimal-app-shell[data-desktop-overlay-outer-left]
153
- > .ui-minimal-app-shell__composition
154
- > .ui-minimal-app-shell__sidebar[data-side="left"][data-variant="outer"]:not(
155
- [data-presentation="overlay"]
156
- ):not([data-desktop-overlay-preview]) {
151
+ .ui-minimal-app-shell__sidebar[data-desktop-constrained][data-constrained-presentation="preview"][data-presentation="inline"] {
157
152
  display: none;
158
153
  }
159
154
 
155
+ .ui-minimal-app-shell__sidebar[data-presentation="replace-main"] {
156
+ position: absolute;
157
+ z-index: 31;
158
+ inset-block-start: var(--ui-shell-replacement-block-start, 0px);
159
+ inset-block-end: var(--ui-shell-replacement-block-end, 0px);
160
+ inset-inline-start: var(--ui-shell-replacement-inline-start, 0px);
161
+ inset-inline-end: var(--ui-shell-replacement-inline-end, 0px);
162
+ display: flex;
163
+ width: auto;
164
+ overflow: hidden;
165
+ margin: 0;
166
+ border: var(--ui-shell-main-border);
167
+ border-radius: var(--ui-shell-main-radius);
168
+ background: var(--background);
169
+ box-shadow: none;
170
+ }
171
+
160
172
  .ui-minimal-app-shell__sidebar[data-side="right"][data-state="expanded"] {
161
173
  border-inline-start: 1px solid var(--border);
162
174
  }
@@ -171,13 +183,11 @@
171
183
  min-width: 0;
172
184
  }
173
185
 
174
- .ui-minimal-app-shell__sidebar[data-presentation="overlay"][data-side="left"],
175
- .ui-minimal-app-shell__sidebar[data-desktop-overlay-preview][data-side="left"] {
186
+ .ui-minimal-app-shell__sidebar[data-presentation="overlay"][data-side="left"] {
176
187
  width: var(--ui-shell-sidebar-size, var(--ui-shell-left-sidebar-width));
177
188
  }
178
189
 
179
- .ui-minimal-app-shell__sidebar[data-presentation="overlay"][data-side="right"],
180
- .ui-minimal-app-shell__sidebar[data-desktop-overlay-preview][data-side="right"] {
190
+ .ui-minimal-app-shell__sidebar[data-presentation="overlay"][data-side="right"] {
181
191
  width: var(--ui-shell-sidebar-size, var(--ui-shell-right-sidebar-width));
182
192
  }
183
193
 
@@ -271,6 +281,76 @@
271
281
  box-shadow: var(--ui-shell-main-shadow);
272
282
  }
273
283
 
284
+ .ui-minimal-app-shell__surface-layer {
285
+ position: absolute;
286
+ z-index: 32;
287
+ min-width: 0;
288
+ min-height: 0;
289
+ pointer-events: none;
290
+ }
291
+
292
+ .ui-minimal-app-shell__surface-layer-scrim {
293
+ position: absolute;
294
+ inset: 0;
295
+ padding: 0;
296
+ border: 0;
297
+ border-radius: var(--ui-shell-main-radius);
298
+ background: color-mix(in oklab, var(--background) 75%, transparent);
299
+ pointer-events: auto;
300
+ cursor: pointer;
301
+ transition: background-color 150ms ease;
302
+ }
303
+
304
+ .ui-minimal-app-shell__surface-layer-scrim:hover {
305
+ background: color-mix(in oklab, var(--background) 65%, transparent);
306
+ }
307
+
308
+ .ui-minimal-app-shell__surface-layer-surface {
309
+ position: absolute;
310
+ inset-block: 0;
311
+ inset-inline-start: var(--ui-shell-surface-layer-main-start-allowance);
312
+ inset-inline-end: var(--ui-shell-surface-layer-inline-end-inset);
313
+ display: grid;
314
+ min-width: 0;
315
+ min-height: 0;
316
+ overflow: hidden;
317
+ grid-template-rows: auto minmax(0, 1fr);
318
+ border: var(--ui-shell-main-border);
319
+ border-radius: var(--ui-shell-main-radius);
320
+ outline: none;
321
+ background: var(--background);
322
+ box-shadow: none;
323
+ pointer-events: auto;
324
+ }
325
+
326
+ .ui-minimal-app-shell__surface-layer-surface:focus-visible {
327
+ outline: 2px solid var(--ring);
328
+ outline-offset: -2px;
329
+ }
330
+
331
+ .ui-minimal-app-shell__surface-layer-header {
332
+ display: flex;
333
+ min-width: 0;
334
+ min-height: var(--ui-shell-toolbar-height);
335
+ align-items: center;
336
+ justify-content: space-between;
337
+ gap: 1rem;
338
+ padding: 0.625rem 0.75rem 0.625rem 1rem;
339
+ }
340
+
341
+ .ui-minimal-app-shell__surface-layer-body {
342
+ display: flex;
343
+ min-width: 0;
344
+ min-height: 0;
345
+ overflow: hidden;
346
+ }
347
+
348
+ .ui-minimal-app-shell__surface-layer-scroll-area {
349
+ flex: 1;
350
+ min-width: 0;
351
+ min-height: 0;
352
+ }
353
+
274
354
  .ui-minimal-app-shell[data-display-mode="desktop"] .ui-minimal-app-shell__main {
275
355
  min-width: min(100%, var(--ui-shell-desktop-min-main-width));
276
356
  }
@@ -301,12 +381,6 @@
301
381
  margin-inline-end: var(--ui-shell-main-block-inset);
302
382
  }
303
383
 
304
- .ui-minimal-app-shell[data-desktop-overlay-right]
305
- > .ui-minimal-app-shell__composition
306
- > .ui-minimal-app-shell__main {
307
- margin-inline-end: var(--ui-shell-main-block-inset);
308
- }
309
-
310
384
  .ui-minimal-app-shell__toolbar {
311
385
  display: flex;
312
386
  flex: none;
@@ -434,6 +508,11 @@
434
508
  min-height: 0;
435
509
  }
436
510
 
511
+ .ui-minimal-app-shell[data-display-mode="mobile"]
512
+ .ui-minimal-app-shell__surface-layer-surface {
513
+ inset-inline-start: 0;
514
+ }
515
+
437
516
  .ui-minimal-app-shell[data-display-mode="mobile"]
438
517
  > .ui-minimal-app-shell__composition {
439
518
  display: none;
@@ -445,7 +524,8 @@
445
524
  height: 100%;
446
525
  min-width: 0;
447
526
  min-height: 0;
448
- overflow: hidden;
527
+ /* Stage position belongs to the translated track, not focus scrolling. */
528
+ overflow: clip;
449
529
  }
450
530
 
451
531
  .ui-minimal-app-shell__mobile-track {
@@ -0,0 +1,37 @@
1
+ /* Story-only viewport variants. Shell and example presentation styles live
2
+ with the components that own them. */
3
+ .ui-shell-story-frame-fluid {
4
+ box-sizing: border-box;
5
+ width: 100%;
6
+ min-width: 0;
7
+ max-width: none;
8
+ }
9
+
10
+ .ui-shell-story-frame-fluid:has([data-display-mode="mobile"]) {
11
+ padding-block: 0;
12
+ }
13
+
14
+ .ui-shell-story-frame-tablet,
15
+ .ui-shell-story-frame-mobile {
16
+ width: 100%;
17
+ min-width: 0;
18
+ margin-inline: auto;
19
+ padding-block: 0;
20
+ }
21
+
22
+ .ui-shell-story-frame-tablet {
23
+ max-width: 56rem;
24
+ }
25
+
26
+ .ui-shell-story-frame-mobile {
27
+ max-width: 30rem;
28
+ padding-inline: 0.5rem;
29
+ }
30
+
31
+ .ui-shell-story-frame-constrained-desktop {
32
+ box-sizing: border-box;
33
+ width: 100%;
34
+ min-width: 0;
35
+ max-width: 56rem;
36
+ margin-inline: auto;
37
+ }
@@ -2,18 +2,22 @@
2
2
  import type { HTMLAttributes } from "svelte/elements";
3
3
  import type { WithElementRef } from "../../../lib/utils.js";
4
4
  import { ScrollArea } from "../../shadcn/scroll-area/index.js";
5
+ import type { ScrollAreaVisibility } from "../../shadcn/scroll-area/scroll-area-model.js";
5
6
  import { useAppShell } from "./app-shell-context.svelte.js";
6
7
  import { useAppShellBody } from "./app-shell-body-context.svelte.js";
7
8
 
8
9
  let {
9
10
  ref = $bindable(null),
10
11
  label,
12
+ scrollbarVisibility,
11
13
  class: className,
12
14
  children,
13
15
  ...restProps
14
16
  }: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
15
17
  /** Optional accessible name for this independently scrolling region. */
16
18
  label?: string;
19
+ /** When its scrollbar is painted: while scrolling, on hover, or always. */
20
+ scrollbarVisibility?: ScrollAreaVisibility;
17
21
  } = $props();
18
22
 
19
23
  useAppShell();
@@ -38,7 +42,7 @@
38
42
  aria-label={label}
39
43
  >
40
44
  <ScrollArea
41
- type="auto"
45
+ type={scrollbarVisibility ?? "auto"}
42
46
  class="ui-minimal-app-shell__body-content-scroll-area"
43
47
  >
44
48
  {@render children?.()}
@@ -7,6 +7,7 @@
7
7
  import type { HTMLAttributes } from "svelte/elements";
8
8
  import type { WithElementRef } from "../../../lib/utils.js";
9
9
  import { ScrollArea } from "../../shadcn/scroll-area/index.js";
10
+ import type { ScrollAreaVisibility } from "../../shadcn/scroll-area/scroll-area-model.js";
10
11
  import { useAppShell } from "./app-shell-context.svelte.js";
11
12
  import { useAppShellBody } from "./app-shell-body-context.svelte.js";
12
13
  import type { AppShellSide } from "./app-shell-controller.svelte.js";
@@ -19,6 +20,7 @@
19
20
  panelId,
20
21
  label,
21
22
  mobileLabel,
23
+ scrollbarVisibility,
22
24
  tabindex: _tabindex,
23
25
  class: className,
24
26
  children,
@@ -34,6 +36,8 @@
34
36
  label?: string;
35
37
  /** Mobile edge selector label. Defaults to the accessible landmark label. */
36
38
  mobileLabel?: string;
39
+ /** When its scrollbar is painted: while scrolling, on hover, or always. */
40
+ scrollbarVisibility?: ScrollAreaVisibility;
37
41
  } = $props();
38
42
 
39
43
  const controller = useAppShell();
@@ -108,7 +112,7 @@
108
112
  inert={(mobileMode && !mobileActive) || (!mobileMode && !open)}
109
113
  >
110
114
  <ScrollArea
111
- type="auto"
115
+ type={scrollbarVisibility ?? "auto"}
112
116
  class="ui-minimal-app-shell__body-sidebar-scroll-area"
113
117
  >
114
118
  {@render children?.()}
@@ -8,15 +8,24 @@
8
8
  let {
9
9
  ref = $bindable(null),
10
10
  tabindex: _tabindex,
11
+ inert: inertProp,
12
+ "aria-hidden": ariaHidden,
11
13
  class: className,
12
14
  children,
13
15
  ...restProps
14
16
  }: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
15
17
 
16
18
  const controller = useAppShell();
19
+ let mainOccluded = $state(controller.mainOccluded);
20
+ const unsubscribeProjection = controller.onProjectionChange(() => {
21
+ mainOccluded = controller.mainOccluded;
22
+ });
17
23
 
18
24
  $effect(() => controller.mobile.setMainElement(ref));
19
- onDestroy(() => controller.mobile.setMainElement(null));
25
+ onDestroy(() => {
26
+ unsubscribeProjection();
27
+ controller.mobile.setMainElement(null);
28
+ });
20
29
  </script>
21
30
 
22
31
  <div
@@ -30,6 +39,8 @@
30
39
  class={["ui-minimal-app-shell__main", className].filter(Boolean).join(" ")}
31
40
  data-ui-component="app-shell"
32
41
  data-ui-part="main"
42
+ inert={Boolean(inertProp) || mainOccluded}
43
+ aria-hidden={Boolean(inertProp) || mainOccluded ? "true" : ariaHidden}
33
44
  >
34
45
  {@render children?.()}
35
46
  </div>
@@ -37,7 +37,7 @@
37
37
  <Select.Root
38
38
  type="single"
39
39
  bind:open
40
- value={activePanelId}
40
+ value={activePanelId ?? panels[0]?.id ?? ""}
41
41
  onValueChange={(value) => {
42
42
  if (value) controller.mobile.selectPanel(side, value);
43
43
  }}
@@ -64,6 +64,10 @@
64
64
  let dragOffset = $state<number | null>(null);
65
65
  let measuredPanelWidth = $state(0);
66
66
  let measuredDesktopMinMainWidth = $state(576);
67
+ let projectionVersion = $state(0);
68
+ const unsubscribeProjection = rootController.onProjectionChange(() => {
69
+ projectionVersion += 1;
70
+ });
67
71
  let rootStyle = $derived(
68
72
  [
69
73
  style,
@@ -93,6 +97,7 @@
93
97
  rootController.mobile.panelsFor("right").length > 0,
94
98
  );
95
99
  let constrainedDesktopPanelIds = $derived.by(() => {
100
+ void projectionVersion;
96
101
  if (resolvedMode !== "desktop" || !Number.isFinite(observedWidth)) {
97
102
  return [] as string[];
98
103
  }
@@ -101,7 +106,7 @@
101
106
  desktopMinMainWidth ?? measuredDesktopMinMainWidth,
102
107
  );
103
108
  if (minimumMainWidth === 0) return [] as string[];
104
- const candidates = [
109
+ const registeredPanels = [
105
110
  ...rootController.mobile
106
111
  .panelsFor("right")
107
112
  .filter((panel) => panel.kind === "sidebar"),
@@ -111,18 +116,33 @@
111
116
  ...rootController.mobile
112
117
  .panelsFor("left")
113
118
  .filter((panel) => panel.kind === "sidebar" && panel.id === "left"),
114
- ].flatMap((panel) => {
119
+ ];
120
+ const candidates = registeredPanels.flatMap((panel, order) => {
115
121
  const sidebar = rootController.getPanel(panel.id);
116
- if (!sidebar || sidebar.closed) return [];
122
+ if (
123
+ !sidebar ||
124
+ sidebar.closed ||
125
+ rootController.isPanelSuspended(panel.id)
126
+ ) {
127
+ return [];
128
+ }
117
129
  return [
118
130
  {
119
131
  id: panel.id,
132
+ order,
133
+ priority: Number.isFinite(panel.constraintPriority)
134
+ ? (panel.constraintPriority ?? order)
135
+ : order,
120
136
  width: sidebar.collapsed
121
137
  ? 48
122
138
  : (sidebar.width ?? APP_SHELL_DEFAULT_SIDEBAR_WIDTH),
123
139
  },
124
140
  ];
125
141
  });
142
+ candidates.sort(
143
+ (left, right) =>
144
+ left.priority - right.priority || left.order - right.order,
145
+ );
126
146
  let occupiedWidth = candidates.reduce(
127
147
  (total, candidate) => total + candidate.width,
128
148
  minimumMainWidth + 16,
@@ -139,16 +159,8 @@
139
159
  $effect(() => rootController.mobile.setResolvedMode(resolvedMode));
140
160
  $effect(() => {
141
161
  const panelIds = constrainedDesktopPanelIds;
142
- queueMicrotask(() => {
143
- for (const sidebar of ref?.querySelectorAll<HTMLElement>(
144
- '[data-ui-part="sidebar"][data-desktop-overlay-preview]',
145
- ) ?? []) {
146
- const panelId = sidebar.dataset.mobilePanelId;
147
- if (!panelId || !panelIds.includes(panelId)) {
148
- sidebar.removeAttribute("data-desktop-overlay-preview");
149
- }
150
- }
151
- });
162
+ untrack(() => rootController.setConstrainedPanelIds(panelIds));
163
+ untrack(() => rootController.syncPanelProjections());
152
164
  });
153
165
  $effect(() => rootController.mobile.setRootElement(ref));
154
166
  $effect(() => rootController.mobile.setPanelHost("left", leftPanelHost));
@@ -175,11 +187,13 @@
175
187
  });
176
188
 
177
189
  onDestroy(() => {
190
+ unsubscribeProjection();
178
191
  clearGesture();
179
192
  rootController.mobile.setRootElement(null);
180
193
  rootController.mobile.setPanelHost("left", null);
181
194
  rootController.mobile.setMainHost(null);
182
195
  rootController.mobile.setPanelHost("right", null);
196
+ rootController.setConstrainedPanelIds([]);
183
197
  void rootController.flushSave();
184
198
  });
185
199