entasis 0.7.0 → 0.8.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.
- package/dist/components/FloatingWindow/FloatingWindow.svelte +26 -2
- package/dist/components/FloatingWindow/floatingWindow.dock.svelte.js +11 -2
- package/dist/components/FloatingWindow/floatingWindow.mcp.d.ts +1 -1
- package/dist/components/FloatingWindow/floatingWindow.mcp.js +7 -4
- package/dist/components/FloatingWindow/floatingWindow.props.d.ts +6 -0
- package/dist/components/FloatingWindow/floatingWindow.state.svelte.d.ts +3 -0
- package/dist/components/FloatingWindow/floatingWindow.state.svelte.js +18 -4
- package/dist/components/FloatingWindow/floatingWindow.theme.d.ts +3 -0
- package/dist/components/FloatingWindow/floatingWindow.theme.js +20 -7
- package/dist/components/Form/File/FileInput.svelte +6 -42
- package/dist/components/Sidebar/Sidebar.svelte +72 -12
- package/dist/components/Sidebar/Sidebar.svelte.d.ts +1 -1
- package/dist/components/Sidebar/SidebarActivityBar.svelte +9 -2
- package/dist/components/Sidebar/SidebarActivityBar.svelte.d.ts +5 -1
- package/dist/components/Sidebar/SidebarDesktopShell.svelte +12 -4
- package/dist/components/Sidebar/SidebarMenuItem.svelte +19 -2
- package/dist/components/Sidebar/SidebarMenuItem.svelte.d.ts +4 -0
- package/dist/components/Sidebar/SidebarPanel.svelte +199 -88
- package/dist/components/Sidebar/SidebarPanel.svelte.d.ts +3 -0
- package/dist/components/Sidebar/SidebarViewStage.svelte +103 -0
- package/dist/components/Sidebar/SidebarViewStage.svelte.d.ts +18 -0
- package/dist/components/Sidebar/index.d.ts +1 -1
- package/dist/components/Sidebar/sidebar-layout.d.ts +7 -2
- package/dist/components/Sidebar/sidebar-layout.js +17 -6
- package/dist/components/Sidebar/sidebar.mcp.d.ts +1 -1
- package/dist/components/Sidebar/sidebar.mcp.js +19 -2
- package/dist/components/Sidebar/sidebar.props.d.ts +56 -1
- package/dist/components/Sidebar/sidebar.state.svelte.d.ts +4 -0
- package/dist/components/Sidebar/sidebar.state.svelte.js +9 -1
- package/dist/components/Sidebar/sidebar.theme.d.ts +149 -5
- package/dist/components/Sidebar/sidebar.theme.js +96 -3
- package/dist/components/Sidebar/sidebar.views.svelte.d.ts +138 -0
- package/dist/components/Sidebar/sidebar.views.svelte.js +303 -0
- package/dist/components/Theme/theme.floatingWindows.d.ts +6 -1
- package/dist/components/Theme/theme.floatingWindows.js +7 -2
- package/dist/components/Theme/theme.mcp.d.ts +1 -1
- package/dist/components/Theme/theme.mcp.js +1 -0
- package/dist/generated/componentContract.d.ts +1 -1
- package/dist/generated/componentContract.js +1 -0
- package/dist/generated/componentMcpRegistry.d.ts +4 -4
- package/dist/tailwind/index.mcp.d.ts +1 -1
- package/dist/tailwind/index.mcp.js +4 -0
- package/dist/tailwind/scales.js +11 -3
- package/dist/tailwind/spacing.js +17 -0
- package/dist/utils/cva/merge.d.ts +7 -0
- package/dist/utils/cva/merge.js +6 -1
- package/dist/utils/pointerDrag.js +7 -1
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { untrack } from 'svelte';
|
|
3
3
|
import { createBindableValue } from '../../utils/state.svelte.js';
|
|
4
|
-
import { crossfade, scale } from 'svelte/transition';
|
|
4
|
+
import { crossfade, fade, scale } from 'svelte/transition';
|
|
5
5
|
import { easingFunctions } from '../../transitions/easingFunctions.js';
|
|
6
6
|
import Button from '../Button/Button.svelte';
|
|
7
7
|
import { minusIcon } from '../Icons/minus.js';
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
minimizable = true,
|
|
33
33
|
closable = true,
|
|
34
34
|
closeOnEscape = true,
|
|
35
|
+
backdrop = false,
|
|
35
36
|
position = $bindable(),
|
|
36
37
|
dimensions = $bindable({ width: 480, height: 320 }),
|
|
37
38
|
class: className,
|
|
@@ -116,6 +117,9 @@
|
|
|
116
117
|
get closeOnEscape() {
|
|
117
118
|
return closeOnEscape;
|
|
118
119
|
},
|
|
120
|
+
get backdrop() {
|
|
121
|
+
return backdrop;
|
|
122
|
+
},
|
|
119
123
|
get position() {
|
|
120
124
|
return position;
|
|
121
125
|
},
|
|
@@ -202,6 +206,26 @@
|
|
|
202
206
|
};
|
|
203
207
|
</script>
|
|
204
208
|
|
|
209
|
+
{#if backdrop && openState.value && !minimized && windowState.position}
|
|
210
|
+
<!-- Portaled into the window layer one index below the window; it fades on the window's own
|
|
211
|
+
enter / exit timing, so minimizing lifts it while the window flies to the dock. -->
|
|
212
|
+
<div
|
|
213
|
+
{@attach windowState.theme.floatingWindows.portal}
|
|
214
|
+
data-slot="floating-window-backdrop"
|
|
215
|
+
aria-hidden="true"
|
|
216
|
+
class={classes.backdrop()}
|
|
217
|
+
style:z-index={windowState.backdropZIndex}
|
|
218
|
+
in:fade={{
|
|
219
|
+
duration: enter.in.duration ?? 0,
|
|
220
|
+
easing: easingFunctions[enter.in.easing ?? 'cubicOut']
|
|
221
|
+
}}
|
|
222
|
+
out:fade={{
|
|
223
|
+
duration: exit.out.duration ?? 0,
|
|
224
|
+
easing: easingFunctions[exit.out.easing ?? 'cubicOut']
|
|
225
|
+
}}
|
|
226
|
+
></div>
|
|
227
|
+
{/if}
|
|
228
|
+
|
|
205
229
|
{#if openState.value && !minimized && windowState.position}
|
|
206
230
|
<div
|
|
207
231
|
{@attach windowState.theme.floatingWindows.portal}
|
|
@@ -214,7 +238,7 @@
|
|
|
214
238
|
bind:this={ref}
|
|
215
239
|
{id}
|
|
216
240
|
role="dialog"
|
|
217
|
-
aria-modal=
|
|
241
|
+
aria-modal={backdrop ? 'true' : 'false'}
|
|
218
242
|
aria-labelledby={`${id}-title`}
|
|
219
243
|
aria-keyshortcuts={draggable
|
|
220
244
|
? 'Alt+ArrowUp Alt+ArrowRight Alt+ArrowDown Alt+ArrowLeft'
|
|
@@ -5,6 +5,11 @@ const DOCK_ITEM_WIDTH = 176;
|
|
|
5
5
|
const DOCK_ITEM_HEIGHT = 36;
|
|
6
6
|
const DOCK_GAP = 8;
|
|
7
7
|
const DOCK_INLINE_GAP = 8;
|
|
8
|
+
const DOCK_DRAG_ACTIVATION = Object.freeze({
|
|
9
|
+
distancePx: 4,
|
|
10
|
+
touchDelayMs: 300,
|
|
11
|
+
touchTolerancePx: 8
|
|
12
|
+
});
|
|
8
13
|
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
9
14
|
export class FloatingWindowDockState {
|
|
10
15
|
windowState;
|
|
@@ -98,6 +103,10 @@ export class FloatingWindowDockState {
|
|
|
98
103
|
};
|
|
99
104
|
drag = createPointerDrag({
|
|
100
105
|
disabled: () => !this.windowState.draggable || !this.windowState.minimized,
|
|
106
|
+
// The item is the title button's container, not a handle: a press that never travels must
|
|
107
|
+
// keep the pointer on the button, or its click lands on the item and never restores.
|
|
108
|
+
capture: 'on-activate',
|
|
109
|
+
activation: () => DOCK_DRAG_ACTIVATION,
|
|
101
110
|
onStart: (payload) => this.startMove(payload),
|
|
102
111
|
onMove: (payload) => this.updateMove(payload),
|
|
103
112
|
onEnd: (payload) => this.endMove(payload)
|
|
@@ -115,8 +124,8 @@ export class FloatingWindowDockState {
|
|
|
115
124
|
return Math.max(1, Math.floor((availableLength - DOCK_INLINE_GAP * 2 + DOCK_GAP) / (itemLength + DOCK_GAP)));
|
|
116
125
|
}
|
|
117
126
|
startMove(payload) {
|
|
118
|
-
if (payload.
|
|
119
|
-
payload.
|
|
127
|
+
if (payload.startTarget instanceof Element &&
|
|
128
|
+
payload.startTarget.closest('[data-floating-window-dock-actions]')) {
|
|
120
129
|
return false;
|
|
121
130
|
}
|
|
122
131
|
this.windowState.activateSurface('dock');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const floatingWindowDescription = "\n# FloatingWindow Component\n\nFloatingWindow renders a non-modal
|
|
1
|
+
export declare const floatingWindowDescription = "\n# FloatingWindow Component\n\nFloatingWindow renders a portaled utility window, non-modal unless `backdrop` is set, that can be moved, resized, minimized into a configurable viewport-edge dock, restored, and closed. Multiple windows inside the same Theme provider coordinate their z-order and stack independently by dock placement. The Theme keeps floating windows below modal Dialog surfaces, so an open window remains mounted behind a dialog and returns unchanged when the dialog closes.\n\n## Basic Usage\n\n```svelte\n<script lang=\"ts\">\n import { Button } from '../Button/index.ts';\n import { FloatingWindow } from './index.ts';\n\n let open = $state(false);\n</script>\n\n<Button onclick={() => (open = true)}>Open notes</Button>\n\n<FloatingWindow bind:open title=\"Notes\">\n <p>Window content remains interactive alongside the page.</p>\n</FloatingWindow>\n```\n\n## Props\n\n- **id**: string - Stable DOM id. Generated when omitted.\n- **open**: boolean (default: true) - Bindable rendered state.\n- **defaultOpen**: boolean (default: true) - Initial state when open is not provided.\n- **minimized**: boolean (default: false) - Bindable docked state.\n- **dockPlacement**: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right' | 'left-top' | 'left-bottom' | 'right-top' | 'right-bottom' (default: 'bottom-left') - Edge and alignment used by the minimized dock. Top and bottom placements stack horizontally; left and right placements use a vertical title bar and stack vertically.\n- **title**: Slot<FloatingWindowPayload> (required) - Window title as text or a snippet.\n- **children**: Slot<FloatingWindowPayload> - Main content.\n- **dragFrom**: 'header' | 'window' (default: 'header') - Restricts dragging to the header or allows any non-interactive surface to start a drag.\n- **draggable**: boolean (default: true) - Enables pointer dragging.\n- **resizable**: boolean (default: true) - Enables four edge and four corner resize handles.\n- **minimizable**: boolean (default: true) - Shows the minimize control.\n- **closable**: boolean (default: true) - Shows the close control.\n- **closeOnEscape**: boolean (default: true) - Closes the topmost expanded floating window when Escape is pressed.\n- **backdrop**: boolean (default: false) - Dims the page behind the expanded window and makes it modal like a Dialog: Tab stays inside, the rest of the page is inert, and page scroll is locked. Minimizing into the dock lifts all of it; restoring brings it back. Clicking the backdrop does nothing: close with the close control or Escape.\n- **position**: { x: number; y: number } - Bindable viewport-relative top-left position. The first render is centered when omitted.\n- **dimensions**: { width: number; height: number; min?: [width, height]; max?: [width, height] } (default: 480 x 320, minimum 280 x 160) - Bindable pixel dimensions and optional constraint tuples. Maximum dimensions remain additionally constrained to the viewport.\n- **class**: string - Additional classes on the visible window.\n- **theme**: FloatingWindowThemeProps - Per-instance theme overrides.\n- **ref**: HTMLDivElement - Bindable reference to the visible window or minimized dock item.\n- **onOpenChange**: (open: boolean) => void - Runs once for each library-requested state change.\n- **onAfterOpen**: (payload) => void - Runs after the open transition finishes.\n- **onAfterClose**: (payload) => void - Runs after the close transition finishes.\n- **onMinimize**: (payload) => void - Runs after minimize state updates.\n- **onRestore**: (payload) => void - Runs after restore state updates.\n- **onMove**: ({ position, window }) => void - Runs when a move commits.\n- **onResize**: ({ dimensions, window }) => void - Runs when a pointer or keyboard resize commits.\n\n## Dragging\n\nHeader dragging is the default because it preserves text selection and content interactions. With `dragFrom=\"window\"`, buttons, links, inputs, editable content, resize handles, and descendants marked `data-floating-window-no-drag` remain excluded from drag starts.\n\n## Accessibility\n\n- The expanded surface uses a `dialog` role labelled by its title: `aria-modal=\"false\"`, or `\"true\"` with `backdrop`.\n- Close, minimize, and restore controls are native Entasis buttons with accessible labels.\n- Edge resize handles use `separator` semantics and support arrow-key resizing; hold Shift for a larger step.\n- Opening focuses the window, closing restores focus to its previous owner, and only the topmost expanded floating window handles Escape.\n- Alt+Arrow moves the focused window; hold Shift for a larger step.\n- Corner handles are pointer-only because a diagonal separator has no valid ARIA orientation.\n- Without `backdrop` it does not trap focus or hide page content. With it, Tab and Shift+Tab stay inside the window, every sibling subtree up to `<body>` is `inert` (other windows and dock items included), and page scroll is locked, until the window closes or is minimized.\n\n## Theme Parts\n\n- **backdrop**: Page dim behind a window opened with `backdrop`, one layer below the window.\n- **root**: Floating window surface and drag/resize states.\n- **header**: Default title bar and drag handle.\n- **title**: Header title.\n- **actions**: Header control group.\n- **control**: Header and dock icon buttons.\n- **scrollArea**: Flexible ScrollArea root that owns body scrolling.\n- **content**: Padded content inside the ScrollArea viewport.\n- **resizeHandle**: Edge and corner handles by direction.\n- **dockItem**: Minimized surface.\n- **dockTitle**: Full-width restore button in the dock item.\n- **dockTitleText**: Truncated title text and lateral writing direction.\n- **dockActions**: Dock restore and close controls.\n\n## Motion\n\n- **motion** theme slot, keyed by `phase`: `flight` times the crossfade between window and\n dock pill, `enter` / `exit` the scale fallback when there is no counterpart.\n- Only `duration` / `easing` (plus the fallback's `scale` / `opacity`) are read.\n- The backdrop fades on the `enter` / `exit` timings.\n- Ladder: `<Theme components={{ 'floating-window': { motion } }}>` \u2192\n `setFloatingWindowTheme({ motion })` \u2192 `theme.motion`. Read once, at mount.\n";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const floatingWindowDescription = `
|
|
2
2
|
# FloatingWindow Component
|
|
3
3
|
|
|
4
|
-
FloatingWindow renders a non-modal
|
|
4
|
+
FloatingWindow renders a portaled utility window, non-modal unless \`backdrop\` is set, that can be moved, resized, minimized into a configurable viewport-edge dock, restored, and closed. Multiple windows inside the same Theme provider coordinate their z-order and stack independently by dock placement. The Theme keeps floating windows below modal Dialog surfaces, so an open window remains mounted behind a dialog and returns unchanged when the dialog closes.
|
|
5
5
|
|
|
6
6
|
## Basic Usage
|
|
7
7
|
|
|
@@ -35,6 +35,7 @@ FloatingWindow renders a non-modal, portaled utility window that can be moved, r
|
|
|
35
35
|
- **minimizable**: boolean (default: true) - Shows the minimize control.
|
|
36
36
|
- **closable**: boolean (default: true) - Shows the close control.
|
|
37
37
|
- **closeOnEscape**: boolean (default: true) - Closes the topmost expanded floating window when Escape is pressed.
|
|
38
|
+
- **backdrop**: boolean (default: false) - Dims the page behind the expanded window and makes it modal like a Dialog: Tab stays inside, the rest of the page is inert, and page scroll is locked. Minimizing into the dock lifts all of it; restoring brings it back. Clicking the backdrop does nothing: close with the close control or Escape.
|
|
38
39
|
- **position**: { x: number; y: number } - Bindable viewport-relative top-left position. The first render is centered when omitted.
|
|
39
40
|
- **dimensions**: { width: number; height: number; min?: [width, height]; max?: [width, height] } (default: 480 x 320, minimum 280 x 160) - Bindable pixel dimensions and optional constraint tuples. Maximum dimensions remain additionally constrained to the viewport.
|
|
40
41
|
- **class**: string - Additional classes on the visible window.
|
|
@@ -54,16 +55,17 @@ Header dragging is the default because it preserves text selection and content i
|
|
|
54
55
|
|
|
55
56
|
## Accessibility
|
|
56
57
|
|
|
57
|
-
- The expanded surface uses a
|
|
58
|
+
- The expanded surface uses a \`dialog\` role labelled by its title: \`aria-modal="false"\`, or \`"true"\` with \`backdrop\`.
|
|
58
59
|
- Close, minimize, and restore controls are native Entasis buttons with accessible labels.
|
|
59
60
|
- Edge resize handles use \`separator\` semantics and support arrow-key resizing; hold Shift for a larger step.
|
|
60
|
-
- Opening focuses the
|
|
61
|
+
- Opening focuses the window, closing restores focus to its previous owner, and only the topmost expanded floating window handles Escape.
|
|
61
62
|
- Alt+Arrow moves the focused window; hold Shift for a larger step.
|
|
62
63
|
- Corner handles are pointer-only because a diagonal separator has no valid ARIA orientation.
|
|
63
|
-
-
|
|
64
|
+
- Without \`backdrop\` it does not trap focus or hide page content. With it, Tab and Shift+Tab stay inside the window, every sibling subtree up to \`<body>\` is \`inert\` (other windows and dock items included), and page scroll is locked, until the window closes or is minimized.
|
|
64
65
|
|
|
65
66
|
## Theme Parts
|
|
66
67
|
|
|
68
|
+
- **backdrop**: Page dim behind a window opened with \`backdrop\`, one layer below the window.
|
|
67
69
|
- **root**: Floating window surface and drag/resize states.
|
|
68
70
|
- **header**: Default title bar and drag handle.
|
|
69
71
|
- **title**: Header title.
|
|
@@ -82,6 +84,7 @@ Header dragging is the default because it preserves text selection and content i
|
|
|
82
84
|
- **motion** theme slot, keyed by \`phase\`: \`flight\` times the crossfade between window and
|
|
83
85
|
dock pill, \`enter\` / \`exit\` the scale fallback when there is no counterpart.
|
|
84
86
|
- Only \`duration\` / \`easing\` (plus the fallback's \`scale\` / \`opacity\`) are read.
|
|
87
|
+
- The backdrop fades on the \`enter\` / \`exit\` timings.
|
|
85
88
|
- Ladder: \`<Theme components={{ 'floating-window': { motion } }}>\` →
|
|
86
89
|
\`setFloatingWindowTheme({ motion })\` → \`theme.motion\`. Read once, at mount.
|
|
87
90
|
`;
|
|
@@ -66,6 +66,12 @@ export type FloatingWindowProps = WithAttachments<{
|
|
|
66
66
|
closable?: boolean;
|
|
67
67
|
/** Closes the topmost expanded floating window when Escape is pressed. */
|
|
68
68
|
closeOnEscape?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Dims the page behind the expanded window and makes the window modal, like a Dialog: Tab
|
|
71
|
+
* stays inside, the rest of the page is inert, and page scroll is locked. Minimizing into the
|
|
72
|
+
* dock lifts all of it; restoring brings it back.
|
|
73
|
+
*/
|
|
74
|
+
backdrop?: boolean;
|
|
69
75
|
/** Bindable viewport-relative top-left position in pixels. Defaults to centered. */
|
|
70
76
|
position?: FloatingWindowPosition;
|
|
71
77
|
/** Bindable width and height in pixels, with optional [width, height] constraint tuples. */
|
|
@@ -12,6 +12,7 @@ type FloatingWindowStateOptions = {
|
|
|
12
12
|
minimizable: boolean;
|
|
13
13
|
closable: boolean;
|
|
14
14
|
closeOnEscape: boolean;
|
|
15
|
+
backdrop: boolean;
|
|
15
16
|
position?: FloatingWindowPosition;
|
|
16
17
|
dimensions: FloatingWindowDimensions;
|
|
17
18
|
onOpenChange?: (open: boolean) => void;
|
|
@@ -61,6 +62,8 @@ export declare class FloatingWindowState extends FloatingWindowState_base {
|
|
|
61
62
|
windowDrag: Attachment<HTMLElement>;
|
|
62
63
|
resize(direction: FloatingWindowResizeDirection): Attachment<HTMLElement>;
|
|
63
64
|
activateSurface(type: FloatingWindowSurface): void;
|
|
65
|
+
/** The index reserved just below the window when it activated with a backdrop. */
|
|
66
|
+
get backdropZIndex(): number;
|
|
64
67
|
bringToFront(): void;
|
|
65
68
|
close(): void;
|
|
66
69
|
minimize(): void;
|
|
@@ -4,6 +4,7 @@ import { createPointerDrag } from '../../utils/pointerDrag.js';
|
|
|
4
4
|
import { withOptions } from '../../utils/state.svelte.js';
|
|
5
5
|
import { useTheme } from '../Theme/theme.state.svelte.js';
|
|
6
6
|
import { useFocusScope } from '../../utils/useFocusScope.svelte.js';
|
|
7
|
+
import { useScrollLock } from '../../utils/useScrollLock.svelte.js';
|
|
7
8
|
import { FloatingWindowDockState } from './floatingWindow.dock.svelte.js';
|
|
8
9
|
import { FLOATING_WINDOW_VIEWPORT_GAP, FloatingWindowGeometry } from './floatingWindow.geometry.js';
|
|
9
10
|
const KEYBOARD_STEP = 10;
|
|
@@ -25,17 +26,20 @@ export class FloatingWindowState extends withOptions() {
|
|
|
25
26
|
viewportHeight = $state(0);
|
|
26
27
|
geometry;
|
|
27
28
|
docking;
|
|
28
|
-
//
|
|
29
|
+
// Focus lands on the window when it opens and returns to the opener on close. Non-modal by
|
|
30
|
+
// default; with a backdrop the window is modal like a Dialog: Tab stays inside and every
|
|
31
|
+
// sibling subtree up to <body> is inert.
|
|
29
32
|
focusScope = useFocusScope({
|
|
30
33
|
isActive: () => this.open && !this.minimized,
|
|
31
|
-
trap: () =>
|
|
34
|
+
trap: () => this.backdrop,
|
|
35
|
+
inertSiblings: () => this.backdrop,
|
|
32
36
|
initialFocus: () => 'container'
|
|
33
37
|
});
|
|
34
38
|
// Escape is dispatched by the shared layer stack to the topmost layer only.
|
|
35
39
|
layer = this.theme.layers.register({
|
|
36
40
|
kind: 'floating-window',
|
|
37
41
|
isOpen: () => this.open && !this.minimized,
|
|
38
|
-
isModal: () =>
|
|
42
|
+
isModal: () => this.backdrop,
|
|
39
43
|
dismissOnEscape: () => this.closeOnEscape && this.closable && this.theme.floatingWindows.isTopWindow(this.id),
|
|
40
44
|
onDismiss: () => this.close(),
|
|
41
45
|
state: this
|
|
@@ -47,6 +51,12 @@ export class FloatingWindowState extends withOptions() {
|
|
|
47
51
|
resizeAttachments = plainMap();
|
|
48
52
|
constructor(options) {
|
|
49
53
|
super(options);
|
|
54
|
+
useScrollLock({ isActive: () => this.backdrop && this.open && !this.minimized });
|
|
55
|
+
// A backdrop switched on while the window is open needs the index below it reserved.
|
|
56
|
+
$effect(() => {
|
|
57
|
+
if (this.backdrop && this.open && !this.minimized)
|
|
58
|
+
untrack(() => this.bringToFront());
|
|
59
|
+
});
|
|
50
60
|
this.geometry = new FloatingWindowGeometry(this);
|
|
51
61
|
this.docking = new FloatingWindowDockState(this);
|
|
52
62
|
if (typeof window !== 'undefined') {
|
|
@@ -153,7 +163,11 @@ export class FloatingWindowState extends withOptions() {
|
|
|
153
163
|
return attachment;
|
|
154
164
|
}
|
|
155
165
|
activateSurface(type) {
|
|
156
|
-
this.zIndex = this.theme.floatingWindows.activate(this.id, type);
|
|
166
|
+
this.zIndex = this.theme.floatingWindows.activate(this.id, type, type === 'window' && this.backdrop);
|
|
167
|
+
}
|
|
168
|
+
/** The index reserved just below the window when it activated with a backdrop. */
|
|
169
|
+
get backdropZIndex() {
|
|
170
|
+
return this.zIndex - 1;
|
|
157
171
|
}
|
|
158
172
|
bringToFront() {
|
|
159
173
|
this.activateSurface(this.minimized ? 'dock' : 'window');
|
|
@@ -32,6 +32,7 @@ export declare const floatingWindowTheme: {
|
|
|
32
32
|
};
|
|
33
33
|
};
|
|
34
34
|
}>;
|
|
35
|
+
backdrop: (props?: import("../../utils/cva/types.js").CVAClassProp | undefined) => string;
|
|
35
36
|
root: (props?: (import("../../utils/cva/types.js").CVAVariantSchema<{
|
|
36
37
|
dragFrom: {
|
|
37
38
|
header: string;
|
|
@@ -129,6 +130,7 @@ export declare const setFloatingWindowTheme: (theme: InferComponentTheme<{
|
|
|
129
130
|
};
|
|
130
131
|
};
|
|
131
132
|
}>;
|
|
133
|
+
backdrop: (props?: import("../../utils/cva/types.js").CVAClassProp | undefined) => string;
|
|
132
134
|
root: (props?: (import("../../utils/cva/types.js").CVAVariantSchema<{
|
|
133
135
|
dragFrom: {
|
|
134
136
|
header: string;
|
|
@@ -224,6 +226,7 @@ export declare const useFloatingWindowTheme: import("../../utils/cva/theme.js").
|
|
|
224
226
|
};
|
|
225
227
|
};
|
|
226
228
|
}>;
|
|
229
|
+
backdrop: (props?: import("../../utils/cva/types.js").CVAClassProp | undefined) => string;
|
|
227
230
|
root: (props?: (import("../../utils/cva/types.js").CVAVariantSchema<{
|
|
228
231
|
dragFrom: {
|
|
229
232
|
header: string;
|
|
@@ -24,7 +24,7 @@ const defaultFloatingWindow = cva({
|
|
|
24
24
|
}
|
|
25
25
|
});
|
|
26
26
|
const defaultFloatingWindowHeader = cva({
|
|
27
|
-
base: 'border-neutral-muted flex h-9 shrink-0 items-center gap-sm border-b px-md select-none',
|
|
27
|
+
base: 'border-neutral-muted flex h-9 shrink-0 items-center gap-sm border-b px-md-concentric select-none',
|
|
28
28
|
variants: {
|
|
29
29
|
draggable: {
|
|
30
30
|
true: 'cursor-move touch-none',
|
|
@@ -66,17 +66,19 @@ const defaultFloatingWindowResizeHandle = cva({
|
|
|
66
66
|
}
|
|
67
67
|
});
|
|
68
68
|
const defaultFloatingWindowDockItem = cva({
|
|
69
|
-
base: 'bg-surface-floating text-neutral pointer-events-auto fixed flex touch-none items-center overflow-hidden lift-4 ring-1 ring-neutral-muted',
|
|
69
|
+
base: 'bg-surface-floating text-neutral pointer-events-auto fixed flex touch-none items-center overflow-hidden lift-4 ring-1 ring-neutral-muted [--dock-radius:min(var(--radius-xl),calc(var(--spacing)*3))]',
|
|
70
70
|
variants: {
|
|
71
71
|
orientation: {
|
|
72
72
|
horizontal: 'h-9 flex-row',
|
|
73
73
|
vertical: 'w-9 flex-col'
|
|
74
74
|
},
|
|
75
75
|
side: {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
// The window's step, capped at a third of the 36px (`spacing × 9`) item: any rounder and a
|
|
77
|
+
// round theme turns the tab into a pill. At that cap `md` padding always clears the corner.
|
|
78
|
+
top: 'rounded-[var(--dock-radius)] rounded-t-none',
|
|
79
|
+
right: 'rounded-[var(--dock-radius)] rounded-r-none',
|
|
80
|
+
bottom: 'rounded-[var(--dock-radius)] rounded-b-none',
|
|
81
|
+
left: 'rounded-[var(--dock-radius)] rounded-l-none'
|
|
80
82
|
},
|
|
81
83
|
dragging: {
|
|
82
84
|
true: 'cursor-grabbing select-none',
|
|
@@ -89,8 +91,13 @@ const defaultFloatingWindowDockItem = cva({
|
|
|
89
91
|
dragging: false
|
|
90
92
|
}
|
|
91
93
|
});
|
|
94
|
+
// `!static`: the title is the pill's drag and restore surface, so its hover / press overlay (the
|
|
95
|
+
// Button's `state-layer` ::before) anchors to the fixed dock item instead of the button and tints
|
|
96
|
+
// the whole pill, clipped by its rounded overflow. The dock controls keep their own overlay.
|
|
97
|
+
// `active:translate-none` drops the Button's press nudge: any `translate` but `none`, 0 included,
|
|
98
|
+
// makes the title the anchor of its own overlay again and pulls the press tint back inside it.
|
|
92
99
|
const defaultFloatingWindowDockTitle = cva({
|
|
93
|
-
base: '!min-w-0 !flex-1 !rounded-none !text-xs !font-medium',
|
|
100
|
+
base: '!static active:translate-none !min-w-0 !flex-1 !rounded-none !text-xs !font-medium',
|
|
94
101
|
variants: {
|
|
95
102
|
orientation: {
|
|
96
103
|
horizontal: '!h-full !w-auto !justify-start !px-md !text-left',
|
|
@@ -135,6 +142,11 @@ const defaultFloatingWindowDockActions = cva({
|
|
|
135
142
|
// The window and its dock pill are two halves of one crossfade: `flight` times the
|
|
136
143
|
// shared transform between them, `enter` / `exit` the scale fallback used when there
|
|
137
144
|
// is no counterpart to fly to. Only `duration` / `easing` are read from each.
|
|
145
|
+
// The page dim behind a window opened with `backdrop`: the same wash as Dialog's backdrop, so a
|
|
146
|
+
// modal window and a modal dialog dim the page alike.
|
|
147
|
+
const defaultFloatingWindowBackdrop = cva({
|
|
148
|
+
base: 'fixed inset-0 bg-neutral/40 backdrop-blur-xs'
|
|
149
|
+
});
|
|
138
150
|
export const defaultFloatingWindowMotion = motion({
|
|
139
151
|
base: {
|
|
140
152
|
in: { x: 0, y: 0, scale: 0.97, opacity: 0 },
|
|
@@ -153,6 +165,7 @@ export const defaultFloatingWindowMotion = motion({
|
|
|
153
165
|
});
|
|
154
166
|
export const floatingWindowTheme = {
|
|
155
167
|
motion: defaultFloatingWindowMotion,
|
|
168
|
+
backdrop: defaultFloatingWindowBackdrop,
|
|
156
169
|
root: defaultFloatingWindow,
|
|
157
170
|
header: defaultFloatingWindowHeader,
|
|
158
171
|
title: defaultFloatingWindowTitle,
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
import { useFileInputTheme } from './fileInput.theme.js';
|
|
10
10
|
import { slide } from 'svelte/transition';
|
|
11
11
|
import Slot from '../../Slot/Slot.svelte';
|
|
12
|
+
import { fileIcon } from '../../Icons/file.js';
|
|
13
|
+
import { plusIcon } from '../../Icons/plus.js';
|
|
14
|
+
import { xIcon } from '../../Icons/x.js';
|
|
12
15
|
import { useI18n } from '../../../i18n/context.svelte.js';
|
|
13
16
|
|
|
14
17
|
let {
|
|
@@ -153,20 +156,7 @@
|
|
|
153
156
|
/>
|
|
154
157
|
{#if dropzone.files.length === 0}
|
|
155
158
|
<div transition:slide class={classes.placeholder({ class: placeholderClass, size: rest.size })}>
|
|
156
|
-
|
|
157
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
158
|
-
width="40"
|
|
159
|
-
height="40"
|
|
160
|
-
viewBox="0 0 24 24"
|
|
161
|
-
fill="none"
|
|
162
|
-
stroke="currentColor"
|
|
163
|
-
stroke-width="2"
|
|
164
|
-
stroke-linecap="round"
|
|
165
|
-
stroke-linejoin="round"
|
|
166
|
-
>
|
|
167
|
-
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
|
168
|
-
<polyline points="14 2 14 8 20 8" />
|
|
169
|
-
</svg>
|
|
159
|
+
{@render fileIcon({ size: 40 })}
|
|
170
160
|
{#if resolvedPlaceholder}
|
|
171
161
|
<span class="mt-2">{resolvedPlaceholder}</span>
|
|
172
162
|
{/if}
|
|
@@ -192,20 +182,7 @@
|
|
|
192
182
|
</div>
|
|
193
183
|
<Button variant="ghost" size="small" squared onclick={() => dropzone.removeFile(fil)}>
|
|
194
184
|
{#snippet prefix()}
|
|
195
|
-
|
|
196
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
197
|
-
width="16"
|
|
198
|
-
height="16"
|
|
199
|
-
viewBox="0 0 24 24"
|
|
200
|
-
fill="none"
|
|
201
|
-
stroke="currentColor"
|
|
202
|
-
stroke-width="2"
|
|
203
|
-
stroke-linecap="round"
|
|
204
|
-
stroke-linejoin="round"
|
|
205
|
-
>
|
|
206
|
-
<line x1="18" y1="6" x2="6" y2="18" />
|
|
207
|
-
<line x1="6" y1="6" x2="18" y2="18" />
|
|
208
|
-
</svg>
|
|
185
|
+
{@render xIcon({ size: 16 })}
|
|
209
186
|
{/snippet}
|
|
210
187
|
</Button>
|
|
211
188
|
</Slot>
|
|
@@ -214,20 +191,7 @@
|
|
|
214
191
|
{#if mode === 'multiple' && dropzone.files.length > 0 && dropzone.files.length < maxFiles}
|
|
215
192
|
<Button variant="soft" color="primary" fullWidth onclick={() => dropzone.open()}>
|
|
216
193
|
{#snippet prefix()}
|
|
217
|
-
|
|
218
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
219
|
-
width="16"
|
|
220
|
-
height="16"
|
|
221
|
-
viewBox="0 0 24 24"
|
|
222
|
-
fill="none"
|
|
223
|
-
stroke="currentColor"
|
|
224
|
-
stroke-width="2"
|
|
225
|
-
stroke-linecap="round"
|
|
226
|
-
stroke-linejoin="round"
|
|
227
|
-
>
|
|
228
|
-
<line x1="12" y1="5" x2="12" y2="19" />
|
|
229
|
-
<line x1="5" y1="12" x2="19" y2="12" />
|
|
230
|
-
</svg>
|
|
194
|
+
{@render plusIcon({ size: 16 })}
|
|
231
195
|
{/snippet}
|
|
232
196
|
{Number.isFinite(maxFiles)
|
|
233
197
|
? t.addMoreFiles(dropzone.files.length, maxFiles)
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import { SidebarResizeState } from './sidebar.resize.svelte.js';
|
|
19
19
|
import { SidebarStateController } from './sidebar.state.svelte.js';
|
|
20
20
|
import { useSidebarTheme } from './sidebar.theme.js';
|
|
21
|
+
import { SidebarViewsState } from './sidebar.views.svelte.js';
|
|
21
22
|
import { useDefaultColor } from '../Theme/theme.state.svelte.js';
|
|
22
23
|
let {
|
|
23
24
|
id: customId,
|
|
@@ -47,6 +48,10 @@
|
|
|
47
48
|
expandOnHover = false,
|
|
48
49
|
activityBar,
|
|
49
50
|
items,
|
|
51
|
+
views,
|
|
52
|
+
view = $bindable(),
|
|
53
|
+
defaultView,
|
|
54
|
+
onViewChange,
|
|
50
55
|
class: className,
|
|
51
56
|
collapseIcon = 'chevron',
|
|
52
57
|
tooltips = 'auto',
|
|
@@ -89,8 +94,16 @@
|
|
|
89
94
|
// contents render expanded whenever the peek renders them at full width.
|
|
90
95
|
let hoverExpanded = $state(false);
|
|
91
96
|
const activityBarWidth = $derived(activityBar?.width ?? '3rem');
|
|
92
|
-
//
|
|
93
|
-
|
|
97
|
+
// The column the rail reserves: the rail itself, plus the outer gutter when it floats as a card
|
|
98
|
+
// of its own (floating, split). `0px` (not `0`) so the `calc()` in the spacer/container classes
|
|
99
|
+
// stays valid without a unit.
|
|
100
|
+
const activityBarOffset = $derived(
|
|
101
|
+
!activityBar
|
|
102
|
+
? '0px'
|
|
103
|
+
: variant === 'floating' || variant === 'split'
|
|
104
|
+
? `calc(${activityBarWidth} + var(--spacing) * 2)`
|
|
105
|
+
: activityBarWidth
|
|
106
|
+
);
|
|
94
107
|
function setOpen(nextOpen: boolean) {
|
|
95
108
|
openState.value = nextOpen;
|
|
96
109
|
}
|
|
@@ -103,19 +116,26 @@
|
|
|
103
116
|
const navLabel = $derived(`${t.sidebar} ${t.navigation}`);
|
|
104
117
|
const classes = $derived(useSidebarTheme(theme));
|
|
105
118
|
const resolvedColor = $derived(useDefaultColor());
|
|
119
|
+
// Every view counts, not just the one on screen: icon collapse must not come and go as the
|
|
120
|
+
// view changes.
|
|
106
121
|
const canCollapseToIcon = $derived.by(() => {
|
|
107
|
-
|
|
122
|
+
const viewList = Object.values(views ?? {});
|
|
123
|
+
if (viewList.length ? viewList.some((entry) => entry.content) : content) return false;
|
|
108
124
|
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
...(
|
|
112
|
-
...(
|
|
113
|
-
|
|
125
|
+
const sources = [{ headerMenu, items, footerMenu, headerButton, footerButton }, ...viewList];
|
|
126
|
+
const menuEntries = sources.flatMap((source) => [
|
|
127
|
+
...(source.headerMenu ?? []),
|
|
128
|
+
...(source.items?.flatMap((group) => group.items ?? []) ?? []),
|
|
129
|
+
...(source.footerMenu ?? [])
|
|
130
|
+
]);
|
|
114
131
|
|
|
115
132
|
return (
|
|
116
133
|
menuEntries.every(hasIcon) &&
|
|
117
|
-
|
|
118
|
-
|
|
134
|
+
sources.every(
|
|
135
|
+
(source) =>
|
|
136
|
+
hasCollapsibleMedia(source.headerButton ?? undefined) &&
|
|
137
|
+
hasCollapsibleMedia(source.footerButton ?? undefined)
|
|
138
|
+
)
|
|
119
139
|
);
|
|
120
140
|
});
|
|
121
141
|
const resolvedCollapsible = $derived(
|
|
@@ -142,6 +162,32 @@
|
|
|
142
162
|
return onDisplayStateChange;
|
|
143
163
|
}
|
|
144
164
|
});
|
|
165
|
+
const panelViews = new SidebarViewsState({
|
|
166
|
+
get views() {
|
|
167
|
+
return views;
|
|
168
|
+
},
|
|
169
|
+
get view() {
|
|
170
|
+
return view;
|
|
171
|
+
},
|
|
172
|
+
get defaultView() {
|
|
173
|
+
return defaultView;
|
|
174
|
+
},
|
|
175
|
+
get onViewChange() {
|
|
176
|
+
return onViewChange;
|
|
177
|
+
},
|
|
178
|
+
get isMobile(): boolean {
|
|
179
|
+
return controller.isMobile;
|
|
180
|
+
},
|
|
181
|
+
get side() {
|
|
182
|
+
return side;
|
|
183
|
+
},
|
|
184
|
+
get root() {
|
|
185
|
+
return { headerButton, search, headerMenu, header, footerButton, footerMenu, footer };
|
|
186
|
+
},
|
|
187
|
+
setViewProp: (next) => {
|
|
188
|
+
view = next;
|
|
189
|
+
}
|
|
190
|
+
});
|
|
145
191
|
const controller = new SidebarStateController({
|
|
146
192
|
get mode() {
|
|
147
193
|
return mode;
|
|
@@ -161,7 +207,11 @@
|
|
|
161
207
|
get peeking() {
|
|
162
208
|
return hoverExpanded;
|
|
163
209
|
},
|
|
164
|
-
|
|
210
|
+
get view(): string | undefined {
|
|
211
|
+
return panelViews.current;
|
|
212
|
+
},
|
|
213
|
+
setDisplayState: displayStateBridge.setDisplayState,
|
|
214
|
+
setView: panelViews.setView
|
|
165
215
|
});
|
|
166
216
|
const resize = new SidebarResizeState({
|
|
167
217
|
get width() {
|
|
@@ -243,12 +293,22 @@
|
|
|
243
293
|
{activeVariant}
|
|
244
294
|
{density}
|
|
245
295
|
label={navLabel}
|
|
296
|
+
views={panelViews.enabled ? panelViews : undefined}
|
|
246
297
|
{theme}
|
|
247
298
|
/>
|
|
248
299
|
{/snippet}
|
|
249
300
|
{#snippet staticActivityBar()}
|
|
250
301
|
{#if activityBar}
|
|
251
|
-
<SidebarActivityBar
|
|
302
|
+
<SidebarActivityBar
|
|
303
|
+
{activityBar}
|
|
304
|
+
{side}
|
|
305
|
+
{size}
|
|
306
|
+
{density}
|
|
307
|
+
{variant}
|
|
308
|
+
placement="static"
|
|
309
|
+
label={activityBarLabel}
|
|
310
|
+
{theme}
|
|
311
|
+
/>
|
|
252
312
|
{/if}
|
|
253
313
|
{/snippet}
|
|
254
314
|
{#if mode === 'panel'}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { SidebarProps } from './sidebar.props.js';
|
|
2
|
-
declare const Sidebar: import("svelte").Component<SidebarProps, {}, "width" | "ref" | "open" | "displayState">;
|
|
2
|
+
declare const Sidebar: import("svelte").Component<SidebarProps, {}, "view" | "width" | "ref" | "open" | "displayState">;
|
|
3
3
|
type Sidebar = ReturnType<typeof Sidebar>;
|
|
4
4
|
export default Sidebar;
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
SidebarActivityBarItem,
|
|
9
9
|
SidebarDensity,
|
|
10
10
|
SidebarSide,
|
|
11
|
-
SidebarSize
|
|
11
|
+
SidebarSize,
|
|
12
|
+
SidebarVariant
|
|
12
13
|
} from './sidebar.props.js';
|
|
13
14
|
import SidebarIcon from './SidebarIcon.svelte';
|
|
14
15
|
import { useSidebarTheme, type SidebarThemeProps } from './sidebar.theme.js';
|
|
@@ -19,6 +20,8 @@
|
|
|
19
20
|
side,
|
|
20
21
|
size,
|
|
21
22
|
density,
|
|
23
|
+
variant = 'admin',
|
|
24
|
+
placement = 'positioned',
|
|
22
25
|
orientation = 'vertical',
|
|
23
26
|
label,
|
|
24
27
|
class: className,
|
|
@@ -28,6 +31,10 @@
|
|
|
28
31
|
side: SidebarSide;
|
|
29
32
|
size: SidebarSize;
|
|
30
33
|
density: SidebarDensity;
|
|
34
|
+
/** The Sidebar variant whose panel surface the rail wears beside the panel. */
|
|
35
|
+
variant?: SidebarVariant;
|
|
36
|
+
/** `static` when there is no desktop container to hold its gutters (`collapsible="none"`). */
|
|
37
|
+
placement?: 'positioned' | 'static';
|
|
31
38
|
/** Vertical along the sidebar edge on desktop; horizontal at the top of the mobile drawer. */
|
|
32
39
|
orientation?: 'vertical' | 'horizontal';
|
|
33
40
|
/** Fallback accessible name when the activity bar sets no label. */
|
|
@@ -179,7 +186,7 @@
|
|
|
179
186
|
data-side={side}
|
|
180
187
|
data-orientation={orientation}
|
|
181
188
|
aria-label={activityBar.label ?? label}
|
|
182
|
-
class={classes.activityBar({ orientation, density, className })}
|
|
189
|
+
class={classes.activityBar({ orientation, variant, placement, density, className })}
|
|
183
190
|
>
|
|
184
191
|
{#if activityBar.header}
|
|
185
192
|
<div
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import type { SidebarActivityBar, SidebarDensity, SidebarSide, SidebarSize } from './sidebar.props.js';
|
|
1
|
+
import type { SidebarActivityBar, SidebarDensity, SidebarSide, SidebarSize, SidebarVariant } from './sidebar.props.js';
|
|
2
2
|
import { type SidebarThemeProps } from './sidebar.theme.js';
|
|
3
3
|
type $$ComponentProps = {
|
|
4
4
|
activityBar: SidebarActivityBar;
|
|
5
5
|
side: SidebarSide;
|
|
6
6
|
size: SidebarSize;
|
|
7
7
|
density: SidebarDensity;
|
|
8
|
+
/** The Sidebar variant whose panel surface the rail wears beside the panel. */
|
|
9
|
+
variant?: SidebarVariant;
|
|
10
|
+
/** `static` when there is no desktop container to hold its gutters (`collapsible="none"`). */
|
|
11
|
+
placement?: 'positioned' | 'static';
|
|
8
12
|
/** Vertical along the sidebar edge on desktop; horizontal at the top of the mobile drawer. */
|
|
9
13
|
orientation?: 'vertical' | 'horizontal';
|
|
10
14
|
/** Fallback accessible name when the activity bar sets no label. */
|