@happyvertical/smrt-svelte 0.55.1 → 0.55.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -2
- package/agents/components.md +44 -1
- package/agents/workspace.md +77 -2
- package/dist/components/app/AppShell.svelte +121 -9
- package/dist/components/app/AppShell.svelte.d.ts +24 -2
- package/dist/components/app/AppShell.svelte.d.ts.map +1 -1
- package/dist/components/app/DockToggles.svelte +103 -0
- package/dist/components/app/DockToggles.svelte.d.ts +8 -0
- package/dist/components/app/DockToggles.svelte.d.ts.map +1 -0
- package/dist/components/app/dock-toggle.d.ts +14 -0
- package/dist/components/app/dock-toggle.d.ts.map +1 -0
- package/dist/components/app/dock-toggle.js +1 -0
- package/dist/components/app/index.d.ts +2 -0
- package/dist/components/app/index.d.ts.map +1 -1
- package/dist/components/app/index.js +1 -0
- package/dist/components/board/Board.svelte +92 -394
- package/dist/components/board/Board.svelte.d.ts.map +1 -1
- package/dist/components/forms/RelationInput.svelte +379 -0
- package/dist/components/forms/RelationInput.svelte.d.ts +75 -0
- package/dist/components/forms/RelationInput.svelte.d.ts.map +1 -0
- package/dist/components/forms/index.d.ts +2 -1
- package/dist/components/forms/index.d.ts.map +1 -1
- package/dist/components/forms/index.js +1 -0
- package/dist/components/forms/types.d.ts +9 -0
- package/dist/components/forms/types.d.ts.map +1 -1
- package/dist/components/sortable/Sortable.svelte +359 -0
- package/dist/components/sortable/Sortable.svelte.d.ts +26 -0
- package/dist/components/sortable/Sortable.svelte.d.ts.map +1 -0
- package/dist/components/sortable/controller.svelte.d.ts +152 -0
- package/dist/components/sortable/controller.svelte.d.ts.map +1 -0
- package/dist/components/sortable/controller.svelte.js +453 -0
- package/dist/components/sortable/index.d.ts +5 -0
- package/dist/components/sortable/index.d.ts.map +1 -0
- package/dist/components/sortable/index.js +2 -0
- package/dist/components/sortable/types.d.ts +80 -0
- package/dist/components/sortable/types.d.ts.map +1 -0
- package/dist/components/sortable/types.js +1 -0
- package/dist/components/workspace/admin-shell/AdminShell.svelte +49 -1
- package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/ShellLayoutEditor.svelte +196 -0
- package/dist/components/workspace/admin-shell/ShellLayoutEditor.svelte.d.ts +17 -0
- package/dist/components/workspace/admin-shell/ShellLayoutEditor.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/TenantNav.svelte +69 -3
- package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/dock.d.ts +37 -0
- package/dist/components/workspace/admin-shell/dock.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/dock.js +25 -0
- package/dist/components/workspace/admin-shell/layout-context.d.ts +7 -0
- package/dist/components/workspace/admin-shell/layout-context.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/layout-context.js +17 -0
- package/dist/components/workspace/admin-shell/layout-controller.svelte.d.ts +69 -0
- package/dist/components/workspace/admin-shell/layout-controller.svelte.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/layout-controller.svelte.js +91 -0
- package/dist/components/workspace/admin-shell/layout.d.ts +120 -0
- package/dist/components/workspace/admin-shell/layout.d.ts.map +1 -0
- package/dist/components/workspace/admin-shell/layout.js +402 -0
- package/dist/components/workspace/admin-shell/settings.d.ts +6 -4
- package/dist/components/workspace/admin-shell/settings.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/settings.js +14 -4
- package/dist/components/workspace/admin-shell/state.svelte.d.ts +59 -1
- package/dist/components/workspace/admin-shell/state.svelte.d.ts.map +1 -1
- package/dist/components/workspace/admin-shell/state.svelte.js +146 -2
- package/dist/components/workspace/admin-shell/types.d.ts +52 -0
- package/dist/components/workspace/admin-shell/types.d.ts.map +1 -1
- package/dist/components/workspace/index.d.ts +6 -1
- package/dist/components/workspace/index.d.ts.map +1 -1
- package/dist/components/workspace/index.js +5 -0
- package/dist/i18n/server.d.ts +1 -0
- package/dist/i18n/server.d.ts.map +1 -1
- package/dist/i18n/server.js +1 -0
- package/dist/i18n/strings.sortable.d.ts +13 -0
- package/dist/i18n/strings.sortable.d.ts.map +1 -0
- package/dist/i18n/strings.sortable.js +13 -0
- package/dist/i18n/strings.workspace.d.ts +13 -0
- package/dist/i18n/strings.workspace.d.ts.map +1 -1
- package/dist/i18n/strings.workspace.js +13 -0
- package/package.json +26 -15
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Switch } from '@happyvertical/smrt-ui/forms';
|
|
3
|
+
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
4
|
+
import { Button } from '@happyvertical/smrt-ui/ui';
|
|
5
|
+
import { type Component, untrack } from 'svelte';
|
|
6
|
+
import { M } from '../../../i18n/strings.workspace.js';
|
|
7
|
+
import Sortable from '../../sortable/Sortable.svelte';
|
|
8
|
+
import type {
|
|
9
|
+
SortableContainer,
|
|
10
|
+
SortableContainerMove,
|
|
11
|
+
SortableItem,
|
|
12
|
+
SortableItemMove,
|
|
13
|
+
} from '../../sortable/types.js';
|
|
14
|
+
import { useShellLayout } from './layout-context.js';
|
|
15
|
+
import type { ShellLayoutController } from './layout-controller.svelte.js';
|
|
16
|
+
import TenantNav from './TenantNav.svelte';
|
|
17
|
+
|
|
18
|
+
interface Props {
|
|
19
|
+
/** The layout API to edit; defaults to the nearest `AppShell`'s. */
|
|
20
|
+
controller?: ShellLayoutController;
|
|
21
|
+
/** Show a live preview of the resulting navigation (default true). */
|
|
22
|
+
preview?: boolean;
|
|
23
|
+
/** Icon renderer for the preview, as for `TenantNav`. */
|
|
24
|
+
iconComponent?: Component<{ name: string; size?: number }>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let { controller: provided, preview = true, iconComponent }: Props = $props();
|
|
28
|
+
|
|
29
|
+
const { t } = useI18n();
|
|
30
|
+
const layout = untrack(() => provided ?? useShellLayout());
|
|
31
|
+
const instanceId = $props.id();
|
|
32
|
+
const headingId = `${instanceId}-heading`;
|
|
33
|
+
|
|
34
|
+
const sections = $derived(layout.sections);
|
|
35
|
+
const panels = $derived(layout.panels);
|
|
36
|
+
const applied = $derived(layout.applied);
|
|
37
|
+
|
|
38
|
+
interface NavContainer extends SortableContainer {
|
|
39
|
+
hidden: boolean;
|
|
40
|
+
root: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const containers = $derived<NavContainer[]>(
|
|
44
|
+
sections.map((section) => ({
|
|
45
|
+
id: section.id,
|
|
46
|
+
label: section.heading ?? t(M['ui.shell_layout_editor.top_level']),
|
|
47
|
+
fixed: section.group === null,
|
|
48
|
+
root: section.group === null,
|
|
49
|
+
hidden: section.hidden,
|
|
50
|
+
})),
|
|
51
|
+
);
|
|
52
|
+
const items = $derived<SortableItem[]>(
|
|
53
|
+
sections.flatMap((section) =>
|
|
54
|
+
section.items.map((entry) => ({
|
|
55
|
+
id: entry.id,
|
|
56
|
+
containerId: section.id,
|
|
57
|
+
label: entry.item.label,
|
|
58
|
+
})),
|
|
59
|
+
),
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
function setShown(id: string, shown: boolean): void {
|
|
63
|
+
if (shown) layout.show(id);
|
|
64
|
+
else layout.hide(id);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function onmove(move: SortableItemMove<SortableItem, NavContainer>): void {
|
|
68
|
+
layout.moveItem(move.item.id, move.target.containerId, move.target.index);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function oncontainermove(move: SortableContainerMove<NavContainer>): void {
|
|
72
|
+
layout.moveSection(move.container.id, move.target);
|
|
73
|
+
}
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
<section class="smrt-shell-layout-editor" aria-labelledby={headingId}>
|
|
77
|
+
<header>
|
|
78
|
+
<div>
|
|
79
|
+
<h2 id={headingId}>{t(M['ui.shell_layout_editor.heading'])}</h2>
|
|
80
|
+
<p>{t(M['ui.shell_layout_editor.description'])}</p>
|
|
81
|
+
</div>
|
|
82
|
+
<Button
|
|
83
|
+
variant="secondary"
|
|
84
|
+
size="sm"
|
|
85
|
+
disabled={!layout.customized}
|
|
86
|
+
onclick={() => layout.reset()}
|
|
87
|
+
>
|
|
88
|
+
{t(M['ui.shell_layout_editor.reset'])}
|
|
89
|
+
</Button>
|
|
90
|
+
</header>
|
|
91
|
+
|
|
92
|
+
<fieldset class="smrt-shell-layout-editor__panels">
|
|
93
|
+
<legend>{t(M['ui.shell_layout_editor.panels'])}</legend>
|
|
94
|
+
{#each panels as panel (panel.edge)}
|
|
95
|
+
<div class="smrt-shell-layout-editor__row" data-edge={panel.edge}>
|
|
96
|
+
{#if panel.available}
|
|
97
|
+
<Switch
|
|
98
|
+
interaction={false}
|
|
99
|
+
checked={panel.visible}
|
|
100
|
+
label={t(M['ui.shell_layout_editor.show_panel'], { panel: panel.label })}
|
|
101
|
+
onchange={(event) =>
|
|
102
|
+
layout.setPanel(panel.edge, { visible: event.currentTarget.checked })}
|
|
103
|
+
/>
|
|
104
|
+
<Switch
|
|
105
|
+
interaction={false}
|
|
106
|
+
checked={panel.initial === 'expanded'}
|
|
107
|
+
disabled={!panel.visible}
|
|
108
|
+
label={t(M['ui.shell_layout_editor.start_expanded'], { panel: panel.label })}
|
|
109
|
+
onchange={(event) =>
|
|
110
|
+
layout.setPanel(panel.edge, {
|
|
111
|
+
initial: event.currentTarget.checked ? 'expanded' : 'collapsed',
|
|
112
|
+
})}
|
|
113
|
+
/>
|
|
114
|
+
{:else}
|
|
115
|
+
<span class="smrt-shell-layout-editor__muted">
|
|
116
|
+
{t(M['ui.shell_layout_editor.panel_unavailable'], { panel: panel.label })}
|
|
117
|
+
</span>
|
|
118
|
+
{/if}
|
|
119
|
+
</div>
|
|
120
|
+
{/each}
|
|
121
|
+
</fieldset>
|
|
122
|
+
|
|
123
|
+
<div class="smrt-shell-layout-editor__body">
|
|
124
|
+
<div class="smrt-shell-layout-editor__nav">
|
|
125
|
+
<h3>{t(M['ui.shell_layout_editor.navigation'])}</h3>
|
|
126
|
+
<Sortable
|
|
127
|
+
{containers}
|
|
128
|
+
{items}
|
|
129
|
+
label={t(M['ui.shell_layout_editor.sortable_label'])}
|
|
130
|
+
reorderContainers={true}
|
|
131
|
+
{onmove}
|
|
132
|
+
{oncontainermove}
|
|
133
|
+
>
|
|
134
|
+
{#snippet containerHeader({ container })}
|
|
135
|
+
{@const entry = containers.find((candidate) => candidate.id === container.id)}
|
|
136
|
+
<strong class:smrt-shell-layout-editor__muted={entry?.hidden}>{container.label}</strong>
|
|
137
|
+
{#if !entry?.root}
|
|
138
|
+
<Switch
|
|
139
|
+
interaction={false}
|
|
140
|
+
checked={!entry?.hidden}
|
|
141
|
+
aria-label={t(M['ui.shell_layout_editor.show_entry'], { label: container.label })}
|
|
142
|
+
onchange={(event) => setShown(container.id, event.currentTarget.checked)}
|
|
143
|
+
/>
|
|
144
|
+
{/if}
|
|
145
|
+
{/snippet}
|
|
146
|
+
{#snippet item({ item: entry, container })}
|
|
147
|
+
{@const hidden = layout.isHidden(entry.id)}
|
|
148
|
+
<span class="smrt-shell-layout-editor__label" class:smrt-shell-layout-editor__muted={hidden || layout.isHidden(container.id)}>{entry.label}</span>
|
|
149
|
+
<Switch
|
|
150
|
+
interaction={false}
|
|
151
|
+
checked={!hidden}
|
|
152
|
+
aria-label={t(M['ui.shell_layout_editor.show_entry'], { label: entry.label })}
|
|
153
|
+
onchange={(event) => setShown(entry.id, event.currentTarget.checked)}
|
|
154
|
+
/>
|
|
155
|
+
{/snippet}
|
|
156
|
+
</Sortable>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
{#if preview}
|
|
160
|
+
<div class="smrt-shell-layout-editor__preview">
|
|
161
|
+
<h3>{t(M['ui.shell_layout_editor.preview'])}</h3>
|
|
162
|
+
<div
|
|
163
|
+
class="smrt-shell-layout-editor__preview-frame"
|
|
164
|
+
role="group"
|
|
165
|
+
aria-label={t(M['ui.shell_layout_editor.preview_label'])}
|
|
166
|
+
inert
|
|
167
|
+
>
|
|
168
|
+
<TenantNav
|
|
169
|
+
items={applied.nav}
|
|
170
|
+
groups={applied.groups}
|
|
171
|
+
{iconComponent}
|
|
172
|
+
aria-label={t(M['ui.shell_layout_editor.preview_label'])}
|
|
173
|
+
/>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
{/if}
|
|
177
|
+
</div>
|
|
178
|
+
</section>
|
|
179
|
+
|
|
180
|
+
<style>
|
|
181
|
+
.smrt-shell-layout-editor { display: grid; gap: var(--smrt-spacing-4); min-inline-size: 0; overflow-wrap: anywhere; }
|
|
182
|
+
.smrt-shell-layout-editor header { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--smrt-spacing-3); }
|
|
183
|
+
.smrt-shell-layout-editor h2, .smrt-shell-layout-editor h3, .smrt-shell-layout-editor p { margin: 0; }
|
|
184
|
+
.smrt-shell-layout-editor p, .smrt-shell-layout-editor__muted { color: var(--smrt-color-on-surface-variant); }
|
|
185
|
+
.smrt-shell-layout-editor h3 { font: var(--smrt-typography-title-small-font); margin-block-end: var(--smrt-spacing-2); }
|
|
186
|
+
.smrt-shell-layout-editor__panels { display: grid; gap: var(--smrt-spacing-2); margin: 0; padding: var(--smrt-spacing-3); border: 1px solid var(--smrt-color-outline-variant); border-radius: var(--smrt-radius-md); }
|
|
187
|
+
.smrt-shell-layout-editor__panels legend { padding-inline: var(--smrt-spacing-1); font: var(--smrt-typography-title-small-font); }
|
|
188
|
+
.smrt-shell-layout-editor__row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--smrt-spacing-4); }
|
|
189
|
+
.smrt-shell-layout-editor__body { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); gap: var(--smrt-spacing-4); align-items: start; }
|
|
190
|
+
.smrt-shell-layout-editor__label { flex: 1 1 auto; min-inline-size: 0; }
|
|
191
|
+
.smrt-shell-layout-editor__preview-frame { padding: var(--smrt-spacing-3); border: 1px dashed var(--smrt-color-outline-variant); border-radius: var(--smrt-radius-md); background: var(--smrt-color-surface-container-low, var(--smrt-color-surface)); }
|
|
192
|
+
@media (max-width: 48rem) {
|
|
193
|
+
.smrt-shell-layout-editor header { flex-direction: column; }
|
|
194
|
+
.smrt-shell-layout-editor__body { grid-template-columns: minmax(0, 1fr); }
|
|
195
|
+
}
|
|
196
|
+
</style>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Component } from 'svelte';
|
|
2
|
+
import type { ShellLayoutController } from './layout-controller.svelte.js';
|
|
3
|
+
interface Props {
|
|
4
|
+
/** The layout API to edit; defaults to the nearest `AppShell`'s. */
|
|
5
|
+
controller?: ShellLayoutController;
|
|
6
|
+
/** Show a live preview of the resulting navigation (default true). */
|
|
7
|
+
preview?: boolean;
|
|
8
|
+
/** Icon renderer for the preview, as for `TenantNav`. */
|
|
9
|
+
iconComponent?: Component<{
|
|
10
|
+
name: string;
|
|
11
|
+
size?: number;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
declare const ShellLayoutEditor: Component<Props, {}, "">;
|
|
15
|
+
type ShellLayoutEditor = ReturnType<typeof ShellLayoutEditor>;
|
|
16
|
+
export default ShellLayoutEditor;
|
|
17
|
+
//# sourceMappingURL=ShellLayoutEditor.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ShellLayoutEditor.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/ShellLayoutEditor.svelte.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,SAAS,EAAW,MAAM,QAAQ,CAAC;AAUjD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAI3E,UAAU,KAAK;IACb,oEAAoE;IACpE,UAAU,CAAC,EAAE,qBAAqB,CAAC;IACnC,sEAAsE;IACtE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yDAAyD;IACzD,aAAa,CAAC,EAAE,SAAS,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC5D;AAiID,QAAA,MAAM,iBAAiB,0BAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
|
|
@@ -4,6 +4,9 @@ import type { Component } from 'svelte';
|
|
|
4
4
|
import { M } from '../../../i18n/strings.workspace.js';
|
|
5
5
|
import type { ShellNavGroup, ShellNavItem } from './types.js';
|
|
6
6
|
|
|
7
|
+
const DEFAULT_ACTION_ICON = 'settings';
|
|
8
|
+
const DEFAULT_ACTION_GLYPH = '\u2699';
|
|
9
|
+
|
|
7
10
|
interface Props {
|
|
8
11
|
/** Navigation items with href, label, icon, and optional children. */
|
|
9
12
|
items?: ShellNavItem[];
|
|
@@ -39,9 +42,27 @@ function isActive(item: ShellNavItem): boolean {
|
|
|
39
42
|
return item.href === currentHref || currentHref.startsWith(`${item.href}/`);
|
|
40
43
|
}
|
|
41
44
|
|
|
45
|
+
/** True when the item, any child, or its action link is the current page. */
|
|
46
|
+
function isItemCurrent(item: ShellNavItem): boolean {
|
|
47
|
+
return (
|
|
48
|
+
isActive(item) ||
|
|
49
|
+
!!item.children?.some((child) => isActive(child)) ||
|
|
50
|
+
(!!item.action && isActive(item.action))
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isGroupCurrent(group: ShellNavGroup): boolean {
|
|
55
|
+
return group.items.some(isItemCurrent);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function showAction(item: ShellNavItem, sectionCurrent: boolean): boolean {
|
|
59
|
+
if (collapsed || !item.action) return false;
|
|
60
|
+
return item.action.visibility !== 'active' || sectionCurrent;
|
|
61
|
+
}
|
|
62
|
+
|
|
42
63
|
function isVisibleActive(item: ShellNavItem): boolean {
|
|
43
64
|
if (isActive(item)) return true;
|
|
44
|
-
return collapsed &&
|
|
65
|
+
return collapsed && isItemCurrent(item);
|
|
45
66
|
}
|
|
46
67
|
|
|
47
68
|
/** Accessible label of an item's attention dot, or null when it has none. */
|
|
@@ -69,9 +90,13 @@ function fallbackIcon(label: string): string {
|
|
|
69
90
|
{/if}
|
|
70
91
|
{/snippet}
|
|
71
92
|
|
|
72
|
-
{#snippet navItems(entries: ShellNavItem[])}
|
|
93
|
+
{#snippet navItems(entries: ShellNavItem[], groupCurrent?: boolean)}
|
|
73
94
|
{#each entries as item (item.href)}
|
|
95
|
+
{@const action = showAction(item, groupCurrent ?? isItemCurrent(item))
|
|
96
|
+
? item.action
|
|
97
|
+
: undefined}
|
|
74
98
|
<div class="smrt-tenant-nav__section">
|
|
99
|
+
<div class="smrt-tenant-nav__row">
|
|
75
100
|
<a
|
|
76
101
|
href={item.href}
|
|
77
102
|
class:smrt-tenant-nav__link--visible-active={isVisibleActive(item)}
|
|
@@ -105,6 +130,25 @@ function fallbackIcon(label: string): string {
|
|
|
105
130
|
{/if}
|
|
106
131
|
{@render attention(item)}
|
|
107
132
|
</a>
|
|
133
|
+
{#if action}
|
|
134
|
+
<a
|
|
135
|
+
href={action.href}
|
|
136
|
+
class="smrt-tenant-nav__action"
|
|
137
|
+
aria-label={action.label}
|
|
138
|
+
aria-current={isActive(action) ? 'page' : undefined}
|
|
139
|
+
title={action.label}
|
|
140
|
+
onclick={onNavigate}
|
|
141
|
+
>
|
|
142
|
+
<span class="smrt-tenant-nav__icon" aria-hidden="true">
|
|
143
|
+
{#if IconComponent}
|
|
144
|
+
<IconComponent name={action.icon ?? DEFAULT_ACTION_ICON} size={16} />
|
|
145
|
+
{:else}
|
|
146
|
+
{action.icon ?? DEFAULT_ACTION_GLYPH}
|
|
147
|
+
{/if}
|
|
148
|
+
</span>
|
|
149
|
+
</a>
|
|
150
|
+
{/if}
|
|
151
|
+
</div>
|
|
108
152
|
{#if item.children?.length && !collapsed}
|
|
109
153
|
<div class="smrt-tenant-nav__children">
|
|
110
154
|
{#each item.children as child (child.href)}
|
|
@@ -153,7 +197,7 @@ function fallbackIcon(label: string): string {
|
|
|
153
197
|
<span>{group.heading}</span>
|
|
154
198
|
{/if}
|
|
155
199
|
</summary>
|
|
156
|
-
<div class="smrt-tenant-nav__group-items">{@render navItems(group.items)}</div>
|
|
200
|
+
<div class="smrt-tenant-nav__group-items">{@render navItems(group.items, isGroupCurrent(group))}</div>
|
|
157
201
|
</details>
|
|
158
202
|
{/each}
|
|
159
203
|
</nav>
|
|
@@ -167,6 +211,28 @@ function fallbackIcon(label: string): string {
|
|
|
167
211
|
gap: var(--smrt-spacing-1);
|
|
168
212
|
}
|
|
169
213
|
|
|
214
|
+
.smrt-tenant-nav__row {
|
|
215
|
+
display: flex;
|
|
216
|
+
align-items: center;
|
|
217
|
+
gap: var(--smrt-spacing-1);
|
|
218
|
+
min-inline-size: 0;
|
|
219
|
+
}
|
|
220
|
+
.smrt-tenant-nav__row > a:first-child { flex: 1 1 auto; min-inline-size: 0; }
|
|
221
|
+
.smrt-tenant-nav a.smrt-tenant-nav__action {
|
|
222
|
+
flex: 0 0 auto;
|
|
223
|
+
display: inline-grid;
|
|
224
|
+
grid-template-columns: minmax(0, 1fr);
|
|
225
|
+
place-items: center;
|
|
226
|
+
inline-size: max(2rem, var(--smrt-control-target-min, 0px));
|
|
227
|
+
block-size: max(2rem, var(--smrt-control-target-min, 0px));
|
|
228
|
+
padding: 0;
|
|
229
|
+
color: var(--smrt-color-on-surface-variant);
|
|
230
|
+
}
|
|
231
|
+
.smrt-tenant-nav a.smrt-tenant-nav__action:focus-visible {
|
|
232
|
+
outline: 2px solid var(--smrt-color-primary);
|
|
233
|
+
outline-offset: 2px;
|
|
234
|
+
}
|
|
235
|
+
|
|
170
236
|
.smrt-tenant-nav__heading {
|
|
171
237
|
box-sizing: border-box;
|
|
172
238
|
min-block-size: var(--smrt-control-target-min, 0px);
|
|
@@ -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,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;
|
|
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;AAkKD,QAAA,MAAM,SAAS,0BAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ShellDockOpenOptions } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Programmatic control of the shell dock (the right edge and the focus tools
|
|
4
|
+
* registered in it with `ShellDockTool`). Tool ids are the `ShellDockTool`
|
|
5
|
+
* ids. All reads are reactive.
|
|
6
|
+
*/
|
|
7
|
+
export interface ShellDock {
|
|
8
|
+
/** Id of the tool shown in the open dock, or `null` when it is closed. */
|
|
9
|
+
readonly active: string | null;
|
|
10
|
+
/** Ids of the registered dock tools. */
|
|
11
|
+
readonly tools: readonly string[];
|
|
12
|
+
/**
|
|
13
|
+
* Whether the dock can be shown at all: false when the host removed the
|
|
14
|
+
* right edge or the user's layout hides it (opening then does nothing).
|
|
15
|
+
*/
|
|
16
|
+
readonly available: boolean;
|
|
17
|
+
/** Whether `tool` is registered as a dock tool. */
|
|
18
|
+
has(tool: string): boolean;
|
|
19
|
+
/** Whether the dock is open on `tool`. */
|
|
20
|
+
isOpen(tool: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Open the dock on `tool` and move focus into it. Returns false (and does
|
|
23
|
+
* nothing) when no such tool is registered or the dock is not `available`.
|
|
24
|
+
*/
|
|
25
|
+
open(tool: string, options?: ShellDockOpenOptions): boolean;
|
|
26
|
+
/** Close the dock; focus returns to where it was opened from. */
|
|
27
|
+
close(): void;
|
|
28
|
+
/** Open `tool`, or close the dock when it is already open on `tool`. */
|
|
29
|
+
toggle(tool: string, options?: ShellDockOpenOptions): boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Dock control for the surrounding `AdminShell` / `AppShell`. Call it during
|
|
33
|
+
* component initialisation inside the shell (for example from the `dock`
|
|
34
|
+
* snippet, a route, or an assistant); it throws outside a shell.
|
|
35
|
+
*/
|
|
36
|
+
export declare function useShellDock(): ShellDock;
|
|
37
|
+
//# sourceMappingURL=dock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dock.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/dock.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,wCAAwC;IACxC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,mDAAmD;IACnD,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,0CAA0C;IAC1C,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9B;;;OAGG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC;IAC5D,iEAAiE;IACjE,KAAK,IAAI,IAAI,CAAC;IACd,wEAAwE;IACxE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC;CAC/D;AAED;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,SAAS,CAkBxC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useAdminShell } from './context.js';
|
|
2
|
+
/**
|
|
3
|
+
* Dock control for the surrounding `AdminShell` / `AppShell`. Call it during
|
|
4
|
+
* component initialisation inside the shell (for example from the `dock`
|
|
5
|
+
* snippet, a route, or an assistant); it throws outside a shell.
|
|
6
|
+
*/
|
|
7
|
+
export function useShellDock() {
|
|
8
|
+
const shell = useAdminShell();
|
|
9
|
+
return {
|
|
10
|
+
get active() {
|
|
11
|
+
return shell.openFocusToolId;
|
|
12
|
+
},
|
|
13
|
+
get tools() {
|
|
14
|
+
return shell.focusTools.map((tool) => tool.id);
|
|
15
|
+
},
|
|
16
|
+
get available() {
|
|
17
|
+
return shell.dockAvailable;
|
|
18
|
+
},
|
|
19
|
+
has: (tool) => shell.focusTools.some((t) => t.id === tool),
|
|
20
|
+
isOpen: (tool) => shell.openFocusToolId === tool,
|
|
21
|
+
open: (tool, options) => shell.openDockTool(tool, options),
|
|
22
|
+
close: () => shell.closeFocusTool(),
|
|
23
|
+
toggle: (tool, options) => shell.toggleDockTool(tool, options),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ShellLayoutController } from './layout-controller.svelte.js';
|
|
2
|
+
export declare const SHELL_LAYOUT_CONTEXT: unique symbol;
|
|
3
|
+
export declare function setShellLayout(controller: ShellLayoutController): ShellLayoutController;
|
|
4
|
+
/** The layout API of the nearest `AppShell`; throws when there is none. */
|
|
5
|
+
export declare function useShellLayout(): ShellLayoutController;
|
|
6
|
+
export declare function tryUseShellLayout(): ShellLayoutController | null;
|
|
7
|
+
//# sourceMappingURL=layout-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout-context.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/layout-context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAE3E,eAAO,MAAM,oBAAoB,eAA8B,CAAC;AAEhE,wBAAgB,cAAc,CAC5B,UAAU,EAAE,qBAAqB,GAChC,qBAAqB,CAGvB;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,IAAI,qBAAqB,CAQtD;AAED,wBAAgB,iBAAiB,IAAI,qBAAqB,GAAG,IAAI,CAIhE"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { getContext, setContext } from 'svelte';
|
|
2
|
+
export const SHELL_LAYOUT_CONTEXT = Symbol('smrt-shell-layout');
|
|
3
|
+
export function setShellLayout(controller) {
|
|
4
|
+
setContext(SHELL_LAYOUT_CONTEXT, controller);
|
|
5
|
+
return controller;
|
|
6
|
+
}
|
|
7
|
+
/** The layout API of the nearest `AppShell`; throws when there is none. */
|
|
8
|
+
export function useShellLayout() {
|
|
9
|
+
const controller = getContext(SHELL_LAYOUT_CONTEXT);
|
|
10
|
+
if (!controller) {
|
|
11
|
+
throw new Error('[useShellLayout] No AppShell layout found on context.');
|
|
12
|
+
}
|
|
13
|
+
return controller;
|
|
14
|
+
}
|
|
15
|
+
export function tryUseShellLayout() {
|
|
16
|
+
return (getContext(SHELL_LAYOUT_CONTEXT) ?? null);
|
|
17
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type ShellLayout, type ShellNavModelSection } from './layout.js';
|
|
2
|
+
import type { PanelEdge, ShellNavGroup, ShellNavItem, ShellPanelDefaults } from './types.js';
|
|
3
|
+
export interface ShellLayoutControllerOptions {
|
|
4
|
+
/** The host's flat navigation, before any layout. */
|
|
5
|
+
nav: () => ShellNavItem[];
|
|
6
|
+
/** The host's navigation groups, before any layout. */
|
|
7
|
+
groups: () => ShellNavGroup[];
|
|
8
|
+
/** The host's panel defaults, before any layout. */
|
|
9
|
+
panels: () => ShellPanelDefaults | undefined;
|
|
10
|
+
/** The layout currently in force (host-owned, or stored by the shell). */
|
|
11
|
+
layout: () => ShellLayout | null | undefined;
|
|
12
|
+
/** Store the next layout (and notify the host). */
|
|
13
|
+
commit: (layout: ShellLayout) => void;
|
|
14
|
+
}
|
|
15
|
+
/** One edge panel as an editor presents it. */
|
|
16
|
+
export interface ShellLayoutPanelView {
|
|
17
|
+
edge: PanelEdge;
|
|
18
|
+
label: string;
|
|
19
|
+
/** `false` when the host removed the panel; a layout cannot restore it. */
|
|
20
|
+
available: boolean;
|
|
21
|
+
visible: boolean;
|
|
22
|
+
initial: 'collapsed' | 'expanded';
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The layout API: the same changes the `ShellLayoutEditor` makes, as plain
|
|
26
|
+
* calls an assistant (or any host code) can make. Obtain it with
|
|
27
|
+
* `useShellLayout()` under an `AppShell`. Every mutation returns `true` when
|
|
28
|
+
* it changed the layout and `false` when it was a no-op (unknown id, already
|
|
29
|
+
* in that state), so a caller can report accurately.
|
|
30
|
+
*/
|
|
31
|
+
export declare class ShellLayoutController {
|
|
32
|
+
private readonly options;
|
|
33
|
+
constructor(options: ShellLayoutControllerOptions);
|
|
34
|
+
/** The layout in force, normalized. */
|
|
35
|
+
get layout(): ShellLayout;
|
|
36
|
+
/** Every section and item in display order, hidden ones flagged. */
|
|
37
|
+
get sections(): ShellNavModelSection[];
|
|
38
|
+
/** The navigation as the shell renders it (hidden entries removed). */
|
|
39
|
+
get applied(): {
|
|
40
|
+
nav: ShellNavItem[];
|
|
41
|
+
groups: ShellNavGroup[];
|
|
42
|
+
};
|
|
43
|
+
/** Each edge panel with its effective visibility and starting state. */
|
|
44
|
+
get panels(): ShellLayoutPanelView[];
|
|
45
|
+
/** Whether a customization is in force. */
|
|
46
|
+
get customized(): boolean;
|
|
47
|
+
/** Whether a section or item is hidden by the layout. */
|
|
48
|
+
isHidden(id: string): boolean;
|
|
49
|
+
private apply;
|
|
50
|
+
/** Move a group section to `toIndex` among the groups (0 = first). */
|
|
51
|
+
moveSection(sectionId: string, toIndex: number): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Move an item into a section (a group id, or `@root` for the top level) at
|
|
54
|
+
* `toIndex` (default: the end).
|
|
55
|
+
*/
|
|
56
|
+
moveItem(itemId: string, toSectionId: string, toIndex?: number): boolean;
|
|
57
|
+
/** Hide a section or item. */
|
|
58
|
+
hide(id: string): boolean;
|
|
59
|
+
/** Show a hidden section or item. */
|
|
60
|
+
show(id: string): boolean;
|
|
61
|
+
/** Show or hide an edge panel and/or choose the state it starts in. */
|
|
62
|
+
setPanel(edge: PanelEdge, patch: {
|
|
63
|
+
visible?: boolean;
|
|
64
|
+
initial?: 'collapsed' | 'expanded';
|
|
65
|
+
}): boolean;
|
|
66
|
+
/** Drop every customization. */
|
|
67
|
+
reset(): boolean;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=layout-controller.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout-controller.svelte.d.ts","sourceRoot":"","sources":["../../../../src/components/workspace/admin-shell/layout-controller.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,EASL,KAAK,WAAW,EAChB,KAAK,oBAAoB,EAG1B,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EACV,SAAS,EACT,aAAa,EACb,YAAY,EACZ,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,4BAA4B;IAC3C,qDAAqD;IACrD,GAAG,EAAE,MAAM,YAAY,EAAE,CAAC;IAC1B,uDAAuD;IACvD,MAAM,EAAE,MAAM,aAAa,EAAE,CAAC;IAC9B,oDAAoD;IACpD,MAAM,EAAE,MAAM,kBAAkB,GAAG,SAAS,CAAC;IAC7C,0EAA0E;IAC1E,MAAM,EAAE,MAAM,WAAW,GAAG,IAAI,GAAG,SAAS,CAAC;IAC7C,mDAAmD;IACnD,MAAM,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;CACvC;AAED,+CAA+C;AAC/C,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,WAAW,GAAG,UAAU,CAAC;CACnC;AAED;;;;;;GAMG;AACH,qBAAa,qBAAqB;IACpB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,4BAA4B;IAElE,uCAAuC;IACvC,IAAI,MAAM,IAAI,WAAW,CAIxB;IAED,oEAAoE;IACpE,IAAI,QAAQ,IAAI,oBAAoB,EAAE,CAMrC;IAED,uEAAuE;IACvE,IAAI,OAAO,IAAI;QAAE,GAAG,EAAE,YAAY,EAAE,CAAC;QAAC,MAAM,EAAE,aAAa,EAAE,CAAA;KAAE,CAQ9D;IAED,wEAAwE;IACxE,IAAI,MAAM,IAAI,oBAAoB,EAAE,CAgBnC;IAED,2CAA2C;IAC3C,IAAI,UAAU,IAAI,OAAO,CAExB;IAED,yDAAyD;IACzD,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAI7B,OAAO,CAAC,KAAK;IAOb,sEAAsE;IACtE,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO;IAYxD;;;OAGG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO;IAaxE,8BAA8B;IAC9B,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAWzB,qCAAqC;IACrC,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAIzB,uEAAuE;IACvE,QAAQ,CACN,IAAI,EAAE,SAAS,EACf,KAAK,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,WAAW,GAAG,UAAU,CAAA;KAAE,GAC/D,OAAO;IAQV,gCAAgC;IAChC,KAAK,IAAI,OAAO;CAGjB"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { applyShellLayout, hideShellEntry, isShellLayoutEmpty, moveShellItem, moveShellSection, normalizeShellLayout, resolveShellNavModel, SHELL_LAYOUT_VERSION, setShellLayoutPanel, showShellEntry, } from './layout.js';
|
|
2
|
+
import { resolveShellConfig } from './settings.js';
|
|
3
|
+
import { PANEL_EDGES } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* The layout API: the same changes the `ShellLayoutEditor` makes, as plain
|
|
6
|
+
* calls an assistant (or any host code) can make. Obtain it with
|
|
7
|
+
* `useShellLayout()` under an `AppShell`. Every mutation returns `true` when
|
|
8
|
+
* it changed the layout and `false` when it was a no-op (unknown id, already
|
|
9
|
+
* in that state), so a caller can report accurately.
|
|
10
|
+
*/
|
|
11
|
+
export class ShellLayoutController {
|
|
12
|
+
options;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.options = options;
|
|
15
|
+
}
|
|
16
|
+
/** The layout in force, normalized. */
|
|
17
|
+
get layout() {
|
|
18
|
+
return normalizeShellLayout(this.options.layout() ?? { version: SHELL_LAYOUT_VERSION });
|
|
19
|
+
}
|
|
20
|
+
/** Every section and item in display order, hidden ones flagged. */
|
|
21
|
+
get sections() {
|
|
22
|
+
return resolveShellNavModel(this.options.nav(), this.options.groups(), this.layout);
|
|
23
|
+
}
|
|
24
|
+
/** The navigation as the shell renders it (hidden entries removed). */
|
|
25
|
+
get applied() {
|
|
26
|
+
const { nav, groups } = applyShellLayout(this.options.nav(), this.options.groups(), this.options.panels(), this.layout);
|
|
27
|
+
return { nav, groups };
|
|
28
|
+
}
|
|
29
|
+
/** Each edge panel with its effective visibility and starting state. */
|
|
30
|
+
get panels() {
|
|
31
|
+
const defaults = resolveShellConfig(this.options.panels()).panels;
|
|
32
|
+
const layout = this.layout;
|
|
33
|
+
return PANEL_EDGES.map((edge) => {
|
|
34
|
+
const config = defaults[edge];
|
|
35
|
+
const override = layout.panels?.[edge];
|
|
36
|
+
return {
|
|
37
|
+
edge,
|
|
38
|
+
label: config.label,
|
|
39
|
+
available: config.initial !== 'hidden',
|
|
40
|
+
visible: config.initial !== 'hidden' && override?.visible !== false,
|
|
41
|
+
initial: override?.initial ??
|
|
42
|
+
(config.initial === 'expanded' ? 'expanded' : 'collapsed'),
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/** Whether a customization is in force. */
|
|
47
|
+
get customized() {
|
|
48
|
+
return !isShellLayoutEmpty(this.layout);
|
|
49
|
+
}
|
|
50
|
+
/** Whether a section or item is hidden by the layout. */
|
|
51
|
+
isHidden(id) {
|
|
52
|
+
return this.layout.hidden?.includes(id) ?? false;
|
|
53
|
+
}
|
|
54
|
+
apply(next) {
|
|
55
|
+
const current = this.layout;
|
|
56
|
+
if (JSON.stringify(next) === JSON.stringify(current))
|
|
57
|
+
return false;
|
|
58
|
+
this.options.commit(next);
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
/** Move a group section to `toIndex` among the groups (0 = first). */
|
|
62
|
+
moveSection(sectionId, toIndex) {
|
|
63
|
+
return this.apply(moveShellSection(this.options.nav(), this.options.groups(), this.layout, sectionId, toIndex));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Move an item into a section (a group id, or `@root` for the top level) at
|
|
67
|
+
* `toIndex` (default: the end).
|
|
68
|
+
*/
|
|
69
|
+
moveItem(itemId, toSectionId, toIndex) {
|
|
70
|
+
return this.apply(moveShellItem(this.options.nav(), this.options.groups(), this.layout, itemId, toSectionId, toIndex));
|
|
71
|
+
}
|
|
72
|
+
/** Hide a section or item. */
|
|
73
|
+
hide(id) {
|
|
74
|
+
return this.apply(hideShellEntry(this.options.nav(), this.options.groups(), this.layout, id));
|
|
75
|
+
}
|
|
76
|
+
/** Show a hidden section or item. */
|
|
77
|
+
show(id) {
|
|
78
|
+
return this.apply(showShellEntry(this.layout, id));
|
|
79
|
+
}
|
|
80
|
+
/** Show or hide an edge panel and/or choose the state it starts in. */
|
|
81
|
+
setPanel(edge, patch) {
|
|
82
|
+
const view = this.panels.find((panel) => panel.edge === edge);
|
|
83
|
+
if (!view?.available)
|
|
84
|
+
return false;
|
|
85
|
+
return this.apply(setShellLayoutPanel(this.layout, edge, patch, this.options.panels()));
|
|
86
|
+
}
|
|
87
|
+
/** Drop every customization. */
|
|
88
|
+
reset() {
|
|
89
|
+
return this.apply({ version: SHELL_LAYOUT_VERSION });
|
|
90
|
+
}
|
|
91
|
+
}
|