@spaethtech/svelte-ui 0.7.1-dev.43.72e77ad → 0.7.1-dev.45.41387ea
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.
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
align = "start",
|
|
36
36
|
boundary = "viewport",
|
|
37
37
|
offset = 4,
|
|
38
|
+
margin = 8,
|
|
38
39
|
matchWidth = false,
|
|
39
40
|
surface = false,
|
|
40
41
|
variant,
|
|
@@ -51,6 +52,9 @@
|
|
|
51
52
|
/** Default the viewport; pass a container element or CSS selector to bound flipping to it. */
|
|
52
53
|
boundary?: Boundary;
|
|
53
54
|
offset?: number;
|
|
55
|
+
/** Minimum gap kept from the boundary edges, in px (default 8). Set `0` to let the box sit flush
|
|
56
|
+
* against an edge — e.g. a sidebar popout that must align to the sidebar's (viewport) edge. */
|
|
57
|
+
margin?: number;
|
|
54
58
|
matchWidth?: boolean;
|
|
55
59
|
/** Render a themed floating PANEL around the children (background, border, padding, shadow) instead
|
|
56
60
|
* of the default bare positioner. `variant`/`size`/`borderless` configure that panel. Consumers
|
|
@@ -132,6 +136,7 @@
|
|
|
132
136
|
align,
|
|
133
137
|
boundary,
|
|
134
138
|
offset,
|
|
139
|
+
margin,
|
|
135
140
|
matchWidth,
|
|
136
141
|
onplaced: (p) => (placed = p),
|
|
137
142
|
}}
|
|
@@ -12,6 +12,9 @@ type $$ComponentProps = {
|
|
|
12
12
|
/** Default the viewport; pass a container element or CSS selector to bound flipping to it. */
|
|
13
13
|
boundary?: Boundary;
|
|
14
14
|
offset?: number;
|
|
15
|
+
/** Minimum gap kept from the boundary edges, in px (default 8). Set `0` to let the box sit flush
|
|
16
|
+
* against an edge — e.g. a sidebar popout that must align to the sidebar's (viewport) edge. */
|
|
17
|
+
margin?: number;
|
|
15
18
|
matchWidth?: boolean;
|
|
16
19
|
/** Render a themed floating PANEL around the children (background, border, padding, shadow) instead
|
|
17
20
|
* of the default bare positioner. `variant`/`size`/`borderless` configure that panel. Consumers
|
|
@@ -123,6 +123,7 @@
|
|
|
123
123
|
hamburger = true,
|
|
124
124
|
iconHoverExpand = false,
|
|
125
125
|
hoverExpandCloseMs = 200,
|
|
126
|
+
side = "left",
|
|
126
127
|
api = $bindable(),
|
|
127
128
|
class: additionalClass = "",
|
|
128
129
|
}: {
|
|
@@ -178,6 +179,10 @@
|
|
|
178
179
|
iconHoverExpand?: boolean;
|
|
179
180
|
/** Exit delay (ms) before a hover-expanded rail collapses on mouse-leave. Default `200`. */
|
|
180
181
|
hoverExpandCloseMs?: number;
|
|
182
|
+
/** Which edge the sidebar docks to. `"right"` mirrors everything: the frame border, the drawer
|
|
183
|
+
* slide direction, the hamburger corner, popout side + border, and item layout (icon on the
|
|
184
|
+
* right, labels right-aligned). Default `"left"`. */
|
|
185
|
+
side?: "left" | "right";
|
|
181
186
|
/** Imperative handle (bindable). Call `api.reset()` from your router's navigation lifecycle
|
|
182
187
|
* (SvelteKit `afterNavigate`, etc.) so a programmatic `goto()`/back-button closes the drawer +
|
|
183
188
|
* any open popout and returns a stepped drawer to the top. */
|
|
@@ -231,6 +236,16 @@
|
|
|
231
236
|
|
|
232
237
|
const isStepped = $derived(currentMode === "stepped");
|
|
233
238
|
const isDrawer = $derived(currentMode === "drawer" || currentMode === "stepped");
|
|
239
|
+
// Dock side. `right` mirrors the frame, drawer slide, hamburger corner, popout side, and item layout.
|
|
240
|
+
const dockRight = $derived(side === "right");
|
|
241
|
+
const popoutSide = $derived<"left" | "right">(dockRight ? "left" : "right");
|
|
242
|
+
const dockEdge = $derived(dockRight ? "right-0" : "left-0");
|
|
243
|
+
const dockOff = $derived(dockRight ? "translate-x-full" : "-translate-x-full");
|
|
244
|
+
const dockBorder = $derived(
|
|
245
|
+
dockRight
|
|
246
|
+
? "[border-left:1px_solid_color-mix(in_srgb,var(--ui-color-text)_15%,transparent)]"
|
|
247
|
+
: "[border-right:1px_solid_color-mix(in_srgb,var(--ui-color-text)_15%,transparent)]",
|
|
248
|
+
);
|
|
234
249
|
|
|
235
250
|
// ── Icon-mode hover-expand ──────────────────────────────────────
|
|
236
251
|
// When enabled + in `icon` mode, hovering the rail floats it out to the `expanded` render OVER a
|
|
@@ -261,6 +276,15 @@
|
|
|
261
276
|
// Labels show in every mode except a collapsed (not hover-expanded) icon rail.
|
|
262
277
|
const labelsVisible = $derived(currentMode !== "icon" || effectivelyExpanded);
|
|
263
278
|
|
|
279
|
+
// Aside width in px, driven by mode (inline style — robust, no Tailwind arbitrary-var class).
|
|
280
|
+
const asideWidthCss = $derived(
|
|
281
|
+
currentMode === "stepped"
|
|
282
|
+
? "100%"
|
|
283
|
+
: currentMode === "icon" && !effectivelyExpanded
|
|
284
|
+
? `${collapsedWidth}px`
|
|
285
|
+
: `${expandedWidth}px`,
|
|
286
|
+
);
|
|
287
|
+
|
|
264
288
|
// ── DEV depth warning ───────────────────────────────────────────
|
|
265
289
|
if (DEV) {
|
|
266
290
|
const walk = (list: SideBarMenuItem[], depth: number, path: string[]) => {
|
|
@@ -505,7 +529,7 @@
|
|
|
505
529
|
// height get clamped to the viewport by the `anchored` engine
|
|
506
530
|
// boundary. That case also needs in-popout scroll (TODO) and
|
|
507
531
|
// the border rule may need to account for viewport-edge cases.
|
|
508
|
-
let borderClass = "border-r";
|
|
532
|
+
let borderClass = dockRight ? "border-l" : "border-r";
|
|
509
533
|
if (group === "bottom") {
|
|
510
534
|
borderClass += spans ? " border-t" : " border-t border-b";
|
|
511
535
|
} else {
|
|
@@ -564,7 +588,9 @@
|
|
|
564
588
|
what `truncate` (overflow-hidden + text-ellipsis) needs to
|
|
565
589
|
actually clip overflow. Without `min-w-0` the label refuses
|
|
566
590
|
to shrink past its natural width and the row overflows. -->
|
|
567
|
-
<span class="flex-1 min-w-0 truncate {labelClassResolved} {sz.text}"
|
|
591
|
+
<span class="flex-1 min-w-0 truncate {dockRight ? 'text-right' : ''} {labelClassResolved} {sz.text}"
|
|
592
|
+
>{item.label}</span
|
|
593
|
+
>
|
|
568
594
|
{#if isParent}
|
|
569
595
|
<!-- Chevron — visible only when the label is visible (no point
|
|
570
596
|
dangling a `>` next to an icon-only icon). Rotates 180° to
|
|
@@ -591,7 +617,7 @@
|
|
|
591
617
|
{@const hasLink = !!item.link}
|
|
592
618
|
{@const isOpen = !!opts.id && openId === opts.id}
|
|
593
619
|
{@const popoutId = opts.id ? "sbm-popout-" + opts.id.replace(":", "-") : undefined}
|
|
594
|
-
{@const rowClass = `group flex items-center w-full ${sz.row} lg:pr-3 transition-colors cursor-pointer ${active ? "[background-color:var(--ui-color-primary)] text-white" : isOpen ? "[background-color:color-mix(in_srgb,var(--ui-color-text)_10%,transparent)]" : "hover:[background-color:color-mix(in_srgb,var(--ui-color-text)_10%,transparent)]"}`}
|
|
620
|
+
{@const rowClass = `group flex items-center w-full ${sz.row} ${dockRight ? "flex-row-reverse lg:pl-3" : "lg:pr-3"} transition-colors cursor-pointer ${active ? "[background-color:var(--ui-color-primary)] text-white" : isOpen ? "[background-color:color-mix(in_srgb,var(--ui-color-text)_10%,transparent)]" : "hover:[background-color:color-mix(in_srgb,var(--ui-color-text)_10%,transparent)]"}`}
|
|
595
621
|
{#if hasLink}
|
|
596
622
|
<a
|
|
597
623
|
href={item.link}
|
|
@@ -682,7 +708,8 @@
|
|
|
682
708
|
role="menu"
|
|
683
709
|
tabindex="-1"
|
|
684
710
|
id={popoutId}
|
|
685
|
-
|
|
711
|
+
style="width: {expandedWidth}px;"
|
|
712
|
+
class="overflow-hidden shadow-lg [background-color:var(--ui-color-background)] [color:var(--ui-color-text)] [border-color:color-mix(in_srgb,var(--ui-color-text)_15%,transparent)] {borderClass}"
|
|
686
713
|
onmouseenter={cancelClose}
|
|
687
714
|
onmouseleave={scheduleClose}
|
|
688
715
|
>
|
|
@@ -706,7 +733,9 @@
|
|
|
706
733
|
to suppress this when the consumer provides its own trigger. -->
|
|
707
734
|
<button
|
|
708
735
|
type="button"
|
|
709
|
-
class="fixed top-3
|
|
736
|
+
class="fixed top-3 {dockRight
|
|
737
|
+
? 'right-3'
|
|
738
|
+
: 'left-3'} z-[80] inline-flex w-10 h-10 items-center justify-center cursor-pointer [background-color:var(--ui-color-background)] border [border-color:color-mix(in_srgb,var(--ui-color-text)_15%,transparent)] shadow-md [&_svg]:w-5 [&_svg]:h-5"
|
|
710
739
|
aria-label={open ? "Close menu" : "Open menu"}
|
|
711
740
|
aria-expanded={open}
|
|
712
741
|
onclick={() => (open = !open)}
|
|
@@ -730,27 +759,28 @@
|
|
|
730
759
|
width, so the expanded float below is positioned against the SIDEBAR's own box (container-relative,
|
|
731
760
|
never the viewport). In every other mode it's `display: contents` (no box) so layout is unchanged. -->
|
|
732
761
|
<div
|
|
733
|
-
class={hoverExpandActive ? "relative shrink-0
|
|
734
|
-
style="
|
|
762
|
+
class={hoverExpandActive ? "relative shrink-0" : "contents"}
|
|
763
|
+
style="{hoverExpandActive ? `width: ${collapsedWidth}px;` : ''}"
|
|
735
764
|
>
|
|
736
765
|
<aside
|
|
737
766
|
bind:this={asideRef}
|
|
767
|
+
style="width: {asideWidthCss};"
|
|
738
768
|
onmouseenter={hoverExpandActive ? openHoverExpand : undefined}
|
|
739
769
|
onmouseleave={hoverExpandActive ? scheduleHoverCollapse : undefined}
|
|
740
770
|
class="
|
|
741
|
-
flex flex-col z-[60] overflow-hidden
|
|
771
|
+
flex flex-col z-[60] overflow-hidden shrink-0
|
|
742
772
|
[background-color:var(--ui-color-background)]
|
|
743
773
|
[color:var(--ui-color-text)]
|
|
744
|
-
|
|
774
|
+
{dockBorder}
|
|
745
775
|
{currentMode === 'stepped'
|
|
746
|
-
? `absolute inset-y-0
|
|
776
|
+
? `absolute inset-y-0 ${dockEdge} z-[75] transition-transform duration-200 ${open ? 'translate-x-0' : dockOff}`
|
|
747
777
|
: currentMode === 'drawer'
|
|
748
|
-
? `absolute inset-y-0
|
|
778
|
+
? `absolute inset-y-0 ${dockEdge} z-[75] transition-transform duration-200 ${open ? 'translate-x-0' : dockOff}`
|
|
749
779
|
: currentMode === 'icon'
|
|
750
780
|
? hoverExpandActive
|
|
751
|
-
? `absolute inset-y-0
|
|
752
|
-
: 'relative
|
|
753
|
-
: 'relative
|
|
781
|
+
? `absolute inset-y-0 ${dockEdge} z-[65] transition-[width] duration-200 ${effectivelyExpanded ? 'shadow-lg' : ''}`
|
|
782
|
+
: 'relative'
|
|
783
|
+
: 'relative'}
|
|
754
784
|
{additionalClass}
|
|
755
785
|
"
|
|
756
786
|
>
|
|
@@ -780,9 +810,10 @@
|
|
|
780
810
|
<Popup
|
|
781
811
|
anchor={geom.anchor}
|
|
782
812
|
open={openId === id}
|
|
783
|
-
side=
|
|
813
|
+
side={popoutSide}
|
|
784
814
|
align={geom.align}
|
|
785
815
|
offset={0}
|
|
816
|
+
margin={0}
|
|
786
817
|
onclose={() => {
|
|
787
818
|
if (openId === id) openId = null;
|
|
788
819
|
}}
|
|
@@ -816,9 +847,10 @@
|
|
|
816
847
|
<Popup
|
|
817
848
|
anchor={geom.anchor}
|
|
818
849
|
open={openId === id}
|
|
819
|
-
side=
|
|
850
|
+
side={popoutSide}
|
|
820
851
|
align={geom.align}
|
|
821
852
|
offset={0}
|
|
853
|
+
margin={0}
|
|
822
854
|
onclose={() => {
|
|
823
855
|
if (openId === id) openId = null;
|
|
824
856
|
}}
|
|
@@ -127,6 +127,10 @@ type $$ComponentProps = {
|
|
|
127
127
|
iconHoverExpand?: boolean;
|
|
128
128
|
/** Exit delay (ms) before a hover-expanded rail collapses on mouse-leave. Default `200`. */
|
|
129
129
|
hoverExpandCloseMs?: number;
|
|
130
|
+
/** Which edge the sidebar docks to. `"right"` mirrors everything: the frame border, the drawer
|
|
131
|
+
* slide direction, the hamburger corner, popout side + border, and item layout (icon on the
|
|
132
|
+
* right, labels right-aligned). Default `"left"`. */
|
|
133
|
+
side?: "left" | "right";
|
|
130
134
|
/** Imperative handle (bindable). Call `api.reset()` from your router's navigation lifecycle
|
|
131
135
|
* (SvelteKit `afterNavigate`, etc.) so a programmatic `goto()`/back-button closes the drawer +
|
|
132
136
|
* any open popout and returns a stepped drawer to the top. */
|