@lapismd/design-core 0.3.0 → 0.3.1
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/CHANGELOG.md +10 -0
- package/dist/shared/ai/composer-input/ComposerInput.svelte +5 -1
- package/dist/shared/ai/experimental/emoji-picker/EmojiPicker.svelte +9 -0
- package/dist/shared/forms/dialog-frame/DialogFrameExample.svelte +7 -0
- package/dist/shared/shadcn/scroll-area/ScrollAreaStyledLifecycleFixture.svelte +38 -0
- package/dist/shared/shadcn/scroll-area/ScrollAreaStyledLifecycleFixture.svelte.d.ts +6 -0
- package/package.json +2 -2
- package/src/shared/ai/composer-input/ComposerInput.svelte +5 -1
- package/src/shared/ai/experimental/emoji-picker/EmojiPicker.svelte +9 -0
- package/src/shared/forms/dialog-frame/DialogFrameExample.svelte +7 -0
- package/src/shared/shadcn/scroll-area/ScrollAreaStyledLifecycleFixture.svelte +38 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @lapismd/design-core
|
|
2
2
|
|
|
3
|
+
## 0.3.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#16](https://github.com/lapismd/design-core/pull/16) [`f94fb98`](https://github.com/lapismd/design-core/commit/f94fb9852f6c3e54ea57f89c2f185b6f00c68e35) Thanks [@stevejuma](https://github.com/stevejuma)! - Release the Composer Input handle correctly through reactive consumer bindings during teardown while preserving replacement handles. Keep Svelte runtime diagnostics fatal even when component cleanup catches the immediate error.
|
|
8
|
+
|
|
9
|
+
- [#13](https://github.com/lapismd/design-core/pull/13) [`5f533e6`](https://github.com/lapismd/design-core/commit/5f533e656601015b5a5dd3a680f5e97fd13cd1d2) Thanks [@stevejuma](https://github.com/stevejuma)! - Preserve emoji-grid keyboard focus when delayed popover opening frames complete.
|
|
10
|
+
|
|
11
|
+
- [#15](https://github.com/lapismd/design-core/pull/15) [`02756cb`](https://github.com/lapismd/design-core/commit/02756cb32a5405db45cb47cae9e28d448d20651f) Thanks [@stevejuma](https://github.com/stevejuma)! - Require Bits UI 2.19.5 to prevent Scroll Area resize callbacks and text-selection pointer handlers from reading destroyed Svelte state. Make Svelte runtime diagnostics fatal in Storybook tests and preserve the editing Dialog Frame example's initial field focus.
|
|
12
|
+
|
|
3
13
|
## 0.3.0
|
|
4
14
|
|
|
5
15
|
### Minor Changes
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
<script lang="ts">
|
|
6
6
|
import * as Command from "@lapismd/design-core/shadcn/command";
|
|
7
7
|
import type { HTMLAttributes } from "svelte/elements";
|
|
8
|
+
import { untrack } from "svelte";
|
|
8
9
|
import {
|
|
9
10
|
createComposerTokens,
|
|
10
11
|
createPasteAsToken,
|
|
@@ -149,12 +150,15 @@
|
|
|
149
150
|
|
|
150
151
|
$effect(() => {
|
|
151
152
|
handle = inputHandle;
|
|
153
|
+
// A consumer's binding may proxy the plain handle. Retain the identity
|
|
154
|
+
// returned by that binding without making it an effect dependency.
|
|
155
|
+
const assignedHandle = untrack(() => handle);
|
|
152
156
|
context?.setInputHandle(inputHandle);
|
|
153
157
|
return () => {
|
|
154
158
|
triggerSearch.cancel();
|
|
155
159
|
tokens.cleanup();
|
|
156
160
|
context?.setInputHandle(null);
|
|
157
|
-
if (handle ===
|
|
161
|
+
if (handle === assignedHandle) handle = null;
|
|
158
162
|
};
|
|
159
163
|
});
|
|
160
164
|
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
} = $props();
|
|
56
56
|
|
|
57
57
|
let query = $state("");
|
|
58
|
+
let searchRef = $state<HTMLInputElement | null>(null);
|
|
58
59
|
let gridRef = $state<HTMLDivElement | null>(null);
|
|
59
60
|
|
|
60
61
|
const visible = $derived.by(() => {
|
|
@@ -77,6 +78,12 @@
|
|
|
77
78
|
setOpen(false);
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
function focusSearchOnOpen(event: Event): void {
|
|
82
|
+
// Own initial focus so a deferred popover callback cannot undo navigation.
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
searchRef?.focus();
|
|
85
|
+
}
|
|
86
|
+
|
|
80
87
|
function gridButtons(): HTMLButtonElement[] {
|
|
81
88
|
const root = gridRef ?? ref;
|
|
82
89
|
return Array.from(
|
|
@@ -155,9 +162,11 @@
|
|
|
155
162
|
align="start"
|
|
156
163
|
aria-label={label}
|
|
157
164
|
data-ai-chat-part="emoji-content"
|
|
165
|
+
onOpenAutoFocus={focusSearchOnOpen}
|
|
158
166
|
>
|
|
159
167
|
<div bind:this={ref} {...restProps} data-ui-part="emoji-panel">
|
|
160
168
|
<Input
|
|
169
|
+
bind:ref={searchRef}
|
|
161
170
|
bind:value={query}
|
|
162
171
|
aria-label={searchLabel}
|
|
163
172
|
placeholder={searchLabel}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { Input } from "@lapismd/design-core/shadcn/input";
|
|
6
6
|
let open = $state(false);
|
|
7
7
|
let name = $state("");
|
|
8
|
+
let firstField = $state<HTMLInputElement | null>(null);
|
|
8
9
|
</script>
|
|
9
10
|
|
|
10
11
|
<Dialog.Root bind:open>
|
|
@@ -20,6 +21,11 @@
|
|
|
20
21
|
size="large"
|
|
21
22
|
surface="inset"
|
|
22
23
|
class={name ? "record-filled" : "record-empty"}
|
|
24
|
+
onOpenAutoFocus={(event) => {
|
|
25
|
+
if (!firstField) return;
|
|
26
|
+
firstField.focus();
|
|
27
|
+
event.preventDefault();
|
|
28
|
+
}}
|
|
23
29
|
>
|
|
24
30
|
{#snippet sidebar()}<nav aria-label="Sections">
|
|
25
31
|
<Button variant="secondary">Details</Button>
|
|
@@ -27,6 +33,7 @@
|
|
|
27
33
|
{#each Array.from({ length: 24 }) as _, index}<label
|
|
28
34
|
style="display: grid; gap: 0.5rem; margin-block: 1rem;"
|
|
29
35
|
>Field {index + 1}{#if index === 0}<Input
|
|
36
|
+
bind:ref={firstField}
|
|
30
37
|
bind:value={name}
|
|
31
38
|
/>{:else}<Input />{/if}</label
|
|
32
39
|
>{/each}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Button } from "../button/index.js";
|
|
3
|
+
import { ScrollArea } from "./index.js";
|
|
4
|
+
|
|
5
|
+
let { type = "always" }: { type?: "always" | "auto" } = $props();
|
|
6
|
+
let mounted = $state(false);
|
|
7
|
+
let clicks = $state(0);
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<Button onclick={() => (mounted = !mounted)}>Toggle styled viewport</Button>
|
|
11
|
+
<Button onclick={() => clicks++}>Remaining control</Button>
|
|
12
|
+
<p role="status">Remaining clicks: {clicks}</p>
|
|
13
|
+
{#if mounted}
|
|
14
|
+
<ScrollArea
|
|
15
|
+
aria-label="Styled lifecycle"
|
|
16
|
+
{type}
|
|
17
|
+
orientation="both"
|
|
18
|
+
scrollStrategy="styled"
|
|
19
|
+
class="styled-lifecycle-area"
|
|
20
|
+
>
|
|
21
|
+
<div class="styled-lifecycle-content">
|
|
22
|
+
{#each Array.from({ length: 40 }) as _, index}
|
|
23
|
+
<p>Entry {index + 1}</p>
|
|
24
|
+
{/each}
|
|
25
|
+
</div>
|
|
26
|
+
</ScrollArea>
|
|
27
|
+
{/if}
|
|
28
|
+
|
|
29
|
+
<style>
|
|
30
|
+
:global(.styled-lifecycle-area) {
|
|
31
|
+
height: 10rem;
|
|
32
|
+
width: 16rem;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.styled-lifecycle-content {
|
|
36
|
+
width: 30rem;
|
|
37
|
+
}
|
|
38
|
+
</style>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
type?: "always" | "auto";
|
|
3
|
+
};
|
|
4
|
+
declare const ScrollAreaStyledLifecycleFixture: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
5
|
+
type ScrollAreaStyledLifecycleFixture = ReturnType<typeof ScrollAreaStyledLifecycleFixture>;
|
|
6
|
+
export default ScrollAreaStyledLifecycleFixture;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lapismd/design-core",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Shared Lapis design system, forms, shell, and workspace primitives",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"@lucide/svelte": "^1.21.0",
|
|
59
59
|
"@tanstack/svelte-virtual": "^3.13.39",
|
|
60
60
|
"@types/luxon": "^3.4.2",
|
|
61
|
-
"bits-ui": "^2.
|
|
61
|
+
"bits-ui": "^2.19.5",
|
|
62
62
|
"chrono-node": "^2.10.1",
|
|
63
63
|
"clsx": "^2.1.1",
|
|
64
64
|
"codemirror-lang-mermaid": "^0.5.0",
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
<script lang="ts">
|
|
6
6
|
import * as Command from "@lapismd/design-core/shadcn/command";
|
|
7
7
|
import type { HTMLAttributes } from "svelte/elements";
|
|
8
|
+
import { untrack } from "svelte";
|
|
8
9
|
import {
|
|
9
10
|
createComposerTokens,
|
|
10
11
|
createPasteAsToken,
|
|
@@ -149,12 +150,15 @@
|
|
|
149
150
|
|
|
150
151
|
$effect(() => {
|
|
151
152
|
handle = inputHandle;
|
|
153
|
+
// A consumer's binding may proxy the plain handle. Retain the identity
|
|
154
|
+
// returned by that binding without making it an effect dependency.
|
|
155
|
+
const assignedHandle = untrack(() => handle);
|
|
152
156
|
context?.setInputHandle(inputHandle);
|
|
153
157
|
return () => {
|
|
154
158
|
triggerSearch.cancel();
|
|
155
159
|
tokens.cleanup();
|
|
156
160
|
context?.setInputHandle(null);
|
|
157
|
-
if (handle ===
|
|
161
|
+
if (handle === assignedHandle) handle = null;
|
|
158
162
|
};
|
|
159
163
|
});
|
|
160
164
|
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
} = $props();
|
|
56
56
|
|
|
57
57
|
let query = $state("");
|
|
58
|
+
let searchRef = $state<HTMLInputElement | null>(null);
|
|
58
59
|
let gridRef = $state<HTMLDivElement | null>(null);
|
|
59
60
|
|
|
60
61
|
const visible = $derived.by(() => {
|
|
@@ -77,6 +78,12 @@
|
|
|
77
78
|
setOpen(false);
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
function focusSearchOnOpen(event: Event): void {
|
|
82
|
+
// Own initial focus so a deferred popover callback cannot undo navigation.
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
searchRef?.focus();
|
|
85
|
+
}
|
|
86
|
+
|
|
80
87
|
function gridButtons(): HTMLButtonElement[] {
|
|
81
88
|
const root = gridRef ?? ref;
|
|
82
89
|
return Array.from(
|
|
@@ -155,9 +162,11 @@
|
|
|
155
162
|
align="start"
|
|
156
163
|
aria-label={label}
|
|
157
164
|
data-ai-chat-part="emoji-content"
|
|
165
|
+
onOpenAutoFocus={focusSearchOnOpen}
|
|
158
166
|
>
|
|
159
167
|
<div bind:this={ref} {...restProps} data-ui-part="emoji-panel">
|
|
160
168
|
<Input
|
|
169
|
+
bind:ref={searchRef}
|
|
161
170
|
bind:value={query}
|
|
162
171
|
aria-label={searchLabel}
|
|
163
172
|
placeholder={searchLabel}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { Input } from "@lapismd/design-core/shadcn/input";
|
|
6
6
|
let open = $state(false);
|
|
7
7
|
let name = $state("");
|
|
8
|
+
let firstField = $state<HTMLInputElement | null>(null);
|
|
8
9
|
</script>
|
|
9
10
|
|
|
10
11
|
<Dialog.Root bind:open>
|
|
@@ -20,6 +21,11 @@
|
|
|
20
21
|
size="large"
|
|
21
22
|
surface="inset"
|
|
22
23
|
class={name ? "record-filled" : "record-empty"}
|
|
24
|
+
onOpenAutoFocus={(event) => {
|
|
25
|
+
if (!firstField) return;
|
|
26
|
+
firstField.focus();
|
|
27
|
+
event.preventDefault();
|
|
28
|
+
}}
|
|
23
29
|
>
|
|
24
30
|
{#snippet sidebar()}<nav aria-label="Sections">
|
|
25
31
|
<Button variant="secondary">Details</Button>
|
|
@@ -27,6 +33,7 @@
|
|
|
27
33
|
{#each Array.from({ length: 24 }) as _, index}<label
|
|
28
34
|
style="display: grid; gap: 0.5rem; margin-block: 1rem;"
|
|
29
35
|
>Field {index + 1}{#if index === 0}<Input
|
|
36
|
+
bind:ref={firstField}
|
|
30
37
|
bind:value={name}
|
|
31
38
|
/>{:else}<Input />{/if}</label
|
|
32
39
|
>{/each}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Button } from "../button/index.js";
|
|
3
|
+
import { ScrollArea } from "./index.js";
|
|
4
|
+
|
|
5
|
+
let { type = "always" }: { type?: "always" | "auto" } = $props();
|
|
6
|
+
let mounted = $state(false);
|
|
7
|
+
let clicks = $state(0);
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<Button onclick={() => (mounted = !mounted)}>Toggle styled viewport</Button>
|
|
11
|
+
<Button onclick={() => clicks++}>Remaining control</Button>
|
|
12
|
+
<p role="status">Remaining clicks: {clicks}</p>
|
|
13
|
+
{#if mounted}
|
|
14
|
+
<ScrollArea
|
|
15
|
+
aria-label="Styled lifecycle"
|
|
16
|
+
{type}
|
|
17
|
+
orientation="both"
|
|
18
|
+
scrollStrategy="styled"
|
|
19
|
+
class="styled-lifecycle-area"
|
|
20
|
+
>
|
|
21
|
+
<div class="styled-lifecycle-content">
|
|
22
|
+
{#each Array.from({ length: 40 }) as _, index}
|
|
23
|
+
<p>Entry {index + 1}</p>
|
|
24
|
+
{/each}
|
|
25
|
+
</div>
|
|
26
|
+
</ScrollArea>
|
|
27
|
+
{/if}
|
|
28
|
+
|
|
29
|
+
<style>
|
|
30
|
+
:global(.styled-lifecycle-area) {
|
|
31
|
+
height: 10rem;
|
|
32
|
+
width: 16rem;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.styled-lifecycle-content {
|
|
36
|
+
width: 30rem;
|
|
37
|
+
}
|
|
38
|
+
</style>
|