@svadmin/ui 0.46.0 → 0.48.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.
Files changed (33) hide show
  1. package/dist/components/AboutSettings.svelte +21 -96
  2. package/dist/components/AdminApp.svelte +20 -0
  3. package/dist/components/AdminApp.svelte.d.ts +5 -1
  4. package/dist/components/ApiSettings.svelte +153 -246
  5. package/dist/components/AppearanceSettings.svelte +36 -165
  6. package/dist/components/AutoForm.svelte +21 -3
  7. package/dist/components/DetailDrawer.svelte +74 -0
  8. package/dist/components/DetailDrawer.svelte.d.ts +16 -0
  9. package/dist/components/FieldRenderer.svelte +70 -2
  10. package/dist/components/FieldRenderer.svelte.d.ts +2 -0
  11. package/dist/components/IntegrationsSettings.svelte +22 -61
  12. package/dist/components/Layout.svelte +15 -1
  13. package/dist/components/NotificationsSettings.svelte +22 -103
  14. package/dist/components/PasswordInput.svelte +4 -0
  15. package/dist/components/PasswordInput.svelte.d.ts +1 -0
  16. package/dist/components/PermissionMatrix.svelte +17 -8
  17. package/dist/components/RowActions.svelte +154 -0
  18. package/dist/components/RowActions.svelte.d.ts +26 -0
  19. package/dist/components/SecuritySettings.svelte +165 -140
  20. package/dist/components/SecuritySettings.svelte.d.ts +1 -2
  21. package/dist/components/SettingsPage.svelte +12 -2
  22. package/dist/components/account/CompanyProfilePage.svelte +10 -4
  23. package/dist/components/account/SettingsEnterprisePage.svelte +227 -23
  24. package/dist/components/account/SettingsSidebarPage.svelte +12 -1
  25. package/dist/components/account/UserProfilePage.svelte +5 -4
  26. package/dist/components/content/ApiKeyList.svelte +30 -5
  27. package/dist/components/content/ApiKeyList.svelte.d.ts +3 -0
  28. package/dist/components/content/WorkspaceLayout.svelte +7 -4
  29. package/dist/components/content/WorkspaceLayout.svelte.d.ts +1 -0
  30. package/dist/components/ui/sheet/sheet.svelte +122 -10
  31. package/dist/index.d.ts +3 -0
  32. package/dist/index.js +2 -0
  33. package/package.json +5 -2
@@ -1,8 +1,51 @@
1
+ <script lang="ts" module>
2
+ let bodyScrollLockDepth = 0;
3
+ let bodyOverflowBeforeLock = "";
4
+ const openSheets: HTMLElement[] = [];
5
+
6
+ function lockBodyScroll() {
7
+ if (bodyScrollLockDepth === 0) bodyOverflowBeforeLock = document.body.style.overflow;
8
+ bodyScrollLockDepth += 1;
9
+ document.body.style.overflow = "hidden";
10
+ }
11
+
12
+ function unlockBodyScroll() {
13
+ bodyScrollLockDepth = Math.max(0, bodyScrollLockDepth - 1);
14
+ if (bodyScrollLockDepth > 0) return;
15
+ document.body.style.overflow = bodyOverflowBeforeLock;
16
+ bodyOverflowBeforeLock = "";
17
+ }
18
+
19
+ function registerSheet(panel: HTMLElement) {
20
+ openSheets.push(panel);
21
+ }
22
+
23
+ function unregisterSheet(panel: HTMLElement) {
24
+ const index = openSheets.lastIndexOf(panel);
25
+ if (index >= 0) openSheets.splice(index, 1);
26
+ }
27
+
28
+ function isTopmostSheet(panel: HTMLElement) {
29
+ return openSheets.at(-1) === panel;
30
+ }
31
+ </script>
32
+
1
33
  <script lang="ts">
2
34
  /* eslint-disable svelte/no-unused-svelte-ignore */
35
+ import { tick } from "svelte";
3
36
  import { cn, type WithElementRef } from "../../../utils.js";
4
37
  import type { HTMLAttributes } from "svelte/elements";
5
38
 
39
+ const focusableSelector = [
40
+ 'a[href]',
41
+ 'button',
42
+ 'input',
43
+ 'select',
44
+ 'textarea',
45
+ '[tabindex]:not([tabindex="-1"])',
46
+ '[contenteditable]:not([contenteditable="false"])',
47
+ ].join(",");
48
+
6
49
  type Props = WithElementRef<HTMLAttributes<HTMLDivElement>> & {
7
50
  open?: boolean;
8
51
  side?: "left" | "right";
@@ -15,6 +58,7 @@
15
58
  side = "right",
16
59
  onClose,
17
60
  class: className,
61
+ tabindex = -1,
18
62
  children,
19
63
  ...restProps
20
64
  }: Props = $props();
@@ -24,22 +68,88 @@
24
68
  onClose?.();
25
69
  }
26
70
 
27
- function handleKeydown(e: KeyboardEvent) {
28
- if (e.key === "Escape") close();
71
+ function isContentEditableHost(element: HTMLElement) {
72
+ const contentEditable = element.getAttribute("contenteditable");
73
+ const isEditable = element.isContentEditable || (contentEditable !== null && contentEditable.toLowerCase() !== "false");
74
+ return isEditable && !element.parentElement?.isContentEditable;
29
75
  }
30
76
 
31
- $effect(() => {
32
- if (open) {
33
- document.addEventListener("keydown", handleKeydown);
34
- document.body.style.overflow = "hidden";
35
- return () => {
36
- document.removeEventListener("keydown", handleKeydown);
37
- document.body.style.overflow = "";
38
- };
77
+ function isVisible(element: HTMLElement) {
78
+ for (let current: HTMLElement | null = element; current; current = current.parentElement) {
79
+ const style = getComputedStyle(current);
80
+ if (style.display === "none" || style.visibility === "hidden") return false;
81
+ if (current === ref) break;
39
82
  }
83
+ return true;
84
+ }
85
+
86
+ function isTabbable(element: HTMLElement) {
87
+ if (element.matches(":disabled, [aria-disabled='true']")) return false;
88
+ if (element.closest("[hidden], [inert], [aria-hidden='true']")) return false;
89
+ if (element.hasAttribute("tabindex") && element.tabIndex < 0) return false;
90
+ if (!element.hasAttribute("tabindex") && !isContentEditableHost(element) && element.tabIndex < 0) return false;
91
+ return isVisible(element);
92
+ }
93
+
94
+ function tabbableElements() {
95
+ return ref ? [...ref.querySelectorAll<HTMLElement>(focusableSelector)].filter(isTabbable) : [];
96
+ }
97
+
98
+ function handleKeydown(event: KeyboardEvent) {
99
+ if (event.defaultPrevented || !open || !ref || !isTopmostSheet(ref)) return;
100
+ if (event.key === "Escape") {
101
+ event.preventDefault();
102
+ close();
103
+ return;
104
+ }
105
+ if (event.key !== "Tab") return;
106
+
107
+ const tabbable = tabbableElements();
108
+ if (tabbable.length === 0) {
109
+ event.preventDefault();
110
+ ref.focus();
111
+ return;
112
+ }
113
+
114
+ const first = tabbable[0];
115
+ const last = tabbable.at(-1);
116
+ const activeIndex = tabbable.indexOf(document.activeElement as HTMLElement);
117
+ if (!ref.contains(document.activeElement) || activeIndex === -1) {
118
+ event.preventDefault();
119
+ (event.shiftKey ? last : first)?.focus();
120
+ } else if (event.shiftKey && activeIndex === 0) {
121
+ event.preventDefault();
122
+ last?.focus();
123
+ } else if (!event.shiftKey && activeIndex === tabbable.length - 1) {
124
+ event.preventDefault();
125
+ first.focus();
126
+ }
127
+ }
128
+
129
+ $effect(() => {
130
+ if (!open || !ref) return;
131
+ const panel = ref;
132
+ const previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
133
+ registerSheet(panel);
134
+ lockBodyScroll();
135
+ void tick().then(() => {
136
+ if (open && ref === panel && isTopmostSheet(panel)) {
137
+ (tabbableElements()[0] ?? panel).focus();
138
+ }
139
+ });
140
+ return () => {
141
+ unregisterSheet(panel);
142
+ unlockBodyScroll();
143
+ const remainingSheet = openSheets.at(-1);
144
+ if (!remainingSheet || (previousFocus && remainingSheet.contains(previousFocus))) {
145
+ previousFocus?.focus();
146
+ }
147
+ };
40
148
  });
41
149
  </script>
42
150
 
151
+ <svelte:document onkeydown={handleKeydown} />
152
+
43
153
  {#if open}
44
154
  <!-- Overlay -->
45
155
  <!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
@@ -50,9 +160,11 @@
50
160
  ></div>
51
161
 
52
162
  <!-- Sheet panel -->
163
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
53
164
  <div
54
165
  bind:this={ref}
55
166
  data-slot="sheet"
167
+ {tabindex}
56
168
  class={cn(
57
169
  "fixed z-50 flex translate-x-0 flex-col gap-4 bg-background p-6 shadow-lg",
58
170
  side === "right"
package/dist/index.d.ts CHANGED
@@ -194,3 +194,6 @@ export { default as TwoFactorStepper } from './components/content/TwoFactorStepp
194
194
  export { default as SystemErrorState } from './components/content/SystemErrorState.svelte';
195
195
  export { referenceDemoData } from './reference-data.js';
196
196
  export type { DemoMember, DemoProject, DemoTeam, DemoIntegration, DemoApiKey, DemoSecurityEvent, DemoNotification } from './reference-data.js';
197
+ export { default as RowActions } from './components/RowActions.svelte';
198
+ export type { RowActionItem } from './components/RowActions.svelte';
199
+ export { default as DetailDrawer } from './components/DetailDrawer.svelte';
package/dist/index.js CHANGED
@@ -195,3 +195,5 @@ export { default as OtpInput } from './components/content/OtpInput.svelte';
195
195
  export { default as TwoFactorStepper } from './components/content/TwoFactorStepper.svelte';
196
196
  export { default as SystemErrorState } from './components/content/SystemErrorState.svelte';
197
197
  export { referenceDemoData } from './reference-data.js';
198
+ export { default as RowActions } from './components/RowActions.svelte';
199
+ export { default as DetailDrawer } from './components/DetailDrawer.svelte';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@svadmin/ui",
3
- "version": "0.46.0",
3
+ "version": "0.48.0",
4
4
  "description": "Pre-built admin UI components — AdminApp, AutoTable, AutoForm, Sidebar, Layout",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -39,7 +39,7 @@
39
39
  "./components/*": "./dist/components/*"
40
40
  },
41
41
  "peerDependencies": {
42
- "@svadmin/core": "^0.38.0",
42
+ "@svadmin/core": "^0.39.0",
43
43
  "@tanstack/svelte-query": "^6.1.38",
44
44
  "highlight.js": "^11.11.1",
45
45
  "isomorphic-dompurify": "^3.22.0",
@@ -96,6 +96,9 @@
96
96
  "bugs": {
97
97
  "url": "https://github.com/vibeunion/svadmin/issues"
98
98
  },
99
+ "publishConfig": {
100
+ "access": "public"
101
+ },
99
102
  "devDependencies": {
100
103
  "@happy-dom/global-registrator": "^20.11.2",
101
104
  "@sveltejs/vite-plugin-svelte": "^7.3.0",