@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.
Files changed (77) hide show
  1. package/README.md +29 -2
  2. package/agents/components.md +44 -1
  3. package/agents/workspace.md +77 -2
  4. package/dist/components/app/AppShell.svelte +121 -9
  5. package/dist/components/app/AppShell.svelte.d.ts +24 -2
  6. package/dist/components/app/AppShell.svelte.d.ts.map +1 -1
  7. package/dist/components/app/DockToggles.svelte +103 -0
  8. package/dist/components/app/DockToggles.svelte.d.ts +8 -0
  9. package/dist/components/app/DockToggles.svelte.d.ts.map +1 -0
  10. package/dist/components/app/dock-toggle.d.ts +14 -0
  11. package/dist/components/app/dock-toggle.d.ts.map +1 -0
  12. package/dist/components/app/dock-toggle.js +1 -0
  13. package/dist/components/app/index.d.ts +2 -0
  14. package/dist/components/app/index.d.ts.map +1 -1
  15. package/dist/components/app/index.js +1 -0
  16. package/dist/components/board/Board.svelte +92 -394
  17. package/dist/components/board/Board.svelte.d.ts.map +1 -1
  18. package/dist/components/forms/RelationInput.svelte +379 -0
  19. package/dist/components/forms/RelationInput.svelte.d.ts +75 -0
  20. package/dist/components/forms/RelationInput.svelte.d.ts.map +1 -0
  21. package/dist/components/forms/index.d.ts +2 -1
  22. package/dist/components/forms/index.d.ts.map +1 -1
  23. package/dist/components/forms/index.js +1 -0
  24. package/dist/components/forms/types.d.ts +9 -0
  25. package/dist/components/forms/types.d.ts.map +1 -1
  26. package/dist/components/sortable/Sortable.svelte +359 -0
  27. package/dist/components/sortable/Sortable.svelte.d.ts +26 -0
  28. package/dist/components/sortable/Sortable.svelte.d.ts.map +1 -0
  29. package/dist/components/sortable/controller.svelte.d.ts +152 -0
  30. package/dist/components/sortable/controller.svelte.d.ts.map +1 -0
  31. package/dist/components/sortable/controller.svelte.js +453 -0
  32. package/dist/components/sortable/index.d.ts +5 -0
  33. package/dist/components/sortable/index.d.ts.map +1 -0
  34. package/dist/components/sortable/index.js +2 -0
  35. package/dist/components/sortable/types.d.ts +80 -0
  36. package/dist/components/sortable/types.d.ts.map +1 -0
  37. package/dist/components/sortable/types.js +1 -0
  38. package/dist/components/workspace/admin-shell/AdminShell.svelte +49 -1
  39. package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts.map +1 -1
  40. package/dist/components/workspace/admin-shell/ShellLayoutEditor.svelte +196 -0
  41. package/dist/components/workspace/admin-shell/ShellLayoutEditor.svelte.d.ts +17 -0
  42. package/dist/components/workspace/admin-shell/ShellLayoutEditor.svelte.d.ts.map +1 -0
  43. package/dist/components/workspace/admin-shell/TenantNav.svelte +69 -3
  44. package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts.map +1 -1
  45. package/dist/components/workspace/admin-shell/dock.d.ts +37 -0
  46. package/dist/components/workspace/admin-shell/dock.d.ts.map +1 -0
  47. package/dist/components/workspace/admin-shell/dock.js +25 -0
  48. package/dist/components/workspace/admin-shell/layout-context.d.ts +7 -0
  49. package/dist/components/workspace/admin-shell/layout-context.d.ts.map +1 -0
  50. package/dist/components/workspace/admin-shell/layout-context.js +17 -0
  51. package/dist/components/workspace/admin-shell/layout-controller.svelte.d.ts +69 -0
  52. package/dist/components/workspace/admin-shell/layout-controller.svelte.d.ts.map +1 -0
  53. package/dist/components/workspace/admin-shell/layout-controller.svelte.js +91 -0
  54. package/dist/components/workspace/admin-shell/layout.d.ts +120 -0
  55. package/dist/components/workspace/admin-shell/layout.d.ts.map +1 -0
  56. package/dist/components/workspace/admin-shell/layout.js +402 -0
  57. package/dist/components/workspace/admin-shell/settings.d.ts +6 -4
  58. package/dist/components/workspace/admin-shell/settings.d.ts.map +1 -1
  59. package/dist/components/workspace/admin-shell/settings.js +14 -4
  60. package/dist/components/workspace/admin-shell/state.svelte.d.ts +59 -1
  61. package/dist/components/workspace/admin-shell/state.svelte.d.ts.map +1 -1
  62. package/dist/components/workspace/admin-shell/state.svelte.js +146 -2
  63. package/dist/components/workspace/admin-shell/types.d.ts +52 -0
  64. package/dist/components/workspace/admin-shell/types.d.ts.map +1 -1
  65. package/dist/components/workspace/index.d.ts +6 -1
  66. package/dist/components/workspace/index.d.ts.map +1 -1
  67. package/dist/components/workspace/index.js +5 -0
  68. package/dist/i18n/server.d.ts +1 -0
  69. package/dist/i18n/server.d.ts.map +1 -1
  70. package/dist/i18n/server.js +1 -0
  71. package/dist/i18n/strings.sortable.d.ts +13 -0
  72. package/dist/i18n/strings.sortable.d.ts.map +1 -0
  73. package/dist/i18n/strings.sortable.js +13 -0
  74. package/dist/i18n/strings.workspace.d.ts +13 -0
  75. package/dist/i18n/strings.workspace.d.ts.map +1 -1
  76. package/dist/i18n/strings.workspace.js +13 -0
  77. 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 && !!item.children?.some((child) => isActive(child));
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;AA6HD,QAAA,MAAM,SAAS,0BAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,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;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
+ }