@svgrid/grid 2.1.22 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/dist/DockNodeView.svelte +306 -0
- package/dist/DockNodeView.svelte.d.ts +7 -0
- package/dist/DockPopoutHost.svelte +17 -0
- package/dist/DockPopoutHost.svelte.d.ts +14 -0
- package/dist/FlexRender.svelte +96 -96
- package/dist/GridFooter.svelte +178 -178
- package/dist/GridMenus.svelte +83 -3
- package/dist/SvAutoComplete.svelte +5 -3
- package/dist/SvCarousel.svelte +141 -163
- package/dist/SvColorInput.svelte +187 -186
- package/dist/SvColorInput.svelte.d.ts +1 -1
- package/dist/SvComboBox.svelte +7 -3
- package/dist/SvCommand.svelte +28 -85
- package/dist/SvContextMenu.svelte +116 -116
- package/dist/SvCountryInput.svelte +1 -1
- package/dist/SvDateRangeInput.svelte +1 -1
- package/dist/SvDateTimePicker.svelte +9 -3
- package/dist/SvDateTimePicker.svelte.d.ts +3 -0
- package/dist/SvDockLayout.svelte +244 -0
- package/dist/SvDockLayout.svelte.d.ts +77 -0
- package/dist/SvDockManager.svelte +922 -0
- package/dist/SvDockManager.svelte.d.ts +106 -0
- package/dist/SvDrawer.svelte +250 -228
- package/dist/SvDrawer.svelte.d.ts +4 -0
- package/dist/SvDropDownList.svelte +85 -13
- package/dist/SvDropDownList.svelte.d.ts +3 -0
- package/dist/SvField.svelte +21 -4
- package/dist/SvField.svelte.d.ts +1 -0
- package/dist/SvForm.svelte +201 -91
- package/dist/SvForm.svelte.d.ts +15 -22
- package/dist/SvGrid.controller.svelte.d.ts +32 -1
- package/dist/SvGrid.controller.svelte.js +156 -27
- package/dist/SvGrid.css +193 -5
- package/dist/SvGrid.svelte +3131 -2786
- package/dist/SvGrid.types.d.ts +452 -1
- package/dist/SvGridBoard.svelte +2339 -2339
- package/dist/SvGridChart.svelte +1724 -1724
- package/dist/SvGridChartPanel.svelte +485 -485
- package/dist/SvGridDropdown.svelte +689 -682
- package/dist/SvGridSelect.svelte +1 -0
- package/dist/SvGroupCell.svelte +109 -109
- package/dist/SvListBox.svelte +36 -14
- package/dist/SvMaskedInput.svelte +4 -3
- package/dist/SvMaskedInput.svelte.d.ts +1 -1
- package/dist/SvMenu.svelte +124 -124
- package/dist/SvMenuList.svelte +117 -165
- package/dist/SvMenuList.svelte.d.ts +2 -13
- package/dist/SvModal.svelte +12 -29
- package/dist/SvMultiSelect.svelte +1 -0
- package/dist/SvNumberInput.svelte +131 -130
- package/dist/SvNumberInput.svelte.d.ts +1 -1
- package/dist/SvOtpInput.svelte +158 -158
- package/dist/SvPagination.svelte +18 -18
- package/dist/SvPasswordInput.svelte +3 -2
- package/dist/SvPasswordInput.svelte.d.ts +1 -1
- package/dist/SvPhoneInput.svelte +3 -2
- package/dist/SvPhoneInput.svelte.d.ts +1 -1
- package/dist/SvPopover.svelte +139 -139
- package/dist/SvRowGroupPanel.svelte +170 -170
- package/dist/SvScrollArea.svelte +61 -61
- package/dist/SvStepper.svelte +6 -9
- package/dist/SvTagsInput.svelte +3 -2
- package/dist/SvTagsInput.svelte.d.ts +1 -1
- package/dist/SvTooltip.svelte +127 -138
- package/dist/SvTour.svelte +204 -176
- package/dist/SvTree.svelte +377 -377
- package/dist/SvTreeSelect.svelte +230 -229
- package/dist/build-api.js +76 -54
- package/dist/cdn/svgrid.js +10913 -6450
- package/dist/cdn/svgrid.svelte-external.js +9628 -5481
- package/dist/chart-export.js +8 -8
- package/dist/columns.js +6 -0
- package/dist/conditional-formatting.d.ts +67 -3
- package/dist/conditional-formatting.js +124 -17
- package/dist/createCarousel.svelte.d.ts +72 -0
- package/dist/createCarousel.svelte.js +80 -0
- package/dist/createCommand.svelte.d.ts +62 -0
- package/dist/createCommand.svelte.js +109 -0
- package/dist/createForm.svelte.d.ts +62 -0
- package/dist/createForm.svelte.js +374 -0
- package/dist/createMenu.svelte.d.ts +88 -0
- package/dist/createMenu.svelte.js +153 -0
- package/dist/createOverlay.svelte.d.ts +38 -0
- package/dist/createOverlay.svelte.js +68 -0
- package/dist/createPagination.d.ts +74 -0
- package/dist/createPagination.js +71 -0
- package/dist/createPopoverSelect.svelte.js +6 -12
- package/dist/createStepper.d.ts +44 -0
- package/dist/createStepper.js +26 -0
- package/dist/createTooltip.svelte.d.ts +32 -0
- package/dist/createTooltip.svelte.js +61 -0
- package/dist/datetime/timezone.d.ts +36 -0
- package/dist/datetime/timezone.js +110 -0
- package/dist/dock-context.d.ts +52 -0
- package/dist/dock-context.js +1 -0
- package/dist/dock-manager-model.d.ts +136 -0
- package/dist/dock-manager-model.js +514 -0
- package/dist/dock-model.d.ts +105 -0
- package/dist/dock-model.js +288 -0
- package/dist/dock-popout.d.ts +30 -0
- package/dist/dock-popout.js +81 -0
- package/dist/editor-contract.d.ts +2 -0
- package/dist/filter-operators.d.ts +6 -0
- package/dist/filter-operators.js +35 -2
- package/dist/filtering/excel-filters.d.ts +18 -1
- package/dist/filtering/excel-filters.js +56 -0
- package/dist/form-field.d.ts +73 -0
- package/dist/form-field.js +15 -0
- package/dist/index.d.ts +25 -5
- package/dist/index.js +40 -4
- package/dist/list-nav.d.ts +18 -0
- package/dist/list-nav.js +30 -0
- package/dist/list-option.d.ts +2 -0
- package/dist/menu-item.d.ts +20 -0
- package/dist/menu-item.js +1 -0
- package/dist/menus.d.ts +5 -0
- package/dist/menus.js +68 -16
- package/dist/panel-resize.d.ts +25 -0
- package/dist/panel-resize.js +40 -0
- package/dist/popover.d.ts +29 -3
- package/dist/popover.js +22 -6
- package/dist/recurrence.d.ts +33 -7
- package/dist/recurrence.js +151 -13
- package/dist/row-drag.d.ts +8 -0
- package/dist/row-drag.js +11 -0
- package/dist/scheduler-ical.d.ts +30 -0
- package/dist/scheduler-ical.js +180 -0
- package/dist/scheduler-model.d.ts +269 -0
- package/dist/scheduler-model.js +602 -0
- package/dist/scheduler-view.svelte.d.ts +22 -0
- package/dist/scheduler-view.svelte.js +13 -0
- package/dist/spreadsheet.js +20 -12
- package/dist/ui-app.types.d.ts +3 -0
- package/package.json +14 -10
- package/src/DockNodeView.svelte +306 -0
- package/src/DockPopoutHost.svelte +17 -0
- package/src/FlexRender.svelte +96 -96
- package/src/GridFooter.svelte +178 -178
- package/src/GridMenus.svelte +83 -3
- package/src/SvAutoComplete.svelte +5 -3
- package/src/SvCarousel.svelte +141 -163
- package/src/SvCarousel.test.ts +68 -0
- package/src/SvColorInput.svelte +187 -186
- package/src/SvComboBox.svelte +7 -3
- package/src/SvCommand.svelte +28 -85
- package/src/SvCommand.test.ts +78 -0
- package/src/SvContextMenu.svelte +116 -116
- package/src/SvCountryInput.svelte +1 -1
- package/src/SvDateRangeInput.svelte +1 -1
- package/src/SvDateTimePicker.svelte +9 -3
- package/src/SvDockLayout.svelte +244 -0
- package/src/SvDockManager.svelte +922 -0
- package/src/SvDrawer.svelte +250 -228
- package/src/SvDropDownList.svelte +85 -13
- package/src/SvField.svelte +21 -4
- package/src/SvForm.svelte +201 -91
- package/src/SvForm.test.ts +299 -0
- package/src/SvGrid.controller.svelte.ts +3158 -3034
- package/src/SvGrid.css +193 -5
- package/src/SvGrid.svelte +3131 -2786
- package/src/SvGrid.types.ts +1489 -1027
- package/src/SvGridBoard.svelte +2339 -2339
- package/src/SvGridChart.svelte +1724 -1724
- package/src/SvGridChartPanel.svelte +485 -485
- package/src/SvGridDropdown.svelte +689 -682
- package/src/SvGridSelect.svelte +1 -0
- package/src/SvGroupCell.svelte +109 -109
- package/src/SvListBox.svelte +36 -14
- package/src/SvMaskedInput.svelte +4 -3
- package/src/SvMenu.svelte +124 -124
- package/src/SvMenu.test.ts +97 -73
- package/src/SvMenuList.svelte +117 -165
- package/src/SvModal.svelte +12 -29
- package/src/SvModal.test.ts +82 -0
- package/src/SvMultiSelect.svelte +1 -0
- package/src/SvNumberInput.svelte +131 -130
- package/src/SvOtpInput.svelte +158 -158
- package/src/SvPagination.svelte +18 -18
- package/src/SvPasswordInput.svelte +3 -2
- package/src/SvPhoneInput.svelte +3 -2
- package/src/SvPopover.svelte +139 -139
- package/src/SvRowGroupPanel.svelte +170 -170
- package/src/SvScrollArea.svelte +61 -61
- package/src/SvStepper.svelte +6 -9
- package/src/SvTagsInput.svelte +3 -2
- package/src/SvTooltip.svelte +127 -138
- package/src/SvTooltip.test.ts +73 -0
- package/src/SvTour.svelte +204 -176
- package/src/SvTree.svelte +377 -377
- package/src/SvTreeSelect.svelte +230 -229
- package/src/a11y.contract.test.ts +49 -49
- package/src/a11y.test.ts +59 -59
- package/src/a11y.ts +61 -61
- package/src/build-api.ts +870 -847
- package/src/cell-formatting.ts +169 -169
- package/src/cell-render.ts +469 -469
- package/src/chart-export.ts +201 -201
- package/src/chart.ts +2296 -2296
- package/src/collaboration.test.ts +104 -104
- package/src/collaboration.ts +167 -167
- package/src/columns.ts +5 -0
- package/src/conditional-formatting.test.ts +104 -0
- package/src/conditional-formatting.ts +176 -19
- package/src/core.performance.test.ts +30 -30
- package/src/core.ts +1111 -1111
- package/src/createAutocomplete.svelte.ts +132 -132
- package/src/createCarousel.svelte.ts +104 -0
- package/src/createCombobox.svelte.ts +179 -179
- package/src/createCommand.svelte.ts +125 -0
- package/src/createCountryInput.svelte.ts +157 -157
- package/src/createDropdownList.svelte.ts +162 -162
- package/src/createForm.svelte.ts +333 -0
- package/src/createGrid.svelte.ts +42 -42
- package/src/createGrid.test.ts +10 -10
- package/src/createGridState.svelte.ts +17 -17
- package/src/createMenu.svelte.ts +202 -0
- package/src/createOverlay.svelte.ts +101 -0
- package/src/createPagination.test.ts +65 -0
- package/src/createPagination.ts +84 -0
- package/src/createPopoverSelect.svelte.ts +206 -210
- package/src/createStepper.test.ts +62 -0
- package/src/createStepper.ts +61 -0
- package/src/createTooltip.svelte.ts +86 -0
- package/src/createTree.svelte.ts +319 -319
- package/src/datetime/timezone.test.ts +69 -0
- package/src/datetime/timezone.ts +134 -0
- package/src/dock-context.ts +47 -0
- package/src/dock-manager-model.test.ts +275 -0
- package/src/dock-manager-model.ts +593 -0
- package/src/dock-model.test.ts +163 -0
- package/src/dock-model.ts +368 -0
- package/src/dock-popout.ts +94 -0
- package/src/editing.test.ts +859 -859
- package/src/editing.ts +675 -675
- package/src/editor-contract.ts +2 -0
- package/src/export-data-api.test.ts +126 -126
- package/src/export-format.test.ts +107 -107
- package/src/export-format.ts +598 -598
- package/src/filter-operators.test.ts +59 -0
- package/src/filter-operators.ts +35 -2
- package/src/filtering/excel-filters.test.ts +86 -1
- package/src/filtering/excel-filters.ts +65 -0
- package/src/flex-render.ts +3 -3
- package/src/form-field.ts +87 -0
- package/src/index.ts +623 -469
- package/src/keyboard.test.ts +59 -59
- package/src/keyboard.ts +97 -97
- package/src/list-nav.test.ts +49 -0
- package/src/list-nav.ts +29 -0
- package/src/list-option.ts +2 -0
- package/src/menu-item.ts +21 -0
- package/src/menus.test.ts +48 -0
- package/src/menus.ts +639 -582
- package/src/merge-objects.ts +48 -48
- package/src/overlays.test.ts +90 -90
- package/src/panel-resize.ts +52 -0
- package/src/popover.bounds.test.ts +93 -0
- package/src/popover.ts +51 -7
- package/src/recurrence.test.ts +88 -1
- package/src/recurrence.ts +166 -15
- package/src/render-component.ts +28 -28
- package/src/row-drag.ts +10 -0
- package/src/scheduler-ical.test.ts +68 -0
- package/src/scheduler-ical.ts +180 -0
- package/src/scheduler-model.test.ts +546 -0
- package/src/scheduler-model.ts +853 -0
- package/src/scheduler-view.svelte.ts +33 -0
- package/src/selection.test.ts +754 -754
- package/src/selection.ts +600 -600
- package/src/server-data-source.test.ts +289 -289
- package/src/server-data-source.ts +413 -413
- package/src/sparkline.test.ts +68 -68
- package/src/sparkline.ts +169 -169
- package/src/spreadsheet.test.ts +489 -489
- package/src/spreadsheet.ts +312 -304
- package/src/static-functions.ts +11 -11
- package/src/subscribe.ts +38 -38
- package/src/svgrid-wrapper.types.ts +501 -501
- package/src/svgrid.behavior.test.ts +706 -706
- package/src/svgrid.charting.test.ts +527 -527
- package/src/svgrid.effect-driven-filter.svelte.test.ts +116 -0
- package/src/svgrid.features.test.ts +157 -157
- package/src/svgrid.new-features.wrapper.test.ts +251 -251
- package/src/svgrid.wrapper.test.ts +40 -40
- package/src/test-setup.ts +62 -62
- package/src/themes/index.ts +159 -159
- package/src/ui-app.types.ts +3 -0
- package/src/virtual.test.ts +6 -4
- package/src/virtualization/column-virtualizer.test.ts +27 -27
- package/src/virtualization/column-virtualizer.ts +30 -30
- package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
- package/src/virtualization/types.ts +30 -30
- package/src/virtualization/virtualizer.test.ts +47 -47
- package/src/virtualization/virtualizer.ts +296 -296
package/src/SvContextMenu.svelte
CHANGED
|
@@ -1,116 +1,116 @@
|
|
|
1
|
-
<script lang="ts" module>
|
|
2
|
-
export type { MenuItem } from './SvMenuList.svelte'
|
|
3
|
-
</script>
|
|
4
|
-
|
|
5
|
-
<script lang="ts">
|
|
6
|
-
/**
|
|
7
|
-
* SvContextMenu - wraps a region and opens a menu at the pointer on right-click
|
|
8
|
-
* (or long-press). Reuses SvMenuList (submenus, keyboard, icons, shortcuts),
|
|
9
|
-
* portals to <body>, and closes via the shared dismissable layer stack.
|
|
10
|
-
* Parity: Smart context-menu.
|
|
11
|
-
*
|
|
12
|
-
* ```svelte
|
|
13
|
-
* <SvContextMenu items={items} onSelect={(i) => run(i)}>
|
|
14
|
-
* <div class="drop-zone">Right-click me</div>
|
|
15
|
-
* </SvContextMenu>
|
|
16
|
-
* ```
|
|
17
|
-
*/
|
|
18
|
-
import type { Snippet } from 'svelte'
|
|
19
|
-
import { portalToBody, popIn } from './popover'
|
|
20
|
-
import { createDismissableLayer } from './a11y/dismissable'
|
|
21
|
-
import { createFocusTrap } from './a11y/focus-trap'
|
|
22
|
-
import SvMenuList, { type MenuItem } from './SvMenuList.svelte'
|
|
23
|
-
|
|
24
|
-
type Props = {
|
|
25
|
-
items: ReadonlyArray<MenuItem>
|
|
26
|
-
onSelect?: (item: MenuItem) => void
|
|
27
|
-
disabled?: boolean
|
|
28
|
-
ariaLabel?: string
|
|
29
|
-
children?: Snippet
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
let { items, onSelect, disabled = false, ariaLabel, children }: Props = $props()
|
|
33
|
-
|
|
34
|
-
let open = $state(false)
|
|
35
|
-
let pos = $state({ x: 0, y: 0 })
|
|
36
|
-
let panelEl = $state<HTMLDivElement | null>(null)
|
|
37
|
-
let zoneEl = $state<HTMLDivElement | null>(null)
|
|
38
|
-
|
|
39
|
-
const MENU_W = 200
|
|
40
|
-
function openAt(clientX: number, clientY: number) {
|
|
41
|
-
// Clamp so the menu stays on-screen (estimate height from item count).
|
|
42
|
-
const estH = Math.min(items.length, 12) * 34 + 8
|
|
43
|
-
const x = Math.min(clientX, window.innerWidth - MENU_W - 6)
|
|
44
|
-
const y = Math.min(clientY, window.innerHeight - estH - 6)
|
|
45
|
-
pos = { x: Math.max(6, x), y: Math.max(6, y) }
|
|
46
|
-
open = true
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function onContextMenu(e: MouseEvent) {
|
|
50
|
-
if (disabled) return
|
|
51
|
-
e.preventDefault()
|
|
52
|
-
openAt(e.clientX, e.clientY)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Keyboard equivalent of a right-click: the ContextMenu key, or Shift+F10.
|
|
56
|
-
// Anchors at the focused zone's own position instead of the (nonexistent)
|
|
57
|
-
// pointer coordinates.
|
|
58
|
-
function onZoneKeydown(e: KeyboardEvent) {
|
|
59
|
-
if (disabled) return
|
|
60
|
-
const isMenuKey = e.key === 'ContextMenu' || (e.key === 'F10' && e.shiftKey)
|
|
61
|
-
if (!isMenuKey) return
|
|
62
|
-
e.preventDefault()
|
|
63
|
-
const rect = zoneEl?.getBoundingClientRect()
|
|
64
|
-
openAt(rect ? rect.left : 0, rect ? rect.bottom : 0)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
$effect(() => {
|
|
68
|
-
if (!open || !panelEl) return
|
|
69
|
-
const trap = createFocusTrap(panelEl, {
|
|
70
|
-
initialFocus: () => panelEl?.querySelector<HTMLElement>('[role="menuitem"]:not([disabled])') ?? null,
|
|
71
|
-
})
|
|
72
|
-
trap.activate()
|
|
73
|
-
const layer = createDismissableLayer({
|
|
74
|
-
element: () => panelEl,
|
|
75
|
-
onDismiss: () => (open = false),
|
|
76
|
-
})
|
|
77
|
-
layer.activate()
|
|
78
|
-
// Reposition-close on scroll (a moved anchor would leave the menu stranded).
|
|
79
|
-
const onScroll = () => (open = false)
|
|
80
|
-
window.addEventListener('scroll', onScroll, true)
|
|
81
|
-
return () => { layer.release(); trap.release(); window.removeEventListener('scroll', onScroll, true) }
|
|
82
|
-
})
|
|
83
|
-
</script>
|
|
84
|
-
|
|
85
|
-
<!-- svelte-ignore a11y_no_static_element_interactions, a11y_no_noninteractive_tabindex -->
|
|
86
|
-
<div class="sv-ctx__zone" bind:this={zoneEl} tabindex={disabled ? -1 : 0} oncontextmenu={onContextMenu} onkeydown={onZoneKeydown}>
|
|
87
|
-
{@render children?.()}
|
|
88
|
-
</div>
|
|
89
|
-
|
|
90
|
-
{#if open}
|
|
91
|
-
<div
|
|
92
|
-
bind:this={panelEl}
|
|
93
|
-
class="sv-ctx"
|
|
94
|
-
use:portalToBody
|
|
95
|
-
use:popIn={{}}
|
|
96
|
-
style:position="fixed"
|
|
97
|
-
style:top={`${pos.y}px`}
|
|
98
|
-
style:left={`${pos.x}px`}
|
|
99
|
-
aria-label={ariaLabel}
|
|
100
|
-
>
|
|
101
|
-
<SvMenuList {items} onclose={() => (open = false)} onselect={(i) => onSelect?.(i)} />
|
|
102
|
-
</div>
|
|
103
|
-
{/if}
|
|
104
|
-
|
|
105
|
-
<style>
|
|
106
|
-
/* display:contents generates no box and per spec cannot receive focus - it
|
|
107
|
-
must be a real box for the keyboard-open path (tabindex) to work at all. */
|
|
108
|
-
.sv-ctx__zone { display: block; }
|
|
109
|
-
.sv-ctx__zone:focus-visible { outline: 2px solid var(--sg-accent, #6366f1); outline-offset: 1px; }
|
|
110
|
-
:global(.sv-ctx) {
|
|
111
|
-
z-index: 2147483646; min-width: 200px;
|
|
112
|
-
background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
113
|
-
border: 1px solid var(--sg-border, #e2e8f0); border-radius: 10px;
|
|
114
|
-
box-shadow: 0 16px 48px -12px rgba(15, 23, 42, 0.35); font-size: 13px;
|
|
115
|
-
}
|
|
116
|
-
</style>
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
export type { MenuItem } from './SvMenuList.svelte'
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
/**
|
|
7
|
+
* SvContextMenu - wraps a region and opens a menu at the pointer on right-click
|
|
8
|
+
* (or long-press). Reuses SvMenuList (submenus, keyboard, icons, shortcuts),
|
|
9
|
+
* portals to <body>, and closes via the shared dismissable layer stack.
|
|
10
|
+
* Parity: Smart context-menu.
|
|
11
|
+
*
|
|
12
|
+
* ```svelte
|
|
13
|
+
* <SvContextMenu items={items} onSelect={(i) => run(i)}>
|
|
14
|
+
* <div class="drop-zone">Right-click me</div>
|
|
15
|
+
* </SvContextMenu>
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
import type { Snippet } from 'svelte'
|
|
19
|
+
import { portalToBody, popIn } from './popover'
|
|
20
|
+
import { createDismissableLayer } from './a11y/dismissable'
|
|
21
|
+
import { createFocusTrap } from './a11y/focus-trap'
|
|
22
|
+
import SvMenuList, { type MenuItem } from './SvMenuList.svelte'
|
|
23
|
+
|
|
24
|
+
type Props = {
|
|
25
|
+
items: ReadonlyArray<MenuItem>
|
|
26
|
+
onSelect?: (item: MenuItem) => void
|
|
27
|
+
disabled?: boolean
|
|
28
|
+
ariaLabel?: string
|
|
29
|
+
children?: Snippet
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let { items, onSelect, disabled = false, ariaLabel, children }: Props = $props()
|
|
33
|
+
|
|
34
|
+
let open = $state(false)
|
|
35
|
+
let pos = $state({ x: 0, y: 0 })
|
|
36
|
+
let panelEl = $state<HTMLDivElement | null>(null)
|
|
37
|
+
let zoneEl = $state<HTMLDivElement | null>(null)
|
|
38
|
+
|
|
39
|
+
const MENU_W = 200
|
|
40
|
+
function openAt(clientX: number, clientY: number) {
|
|
41
|
+
// Clamp so the menu stays on-screen (estimate height from item count).
|
|
42
|
+
const estH = Math.min(items.length, 12) * 34 + 8
|
|
43
|
+
const x = Math.min(clientX, window.innerWidth - MENU_W - 6)
|
|
44
|
+
const y = Math.min(clientY, window.innerHeight - estH - 6)
|
|
45
|
+
pos = { x: Math.max(6, x), y: Math.max(6, y) }
|
|
46
|
+
open = true
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function onContextMenu(e: MouseEvent) {
|
|
50
|
+
if (disabled) return
|
|
51
|
+
e.preventDefault()
|
|
52
|
+
openAt(e.clientX, e.clientY)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Keyboard equivalent of a right-click: the ContextMenu key, or Shift+F10.
|
|
56
|
+
// Anchors at the focused zone's own position instead of the (nonexistent)
|
|
57
|
+
// pointer coordinates.
|
|
58
|
+
function onZoneKeydown(e: KeyboardEvent) {
|
|
59
|
+
if (disabled) return
|
|
60
|
+
const isMenuKey = e.key === 'ContextMenu' || (e.key === 'F10' && e.shiftKey)
|
|
61
|
+
if (!isMenuKey) return
|
|
62
|
+
e.preventDefault()
|
|
63
|
+
const rect = zoneEl?.getBoundingClientRect()
|
|
64
|
+
openAt(rect ? rect.left : 0, rect ? rect.bottom : 0)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
$effect(() => {
|
|
68
|
+
if (!open || !panelEl) return
|
|
69
|
+
const trap = createFocusTrap(panelEl, {
|
|
70
|
+
initialFocus: () => panelEl?.querySelector<HTMLElement>('[role="menuitem"]:not([disabled])') ?? null,
|
|
71
|
+
})
|
|
72
|
+
trap.activate()
|
|
73
|
+
const layer = createDismissableLayer({
|
|
74
|
+
element: () => panelEl,
|
|
75
|
+
onDismiss: () => (open = false),
|
|
76
|
+
})
|
|
77
|
+
layer.activate()
|
|
78
|
+
// Reposition-close on scroll (a moved anchor would leave the menu stranded).
|
|
79
|
+
const onScroll = () => (open = false)
|
|
80
|
+
window.addEventListener('scroll', onScroll, true)
|
|
81
|
+
return () => { layer.release(); trap.release(); window.removeEventListener('scroll', onScroll, true) }
|
|
82
|
+
})
|
|
83
|
+
</script>
|
|
84
|
+
|
|
85
|
+
<!-- svelte-ignore a11y_no_static_element_interactions, a11y_no_noninteractive_tabindex -->
|
|
86
|
+
<div class="sv-ctx__zone" bind:this={zoneEl} tabindex={disabled ? -1 : 0} oncontextmenu={onContextMenu} onkeydown={onZoneKeydown}>
|
|
87
|
+
{@render children?.()}
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
{#if open}
|
|
91
|
+
<div
|
|
92
|
+
bind:this={panelEl}
|
|
93
|
+
class="sv-ctx"
|
|
94
|
+
use:portalToBody
|
|
95
|
+
use:popIn={{}}
|
|
96
|
+
style:position="fixed"
|
|
97
|
+
style:top={`${pos.y}px`}
|
|
98
|
+
style:left={`${pos.x}px`}
|
|
99
|
+
aria-label={ariaLabel}
|
|
100
|
+
>
|
|
101
|
+
<SvMenuList {items} onclose={() => (open = false)} onselect={(i) => onSelect?.(i)} />
|
|
102
|
+
</div>
|
|
103
|
+
{/if}
|
|
104
|
+
|
|
105
|
+
<style>
|
|
106
|
+
/* display:contents generates no box and per spec cannot receive focus - it
|
|
107
|
+
must be a real box for the keyboard-open path (tabindex) to work at all. */
|
|
108
|
+
.sv-ctx__zone { display: block; }
|
|
109
|
+
.sv-ctx__zone:focus-visible { outline: 2px solid var(--sg-accent, #6366f1); outline-offset: 1px; }
|
|
110
|
+
:global(.sv-ctx) {
|
|
111
|
+
z-index: 2147483646; min-width: 200px;
|
|
112
|
+
background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a);
|
|
113
|
+
border: 1px solid var(--sg-border, #e2e8f0); border-radius: 10px;
|
|
114
|
+
box-shadow: 0 16px 48px -12px rgba(15, 23, 42, 0.35); font-size: 13px;
|
|
115
|
+
}
|
|
116
|
+
</style>
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
let triggerEl = $state<HTMLButtonElement | null>(null)
|
|
55
55
|
let searchEl = $state<HTMLInputElement | null>(null)
|
|
56
56
|
let panelEl = $state<HTMLDivElement | null>(null)
|
|
57
|
-
let rect = $state<AnchoredRect>({ top: 0, left: 0, width: 0, openUpward: false })
|
|
57
|
+
let rect = $state<AnchoredRect>({ top: 0, left: 0, width: 0, openUpward: false, maxHeight: 0, availHeight: 0 })
|
|
58
58
|
|
|
59
59
|
const ci = createCountryInput({
|
|
60
60
|
value: () => value,
|
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
// --- Portalled popover positioning (DOM-bound; stays in the component) -------
|
|
119
119
|
let triggerEl = $state<HTMLDivElement | null>(null)
|
|
120
120
|
let panelEl = $state<HTMLDivElement | null>(null)
|
|
121
|
-
let panelRect = $state<AnchoredRect>({ top: 0, left: 0, width: 0, openUpward: false })
|
|
121
|
+
let panelRect = $state<AnchoredRect>({ top: 0, left: 0, width: 0, openUpward: false, maxHeight: 0, availHeight: 0 })
|
|
122
122
|
|
|
123
123
|
function updatePos() {
|
|
124
124
|
if (!triggerEl) return
|
|
@@ -54,6 +54,9 @@
|
|
|
54
54
|
stepMinutes?: number
|
|
55
55
|
/** Open the dropdown as soon as the field is focused (grid in-cell editing). */
|
|
56
56
|
autoOpen?: boolean
|
|
57
|
+
/** Stretch the field to fill its container (100% width) instead of the fixed
|
|
58
|
+
* default width - used when mounted as a grid cell editor. */
|
|
59
|
+
block?: boolean
|
|
57
60
|
/** Animate the calendar's month/drill navigation (honors reduced-motion). */
|
|
58
61
|
animate?: boolean | 'slide' | 'fade'
|
|
59
62
|
/** Override the built-in strings (tabs, dialog, clear/open aria-labels). */
|
|
@@ -82,6 +85,7 @@
|
|
|
82
85
|
spinButtons = false,
|
|
83
86
|
stepMinutes = 1,
|
|
84
87
|
autoOpen = false,
|
|
88
|
+
block = false,
|
|
85
89
|
animate = false,
|
|
86
90
|
ariaLabel,
|
|
87
91
|
invalid = false,
|
|
@@ -130,7 +134,7 @@
|
|
|
130
134
|
// --- Portalled dropdown positioning (DOM-bound; stays in the component) -----
|
|
131
135
|
let triggerEl = $state<HTMLDivElement | null>(null)
|
|
132
136
|
let panelEl = $state<HTMLDivElement | null>(null)
|
|
133
|
-
let panelRect = $state<AnchoredRect>({ top: 0, left: 0, width: 0, openUpward: false })
|
|
137
|
+
let panelRect = $state<AnchoredRect>({ top: 0, left: 0, width: 0, openUpward: false, maxHeight: 0, availHeight: 0 })
|
|
134
138
|
|
|
135
139
|
function updatePos() {
|
|
136
140
|
if (!triggerEl) return
|
|
@@ -171,8 +175,8 @@
|
|
|
171
175
|
}
|
|
172
176
|
</script>
|
|
173
177
|
|
|
174
|
-
<SvField id={uid} {label} {hint} {error} {required} {dir}>
|
|
175
|
-
<div class="sv-dtp" class:sv-dtp--disabled={disabled} dir={resolvedDir} onfocusout={onRootFocusOut}>
|
|
178
|
+
<SvField id={uid} {label} {hint} {error} {required} {dir} {block}>
|
|
179
|
+
<div class="sv-dtp" class:sv-dtp--disabled={disabled} class:sv-dtp--block={block} dir={resolvedDir} onfocusout={onRootFocusOut}>
|
|
176
180
|
<div class="sv-dtp__field" class:is-invalid={invalid} bind:this={triggerEl}>
|
|
177
181
|
{#if spinButtons}
|
|
178
182
|
<div class="sv-dtp__spin">
|
|
@@ -256,6 +260,8 @@
|
|
|
256
260
|
--_radius: var(--sg-radius, 8px);
|
|
257
261
|
display: inline-block; position: relative; width: 240px; color: var(--_fg);
|
|
258
262
|
}
|
|
263
|
+
/* Fill the container (grid cell editor) instead of the fixed default width. */
|
|
264
|
+
.sv-dtp--block { display: block; width: 100%; }
|
|
259
265
|
.sv-dtp--disabled { opacity: 0.6; }
|
|
260
266
|
.sv-dtp__field {
|
|
261
267
|
display: flex; align-items: center; gap: 2px;
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SvDockLayout - a basic docking layout: nested resizable split groups and
|
|
4
|
+
* tabbed leaves, with drag-to-dock. Drag a pane's tab onto another pane and
|
|
5
|
+
* drop it on an edge (split) or the centre (add as a tab); drag the splitters
|
|
6
|
+
* to resize; close panes. The whole layout is a plain `DockNode` tree you pass
|
|
7
|
+
* in and bind - every gesture is a pure transform from `dock-model`, so state
|
|
8
|
+
* is serializable and testable. Inspired by Smart's `smart-layout`.
|
|
9
|
+
*
|
|
10
|
+
* Usage: build a tree with `dockGroup` / `dockTabs` / `dockPane`, bind it, and
|
|
11
|
+
* provide a `pane` snippet that renders each pane's content by id:
|
|
12
|
+
*
|
|
13
|
+
* let layout = $state(dockGroup('row', [
|
|
14
|
+
* dockTabs([dockPane('files', 'Files')]),
|
|
15
|
+
* dockTabs([dockPane('editor', 'Editor')]),
|
|
16
|
+
* ]))
|
|
17
|
+
*
|
|
18
|
+
* <SvDockLayout bind:layout>
|
|
19
|
+
* {#snippet pane(p)} ...render by p.id... {/snippet}
|
|
20
|
+
* </SvDockLayout>
|
|
21
|
+
*/
|
|
22
|
+
import { setContext, type Snippet } from 'svelte'
|
|
23
|
+
import { DOCK_CONTEXT, type DockContext } from './dock-context'
|
|
24
|
+
import DockNodeView from './DockNodeView.svelte'
|
|
25
|
+
import {
|
|
26
|
+
setActive,
|
|
27
|
+
setSizes,
|
|
28
|
+
removePane,
|
|
29
|
+
movePane,
|
|
30
|
+
type DockNode,
|
|
31
|
+
type DockPane,
|
|
32
|
+
type DockZone,
|
|
33
|
+
} from './dock-model'
|
|
34
|
+
|
|
35
|
+
type Props = {
|
|
36
|
+
/** The layout tree. Bindable - every dock/resize/close writes a new tree. */
|
|
37
|
+
layout: DockNode
|
|
38
|
+
/** Renders each pane's content, keyed off the `DockPane`. */
|
|
39
|
+
pane: Snippet<[DockPane]>
|
|
40
|
+
/** Notified after any structural change (dock / resize / close). */
|
|
41
|
+
onLayoutChange?: (layout: DockNode) => void
|
|
42
|
+
/** Minimum pane size in px along the split axis. Default 80. */
|
|
43
|
+
minSize?: number
|
|
44
|
+
/** Surface id stamped on the root (`data-dock-surface`) - used by
|
|
45
|
+
* SvDockManager to hit-test which layout a drag is over. Default 'main'. */
|
|
46
|
+
surface?: string
|
|
47
|
+
/**
|
|
48
|
+
* Manager mode: when provided, SvDockLayout hands OFF tab-drag to the parent
|
|
49
|
+
* (SvDockManager) instead of running its own drag-to-dock, so a drag can flow
|
|
50
|
+
* across the main area, floating windows and edge strips. Return value is
|
|
51
|
+
* ignored; the manager owns the gesture from here.
|
|
52
|
+
*/
|
|
53
|
+
onBeginDrag?: (event: PointerEvent, paneId: string, tabsId: string) => void
|
|
54
|
+
/** Manager mode: the externally-computed dock guide/highlight to paint. */
|
|
55
|
+
externalDrop?: { tabsId: string; zone: DockZone | null; centerOnly?: boolean } | null
|
|
56
|
+
/** Manager mode: the externally-computed tab-reorder insertion indicator. */
|
|
57
|
+
externalReorder?: { tabsId: string; index: number } | null
|
|
58
|
+
/** Stack-header controls for a leaf (maximize / float / pop-out), rendered
|
|
59
|
+
* by the manager once per leaf at the right of its tab strip. */
|
|
60
|
+
leafActions?: Snippet<[import('./dock-model').DockTabs]>
|
|
61
|
+
/** Manager mode: route tab activation to the parent (controlled). */
|
|
62
|
+
onActivate?: (tabsId: string, index: number) => void
|
|
63
|
+
/** Manager mode: route pane close to the parent (controlled). */
|
|
64
|
+
onClose?: (paneId: string) => void
|
|
65
|
+
/** Manager mode: route splitter resize to the parent (controlled). */
|
|
66
|
+
onResize?: (groupId: string, sizes: number[]) => void
|
|
67
|
+
/** Which side of each leaf the tab strip sits on. Default `'top'`. */
|
|
68
|
+
headerPosition?: 'top' | 'bottom' | 'left' | 'right'
|
|
69
|
+
/** Manager mode: id of the focused leaf, for the active-panel highlight. */
|
|
70
|
+
focusedLeaf?: string | null
|
|
71
|
+
/** Keep inactive tabs mounted (hidden) so their content state persists. */
|
|
72
|
+
keepAlive?: boolean
|
|
73
|
+
/** In a single-pane leaf, hide the redundant tab button (auto-hide fly-out). */
|
|
74
|
+
hideSingleTab?: boolean
|
|
75
|
+
/** Locked / split mode: tabs cannot be dragged (no reorder / dock / float) and
|
|
76
|
+
* panel action buttons are hidden - only splitter resize + tab switching remain.
|
|
77
|
+
* Turns the layout into a fixed set of resizable panes. */
|
|
78
|
+
locked?: boolean
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
let {
|
|
82
|
+
layout = $bindable(),
|
|
83
|
+
pane,
|
|
84
|
+
onLayoutChange,
|
|
85
|
+
minSize = 80,
|
|
86
|
+
surface = 'main',
|
|
87
|
+
onBeginDrag,
|
|
88
|
+
externalDrop = null,
|
|
89
|
+
externalReorder = null,
|
|
90
|
+
leafActions,
|
|
91
|
+
onActivate,
|
|
92
|
+
onClose,
|
|
93
|
+
onResize,
|
|
94
|
+
headerPosition = 'top',
|
|
95
|
+
focusedLeaf = null,
|
|
96
|
+
keepAlive = false,
|
|
97
|
+
hideSingleTab = false,
|
|
98
|
+
locked = false,
|
|
99
|
+
}: Props = $props()
|
|
100
|
+
|
|
101
|
+
const managed = $derived(!!onBeginDrag)
|
|
102
|
+
|
|
103
|
+
// Ids for nodes created by docking. The per-instance base (from a module
|
|
104
|
+
// counter) keeps them unique across instances and clear of caller ids.
|
|
105
|
+
let nextId = instanceBase()
|
|
106
|
+
const genId = () => `dk-${nextId++}`
|
|
107
|
+
|
|
108
|
+
function commit(next: DockNode) {
|
|
109
|
+
layout = next
|
|
110
|
+
onLayoutChange?.(next)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const activate = (tabsId: string, index: number) =>
|
|
114
|
+
onActivate ? onActivate(tabsId, index) : commit(setActive(layout, tabsId, index))
|
|
115
|
+
const resize = (groupId: string, sizes: number[]) =>
|
|
116
|
+
onResize ? onResize(groupId, sizes) : commit(setSizes(layout, groupId, sizes))
|
|
117
|
+
const close = (paneId: string) => {
|
|
118
|
+
if (onClose) { onClose(paneId); return }
|
|
119
|
+
const { root } = removePane(layout, paneId)
|
|
120
|
+
if (root) commit(root)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ---- drag-to-dock ------------------------------------------------------
|
|
124
|
+
type Drag = { paneId: string; sourceTabsId: string; title: string; x: number; y: number }
|
|
125
|
+
let drag = $state<Drag | null>(null)
|
|
126
|
+
let dropTarget = $state<{ tabsId: string; zone: DockZone } | null>(null)
|
|
127
|
+
let candidate: { paneId: string; sourceTabsId: string; title: string; sx: number; sy: number } | null = null
|
|
128
|
+
|
|
129
|
+
function paneTitle(paneId: string): string {
|
|
130
|
+
const walk = (n: DockNode): string | null => {
|
|
131
|
+
if (n.type === 'tabs') return n.panes.find((p) => p.id === paneId)?.title ?? null
|
|
132
|
+
for (const c of n.children) { const t = walk(c); if (t) return t }
|
|
133
|
+
return null
|
|
134
|
+
}
|
|
135
|
+
return walk(layout) ?? ''
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function beginDrag(e: PointerEvent, paneId: string, tabsId: string) {
|
|
139
|
+
if (e.button !== 0) return
|
|
140
|
+
// Locked / split mode: tabs are fixed - a pointerdown still activates the tab
|
|
141
|
+
// (handled separately), but starts no drag.
|
|
142
|
+
if (locked) return
|
|
143
|
+
// Manager mode: delegate the whole gesture to the parent.
|
|
144
|
+
if (onBeginDrag) { onBeginDrag(e, paneId, tabsId); return }
|
|
145
|
+
candidate = { paneId, sourceTabsId: tabsId, title: paneTitle(paneId), sx: e.clientX, sy: e.clientY }
|
|
146
|
+
window.addEventListener('pointermove', onPointerMove)
|
|
147
|
+
window.addEventListener('pointerup', onPointerUp)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Which leaf is under the pointer, and which of its 5 zones. */
|
|
151
|
+
function hitTest(x: number, y: number): { tabsId: string; zone: DockZone } | null {
|
|
152
|
+
const el = document.elementFromPoint(x, y)?.closest('[data-dock-tabs]') as HTMLElement | null
|
|
153
|
+
if (!el) return null
|
|
154
|
+
const tabsId = el.dataset.dockTabs!
|
|
155
|
+
const r = el.getBoundingClientRect()
|
|
156
|
+
if (r.width <= 0 || r.height <= 0) return null
|
|
157
|
+
const rx = (x - r.left) / r.width
|
|
158
|
+
const ry = (y - r.top) / r.height
|
|
159
|
+
const edge = 0.25
|
|
160
|
+
const m = Math.min(rx, 1 - rx, ry, 1 - ry)
|
|
161
|
+
let zone: DockZone = 'center'
|
|
162
|
+
if (m <= edge) {
|
|
163
|
+
const nearest = Math.min(rx, 1 - rx, ry, 1 - ry)
|
|
164
|
+
zone = nearest === rx ? 'left' : nearest === 1 - rx ? 'right' : nearest === ry ? 'top' : 'bottom'
|
|
165
|
+
}
|
|
166
|
+
return { tabsId, zone }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function onPointerMove(e: PointerEvent) {
|
|
170
|
+
if (!candidate) return
|
|
171
|
+
if (!drag) {
|
|
172
|
+
// Small threshold so a click-to-activate does not start a drag.
|
|
173
|
+
if (Math.hypot(e.clientX - candidate.sx, e.clientY - candidate.sy) < 5) return
|
|
174
|
+
drag = { paneId: candidate.paneId, sourceTabsId: candidate.sourceTabsId, title: candidate.title, x: e.clientX, y: e.clientY }
|
|
175
|
+
document.body.style.userSelect = 'none'
|
|
176
|
+
document.body.style.cursor = 'grabbing'
|
|
177
|
+
}
|
|
178
|
+
drag = { ...drag, x: e.clientX, y: e.clientY }
|
|
179
|
+
dropTarget = hitTest(e.clientX, e.clientY)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function onPointerUp() {
|
|
183
|
+
window.removeEventListener('pointermove', onPointerMove)
|
|
184
|
+
window.removeEventListener('pointerup', onPointerUp)
|
|
185
|
+
if (drag && dropTarget) {
|
|
186
|
+
commit(movePane(layout, drag.paneId, dropTarget.tabsId, dropTarget.zone, genId))
|
|
187
|
+
}
|
|
188
|
+
drag = null
|
|
189
|
+
dropTarget = null
|
|
190
|
+
candidate = null
|
|
191
|
+
document.body.style.userSelect = ''
|
|
192
|
+
document.body.style.cursor = ''
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
setContext<DockContext>(DOCK_CONTEXT, {
|
|
196
|
+
get pane() { return pane },
|
|
197
|
+
get leafActions() { return locked ? undefined : leafActions },
|
|
198
|
+
activate,
|
|
199
|
+
close,
|
|
200
|
+
beginDrag,
|
|
201
|
+
resize,
|
|
202
|
+
minSize: () => minSize,
|
|
203
|
+
// In manager mode the highlight is driven from outside.
|
|
204
|
+
dropTarget: () => (managed ? externalDrop : dropTarget),
|
|
205
|
+
reorderTarget: () => (managed ? externalReorder : null),
|
|
206
|
+
headerPosition: () => headerPosition,
|
|
207
|
+
focusedLeaf: () => focusedLeaf,
|
|
208
|
+
keepAlive: () => keepAlive,
|
|
209
|
+
hideSingleTab: () => hideSingleTab,
|
|
210
|
+
})
|
|
211
|
+
</script>
|
|
212
|
+
|
|
213
|
+
<div class="sv-dock" class:is-dragging={!!drag} data-dock-surface={surface}>
|
|
214
|
+
<DockNodeView node={layout} />
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
{#if drag}
|
|
218
|
+
<div class="sv-dock__ghost" style:left={`${drag.x + 12}px`} style:top={`${drag.y + 12}px`}>
|
|
219
|
+
{drag.title}
|
|
220
|
+
</div>
|
|
221
|
+
{/if}
|
|
222
|
+
|
|
223
|
+
<script lang="ts" module>
|
|
224
|
+
// Per-instance id base so concurrent layouts never mint the same node id.
|
|
225
|
+
let SEQ = 0
|
|
226
|
+
function instanceBase(): number {
|
|
227
|
+
SEQ += 1
|
|
228
|
+
return SEQ * 1_000_000
|
|
229
|
+
}
|
|
230
|
+
</script>
|
|
231
|
+
|
|
232
|
+
<style>
|
|
233
|
+
.sv-dock { width: 100%; height: 100%; min-width: 0; min-height: 0; display: flex; }
|
|
234
|
+
.sv-dock > :global(*) { flex: 1; min-width: 0; min-height: 0; }
|
|
235
|
+
.sv-dock.is-dragging { cursor: grabbing; }
|
|
236
|
+
.sv-dock.is-dragging :global(.sv-dock__content) { pointer-events: none; }
|
|
237
|
+
|
|
238
|
+
.sv-dock__ghost {
|
|
239
|
+
position: fixed; z-index: 2147483646; pointer-events: none;
|
|
240
|
+
padding: 5px 10px; font-size: 12.5px; font-weight: 600;
|
|
241
|
+
color: var(--sg-on-accent, #fff); background: var(--sg-accent, #2563eb);
|
|
242
|
+
border-radius: 6px; box-shadow: 0 8px 24px -6px rgba(15,23,42,0.5); opacity: 0.95;
|
|
243
|
+
}
|
|
244
|
+
</style>
|