lutra 0.0.18 → 0.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/data/Stat.svelte.d.ts +12 -6
  2. package/dist/display/Avatar.svelte.d.ts +5 -0
  3. package/dist/display/Badge.svelte.d.ts +10 -6
  4. package/dist/display/Callout.svelte +8 -4
  5. package/dist/display/Callout.svelte.d.ts +11 -9
  6. package/dist/display/Close.svelte +58 -0
  7. package/dist/display/Close.svelte.d.ts +21 -0
  8. package/dist/display/Code.svelte.d.ts +10 -8
  9. package/dist/display/ContextTip.svelte +1 -2
  10. package/dist/display/ContextTip.svelte.d.ts +5 -0
  11. package/dist/display/Details.svelte.d.ts +11 -5
  12. package/dist/display/Hero.svelte.d.ts +9 -4
  13. package/dist/display/Icon.svelte +4 -6
  14. package/dist/display/Icon.svelte.d.ts +6 -0
  15. package/dist/display/IconButton.svelte +2 -3
  16. package/dist/display/IconButton.svelte.d.ts +8 -4
  17. package/dist/display/Image.svelte +63 -13
  18. package/dist/display/Image.svelte.d.ts +15 -2
  19. package/dist/display/Indicator.svelte.d.ts +5 -3
  20. package/dist/display/Inset.svelte.d.ts +5 -0
  21. package/dist/display/Notification.svelte +104 -0
  22. package/dist/display/Notification.svelte.d.ts +42 -0
  23. package/dist/display/Popup.svelte.d.ts +10 -4
  24. package/dist/display/Table.svelte +3 -0
  25. package/dist/display/Table.svelte.d.ts +11 -0
  26. package/dist/display/Tag.svelte.d.ts +11 -7
  27. package/dist/display/Tooltip.svelte.d.ts +9 -3
  28. package/dist/display/notifications.svelte.d.ts +21 -0
  29. package/dist/display/notifications.svelte.js +31 -0
  30. package/dist/form/Button.svelte.d.ts +12 -0
  31. package/dist/form/FieldActions.svelte +1 -1
  32. package/dist/form/FieldActions.svelte.d.ts +6 -0
  33. package/dist/form/FieldContainer.svelte +5 -0
  34. package/dist/form/FieldContainer.svelte.d.ts +6 -0
  35. package/dist/form/FieldContent.svelte +3 -0
  36. package/dist/form/FieldContent.svelte.d.ts +23 -0
  37. package/dist/form/FieldError.svelte.d.ts +4 -0
  38. package/dist/form/FieldSection.svelte +13 -1
  39. package/dist/form/FieldSection.svelte.d.ts +11 -0
  40. package/dist/form/Fieldset.svelte.d.ts +15 -7
  41. package/dist/form/Form.svelte +14 -12
  42. package/dist/form/Form.svelte.d.ts +10 -0
  43. package/dist/form/Input.svelte +4 -2
  44. package/dist/form/Input.svelte.d.ts +66 -55
  45. package/dist/form/InputLength.svelte.d.ts +4 -2
  46. package/dist/form/Label.svelte.d.ts +9 -3
  47. package/dist/form/Select.svelte +0 -1
  48. package/dist/form/Select.svelte.d.ts +44 -27
  49. package/dist/form/form.js +1 -1
  50. package/dist/grid/Column.svelte.d.ts +5 -0
  51. package/dist/grid/Grid.svelte.d.ts +6 -0
  52. package/dist/grid/Row.svelte.d.ts +6 -1
  53. package/dist/layout/Layout.svelte +2 -0
  54. package/dist/layout/Layout.svelte.d.ts +6 -2
  55. package/dist/layout/LayoutFooter.svelte.d.ts +3 -1
  56. package/dist/layout/LayoutGrid.svelte.d.ts +13 -5
  57. package/dist/layout/LayoutHeader.svelte.d.ts +13 -12
  58. package/dist/layout/LayoutSideMenu.svelte +54 -0
  59. package/dist/layout/LayoutSideMenu.svelte.d.ts +25 -0
  60. package/dist/layout/Overlay.svelte +20 -0
  61. package/dist/layout/Overlay.svelte.d.ts +35 -0
  62. package/dist/layout/OverlayContainer.svelte +28 -0
  63. package/dist/layout/OverlayContainer.svelte.d.ts +16 -0
  64. package/dist/layout/OverlayLayer.svelte +145 -0
  65. package/dist/layout/OverlayLayer.svelte.d.ts +22 -0
  66. package/dist/layout/PageContent.svelte +9 -1
  67. package/dist/layout/PageContent.svelte.d.ts +8 -3
  68. package/dist/layout/Theme.svelte +8 -0
  69. package/dist/layout/Theme.svelte.d.ts +6 -0
  70. package/dist/layout/UIContent.svelte.d.ts +5 -0
  71. package/dist/layout/overlays.svelte.d.ts +34 -0
  72. package/dist/layout/overlays.svelte.js +44 -0
  73. package/dist/nav/Breadcrumb.svelte.d.ts +9 -5
  74. package/dist/nav/Menu.svelte +43 -62
  75. package/dist/nav/Menu.svelte.d.ts +11 -4
  76. package/dist/nav/MenuItem.svelte +25 -7
  77. package/dist/nav/MenuItem.svelte.d.ts +9 -0
  78. package/dist/nav/NavMenu.svelte.d.ts +3 -1
  79. package/dist/nav/TabbedContent.svelte.d.ts +5 -3
  80. package/dist/nav/Tabs.svelte.d.ts +6 -4
  81. package/dist/style.css +74 -36
  82. package/dist/typo/Clamp.svelte.d.ts +8 -3
  83. package/dist/typo/H.svelte.d.ts +10 -5
  84. package/dist/typo/H1.svelte.d.ts +9 -4
  85. package/dist/typo/H2.svelte.d.ts +9 -4
  86. package/dist/typo/H3.svelte.d.ts +9 -4
  87. package/dist/typo/H4.svelte.d.ts +9 -4
  88. package/dist/typo/H5.svelte.d.ts +9 -4
  89. package/dist/typo/H6.svelte.d.ts +9 -4
  90. package/dist/typo/P.svelte.d.ts +9 -4
  91. package/dist/utils/StringOrComponentOrSnippet.svelte +3 -2
  92. package/dist/utils/StringOrComponentOrSnippet.svelte.d.ts +7 -0
  93. package/dist/utils/attr.d.ts +2 -2
  94. package/dist/utils/attr.js +2 -2
  95. package/dist/utils/dom.d.ts +15 -0
  96. package/dist/utils/dom.js +74 -0
  97. package/dist/utils/keyboard.svelte.d.ts +19 -0
  98. package/dist/utils/keyboard.svelte.js +22 -3
  99. package/dist/utils/transitions.d.ts +1 -0
  100. package/dist/utils/transitions.js +9 -2
  101. package/package.json +11 -9
@@ -0,0 +1,20 @@
1
+ <script lang="ts">import { onDestroy, onMount } from "svelte";
2
+ import { addOverlay, removeOverlay } from "./overlays.svelte.js";
3
+ import { createId } from "../utils/id.js";
4
+ let {
5
+ id = createId(),
6
+ children,
7
+ position = "center",
8
+ anchor,
9
+ layer,
10
+ transition,
11
+ z
12
+ } = $props();
13
+ onMount(() => {
14
+ addOverlay({ id, z, layer, content: children, transition, position, anchor });
15
+ });
16
+ onDestroy(() => {
17
+ removeOverlay(id);
18
+ });
19
+ </script>
20
+
@@ -0,0 +1,35 @@
1
+ import { SvelteComponent } from "svelte";
2
+ import { type Snippet } from "svelte";
3
+ import { type OverlayPosition, type TransitionOpts } from "./overlays.svelte.js";
4
+ declare const __propDef: {
5
+ props: {
6
+ id?: string | undefined;
7
+ children: Snippet;
8
+ anchor?: HTMLElement | undefined;
9
+ layer?: string | undefined;
10
+ position?: OverlayPosition | undefined;
11
+ transition?: TransitionOpts | undefined;
12
+ z?: number | undefined;
13
+ };
14
+ events: {
15
+ [evt: string]: CustomEvent<any>;
16
+ };
17
+ slots: {};
18
+ };
19
+ export type OverlayProps = typeof __propDef.props;
20
+ export type OverlayEvents = typeof __propDef.events;
21
+ export type OverlaySlots = typeof __propDef.slots;
22
+ export default class Overlay extends SvelteComponent<OverlayProps, OverlayEvents, OverlaySlots> {
23
+ constructor(options?: import("svelte").ComponentConstructorOptions<{
24
+ id?: string | undefined;
25
+ children: (this: void) => typeof import("svelte").SnippetReturn & {
26
+ _: "functions passed to {@render ...} tags must use the `Snippet` type imported from \"svelte\"";
27
+ };
28
+ anchor?: HTMLElement | undefined;
29
+ layer?: string | undefined;
30
+ position?: OverlayPosition | undefined;
31
+ transition?: TransitionOpts | undefined;
32
+ z?: number | undefined;
33
+ }>);
34
+ }
35
+ export {};
@@ -0,0 +1,28 @@
1
+ <script lang="ts">import { overlays } from "./overlays.svelte.js";
2
+ import OverlayLayer from "./OverlayLayer.svelte";
3
+ import { slidefade } from "../utils/transitions.js";
4
+ </script>
5
+
6
+ <div class="Overlay">
7
+ <OverlayLayer position="top left" items={overlays.topLeft} />
8
+ <OverlayLayer position="top right" items={overlays.topRight} />
9
+ <OverlayLayer position="top center" items={overlays.topCenter} />
10
+ <OverlayLayer position="bottom left" items={overlays.bottomLeft} />
11
+ <OverlayLayer position="bottom right" items={overlays.bottomRight} />
12
+ <OverlayLayer position="bottom center" items={overlays.bottomCenter} />
13
+ <OverlayLayer position="center" items={overlays.center} />
14
+ <OverlayLayer position="anchor" items={overlays.anchor} />
15
+ </div>
16
+
17
+ <style>
18
+ .Overlay {
19
+ display: block;
20
+ position: fixed;
21
+ top: 0;
22
+ left: 0;
23
+ right: 0;
24
+ bottom: 0;
25
+ z-index: 1000;
26
+ pointer-events: none;
27
+ }
28
+ </style>
@@ -0,0 +1,16 @@
1
+ import { SvelteComponent } from "svelte";
2
+ declare const __propDef: {
3
+ props: {
4
+ [x: string]: never;
5
+ };
6
+ events: {
7
+ [evt: string]: CustomEvent<any>;
8
+ };
9
+ slots: {};
10
+ };
11
+ export type OverlayContainerProps = typeof __propDef.props;
12
+ export type OverlayContainerEvents = typeof __propDef.events;
13
+ export type OverlayContainerSlots = typeof __propDef.slots;
14
+ export default class OverlayContainer extends SvelteComponent<OverlayContainerProps, OverlayContainerEvents, OverlayContainerSlots> {
15
+ }
16
+ export {};
@@ -0,0 +1,145 @@
1
+ <script lang="ts">import {} from "./overlays.svelte.js";
2
+ import { slidefade } from "../utils/transitions.js";
3
+ import { BROWSER } from "esm-env";
4
+ import { isComponent, isSnippet } from "../utils/isSnippet.js";
5
+ import { untrack } from "svelte";
6
+ let {
7
+ position,
8
+ items
9
+ } = $props();
10
+ const fudge = 8;
11
+ let contentEls = $state({});
12
+ let scrollX = $state(BROWSER ? window.scrollX : 0);
13
+ let scrollY = $state(BROWSER ? window.scrollY : 0);
14
+ let innerWidth = $state(BROWSER ? window.innerWidth : 0);
15
+ let innerHeight = $state(BROWSER ? window.innerHeight : 0);
16
+ let positions = $derived.by(() => {
17
+ innerWidth;
18
+ innerHeight;
19
+ scrollX;
20
+ scrollY;
21
+ contentEls;
22
+ return items.map((item) => {
23
+ const contentEl = contentEls[item.id];
24
+ if (!item.anchor || !contentEl)
25
+ return { left: 0, top: 0, index: 1 };
26
+ const triggerPos = item.anchor.getBoundingClientRect();
27
+ const height = contentEl.clientHeight;
28
+ const width = contentEl.clientWidth;
29
+ const isOffBottom = triggerPos.bottom + height > window.innerHeight - fudge;
30
+ const isOffRight = triggerPos.left + width > window.innerWidth - fudge;
31
+ let left = triggerPos.left;
32
+ let top = triggerPos.top + triggerPos.height + fudge;
33
+ if (isOffRight)
34
+ left = left - width + triggerPos.width;
35
+ if (isOffBottom)
36
+ top = top - height - triggerPos.height - fudge * 2;
37
+ return {
38
+ left,
39
+ top,
40
+ index: 1
41
+ };
42
+ });
43
+ });
44
+ let originCache = $state({});
45
+ let origins = $derived.by(() => {
46
+ return items.map((item, index) => {
47
+ if (!item.anchor || item.position !== "anchor") {
48
+ return item.position || "center";
49
+ }
50
+ const triggerPos = item.anchor.getBoundingClientRect();
51
+ const contentEl = document.getElementById(item.id);
52
+ const contentRect = contentEl?.getBoundingClientRect();
53
+ const height = contentRect.height;
54
+ const width = contentRect.width;
55
+ const isOffBottom = triggerPos.bottom + height > innerHeight - fudge;
56
+ const isOffRight = triggerPos.left + width > innerWidth - fudge;
57
+ let text = "top left";
58
+ if (isOffRight)
59
+ text = text.replace("left", "right");
60
+ if (isOffBottom)
61
+ text = text.replace("top", "bottom");
62
+ return text;
63
+ });
64
+ });
65
+ $effect(() => {
66
+ origins;
67
+ origins.forEach((origin, index) => {
68
+ originCache[untrack(() => items[index].id)] = origin;
69
+ });
70
+ });
71
+ function introstart(item) {
72
+ document.getElementById(item.id).style.pointerEvents = "none";
73
+ }
74
+ function introend(item) {
75
+ document.getElementById(item.id).style.pointerEvents = "auto";
76
+ }
77
+ </script>
78
+
79
+ <svelte:window bind:scrollX bind:scrollY bind:innerWidth bind:innerHeight />
80
+
81
+ <div class="Layer {position}">
82
+ {#each items as item, index (item.id)}
83
+ <div
84
+ id={item.id}
85
+ bind:this={contentEls[item.id]}
86
+ class="LayerItem"
87
+ class:anchor={item.anchor ? true : false}
88
+ onintrostart={() => introstart(item)}
89
+ onintroend={() => introend(item)}
90
+ transition:slidefade|global={{ duration: 150, origin: originCache[item.id] || origins[index], noMargin: !!!item.anchor }}
91
+ style="--index: {index}; --z: {item.z}; --left: {positions[index].left}px; --top: {positions[index].top}px;"
92
+ >
93
+ {#if isComponent(item.content)}
94
+ <svelte:component this={item.content} {...item.props} />
95
+ {:else if isSnippet(item.content)}
96
+ {@render item.content()}
97
+ {/if}
98
+ </div>
99
+ {/each}
100
+ </div>
101
+
102
+ <style>
103
+ .Layer {
104
+ pointer-events: auto;
105
+ position: absolute;
106
+ display: flex;
107
+ flex-direction: column-reverse;
108
+ gap: 0.75rem;
109
+ }
110
+ .Layer.center {
111
+ left: 50%;
112
+ right: auto;
113
+ transform: translateX(-50%);
114
+ }
115
+ .Layer.top {
116
+ top: calc(1rem + env(safe-area-inset-top));
117
+ bottom: unset;
118
+ }
119
+ .Layer.bottom {
120
+ top: unset;
121
+ bottom: calc(1rem + env(safe-area-inset-bottom));
122
+ }
123
+ .Layer.right {
124
+ left: unset;
125
+ right: calc(1rem + env(safe-area-inset-right));
126
+ }
127
+ .Layer.left {
128
+ left: calc(1rem + env(safe-area-inset-left));
129
+ right: unset;
130
+ }
131
+ .Layer.center:not(.top):not(.bottom):not(.anchor) {
132
+ top: 50%;
133
+ bottom: auto;
134
+ transform: translate(calc(-50% + env(safe-area-inset-left) + env(safe-area-inset-right)), calc(-50% + env(safe-area-inset-top) + env(safe-area-inset-bottom)));
135
+ }
136
+ .LayerItem {
137
+ position: relative;
138
+ z-index: calc(100 + var(--z, 1) - var(--index, 0));
139
+ }
140
+ .LayerItem.anchor {
141
+ position: absolute;
142
+ top: var(--top, 0);
143
+ left: var(--left, 0);
144
+ }
145
+ </style>
@@ -0,0 +1,22 @@
1
+ import { SvelteComponent } from "svelte";
2
+ import { type OverlayItem, type OverlayPosition } from "./overlays.svelte.js";
3
+ declare const __propDef: {
4
+ props: {
5
+ position?: OverlayPosition | undefined;
6
+ items: OverlayItem[];
7
+ };
8
+ events: {
9
+ [evt: string]: CustomEvent<any>;
10
+ };
11
+ slots: {};
12
+ };
13
+ export type OverlayLayerProps = typeof __propDef.props;
14
+ export type OverlayLayerEvents = typeof __propDef.events;
15
+ export type OverlayLayerSlots = typeof __propDef.slots;
16
+ export default class OverlayLayer extends SvelteComponent<OverlayLayerProps, OverlayLayerEvents, OverlayLayerSlots> {
17
+ constructor(options?: import("svelte").ComponentConstructorOptions<{
18
+ position?: OverlayPosition | undefined;
19
+ items: OverlayItem[];
20
+ }>);
21
+ }
22
+ export {};
@@ -78,7 +78,7 @@ let style = $derived.by(() => {
78
78
  justify-content: center;
79
79
  }
80
80
 
81
- @media (max-width: 960px) {
81
+ @media (max-width: 1280px) {
82
82
  .Page .PaddingContainer {
83
83
  padding-block-start: calc(var(--padBlockStart) / 2);
84
84
  padding-block-end: calc(var(--padBlockEnd) / 2);
@@ -86,4 +86,12 @@ let style = $derived.by(() => {
86
86
  padding-inline-end: calc(var(--padInlineEnd) / 2);
87
87
  }
88
88
  }
89
+ @media (max-width: 640px) {
90
+ .Page .PaddingContainer {
91
+ padding-block-start: calc(var(--padBlockStart) / 4);
92
+ padding-block-end: calc(var(--padBlockEnd) / 4);
93
+ padding-inline-start: calc(var(--padInlineStart) / 4);
94
+ padding-inline-end: calc(var(--padInlineEnd) / 4);
95
+ }
96
+ }
89
97
  </style>
@@ -3,11 +3,8 @@ import { type Snippet } from "svelte";
3
3
  declare const __propDef: {
4
4
  props: {
5
5
  children: Snippet;
6
- /** Add padding to the container. Pass in either a boolean or an array of booleans to specify padding on the block and inline axes. */
7
6
  pad?: string | boolean | [boolean, boolean] | [boolean, boolean, boolean] | [boolean, boolean, boolean, boolean] | [string, string] | [string, string, string] | [string, string, string, string] | undefined;
8
- /** Center the content in the block direction. */
9
7
  middle?: boolean | undefined;
10
- /** Center the content in the inline direction. */
11
8
  center?: boolean | undefined;
12
9
  };
13
10
  events: {
@@ -19,5 +16,13 @@ export type PageContentProps = typeof __propDef.props;
19
16
  export type PageContentEvents = typeof __propDef.events;
20
17
  export type PageContentSlots = typeof __propDef.slots;
21
18
  export default class PageContent extends SvelteComponent<PageContentProps, PageContentEvents, PageContentSlots> {
19
+ constructor(options?: import("svelte").ComponentConstructorOptions<{
20
+ children: (this: void) => typeof import("svelte").SnippetReturn & {
21
+ _: "functions passed to {@render ...} tags must use the `Snippet` type imported from \"svelte\"";
22
+ };
23
+ pad?: string | boolean | [boolean, boolean] | [boolean, boolean, boolean] | [boolean, boolean, boolean, boolean] | [string, string] | [string, string, string] | [string, string, string, string] | undefined;
24
+ middle?: boolean | undefined;
25
+ center?: boolean | undefined;
26
+ }>);
22
27
  }
23
28
  export {};
@@ -31,6 +31,12 @@ setContext("theme", theme);
31
31
  .Theme {
32
32
  display: contents;
33
33
  }
34
+ .Theme.light :global(:root) {
35
+ --scrollbar-color: var(--l-scrollbar-color);
36
+ }
37
+ .Theme.dark :global(:root) {
38
+ --scrollbar-color: var(--d-scrollbar-color);
39
+ }
34
40
  .Theme.light {
35
41
  --mix-target: var(--l-mix-target);
36
42
  /* bg */
@@ -122,6 +128,7 @@ setContext("theme", theme);
122
128
  --status-warn: var(--l-status-warn);
123
129
  --status-info: var(--l-status-info);
124
130
  --status-task: var(--l-status-task);
131
+ --scrollbar-color: var(--l-scrollbar-color);
125
132
  }
126
133
  .Theme.dark {
127
134
  --mix-target: var(--d-mix-target);
@@ -216,5 +223,6 @@ setContext("theme", theme);
216
223
  --status-warn: var(--d-status-warn);
217
224
  --status-info: var(--d-status-info);
218
225
  --status-task: var(--d-status-task);
226
+ --scrollbar-color: var(--d-scrollbar-color);
219
227
  }
220
228
  </style>
@@ -14,5 +14,11 @@ export type ThemeProps = typeof __propDef.props;
14
14
  export type ThemeEvents = typeof __propDef.events;
15
15
  export type ThemeSlots = typeof __propDef.slots;
16
16
  export default class Theme extends SvelteComponent<ThemeProps, ThemeEvents, ThemeSlots> {
17
+ constructor(options?: import("svelte").ComponentConstructorOptions<{
18
+ theme?: "light" | "dark" | "invert" | undefined;
19
+ children: (this: void) => typeof import("svelte").SnippetReturn & {
20
+ _: "functions passed to {@render ...} tags must use the `Snippet` type imported from \"svelte\"";
21
+ };
22
+ }>);
17
23
  }
18
24
  export {};
@@ -13,5 +13,10 @@ export type UiContentProps = typeof __propDef.props;
13
13
  export type UiContentEvents = typeof __propDef.events;
14
14
  export type UiContentSlots = typeof __propDef.slots;
15
15
  export default class UiContent extends SvelteComponent<UiContentProps, UiContentEvents, UiContentSlots> {
16
+ constructor(options?: import("svelte").ComponentConstructorOptions<{
17
+ children: (this: void) => typeof import("svelte").SnippetReturn & {
18
+ _: "functions passed to {@render ...} tags must use the `Snippet` type imported from \"svelte\"";
19
+ };
20
+ }>);
16
21
  }
17
22
  export {};
@@ -0,0 +1,34 @@
1
+ import type { ComponentType, Snippet } from "svelte";
2
+ export type OverlayPosition = "top left" | "top center" | "top right" | "bottom left" | "bottom center" | "bottom right" | "center" | "anchor";
3
+ export type TransitionOpts = {
4
+ y?: number;
5
+ x?: number;
6
+ duration?: number;
7
+ origin?: string;
8
+ easing?: (t: number) => number;
9
+ };
10
+ export type OverlayItem = {
11
+ id: string;
12
+ z?: number;
13
+ content: string | Snippet | ComponentType;
14
+ props?: Record<string, any>;
15
+ layer?: string;
16
+ anchor?: HTMLElement;
17
+ transition?: TransitionOpts;
18
+ position?: OverlayPosition;
19
+ sticky?: boolean;
20
+ stack?: string;
21
+ };
22
+ declare let overlays: {
23
+ topRight: OverlayItem[];
24
+ topLeft: OverlayItem[];
25
+ topCenter: OverlayItem[];
26
+ bottomRight: OverlayItem[];
27
+ bottomLeft: OverlayItem[];
28
+ bottomCenter: OverlayItem[];
29
+ center: OverlayItem[];
30
+ anchor: OverlayItem[];
31
+ };
32
+ declare function addOverlay(opts: OverlayItem): void;
33
+ declare function removeOverlay(id: string): void;
34
+ export { overlays, addOverlay, removeOverlay };
@@ -0,0 +1,44 @@
1
+ let overlays = $state({
2
+ topRight: [],
3
+ topLeft: [],
4
+ topCenter: [],
5
+ bottomRight: [],
6
+ bottomLeft: [],
7
+ bottomCenter: [],
8
+ center: [],
9
+ anchor: [],
10
+ });
11
+ function addOverlay(opts) {
12
+ switch (opts.position) {
13
+ case "top left":
14
+ overlays.topLeft.push(opts);
15
+ break;
16
+ case "top right":
17
+ overlays.topRight.push(opts);
18
+ break;
19
+ case "top center":
20
+ overlays.topCenter.push(opts);
21
+ break;
22
+ case "bottom left":
23
+ overlays.bottomLeft.push(opts);
24
+ break;
25
+ case "bottom right":
26
+ overlays.bottomRight.push(opts);
27
+ break;
28
+ case "bottom center":
29
+ overlays.bottomCenter.push(opts);
30
+ break;
31
+ case "center":
32
+ overlays.center.push(opts);
33
+ break;
34
+ default:
35
+ overlays.anchor.push(opts);
36
+ break;
37
+ }
38
+ }
39
+ function removeOverlay(id) {
40
+ for (const key in overlays) {
41
+ overlays[key] = overlays[key].filter((o) => o.id !== id);
42
+ }
43
+ }
44
+ export { overlays, addOverlay, removeOverlay };
@@ -3,17 +3,12 @@ import type { ComponentType } from "svelte";
3
3
  import type { BreadcrumbItem } from "./MenuTypes.js";
4
4
  declare const __propDef: {
5
5
  props: {
6
- /** The items to display in the breadcrumb. */
7
6
  items: BreadcrumbItem[];
8
- /** The separator to use between breadcrumb items. */
9
7
  separator?: string | ((this: void) => typeof import("svelte").SnippetReturn & {
10
8
  _: "functions passed to {@render ...} tags must use the `Snippet` type imported from \"svelte\"";
11
9
  }) | ComponentType | undefined;
12
- /** Contain the element in a box. */
13
10
  contained?: boolean | undefined;
14
- /** Make the element full width. */
15
11
  fullWidth?: boolean | undefined;
16
- /** Center the items in the element. */
17
12
  center?: boolean | undefined;
18
13
  };
19
14
  events: {
@@ -25,5 +20,14 @@ export type BreadcrumbProps = typeof __propDef.props;
25
20
  export type BreadcrumbEvents = typeof __propDef.events;
26
21
  export type BreadcrumbSlots = typeof __propDef.slots;
27
22
  export default class Breadcrumb extends SvelteComponent<BreadcrumbProps, BreadcrumbEvents, BreadcrumbSlots> {
23
+ constructor(options?: import("svelte").ComponentConstructorOptions<{
24
+ items: BreadcrumbItem[];
25
+ separator?: string | ((this: void) => typeof import("svelte").SnippetReturn & {
26
+ _: "functions passed to {@render ...} tags must use the `Snippet` type imported from \"svelte\"";
27
+ }) | ComponentType | undefined;
28
+ contained?: boolean | undefined;
29
+ fullWidth?: boolean | undefined;
30
+ center?: boolean | undefined;
31
+ }>);
28
32
  }
29
33
  export {};
@@ -1,10 +1,13 @@
1
- <script lang="ts">import MenuItem from "./MenuItem.svelte";
1
+ <script lang="ts">import { BROWSER } from "esm-env";
2
+ import MenuItem from "./MenuItem.svelte";
2
3
  import { isComponent } from "../utils/isSnippet.js";
3
4
  import { createId } from "../utils/id.js";
4
5
  import { slidefade } from "../utils/transitions.js";
5
6
  import UiContent from "../layout/UIContent.svelte";
6
7
  import { arrowNavigation, getNextFocusableElement, matchOnType } from "../utils/keyboard.svelte.js";
7
8
  import StringOrComponentOrSnippet from "../utils/StringOrComponentOrSnippet.svelte";
9
+ import { findContainingBlock, getPossiblyContainedPosition } from "../utils/dom.js";
10
+ import Overlay from "../layout/Overlay.svelte";
8
11
  let {
9
12
  open = $bindable(false),
10
13
  items,
@@ -14,10 +17,9 @@ let _open = $state(open);
14
17
  let triggerEl = $state(null);
15
18
  let contentEl = $state(null);
16
19
  let menuEl = $state(null);
17
- let menuHeight = $state(0);
18
- let menuWidth = $state(0);
20
+ let currentIndex = $state(-1);
21
+ let keyboardHasFocus = $state(false);
19
22
  const id = createId();
20
- const fudge = 16;
21
23
  $effect(() => {
22
24
  if (_open) {
23
25
  window.addEventListener("click", clickoutside);
@@ -30,40 +32,10 @@ $effect(() => {
30
32
  function toggle() {
31
33
  _open = !_open;
32
34
  }
33
- let origin = $derived.by(() => {
34
- if (!triggerEl || !contentEl)
35
- return "top-left";
36
- const triggerPos = triggerEl.getBoundingClientRect();
37
- const elPos = contentEl.getBoundingClientRect();
38
- const isOffRight = triggerPos.left + elPos.width > window.innerWidth - fudge;
39
- const isOffBottom = triggerPos.bottom + elPos.height > window.innerHeight - fudge;
40
- let text = "top left";
41
- if (isOffRight)
42
- text = text.replace("left", "right");
43
- if (isOffBottom)
44
- text = text.replace("top", "bottom");
45
- return text;
46
- });
47
- let posLeft = $derived.by(() => {
48
- if (!triggerEl || !contentEl)
49
- return 0;
50
- const triggerPos = triggerEl.getBoundingClientRect();
51
- const isOffRight = triggerPos.left + menuWidth > window.innerWidth - fudge;
52
- if (isOffRight) {
53
- return triggerPos.left + triggerPos.width - menuWidth;
54
- }
55
- return triggerPos.left;
56
- });
57
- let posTop = $derived.by(() => {
58
- if (!triggerEl || !contentEl)
59
- return 0;
60
- const triggerPos = triggerEl.getBoundingClientRect();
61
- const scrollY = window.scrollY;
62
- const isOffBottom = triggerPos.bottom + menuHeight > window.innerHeight - fudge;
63
- if (isOffBottom) {
64
- return triggerPos.top - menuHeight - triggerPos.height + scrollY;
65
- }
66
- return triggerPos.top + scrollY;
35
+ let scrollable = $derived.by(() => {
36
+ if (!contentEl)
37
+ return false;
38
+ return contentEl.scrollHeight > contentEl.clientHeight;
67
39
  });
68
40
  function onclick(e) {
69
41
  e.preventDefault();
@@ -104,11 +76,13 @@ function onkeydown(e) {
104
76
  e.preventDefault();
105
77
  arrowNavigation(contentEl, "down");
106
78
  matchOnType(contentEl, e);
79
+ keyboardHasFocus = true;
107
80
  break;
108
81
  case "ArrowUp":
109
82
  e.preventDefault();
110
83
  arrowNavigation(contentEl, "up");
111
84
  matchOnType(contentEl, e);
85
+ keyboardHasFocus = true;
112
86
  break;
113
87
  case "Enter":
114
88
  case "Space":
@@ -119,8 +93,14 @@ function onkeydown(e) {
119
93
  matchOnType(contentEl, e);
120
94
  }
121
95
  }
96
+ function mouseover(e, item, index) {
97
+ if (item.type === "item") {
98
+ currentIndex = index;
99
+ }
100
+ }
122
101
  </script>
123
102
 
103
+
124
104
  <UiContent>
125
105
  <div class="Menu" bind:this={menuEl}>
126
106
  <div
@@ -135,22 +115,21 @@ function onkeydown(e) {
135
115
  {@render trigger({ toggle: toggle, isOpen: _open })}
136
116
  {/if}
137
117
  </div>
138
- {#if _open}
139
- <div {id}
140
- class="MenuContent"
141
- role="menu"
142
- bind:clientWidth={menuWidth}
143
- bind:clientHeight={menuHeight}
144
- bind:this={contentEl}
145
- style="--left: {posLeft}px; --top: {posTop}px;"
146
- transition:slidefade={{ y: 5, origin: origin, duration: 100 }}
147
- >
148
- <ul>
149
- {#each items as item}
150
- <MenuItem item={item} />
151
- {/each}
152
- </ul>
153
- </div>
118
+ {#if _open && triggerEl}
119
+ <Overlay position="anchor" id="o-{id}" anchor={triggerEl} layer="menu">
120
+ <div {id}
121
+ class="MenuContent"
122
+ class:scrollable={scrollable}
123
+ role="menu"
124
+ bind:this={contentEl}
125
+ >
126
+ <ul>
127
+ {#each items as item, index}
128
+ <MenuItem {keyboardHasFocus} onmouseover={mouseover} item={item} {index} />
129
+ {/each}
130
+ </ul>
131
+ </div>
132
+ </Overlay>
154
133
  {/if}
155
134
  </div>
156
135
  </UiContent>
@@ -166,9 +145,7 @@ function onkeydown(e) {
166
145
  }
167
146
 
168
147
  .MenuContent {
169
- position: fixed;
170
- left: var(--left, 0);
171
- top: var(--top, 0);
148
+ max-height: calc(50vh - 2rem);
172
149
  margin: 0;
173
150
  z-index: 1000;
174
151
  margin: 0;
@@ -177,17 +154,21 @@ function onkeydown(e) {
177
154
  box-shadow: 0 0.5rem 1rem var(--shadow);
178
155
  background-color: var(--menu-bg);
179
156
  width: var(--width, 25ch);
180
- overflow: clip;
157
+ overflow-x: clip;
158
+ overflow-y: auto;
159
+ scrollbar-width: thin;
160
+ scrollbar-color: var(--scrollbar-color);
161
+ }
162
+
163
+ .MenuContent.scrollable {
164
+ border-top-right-radius: 0;
165
+ border-bottom-right-radius: 0;
181
166
  }
182
167
 
183
168
  .MenuContent:has(li:last-of-type[data-type="item"]) {
184
169
  padding-block-end: 0.5rem;
185
170
  }
186
171
 
187
- nav[aria-expanded="true"] {
188
- display: block;
189
- }
190
-
191
172
  ul {
192
173
  margin: 0;
193
174
  list-style: none;