@happyvertical/smrt-svelte 0.51.39 → 0.53.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/AGENTS.md +3 -0
- package/README.md +214 -1
- package/agents/components.md +8 -1
- package/agents/workspace.md +24 -0
- package/dist/Provider.svelte +19 -15
- package/dist/Provider.svelte.d.ts +2 -2
- package/dist/Provider.svelte.d.ts.map +1 -1
- package/dist/__tests__/provider-mode-harness.svelte +14 -0
- package/dist/__tests__/provider-mode-harness.svelte.d.ts +11 -0
- package/dist/__tests__/provider-mode-harness.svelte.d.ts.map +1 -0
- package/dist/__tests__/provider-mode-probe.svelte +13 -0
- package/dist/__tests__/provider-mode-probe.svelte.d.ts +8 -0
- package/dist/__tests__/provider-mode-probe.svelte.d.ts.map +1 -0
- package/dist/browser-ai/adapters/stt/browser-speech.d.ts +11 -0
- package/dist/browser-ai/adapters/stt/browser-speech.d.ts.map +1 -1
- package/dist/browser-ai/adapters/stt/browser-speech.js +20 -4
- package/dist/browser-ai/adapters/stt/browser-speech.test.js +113 -0
- package/dist/browser-ai/adapters/stt/dictation-source.d.ts +3 -0
- package/dist/browser-ai/adapters/stt/dictation-source.d.ts.map +1 -0
- package/dist/browser-ai/adapters/stt/dictation-source.js +38 -0
- package/dist/browser-ai/adapters/stt/dictation-source.test.js +52 -0
- package/dist/browser-ai/adapters/stt/index.d.ts +2 -1
- package/dist/browser-ai/adapters/stt/index.d.ts.map +1 -1
- package/dist/browser-ai/adapters/stt/index.js +2 -1
- package/dist/components/forms/FileUpload.svelte +120 -16
- package/dist/components/forms/FileUpload.svelte.d.ts +7 -1
- package/dist/components/forms/FileUpload.svelte.d.ts.map +1 -1
- package/dist/components/forms/Form.svelte +118 -8
- package/dist/components/forms/Form.svelte.d.ts +18 -2
- package/dist/components/forms/Form.svelte.d.ts.map +1 -1
- package/dist/components/forms/FormScope.svelte +202 -0
- package/dist/components/forms/FormScope.svelte.d.ts +22 -0
- package/dist/components/forms/FormScope.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/FileUpload.native-form.test.js +249 -0
- package/dist/components/forms/__tests__/FileUploadReset.fixture.svelte +24 -0
- package/dist/components/forms/__tests__/FileUploadReset.fixture.svelte.d.ts +10 -0
- package/dist/components/forms/__tests__/FileUploadReset.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/Form.ui-controls.test.js +119 -0
- package/dist/components/forms/__tests__/Form.webmcp.test.js +5 -3
- package/dist/components/forms/__tests__/FormScope.test.js +39 -0
- package/dist/components/forms/__tests__/form-scope.fixture.svelte +30 -0
- package/dist/components/forms/__tests__/form-scope.fixture.svelte.d.ts +10 -0
- package/dist/components/forms/__tests__/form-scope.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte +84 -0
- package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte.d.ts +14 -0
- package/dist/components/forms/__tests__/form-with-ui-controls.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/index.d.ts +1 -0
- package/dist/components/forms/index.d.ts.map +1 -1
- package/dist/components/forms/index.js +1 -0
- package/dist/components/workspace/README.md +11 -0
- package/dist/components/workspace/__tests__/AdminShell.responsive.test.js +597 -0
- package/dist/components/workspace/__tests__/AdminShell.test.js +257 -1
- package/dist/components/workspace/__tests__/TenantNav.test.js +115 -0
- package/dist/components/workspace/__tests__/WorkspaceAccountMenu.test.js +6 -0
- package/dist/components/workspace/__tests__/admin-shell-header-overflow.test.js +25 -0
- package/dist/components/workspace/__tests__/admin-shell-mobile.test.js +217 -0
- package/dist/components/workspace/__tests__/admin-shell-page-contracts.test.js +30 -0
- package/dist/components/workspace/__tests__/admin-shell-responsive-state.test.js +184 -0
- package/dist/components/workspace/__tests__/admin-shell-state.test.js +25 -0
- package/dist/components/workspace/__tests__/admin-shell-theme-hooks.test.js +36 -0
- package/dist/components/workspace/__tests__/shell-chrome-components.test.js +108 -0
- package/dist/components/workspace/admin-shell/AdminShell.svelte +1099 -59
- package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts +44 -1
- package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/PhoneBottomBar.svelte +170 -0
- package/dist/components/workspace/admin-shell/PhoneBottomBar.svelte.d.ts +11 -0
- package/dist/components/workspace/admin-shell/PhoneBottomBar.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/PhoneTopBar.svelte +93 -0
- package/dist/components/workspace/admin-shell/PhoneTopBar.svelte.d.ts +11 -0
- package/dist/components/workspace/admin-shell/PhoneTopBar.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/ShellNavToggle.svelte +88 -0
- package/dist/components/workspace/admin-shell/ShellNavToggle.svelte.d.ts +14 -0
- package/dist/components/workspace/admin-shell/ShellNavToggle.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/ShellTitle.svelte +42 -0
- package/dist/components/workspace/admin-shell/ShellTitle.svelte.d.ts +11 -0
- package/dist/components/workspace/admin-shell/ShellTitle.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/TenantNav.svelte +116 -13
- package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts +8 -2
- package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/WorkspaceAccountMenu.svelte +16 -2
- package/dist/components/workspace/admin-shell/WorkspaceAccountMenu.svelte.d.ts +2 -0
- package/dist/components/workspace/admin-shell/WorkspaceAccountMenu.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/mobile-shell-dom.d.ts +37 -0
- package/dist/components/workspace/admin-shell/mobile-shell-dom.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/mobile-shell-dom.js +150 -0
- package/dist/components/workspace/admin-shell/mobile-shell.d.ts +191 -0
- package/dist/components/workspace/admin-shell/mobile-shell.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/mobile-shell.js +165 -0
- package/dist/components/workspace/admin-shell/settings.d.ts +27 -2
- package/dist/components/workspace/admin-shell/settings.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/settings.js +84 -2
- package/dist/components/workspace/admin-shell/state.svelte.d.ts +57 -1
- package/dist/components/workspace/admin-shell/state.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/state.svelte.js +156 -6
- package/dist/components/workspace/admin-shell/types.d.ts +115 -1
- package/dist/components/workspace/admin-shell/types.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/types.js +13 -0
- package/dist/components/workspace/index.d.ts +11 -4
- package/dist/components/workspace/index.d.ts.map +1 -1
- package/dist/components/workspace/index.js +12 -3
- package/dist/i18n/strings.forms.d.ts +3 -0
- package/dist/i18n/strings.forms.d.ts.map +1 -1
- package/dist/i18n/strings.forms.js +3 -0
- package/dist/i18n/strings.workspace.d.ts +14 -0
- package/dist/i18n/strings.workspace.d.ts.map +1 -1
- package/dist/i18n/strings.workspace.js +14 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/provider-mode.test.js +86 -0
- package/dist/state/app-state.svelte.d.ts.map +1 -1
- package/dist/state/app-state.svelte.js +8 -8
- package/dist/web/__tests__/assistant-dock.integration.svelte.test.js +2 -2
- package/dist/web/__tests__/list-sort-surface.fixture.svelte +41 -0
- package/dist/web/__tests__/list-sort-surface.fixture.svelte.d.ts +15 -0
- package/dist/web/__tests__/list-sort-surface.fixture.svelte.d.ts.map +1 -0
- package/dist/web/__tests__/list-sort-surface.svelte.test.js +59 -0
- package/dist/web/__tests__/surfaces.child.fixture.svelte +69 -0
- package/dist/web/__tests__/surfaces.child.fixture.svelte.d.ts +16 -0
- package/dist/web/__tests__/surfaces.child.fixture.svelte.d.ts.map +1 -0
- package/dist/web/__tests__/surfaces.fixture.svelte +26 -0
- package/dist/web/__tests__/surfaces.fixture.svelte.d.ts +18 -0
- package/dist/web/__tests__/surfaces.fixture.svelte.d.ts.map +1 -0
- package/dist/web/__tests__/surfaces.svelte.test.js +140 -0
- package/dist/web/__tests__/webmcp-ui.test.js +18 -0
- package/dist/web/index.d.ts +4 -1
- package/dist/web/index.d.ts.map +1 -1
- package/dist/web/index.js +4 -1
- package/dist/web/link-surface.svelte.d.ts +14 -0
- package/dist/web/link-surface.svelte.d.ts.map +1 -0
- package/dist/web/link-surface.svelte.js +49 -0
- package/dist/web/list-surface.svelte.d.ts +80 -0
- package/dist/web/list-surface.svelte.d.ts.map +1 -0
- package/dist/web/list-surface.svelte.js +309 -0
- package/dist/web/step-surface.svelte.d.ts +17 -0
- package/dist/web/step-surface.svelte.d.ts.map +1 -0
- package/dist/web/step-surface.svelte.js +56 -0
- package/dist/web/webmcp-ui-context.d.ts +7 -0
- package/dist/web/webmcp-ui-context.d.ts.map +1 -1
- package/dist/web/webmcp-ui-context.js +10 -0
- package/dist/web/webmcp-ui.d.ts.map +1 -1
- package/dist/web/webmcp-ui.js +16 -3
- package/package.json +17 -17
|
@@ -2,11 +2,17 @@
|
|
|
2
2
|
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
3
3
|
import type { Component } from 'svelte';
|
|
4
4
|
import { M } from '../../../i18n/strings.workspace.js';
|
|
5
|
-
import type { ShellNavItem } from './types.js';
|
|
5
|
+
import type { ShellNavGroup, ShellNavItem } from './types.js';
|
|
6
6
|
|
|
7
7
|
interface Props {
|
|
8
8
|
/** Navigation items with href, label, icon, and optional children. */
|
|
9
|
-
items
|
|
9
|
+
items?: ShellNavItem[];
|
|
10
|
+
/** Labelled, collapsible navigation groups, rendered after flat items. */
|
|
11
|
+
groups?: ShellNavGroup[];
|
|
12
|
+
/** Accessible name for this navigation landmark. */
|
|
13
|
+
'aria-label'?: string;
|
|
14
|
+
/** Target density; omit to inherit the theme density. */
|
|
15
|
+
density?: 'comfortable' | 'touch';
|
|
10
16
|
/** Current URL to highlight the active navigation link. */
|
|
11
17
|
currentHref?: string;
|
|
12
18
|
/** Component used to render icon names as SVG or custom icons. */
|
|
@@ -18,7 +24,10 @@ interface Props {
|
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
let {
|
|
21
|
-
items,
|
|
27
|
+
items = [],
|
|
28
|
+
groups = [],
|
|
29
|
+
'aria-label': ariaLabel,
|
|
30
|
+
density,
|
|
22
31
|
currentHref = '',
|
|
23
32
|
iconComponent: IconComponent,
|
|
24
33
|
collapsed = false,
|
|
@@ -35,17 +44,33 @@ function isVisibleActive(item: ShellNavItem): boolean {
|
|
|
35
44
|
return collapsed && !!item.children?.some((child) => isActive(child));
|
|
36
45
|
}
|
|
37
46
|
|
|
47
|
+
/** Accessible label of an item's attention dot, or null when it has none. */
|
|
48
|
+
function attentionLabel(item: ShellNavItem): string | null {
|
|
49
|
+
if (!item.attention) return null;
|
|
50
|
+
return typeof item.attention === 'string' && item.attention.trim()
|
|
51
|
+
? item.attention.trim()
|
|
52
|
+
: t(M['ui.tenant_nav.needs_attention']);
|
|
53
|
+
}
|
|
54
|
+
|
|
38
55
|
function fallbackIcon(label: string): string {
|
|
39
56
|
return label.trim().charAt(0).toLocaleUpperCase() || '?';
|
|
40
57
|
}
|
|
41
58
|
</script>
|
|
42
59
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
60
|
+
{#snippet attention(item: ShellNavItem)}
|
|
61
|
+
{@const label = attentionLabel(item)}
|
|
62
|
+
{#if label}
|
|
63
|
+
<span
|
|
64
|
+
class="smrt-tenant-nav__attention"
|
|
65
|
+
data-attention
|
|
66
|
+
aria-hidden="true"
|
|
67
|
+
></span>
|
|
68
|
+
<span class="smrt-tenant-nav__sr-only">({label})</span>
|
|
69
|
+
{/if}
|
|
70
|
+
{/snippet}
|
|
71
|
+
|
|
72
|
+
{#snippet navItems(entries: ShellNavItem[])}
|
|
73
|
+
{#each entries as item (item.href)}
|
|
49
74
|
<div class="smrt-tenant-nav__section">
|
|
50
75
|
<a
|
|
51
76
|
href={item.href}
|
|
@@ -78,6 +103,7 @@ function fallbackIcon(label: string): string {
|
|
|
78
103
|
<small>{item.badge}</small>
|
|
79
104
|
{/if}
|
|
80
105
|
{/if}
|
|
106
|
+
{@render attention(item)}
|
|
81
107
|
</a>
|
|
82
108
|
{#if item.children?.length && !collapsed}
|
|
83
109
|
<div class="smrt-tenant-nav__children">
|
|
@@ -100,25 +126,82 @@ function fallbackIcon(label: string): string {
|
|
|
100
126
|
{#if child.badge !== null && child.badge !== undefined}
|
|
101
127
|
<small>{child.badge}</small>
|
|
102
128
|
{/if}
|
|
129
|
+
{@render attention(child)}
|
|
103
130
|
</a>
|
|
104
131
|
{/each}
|
|
105
132
|
</div>
|
|
106
133
|
{/if}
|
|
107
134
|
</div>
|
|
108
135
|
{/each}
|
|
136
|
+
{/snippet}
|
|
137
|
+
|
|
138
|
+
<nav
|
|
139
|
+
class="smrt-tenant-nav"
|
|
140
|
+
data-density={density}
|
|
141
|
+
class:smrt-tenant-nav--collapsed={collapsed}
|
|
142
|
+
aria-label={ariaLabel || t(M['ui.tenant_nav.tenant_navigation'])}
|
|
143
|
+
>
|
|
144
|
+
|
|
145
|
+
{@render navItems(items)}
|
|
146
|
+
{#each groups as group}
|
|
147
|
+
<details class="smrt-tenant-nav__group" aria-label={group.heading} open>
|
|
148
|
+
<summary class="smrt-tenant-nav__heading">
|
|
149
|
+
{#if collapsed}
|
|
150
|
+
<span aria-hidden="true">{fallbackIcon(group.heading)}</span>
|
|
151
|
+
<span class="smrt-tenant-nav__sr-only">{group.heading}</span>
|
|
152
|
+
{:else}
|
|
153
|
+
<span>{group.heading}</span>
|
|
154
|
+
{/if}
|
|
155
|
+
</summary>
|
|
156
|
+
<div class="smrt-tenant-nav__group-items">{@render navItems(group.items)}</div>
|
|
157
|
+
</details>
|
|
158
|
+
{/each}
|
|
109
159
|
</nav>
|
|
110
160
|
|
|
111
161
|
<style>
|
|
112
162
|
.smrt-tenant-nav,
|
|
113
163
|
.smrt-tenant-nav__section,
|
|
114
|
-
.smrt-tenant-nav__children
|
|
164
|
+
.smrt-tenant-nav__children,
|
|
165
|
+
.smrt-tenant-nav__group-items {
|
|
115
166
|
display: grid;
|
|
116
167
|
gap: var(--smrt-spacing-1);
|
|
117
168
|
}
|
|
118
169
|
|
|
170
|
+
.smrt-tenant-nav__heading {
|
|
171
|
+
box-sizing: border-box;
|
|
172
|
+
min-block-size: var(--smrt-control-target-min, 0px);
|
|
173
|
+
padding: var(--smrt-spacing-2) var(--smrt-spacing-3);
|
|
174
|
+
color: var(--smrt-color-on-surface-variant);
|
|
175
|
+
font-size: var(--smrt-typography-label-large-size);
|
|
176
|
+
font-weight: var(--smrt-typography-weight-semibold);
|
|
177
|
+
border-radius: var(--smrt-radius-medium);
|
|
178
|
+
cursor: pointer;
|
|
179
|
+
}
|
|
180
|
+
.smrt-tenant-nav__heading:focus-visible {
|
|
181
|
+
outline: 2px solid var(--smrt-color-primary);
|
|
182
|
+
outline-offset: 2px;
|
|
183
|
+
}
|
|
184
|
+
.smrt-tenant-nav__group-items { margin-block-start: var(--smrt-spacing-1); }
|
|
185
|
+
.smrt-tenant-nav--collapsed .smrt-tenant-nav__heading {
|
|
186
|
+
display: grid;
|
|
187
|
+
place-items: center;
|
|
188
|
+
list-style: none;
|
|
189
|
+
inline-size: max(2.25rem, var(--smrt-control-target-min, 0px));
|
|
190
|
+
block-size: max(2.25rem, var(--smrt-control-target-min, 0px));
|
|
191
|
+
padding: 0;
|
|
192
|
+
}
|
|
193
|
+
.smrt-tenant-nav--collapsed .smrt-tenant-nav__heading::-webkit-details-marker { display: none; }
|
|
194
|
+
|
|
195
|
+
.smrt-tenant-nav[data-density='touch'] { --smrt-control-target-min: var(--smrt-touch-target-min, 48px); }
|
|
196
|
+
.smrt-tenant-nav[data-density='comfortable'] { --smrt-control-target-min: 0px; }
|
|
119
197
|
.smrt-tenant-nav a {
|
|
198
|
+
box-sizing: border-box;
|
|
199
|
+
min-block-size: var(--smrt-control-target-min, 0px);
|
|
120
200
|
display: grid;
|
|
121
|
-
|
|
201
|
+
/* Icon, label, then one auto column per trailing mark (badge, dot). */
|
|
202
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
203
|
+
grid-auto-flow: column;
|
|
204
|
+
grid-auto-columns: auto;
|
|
122
205
|
align-items: center;
|
|
123
206
|
gap: var(--smrt-spacing-2);
|
|
124
207
|
min-inline-size: 0;
|
|
@@ -136,8 +219,8 @@ function fallbackIcon(label: string): string {
|
|
|
136
219
|
.smrt-tenant-nav--collapsed a {
|
|
137
220
|
grid-template-columns: minmax(0, 1fr);
|
|
138
221
|
place-items: center;
|
|
139
|
-
inline-size: 2.25rem;
|
|
140
|
-
block-size: 2.25rem;
|
|
222
|
+
inline-size: max(2.25rem, var(--smrt-control-target-min, 0px));
|
|
223
|
+
block-size: max(2.25rem, var(--smrt-control-target-min, 0px));
|
|
141
224
|
padding: 0;
|
|
142
225
|
}
|
|
143
226
|
|
|
@@ -178,6 +261,26 @@ function fallbackIcon(label: string): string {
|
|
|
178
261
|
padding-inline-start: var(--smrt-spacing-4);
|
|
179
262
|
}
|
|
180
263
|
|
|
264
|
+
.smrt-tenant-nav a {
|
|
265
|
+
position: relative;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.smrt-tenant-nav__attention {
|
|
269
|
+
inline-size: 0.5rem;
|
|
270
|
+
block-size: 0.5rem;
|
|
271
|
+
border-radius: var(--smrt-radius-full);
|
|
272
|
+
background: var(--smrt-color-error);
|
|
273
|
+
justify-self: end;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* Collapsed: the dot sits on the icon's corner. */
|
|
277
|
+
.smrt-tenant-nav--collapsed .smrt-tenant-nav__attention {
|
|
278
|
+
position: absolute;
|
|
279
|
+
inset-block-start: 0.125rem;
|
|
280
|
+
inset-inline-end: 0.125rem;
|
|
281
|
+
box-shadow: 0 0 0 2px var(--smrt-color-surface);
|
|
282
|
+
}
|
|
283
|
+
|
|
181
284
|
.smrt-tenant-nav__sr-only {
|
|
182
285
|
position: absolute;
|
|
183
286
|
inline-size: 1px;
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import type { Component } from 'svelte';
|
|
2
|
-
import type { ShellNavItem } from './types.js';
|
|
2
|
+
import type { ShellNavGroup, ShellNavItem } from './types.js';
|
|
3
3
|
interface Props {
|
|
4
4
|
/** Navigation items with href, label, icon, and optional children. */
|
|
5
|
-
items
|
|
5
|
+
items?: ShellNavItem[];
|
|
6
|
+
/** Labelled, collapsible navigation groups, rendered after flat items. */
|
|
7
|
+
groups?: ShellNavGroup[];
|
|
8
|
+
/** Accessible name for this navigation landmark. */
|
|
9
|
+
'aria-label'?: string;
|
|
10
|
+
/** Target density; omit to inherit the theme density. */
|
|
11
|
+
density?: 'comfortable' | 'touch';
|
|
6
12
|
/** Current URL to highlight the active navigation link. */
|
|
7
13
|
currentHref?: string;
|
|
8
14
|
/** Component used to render icon names as SVG or custom icons. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TenantNav.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/TenantNav.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAExC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"TenantNav.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/TenantNav.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAExC,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG9D,UAAU,KAAK;IACb,sEAAsE;IACtE,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;IACvB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,aAAa,EAAE,CAAC;IACzB,oDAAoD;IACpD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yDAAyD;IACzD,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC;IAClC,2DAA2D;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kEAAkE;IAClE,aAAa,CAAC,EAAE,SAAS,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3D,2DAA2D;IAC3D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AA6HD,QAAA,MAAM,SAAS,0BAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
|
|
@@ -11,6 +11,8 @@ const TENANT_PREFIX = 'workspace-account:tenant:';
|
|
|
11
11
|
export interface Props {
|
|
12
12
|
/** Username or identifier displayed in the account menu. */
|
|
13
13
|
userName: string;
|
|
14
|
+
/** Target density; omit to inherit the theme density. */
|
|
15
|
+
density?: 'comfortable' | 'touch';
|
|
14
16
|
/** Custom display name for the user, falls back to userName. */
|
|
15
17
|
userLabel?: string;
|
|
16
18
|
/** URL for the user's avatar image. */
|
|
@@ -35,6 +37,7 @@ export interface Props {
|
|
|
35
37
|
|
|
36
38
|
let {
|
|
37
39
|
userName,
|
|
40
|
+
density,
|
|
38
41
|
userLabel = '',
|
|
39
42
|
avatarUrl,
|
|
40
43
|
tenantLabel = '',
|
|
@@ -100,7 +103,7 @@ function handleSelect(id: string): void {
|
|
|
100
103
|
}
|
|
101
104
|
</script>
|
|
102
105
|
|
|
103
|
-
<div class="smrt-workspace-account-menu" title={summaryTitle}>
|
|
106
|
+
<div data-density={density} class="smrt-workspace-account-menu" title={summaryTitle}>
|
|
104
107
|
<Dropdown
|
|
105
108
|
{items}
|
|
106
109
|
{placement}
|
|
@@ -129,6 +132,17 @@ function handleSelect(id: string): void {
|
|
|
129
132
|
width: 100%;
|
|
130
133
|
}
|
|
131
134
|
|
|
135
|
+
.smrt-workspace-account-menu[data-density='touch'] {
|
|
136
|
+
--smrt-control-target-min: var(--smrt-touch-target-min, 48px);
|
|
137
|
+
}
|
|
138
|
+
.smrt-workspace-account-menu[data-density='comfortable'] {
|
|
139
|
+
--smrt-control-target-min: 0px;
|
|
140
|
+
}
|
|
141
|
+
.smrt-workspace-account-menu :global(.dropdown__item) {
|
|
142
|
+
min-block-size: var(--smrt-control-target-min, 0px);
|
|
143
|
+
box-sizing: border-box;
|
|
144
|
+
}
|
|
145
|
+
|
|
132
146
|
.smrt-workspace-account-menu :global(.dropdown) {
|
|
133
147
|
display: flex;
|
|
134
148
|
width: 100%;
|
|
@@ -141,7 +155,7 @@ function handleSelect(id: string): void {
|
|
|
141
155
|
gap: var(--smrt-spacing-2);
|
|
142
156
|
width: 100%;
|
|
143
157
|
min-width: 0;
|
|
144
|
-
min-height: 2.75rem;
|
|
158
|
+
min-height: max(2.75rem, var(--smrt-control-target-min, 0px));
|
|
145
159
|
padding: var(--smrt-spacing-2);
|
|
146
160
|
border-radius: var(--smrt-radius-large);
|
|
147
161
|
text-align: start;
|
|
@@ -3,6 +3,8 @@ import type { WorkspaceAccountTenant } from './types.js';
|
|
|
3
3
|
export interface Props {
|
|
4
4
|
/** Username or identifier displayed in the account menu. */
|
|
5
5
|
userName: string;
|
|
6
|
+
/** Target density; omit to inherit the theme density. */
|
|
7
|
+
density?: 'comfortable' | 'touch';
|
|
6
8
|
/** Custom display name for the user, falls back to userName. */
|
|
7
9
|
userLabel?: string;
|
|
8
10
|
/** URL for the user's avatar image. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkspaceAccountMenu.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/WorkspaceAccountMenu.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAY,MAAM,wBAAwB,CAAC;AAI1E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAGzD,MAAM,WAAW,KAAK;IACpB,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,OAAO,CAAC,EAAE,sBAAsB,EAAE,CAAC;IACnC,2CAA2C;IAC3C,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,sEAAsE;IACtE,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,wCAAwC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uDAAuD;IACvD,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;
|
|
1
|
+
{"version":3,"file":"WorkspaceAccountMenu.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/WorkspaceAccountMenu.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAY,MAAM,wBAAwB,CAAC;AAI1E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAGzD,MAAM,WAAW,KAAK;IACpB,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC;IAClC,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,OAAO,CAAC,EAAE,sBAAsB,EAAE,CAAC;IACnC,2CAA2C;IAC3C,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,sEAAsE;IACtE,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,wCAAwC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uDAAuD;IACvD,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAsGD,QAAA,MAAM,oBAAoB,2CAAwC,CAAC;AACnE,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,oBAAoB,CAAC,CAAC;AACpE,eAAe,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type ScrollChromeOptions } from './mobile-shell.js';
|
|
2
|
+
import { type ShellState } from './state.svelte.js';
|
|
3
|
+
/**
|
|
4
|
+
* Keep `shell.viewport` in step with the phone/desktop media queries (which
|
|
5
|
+
* applies each edge's `viewportDefaults` when the class changes).
|
|
6
|
+
*/
|
|
7
|
+
export declare function installShellViewport(shell: ShellState): () => void;
|
|
8
|
+
/**
|
|
9
|
+
* Keep `shell.overlayMatches` in step with each side edge's `overlayMedia`
|
|
10
|
+
* query (edges without one are skipped).
|
|
11
|
+
*/
|
|
12
|
+
export declare function installShellOverlayMedia(shell: ShellState): () => void;
|
|
13
|
+
/**
|
|
14
|
+
* Hide-on-scroll for one scroller (AdminShell's main region). Samples once
|
|
15
|
+
* per animation frame and reports changes of the hidden state.
|
|
16
|
+
*/
|
|
17
|
+
export declare function installScrollChrome(scroller: HTMLElement, options: {
|
|
18
|
+
onChange: (hidden: boolean) => void;
|
|
19
|
+
isPinned: () => boolean;
|
|
20
|
+
tuning?: ScrollChromeOptions;
|
|
21
|
+
}): {
|
|
22
|
+
recheck: () => void;
|
|
23
|
+
reset: () => void;
|
|
24
|
+
destroy: () => void;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Watch for the on-screen keyboard. Sets `data-keyboard-open` on `<html>`
|
|
28
|
+
* (phone bars and `FormActionBar` hide on it) and keeps the focused field in
|
|
29
|
+
* view when the keyboard opens.
|
|
30
|
+
*/
|
|
31
|
+
export declare function installKeyboardWatcher(onChange: (open: boolean) => void): () => void;
|
|
32
|
+
/**
|
|
33
|
+
* Report whether `container` holds a form action bar
|
|
34
|
+
* (`[data-form-action-bar]`), now and whenever its subtree changes.
|
|
35
|
+
*/
|
|
36
|
+
export declare function watchFormActionBar(container: HTMLElement, onChange: (present: boolean) => void): () => void;
|
|
37
|
+
//# sourceMappingURL=mobile-shell-dom.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mobile-shell-dom.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/mobile-shell-dom.ts"],"names":[],"mappings":"AAOA,OAAO,EAIL,KAAK,mBAAmB,EAEzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAGL,KAAK,UAAU,EAChB,MAAM,mBAAmB,CAAC;AAE3B;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,IAAI,CAoBlE;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,IAAI,CAoBtE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,WAAW,EACrB,OAAO,EAAE;IACP,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACpC,QAAQ,EAAE,MAAM,OAAO,CAAC;IACxB,MAAM,CAAC,EAAE,mBAAmB,CAAC;CAC9B,GACA;IAAE,OAAO,EAAE,MAAM,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,CAqCjE;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,GAChC,MAAM,IAAI,CA6BZ;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,WAAW,EACtB,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GACnC,MAAM,IAAI,CAYZ"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM wiring for AdminShell's responsive chrome. Every installer returns a
|
|
3
|
+
* disposer and is a no-op without the browser API it needs, so callers can
|
|
4
|
+
* run them from `onMount` without guards. The decisions live in
|
|
5
|
+
* `./mobile-shell.ts`.
|
|
6
|
+
*/
|
|
7
|
+
import { isEditableTarget } from './hotkeys.js';
|
|
8
|
+
import { FORM_ACTION_BAR_SELECTOR, keyboardLikelyOpen, ScrollChrome, viewportFor, } from './mobile-shell.js';
|
|
9
|
+
import { ADMIN_SHELL_DESKTOP_QUERY, ADMIN_SHELL_PHONE_QUERY, } from './state.svelte.js';
|
|
10
|
+
/**
|
|
11
|
+
* Keep `shell.viewport` in step with the phone/desktop media queries (which
|
|
12
|
+
* applies each edge's `viewportDefaults` when the class changes).
|
|
13
|
+
*/
|
|
14
|
+
export function installShellViewport(shell) {
|
|
15
|
+
if (typeof window === 'undefined' ||
|
|
16
|
+
typeof window.matchMedia !== 'function') {
|
|
17
|
+
return () => { };
|
|
18
|
+
}
|
|
19
|
+
const phone = window.matchMedia(ADMIN_SHELL_PHONE_QUERY);
|
|
20
|
+
const desktop = window.matchMedia(ADMIN_SHELL_DESKTOP_QUERY);
|
|
21
|
+
const sync = () => shell.setViewport(viewportFor({ phone: phone.matches, desktop: desktop.matches }));
|
|
22
|
+
sync();
|
|
23
|
+
phone.addEventListener('change', sync);
|
|
24
|
+
desktop.addEventListener('change', sync);
|
|
25
|
+
return () => {
|
|
26
|
+
phone.removeEventListener('change', sync);
|
|
27
|
+
desktop.removeEventListener('change', sync);
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Keep `shell.overlayMatches` in step with each side edge's `overlayMedia`
|
|
32
|
+
* query (edges without one are skipped).
|
|
33
|
+
*/
|
|
34
|
+
export function installShellOverlayMedia(shell) {
|
|
35
|
+
if (typeof window === 'undefined' ||
|
|
36
|
+
typeof window.matchMedia !== 'function') {
|
|
37
|
+
return () => { };
|
|
38
|
+
}
|
|
39
|
+
const disposers = [];
|
|
40
|
+
for (const edge of ['left', 'right']) {
|
|
41
|
+
const query = shell.config.panels[edge].overlayMedia;
|
|
42
|
+
if (!query)
|
|
43
|
+
continue;
|
|
44
|
+
const media = window.matchMedia(query);
|
|
45
|
+
const sync = () => shell.setOverlayMatch(edge, media.matches);
|
|
46
|
+
sync();
|
|
47
|
+
media.addEventListener('change', sync);
|
|
48
|
+
disposers.push(() => media.removeEventListener('change', sync));
|
|
49
|
+
}
|
|
50
|
+
return () => {
|
|
51
|
+
for (const dispose of disposers)
|
|
52
|
+
dispose();
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Hide-on-scroll for one scroller (AdminShell's main region). Samples once
|
|
57
|
+
* per animation frame and reports changes of the hidden state.
|
|
58
|
+
*/
|
|
59
|
+
export function installScrollChrome(scroller, options) {
|
|
60
|
+
const chrome = new ScrollChrome(options.tuning);
|
|
61
|
+
let frame = 0;
|
|
62
|
+
let last = false;
|
|
63
|
+
function sample() {
|
|
64
|
+
frame = 0;
|
|
65
|
+
const hidden = chrome.update({
|
|
66
|
+
y: scroller.scrollTop,
|
|
67
|
+
maxY: scroller.scrollHeight - scroller.clientHeight,
|
|
68
|
+
pinned: options.isPinned(),
|
|
69
|
+
});
|
|
70
|
+
if (hidden !== last) {
|
|
71
|
+
last = hidden;
|
|
72
|
+
options.onChange(hidden);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function onScroll() {
|
|
76
|
+
if (!frame)
|
|
77
|
+
frame = requestAnimationFrame(sample);
|
|
78
|
+
}
|
|
79
|
+
scroller.addEventListener('scroll', onScroll, { passive: true });
|
|
80
|
+
return {
|
|
81
|
+
recheck: sample,
|
|
82
|
+
reset() {
|
|
83
|
+
chrome.reset();
|
|
84
|
+
if (last) {
|
|
85
|
+
last = false;
|
|
86
|
+
options.onChange(false);
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
destroy() {
|
|
90
|
+
if (frame)
|
|
91
|
+
cancelAnimationFrame(frame);
|
|
92
|
+
scroller.removeEventListener('scroll', onScroll);
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Watch for the on-screen keyboard. Sets `data-keyboard-open` on `<html>`
|
|
98
|
+
* (phone bars and `FormActionBar` hide on it) and keeps the focused field in
|
|
99
|
+
* view when the keyboard opens.
|
|
100
|
+
*/
|
|
101
|
+
export function installKeyboardWatcher(onChange) {
|
|
102
|
+
if (typeof window === 'undefined' || !window.visualViewport) {
|
|
103
|
+
return () => { };
|
|
104
|
+
}
|
|
105
|
+
const viewport = window.visualViewport;
|
|
106
|
+
let open = false;
|
|
107
|
+
function sync() {
|
|
108
|
+
const next = keyboardLikelyOpen({
|
|
109
|
+
layoutHeight: window.innerHeight,
|
|
110
|
+
visualHeight: viewport.height,
|
|
111
|
+
scale: viewport.scale,
|
|
112
|
+
});
|
|
113
|
+
if (next === open)
|
|
114
|
+
return;
|
|
115
|
+
open = next;
|
|
116
|
+
if (open)
|
|
117
|
+
document.documentElement.dataset.keyboardOpen = '';
|
|
118
|
+
else
|
|
119
|
+
delete document.documentElement.dataset.keyboardOpen;
|
|
120
|
+
onChange(open);
|
|
121
|
+
const active = document.activeElement;
|
|
122
|
+
if (open && active instanceof HTMLElement && isEditableTarget(active)) {
|
|
123
|
+
active.scrollIntoView({ block: 'center' });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
viewport.addEventListener('resize', sync);
|
|
127
|
+
return () => {
|
|
128
|
+
viewport.removeEventListener('resize', sync);
|
|
129
|
+
delete document.documentElement.dataset.keyboardOpen;
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Report whether `container` holds a form action bar
|
|
134
|
+
* (`[data-form-action-bar]`), now and whenever its subtree changes.
|
|
135
|
+
*/
|
|
136
|
+
export function watchFormActionBar(container, onChange) {
|
|
137
|
+
let present = Boolean(container.querySelector(FORM_ACTION_BAR_SELECTOR));
|
|
138
|
+
onChange(present);
|
|
139
|
+
if (typeof MutationObserver === 'undefined')
|
|
140
|
+
return () => { };
|
|
141
|
+
const observer = new MutationObserver(() => {
|
|
142
|
+
const next = Boolean(container.querySelector(FORM_ACTION_BAR_SELECTOR));
|
|
143
|
+
if (next === present)
|
|
144
|
+
return;
|
|
145
|
+
present = next;
|
|
146
|
+
onChange(present);
|
|
147
|
+
});
|
|
148
|
+
observer.observe(container, { childList: true, subtree: true });
|
|
149
|
+
return () => observer.disconnect();
|
|
150
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure logic behind AdminShell's phone chrome: the hide-on-scroll state
|
|
3
|
+
* machine, the on-screen keyboard heuristic, the phone top-bar model, and the
|
|
4
|
+
* bottom-bar mode. DOM-free and SSR-safe so it can be unit tested; the DOM
|
|
5
|
+
* wiring lives in `./mobile-shell-dom.ts`.
|
|
6
|
+
*/
|
|
7
|
+
import type { Component, ComponentType, SvelteComponent } from 'svelte';
|
|
8
|
+
import type { ShellViewport } from './types.js';
|
|
9
|
+
/** Classify a viewport from the phone and desktop media-query matches. */
|
|
10
|
+
export declare function viewportFor(matches: {
|
|
11
|
+
phone: boolean;
|
|
12
|
+
desktop: boolean;
|
|
13
|
+
}): ShellViewport;
|
|
14
|
+
/** Tuning for {@link ScrollChrome}. */
|
|
15
|
+
export interface ScrollChromeOptions {
|
|
16
|
+
/** Always show the chrome within this many px of the top (default 56). */
|
|
17
|
+
revealWithin?: number;
|
|
18
|
+
/** Downward travel in px before the chrome hides (default 12). */
|
|
19
|
+
hideAfter?: number;
|
|
20
|
+
/** Upward travel in px before it comes back (default 4, "first scroll up"). */
|
|
21
|
+
showAfter?: number;
|
|
22
|
+
}
|
|
23
|
+
/** One scroll position fed to {@link ScrollChrome.update}. */
|
|
24
|
+
export interface ScrollSample {
|
|
25
|
+
/** Current scrollTop. */
|
|
26
|
+
y: number;
|
|
27
|
+
/** Largest reachable scrollTop, to ignore iOS overscroll bounce. */
|
|
28
|
+
maxY?: number;
|
|
29
|
+
/** Keep the chrome shown (a banner is visible, a drawer is open, …). */
|
|
30
|
+
pinned?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Hide-on-scroll state machine for the phone top bar (and anything that
|
|
34
|
+
* slides with it). Feed it every scroll position; read `hidden`. Travel
|
|
35
|
+
* accumulates in one direction and resets when the direction reverses.
|
|
36
|
+
*/
|
|
37
|
+
export declare class ScrollChrome {
|
|
38
|
+
#private;
|
|
39
|
+
hidden: boolean;
|
|
40
|
+
constructor(options?: ScrollChromeOptions);
|
|
41
|
+
/** Feed a scroll sample; returns whether the chrome is now hidden. */
|
|
42
|
+
update(sample: ScrollSample): boolean;
|
|
43
|
+
/** Show the chrome and forget the scroll history (e.g. after navigating). */
|
|
44
|
+
reset(): void;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* `visualViewport` heuristic: the keyboard is open when the visible viewport
|
|
48
|
+
* is much shorter than the layout viewport. `scale` undoes pinch zoom, which
|
|
49
|
+
* also shrinks `visualViewport.height`.
|
|
50
|
+
*/
|
|
51
|
+
export declare function keyboardLikelyOpen(input: {
|
|
52
|
+
layoutHeight: number;
|
|
53
|
+
visualHeight: number;
|
|
54
|
+
scale?: number;
|
|
55
|
+
threshold?: number;
|
|
56
|
+
}): boolean;
|
|
57
|
+
/** A nav entry as far as path matching is concerned. */
|
|
58
|
+
export interface ShellNavPathItem {
|
|
59
|
+
href: string;
|
|
60
|
+
exact?: boolean;
|
|
61
|
+
children?: ShellNavPathItem[];
|
|
62
|
+
}
|
|
63
|
+
/** Drop trailing slashes (`/` stays `/`). */
|
|
64
|
+
export declare function normalizeShellPath(path: string): string;
|
|
65
|
+
/** Whether `path` is the item's page or (unless `exact`) below it. */
|
|
66
|
+
export declare function shellNavItemMatches(item: ShellNavPathItem, path: string): boolean;
|
|
67
|
+
/** The chain of nav items from the top level down to the one matching `path`. */
|
|
68
|
+
export declare function findShellNavTrail<T extends ShellNavPathItem>(items: T[], path: string): T[];
|
|
69
|
+
/** One ancestor of the current page: a breadcrumb and a possible back target. */
|
|
70
|
+
export interface ShellCrumb {
|
|
71
|
+
label: string;
|
|
72
|
+
href: string;
|
|
73
|
+
}
|
|
74
|
+
/** Input for {@link shellPageTrailFor}. */
|
|
75
|
+
export interface ShellPageTrailInput<T extends ShellNavPathItem & {
|
|
76
|
+
label: string;
|
|
77
|
+
}> {
|
|
78
|
+
/** Current pathname. */
|
|
79
|
+
path: string;
|
|
80
|
+
/** The workspace home (overview) page. */
|
|
81
|
+
homeHref: string;
|
|
82
|
+
/** Workspace name: the first ancestor of every non-home page. */
|
|
83
|
+
homeTitle: string;
|
|
84
|
+
/** The workspace nav. */
|
|
85
|
+
navItems: T[];
|
|
86
|
+
/**
|
|
87
|
+
* Ancestors deeper than the nav knows (an article above its video, a
|
|
88
|
+
* council above its meeting), nearest last. They follow the nav trail.
|
|
89
|
+
*/
|
|
90
|
+
parents?: readonly ShellCrumb[] | null;
|
|
91
|
+
}
|
|
92
|
+
/** Where a page sits: a section home, or a page with ancestors. */
|
|
93
|
+
export interface ShellPageTrail {
|
|
94
|
+
/** The home page or a top-level nav page (and no page-given parents). */
|
|
95
|
+
sectionHome: boolean;
|
|
96
|
+
/** Ancestors only, never the current page; empty on section homes. */
|
|
97
|
+
crumbs: ShellCrumb[];
|
|
98
|
+
/** The nav item for the current page itself, if it is one. */
|
|
99
|
+
current: {
|
|
100
|
+
label: string;
|
|
101
|
+
href: string;
|
|
102
|
+
} | null;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The single source for a page's place in the workspace: breadcrumbs on
|
|
106
|
+
* wider screens and the phone top bar's back target both come from here.
|
|
107
|
+
*
|
|
108
|
+
* Section homes (the home page and top-level nav pages) have no ancestors.
|
|
109
|
+
* Anything else lists the workspace home, the nav items above it (a nested
|
|
110
|
+
* nav page's parent item counts, even when its URL is not below it), then the
|
|
111
|
+
* page-given `parents`. The current page is never a crumb.
|
|
112
|
+
*/
|
|
113
|
+
export declare function shellPageTrailFor<T extends ShellNavPathItem & {
|
|
114
|
+
label: string;
|
|
115
|
+
}>(input: ShellPageTrailInput<T>): ShellPageTrail;
|
|
116
|
+
/**
|
|
117
|
+
* What the phone top bar shows: the workspace name on section home pages, or
|
|
118
|
+
* a back arrow (to the parent, never `history.back()`) plus the page's
|
|
119
|
+
* title on every other page.
|
|
120
|
+
*/
|
|
121
|
+
export type PhoneTopBarModel = {
|
|
122
|
+
kind: 'home';
|
|
123
|
+
title: string;
|
|
124
|
+
} | {
|
|
125
|
+
kind: 'detail';
|
|
126
|
+
title: string;
|
|
127
|
+
backHref: string;
|
|
128
|
+
backLabel: string;
|
|
129
|
+
};
|
|
130
|
+
/** Input for {@link phoneTopBarFor}. */
|
|
131
|
+
export interface PhoneTopBarInput<T extends ShellNavPathItem & {
|
|
132
|
+
label: string;
|
|
133
|
+
}> extends ShellPageTrailInput<T> {
|
|
134
|
+
/** Title the page provided (article headline, person's name, …). */
|
|
135
|
+
pageTitle?: string | null;
|
|
136
|
+
/**
|
|
137
|
+
* Explicit back target. Prefer `parents` (the same ancestors the page's
|
|
138
|
+
* breadcrumbs show); this overrides the last one.
|
|
139
|
+
*/
|
|
140
|
+
backHref?: string | null;
|
|
141
|
+
/** Back label used with an explicit `backHref` (default "Back"). */
|
|
142
|
+
backLabel?: string;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Section homes (see {@link shellPageTrailFor}) show the workspace name.
|
|
146
|
+
* Every other page shows a back arrow to its nearest ancestor (the last
|
|
147
|
+
* breadcrumb) and the page's title.
|
|
148
|
+
*/
|
|
149
|
+
export declare function phoneTopBarFor<T extends ShellNavPathItem & {
|
|
150
|
+
label: string;
|
|
151
|
+
}>(input: PhoneTopBarInput<T>): PhoneTopBarModel;
|
|
152
|
+
/**
|
|
153
|
+
* What sits at the bottom of a phone screen: the nav bar, a form's own
|
|
154
|
+
* action bar (which replaces it), or nothing while the keyboard is open.
|
|
155
|
+
*/
|
|
156
|
+
export type BottomBarMode = 'nav' | 'form' | 'none';
|
|
157
|
+
/** Decide the {@link BottomBarMode}. */
|
|
158
|
+
export declare function bottomBarMode(input: {
|
|
159
|
+
formActions: boolean;
|
|
160
|
+
keyboardOpen: boolean;
|
|
161
|
+
}): BottomBarMode;
|
|
162
|
+
/** Selector a form's action row carries to replace the phone bottom bar. */
|
|
163
|
+
export declare const FORM_ACTION_BAR_SELECTOR = "[data-form-action-bar]";
|
|
164
|
+
/**
|
|
165
|
+
* An icon component: a Svelte 5 function component or a legacy class
|
|
166
|
+
* component (e.g. lucide-svelte icons).
|
|
167
|
+
*/
|
|
168
|
+
export type ShellIcon = Component<{
|
|
169
|
+
size?: number | string;
|
|
170
|
+
}> | ComponentType<SvelteComponent<{
|
|
171
|
+
size?: number | string;
|
|
172
|
+
}>>;
|
|
173
|
+
/** One phone bottom bar entry: a link (`href`; `onclick` also runs) or a button. */
|
|
174
|
+
export interface PhoneBottomBarItem {
|
|
175
|
+
id: string;
|
|
176
|
+
label: string;
|
|
177
|
+
icon: ShellIcon;
|
|
178
|
+
href?: string;
|
|
179
|
+
onclick?: () => void;
|
|
180
|
+
active?: boolean;
|
|
181
|
+
/** Count badge (hidden at 0). */
|
|
182
|
+
badge?: number;
|
|
183
|
+
/** Small dot, e.g. an unread reply. */
|
|
184
|
+
dot?: boolean;
|
|
185
|
+
/** Accessible description of the dot (default "new"). */
|
|
186
|
+
dotLabel?: string;
|
|
187
|
+
/** For items that open a panel: its expanded state and id. */
|
|
188
|
+
expanded?: boolean;
|
|
189
|
+
controls?: string;
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=mobile-shell.d.ts.map
|