@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
@@ -0,0 +1,38 @@
1
+ <script lang="ts">
2
+ import { Button, type ButtonProps } from "../../shadcn/button/index.js";
3
+ import ConfirmDialog, {
4
+ type ConfirmDialogProps,
5
+ } from "../confirm-dialog/ConfirmDialog.svelte";
6
+ import type { Snippet } from "svelte";
7
+ let {
8
+ children,
9
+ disabled = false,
10
+ variant,
11
+ size = "default",
12
+ ...confirmation
13
+ }: Omit<ConfirmDialogProps, "open"> & {
14
+ children: Snippet;
15
+ disabled?: boolean;
16
+ variant?: ButtonProps["variant"];
17
+ size?: ButtonProps["size"];
18
+ } = $props();
19
+ let open = $state(false);
20
+ let trigger = $state<HTMLButtonElement | null>(null);
21
+ </script>
22
+
23
+ <Button
24
+ bind:ref={trigger}
25
+ {disabled}
26
+ {size}
27
+ variant={variant ?? (confirmation.destructive ? "destructive" : "outline")}
28
+ onclick={() => (open = true)}>{@render children()}</Button
29
+ >
30
+ <ConfirmDialog
31
+ {...confirmation}
32
+ bind:open
33
+ onCloseAutoFocus={(event) => {
34
+ event.preventDefault();
35
+ trigger?.focus();
36
+ confirmation.onCloseAutoFocus?.(event);
37
+ }}
38
+ />
@@ -0,0 +1,14 @@
1
+ <script lang="ts">
2
+ import { ConfirmButton } from "@lapismd/design-core/forms/dialog";
3
+ let count = $state(0);
4
+ </script>
5
+
6
+ <ConfirmButton
7
+ title="Clear history?"
8
+ description="Remove saved history from this device."
9
+ destructive
10
+ onConfirm={() => {
11
+ count += 1;
12
+ }}>Clear history</ConfirmButton
13
+ >
14
+ <p>Cleared {count} time</p>
@@ -0,0 +1 @@
1
+ export { default as ConfirmButton } from "./ConfirmButton.svelte";
@@ -0,0 +1,21 @@
1
+ [data-ui-confirm-dialog][data-ui-part="alert-dialog-content"] {
2
+ width: min(28rem, calc(100vw - 2rem));
3
+ max-width: 28rem;
4
+ max-height: calc(100dvh - 2rem);
5
+ overflow: auto;
6
+ font-family: var(--font-sans, sans-serif);
7
+ font-size: var(--ui-dialog-frame-font-size, 0.875rem);
8
+ }
9
+ [data-ui-confirm-dialog] [data-slot="alert-dialog-title"] {
10
+ font-size: var(--ui-dialog-frame-title-size, 1.125rem);
11
+ }
12
+ [data-ui-confirm-dialog] [data-slot="alert-dialog-footer"] {
13
+ display: flex;
14
+ flex-direction: row;
15
+ justify-content: flex-end;
16
+ gap: 0.5rem;
17
+ }
18
+ [data-ui-confirm-dialog] [role="alert"] {
19
+ color: var(--destructive);
20
+ margin: 0;
21
+ }
@@ -0,0 +1,116 @@
1
+ <script lang="ts" module>
2
+ import type { ComponentProps, Snippet } from "svelte";
3
+ import type * as AlertDialogTypes from "../../shadcn/alert-dialog/index.js";
4
+ export interface ConfirmDialogProps {
5
+ open?: boolean;
6
+ title: string | Snippet;
7
+ description: string | Snippet;
8
+ confirmLabel?: string | Snippet;
9
+ children?: Snippet;
10
+ confirmDisabled?: boolean;
11
+ cancelLabel?: string;
12
+ destructive?: boolean;
13
+ pending?: boolean;
14
+ error?: string;
15
+ /** Resolve false to retain an externally reported validation error. Reject to show a retryable error. */
16
+ onConfirm: () => void | boolean | Promise<void | boolean>;
17
+ onCancel?: () => void;
18
+ onOpenChange?: (open: boolean) => void;
19
+ onCloseAutoFocus?: ComponentProps<
20
+ typeof AlertDialogTypes.Content
21
+ >["onCloseAutoFocus"];
22
+ portalProps?: ComponentProps<
23
+ typeof AlertDialogTypes.Content
24
+ >["portalProps"];
25
+ }
26
+ </script>
27
+
28
+ <script lang="ts">
29
+ import * as AlertDialog from "../../shadcn/alert-dialog/index.js";
30
+ import { Button } from "../../shadcn/button/index.js";
31
+ import "./ConfirmDialog.css";
32
+ let {
33
+ open = $bindable(false),
34
+ title,
35
+ description,
36
+ confirmLabel = "Confirm",
37
+ cancelLabel = "Cancel",
38
+ children,
39
+ confirmDisabled = false,
40
+ destructive = false,
41
+ pending = false,
42
+ error = "",
43
+ onConfirm,
44
+ onCancel,
45
+ onOpenChange,
46
+ onCloseAutoFocus,
47
+ portalProps,
48
+ }: ConfirmDialogProps = $props();
49
+ let submitting = $state(false);
50
+ let failure = $state("");
51
+ const busy = $derived(pending || submitting);
52
+ $effect(() => {
53
+ if (!open) failure = "";
54
+ });
55
+ function changeOpen(next: boolean) {
56
+ if (busy) return;
57
+ open = next;
58
+ onOpenChange?.(next);
59
+ if (!next) onCancel?.();
60
+ }
61
+ async function confirm() {
62
+ if (busy) return;
63
+ submitting = true;
64
+ failure = "";
65
+ try {
66
+ if ((await onConfirm()) !== false) {
67
+ open = false;
68
+ onOpenChange?.(false);
69
+ }
70
+ } catch (cause) {
71
+ failure =
72
+ cause instanceof Error
73
+ ? cause.message
74
+ : "The action failed. Please try again.";
75
+ } finally {
76
+ submitting = false;
77
+ }
78
+ }
79
+ </script>
80
+
81
+ <AlertDialog.Root {open} onOpenChange={changeOpen}>
82
+ <AlertDialog.Content
83
+ data-ui-confirm-dialog=""
84
+ {...portalProps === undefined ? {} : { portalProps }}
85
+ {...onCloseAutoFocus === undefined ? {} : { onCloseAutoFocus }}
86
+ onEscapeKeydown={(event) => {
87
+ if (busy) event.preventDefault();
88
+ }}
89
+ >
90
+ <AlertDialog.Header>
91
+ <AlertDialog.Title
92
+ >{#if typeof title === "string"}{title}{:else}{@render title()}{/if}</AlertDialog.Title
93
+ >
94
+ <AlertDialog.Description
95
+ >{#if typeof description === "string"}{description}{:else}{@render description()}{/if}</AlertDialog.Description
96
+ >
97
+ </AlertDialog.Header>
98
+ {@render children?.()}
99
+ {#if failure || error}<p role="alert">{failure || error}</p>{/if}
100
+ <AlertDialog.Footer>
101
+ <Button
102
+ variant="outline"
103
+ disabled={busy}
104
+ onclick={() => changeOpen(false)}
105
+ autofocus>{cancelLabel}</Button
106
+ >
107
+ <Button
108
+ variant={destructive ? "destructive" : "default"}
109
+ disabled={busy || confirmDisabled}
110
+ aria-busy={busy}
111
+ onclick={confirm}
112
+ >{#if busy}Working…{:else if typeof confirmLabel === "string"}{confirmLabel}{:else}{@render confirmLabel()}{/if}</Button
113
+ >
114
+ </AlertDialog.Footer>
115
+ </AlertDialog.Content>
116
+ </AlertDialog.Root>
@@ -0,0 +1,17 @@
1
+ <script lang="ts">
2
+ import { ConfirmDialog } from "@lapismd/design-core/forms/dialog";
3
+ let open = $state(true);
4
+ let removed = $state(false);
5
+ </script>
6
+
7
+ <ConfirmDialog
8
+ bind:open
9
+ title="Remove record?"
10
+ description="Remove the selected record."
11
+ confirmLabel="Remove"
12
+ destructive
13
+ onConfirm={async () => {
14
+ removed = true;
15
+ }}
16
+ />
17
+ <p>{removed ? "Removed" : "Available"}</p>
@@ -0,0 +1,2 @@
1
+ export { default as ConfirmDialog } from "./ConfirmDialog.svelte";
2
+ export type { ConfirmDialogProps } from "./ConfirmDialog.svelte";
@@ -0,0 +1,130 @@
1
+ [data-ui-dialog-frame][data-ui-part="dialog-content"] {
2
+ --ui-dialog-frame-width: 40rem;
3
+ display: flex;
4
+ flex-direction: column;
5
+ gap: 0;
6
+ width: min(var(--ui-dialog-frame-width), calc(100vw - 2rem));
7
+ max-width: calc(100vw - 2rem);
8
+ max-height: calc(100dvh - 2rem);
9
+ padding: 0;
10
+ overflow: hidden;
11
+ font-family: var(--font-sans, sans-serif);
12
+ font-size: var(--ui-dialog-frame-font-size, 0.875rem);
13
+ line-height: 1.5;
14
+ }
15
+ [data-ui-dialog-frame][data-frame-size="compact"] {
16
+ --ui-dialog-frame-width: 28rem;
17
+ }
18
+ [data-ui-dialog-frame][data-frame-size="wide"] {
19
+ --ui-dialog-frame-width: 68rem;
20
+ }
21
+ [data-ui-dialog-frame][data-frame-size="large"] {
22
+ --ui-dialog-frame-width: 56rem;
23
+ max-height: min(42rem, calc(100dvh - 2rem));
24
+ }
25
+ [data-ui-dialog-frame][data-frame-surface="inset"] {
26
+ background: var(--muted);
27
+ }
28
+ [data-ui-dialog-frame] > [data-ui-part="dialog-frame-header"] {
29
+ display: flex;
30
+ align-items: flex-start;
31
+ gap: 1rem;
32
+ flex: none;
33
+ padding: 1.25rem 1.5rem;
34
+ }
35
+ [data-ui-dialog-frame] [data-ui-part="dialog-frame-heading"] {
36
+ flex: 1;
37
+ min-width: 0;
38
+ display: grid;
39
+ gap: 0.5rem;
40
+ }
41
+ [data-ui-dialog-frame] [data-slot="dialog-title"] {
42
+ font-size: var(--ui-dialog-frame-title-size, 1.125rem);
43
+ line-height: 1.4;
44
+ font-weight: 600;
45
+ }
46
+ [data-ui-dialog-frame] [data-ui-part="dialog-frame-close"] {
47
+ flex: none;
48
+ }
49
+ [data-ui-dialog-frame][data-frame-surface="inset"]
50
+ > [data-ui-part="dialog-frame-header"] {
51
+ background: var(--muted);
52
+ }
53
+ [data-ui-dialog-frame][data-frame-surface="inset"]
54
+ [data-ui-part="dialog-frame-close"]:is(:hover, :focus-visible) {
55
+ background: var(--background);
56
+ color: var(--foreground);
57
+ }
58
+ [data-ui-dialog-frame] [data-ui-part="dialog-frame-layout"] {
59
+ display: flex;
60
+ flex: 1;
61
+ min-height: 0;
62
+ min-width: 0;
63
+ }
64
+ [data-ui-dialog-frame] [data-ui-part="dialog-frame-sidebar"] {
65
+ flex: 0 0 10rem;
66
+ padding: 0.5rem 1rem 1rem;
67
+ background: var(--muted);
68
+ }
69
+ [data-ui-dialog-frame][data-frame-surface="inset"]
70
+ [data-ui-part="dialog-frame-sidebar"] {
71
+ flex-basis: 13rem;
72
+ }
73
+ [data-ui-dialog-frame] [data-ui-part="dialog-frame-main"] {
74
+ display: flex;
75
+ flex-direction: column;
76
+ flex: 1;
77
+ min-width: 0;
78
+ min-height: 0;
79
+ }
80
+ [data-ui-dialog-frame][data-frame-surface="inset"]
81
+ [data-ui-part="dialog-frame-main"] {
82
+ margin: 0 1rem 1rem 0;
83
+ border-radius: 1rem;
84
+ background: var(--background);
85
+ overflow: hidden;
86
+ }
87
+ [data-ui-dialog-frame] .ui-dialog-frame-scroll {
88
+ flex: 1;
89
+ min-height: 0;
90
+ min-width: 0;
91
+ overflow: hidden;
92
+ display: flex;
93
+ flex-direction: column;
94
+ }
95
+ [data-ui-dialog-frame]
96
+ .ui-dialog-frame-scroll
97
+ > [data-ui-part="scroll-area-viewport"] {
98
+ flex: 1;
99
+ height: auto;
100
+ min-height: 0;
101
+ }
102
+ [data-ui-dialog-frame] [data-ui-part="dialog-frame-body"] {
103
+ padding: 0.5rem 1.5rem 1.5rem;
104
+ }
105
+ [data-ui-dialog-frame][data-frame-padding="none"]
106
+ [data-ui-part="dialog-frame-body"] {
107
+ padding: 0;
108
+ }
109
+ [data-ui-dialog-frame] [data-ui-part="dialog-frame-footer"] {
110
+ display: flex;
111
+ flex: none;
112
+ flex-direction: row;
113
+ justify-content: flex-end;
114
+ flex-wrap: wrap;
115
+ gap: 0.5rem;
116
+ padding: 1rem 1.5rem;
117
+ background: var(--popover);
118
+ }
119
+ @media (max-width: 40rem) {
120
+ [data-ui-dialog-frame] [data-ui-part="dialog-frame-layout"] {
121
+ flex-direction: column;
122
+ }
123
+ [data-ui-dialog-frame] [data-ui-part="dialog-frame-sidebar"] {
124
+ flex: none;
125
+ }
126
+ [data-ui-dialog-frame][data-frame-surface="inset"]
127
+ [data-ui-part="dialog-frame-main"] {
128
+ margin-left: 1rem;
129
+ }
130
+ }
@@ -0,0 +1,108 @@
1
+ <script lang="ts" module>
2
+ import type { ComponentProps, Snippet } from "svelte";
3
+ import type * as DialogTypes from "../../shadcn/dialog/index.js";
4
+
5
+ export type DialogFrameSize = "compact" | "form" | "large" | "wide";
6
+ export type DialogFrameSurface = "plain" | "inset";
7
+ export type DialogFrameProps = Omit<
8
+ ComponentProps<typeof DialogTypes.Content>,
9
+ "children" | "title" | "showCloseButton" | "dataUiComponent" | "dataUiPart"
10
+ > & {
11
+ title: string | Snippet;
12
+ description?: string | Snippet;
13
+ descriptionHidden?: boolean;
14
+ size?: DialogFrameSize;
15
+ /** Visual treatment for the dialog shell and main content surface. */
16
+ surface?: DialogFrameSurface;
17
+ busy?: boolean;
18
+ closeLabel?: string;
19
+ sidebar?: Snippet;
20
+ headerActions?: Snippet;
21
+ footer?: Snippet;
22
+ children: Snippet;
23
+ bodyPadding?: "default" | "none";
24
+ };
25
+ </script>
26
+
27
+ <script lang="ts">
28
+ import * as Dialog from "../../shadcn/dialog/index.js";
29
+ import { Button } from "../../shadcn/button/index.js";
30
+ import { ScrollArea } from "../../shadcn/scroll-area/index.js";
31
+ import XIcon from "@lucide/svelte/icons/x";
32
+ import "./DialogFrame.css";
33
+
34
+ let {
35
+ title,
36
+ description,
37
+ descriptionHidden = false,
38
+ size = "form",
39
+ surface = "plain",
40
+ busy = false,
41
+ closeLabel = "Close",
42
+ sidebar,
43
+ headerActions,
44
+ footer,
45
+ children,
46
+ bodyPadding = "default",
47
+ ref = $bindable(null),
48
+ onInteractOutside,
49
+ onEscapeKeydown,
50
+ ...contentProps
51
+ }: DialogFrameProps = $props();
52
+ </script>
53
+
54
+ <Dialog.Content
55
+ {...contentProps}
56
+ bind:ref
57
+ showCloseButton={false}
58
+ data-ui-dialog-frame=""
59
+ data-frame-size={size}
60
+ data-frame-surface={surface}
61
+ data-frame-padding={bodyPadding}
62
+ onInteractOutside={(event) => {
63
+ if (busy) event.preventDefault();
64
+ else onInteractOutside?.(event);
65
+ }}
66
+ onEscapeKeydown={(event) => {
67
+ if (busy) event.preventDefault();
68
+ else onEscapeKeydown?.(event);
69
+ }}
70
+ >
71
+ <div data-ui-part="dialog-frame-header">
72
+ <div data-ui-part="dialog-frame-heading">
73
+ <Dialog.Title
74
+ >{#if typeof title === "string"}{title}{:else}{@render title()}{/if}</Dialog.Title
75
+ >
76
+ {#if description}<Dialog.Description
77
+ class={descriptionHidden ? "sr-only" : undefined}
78
+ >{#if typeof description === "string"}{description}{:else}{@render description()}{/if}</Dialog.Description
79
+ >{/if}
80
+ </div>
81
+ {@render headerActions?.()}
82
+ <Dialog.Close>
83
+ {#snippet child({ props })}
84
+ <Button
85
+ {...props}
86
+ variant="ghost"
87
+ size="icon-sm"
88
+ disabled={busy}
89
+ aria-label={closeLabel}
90
+ data-ui-part="dialog-frame-close"><XIcon aria-hidden="true" /></Button
91
+ >
92
+ {/snippet}
93
+ </Dialog.Close>
94
+ </div>
95
+ <div data-ui-part="dialog-frame-layout" data-has-sidebar={Boolean(sidebar)}>
96
+ {#if sidebar}<aside data-ui-part="dialog-frame-sidebar">
97
+ {@render sidebar()}
98
+ </aside>{/if}
99
+ <div data-ui-part="dialog-frame-main">
100
+ <ScrollArea class="ui-dialog-frame-scroll" type="always">
101
+ <div data-ui-part="dialog-frame-body">{@render children()}</div>
102
+ </ScrollArea>
103
+ {#if footer}<Dialog.Footer data-ui-part="dialog-frame-footer"
104
+ >{@render footer()}</Dialog.Footer
105
+ >{/if}
106
+ </div>
107
+ </div>
108
+ </Dialog.Content>
@@ -0,0 +1,37 @@
1
+ <script lang="ts">
2
+ import { DialogFrame } from "@lapismd/design-core/forms/dialog";
3
+ import * as Dialog from "@lapismd/design-core/shadcn/dialog";
4
+ import { Button } from "@lapismd/design-core/shadcn/button";
5
+ import { Input } from "@lapismd/design-core/shadcn/input";
6
+ let open = $state(false);
7
+ let name = $state("");
8
+ </script>
9
+
10
+ <Dialog.Root bind:open>
11
+ <Dialog.Trigger
12
+ >{#snippet child({ props })}<Button {...props}>Edit record</Button
13
+ >{/snippet}</Dialog.Trigger
14
+ >
15
+ <DialogFrame
16
+ title="Edit record"
17
+ description={name
18
+ ? `Editing ${name}`
19
+ : "The form scrolls while its actions remain visible."}
20
+ size="large"
21
+ surface="inset"
22
+ class={name ? "record-filled" : "record-empty"}
23
+ >
24
+ {#snippet sidebar()}<nav aria-label="Sections">
25
+ <Button variant="secondary">Details</Button>
26
+ </nav>{/snippet}
27
+ {#each Array.from({ length: 24 }) as _, index}<label
28
+ style="display: grid; gap: 0.5rem; margin-block: 1rem;"
29
+ >Field {index + 1}{#if index === 0}<Input
30
+ bind:value={name}
31
+ />{:else}<Input />{/if}</label
32
+ >{/each}
33
+ {#snippet footer()}<Button variant="outline" onclick={() => (open = false)}
34
+ >Cancel</Button
35
+ ><Button onclick={() => (open = false)}>Save</Button>{/snippet}
36
+ </DialogFrame>
37
+ </Dialog.Root>
@@ -0,0 +1,6 @@
1
+ export { default as DialogFrame } from "./DialogFrame.svelte";
2
+ export type {
3
+ DialogFrameProps,
4
+ DialogFrameSize,
5
+ DialogFrameSurface,
6
+ } from "./DialogFrame.svelte";
@@ -0,0 +1,8 @@
1
+ export { DialogFrame } from "./dialog-frame/index.js";
2
+ export type {
3
+ DialogFrameProps,
4
+ DialogFrameSize,
5
+ } from "./dialog-frame/index.js";
6
+ export { ConfirmDialog } from "./confirm-dialog/index.js";
7
+ export type { ConfirmDialogProps } from "./confirm-dialog/index.js";
8
+ export { ConfirmButton } from "./confirm-button/index.js";
@@ -129,3 +129,14 @@ export {
129
129
  export type { InlineOptionPickerOption } from "./inline-option-picker/InlineOptionPicker.svelte";
130
130
  export type { AddSectionOption } from "./add-section-chooser/AddSectionChooser.svelte";
131
131
  export * from "./core/core";
132
+
133
+ export {
134
+ DialogFrame,
135
+ type DialogFrameProps,
136
+ type DialogFrameSize,
137
+ } from "./dialog-frame/index.js";
138
+ export {
139
+ ConfirmDialog,
140
+ type ConfirmDialogProps,
141
+ } from "./confirm-dialog/index.js";
142
+ export { ConfirmButton } from "./confirm-button/index.js";
@@ -276,6 +276,7 @@
276
276
  }
277
277
  [data-ui-component="button"][data-variant="outline"]:hover,
278
278
  [data-ui-component="button"][data-variant="ghost"]:hover {
279
+ background-color: var(--accent);
279
280
  color: var(--foreground);
280
281
  }
281
282
  [data-ui-component="button"][data-variant="link"]:hover {
@@ -294,6 +295,9 @@
294
295
  );
295
296
  }
296
297
  }
298
+ [data-ui-component="button"][data-variant="ghost"]:focus-visible {
299
+ background-color: var(--accent);
300
+ }
297
301
  [data-ui-component="button"]:focus-visible {
298
302
  border-color: var(--ui-button-focus-ring-color, var(--ring));
299
303
  }
@@ -179,8 +179,9 @@ function applyRangesToContainer(
179
179
  for (let i = 0; i < lineDivs.length; i++) {
180
180
  const tokenIndex = globalLineOffset + i;
181
181
  const tokens = tokenLines[tokenIndex];
182
- if (tokens && tokens.length > 0) {
183
- applyLineRanges(lineDivs[i], tokens, results, resolve);
182
+ const lineDiv = lineDivs[i];
183
+ if (lineDiv && tokens && tokens.length > 0) {
184
+ applyLineRanges(lineDiv, tokens, results, resolve);
184
185
  }
185
186
  }
186
187
  return results;
@@ -216,6 +217,7 @@ export function applyHighlightRangesChunked(
216
217
 
217
218
  for (let i = 0; i < codeEl.children.length; i++) {
218
219
  const child = codeEl.children[i];
220
+ if (!child) continue;
219
221
  const lineDivs = child.querySelectorAll("[data-line]");
220
222
  if (
221
223
  lineDivs.length > 0 &&
@@ -235,8 +237,9 @@ export function applyHighlightRangesChunked(
235
237
  const lineDivs = codeEl.querySelectorAll("[data-line]");
236
238
  for (let i = 0; i < lineDivs.length; i++) {
237
239
  const tokens = tokenLines[i];
238
- if (tokens && tokens.length > 0) {
239
- applyLineRanges(lineDivs[i], tokens, allRanges, resolve);
240
+ const lineDiv = lineDivs[i];
241
+ if (lineDiv && tokens && tokens.length > 0) {
242
+ applyLineRanges(lineDiv, tokens, allRanges, resolve);
240
243
  }
241
244
  }
242
245
  return () => cleanupRanges(allRanges);
@@ -253,7 +256,7 @@ export function applyHighlightRangesChunked(
253
256
  const ranges = applyRangesToContainer(
254
257
  wrapper,
255
258
  tokenLines,
256
- chunkLineOffsets[index],
259
+ chunkLineOffsets[index] ?? 0,
257
260
  resolve,
258
261
  );
259
262
  chunkRanges.set(wrapper, ranges);
@@ -269,6 +272,7 @@ export function applyHighlightRangesChunked(
269
272
 
270
273
  for (let i = 0; i < chunkWrappers.length; i++) {
271
274
  const wrapper = chunkWrappers[i];
275
+ if (!wrapper) continue;
272
276
 
273
277
  const handler = (e: Event) => {
274
278
  // contentvisibilityautostatechange fires with .skipped = true when
@@ -347,7 +351,7 @@ export function applyHighlightRangesBatch(
347
351
 
348
352
  const lineDiv = lineDivs[divIndex];
349
353
  const tokens = tokenLines[i];
350
- if (tokens && tokens.length > 0) {
354
+ if (lineDiv && tokens && tokens.length > 0) {
351
355
  applyLineRanges(lineDiv, tokens, results, resolve);
352
356
  }
353
357
  }
@@ -60,11 +60,13 @@ export function resolveSyntaxTokenForMode(
60
60
  mode: "light" | "dark",
61
61
  ): string {
62
62
  if (Array.isArray(value)) {
63
- return mode === "dark" ? value[1] : value[0];
63
+ return (mode === "dark" ? value[1] : value[0]) ?? "";
64
64
  }
65
65
  const match = value.match(/^light-dark\(([^,]+),([^)]+)\)$/);
66
66
  if (match) {
67
- return mode === "dark" ? match[2].trim() : match[1].trim();
67
+ return mode === "dark"
68
+ ? (match[2]?.trim() ?? "")
69
+ : (match[1]?.trim() ?? "");
68
70
  }
69
71
  return value;
70
72
  }
@@ -517,13 +517,13 @@ export function flatTokensToLines(
517
517
  for (const token of tokens) {
518
518
  while (
519
519
  lineIdx < lineStarts.length - 1 &&
520
- token.start >= lineStarts[lineIdx + 1]
520
+ token.start >= (lineStarts[lineIdx + 1] ?? Number.POSITIVE_INFINITY)
521
521
  ) {
522
522
  lineIdx++;
523
523
  }
524
524
 
525
- const lineStart = lineStarts[lineIdx];
526
- result[lineIdx].push({
525
+ const lineStart = lineStarts[lineIdx] ?? 0;
526
+ result[lineIdx]?.push({
527
527
  type: token.type,
528
528
  start: token.start - lineStart,
529
529
  end: token.end - lineStart,
@@ -41,7 +41,7 @@
41
41
  data-slot="command-content"
42
42
  class={className}
43
43
  {showCloseButton}
44
- {portalProps}
44
+ {...portalProps === undefined ? {} : { portalProps }}
45
45
  >
46
46
  <Command {...restProps} bind:value bind:ref {children} />
47
47
  </Dialog.Content>
@@ -31,5 +31,7 @@
31
31
  {heading}
32
32
  </CommandPrimitive.GroupHeading>
33
33
  {/if}
34
- <CommandPrimitive.GroupItems {children} />
34
+ <CommandPrimitive.GroupItems>
35
+ {#if children}{@render children()}{/if}
36
+ </CommandPrimitive.GroupItems>
35
37
  </CommandPrimitive.Group>
@@ -19,12 +19,7 @@
19
19
  data-slot="command-view"
20
20
  class={className}
21
21
  >
22
- <Command.Root
23
- bind:value
24
- bind:ref
25
- {shouldFilter}
26
- {...restProps}
27
- >
22
+ <Command.Root bind:value bind:ref {shouldFilter} {...restProps}>
28
23
  {@render children?.()}
29
24
  </Command.Root>
30
25
  </div>