@reddb-io/ui-kit 0.4.0 → 0.4.2

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 (44) hide show
  1. package/package.json +1 -1
  2. package/vendor/ds/design-system.receipt.json +44 -19
  3. package/vendor/ds/kits/app/README.md +38 -0
  4. package/vendor/ds/kits/app/src/change-review.variants.ts +27 -0
  5. package/vendor/ds/kits/app/src/composites/ChangeReview.svelte +110 -0
  6. package/vendor/ds/kits/app/src/composites/ErrorBoundary.svelte +50 -0
  7. package/vendor/ds/kits/app/src/composites/ShortcutHelp.svelte +50 -0
  8. package/vendor/ds/kits/app/src/composites/WorkspaceTabs.svelte +150 -0
  9. package/vendor/ds/kits/app/src/core/change-review.ts +37 -0
  10. package/vendor/ds/kits/app/src/core/contracts/changereview.ts +38 -0
  11. package/vendor/ds/kits/app/src/core/contracts/errorboundary.ts +15 -0
  12. package/vendor/ds/kits/app/src/core/contracts/shortcuthelp.ts +16 -0
  13. package/vendor/ds/kits/app/src/core/contracts/workspacetabs.ts +22 -0
  14. package/vendor/ds/kits/app/src/core/shortcut-help.ts +40 -0
  15. package/vendor/ds/kits/app/src/core/workspace-tabs.ts +69 -0
  16. package/vendor/ds/kits/app/src/error-boundary.variants.ts +12 -0
  17. package/vendor/ds/kits/app/src/index.ts +21 -0
  18. package/vendor/ds/kits/app/src/recipes.ts +8 -0
  19. package/vendor/ds/kits/app/src/shortcut-help.variants.ts +17 -0
  20. package/vendor/ds/kits/app/src/workspace-tabs.variants.ts +30 -0
  21. package/vendor/ds/kits/app/test/coherence.test.ts +2 -2
  22. package/vendor/ds/kits/app/test/density.test.ts +3 -3
  23. package/vendor/ds/kits/app/test/fixtures/ChangeReview.svelte +11 -0
  24. package/vendor/ds/kits/app/test/fixtures/ShortcutHelp.svelte +11 -0
  25. package/vendor/ds/kits/app/test/fixtures/WorkspaceTabs.svelte +11 -0
  26. package/vendor/ds/kits/app/test/react-consumer.tsx +3 -0
  27. package/vendor/ds/kits/app/test/speed-dial.test.ts +1 -1
  28. package/vendor/ds/kits/app/test/tree-shaking.test.ts +6 -0
  29. package/vendor/ds/kits/app/test/workspace-harvest.test.ts +187 -0
  30. package/vendor/ds/kits/base/README.md +16 -0
  31. package/vendor/ds/kits/base/src/SecretValue.svelte +86 -0
  32. package/vendor/ds/kits/base/src/button.variants.ts +6 -4
  33. package/vendor/ds/kits/base/src/core/contracts/secretvalue.ts +27 -0
  34. package/vendor/ds/kits/base/src/core/secret-value.ts +78 -0
  35. package/vendor/ds/kits/base/src/index.ts +6 -0
  36. package/vendor/ds/kits/base/src/recipes.ts +2 -0
  37. package/vendor/ds/kits/base/src/secret-value.variants.ts +16 -0
  38. package/vendor/ds/kits/base/test/components.test.ts +1 -0
  39. package/vendor/ds/kits/base/test/control-size.test.ts +1 -1
  40. package/vendor/ds/kits/base/test/disclosure.test.ts +1 -1
  41. package/vendor/ds/kits/base/test/dropdown-menu.test.ts +1 -1
  42. package/vendor/ds/kits/base/test/fixtures/SecretValue.svelte +11 -0
  43. package/vendor/ds/kits/base/test/navbar.test.ts +1 -1
  44. package/vendor/ds/kits/base/test/secret-value.test.ts +94 -0
@@ -0,0 +1,150 @@
1
+ <script module lang="ts">
2
+ export type { WorkspaceTab } from "../core/workspace-tabs";
3
+ </script>
4
+
5
+ <script lang="ts">
6
+ import { type Snippet } from "svelte";
7
+ import { Tabs as Bits } from "bits-ui";
8
+ import { Button, quietControl } from "@reddb-io/design-system/base";
9
+ import {
10
+ workspaceTabs,
11
+ workspaceSelection,
12
+ mountWorkspaceFocus,
13
+ } from "../core/workspace-tabs";
14
+ import { workspaceTabsAppearance } from "../workspace-tabs.variants";
15
+ import type { WorkspaceTabsContract } from "../core/contracts/workspacetabs";
16
+ interface Props extends WorkspaceTabsContract {
17
+ /** Native content per document; inactive panels remain mounted and hidden. */
18
+ children?: Snippet<[{ id: string }]>;
19
+ /** Additional composition classes. */
20
+ class?: string;
21
+ }
22
+ let {
23
+ label,
24
+ items,
25
+ selected,
26
+ onselectedchange,
27
+ oncloserequest,
28
+ closeLabel = "Close",
29
+ dirtyLabel = "Unsaved changes",
30
+ busyLabel = "Running",
31
+ emptyLabel = "No open documents",
32
+ children,
33
+ class: className,
34
+ }: Props = $props();
35
+ const s = workspaceTabsAppearance(),
36
+ uid = $props.id();
37
+ let local = $state<string | null>(null);
38
+ let root: HTMLDivElement;
39
+ let focus: ReturnType<typeof mountWorkspaceFocus> | undefined;
40
+ const documents = $derived(workspaceTabs(items));
41
+ const active = $derived(
42
+ workspaceSelection(documents, selected === undefined ? local : selected),
43
+ );
44
+ function select(id: string) {
45
+ if (id === active) return;
46
+ if (selected === undefined) local = id;
47
+ onselectedchange?.(id);
48
+ }
49
+ function requestClose(id: string) {
50
+ const item = documents.find((item) => item.id === id);
51
+ if (item && !item.disabled && item.closable !== false) oncloserequest?.(id);
52
+ }
53
+ $effect(() => {
54
+ focus = mountWorkspaceFocus(root);
55
+ return () => focus?.destroy();
56
+ });
57
+ $effect(() => {
58
+ focus?.sync(documents, active);
59
+ });
60
+ </script>
61
+
62
+ <div bind:this={root} data-workspace-tabs class={s.root({ class: className })}>
63
+ <Bits.Root
64
+ value={active}
65
+ onValueChange={select}
66
+ activationMode="automatic"
67
+ loop
68
+ >
69
+ <div
70
+ data-workspace-scroll
71
+ class={s.frame()}
72
+ style:grid-template-columns={`repeat(${Math.max(documents.length, 1)},max-content)`}
73
+ >
74
+ <Bits.List
75
+ aria-label={label}
76
+ class={s.list()}
77
+ onkeydown={(event) => {
78
+ if (
79
+ event.key !== "Delete" ||
80
+ !(event.target instanceof HTMLElement) ||
81
+ event.target.getAttribute("role") !== "tab"
82
+ )
83
+ return;
84
+ const id = event.target.dataset.workspaceTab;
85
+ if (id && oncloserequest) {
86
+ event.preventDefault();
87
+ requestClose(id);
88
+ }
89
+ }}
90
+ >
91
+ {#each documents as item (item.id)}
92
+ <Bits.Trigger
93
+ value={item.id}
94
+ disabled={item.disabled}
95
+ data-workspace-item={item.id}
96
+ data-workspace-tab={item.id}
97
+ aria-label={`${item.label}${item.busy ? `, ${busyLabel}` : ""}`}
98
+ aria-describedby={item.dirty || item.busy
99
+ ? `${uid}-${encodeURIComponent(item.id)}-status`
100
+ : undefined}
101
+ class={quietControl({
102
+ ink: "foreground",
103
+ class: s.trigger({
104
+ withClose: !!oncloserequest && item.closable !== false,
105
+ }),
106
+ })}
107
+ >
108
+ <span>{item.label}</span>
109
+ {#if item.dirty || item.busy}<span
110
+ class={s.status()}
111
+ aria-hidden="true"
112
+ >{item.dirty ? "•" : ""}{item.busy ? ` ${busyLabel}` : ""}</span
113
+ ><span
114
+ id={`${uid}-${encodeURIComponent(item.id)}-status`}
115
+ class="sr-only"
116
+ >{[item.dirty ? dirtyLabel : "", item.busy ? busyLabel : ""]
117
+ .filter(Boolean)
118
+ .join(", ")}</span
119
+ >{/if}
120
+ </Bits.Trigger>
121
+ {/each}
122
+ </Bits.List>
123
+ {#each documents as item, index (item.id)}
124
+ {#if oncloserequest && item.closable !== false}<div
125
+ data-workspace-item={item.id}
126
+ class={s.close()}
127
+ style:grid-column={index + 1}
128
+ >
129
+ <Button
130
+ size="sm"
131
+ variant="ghost"
132
+ disabled={item.disabled}
133
+ tabindex={active === item.id ? 0 : -1}
134
+ class={s.closeButton()}
135
+ aria-label={`${closeLabel} ${item.label}`}
136
+ onclick={() => requestClose(item.id)}
137
+ ><span aria-hidden="true">×</span></Button
138
+ >
139
+ </div>{/if}
140
+ {/each}
141
+ </div>
142
+ {#each documents as item (item.id)}<Bits.Content
143
+ value={item.id}
144
+ data-workspace-panel={item.id}
145
+ class={s.panel()}
146
+ tabindex={0}>{@render children?.({ id: item.id })}</Bits.Content
147
+ >{/each}
148
+ </Bits.Root>
149
+ {#if !documents.length}<p class={s.empty()}>{emptyLabel}</p>{/if}
150
+ </div>
@@ -0,0 +1,37 @@
1
+ export type ReviewState = "pending" | "applying" | "applied" | "failed";
2
+ export interface ReviewChange {
3
+ /** Unique stable identity; never a row position. */
4
+ id: string;
5
+ /** Human-readable changed entity or property. */
6
+ label: string;
7
+ /** Caller-formatted previous value; never executed. */
8
+ before: string;
9
+ /** Caller-formatted proposed value; never executed. */
10
+ after: string;
11
+ /** Observed result; omitted means pending. */
12
+ state?: ReviewState;
13
+ /** Caller-safe failure explanation. */
14
+ error?: string;
15
+ /** Optional context for the change. */
16
+ description?: string;
17
+ }
18
+ export function reviewChanges(items: readonly ReviewChange[]) {
19
+ const ids = new Set<string>();
20
+ for (const item of items) {
21
+ if (!item.id.trim() || ids.has(item.id))
22
+ throw new Error("ChangeReview requires unique non-empty ids.");
23
+ if (
24
+ item.state &&
25
+ !["pending", "applying", "applied", "failed"].includes(item.state)
26
+ )
27
+ throw new Error("Unknown review state.");
28
+ ids.add(item.id);
29
+ }
30
+ return {
31
+ items,
32
+ actionable: items
33
+ .filter((item) => item.state !== "applied")
34
+ .map((item) => item.id),
35
+ applying: items.some((item) => item.state === "applying"),
36
+ };
37
+ }
@@ -0,0 +1,38 @@
1
+ import type { ReviewChange } from "../change-review";
2
+ /** Review of caller-owned changes, with explicit request-only batch actions. */
3
+ export interface ChangeReviewContract {
4
+ /** Visible and accessible review name. */
5
+ title: string;
6
+ /** Explicit proposed values and observed per-item results. */
7
+ items: readonly ReviewChange[];
8
+ /** Disable actions during application-owned work. */
9
+ applying?: boolean;
10
+ /** Explain why changes cannot be applied. */
11
+ blockedReason?: string;
12
+ /** Requests application of pending/failed ids; no mutation or success is inferred. */
13
+ onapplyrequest?: (ids: readonly string[]) => void;
14
+ /** Requests discarding one or all pending/failed ids. */
15
+ ondiscardrequest?: (ids: readonly string[]) => void;
16
+ /** Name for previous values. */
17
+ beforeLabel?: string;
18
+ /** Name for proposed values. */
19
+ afterLabel?: string;
20
+ /** Batch apply action label. */
21
+ applyLabel?: string;
22
+ /** Batch discard action label. */
23
+ discardLabel?: string;
24
+ /** Per-item discard action prefix. */
25
+ discardItemLabel?: string;
26
+ /** Empty review explanation. */
27
+ emptyLabel?: string;
28
+ /** Localized pending state. */
29
+ pendingLabel?: string;
30
+ /** Localized applying state. */
31
+ applyingLabel?: string;
32
+ /** Localized applied state. */
33
+ appliedLabel?: string;
34
+ /** Localized failed state. */
35
+ failedLabel?: string;
36
+ /** Outline depth of the visible review title. */
37
+ headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
38
+ }
@@ -0,0 +1,15 @@
1
+ /** Render-recovery presentation shared by native client error boundaries. */
2
+ export interface ErrorBoundaryContract {
3
+ /** Safe visible fallback title; raw exception text is never displayed by default. */
4
+ title?: string;
5
+ /** Caller-safe explanation of the failed region. */
6
+ description?: string;
7
+ /** Explicit retry action label. */
8
+ retryLabel?: string;
9
+ /** Changing identity remounts the region and clears its captured render failure. */
10
+ resetKey?: string | number;
11
+ /** Observes native client render/lifecycle failures; logging remains application-owned. */
12
+ onfailure?: (error: unknown) => void;
13
+ /** Called before retrying the region; the application can repair the failing state. */
14
+ onretry?: () => void;
15
+ }
@@ -0,0 +1,16 @@
1
+ import type { ShortcutGroup } from "../shortcut-help";
2
+ /** Contextual keyboard reference; command registration remains application-owned. */
3
+ export interface ShortcutHelpContract {
4
+ /** Accessible and visible dialog title. */
5
+ title: string;
6
+ /** Contextual groups with caller-supplied platform-specific key labels. */
7
+ groups: readonly ShortcutGroup[];
8
+ /** Optional invoking control; omit to drive open from the application. */
9
+ triggerLabel?: string;
10
+ /** Whether the keyboard reference is open. */
11
+ open?: boolean;
12
+ /** Reports any requested open-state change, including Escape and dismissal. */
13
+ onopenchange?: (open: boolean) => void;
14
+ /** Empty reference explanation. */
15
+ emptyLabel?: string;
16
+ }
@@ -0,0 +1,22 @@
1
+ import type { WorkspaceTab } from "../workspace-tabs";
2
+ /** Caller-owned document tabs; no persistence or close decision is inferred. */
3
+ export interface WorkspaceTabsContract {
4
+ /** Accessible name for the workspace tablist. */
5
+ label: string;
6
+ /** Documents in display order, with stable unique ids. */
7
+ items: readonly WorkspaceTab[];
8
+ /** Selected document; absent or removed selections fall back to the first enabled item. */
9
+ selected?: string | null;
10
+ /** Requests selection without changing document contents. */
11
+ onselectedchange?: (id: string) => void;
12
+ /** Requests closing; the application confirms dirty drafts and removes accepted items. */
13
+ oncloserequest?: (id: string) => void;
14
+ /** Accessible prefix for each close action. */
15
+ closeLabel?: string;
16
+ /** Accessible description for unsaved changes. */
17
+ dirtyLabel?: string;
18
+ /** Accessible description for active execution. */
19
+ busyLabel?: string;
20
+ /** Explanation when no documents are open. */
21
+ emptyLabel?: string;
22
+ }
@@ -0,0 +1,40 @@
1
+ export interface ShortcutCommand {
2
+ /** Stable identity within its group. */
3
+ id: string;
4
+ /** Visible purpose of the command. */
5
+ label: string;
6
+ /** Ordered keys of one chord, supplied for the application's platform. */
7
+ keys: readonly string[];
8
+ }
9
+ export interface ShortcutGroup {
10
+ /** Unique group identity. */
11
+ id: string;
12
+ /** Context in which these commands apply. */
13
+ label: string;
14
+ /** Optional context explanation. */
15
+ description?: string;
16
+ /** Displayed commands; this component registers no keyboard handlers. */
17
+ items: readonly ShortcutCommand[];
18
+ }
19
+ export function shortcutGroups(groups: readonly ShortcutGroup[]) {
20
+ const ids = new Set<string>();
21
+ for (const group of groups) {
22
+ if (!group.id.trim() || ids.has(group.id))
23
+ throw new Error("ShortcutHelp requires unique group ids.");
24
+ ids.add(group.id);
25
+ const commands = new Set<string>();
26
+ for (const item of group.items) {
27
+ if (
28
+ !item.id.trim() ||
29
+ commands.has(item.id) ||
30
+ !item.keys.length ||
31
+ item.keys.some((key) => !key.trim())
32
+ )
33
+ throw new Error(
34
+ "ShortcutHelp requires unique command ids and non-empty chords.",
35
+ );
36
+ commands.add(item.id);
37
+ }
38
+ }
39
+ return groups.filter((group) => group.items.length);
40
+ }
@@ -0,0 +1,69 @@
1
+ export interface WorkspaceTab {
2
+ /** Stable application-owned identity. */
3
+ id: string;
4
+ /** Visible tab name. */
5
+ label: string;
6
+ /** Whether the application accepts close requests. */
7
+ closable?: boolean;
8
+ /** Caller-observed unsaved changes, independent of execution. */
9
+ dirty?: boolean;
10
+ /** Caller-observed operation in progress. */
11
+ busy?: boolean;
12
+ /** Prevent selection and closing. */
13
+ disabled?: boolean;
14
+ }
15
+ export function workspaceTabs(
16
+ items: readonly WorkspaceTab[],
17
+ ): readonly WorkspaceTab[] {
18
+ const ids = new Set<string>();
19
+ for (const item of items) {
20
+ if (!item.id.trim() || ids.has(item.id))
21
+ throw new Error("WorkspaceTabs requires unique non-empty ids.");
22
+ ids.add(item.id);
23
+ }
24
+ return items;
25
+ }
26
+ export function workspaceSelection(
27
+ items: readonly WorkspaceTab[],
28
+ selected?: string | null,
29
+ ): string {
30
+ return (
31
+ items.find((item) => item.id === selected && !item.disabled)?.id ??
32
+ items.find((item) => !item.disabled)?.id ??
33
+ ""
34
+ );
35
+ }
36
+ /** Restore focus only when removing the workspace control that owned it. */
37
+ export function mountWorkspaceFocus(root: HTMLElement) {
38
+ let focused: string | null = null;
39
+ const remember = (event: FocusEvent) => {
40
+ focused =
41
+ (event.target as HTMLElement).closest<HTMLElement>(
42
+ "[data-workspace-item]",
43
+ )?.dataset.workspaceItem ?? null;
44
+ };
45
+ root.addEventListener("focusin", remember);
46
+ return {
47
+ sync(items: readonly WorkspaceTab[], selected: string) {
48
+ if (
49
+ !focused ||
50
+ items.some((item) => item.id === focused && !item.disabled)
51
+ )
52
+ return;
53
+ focused = null;
54
+ const active = root.ownerDocument.activeElement;
55
+ if (
56
+ !active ||
57
+ active === root.ownerDocument.body ||
58
+ root.contains(active)
59
+ ) {
60
+ [...root.querySelectorAll<HTMLButtonElement>('[role="tab"]')]
61
+ .find((item) => item.dataset.workspaceTab === selected)
62
+ ?.focus();
63
+ }
64
+ },
65
+ destroy() {
66
+ root.removeEventListener("focusin", remember);
67
+ },
68
+ };
69
+ }
@@ -0,0 +1,12 @@
1
+ import { tv } from "tailwind-variants";
2
+ import { typeRoleMerge } from "@reddb-io/design-system/base/recipes";
3
+ export const errorBoundary = tv(
4
+ {
5
+ slots: {
6
+ root: "@container/error-boundary grid min-w-0 justify-items-start gap-[var(--reddb-spatial-layout-gap-sm)] p-[var(--reddb-spatial-inset-md)] text-foreground",
7
+ title: "m-0 text-heading [overflow-wrap:anywhere]",
8
+ description: "m-0 text-body text-ink-muted [overflow-wrap:anywhere]",
9
+ },
10
+ },
11
+ typeRoleMerge,
12
+ );
@@ -305,6 +305,11 @@ export type PrimitiveName = (typeof PRIMITIVES)[number];
305
305
  * the Taxonomy's other half, kept for the same reason.
306
306
  */
307
307
  export const COMPOSITES = [
308
+ "WorkspaceTabs",
309
+ "ChangeReview",
310
+ "ErrorBoundary",
311
+ "ShortcutHelp",
312
+
308
313
  "EntityInspector",
309
314
  "EntityJourney",
310
315
  "SettingsRow",
@@ -362,6 +367,10 @@ export type ComponentName = PrimitiveName | CompositeName;
362
367
  * capabilities use the same shared purpose vocabulary.
363
368
  */
364
369
  export const COMPONENT_PURPOSES = {
370
+ WorkspaceTabs: "Navigation",
371
+ ChangeReview: "Data display",
372
+ ErrorBoundary: "Feedback",
373
+ ShortcutHelp: "Navigation",
365
374
  EntityInspector: "Data display",
366
375
  EntityJourney: "Data display",
367
376
  SettingsRow: "Data input",
@@ -427,3 +436,15 @@ export type { InspectorEntity } from "./core/entity-inspector";
427
436
 
428
437
  export { default as EntityJourney } from "./composites/EntityJourney.svelte";
429
438
  export type { JourneyStage, JourneyState } from "./core/entity-journey";
439
+
440
+ export { default as WorkspaceTabs } from "./composites/WorkspaceTabs.svelte";
441
+
442
+ export { default as ChangeReview } from "./composites/ChangeReview.svelte";
443
+
444
+ export { default as ErrorBoundary } from "./composites/ErrorBoundary.svelte";
445
+
446
+ export { default as ShortcutHelp } from "./composites/ShortcutHelp.svelte";
447
+
448
+ export type { WorkspaceTab } from "./core/workspace-tabs";
449
+ export type { ReviewChange, ReviewState } from "./core/change-review";
450
+ export type { ShortcutGroup, ShortcutCommand } from "./core/shortcut-help";
@@ -42,3 +42,11 @@ export * from "./composites/settings-row.variants";
42
42
 
43
43
  export * from "./entity-inspector.variants";
44
44
  export * from "./entity-journey.variants";
45
+
46
+ export * from "./workspace-tabs.variants";
47
+
48
+ export * from "./change-review.variants";
49
+
50
+ export * from "./error-boundary.variants";
51
+
52
+ export * from "./shortcut-help.variants";
@@ -0,0 +1,17 @@
1
+ import { tv } from "tailwind-variants";
2
+ import { typeRoleMerge } from "@reddb-io/design-system/base/recipes";
3
+ export const shortcutHelp = tv(
4
+ {
5
+ slots: {
6
+ root: "@container/shortcut-help grid min-w-0 gap-[var(--reddb-spatial-layout-gap-md)] text-foreground",
7
+ group: "grid min-w-0 gap-[var(--reddb-spatial-gap-md)]",
8
+ title: "m-0 text-heading [overflow-wrap:anywhere]",
9
+ description: "m-0 text-caption text-ink-muted [overflow-wrap:anywhere]",
10
+ list: "m-0 grid min-w-0 list-none p-0",
11
+ item: "flex min-w-0 flex-wrap items-center justify-between gap-[var(--reddb-spatial-gap-sm)] border-b border-muted py-[var(--reddb-spatial-inset-sm)]",
12
+ name: "min-w-0 text-body [overflow-wrap:anywhere]",
13
+ keys: "inline-flex max-w-full flex-wrap items-center gap-[var(--reddb-spatial-gap-sm)]",
14
+ },
15
+ },
16
+ typeRoleMerge,
17
+ );
@@ -0,0 +1,30 @@
1
+ import { tv } from "tailwind-variants";
2
+ import { typeRoleMerge } from "@reddb-io/design-system/base/recipes";
3
+ export const workspaceTabsAppearance = tv(
4
+ {
5
+ slots: {
6
+ root: "@container/workspace-tabs min-w-0 text-foreground",
7
+ frame:
8
+ "grid min-w-0 max-w-full gap-x-[var(--reddb-spatial-gap-sm)] overflow-x-auto border-b border-muted p-[var(--reddb-spatial-inset-sm)]",
9
+ list: "col-span-full row-start-1 grid min-w-0 grid-cols-subgrid",
10
+ trigger:
11
+ "relative flex min-h-[var(--reddb-spatial-control-height-md)] max-w-[calc(var(--reddb-spatial-control-height-md)*8)] min-w-[var(--reddb-spatial-target-min)] items-center justify-start gap-[var(--reddb-spatial-gap-sm)] rounded-t-[var(--reddb-radius-sm)] border-b-2 border-transparent px-[var(--reddb-spatial-inset-sm)] py-[var(--reddb-spatial-inset-sm)] text-body text-start [overflow-wrap:anywhere] aria-selected:border-foreground aria-selected:bg-foreground/10 disabled:opacity-50",
12
+ status: "inline-flex shrink-0 items-center text-caption text-ink-muted",
13
+ close:
14
+ "relative z-10 pointer-events-none row-start-1 flex items-center justify-self-end self-stretch",
15
+ closeButton:
16
+ "pointer-events-auto w-[var(--reddb-spatial-control-height-sm)] min-w-[var(--reddb-spatial-target-min)]",
17
+ panel:
18
+ "min-w-0 p-[var(--reddb-spatial-inset-md)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus",
19
+ empty: "m-0 p-[var(--reddb-spatial-inset-md)] text-body text-ink-muted",
20
+ },
21
+ variants: {
22
+ withClose: {
23
+ true: {
24
+ trigger: "pe-[var(--reddb-spatial-control-height-sm)]",
25
+ },
26
+ },
27
+ },
28
+ },
29
+ typeRoleMerge,
30
+ );
@@ -149,10 +149,10 @@ describe("the application layout composites", () => {
149
149
  describe("the application Kit's event dialect", () => {
150
150
  /** Discrete DS events: a noun, never a native event name — except `onselect`. */
151
151
  // PlaybackControls emits a discrete step request; the application owns execution.
152
- const DISCRETE = new Set(["ondismiss", "onremove", "onstep"]);
152
+ const DISCRETE = new Set(["ondismiss", "onremove", "onstep", "oncloserequest", "onapplyrequest", "ondiscardrequest", "onfailure", "onretry"]);
153
153
  /** The sanctioned exception (maintainer decision, 2026-10-06): item activation. */
154
154
  const SANCTIONED = new Set(["onselect"]);
155
- const NATIVE = new Set(["onchange", "oninput", "onclick"]);
155
+ const NATIVE = new Set(["onchange", "oninput", "onclick", "onclose", "onerror"]);
156
156
 
157
157
  it("names every callback in the one dialect, with onselect as the sanctioned exception", () => {
158
158
  const offenders: string[] = [];
@@ -217,17 +217,17 @@ const SPECIMENS: readonly Specimen[] = [
217
217
  {
218
218
  name: "Button, size sm",
219
219
  classes: () => button({ size: "sm" }),
220
- routed: [HEIGHT_SM, inset("px", "sm"), GAP_MD],
220
+ routed: [{ ...HEIGHT_SM, position: "min-h" }, inset("px", "sm"), GAP_MD],
221
221
  },
222
222
  {
223
223
  name: "Button, size md",
224
224
  classes: () => button({ size: "md" }),
225
- routed: [HEIGHT_MD, inset("px", "md"), GAP_MD],
225
+ routed: [{ ...HEIGHT_MD, position: "min-h" }, inset("px", "md"), GAP_MD],
226
226
  },
227
227
  {
228
228
  name: "Button, size lg",
229
229
  classes: () => button({ size: "lg" }),
230
- routed: [HEIGHT_LG, inset("px", "lg"), GAP_MD],
230
+ routed: [{ ...HEIGHT_LG, position: "min-h" }, inset("px", "lg"), GAP_MD],
231
231
  },
232
232
  {
233
233
  name: "Card header, padding md",
@@ -0,0 +1,11 @@
1
+ <script lang="ts">
2
+ import { untrack } from "svelte";
3
+ import ChangeReview from "../../src/composites/ChangeReview.svelte";
4
+ let { options }: { options: any } = $props();
5
+ let current = $state(untrack(() => options));
6
+ export function update(next: any) {
7
+ current = next;
8
+ }
9
+ </script>
10
+
11
+ <ChangeReview {...current} />
@@ -0,0 +1,11 @@
1
+ <script lang="ts">
2
+ import { untrack } from "svelte";
3
+ import ShortcutHelp from "../../src/composites/ShortcutHelp.svelte";
4
+ let { options }: { options: any } = $props();
5
+ let current = $state(untrack(() => options));
6
+ export function update(next: any) {
7
+ current = next;
8
+ }
9
+ </script>
10
+
11
+ <ShortcutHelp {...current} />
@@ -0,0 +1,11 @@
1
+ <script lang="ts">
2
+ import { untrack } from "svelte";
3
+ import WorkspaceTabs from "../../src/composites/WorkspaceTabs.svelte";
4
+ let { options }: { options: any } = $props();
5
+ let current = $state(untrack(() => options));
6
+ export function update(next: any) {
7
+ current = next;
8
+ }
9
+ </script>
10
+
11
+ <WorkspaceTabs {...current} />
@@ -34,3 +34,6 @@ const invalidEvent = <CheckoutForm onSubmit={(value: string)=>value.trim()} />;
34
34
  // @ts-expect-error A span ref cannot target a form.
35
35
  const invalidRef = <CheckoutForm ref={createRef<HTMLSpanElement>()} />;
36
36
  void [missingMedia, missingAction, invalidTask, invalidEvent, invalidRef];
37
+
38
+ import { WorkspaceTabs, ChangeReview, ShortcutHelp, ErrorBoundary } from "../react/src";
39
+ export const recoveredWorkspace = <ErrorBoundary onfailure={error => String(error)}><WorkspaceTabs label="Documents" items={[{id:"a",label:"Draft",dirty:true}]} oncloserequest={id => id.trim()}>{item => item.id}</WorkspaceTabs><ChangeReview title="Review" items={[{id:"a",label:"Timeout",before:"1",after:"2"}]} onapplyrequest={ids => ids.map(id => id.trim())} /><ShortcutHelp title="Keys" groups={[]} /></ErrorBoundary>;
@@ -48,6 +48,6 @@ describe("the application SpeedDial", () => {
48
48
 
49
49
  const surface = document.querySelector<HTMLElement>("[data-popover-surface]")!;
50
50
  expect(classes(surface)).toEqual(classesOf(speedDial({ size: "sm" }).content()));
51
- expect(classes(surface.querySelector("[data-speed-dial-action]")!).has("h-[var(--reddb-spatial-control-height-sm)]")).toBe(true);
51
+ expect(classes(surface.querySelector("[data-speed-dial-action]")!).has("min-h-[var(--reddb-spatial-control-height-sm)]")).toBe(true);
52
52
  });
53
53
  });
@@ -103,6 +103,11 @@ async function bundle(entry: string): Promise<string> {
103
103
  * variants module and fails if a marker is written by more or fewer than one.
104
104
  */
105
105
  const MARKERS: Readonly<Record<string, string>> = {
106
+ WorkspaceTabs: "@container/workspace-tabs",
107
+ ChangeReview: "@container/change-review",
108
+ ErrorBoundary: "@container/error-boundary",
109
+ ShortcutHelp: "@container/shortcut-help",
110
+ SecretValue: "@container/secret-value",
106
111
  EntityJourney: "@container/entity-journey",
107
112
  EntityInspector: "flex min-w-0 flex-col overflow-hidden rounded-[var(--reddb-radius-lg)] border border-muted bg-elevation-raised-surface text-foreground",
108
113
  BottomNavigation: "m-0 flex list-none items-stretch gap-[var(--reddb-spatial-gap-sm)] p-0",
@@ -235,6 +240,7 @@ const variantsModules = [
235
240
  join(BASE_SRC, "button.variants.ts"),
236
241
  join(BASE_SRC, "dropdown-menu.variants.ts"),
237
242
  join(BASE_SRC, "kbd.variants.ts"),
243
+ join(BASE_SRC, "secret-value.variants.ts"),
238
244
  ];
239
245
 
240
246
  /**