sh3-core 0.1.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/dist/Shell.svelte +185 -0
- package/dist/Shell.svelte.d.ts +4 -0
- package/dist/api.d.ts +22 -0
- package/dist/api.js +45 -0
- package/dist/apps/lifecycle.d.ts +37 -0
- package/dist/apps/lifecycle.js +153 -0
- package/dist/apps/registry.svelte.d.ts +37 -0
- package/dist/apps/registry.svelte.js +60 -0
- package/dist/apps/types.d.ts +61 -0
- package/dist/apps/types.js +10 -0
- package/dist/assets/icons.svg +1119 -0
- package/dist/auth/auth.svelte.d.ts +44 -0
- package/dist/auth/auth.svelte.js +119 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.js +1 -0
- package/dist/build.d.ts +29 -0
- package/dist/build.js +85 -0
- package/dist/contract.d.ts +20 -0
- package/dist/contract.js +28 -0
- package/dist/documents/backends.d.ts +17 -0
- package/dist/documents/backends.js +156 -0
- package/dist/documents/config.d.ts +7 -0
- package/dist/documents/config.js +27 -0
- package/dist/documents/handle.d.ts +6 -0
- package/dist/documents/handle.js +154 -0
- package/dist/documents/http-backend.d.ts +22 -0
- package/dist/documents/http-backend.js +78 -0
- package/dist/documents/index.d.ts +6 -0
- package/dist/documents/index.js +8 -0
- package/dist/documents/notifications.d.ts +9 -0
- package/dist/documents/notifications.js +39 -0
- package/dist/documents/types.d.ts +97 -0
- package/dist/documents/types.js +12 -0
- package/dist/host-entry.d.ts +9 -0
- package/dist/host-entry.js +15 -0
- package/dist/host.d.ts +13 -0
- package/dist/host.js +73 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +13 -0
- package/dist/layout/DragPreview.svelte +63 -0
- package/dist/layout/DragPreview.svelte.d.ts +3 -0
- package/dist/layout/LayoutRenderer.svelte +260 -0
- package/dist/layout/LayoutRenderer.svelte.d.ts +6 -0
- package/dist/layout/SlotContainer.svelte +140 -0
- package/dist/layout/SlotContainer.svelte.d.ts +8 -0
- package/dist/layout/SlotDropZone.svelte +122 -0
- package/dist/layout/SlotDropZone.svelte.d.ts +8 -0
- package/dist/layout/drag.svelte.d.ts +45 -0
- package/dist/layout/drag.svelte.js +191 -0
- package/dist/layout/inspection.d.ts +52 -0
- package/dist/layout/inspection.js +157 -0
- package/dist/layout/ops.d.ts +78 -0
- package/dist/layout/ops.js +281 -0
- package/dist/layout/slotHostPool.svelte.d.ts +36 -0
- package/dist/layout/slotHostPool.svelte.js +229 -0
- package/dist/layout/store.svelte.d.ts +39 -0
- package/dist/layout/store.svelte.js +150 -0
- package/dist/layout/tree-walk.d.ts +15 -0
- package/dist/layout/tree-walk.js +33 -0
- package/dist/layout/types.d.ts +108 -0
- package/dist/layout/types.js +25 -0
- package/dist/overlays/ModalFrame.svelte +87 -0
- package/dist/overlays/ModalFrame.svelte.d.ts +10 -0
- package/dist/overlays/PopupFrame.svelte +85 -0
- package/dist/overlays/PopupFrame.svelte.d.ts +10 -0
- package/dist/overlays/ToastItem.svelte +77 -0
- package/dist/overlays/ToastItem.svelte.d.ts +9 -0
- package/dist/overlays/focusTrap.d.ts +1 -0
- package/dist/overlays/focusTrap.js +64 -0
- package/dist/overlays/modal.d.ts +9 -0
- package/dist/overlays/modal.js +141 -0
- package/dist/overlays/popup.d.ts +9 -0
- package/dist/overlays/popup.js +108 -0
- package/dist/overlays/roots.d.ts +4 -0
- package/dist/overlays/roots.js +31 -0
- package/dist/overlays/toast.d.ts +6 -0
- package/dist/overlays/toast.js +93 -0
- package/dist/overlays/types.d.ts +31 -0
- package/dist/overlays/types.js +15 -0
- package/dist/primitives/.gitkeep +0 -0
- package/dist/primitives/ResizableSplitter.svelte +333 -0
- package/dist/primitives/ResizableSplitter.svelte.d.ts +35 -0
- package/dist/primitives/TabbedPanel.svelte +305 -0
- package/dist/primitives/TabbedPanel.svelte.d.ts +50 -0
- package/dist/registry/client.d.ts +74 -0
- package/dist/registry/client.js +118 -0
- package/dist/registry/index.d.ts +13 -0
- package/dist/registry/index.js +14 -0
- package/dist/registry/installer.d.ts +53 -0
- package/dist/registry/installer.js +170 -0
- package/dist/registry/integrity.d.ts +32 -0
- package/dist/registry/integrity.js +92 -0
- package/dist/registry/loader.d.ts +50 -0
- package/dist/registry/loader.js +145 -0
- package/dist/registry/schema.d.ts +47 -0
- package/dist/registry/schema.js +180 -0
- package/dist/registry/storage.d.ts +37 -0
- package/dist/registry/storage.js +101 -0
- package/dist/registry/types.d.ts +245 -0
- package/dist/registry/types.js +14 -0
- package/dist/registry-shard/RegistryView.svelte +561 -0
- package/dist/registry-shard/RegistryView.svelte.d.ts +3 -0
- package/dist/registry-shard/registryApp.d.ts +10 -0
- package/dist/registry-shard/registryApp.js +24 -0
- package/dist/registry-shard/registryShard.svelte.d.ts +45 -0
- package/dist/registry-shard/registryShard.svelte.js +125 -0
- package/dist/shards/activate.svelte.d.ts +45 -0
- package/dist/shards/activate.svelte.js +124 -0
- package/dist/shards/registry.d.ts +4 -0
- package/dist/shards/registry.js +28 -0
- package/dist/shards/types.d.ts +155 -0
- package/dist/shards/types.js +20 -0
- package/dist/shell-shard/ShellHome.svelte +285 -0
- package/dist/shell-shard/ShellHome.svelte.d.ts +3 -0
- package/dist/shell-shard/shellShard.svelte.d.ts +2 -0
- package/dist/shell-shard/shellShard.svelte.js +47 -0
- package/dist/shellRuntime.svelte.d.ts +27 -0
- package/dist/shellRuntime.svelte.js +27 -0
- package/dist/state/backends.d.ts +26 -0
- package/dist/state/backends.js +99 -0
- package/dist/state/types.d.ts +38 -0
- package/dist/state/types.js +15 -0
- package/dist/state/zones.svelte.d.ts +52 -0
- package/dist/state/zones.svelte.js +141 -0
- package/dist/store/InstalledView.svelte +201 -0
- package/dist/store/InstalledView.svelte.d.ts +3 -0
- package/dist/store/StoreView.svelte +470 -0
- package/dist/store/StoreView.svelte.d.ts +3 -0
- package/dist/store/storeApp.d.ts +11 -0
- package/dist/store/storeApp.js +26 -0
- package/dist/store/storeShard.svelte.d.ts +29 -0
- package/dist/store/storeShard.svelte.js +99 -0
- package/dist/tokens.css +79 -0
- package/package.json +50 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Component } from 'svelte';
|
|
2
|
+
import type { ModalHandle, ModalOptions } from './types';
|
|
3
|
+
export interface ModalManager {
|
|
4
|
+
open<P extends Record<string, unknown>>(Content: Component<P & {
|
|
5
|
+
close: () => void;
|
|
6
|
+
}>, props?: P, options?: ModalOptions): ModalHandle;
|
|
7
|
+
closeAll(): void;
|
|
8
|
+
}
|
|
9
|
+
export declare const modalManager: ModalManager;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Modal manager — stackable, Escape-dismissed, focus-trapped dialogs.
|
|
3
|
+
*
|
|
4
|
+
* Public API:
|
|
5
|
+
* modalManager.open(Content, props?) → ModalHandle
|
|
6
|
+
* modalManager.closeAll()
|
|
7
|
+
*
|
|
8
|
+
* Semantics (from docs/design/layout.md):
|
|
9
|
+
* - Modals stack. Opening a second modal pushes it on top of the first.
|
|
10
|
+
* - Escape pops the topmost modal.
|
|
11
|
+
* - Backdrop click does NOT dismiss. Content owns its own close UI.
|
|
12
|
+
* - Each modal has its own focus trap (installed by ModalFrame).
|
|
13
|
+
*
|
|
14
|
+
* Implementation notes:
|
|
15
|
+
* - Each open() creates a per-modal host <div> under the layer-4 root
|
|
16
|
+
* and mounts a ModalFrame into it. The host isolates modals for clean
|
|
17
|
+
* unmount/remove.
|
|
18
|
+
* - A SINGLE shared backdrop element is inserted into the layer-4 root
|
|
19
|
+
* on the first open and removed on the last close. It is repositioned
|
|
20
|
+
* on every stack change to sit directly BENEATH the topmost modal host
|
|
21
|
+
* (and above all other modal hosts), so stacked modals read as dimmed
|
|
22
|
+
* and clearly non-interactive while the top modal renders clear. The
|
|
23
|
+
* opacity is a single fixed value — depth-scaled opacity (an earlier
|
|
24
|
+
* cut) is no longer meaningful because only the "layer beneath the
|
|
25
|
+
* top" is ever being dimmed. Per-modal backdrops were the phase-5
|
|
26
|
+
* first cut; they compounded (2 modals ≈ 0.75, 3 ≈ 0.88, …) and were
|
|
27
|
+
* both unbounded and expensive.
|
|
28
|
+
* - The manager owns a single document-level Escape listener, installed
|
|
29
|
+
* lazily on first open and removed when the stack empties. This is
|
|
30
|
+
* simpler than per-modal listeners and avoids "which listener wins"
|
|
31
|
+
* ordering puzzles when modals stack.
|
|
32
|
+
* - close() is idempotent: calling it after the modal has been removed
|
|
33
|
+
* is a no-op, which makes it safe for content components to call from
|
|
34
|
+
* both a close button and an "async action finished" callback.
|
|
35
|
+
*/
|
|
36
|
+
import { mount, unmount } from 'svelte';
|
|
37
|
+
import ModalFrame from './ModalFrame.svelte';
|
|
38
|
+
import { getLayerRoot } from './roots';
|
|
39
|
+
const stack = [];
|
|
40
|
+
let escapeInstalled = false;
|
|
41
|
+
let backdrop = null;
|
|
42
|
+
/** Single fixed backdrop opacity. The backdrop is repositioned beneath
|
|
43
|
+
* the topmost modal on every stack change, so underlying modals appear
|
|
44
|
+
* dimmed (visually communicating they're non-interactive) while the top
|
|
45
|
+
* modal sits above the dim. Depth-scaled opacity is therefore obsolete:
|
|
46
|
+
* only one "layer beneath the top" is ever being dimmed. */
|
|
47
|
+
const BACKDROP_OPACITY = 0.5;
|
|
48
|
+
function syncBackdrop() {
|
|
49
|
+
const root = getLayerRoot('modal');
|
|
50
|
+
if (stack.length === 0) {
|
|
51
|
+
if (backdrop) {
|
|
52
|
+
backdrop.remove();
|
|
53
|
+
backdrop = null;
|
|
54
|
+
}
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (!backdrop) {
|
|
58
|
+
backdrop = document.createElement('div');
|
|
59
|
+
backdrop.className = 'sh3-modal-backdrop';
|
|
60
|
+
backdrop.style.position = 'absolute';
|
|
61
|
+
backdrop.style.inset = '0';
|
|
62
|
+
backdrop.style.pointerEvents = 'auto';
|
|
63
|
+
backdrop.style.transition = 'background-color 120ms ease';
|
|
64
|
+
backdrop.style.background = `rgba(0, 0, 0, ${BACKDROP_OPACITY})`;
|
|
65
|
+
}
|
|
66
|
+
// Place the backdrop directly before the topmost modal host so every
|
|
67
|
+
// modal beneath it is dimmed while the top modal renders clear above.
|
|
68
|
+
const topHost = stack[stack.length - 1].host;
|
|
69
|
+
root.insertBefore(backdrop, topHost);
|
|
70
|
+
}
|
|
71
|
+
function onDocumentKeydown(e) {
|
|
72
|
+
if (e.key !== 'Escape')
|
|
73
|
+
return;
|
|
74
|
+
if (stack.length === 0)
|
|
75
|
+
return;
|
|
76
|
+
e.stopPropagation();
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
stack[stack.length - 1].handle.close();
|
|
79
|
+
}
|
|
80
|
+
function ensureEscapeListener() {
|
|
81
|
+
if (escapeInstalled)
|
|
82
|
+
return;
|
|
83
|
+
escapeInstalled = true;
|
|
84
|
+
document.addEventListener('keydown', onDocumentKeydown, true);
|
|
85
|
+
}
|
|
86
|
+
function removeEscapeListenerIfIdle() {
|
|
87
|
+
if (stack.length > 0)
|
|
88
|
+
return;
|
|
89
|
+
if (!escapeInstalled)
|
|
90
|
+
return;
|
|
91
|
+
escapeInstalled = false;
|
|
92
|
+
document.removeEventListener('keydown', onDocumentKeydown, true);
|
|
93
|
+
}
|
|
94
|
+
function removeEntry(entry) {
|
|
95
|
+
const idx = stack.indexOf(entry);
|
|
96
|
+
if (idx < 0)
|
|
97
|
+
return; // already closed — idempotent
|
|
98
|
+
stack.splice(idx, 1);
|
|
99
|
+
unmount(entry.frame);
|
|
100
|
+
entry.host.remove();
|
|
101
|
+
syncBackdrop();
|
|
102
|
+
removeEscapeListenerIfIdle();
|
|
103
|
+
}
|
|
104
|
+
function openModal(Content, props, options) {
|
|
105
|
+
const root = getLayerRoot('modal');
|
|
106
|
+
const host = document.createElement('div');
|
|
107
|
+
host.className = 'sh3-modal-host';
|
|
108
|
+
host.style.position = 'absolute';
|
|
109
|
+
host.style.inset = '0';
|
|
110
|
+
host.style.pointerEvents = 'auto';
|
|
111
|
+
root.appendChild(host);
|
|
112
|
+
const entry = {};
|
|
113
|
+
const handle = {
|
|
114
|
+
close: () => removeEntry(entry),
|
|
115
|
+
};
|
|
116
|
+
const frame = mount(ModalFrame, {
|
|
117
|
+
target: host,
|
|
118
|
+
props: {
|
|
119
|
+
Content: Content,
|
|
120
|
+
contentProps: (props !== null && props !== void 0 ? props : {}),
|
|
121
|
+
close: handle.close,
|
|
122
|
+
boxStyle: options === null || options === void 0 ? void 0 : options.boxStyle,
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
entry.host = host;
|
|
126
|
+
entry.frame = frame;
|
|
127
|
+
entry.handle = handle;
|
|
128
|
+
stack.push(entry);
|
|
129
|
+
syncBackdrop();
|
|
130
|
+
ensureEscapeListener();
|
|
131
|
+
return handle;
|
|
132
|
+
}
|
|
133
|
+
function closeAll() {
|
|
134
|
+
while (stack.length > 0) {
|
|
135
|
+
removeEntry(stack[stack.length - 1]);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
export const modalManager = {
|
|
139
|
+
open: openModal,
|
|
140
|
+
closeAll,
|
|
141
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type Component } from 'svelte';
|
|
2
|
+
import type { PopupHandle, PopupOptions } from './types';
|
|
3
|
+
export interface PopupManager {
|
|
4
|
+
show<P extends Record<string, unknown>>(Content: Component<P & {
|
|
5
|
+
close: () => void;
|
|
6
|
+
}>, options: PopupOptions, props?: P): PopupHandle;
|
|
7
|
+
close(): void;
|
|
8
|
+
}
|
|
9
|
+
export declare const popupManager: PopupManager;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Popup manager — anchored, non-stacking, outside-click-dismissable.
|
|
3
|
+
*
|
|
4
|
+
* Public API:
|
|
5
|
+
* popupManager.show(Content, { anchor, placement? }, props?) → PopupHandle
|
|
6
|
+
* popupManager.close()
|
|
7
|
+
*
|
|
8
|
+
* Semantics (from docs/design/layout.md):
|
|
9
|
+
* - Popups do NOT stack. Opening a second popup dismisses the first.
|
|
10
|
+
* - Clicking outside the popup dismisses it.
|
|
11
|
+
* - Pressing Escape dismisses it.
|
|
12
|
+
* - The caller provides an HTMLElement anchor; the popup positions
|
|
13
|
+
* itself relative to the anchor's current viewport rect.
|
|
14
|
+
*
|
|
15
|
+
* Implementation notes:
|
|
16
|
+
* - The manager keeps at most one active entry. show() closes any
|
|
17
|
+
* prior entry before opening a new one — so callers can wire a
|
|
18
|
+
* menu button to `shell.popup.show(...)` without worrying about
|
|
19
|
+
* toggling themselves.
|
|
20
|
+
* - Dismissal listeners (pointerdown for outside-click, keydown for
|
|
21
|
+
* Escape) are installed when the popup opens and removed on close.
|
|
22
|
+
* Outside-click detection uses the mounted frame element's DOM
|
|
23
|
+
* containment check, so nested popups-inside-content still click-
|
|
24
|
+
* through without closing (there are no nested popups in phase 5,
|
|
25
|
+
* but the check is cheap and correct).
|
|
26
|
+
* - close() is idempotent.
|
|
27
|
+
*/
|
|
28
|
+
import { mount, unmount } from 'svelte';
|
|
29
|
+
import PopupFrame from './PopupFrame.svelte';
|
|
30
|
+
import { getLayerRoot } from './roots';
|
|
31
|
+
let current = null;
|
|
32
|
+
function onDocumentPointerDown(e) {
|
|
33
|
+
if (!current)
|
|
34
|
+
return;
|
|
35
|
+
const target = e.target;
|
|
36
|
+
if (target && current.host.contains(target))
|
|
37
|
+
return;
|
|
38
|
+
current.handle.close();
|
|
39
|
+
}
|
|
40
|
+
function onDocumentKeydown(e) {
|
|
41
|
+
if (!current)
|
|
42
|
+
return;
|
|
43
|
+
if (e.key !== 'Escape')
|
|
44
|
+
return;
|
|
45
|
+
e.stopPropagation();
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
current.handle.close();
|
|
48
|
+
}
|
|
49
|
+
function installDismissListeners() {
|
|
50
|
+
// `true` capture phase so we see the event before view-level handlers
|
|
51
|
+
// that might stopPropagation.
|
|
52
|
+
document.addEventListener('pointerdown', onDocumentPointerDown, true);
|
|
53
|
+
document.addEventListener('keydown', onDocumentKeydown, true);
|
|
54
|
+
}
|
|
55
|
+
function removeDismissListeners() {
|
|
56
|
+
document.removeEventListener('pointerdown', onDocumentPointerDown, true);
|
|
57
|
+
document.removeEventListener('keydown', onDocumentKeydown, true);
|
|
58
|
+
}
|
|
59
|
+
function removeEntry(entry) {
|
|
60
|
+
if (current !== entry)
|
|
61
|
+
return; // already closed / superseded
|
|
62
|
+
current = null;
|
|
63
|
+
removeDismissListeners();
|
|
64
|
+
unmount(entry.frame);
|
|
65
|
+
entry.host.remove();
|
|
66
|
+
}
|
|
67
|
+
function showPopup(Content, options, props) {
|
|
68
|
+
// Non-stacking: dismiss any existing popup first.
|
|
69
|
+
if (current)
|
|
70
|
+
removeEntry(current);
|
|
71
|
+
const root = getLayerRoot('popup');
|
|
72
|
+
const host = document.createElement('div');
|
|
73
|
+
host.className = 'sh3-popup-host';
|
|
74
|
+
host.style.position = 'absolute';
|
|
75
|
+
host.style.inset = '0';
|
|
76
|
+
host.style.pointerEvents = 'none'; // only the frame captures pointer events
|
|
77
|
+
root.appendChild(host);
|
|
78
|
+
const anchorRect = options.anchor.getBoundingClientRect();
|
|
79
|
+
const entry = {};
|
|
80
|
+
const handle = {
|
|
81
|
+
close: () => removeEntry(entry),
|
|
82
|
+
};
|
|
83
|
+
const frame = mount(PopupFrame, {
|
|
84
|
+
target: host,
|
|
85
|
+
props: {
|
|
86
|
+
Content: Content,
|
|
87
|
+
contentProps: (props !== null && props !== void 0 ? props : {}),
|
|
88
|
+
anchorRect,
|
|
89
|
+
close: handle.close,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
entry.host = host;
|
|
93
|
+
entry.frame = frame;
|
|
94
|
+
entry.handle = handle;
|
|
95
|
+
current = entry;
|
|
96
|
+
// Defer listener install by a microtask so the same pointerdown that
|
|
97
|
+
// opened the popup (from a button click) doesn't instantly dismiss it.
|
|
98
|
+
queueMicrotask(installDismissListeners);
|
|
99
|
+
return handle;
|
|
100
|
+
}
|
|
101
|
+
function closeCurrent() {
|
|
102
|
+
if (current)
|
|
103
|
+
removeEntry(current);
|
|
104
|
+
}
|
|
105
|
+
export const popupManager = {
|
|
106
|
+
show: showPopup,
|
|
107
|
+
close: closeCurrent,
|
|
108
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { OverlayLayer } from './types';
|
|
2
|
+
export declare function registerLayerRoot(layer: OverlayLayer, el: HTMLElement): void;
|
|
3
|
+
export declare function unregisterLayerRoot(layer: OverlayLayer): void;
|
|
4
|
+
export declare function getLayerRoot(layer: OverlayLayer): HTMLElement;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Layer root registry.
|
|
3
|
+
*
|
|
4
|
+
* Shell.svelte owns the six overlay root <div>s (one per layer). Overlay
|
|
5
|
+
* managers — which are plain TypeScript modules, not Svelte components —
|
|
6
|
+
* need DOM references to those roots so they can append transient content
|
|
7
|
+
* (modals, popups, toasts) into the correct stacking context.
|
|
8
|
+
*
|
|
9
|
+
* Shell.svelte calls `registerLayerRoot(name, el)` on each root during its
|
|
10
|
+
* own mount effect. Managers call `getLayerRoot(name)` lazily the first
|
|
11
|
+
* time they need to open something, which is always *after* Shell has
|
|
12
|
+
* rendered because overlay triggers originate from views mounted into the
|
|
13
|
+
* layout tree. No race; no DOM queries.
|
|
14
|
+
*
|
|
15
|
+
* Unregister is exposed too so tests and hot-reload can reset the registry.
|
|
16
|
+
*/
|
|
17
|
+
const roots = {};
|
|
18
|
+
export function registerLayerRoot(layer, el) {
|
|
19
|
+
roots[layer] = el;
|
|
20
|
+
}
|
|
21
|
+
export function unregisterLayerRoot(layer) {
|
|
22
|
+
delete roots[layer];
|
|
23
|
+
}
|
|
24
|
+
export function getLayerRoot(layer) {
|
|
25
|
+
const el = roots[layer];
|
|
26
|
+
if (!el) {
|
|
27
|
+
throw new Error(`Overlay layer "${layer}" root is not registered — ` +
|
|
28
|
+
`Shell.svelte must mount before opening overlays on this layer.`);
|
|
29
|
+
}
|
|
30
|
+
return el;
|
|
31
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Toast manager — auto-dismissing non-interactive notifications.
|
|
3
|
+
*
|
|
4
|
+
* Public API:
|
|
5
|
+
* toastManager.notify(message, options?) → ToastHandle
|
|
6
|
+
* toastManager.clear()
|
|
7
|
+
*
|
|
8
|
+
* Semantics (from docs/design/layout.md):
|
|
9
|
+
* - Toasts queue and stack vertically at the bottom-right of the shell.
|
|
10
|
+
* - Each toast auto-dismisses after `duration` ms (default 3000).
|
|
11
|
+
* `duration: Infinity` pins the toast until the caller calls close().
|
|
12
|
+
* - Clicking a toast dismisses it immediately.
|
|
13
|
+
* - Level styling: info / warn / error / success.
|
|
14
|
+
*
|
|
15
|
+
* Implementation notes:
|
|
16
|
+
* - A lazy layout container is appended to the layer-5 root on first
|
|
17
|
+
* notify; it's a flex column anchored to the bottom-right with a
|
|
18
|
+
* small gap between toasts.
|
|
19
|
+
* - Each toast is a standalone mount — simpler than a single list
|
|
20
|
+
* component tracking an array, and naturally handles independent
|
|
21
|
+
* fade-in animations per item.
|
|
22
|
+
* - The manager tracks entries for `clear()` and for idempotent close.
|
|
23
|
+
*/
|
|
24
|
+
import { mount, unmount } from 'svelte';
|
|
25
|
+
import ToastItem from './ToastItem.svelte';
|
|
26
|
+
import { getLayerRoot } from './roots';
|
|
27
|
+
const entries = new Set();
|
|
28
|
+
let container = null;
|
|
29
|
+
function ensureContainer() {
|
|
30
|
+
if (container && container.isConnected)
|
|
31
|
+
return container;
|
|
32
|
+
const root = getLayerRoot('toast');
|
|
33
|
+
container = document.createElement('div');
|
|
34
|
+
container.className = 'sh3-toast-stack';
|
|
35
|
+
container.style.position = 'absolute';
|
|
36
|
+
container.style.right = '16px';
|
|
37
|
+
container.style.bottom = '16px';
|
|
38
|
+
container.style.display = 'flex';
|
|
39
|
+
container.style.flexDirection = 'column-reverse';
|
|
40
|
+
container.style.gap = '6px';
|
|
41
|
+
container.style.pointerEvents = 'none';
|
|
42
|
+
root.appendChild(container);
|
|
43
|
+
return container;
|
|
44
|
+
}
|
|
45
|
+
function removeEntry(entry) {
|
|
46
|
+
if (!entries.has(entry))
|
|
47
|
+
return;
|
|
48
|
+
entries.delete(entry);
|
|
49
|
+
if (entry.timer !== null) {
|
|
50
|
+
clearTimeout(entry.timer);
|
|
51
|
+
entry.timer = null;
|
|
52
|
+
}
|
|
53
|
+
unmount(entry.instance);
|
|
54
|
+
entry.host.remove();
|
|
55
|
+
}
|
|
56
|
+
function notify(message, options = {}) {
|
|
57
|
+
var _a, _b;
|
|
58
|
+
const level = (_a = options.level) !== null && _a !== void 0 ? _a : 'info';
|
|
59
|
+
const duration = (_b = options.duration) !== null && _b !== void 0 ? _b : 3000;
|
|
60
|
+
const parent = ensureContainer();
|
|
61
|
+
const host = document.createElement('div');
|
|
62
|
+
host.className = 'sh3-toast-host';
|
|
63
|
+
parent.appendChild(host);
|
|
64
|
+
const entry = {};
|
|
65
|
+
const handle = {
|
|
66
|
+
close: () => removeEntry(entry),
|
|
67
|
+
};
|
|
68
|
+
const instance = mount(ToastItem, {
|
|
69
|
+
target: host,
|
|
70
|
+
props: {
|
|
71
|
+
message,
|
|
72
|
+
level,
|
|
73
|
+
close: handle.close,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
entry.host = host;
|
|
77
|
+
entry.instance = instance;
|
|
78
|
+
entry.handle = handle;
|
|
79
|
+
entry.timer =
|
|
80
|
+
Number.isFinite(duration) && duration > 0
|
|
81
|
+
? setTimeout(() => removeEntry(entry), duration)
|
|
82
|
+
: null;
|
|
83
|
+
entries.add(entry);
|
|
84
|
+
return handle;
|
|
85
|
+
}
|
|
86
|
+
function clear() {
|
|
87
|
+
for (const entry of [...entries])
|
|
88
|
+
removeEntry(entry);
|
|
89
|
+
}
|
|
90
|
+
export const toastManager = {
|
|
91
|
+
notify,
|
|
92
|
+
clear,
|
|
93
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type OverlayLayer = 'floating' | 'drag-preview' | 'popup' | 'modal' | 'toast' | 'command';
|
|
2
|
+
/** A handle returned by every overlay opener. Calling close() is idempotent. */
|
|
3
|
+
export interface OverlayHandle {
|
|
4
|
+
close(): void;
|
|
5
|
+
}
|
|
6
|
+
export type ModalHandle = OverlayHandle;
|
|
7
|
+
export type PopupHandle = OverlayHandle;
|
|
8
|
+
export type ToastHandle = OverlayHandle;
|
|
9
|
+
export type ToastLevel = 'info' | 'warn' | 'error' | 'success';
|
|
10
|
+
/** Where a popup should sit relative to its anchor. Phase 5 ships bottom-start. */
|
|
11
|
+
export type PopupPlacement = 'bottom-start';
|
|
12
|
+
export interface PopupOptions {
|
|
13
|
+
anchor: HTMLElement;
|
|
14
|
+
placement?: PopupPlacement;
|
|
15
|
+
}
|
|
16
|
+
export interface ToastOptions {
|
|
17
|
+
level?: ToastLevel;
|
|
18
|
+
/** Auto-dismiss after this many ms. `Infinity` disables auto-dismiss. */
|
|
19
|
+
duration?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface ModalOptions {
|
|
22
|
+
/**
|
|
23
|
+
* Inline style string applied to the modal's dialog box. Lets callers
|
|
24
|
+
* override the default centered-auto-sized layout for custom sizing
|
|
25
|
+
* and positioning. Setting `position: absolute; top: …; left: …`
|
|
26
|
+
* escapes the frame's grid centering and places the box at the given
|
|
27
|
+
* viewport-relative coordinates. Useful for test scaffolding and for
|
|
28
|
+
* views like non-centered palettes or inspectors.
|
|
29
|
+
*/
|
|
30
|
+
boxStyle?: string;
|
|
31
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Overlay API types — shared between layer managers.
|
|
3
|
+
*
|
|
4
|
+
* The "layer" concept comes from docs/design/layout.md: overlays live
|
|
5
|
+
* above the docked layout in an explicit numbered stack (0 = docked,
|
|
6
|
+
* 1 = floating, 2 = drag preview, 3 = popup, 4 = modal, 5 = toast,
|
|
7
|
+
* 6 = command palette). Phase 5 wires up layers 3/4/5; the rest are
|
|
8
|
+
* stubbed as DOM roots in Shell.svelte but have no managers yet.
|
|
9
|
+
*
|
|
10
|
+
* No component in the codebase writes a z-index directly — the only
|
|
11
|
+
* z-index values live on the overlay root divs in Shell.svelte driven
|
|
12
|
+
* by the --shell-z-layer-N tokens. Overlay content is appended into the
|
|
13
|
+
* corresponding layer root and inherits its stacking context.
|
|
14
|
+
*/
|
|
15
|
+
export {};
|
|
File without changes
|