@noxlovette/material 0.3.3 → 0.4.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/README.md +1 -0
- package/dist/actions/index.d.ts +1 -0
- package/dist/actions/index.js +1 -0
- package/dist/actions/rovingFocusGroup.d.ts +20 -0
- package/dist/actions/rovingFocusGroup.js +82 -0
- package/dist/components/badge/theme.d.ts +8 -8
- package/dist/components/buttons/button-group/ButtonGroup.stories.svelte +83 -0
- package/dist/components/buttons/button-group/ButtonGroup.stories.svelte.d.ts +19 -0
- package/dist/components/buttons/button-group/ButtonGroup.svelte +10 -29
- package/dist/components/buttons/button-group/ButtonGroup.svelte.d.ts +7 -3
- package/dist/components/buttons/button-group/index.d.ts +0 -1
- package/dist/components/buttons/button-group/index.js +3 -1
- package/dist/components/buttons/button-group/theme.d.ts +17 -103
- package/dist/components/buttons/button-group/theme.js +7 -35
- package/dist/components/buttons/button-group/types.d.ts +9 -20
- package/dist/components/buttons/connected/ConnectedButtonGroup.stories.svelte +118 -0
- package/dist/components/buttons/connected/ConnectedButtonGroup.stories.svelte.d.ts +19 -0
- package/dist/components/buttons/connected/ConnectedButtonGroup.svelte +52 -0
- package/dist/components/buttons/connected/ConnectedButtonGroup.svelte.d.ts +12 -0
- package/dist/components/buttons/connected/ConnectedButtonGroupItem.svelte +37 -0
- package/dist/components/buttons/connected/ConnectedButtonGroupItem.svelte.d.ts +9 -0
- package/dist/components/buttons/connected/index.d.ts +3 -1
- package/dist/components/buttons/connected/index.js +3 -1
- package/dist/components/buttons/connected/theme.d.ts +125 -0
- package/dist/components/buttons/connected/theme.js +38 -0
- package/dist/components/buttons/connected/types.d.ts +32 -13
- package/dist/components/buttons/index.d.ts +1 -0
- package/dist/components/buttons/index.js +1 -0
- package/dist/components/buttons/split-button/SplitButton.stories.svelte +103 -0
- package/dist/components/buttons/split-button/SplitButton.stories.svelte.d.ts +19 -0
- package/dist/components/buttons/split-button/SplitButton.svelte +104 -0
- package/dist/components/buttons/split-button/SplitButton.svelte.d.ts +19 -0
- package/dist/components/buttons/split-button/index.d.ts +3 -0
- package/dist/components/{containers/panes → buttons/split-button}/index.js +1 -3
- package/dist/components/buttons/split-button/theme.d.ts +35 -0
- package/dist/components/buttons/split-button/theme.js +26 -0
- package/dist/components/buttons/split-button/types.d.ts +33 -0
- package/dist/components/buttons/theme.d.ts +142 -142
- package/dist/components/buttons/theme.js +2 -2
- package/dist/components/cards/Card.stories.svelte +58 -0
- package/dist/components/cards/Card.stories.svelte.d.ts +2 -17
- package/dist/components/cards/Card.svelte +76 -8
- package/dist/components/cards/theme.d.ts +26 -14
- package/dist/components/cards/theme.js +5 -1
- package/dist/components/cards/types.d.ts +39 -1
- package/dist/components/chips/Chip.stories.svelte +64 -0
- package/dist/components/chips/Chip.stories.svelte.d.ts +19 -0
- package/dist/components/chips/Chip.svelte +115 -0
- package/dist/components/chips/Chip.svelte.d.ts +14 -0
- package/dist/components/chips/index.d.ts +3 -0
- package/dist/components/{containers/merger → chips}/index.js +2 -1
- package/dist/components/chips/theme.d.ts +78 -0
- package/dist/components/chips/theme.js +72 -0
- package/dist/components/chips/types.d.ts +43 -0
- package/dist/components/containers/action-rail/ActionRail.stories.svelte +36 -0
- package/dist/components/containers/{panes/SinglePane.stories.svelte.d.ts → action-rail/ActionRail.stories.svelte.d.ts} +4 -4
- package/dist/components/containers/action-rail/ActionRail.svelte +35 -0
- package/dist/components/containers/action-rail/ActionRail.svelte.d.ts +17 -0
- package/dist/components/containers/action-rail/index.d.ts +3 -0
- package/dist/components/containers/action-rail/index.js +6 -0
- package/dist/components/containers/action-rail/theme.d.ts +45 -0
- package/dist/components/containers/action-rail/theme.js +26 -0
- package/dist/components/containers/action-rail/types.d.ts +20 -0
- package/dist/components/containers/action-rail/types.js +1 -0
- package/dist/components/containers/app/App.svelte +6 -6
- package/dist/components/containers/app/App.svelte.d.ts +4 -2
- package/dist/components/containers/app/theme.d.ts +1 -1
- package/dist/components/containers/app/types.d.ts +9 -4
- package/dist/components/containers/bottom-sheet/BottomSheet.stories.svelte +1 -1
- package/dist/components/containers/bottom-sheet/BottomSheet.svelte +1 -1
- package/dist/components/containers/context-menu/ContextMenu.stories.svelte +44 -0
- package/dist/components/containers/context-menu/ContextMenu.svelte +21 -5
- package/dist/components/containers/context-menu/ContextMenu.svelte.d.ts +11 -0
- package/dist/components/containers/context-menu/theme.d.ts +20 -2
- package/dist/components/containers/context-menu/theme.js +31 -8
- package/dist/components/containers/context-menu/types.d.ts +14 -2
- package/dist/components/containers/dialogue/theme.d.ts +20 -20
- package/dist/components/containers/divider/theme.d.ts +17 -17
- package/dist/components/containers/index.d.ts +2 -3
- package/dist/components/containers/index.js +2 -3
- package/dist/components/containers/link-preview/theme.d.ts +11 -11
- package/dist/components/containers/list/theme.d.ts +14 -14
- package/dist/components/containers/list/theme.js +1 -1
- package/dist/components/containers/menu/Menu.stories.svelte +105 -3
- package/dist/components/containers/menu/Menu.stories.svelte.d.ts +2 -17
- package/dist/components/containers/menu/Menu.svelte +9 -7
- package/dist/components/containers/menu/MenuCheckboxItem.svelte +55 -0
- package/dist/components/containers/menu/MenuCheckboxItem.svelte.d.ts +25 -0
- package/dist/components/containers/menu/MenuGroup.svelte +43 -0
- package/dist/components/containers/menu/MenuGroup.svelte.d.ts +28 -0
- package/dist/components/containers/menu/MenuRadioGroup.svelte +39 -0
- package/dist/components/containers/menu/MenuRadioGroup.svelte.d.ts +27 -0
- package/dist/components/containers/menu/MenuRadioItem.svelte +49 -0
- package/dist/components/containers/menu/MenuRadioItem.svelte.d.ts +17 -0
- package/dist/components/containers/menu/MenuSub.svelte +91 -0
- package/dist/components/containers/menu/MenuSub.svelte.d.ts +29 -0
- package/dist/components/containers/menu/index.d.ts +7 -0
- package/dist/components/containers/menu/index.js +8 -0
- package/dist/components/containers/menu/menu-item/MenuItem.svelte +71 -0
- package/dist/components/containers/{menu-item → menu/menu-item}/MenuItem.svelte.d.ts +2 -1
- package/dist/components/containers/{menu-item → menu/menu-item}/types.d.ts +5 -1
- package/dist/components/containers/menu/menu-item/types.js +1 -0
- package/dist/components/containers/menu/theme.d.ts +68 -0
- package/dist/components/containers/menu/theme.js +36 -0
- package/dist/components/containers/menu/types.d.ts +113 -2
- package/dist/components/containers/pane/DraggablePane.stories.svelte +199 -0
- package/dist/components/containers/pane/DraggablePane.stories.svelte.d.ts +4 -0
- package/dist/components/containers/pane/DraggablePane.svelte +533 -0
- package/dist/components/containers/pane/DraggablePane.svelte.d.ts +29 -0
- package/dist/components/containers/pane/Pane.stories.svelte +104 -0
- package/dist/components/containers/pane/Pane.stories.svelte.d.ts +19 -0
- package/dist/components/containers/pane/Pane.svelte +78 -0
- package/dist/components/containers/pane/Pane.svelte.d.ts +18 -0
- package/dist/components/containers/pane/PaneGrid.stories.svelte +134 -0
- package/dist/components/containers/pane/PaneGrid.stories.svelte.d.ts +19 -0
- package/dist/components/containers/pane/PaneGrid.svelte +43 -0
- package/dist/components/containers/pane/PaneGrid.svelte.d.ts +13 -0
- package/dist/components/containers/pane/PaneHandle.svelte +90 -0
- package/dist/components/containers/pane/PaneHandle.svelte.d.ts +12 -0
- package/dist/components/containers/pane/dragStore.svelte.d.ts +16 -0
- package/dist/components/containers/pane/dragStore.svelte.js +38 -0
- package/dist/components/containers/pane/index.d.ts +8 -0
- package/dist/components/containers/pane/index.js +11 -0
- package/dist/components/containers/pane/resizeStore.svelte.d.ts +10 -0
- package/dist/components/containers/pane/resizeStore.svelte.js +30 -0
- package/dist/components/containers/pane/theme.d.ts +571 -0
- package/dist/components/containers/pane/theme.js +211 -0
- package/dist/components/containers/pane/types.d.ts +249 -0
- package/dist/components/containers/pane/types.js +1 -0
- package/dist/components/containers/popover/Popover.stories.svelte +4 -4
- package/dist/components/containers/popover/Popover.svelte +3 -1
- package/dist/components/containers/popover/theme.d.ts +20 -20
- package/dist/components/containers/popover/types.d.ts +6 -2
- package/dist/components/containers/scroll-area/theme.d.ts +2 -2
- package/dist/components/containers/side-sheet/SideSheet.stories.svelte +16 -24
- package/dist/components/containers/side-sheet/SideSheet.svelte +73 -7
- package/dist/components/containers/side-sheet/SideSheet.svelte.d.ts +5 -0
- package/dist/components/containers/stack/theme.d.ts +44 -44
- package/dist/components/date/DateField.stories.svelte +57 -0
- package/dist/components/{containers/panes/SplitPane.stories.svelte.d.ts → date/DateField.stories.svelte.d.ts} +4 -4
- package/dist/components/date/DateField.svelte +11 -2
- package/dist/components/date/DateRangeField.stories.svelte +53 -0
- package/dist/components/{containers/panes/SupportingPane.stories.svelte.d.ts → date/DateRangeField.stories.svelte.d.ts} +4 -4
- package/dist/components/date/DateRangeField.svelte +2 -1
- package/dist/components/date/theme.d.ts +73 -4
- package/dist/components/date/theme.js +95 -26
- package/dist/components/date/types.d.ts +5 -0
- package/dist/components/forms/checkbox/Checkbox.svelte +7 -9
- package/dist/components/forms/checkbox/theme.d.ts +8 -8
- package/dist/components/forms/checkbox/theme.js +31 -9
- package/dist/components/forms/command/theme.d.ts +35 -35
- package/dist/components/forms/pin/theme.d.ts +2 -2
- package/dist/components/forms/radio-group/theme.d.ts +2 -2
- package/dist/components/forms/search/theme.d.ts +8 -8
- package/dist/components/forms/select/Select.svelte +3 -2
- package/dist/components/forms/select/theme.d.ts +26 -2
- package/dist/components/forms/select/theme.js +56 -22
- package/dist/components/forms/slider/theme.d.ts +20 -20
- package/dist/components/forms/switch/theme.d.ts +2 -2
- package/dist/components/forms/textfield/Textfield.stories.svelte +22 -0
- package/dist/components/forms/textfield/Textfield.svelte +10 -1
- package/dist/components/forms/textfield/theme.d.ts +44 -2
- package/dist/components/forms/textfield/theme.js +56 -16
- package/dist/components/forms/toggle-group/ToggleGroup.svelte +2 -2
- package/dist/components/forms/toggle-group/ToggleGroup.svelte.d.ts +2 -2
- package/dist/components/forms/toggle-group/ToggleGroupItem.svelte +1 -1
- package/dist/components/forms/toggle-group/ToggleGroupItem.svelte.d.ts +1 -1
- package/dist/components/forms/toggle-group/theme.d.ts +16 -16
- package/dist/components/forms/toggle-group/types.d.ts +2 -2
- package/dist/components/forms/tooltip/Tooltip.svelte +1 -1
- package/dist/components/forms/tooltip/theme.d.ts +2 -2
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +1 -0
- package/dist/components/misc/Avatar.stories.svelte +8 -0
- package/dist/components/misc/Avatar.svelte +35 -10
- package/dist/components/misc/DicebearAvatarBuilder.d.ts +59 -0
- package/dist/components/misc/DicebearAvatarBuilder.js +115 -0
- package/dist/components/misc/ThemeSettings.svelte +1 -1
- package/dist/components/misc/ThemeSwitcher.svelte +2 -2
- package/dist/components/misc/dicebearStyles.d.ts +21 -0
- package/dist/components/misc/dicebearStyles.js +169 -0
- package/dist/components/misc/index.d.ts +3 -0
- package/dist/components/misc/index.js +3 -0
- package/dist/components/misc/materialAvatarPalette.d.ts +6 -0
- package/dist/components/misc/materialAvatarPalette.js +44 -0
- package/dist/components/misc/theme.d.ts +20 -2
- package/dist/components/misc/theme.js +7 -1
- package/dist/components/misc/types.d.ts +20 -2
- package/dist/components/nav/appbar/AppBar.stories.svelte +1 -1
- package/dist/components/nav/appbar/AppBar.svelte +46 -15
- package/dist/components/nav/appbar/AppBar.svelte.d.ts +5 -0
- package/dist/components/nav/appbar/theme.d.ts +20 -14
- package/dist/components/nav/appbar/theme.js +11 -7
- package/dist/components/nav/appbar/types.d.ts +3 -1
- package/dist/components/nav/breadcrumb/Breadcrumb.stories.svelte +50 -0
- package/dist/components/nav/breadcrumb/Breadcrumb.stories.svelte.d.ts +19 -0
- package/dist/components/nav/breadcrumb/Breadcrumb.svelte +46 -0
- package/dist/components/nav/breadcrumb/Breadcrumb.svelte.d.ts +13 -0
- package/dist/components/nav/breadcrumb/index.d.ts +3 -0
- package/dist/components/nav/breadcrumb/index.js +6 -0
- package/dist/components/nav/breadcrumb/theme.d.ts +84 -0
- package/dist/components/nav/breadcrumb/theme.js +35 -0
- package/dist/components/nav/breadcrumb/types.d.ts +14 -0
- package/dist/components/nav/breadcrumb/types.js +1 -0
- package/dist/components/nav/index.d.ts +1 -0
- package/dist/components/nav/index.js +1 -0
- package/dist/components/nav/navbar/theme.d.ts +19 -19
- package/dist/components/nav/rail/Rail.stories.svelte +1 -5
- package/dist/components/nav/rail/Rail.svelte +3 -25
- package/dist/components/nav/rail/theme.d.ts +4 -4
- package/dist/components/nav/rail/types.d.ts +0 -5
- package/dist/components/nav/tabs/TabHolder.svelte +2 -1
- package/dist/components/nav/tabs/theme.d.ts +21 -21
- package/dist/components/nav/tabs/theme.js +6 -0
- package/dist/components/nav/tabs/types.d.ts +2 -2
- package/dist/components/pill/theme.d.ts +35 -35
- package/dist/components/snackbar/theme.d.ts +8 -8
- package/dist/components/table/Table.stories.svelte +267 -0
- package/dist/components/table/Table.stories.svelte.d.ts +19 -0
- package/dist/components/table/Table.svelte +1 -1
- package/dist/components/table/TableBody.svelte +16 -3
- package/dist/components/table/TableBody.svelte.d.ts +5 -1
- package/dist/components/table/TableRow.svelte +1 -1
- package/dist/components/table/theme.d.ts +2 -2
- package/dist/components/table/theme.js +2 -2
- package/dist/components/table/types.d.ts +7 -0
- package/dist/components/time/theme.d.ts +29 -29
- package/dist/components/toolbar/Toolbar.stories.svelte +71 -0
- package/dist/components/toolbar/Toolbar.stories.svelte.d.ts +19 -0
- package/dist/components/toolbar/Toolbar.svelte +19 -3
- package/dist/components/toolbar/Toolbar.svelte.d.ts +8 -2
- package/dist/components/toolbar/ToolbarButton.svelte +28 -4
- package/dist/components/toolbar/ToolbarDivider.svelte +4 -3
- package/dist/components/toolbar/ToolbarGroup.svelte +7 -2
- package/dist/components/toolbar/ToolbarGroupItem.svelte +29 -4
- package/dist/components/toolbar/ToolbarSection.svelte +35 -0
- package/dist/components/toolbar/ToolbarSection.svelte.d.ts +9 -0
- package/dist/components/toolbar/ToolbarSpacer.svelte +18 -0
- package/dist/components/toolbar/ToolbarSpacer.svelte.d.ts +9 -0
- package/dist/components/toolbar/index.d.ts +2 -0
- package/dist/components/toolbar/index.js +2 -0
- package/dist/components/toolbar/theme.d.ts +36 -21
- package/dist/components/toolbar/theme.js +27 -11
- package/dist/components/toolbar/types.d.ts +37 -4
- package/dist/components/typography/body/theme.d.ts +14 -14
- package/dist/components/typography/display/theme.d.ts +14 -14
- package/dist/components/typography/headline/theme.d.ts +14 -14
- package/dist/components/typography/kbd/Kbd.stories.svelte +1 -1
- package/dist/components/typography/kbd/theme.d.ts +8 -8
- package/dist/components/typography/label/theme.d.ts +14 -14
- package/dist/components/typography/title/theme.d.ts +14 -14
- package/dist/styles/component.css +126 -4
- package/dist/styles/motion.css +6 -3
- package/dist/utils/Layer.svelte +7 -3
- package/dist/utils/Theme.svelte +20 -20
- package/dist/utils/ThemeScript.svelte +1 -1
- package/dist/utils/icon/theme.d.ts +14 -14
- package/dist/utils/theme.svelte.js +1 -1
- package/package.json +39 -38
- package/bin/install-skill.js +0 -29
- package/dist/components/buttons/button-group/ButtonGroupItem.svelte +0 -41
- package/dist/components/buttons/button-group/ButtonGroupItem.svelte.d.ts +0 -8
- package/dist/components/buttons/connected/ConnectedButtons.svelte +0 -14
- package/dist/components/buttons/connected/ConnectedButtons.svelte.d.ts +0 -8
- package/dist/components/containers/menu-item/MenuItem.svelte +0 -74
- package/dist/components/containers/merger/InputMerger.svelte +0 -24
- package/dist/components/containers/merger/InputMerger.svelte.d.ts +0 -14
- package/dist/components/containers/merger/index.d.ts +0 -2
- package/dist/components/containers/merger/theme.d.ts +0 -1
- package/dist/components/containers/merger/theme.js +0 -4
- package/dist/components/containers/panes/SinglePane.stories.svelte +0 -67
- package/dist/components/containers/panes/SinglePane.svelte +0 -32
- package/dist/components/containers/panes/SinglePane.svelte.d.ts +0 -5
- package/dist/components/containers/panes/SplitPane.stories.svelte +0 -124
- package/dist/components/containers/panes/SplitPane.svelte +0 -144
- package/dist/components/containers/panes/SplitPane.svelte.d.ts +0 -8
- package/dist/components/containers/panes/SupportingPane.stories.svelte +0 -113
- package/dist/components/containers/panes/SupportingPane.svelte +0 -175
- package/dist/components/containers/panes/SupportingPane.svelte.d.ts +0 -9
- package/dist/components/containers/panes/index.d.ts +0 -5
- package/dist/components/containers/panes/theme.d.ts +0 -490
- package/dist/components/containers/panes/theme.js +0 -185
- package/dist/components/containers/panes/types.d.ts +0 -108
- package/dist/components/nav/rail/RailNavContext.svelte +0 -14
- package/dist/components/nav/rail/RailNavContext.svelte.d.ts +0 -8
- /package/dist/components/{containers/menu-item → buttons/split-button}/types.js +0 -0
- /package/dist/components/{containers/panes → chips}/types.js +0 -0
- /package/dist/components/containers/{menu-item → menu/menu-item}/index.d.ts +0 -0
- /package/dist/components/containers/{menu-item → menu/menu-item}/index.js +0 -0
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component
|
|
3
|
+
A floating panel that can be dragged anywhere within `bounds` by its header,
|
|
4
|
+
resized from any edge/corner when `resizable`, and collapsed into a small
|
|
5
|
+
fixed-corner pill when `collapsible`. Unlike `Pane`/`PaneGrid`, which lay out
|
|
6
|
+
fixed regions in normal flow, a `DraggablePane` floats above content
|
|
7
|
+
(`position: fixed`, elevated surface) and is positioned by `x`/`y` rather than
|
|
8
|
+
participating in flex layout. With `persistKey`, its last dragged position and
|
|
9
|
+
size survive reloads via `localStorage`.
|
|
10
|
+
|
|
11
|
+
Even with `width`/`height` unset (content-sized), the panel always carries a
|
|
12
|
+
standing `max-width`/`max-height` — `maxWidth`/`maxHeight` capped by `bounds`
|
|
13
|
+
(see `sizeCaps()`) — so content taller/wider than that ceiling scrolls inside
|
|
14
|
+
the `content` slot instead of rendering off-screen with no way to reach it.
|
|
15
|
+
|
|
16
|
+
`onClickOutside` fires on any pointer click landing outside the panel (header,
|
|
17
|
+
content, and resize handles all count as inside) while expanded — pass
|
|
18
|
+
`onClose` there to dismiss on outside click, or drive `collapsed` for a
|
|
19
|
+
minimize-on-outside-click panel.
|
|
20
|
+
|
|
21
|
+
Note: with `bounds` set to an element, the clamp is computed from that
|
|
22
|
+
element's rect at drag/resize/nudge time; a `ResizeObserver` on `bounds`
|
|
23
|
+
plus a capturing window `scroll` listener keep it recomputed as that
|
|
24
|
+
element resizes or scrolls (itself or via any scrolling ancestor), since
|
|
25
|
+
the pane itself stays `position: fixed` and doesn't move with either.
|
|
26
|
+
-->
|
|
27
|
+
<script lang="ts">
|
|
28
|
+
import clsx from 'clsx';
|
|
29
|
+
import { onMount } from 'svelte';
|
|
30
|
+
import { Icon, Layer } from '../../../utils/index.js';
|
|
31
|
+
import { ButtonIcon } from '../../buttons/index.js';
|
|
32
|
+
import { clickOutside } from '../../../actions/index.js';
|
|
33
|
+
import { draggablePane, type ResizeEdge } from './theme.js';
|
|
34
|
+
import { dragPositions } from './dragStore.svelte.js';
|
|
35
|
+
import type { DraggablePaneProps } from './types.js';
|
|
36
|
+
|
|
37
|
+
let {
|
|
38
|
+
children,
|
|
39
|
+
header,
|
|
40
|
+
title,
|
|
41
|
+
onClose,
|
|
42
|
+
onClickOutside,
|
|
43
|
+
x = $bindable(undefined),
|
|
44
|
+
y = $bindable(undefined),
|
|
45
|
+
initialX = 24,
|
|
46
|
+
initialY = 24,
|
|
47
|
+
persistKey,
|
|
48
|
+
bounds = 'viewport',
|
|
49
|
+
boundsPadding = 8,
|
|
50
|
+
width = $bindable(undefined),
|
|
51
|
+
height = $bindable(undefined),
|
|
52
|
+
resizable = false,
|
|
53
|
+
minWidth = 200,
|
|
54
|
+
minHeight = 160,
|
|
55
|
+
maxWidth,
|
|
56
|
+
maxHeight,
|
|
57
|
+
disableDrag = false,
|
|
58
|
+
collapsible = false,
|
|
59
|
+
collapsed = $bindable(false),
|
|
60
|
+
minimizedTitle,
|
|
61
|
+
minimizedCorner = 'bottom-left',
|
|
62
|
+
minimizedClass,
|
|
63
|
+
class: className,
|
|
64
|
+
contentClass,
|
|
65
|
+
...rest
|
|
66
|
+
}: DraggablePaneProps = $props();
|
|
67
|
+
|
|
68
|
+
let panelEl: HTMLDivElement | undefined = $state();
|
|
69
|
+
let dragging = $state(false);
|
|
70
|
+
let resizeDir: ResizeEdge | null = $state(null);
|
|
71
|
+
/**
|
|
72
|
+
* The standing `max-width`/`max-height` ceiling (`sizeCaps()`, tracked
|
|
73
|
+
* live) — applied via `style` regardless of whether `height`/`width` are
|
|
74
|
+
* set, so a content-sized pane (the default — see `height`'s docs) can't
|
|
75
|
+
* render taller/wider than `bounds` with no way to reach the rest of it.
|
|
76
|
+
* Undefined only before the first `onMount`/`recomputeBounds` run — which
|
|
77
|
+
* includes SSR, since both only run client-side. Also read directly for
|
|
78
|
+
* the resize handles' `aria-valuemax` instead of calling `sizeCaps()`
|
|
79
|
+
* fresh in the template: that call site runs during SSR too (it's a plain
|
|
80
|
+
* expression producing HTML), and `sizeCaps()` → `boundsRect()` touches
|
|
81
|
+
* `window`/`HTMLElement`, which don't exist there.
|
|
82
|
+
*/
|
|
83
|
+
let capWidth: number | undefined = $state();
|
|
84
|
+
let capHeight: number | undefined = $state();
|
|
85
|
+
|
|
86
|
+
const active = $derived(dragging || resizeDir !== null);
|
|
87
|
+
|
|
88
|
+
const { base, headerBar, grip, headline, actions, content, miniBase, miniIcon, miniLabel } =
|
|
89
|
+
$derived(draggablePane({ dragging: active, disableDrag, corner: minimizedCorner }));
|
|
90
|
+
|
|
91
|
+
// Once resizable, min-width/min-height move from the base slot's static
|
|
92
|
+
// `min-w-72` class to inline styles driven by the minWidth/minHeight props
|
|
93
|
+
// — a class-based min-width would win over a smaller inline `width`
|
|
94
|
+
// regardless of what those props say, silently overriding the safety-net
|
|
95
|
+
// floor a consumer configured. A non-resizable pane keeps the plain
|
|
96
|
+
// `min-w-72` class fallback (unaffected by minWidth/minHeight).
|
|
97
|
+
const style = $derived(
|
|
98
|
+
clsx(
|
|
99
|
+
x !== undefined && `left: ${x}px;`,
|
|
100
|
+
y !== undefined && `top: ${y}px;`,
|
|
101
|
+
width !== undefined && `width: ${width}px;`,
|
|
102
|
+
height !== undefined && `height: ${height}px;`,
|
|
103
|
+
resizable && `min-width: ${minWidth}px;`,
|
|
104
|
+
resizable && `min-height: ${minHeight}px;`,
|
|
105
|
+
capWidth !== undefined && `max-width: ${capWidth}px;`,
|
|
106
|
+
capHeight !== undefined && `max-height: ${capHeight}px;`
|
|
107
|
+
)
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
function persist() {
|
|
111
|
+
if (!persistKey) return;
|
|
112
|
+
dragPositions.set(persistKey, { x: x ?? initialX, y: y ?? initialY, width, height });
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function boundsRect() {
|
|
116
|
+
if (bounds instanceof HTMLElement) {
|
|
117
|
+
const rect = bounds.getBoundingClientRect();
|
|
118
|
+
return { left: rect.left, top: rect.top, right: rect.right, bottom: rect.bottom };
|
|
119
|
+
}
|
|
120
|
+
return { left: 0, top: 0, right: window.innerWidth, bottom: window.innerHeight };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function clamp(
|
|
124
|
+
nextX: number,
|
|
125
|
+
nextY: number,
|
|
126
|
+
w = width ?? panelEl?.offsetWidth ?? 0,
|
|
127
|
+
h = height ?? panelEl?.offsetHeight ?? 0
|
|
128
|
+
) {
|
|
129
|
+
const rect = boundsRect();
|
|
130
|
+
const minX = rect.left + boundsPadding;
|
|
131
|
+
const maxX = Math.max(minX, rect.right - w - boundsPadding);
|
|
132
|
+
const minY = rect.top + boundsPadding;
|
|
133
|
+
const maxY = Math.max(minY, rect.bottom - h - boundsPadding);
|
|
134
|
+
return {
|
|
135
|
+
x: Math.min(Math.max(nextX, minX), maxX),
|
|
136
|
+
y: Math.min(Math.max(nextY, minY), maxY)
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** The effective width/height ceiling right now: `maxWidth`/`maxHeight` capped by `bounds`. */
|
|
141
|
+
function sizeCaps() {
|
|
142
|
+
const rect = boundsRect();
|
|
143
|
+
const availW = rect.right - rect.left - boundsPadding * 2;
|
|
144
|
+
const availH = rect.bottom - rect.top - boundsPadding * 2;
|
|
145
|
+
return {
|
|
146
|
+
width: Math.max(minWidth, Math.min(maxWidth ?? Infinity, availW)),
|
|
147
|
+
height: Math.max(minHeight, Math.min(maxHeight ?? Infinity, availH))
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Refreshes `capWidth`/`capHeight` from `sizeCaps()` — call whenever `bounds`'s rect or the cap inputs may have changed. */
|
|
152
|
+
function updateCaps() {
|
|
153
|
+
const caps = sizeCaps();
|
|
154
|
+
capWidth = caps.width;
|
|
155
|
+
capHeight = caps.height;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Clamps a candidate size to `minWidth`/`minHeight` (a hard floor) and `maxWidth`/`maxHeight`/`bounds` (a ceiling). */
|
|
159
|
+
function clampSize(w: number, h: number) {
|
|
160
|
+
const caps = sizeCaps();
|
|
161
|
+
return {
|
|
162
|
+
width: Math.min(Math.max(w, minWidth), caps.width),
|
|
163
|
+
height: Math.min(Math.max(h, minHeight), caps.height)
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function moveTo(nextX: number, nextY: number) {
|
|
168
|
+
const next = clamp(nextX, nextY);
|
|
169
|
+
x = next.x;
|
|
170
|
+
y = next.y;
|
|
171
|
+
persist();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Grows/shrinks from `dir`'s edge(s), keeping the opposite edge(s) fixed —
|
|
176
|
+
* pure math from an explicit (baseW, baseH, baseX, baseY) baseline plus a
|
|
177
|
+
* (dx, dy) offset from it, so callers can pick their own baseline: the
|
|
178
|
+
* pointer-drag start position (cumulative offset, recomputed fresh each
|
|
179
|
+
* move so drift can't accumulate — see `moveResize`), or the current state
|
|
180
|
+
* (a one-shot incremental nudge — see `resizeKeydown`).
|
|
181
|
+
*/
|
|
182
|
+
function computeResize(
|
|
183
|
+
dir: ResizeEdge,
|
|
184
|
+
baseW: number,
|
|
185
|
+
baseH: number,
|
|
186
|
+
baseX: number,
|
|
187
|
+
baseY: number,
|
|
188
|
+
dx: number,
|
|
189
|
+
dy: number
|
|
190
|
+
) {
|
|
191
|
+
let w = baseW;
|
|
192
|
+
let h = baseH;
|
|
193
|
+
if (dir.includes('e')) w = baseW + dx;
|
|
194
|
+
if (dir.includes('w')) w = baseW - dx;
|
|
195
|
+
if (dir.includes('s')) h = baseH + dy;
|
|
196
|
+
if (dir.includes('n')) h = baseH - dy;
|
|
197
|
+
|
|
198
|
+
const clamped = clampSize(w, h);
|
|
199
|
+
|
|
200
|
+
let nextX = baseX;
|
|
201
|
+
let nextY = baseY;
|
|
202
|
+
if (dir.includes('w')) nextX = baseX + (baseW - clamped.width);
|
|
203
|
+
if (dir.includes('n')) nextY = baseY + (baseH - clamped.height);
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
width: clamped.width,
|
|
207
|
+
height: clamped.height,
|
|
208
|
+
x: nextX,
|
|
209
|
+
y: nextY,
|
|
210
|
+
moved: dir.includes('w') || dir.includes('n')
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function applyResize(result: ReturnType<typeof computeResize>) {
|
|
215
|
+
let w = result.width;
|
|
216
|
+
let h = result.height;
|
|
217
|
+
if (result.moved) {
|
|
218
|
+
const posClamped = clamp(result.x, result.y, w, h);
|
|
219
|
+
// A w/n resize keeps its opposite (anchor) edge fixed by construction
|
|
220
|
+
// (see computeResize) — but if boundsPadding then clamps the position
|
|
221
|
+
// further, the anchor would drift unless the size shrinks by the same
|
|
222
|
+
// amount the position got held back.
|
|
223
|
+
if (posClamped.x !== result.x) w = Math.max(minWidth, w - (posClamped.x - result.x));
|
|
224
|
+
if (posClamped.y !== result.y) h = Math.max(minHeight, h - (posClamped.y - result.y));
|
|
225
|
+
x = posClamped.x;
|
|
226
|
+
y = posClamped.y;
|
|
227
|
+
}
|
|
228
|
+
width = w;
|
|
229
|
+
height = h;
|
|
230
|
+
persist();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** One-shot incremental resize from the current state — for keyboard nudges. */
|
|
234
|
+
function nudgeResize(dir: ResizeEdge, dx: number, dy: number) {
|
|
235
|
+
applyResize(
|
|
236
|
+
computeResize(
|
|
237
|
+
dir,
|
|
238
|
+
width ?? panelEl?.offsetWidth ?? minWidth,
|
|
239
|
+
height ?? panelEl?.offsetHeight ?? minHeight,
|
|
240
|
+
x ?? initialX,
|
|
241
|
+
y ?? initialY,
|
|
242
|
+
dx,
|
|
243
|
+
dy
|
|
244
|
+
)
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Re-clamps the current position/size, and the standing size cap, against `bounds`'s current rect — call whenever that rect may have changed. */
|
|
249
|
+
function recomputeBounds() {
|
|
250
|
+
updateCaps();
|
|
251
|
+
moveTo(x ?? initialX, y ?? initialY);
|
|
252
|
+
if (width !== undefined || height !== undefined) nudgeResize('se', 0, 0);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
onMount(() => {
|
|
256
|
+
const fallback = { x: x ?? initialX, y: y ?? initialY, width, height };
|
|
257
|
+
const hydrated = persistKey ? dragPositions.hydrate(persistKey, fallback) : fallback;
|
|
258
|
+
width = hydrated.width;
|
|
259
|
+
height = hydrated.height;
|
|
260
|
+
updateCaps();
|
|
261
|
+
const clamped = clamp(
|
|
262
|
+
hydrated.x,
|
|
263
|
+
hydrated.y,
|
|
264
|
+
hydrated.width ?? panelEl?.offsetWidth ?? 0,
|
|
265
|
+
hydrated.height ?? panelEl?.offsetHeight ?? 0
|
|
266
|
+
);
|
|
267
|
+
x = clamped.x;
|
|
268
|
+
y = clamped.y;
|
|
269
|
+
persist();
|
|
270
|
+
|
|
271
|
+
window.addEventListener('resize', recomputeBounds);
|
|
272
|
+
return () => window.removeEventListener('resize', recomputeBounds);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// `bounds` can be an element that resizes or scrolls independently of the
|
|
276
|
+
// window (e.g. via a scrolling ancestor) — a plain window `resize`
|
|
277
|
+
// listener misses both, so its rect is tracked directly whenever it's an
|
|
278
|
+
// element. Capturing `scroll` catches any scrolling ancestor, not just
|
|
279
|
+
// `bounds` itself, since scroll events don't bubble. Reading
|
|
280
|
+
// maxWidth/maxHeight/minWidth/minHeight up front (via updateCaps) also
|
|
281
|
+
// makes this effect re-run — and the cap refresh — when those change.
|
|
282
|
+
$effect(() => {
|
|
283
|
+
updateCaps();
|
|
284
|
+
if (!(bounds instanceof HTMLElement)) return;
|
|
285
|
+
const target = bounds;
|
|
286
|
+
const ro = new ResizeObserver(recomputeBounds);
|
|
287
|
+
ro.observe(target);
|
|
288
|
+
window.addEventListener('scroll', recomputeBounds, true);
|
|
289
|
+
return () => {
|
|
290
|
+
ro.disconnect();
|
|
291
|
+
window.removeEventListener('scroll', recomputeBounds, true);
|
|
292
|
+
};
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
$effect(() => {
|
|
296
|
+
if (!persistKey) return;
|
|
297
|
+
const stored = dragPositions.get(persistKey);
|
|
298
|
+
if (stored) {
|
|
299
|
+
x = stored.x;
|
|
300
|
+
y = stored.y;
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
let dragStartX = 0;
|
|
305
|
+
let dragStartY = 0;
|
|
306
|
+
let originX = 0;
|
|
307
|
+
let originY = 0;
|
|
308
|
+
|
|
309
|
+
function startDrag(event: PointerEvent) {
|
|
310
|
+
if (disableDrag) return;
|
|
311
|
+
dragging = true;
|
|
312
|
+
dragStartX = event.clientX;
|
|
313
|
+
dragStartY = event.clientY;
|
|
314
|
+
originX = x ?? initialX;
|
|
315
|
+
originY = y ?? initialY;
|
|
316
|
+
(event.currentTarget as HTMLElement).setPointerCapture(event.pointerId);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function moveDrag(event: PointerEvent) {
|
|
320
|
+
if (!dragging) return;
|
|
321
|
+
moveTo(originX + (event.clientX - dragStartX), originY + (event.clientY - dragStartY));
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function endDrag() {
|
|
325
|
+
dragging = false;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const NUDGE = 8;
|
|
329
|
+
|
|
330
|
+
function onKeydown(event: KeyboardEvent) {
|
|
331
|
+
if (disableDrag) return;
|
|
332
|
+
const step = event.shiftKey ? NUDGE * 4 : NUDGE;
|
|
333
|
+
const deltas: Record<string, [number, number]> = {
|
|
334
|
+
ArrowUp: [0, -step],
|
|
335
|
+
ArrowDown: [0, step],
|
|
336
|
+
ArrowLeft: [-step, 0],
|
|
337
|
+
ArrowRight: [step, 0]
|
|
338
|
+
};
|
|
339
|
+
const delta = deltas[event.key];
|
|
340
|
+
if (!delta) return;
|
|
341
|
+
event.preventDefault();
|
|
342
|
+
moveTo((x ?? initialX) + delta[0], (y ?? initialY) + delta[1]);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
let resizeStartClientX = 0;
|
|
346
|
+
let resizeStartClientY = 0;
|
|
347
|
+
let resizeStartWidth = 0;
|
|
348
|
+
let resizeStartHeight = 0;
|
|
349
|
+
let resizeStartPosX = 0;
|
|
350
|
+
let resizeStartPosY = 0;
|
|
351
|
+
|
|
352
|
+
function startResize(dir: ResizeEdge) {
|
|
353
|
+
return (event: PointerEvent) => {
|
|
354
|
+
if (!resizable) return;
|
|
355
|
+
event.stopPropagation();
|
|
356
|
+
resizeDir = dir;
|
|
357
|
+
resizeStartClientX = event.clientX;
|
|
358
|
+
resizeStartClientY = event.clientY;
|
|
359
|
+
resizeStartWidth = width ?? panelEl?.offsetWidth ?? minWidth;
|
|
360
|
+
resizeStartHeight = height ?? panelEl?.offsetHeight ?? minHeight;
|
|
361
|
+
resizeStartPosX = x ?? initialX;
|
|
362
|
+
resizeStartPosY = y ?? initialY;
|
|
363
|
+
(event.currentTarget as HTMLElement).setPointerCapture(event.pointerId);
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Deliberately recomputed from the fixed (resizeStartClientX/Y) baseline on
|
|
368
|
+
// every move rather than accumulated via `event.movementX`/`movementY` —
|
|
369
|
+
// the latter isn't reliably populated across every input path (some
|
|
370
|
+
// synthetic/automated drag sequences leave it 0), which would silently
|
|
371
|
+
// drop the resize. Matches `moveDrag`'s baseline-delta approach.
|
|
372
|
+
function moveResize(event: PointerEvent) {
|
|
373
|
+
if (!resizeDir) return;
|
|
374
|
+
applyResize(
|
|
375
|
+
computeResize(
|
|
376
|
+
resizeDir,
|
|
377
|
+
resizeStartWidth,
|
|
378
|
+
resizeStartHeight,
|
|
379
|
+
resizeStartPosX,
|
|
380
|
+
resizeStartPosY,
|
|
381
|
+
event.clientX - resizeStartClientX,
|
|
382
|
+
event.clientY - resizeStartClientY
|
|
383
|
+
)
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function endResize() {
|
|
388
|
+
resizeDir = null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const edgeAxis: Record<'n' | 's' | 'e' | 'w', 'horizontal' | 'vertical'> = {
|
|
392
|
+
n: 'horizontal',
|
|
393
|
+
s: 'horizontal',
|
|
394
|
+
e: 'vertical',
|
|
395
|
+
w: 'vertical'
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
const edgeLabel: Record<'n' | 's' | 'e' | 'w', string> = {
|
|
399
|
+
n: 'top edge',
|
|
400
|
+
s: 'bottom edge',
|
|
401
|
+
e: 'right edge',
|
|
402
|
+
w: 'left edge'
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
function resizeKeydown(dir: 'n' | 's' | 'e' | 'w') {
|
|
406
|
+
return (event: KeyboardEvent) => {
|
|
407
|
+
if (!resizable) return;
|
|
408
|
+
const step = event.shiftKey ? NUDGE * 4 : NUDGE;
|
|
409
|
+
const deltas: Record<string, [number, number]> = {
|
|
410
|
+
ArrowRight: [step, 0],
|
|
411
|
+
ArrowLeft: [-step, 0],
|
|
412
|
+
ArrowDown: [0, step],
|
|
413
|
+
ArrowUp: [0, -step]
|
|
414
|
+
};
|
|
415
|
+
const delta = deltas[event.key];
|
|
416
|
+
if (!delta) return;
|
|
417
|
+
event.preventDefault();
|
|
418
|
+
nudgeResize(dir, delta[0], delta[1]);
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const edges = ['n', 's', 'e', 'w'] as const;
|
|
423
|
+
const corners = ['ne', 'nw', 'se', 'sw'] as const;
|
|
424
|
+
</script>
|
|
425
|
+
|
|
426
|
+
{#if collapsed}
|
|
427
|
+
<button
|
|
428
|
+
type="button"
|
|
429
|
+
class={miniBase({ class: minimizedClass })}
|
|
430
|
+
onclick={() => (collapsed = false)}
|
|
431
|
+
aria-label={`Expand ${minimizedTitle ?? title ?? 'panel'}`}
|
|
432
|
+
>
|
|
433
|
+
<Icon name="open_in_full" size="sm" class={miniIcon()} />
|
|
434
|
+
<span class={miniLabel()}>{minimizedTitle ?? title}</span>
|
|
435
|
+
</button>
|
|
436
|
+
{:else}
|
|
437
|
+
<div
|
|
438
|
+
bind:this={panelEl}
|
|
439
|
+
class={base({ class: className })}
|
|
440
|
+
{style}
|
|
441
|
+
role="group"
|
|
442
|
+
aria-label={title}
|
|
443
|
+
use:clickOutside={() => onClickOutside?.()}
|
|
444
|
+
{...rest}
|
|
445
|
+
>
|
|
446
|
+
<div
|
|
447
|
+
class={headerBar()}
|
|
448
|
+
role="button"
|
|
449
|
+
tabindex={disableDrag ? undefined : 0}
|
|
450
|
+
aria-label={disableDrag ? undefined : title ? `Drag to move ${title}` : 'Drag to move'}
|
|
451
|
+
aria-keyshortcuts={disableDrag ? undefined : 'ArrowUp ArrowDown ArrowLeft ArrowRight'}
|
|
452
|
+
onpointerdown={disableDrag ? undefined : startDrag}
|
|
453
|
+
onpointermove={disableDrag ? undefined : moveDrag}
|
|
454
|
+
onpointerup={disableDrag ? undefined : endDrag}
|
|
455
|
+
onpointercancel={disableDrag ? undefined : endDrag}
|
|
456
|
+
onkeydown={disableDrag ? undefined : onKeydown}
|
|
457
|
+
>
|
|
458
|
+
{#if !disableDrag}
|
|
459
|
+
<Layer />
|
|
460
|
+
{/if}
|
|
461
|
+
{#if header}
|
|
462
|
+
{@render header()}
|
|
463
|
+
{:else}
|
|
464
|
+
{#if !disableDrag}
|
|
465
|
+
<Icon name="drag_indicator" class={grip()} />
|
|
466
|
+
{/if}
|
|
467
|
+
{#if title}
|
|
468
|
+
<span class={headline()}>{title}</span>
|
|
469
|
+
{/if}
|
|
470
|
+
{#if collapsible || onClose}
|
|
471
|
+
<div
|
|
472
|
+
class={actions()}
|
|
473
|
+
role="presentation"
|
|
474
|
+
onpointerdown={(event) => event.stopPropagation()}
|
|
475
|
+
>
|
|
476
|
+
{#if collapsible}
|
|
477
|
+
<ButtonIcon
|
|
478
|
+
variant="text"
|
|
479
|
+
size="sm"
|
|
480
|
+
iconProps={{ name: 'collapse_all' }}
|
|
481
|
+
aria-label="Minimize"
|
|
482
|
+
onclick={() => (collapsed = true)}
|
|
483
|
+
/>
|
|
484
|
+
{/if}
|
|
485
|
+
{#if onClose}
|
|
486
|
+
<ButtonIcon
|
|
487
|
+
variant="text"
|
|
488
|
+
size="sm"
|
|
489
|
+
iconProps={{ name: 'close' }}
|
|
490
|
+
aria-label="Close"
|
|
491
|
+
onclick={onClose}
|
|
492
|
+
/>
|
|
493
|
+
{/if}
|
|
494
|
+
</div>
|
|
495
|
+
{/if}
|
|
496
|
+
{/if}
|
|
497
|
+
</div>
|
|
498
|
+
<div class={content({ class: clsx(contentClass) })}>
|
|
499
|
+
{@render children()}
|
|
500
|
+
</div>
|
|
501
|
+
{#if resizable}
|
|
502
|
+
{#each edges as dir (dir)}
|
|
503
|
+
<div
|
|
504
|
+
class={draggablePane({ edge: dir }).resizeHandle()}
|
|
505
|
+
role="slider"
|
|
506
|
+
aria-orientation={edgeAxis[dir]}
|
|
507
|
+
aria-label={`Resize ${edgeLabel[dir]}`}
|
|
508
|
+
aria-valuenow={dir === 'e' || dir === 'w'
|
|
509
|
+
? (width ?? panelEl?.offsetWidth)
|
|
510
|
+
: (height ?? panelEl?.offsetHeight)}
|
|
511
|
+
aria-valuemin={dir === 'e' || dir === 'w' ? minWidth : minHeight}
|
|
512
|
+
aria-valuemax={dir === 'e' || dir === 'w' ? capWidth : capHeight}
|
|
513
|
+
tabindex="0"
|
|
514
|
+
onpointerdown={startResize(dir)}
|
|
515
|
+
onpointermove={moveResize}
|
|
516
|
+
onpointerup={endResize}
|
|
517
|
+
onpointercancel={endResize}
|
|
518
|
+
onkeydown={resizeKeydown(dir)}
|
|
519
|
+
></div>
|
|
520
|
+
{/each}
|
|
521
|
+
{#each corners as dir (dir)}
|
|
522
|
+
<div
|
|
523
|
+
class={draggablePane({ edge: dir }).resizeHandle()}
|
|
524
|
+
role="presentation"
|
|
525
|
+
onpointerdown={startResize(dir)}
|
|
526
|
+
onpointermove={moveResize}
|
|
527
|
+
onpointerup={endResize}
|
|
528
|
+
onpointercancel={endResize}
|
|
529
|
+
></div>
|
|
530
|
+
{/each}
|
|
531
|
+
{/if}
|
|
532
|
+
</div>
|
|
533
|
+
{/if}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { DraggablePaneProps } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* A floating panel that can be dragged anywhere within `bounds` by its header,
|
|
4
|
+
* resized from any edge/corner when `resizable`, and collapsed into a small
|
|
5
|
+
* fixed-corner pill when `collapsible`. Unlike `Pane`/`PaneGrid`, which lay out
|
|
6
|
+
* fixed regions in normal flow, a `DraggablePane` floats above content
|
|
7
|
+
* (`position: fixed`, elevated surface) and is positioned by `x`/`y` rather than
|
|
8
|
+
* participating in flex layout. With `persistKey`, its last dragged position and
|
|
9
|
+
* size survive reloads via `localStorage`.
|
|
10
|
+
*
|
|
11
|
+
* Even with `width`/`height` unset (content-sized), the panel always carries a
|
|
12
|
+
* standing `max-width`/`max-height` — `maxWidth`/`maxHeight` capped by `bounds`
|
|
13
|
+
* (see `sizeCaps()`) — so content taller/wider than that ceiling scrolls inside
|
|
14
|
+
* the `content` slot instead of rendering off-screen with no way to reach it.
|
|
15
|
+
*
|
|
16
|
+
* `onClickOutside` fires on any pointer click landing outside the panel (header,
|
|
17
|
+
* content, and resize handles all count as inside) while expanded — pass
|
|
18
|
+
* `onClose` there to dismiss on outside click, or drive `collapsed` for a
|
|
19
|
+
* minimize-on-outside-click panel.
|
|
20
|
+
*
|
|
21
|
+
* Note: with `bounds` set to an element, the clamp is computed from that
|
|
22
|
+
* element's rect at drag/resize/nudge time; a `ResizeObserver` on `bounds`
|
|
23
|
+
* plus a capturing window `scroll` listener keep it recomputed as that
|
|
24
|
+
* element resizes or scrolls (itself or via any scrolling ancestor), since
|
|
25
|
+
* the pane itself stays `position: fixed` and doesn't move with either.
|
|
26
|
+
*/
|
|
27
|
+
declare const DraggablePane: import("svelte").Component<DraggablePaneProps, {}, "x" | "y" | "width" | "height" | "collapsed">;
|
|
28
|
+
type DraggablePane = ReturnType<typeof DraggablePane>;
|
|
29
|
+
export default DraggablePane;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import { defineMeta } from '@storybook/addon-svelte-csf';
|
|
3
|
+
import Pane from './Pane.svelte';
|
|
4
|
+
import AppBar from '../../nav/appbar/AppBar.svelte';
|
|
5
|
+
|
|
6
|
+
const { Story } = defineMeta({
|
|
7
|
+
title: 'Containers/Pane',
|
|
8
|
+
component: Pane,
|
|
9
|
+
argTypes: {
|
|
10
|
+
padding: {
|
|
11
|
+
control: 'select',
|
|
12
|
+
options: ['none', 'sm', 'md', 'lg']
|
|
13
|
+
},
|
|
14
|
+
gap: {
|
|
15
|
+
control: 'select',
|
|
16
|
+
options: ['none', 'sm', 'md', 'lg', 'xl']
|
|
17
|
+
},
|
|
18
|
+
background: { control: 'boolean' },
|
|
19
|
+
full: { control: 'boolean' },
|
|
20
|
+
rounded: { control: 'boolean' }
|
|
21
|
+
},
|
|
22
|
+
args: {
|
|
23
|
+
padding: 'md',
|
|
24
|
+
gap: 'md',
|
|
25
|
+
background: true,
|
|
26
|
+
full: false,
|
|
27
|
+
rounded: true
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<!-- Standalone usage: a single page-level content region, the same role SinglePane used to play. -->
|
|
33
|
+
<Story name="Standalone">
|
|
34
|
+
{#snippet template(args)}
|
|
35
|
+
<Pane
|
|
36
|
+
padding={args.padding}
|
|
37
|
+
gap={args.gap}
|
|
38
|
+
background={args.background}
|
|
39
|
+
full={args.full}
|
|
40
|
+
rounded={args.rounded}
|
|
41
|
+
>
|
|
42
|
+
<div class="bg-md-sys-color-surface-container-high rounded-lg p-6">Block one</div>
|
|
43
|
+
<div class="bg-md-sys-color-surface-container-high rounded-lg p-6">Block two</div>
|
|
44
|
+
<div class="bg-md-sys-color-surface-container-high rounded-lg p-6">Block three</div>
|
|
45
|
+
</Pane>
|
|
46
|
+
{/snippet}
|
|
47
|
+
</Story>
|
|
48
|
+
|
|
49
|
+
<!-- Pane has no built-in centered-width variant — center content by passing `contentClass`. -->
|
|
50
|
+
<Story name="Centered Widths" asChild>
|
|
51
|
+
<div class="flex flex-col gap-6">
|
|
52
|
+
<Pane contentClass="max-w-2xl mx-auto" background={false}>
|
|
53
|
+
<div class="bg-md-sys-color-secondary-container rounded-lg p-4 text-sm">narrow</div>
|
|
54
|
+
</Pane>
|
|
55
|
+
<Pane contentClass="max-w-5xl mx-auto" background={false}>
|
|
56
|
+
<div class="bg-md-sys-color-secondary-container rounded-lg p-4 text-sm">medium</div>
|
|
57
|
+
</Pane>
|
|
58
|
+
<Pane background={false}>
|
|
59
|
+
<div class="bg-md-sys-color-secondary-container rounded-lg p-4 text-sm">
|
|
60
|
+
none (full width)
|
|
61
|
+
</div>
|
|
62
|
+
</Pane>
|
|
63
|
+
</div>
|
|
64
|
+
</Story>
|
|
65
|
+
|
|
66
|
+
<!--
|
|
67
|
+
As a PaneGrid child: an unset `width` makes a pane flexible (grows to fill
|
|
68
|
+
remaining space). See PaneGrid's own stories for direction/gap/margin/resizable
|
|
69
|
+
demos — this story is scoped to Pane's own props.
|
|
70
|
+
-->
|
|
71
|
+
<!--
|
|
72
|
+
A mounted AppBar's measured height is published to appbarStore.svelte.ts;
|
|
73
|
+
a `full` Pane (the default) reads it and subtracts it from `100dvh` so the
|
|
74
|
+
two don't stack into an overflow-scrolling page. No props needed on either
|
|
75
|
+
side — AppBar just needs to actually be mounted somewhere on the page.
|
|
76
|
+
-->
|
|
77
|
+
<Story name="With AppBar" asChild parameters={{ viewport: { defaultViewport: 'mobile1' } }}>
|
|
78
|
+
<AppBar title="Inbox" ghost />
|
|
79
|
+
<Pane>
|
|
80
|
+
<div class="bg-md-sys-color-secondary-container rounded-lg p-4 text-sm">
|
|
81
|
+
This pane's min-height is <code>100dvh</code> minus the AppBar's measured height above —
|
|
82
|
+
automatic, via <code>appbarStore.svelte.ts</code>.
|
|
83
|
+
</div>
|
|
84
|
+
</Pane>
|
|
85
|
+
</Story>
|
|
86
|
+
|
|
87
|
+
<Story name="As Grid Item" asChild>
|
|
88
|
+
<div
|
|
89
|
+
class="border-md-sys-color-outline-variant relative h-[280px] overflow-hidden rounded-lg border"
|
|
90
|
+
>
|
|
91
|
+
<div class="flex h-full">
|
|
92
|
+
<Pane width={200} background={false} padding="sm">
|
|
93
|
+
<div class="bg-md-sys-color-secondary-container h-full rounded-lg p-4 text-sm">
|
|
94
|
+
width=200 (fixed)
|
|
95
|
+
</div>
|
|
96
|
+
</Pane>
|
|
97
|
+
<Pane background={false} padding="sm">
|
|
98
|
+
<div class="bg-md-sys-color-surface-container-high h-full rounded-lg p-4 text-sm">
|
|
99
|
+
width unset (flexible, grows)
|
|
100
|
+
</div>
|
|
101
|
+
</Pane>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
</Story>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import Pane from './Pane.svelte';
|
|
2
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
3
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
4
|
+
$$bindings?: Bindings;
|
|
5
|
+
} & Exports;
|
|
6
|
+
(internal: unknown, props: {
|
|
7
|
+
$$events?: Events;
|
|
8
|
+
$$slots?: Slots;
|
|
9
|
+
}): Exports & {
|
|
10
|
+
$set?: any;
|
|
11
|
+
$on?: any;
|
|
12
|
+
};
|
|
13
|
+
z_$$bindings?: Bindings;
|
|
14
|
+
}
|
|
15
|
+
declare const Pane: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
16
|
+
[evt: string]: CustomEvent<any>;
|
|
17
|
+
}, {}, {}, string>;
|
|
18
|
+
type Pane = InstanceType<typeof Pane>;
|
|
19
|
+
export default Pane;
|