@adia-ai/web-components 0.0.25 → 0.0.27
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/components/agent-artifact/agent-artifact.a2ui.json +1 -1
- package/components/agent-artifact/agent-artifact.css +11 -0
- package/components/agent-artifact/agent-artifact.js +23 -2
- package/components/agent-artifact/agent-artifact.yaml +1 -1
- package/components/agent-reasoning/agent-reasoning.css +11 -0
- package/components/agent-reasoning/agent-reasoning.js +16 -0
- package/components/agent-trace/agent-trace.css +19 -0
- package/components/alert/alert.a2ui.json +10 -4
- package/components/alert/alert.css +13 -0
- package/components/alert/alert.js +1 -1
- package/components/alert/alert.yaml +21 -4
- package/components/badge/badge.a2ui.json +0 -2
- package/components/badge/badge.css +20 -0
- package/components/badge/badge.js +1 -1
- package/components/badge/badge.yaml +0 -2
- package/components/calendar-picker/calendar-picker.css +17 -0
- package/components/code/code.css +41 -0
- package/components/code/code.js +44 -3
- package/components/empty-state/empty-state.js +32 -21
- package/components/index.js +0 -1
- package/components/list/list.js +20 -16
- package/components/menu/menu.css +18 -0
- package/components/menu/menu.js +24 -10
- package/components/pane/pane.css +5 -0
- package/components/pipeline-status/pipeline-status.css +15 -1
- package/components/popover/popover.css +17 -0
- package/components/select/select.css +18 -0
- package/components/swiper/swiper.css +9 -0
- package/components/table/table.css +5 -0
- package/components/table/table.js +45 -1
- package/components/table-toolbar/table-toolbar.css +13 -0
- package/components/tag/tag.css +10 -0
- package/components/timeline/timeline.css +10 -3
- package/components/toast/toast.css +93 -48
- package/components/toast/toast.js +101 -22
- package/components/toolbar/toolbar.css +13 -0
- package/components/tooltip/tooltip.css +8 -0
- package/package.json +1 -1
- package/patterns/app-shell/app-shell.css +0 -12
- package/styles/colors/semantics.css +1 -1
- package/styles/components.css +0 -1
- package/components/app-shell/app-shell.a2ui.json +0 -136
- package/components/app-shell/app-shell.css +0 -16
- package/components/app-shell/app-shell.js +0 -202
- package/components/app-shell/app-shell.yaml +0 -183
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* <toast-ui> — Notification popup
|
|
2
|
+
* <toast-ui> — Notification popup wired through a shared top-layer
|
|
3
|
+
* messaging channel.
|
|
3
4
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
5
|
+
* All toasts — declarative AND imperative — route through one
|
|
6
|
+
* lazily-mounted `[data-toast-container][data-toast-position]` per
|
|
7
|
+
* position. Per-position singletons; consumers can post from anywhere
|
|
8
|
+
* without holding a reference to the toast component:
|
|
6
9
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
10
|
+
* // Imperative API
|
|
11
|
+
* AdiaToast.show({ text: 'Saved!', variant: 'success' });
|
|
9
12
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
13
|
+
* // Global event channel — same shape, dispatched on `window`.
|
|
14
|
+
* // Any code (other components, integration scripts) can post
|
|
15
|
+
* // without importing AdiaToast.
|
|
16
|
+
* window.dispatchEvent(new CustomEvent('toast', {
|
|
17
|
+
* detail: { text: 'Saved!', variant: 'success' }
|
|
18
|
+
* }));
|
|
19
|
+
*
|
|
20
|
+
* // Declarative — auto-routes to the per-position container on
|
|
21
|
+
* // connect. No need to author <toast-ui> inside the container.
|
|
22
|
+
* <toast-ui text="Saved!" variant="success"></toast-ui>
|
|
12
23
|
*
|
|
13
24
|
* Events:
|
|
14
25
|
* close — fired after the toast finishes its exit animation
|
|
@@ -21,6 +32,7 @@ class AdiaToast extends AdiaElement {
|
|
|
21
32
|
#removing = false;
|
|
22
33
|
#closeTimer = null;
|
|
23
34
|
#openRaf = null;
|
|
35
|
+
#routed = false;
|
|
24
36
|
|
|
25
37
|
static properties = {
|
|
26
38
|
text: { type: String, default: '', reflect: true },
|
|
@@ -31,25 +43,27 @@ class AdiaToast extends AdiaElement {
|
|
|
31
43
|
|
|
32
44
|
static parts = {
|
|
33
45
|
message: '<div slot="message"></div>',
|
|
34
|
-
close: '<
|
|
46
|
+
close: '<button-ui slot="close" icon="x" variant="ghost" size="sm" aria-label="Dismiss"></button-ui>',
|
|
35
47
|
};
|
|
36
48
|
|
|
37
49
|
static template = () => html``;
|
|
38
50
|
|
|
39
|
-
#
|
|
51
|
+
#onPress = (e) => {
|
|
40
52
|
if (e.target.closest('[slot="close"]')) this.dismiss();
|
|
41
53
|
};
|
|
42
54
|
|
|
43
|
-
#onKeydown = (e) => {
|
|
44
|
-
if (e.target.closest('[slot="close"]') && (e.key === 'Enter' || e.key === ' ')) {
|
|
45
|
-
e.preventDefault();
|
|
46
|
-
this.dismiss();
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
|
|
50
55
|
connected() {
|
|
51
|
-
this.addEventListener('
|
|
52
|
-
|
|
56
|
+
this.addEventListener('press', this.#onPress);
|
|
57
|
+
|
|
58
|
+
// Route declarative <toast-ui> instances into the shared per-position
|
|
59
|
+
// container so authored toasts share the same lane as imperatively-
|
|
60
|
+
// posted ones (no overlap, consistent stacking). Skipped if we're
|
|
61
|
+
// already inside a container (re-entrant connect after reparent).
|
|
62
|
+
if (!this.#routed && !this.parentElement?.matches?.('[data-toast-container]')) {
|
|
63
|
+
this.#routed = true;
|
|
64
|
+
const container = AdiaToast.#getContainer(this.position || 'bottom-right');
|
|
65
|
+
if (this.parentElement !== container) container.appendChild(this);
|
|
66
|
+
}
|
|
53
67
|
}
|
|
54
68
|
|
|
55
69
|
#getDuration() {
|
|
@@ -97,14 +111,19 @@ class AdiaToast extends AdiaElement {
|
|
|
97
111
|
this.#closeTimer = setTimeout(() => {
|
|
98
112
|
this.#closeTimer = null;
|
|
99
113
|
this.removeAttribute('data-closing');
|
|
114
|
+
const container = this.parentElement;
|
|
100
115
|
this.dispatchEvent(new Event('close', { bubbles: true }));
|
|
101
116
|
this.remove();
|
|
117
|
+
/* If the lane is now empty, hide its popover and remove the
|
|
118
|
+
container — keeps document.body free of leaked containers. */
|
|
119
|
+
if (container?.matches?.('[data-toast-container]')) {
|
|
120
|
+
AdiaToast.#releaseContainerIfEmpty(container);
|
|
121
|
+
}
|
|
102
122
|
}, this.#getDuration());
|
|
103
123
|
}
|
|
104
124
|
|
|
105
125
|
disconnected() {
|
|
106
|
-
this.removeEventListener('
|
|
107
|
-
this.removeEventListener('keydown', this.#onKeydown);
|
|
126
|
+
this.removeEventListener('press', this.#onPress);
|
|
108
127
|
if (this.#timer) { clearTimeout(this.#timer); this.#timer = null; }
|
|
109
128
|
if (this.#closeTimer) { clearTimeout(this.#closeTimer); this.#closeTimer = null; }
|
|
110
129
|
if (this.#openRaf != null) { cancelAnimationFrame(this.#openRaf); this.#openRaf = null; }
|
|
@@ -121,16 +140,58 @@ class AdiaToast extends AdiaElement {
|
|
|
121
140
|
*/
|
|
122
141
|
static #containers = new Map();
|
|
123
142
|
|
|
143
|
+
/**
|
|
144
|
+
* Get (or lazily create) the per-position lane. Each lane is a manual
|
|
145
|
+
* Popover-API container — `[popover="manual"]` puts it in the browser's
|
|
146
|
+
* top-layer, above ALL page content with no z-index wars, and the
|
|
147
|
+
* native popover stack lets multiple lanes coexist (e.g. one toast in
|
|
148
|
+
* top-right + another in bottom-center) without collision.
|
|
149
|
+
*
|
|
150
|
+
* `popover="manual"` (not `auto`) — toasts must NOT light-dismiss on
|
|
151
|
+
* outside click; they auto-fade by their own duration timer.
|
|
152
|
+
*
|
|
153
|
+
* Falls back gracefully if the Popover API is unsupported (Safari < 17 /
|
|
154
|
+
* Firefox < 125): the lane still renders as a `position: fixed` div via
|
|
155
|
+
* the CSS `[data-toast-container]` rules. Browser baseline (Chromium
|
|
156
|
+
* 125+, Safari 18.0+, Firefox 129+) all support `[popover]`.
|
|
157
|
+
*/
|
|
124
158
|
static #getContainer(position) {
|
|
125
|
-
|
|
126
|
-
|
|
159
|
+
let el = AdiaToast.#containers.get(position);
|
|
160
|
+
if (el && el.isConnected) return el;
|
|
161
|
+
el = document.createElement('div');
|
|
127
162
|
el.setAttribute('data-toast-container', position);
|
|
163
|
+
/* `manual` = no light-dismiss; container stays open until we
|
|
164
|
+
explicitly hidePopover() it when its last toast leaves. */
|
|
165
|
+
if ('popover' in HTMLElement.prototype) {
|
|
166
|
+
el.setAttribute('popover', 'manual');
|
|
167
|
+
}
|
|
128
168
|
document.body.appendChild(el);
|
|
169
|
+
/* Show the popover so the lane lifts into the top-layer. Wrapped in
|
|
170
|
+
try/catch because some test rigs (happy-dom) don't ship the API. */
|
|
171
|
+
try { el.showPopover?.(); } catch { /* graceful fallback to fixed */ }
|
|
129
172
|
AdiaToast.#containers.set(position, el);
|
|
130
173
|
return el;
|
|
131
174
|
}
|
|
132
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Tear down the per-position lane when its last toast has been
|
|
178
|
+
* dismissed. Keeps `document.body` clean — addresses the audit's L-B4
|
|
179
|
+
* "container leak" finding (toasts permanently leaving DIVs in body).
|
|
180
|
+
*/
|
|
181
|
+
static #releaseContainerIfEmpty(container) {
|
|
182
|
+
if (!container) return;
|
|
183
|
+
if (container.children.length > 0) return;
|
|
184
|
+
try { container.hidePopover?.(); } catch { /* noop */ }
|
|
185
|
+
container.remove();
|
|
186
|
+
/* Clear from the singleton map so the next post() rebuilds. */
|
|
187
|
+
for (const [pos, el] of AdiaToast.#containers) {
|
|
188
|
+
if (el === container) AdiaToast.#containers.delete(pos);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
133
192
|
static show({ text, variant = 'info', duration = 4000, position = 'bottom-right' } = {}) {
|
|
193
|
+
/* `error` is a documented alias of `danger` (Phase 6 variant table). */
|
|
194
|
+
if (variant === 'error') variant = 'danger';
|
|
134
195
|
const container = AdiaToast.#getContainer(position);
|
|
135
196
|
const toast = document.createElement('toast-ui');
|
|
136
197
|
toast.text = text;
|
|
@@ -140,7 +201,25 @@ class AdiaToast extends AdiaElement {
|
|
|
140
201
|
container.appendChild(toast);
|
|
141
202
|
return toast;
|
|
142
203
|
}
|
|
204
|
+
|
|
143
205
|
}
|
|
206
|
+
|
|
207
|
+
/* Install the global 'toast' CustomEvent listener once, at module load,
|
|
208
|
+
so any code can post into the channel without importing AdiaToast
|
|
209
|
+
directly:
|
|
210
|
+
|
|
211
|
+
window.dispatchEvent(new CustomEvent('toast', {
|
|
212
|
+
detail: { text: 'Saved!', variant: 'success' }
|
|
213
|
+
}));
|
|
214
|
+
|
|
215
|
+
Idempotent — guarded by a window flag so HMR / re-imports are safe. */
|
|
216
|
+
if (typeof window !== 'undefined' && !window.__adiaToastListenerInstalled) {
|
|
217
|
+
window.__adiaToastListenerInstalled = true;
|
|
218
|
+
window.addEventListener('toast', (e) => {
|
|
219
|
+
if (e?.detail && typeof e.detail === 'object') AdiaToast.show(e.detail);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
144
223
|
customElements.define('toast-ui', AdiaToast);
|
|
145
224
|
|
|
146
225
|
export { AdiaToast };
|
|
@@ -109,6 +109,19 @@ toolbar-ui [data-toolbar-spillover-menu]:popover-open {
|
|
|
109
109
|
display: flex;
|
|
110
110
|
flex-direction: column;
|
|
111
111
|
gap: var(--a-space-1);
|
|
112
|
+
/* Fade + lift in on first paint. */
|
|
113
|
+
opacity: 1;
|
|
114
|
+
translate: 0 0;
|
|
115
|
+
transition: opacity var(--a-duration-fast) var(--a-easing-out),
|
|
116
|
+
translate var(--a-duration-fast) var(--a-easing-out);
|
|
117
|
+
@starting-style {
|
|
118
|
+
opacity: 0;
|
|
119
|
+
translate: 0 -4px;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@media (prefers-reduced-motion: reduce) {
|
|
124
|
+
toolbar-ui [data-toolbar-spillover-menu]:popover-open { transition: none; }
|
|
112
125
|
}
|
|
113
126
|
|
|
114
127
|
/* Inside the spillover, render as a vertical list — every item (groups and
|
|
@@ -36,12 +36,20 @@
|
|
|
36
36
|
box-shadow: var(--a-shadow-sm);
|
|
37
37
|
white-space: nowrap;
|
|
38
38
|
pointer-events: none;
|
|
39
|
+
/* Fade in on first paint via @starting-style. */
|
|
40
|
+
opacity: 1;
|
|
41
|
+
transition: opacity var(--a-duration-fast) var(--a-easing-out);
|
|
39
42
|
/* No z-index needed — Popover API renders in the top layer which
|
|
40
43
|
has its own stacking above all document content. */
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
.tooltip-popup:popover-open {
|
|
44
47
|
display: block;
|
|
48
|
+
@starting-style { opacity: 0; }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@media (prefers-reduced-motion: reduce) {
|
|
52
|
+
.tooltip-popup { transition: none; }
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
/* ── Pointer-follow mode (chart/heatmap) ──
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adia-ai/web-components",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.27",
|
|
4
4
|
"description": "AdiaUI web components — vanilla custom elements. A2UI runtime (renderer, registry, streams, wiring) lives in @adia-ai/a2ui-utils.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -1,18 +1,6 @@
|
|
|
1
1
|
/* ═══════════════════════════════════════════════════════════════
|
|
2
2
|
App Shell — <app-shell-ui> pattern component CSS
|
|
3
3
|
|
|
4
|
-
⚠ DEPRECATED PATH (2026-04-29): preserved as an alias for one
|
|
5
|
-
release while consumers migrate. The CSS now lives behind the
|
|
6
|
-
<app-shell-ui> custom element at
|
|
7
|
-
packages/web-components/components/app-shell/ and ships via
|
|
8
|
-
packages/web-components/styles/components.css.
|
|
9
|
-
|
|
10
|
-
Migration: drop the explicit <link rel="stylesheet"> to this file —
|
|
11
|
-
styles/components.css now bundles the same content via the new
|
|
12
|
-
component.
|
|
13
|
-
|
|
14
|
-
Companion ADR: ../../../../.brain/adrs/0009-promote-app-shell-and-page-to-components.md.
|
|
15
|
-
|
|
16
4
|
Split into constituent parts for maintainability.
|
|
17
5
|
Import order matters: tokens first, then structure, then overrides.
|
|
18
6
|
═══════════════════════════════════════════════════════════════ */
|
|
@@ -176,7 +176,7 @@
|
|
|
176
176
|
--a-primary-bg-disabled: var(--a-accent-muted);
|
|
177
177
|
--a-primary-bg-invalid: var(--a-danger-muted);
|
|
178
178
|
|
|
179
|
-
--a-primary-fg: var(--a-accent-
|
|
179
|
+
--a-primary-fg: var(--a-accent-05-tint);
|
|
180
180
|
--a-primary-fg-hover: var(--a-accent-text-strong);
|
|
181
181
|
--a-primary-fg-active: var(--a-accent-text-strong);
|
|
182
182
|
--a-primary-fg-selected: var(--a-accent-text-strong);
|
package/styles/components.css
CHANGED
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
@import "../components/range/range.css";
|
|
27
27
|
@import "../components/tree/tree.css";
|
|
28
28
|
@import "../components/pane/pane.css";
|
|
29
|
-
@import "../components/app-shell/app-shell.css";
|
|
30
29
|
@import "../components/page/page.css";
|
|
31
30
|
@import "../components/chat/chat-input.css";
|
|
32
31
|
@import "../components/chat/chat.css";
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://adiaui.dev/a2ui/v0_9/components/AppShell.json",
|
|
4
|
-
"title": "AppShell",
|
|
5
|
-
"description": "Application shell layout. Holds a leading nav rail, a main column\n(topbar + scroll area + statusbar), an optional trailing inspector,\nand an optional command-palette `<dialog>`. The component wires\nsidebar toggle, sidebar resize, and Cmd+K shortcut on top of the\nlong-lived CSS pattern that already targets `app-shell-ui` as a tag.\nChildren use native HTML semantic elements (`<aside data-sidebar>`,\n`<main>`, `<header>`, `<section>`, `<footer>`); the existing\nCSS scopes by tag.\n",
|
|
6
|
-
"type": "object",
|
|
7
|
-
"allOf": [
|
|
8
|
-
{
|
|
9
|
-
"$ref": "common_types.json#/$defs/ComponentCommon"
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
"$ref": "common_types.json#/$defs/CatalogComponentCommon"
|
|
13
|
-
}
|
|
14
|
-
],
|
|
15
|
-
"properties": {
|
|
16
|
-
"cmdK": {
|
|
17
|
-
"description": "Installs a global Cmd+K / Ctrl+K listener that opens the inner\n`<dialog data-command>` via `showModal()`. No-op when no such\ndialog is present.\n",
|
|
18
|
-
"type": "boolean",
|
|
19
|
-
"default": false
|
|
20
|
-
},
|
|
21
|
-
"component": {
|
|
22
|
-
"const": "AppShell"
|
|
23
|
-
},
|
|
24
|
-
"leadingCollapsed": {
|
|
25
|
-
"description": "Reflects to the leading aside's `[data-collapsed]`. Toggled\nprogrammatically (`shell.toggleLeading()`) or via a click on any\nelement with `[data-sidebar-toggle=\"leading\"]` inside the shell.\n",
|
|
26
|
-
"type": "boolean",
|
|
27
|
-
"default": false
|
|
28
|
-
},
|
|
29
|
-
"leadingMaxWidth": {
|
|
30
|
-
"description": "Maximum leading-aside width (px) when resizing.",
|
|
31
|
-
"type": "number",
|
|
32
|
-
"default": 480
|
|
33
|
-
},
|
|
34
|
-
"leadingMinWidth": {
|
|
35
|
-
"description": "Minimum leading-aside width (px) when resizing.",
|
|
36
|
-
"type": "number",
|
|
37
|
-
"default": 48
|
|
38
|
-
},
|
|
39
|
-
"mode": {
|
|
40
|
-
"description": "Space-separated list of layout modes. `rounded` adds border-radius\nto the scroll section; `borderless` removes chrome borders (content\nborders persist). Pass both for a soft, edgeless shell.\n",
|
|
41
|
-
"type": "string",
|
|
42
|
-
"default": ""
|
|
43
|
-
},
|
|
44
|
-
"trailingCollapsed": {
|
|
45
|
-
"description": "Reflects to the trailing aside's `[data-collapsed]`. Toggled via\n`shell.toggleTrailing()` or `[data-sidebar-toggle=\"trailing\"]`.\n",
|
|
46
|
-
"type": "boolean",
|
|
47
|
-
"default": false
|
|
48
|
-
},
|
|
49
|
-
"trailingMaxWidth": {
|
|
50
|
-
"description": "Maximum trailing-aside width (px) when resizing.",
|
|
51
|
-
"type": "number",
|
|
52
|
-
"default": 480
|
|
53
|
-
},
|
|
54
|
-
"trailingMinWidth": {
|
|
55
|
-
"description": "Minimum trailing-aside width (px) when resizing.",
|
|
56
|
-
"type": "number",
|
|
57
|
-
"default": 48
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
"required": [
|
|
61
|
-
"component"
|
|
62
|
-
],
|
|
63
|
-
"unevaluatedProperties": false,
|
|
64
|
-
"x-adiaui": {
|
|
65
|
-
"anti_patterns": [],
|
|
66
|
-
"category": "container",
|
|
67
|
-
"events": {
|
|
68
|
-
"command-close": {
|
|
69
|
-
"description": "Fired after the command-palette `<dialog>` closes."
|
|
70
|
-
},
|
|
71
|
-
"command-open": {
|
|
72
|
-
"description": "Fired after the command-palette `<dialog>` opens."
|
|
73
|
-
},
|
|
74
|
-
"sidebar-toggle": {
|
|
75
|
-
"description": "Fired after a sidebar's collapsed state flips. Detail:\n`{ side: 'leading'|'trailing', collapsed: boolean }`.\n"
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
|
-
"examples": [
|
|
79
|
-
{
|
|
80
|
-
"description": "Documentation shell — leading nav, topbar with breadcrumb, scroll content, statusbar.",
|
|
81
|
-
"a2ui": "[\n {\n \"id\": \"root\",\n \"component\": \"AppShell\",\n \"mode\": \"rounded\",\n \"cmdK\": true,\n \"children\": [\"leading\", \"main\", \"cmd\"]\n },\n {\n \"id\": \"leading\",\n \"component\": \"Aside\",\n \"data-sidebar\": \"leading\",\n \"children\": [\"nav-hdr\", \"nav-list\", \"nav-ftr\"]\n },\n {\n \"id\": \"nav-hdr\",\n \"component\": \"Header\",\n \"children\": []\n },\n {\n \"id\": \"nav-list\",\n \"component\": \"Section\",\n \"children\": []\n },\n {\n \"id\": \"nav-ftr\",\n \"component\": \"Footer\",\n \"children\": []\n },\n {\n \"id\": \"main\",\n \"component\": \"Column\",\n \"children\": [\"topbar\", \"scroll\", \"statusbar\"]\n },\n {\n \"id\": \"topbar\",\n \"component\": \"Header\",\n \"children\": []\n },\n {\n \"id\": \"scroll\",\n \"component\": \"Section\",\n \"scroll\": true,\n \"children\": []\n },\n {\n \"id\": \"statusbar\",\n \"component\": \"Footer\",\n \"children\": []\n },\n {\n \"id\": \"cmd\",\n \"component\": \"Modal\",\n \"data-command\": true,\n \"children\": []\n }\n]",
|
|
82
|
-
"name": "docs-shell"
|
|
83
|
-
}
|
|
84
|
-
],
|
|
85
|
-
"keywords": [
|
|
86
|
-
"app-shell",
|
|
87
|
-
"shell",
|
|
88
|
-
"layout",
|
|
89
|
-
"sidebar",
|
|
90
|
-
"topbar",
|
|
91
|
-
"command-palette",
|
|
92
|
-
"cmdK",
|
|
93
|
-
"dashboard-shell"
|
|
94
|
-
],
|
|
95
|
-
"name": "AdiaAppShell",
|
|
96
|
-
"related": [
|
|
97
|
-
"aside",
|
|
98
|
-
"header",
|
|
99
|
-
"footer",
|
|
100
|
-
"section",
|
|
101
|
-
"drawer",
|
|
102
|
-
"modal",
|
|
103
|
-
"command",
|
|
104
|
-
"app-nav",
|
|
105
|
-
"section-nav"
|
|
106
|
-
],
|
|
107
|
-
"slots": {
|
|
108
|
-
"default": {
|
|
109
|
-
"description": "Composes from native HTML children: `<aside data-sidebar=\"leading\">`,\n`<main>` (with `<header>`, `<section>`, `<footer>` inside),\noptional `<aside data-sidebar=\"trailing\">`, optional\n`<dialog data-command>`. The existing pattern CSS scopes each\nchild by tag + attribute; no `slot=` attributes needed.\n"
|
|
110
|
-
}
|
|
111
|
-
},
|
|
112
|
-
"states": [
|
|
113
|
-
{
|
|
114
|
-
"description": "Default, ready for interaction.",
|
|
115
|
-
"name": "idle"
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
"description": "Leading aside collapsed via attribute / API.",
|
|
119
|
-
"name": "leading-collapsed"
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
"description": "Trailing aside collapsed via attribute / API.",
|
|
123
|
-
"name": "trailing-collapsed"
|
|
124
|
-
},
|
|
125
|
-
{
|
|
126
|
-
"description": "Command-palette dialog is showing.",
|
|
127
|
-
"name": "command-open"
|
|
128
|
-
}
|
|
129
|
-
],
|
|
130
|
-
"synonyms": {},
|
|
131
|
-
"tag": "app-shell-ui",
|
|
132
|
-
"tokens": {},
|
|
133
|
-
"traits": [],
|
|
134
|
-
"version": 1
|
|
135
|
-
}
|
|
136
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/* ═══════════════════════════════════════════════════════════════
|
|
2
|
-
<app-shell-ui> component CSS
|
|
3
|
-
|
|
4
|
-
The substantive CSS lives in `patterns/app-shell/css/*.css` —
|
|
5
|
-
that surface predates this component and already targets
|
|
6
|
-
`app-shell-ui` as a tag. We re-import it here so consumers
|
|
7
|
-
importing this component's CSS get the full styling without
|
|
8
|
-
duplication. A future cleanup may invert the relationship
|
|
9
|
-
(move the CSS into this folder and have the pattern import
|
|
10
|
-
from here, or retire the pattern folder entirely once all
|
|
11
|
-
consumers have migrated to the component).
|
|
12
|
-
|
|
13
|
-
Companion ADR: .brain/adrs/0009-promote-app-shell-and-page-to-components.md
|
|
14
|
-
═══════════════════════════════════════════════════════════════ */
|
|
15
|
-
|
|
16
|
-
@import "../../patterns/app-shell/app-shell.css";
|
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* <app-shell-ui> — Application shell layout component.
|
|
3
|
-
*
|
|
4
|
-
* Promotes the long-lived `patterns/app-shell/` CSS pattern (which already
|
|
5
|
-
* targeted `app-shell-ui` as a tag) to a real custom element. CSS continues
|
|
6
|
-
* to live in `patterns/app-shell/app-shell.css` (re-imported from this
|
|
7
|
-
* component's CSS); the JS layer adds:
|
|
8
|
-
*
|
|
9
|
-
* - sidebar toggle (delegated [data-sidebar-toggle="leading|trailing"])
|
|
10
|
-
* - sidebar resize (pointer drag on [data-resize])
|
|
11
|
-
* - command-palette wiring (delegated [data-command-trigger] +
|
|
12
|
-
* optional Cmd+K global shortcut when [cmd-k])
|
|
13
|
-
*
|
|
14
|
-
* Authoring shape (consumer markup, unchanged from the pre-existing pattern):
|
|
15
|
-
*
|
|
16
|
-
* <app-shell-ui mode="rounded borderless" cmd-k>
|
|
17
|
-
* <aside data-sidebar="leading"> <-- nav rail
|
|
18
|
-
* <header>...</header>
|
|
19
|
-
* <section>...</section>
|
|
20
|
-
* <footer>...</footer>
|
|
21
|
-
* <div data-resize></div>
|
|
22
|
-
* </aside>
|
|
23
|
-
* <main>
|
|
24
|
-
* <header>...</header> <-- topbar
|
|
25
|
-
* <section>...</section> <-- scroll area
|
|
26
|
-
* <footer>...</footer> <-- statusbar
|
|
27
|
-
* </main>
|
|
28
|
-
* <aside data-sidebar="trailing">...</aside> <!-- optional inspector -->
|
|
29
|
-
* <dialog data-command> <!-- optional cmd palette -->
|
|
30
|
-
* <command-ui>...</command-ui>
|
|
31
|
-
* </dialog>
|
|
32
|
-
* </app-shell-ui>
|
|
33
|
-
*
|
|
34
|
-
* Attributes:
|
|
35
|
-
* mode — space-separated list: "rounded" | "borderless" | both.
|
|
36
|
-
* leading-collapsed — boolean, reflects to leading aside's [data-collapsed].
|
|
37
|
-
* trailing-collapsed — boolean, reflects to trailing aside's [data-collapsed].
|
|
38
|
-
* cmd-k — boolean, installs Cmd+K / Ctrl+K global listener
|
|
39
|
-
* that opens the inner <dialog data-command> via
|
|
40
|
-
* showModal(). No-op when no such dialog is present.
|
|
41
|
-
* leading-min-width — minimum width when resizing leading sidebar (px).
|
|
42
|
-
* leading-max-width — maximum width (px).
|
|
43
|
-
* trailing-min-width / trailing-max-width — same for trailing.
|
|
44
|
-
*
|
|
45
|
-
* API:
|
|
46
|
-
* shell.toggleLeading() shell.toggleTrailing()
|
|
47
|
-
* shell.openCommand() shell.closeCommand()
|
|
48
|
-
*
|
|
49
|
-
* Events (bubbling):
|
|
50
|
-
* sidebar-toggle { detail: { side: 'leading'|'trailing', collapsed: boolean } }
|
|
51
|
-
* command-open no detail
|
|
52
|
-
* command-close no detail
|
|
53
|
-
*
|
|
54
|
-
* Spec: docs/specs/genui-multiturn-architecture.md (orthogonal — gen-UI uses
|
|
55
|
-
* this layer). ADR: .brain/adrs/0009-promote-app-shell-and-page-to-components.md.
|
|
56
|
-
*/
|
|
57
|
-
|
|
58
|
-
import { AdiaElement } from '../../core/element.js';
|
|
59
|
-
|
|
60
|
-
const DEFAULT_MIN_WIDTH = 48;
|
|
61
|
-
const DEFAULT_MAX_WIDTH = 480;
|
|
62
|
-
|
|
63
|
-
class AdiaAppShell extends AdiaElement {
|
|
64
|
-
static properties = {
|
|
65
|
-
mode: { type: String, default: '', reflect: true },
|
|
66
|
-
leadingCollapsed: { type: Boolean, default: false, attribute: 'leading-collapsed', reflect: true },
|
|
67
|
-
trailingCollapsed: { type: Boolean, default: false, attribute: 'trailing-collapsed', reflect: true },
|
|
68
|
-
cmdK: { type: Boolean, default: false, attribute: 'cmd-k', reflect: true },
|
|
69
|
-
leadingMinWidth: { type: Number, default: DEFAULT_MIN_WIDTH, attribute: 'leading-min-width' },
|
|
70
|
-
leadingMaxWidth: { type: Number, default: DEFAULT_MAX_WIDTH, attribute: 'leading-max-width' },
|
|
71
|
-
trailingMinWidth: { type: Number, default: DEFAULT_MIN_WIDTH, attribute: 'trailing-min-width' },
|
|
72
|
-
trailingMaxWidth: { type: Number, default: DEFAULT_MAX_WIDTH, attribute: 'trailing-max-width' },
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
static template = () => null;
|
|
76
|
-
|
|
77
|
-
#bound = false;
|
|
78
|
-
#drag = null; // { aside, startX, startW, side }
|
|
79
|
-
|
|
80
|
-
connected() {
|
|
81
|
-
if (!this.#bound) {
|
|
82
|
-
this.#bound = true;
|
|
83
|
-
this.addEventListener('click', this.#onClick);
|
|
84
|
-
this.addEventListener('pointerdown', this.#onPointerDown);
|
|
85
|
-
}
|
|
86
|
-
document.addEventListener('keydown', this.#onKeydown);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
disconnected() {
|
|
90
|
-
document.removeEventListener('keydown', this.#onKeydown);
|
|
91
|
-
document.removeEventListener('pointermove', this.#onResizeMove);
|
|
92
|
-
document.removeEventListener('pointerup', this.#onResizeUp);
|
|
93
|
-
this.#drag = null;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
render() {
|
|
97
|
-
const leading = this.querySelector(':scope > aside[data-sidebar="leading"]');
|
|
98
|
-
if (leading) leading.toggleAttribute('data-collapsed', !!this.leadingCollapsed);
|
|
99
|
-
const trailing = this.querySelector(':scope > aside[data-sidebar="trailing"]');
|
|
100
|
-
if (trailing) trailing.toggleAttribute('data-collapsed', !!this.trailingCollapsed);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// ── API ─────────────────────────────────────────────────────────
|
|
104
|
-
|
|
105
|
-
toggleLeading() {
|
|
106
|
-
this.leadingCollapsed = !this.leadingCollapsed;
|
|
107
|
-
this.dispatchEvent(new CustomEvent('sidebar-toggle', {
|
|
108
|
-
bubbles: true,
|
|
109
|
-
detail: { side: 'leading', collapsed: this.leadingCollapsed },
|
|
110
|
-
}));
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
toggleTrailing() {
|
|
114
|
-
this.trailingCollapsed = !this.trailingCollapsed;
|
|
115
|
-
this.dispatchEvent(new CustomEvent('sidebar-toggle', {
|
|
116
|
-
bubbles: true,
|
|
117
|
-
detail: { side: 'trailing', collapsed: this.trailingCollapsed },
|
|
118
|
-
}));
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
openCommand() {
|
|
122
|
-
const dialog = this.querySelector(':scope > dialog[data-command]');
|
|
123
|
-
if (!dialog || dialog.open) return;
|
|
124
|
-
dialog.showModal();
|
|
125
|
-
this.dispatchEvent(new CustomEvent('command-open', { bubbles: true }));
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
closeCommand() {
|
|
129
|
-
const dialog = this.querySelector(':scope > dialog[data-command]');
|
|
130
|
-
if (!dialog || !dialog.open) return;
|
|
131
|
-
dialog.close();
|
|
132
|
-
this.dispatchEvent(new CustomEvent('command-close', { bubbles: true }));
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// ── Delegated click ─────────────────────────────────────────────
|
|
136
|
-
|
|
137
|
-
#onClick = (e) => {
|
|
138
|
-
const toggle = e.target.closest('[data-sidebar-toggle]');
|
|
139
|
-
if (toggle && this.contains(toggle)) {
|
|
140
|
-
const side = toggle.getAttribute('data-sidebar-toggle');
|
|
141
|
-
if (side === 'leading') { this.toggleLeading(); return; }
|
|
142
|
-
if (side === 'trailing') { this.toggleTrailing(); return; }
|
|
143
|
-
}
|
|
144
|
-
const trigger = e.target.closest('[data-command-trigger]');
|
|
145
|
-
if (trigger && this.contains(trigger)) {
|
|
146
|
-
this.openCommand();
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
// ── Cmd+K / Ctrl+K ──────────────────────────────────────────────
|
|
151
|
-
|
|
152
|
-
#onKeydown = (e) => {
|
|
153
|
-
if (!this.cmdK) return;
|
|
154
|
-
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
|
|
155
|
-
e.preventDefault();
|
|
156
|
-
this.openCommand();
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
// ── Resize handle ───────────────────────────────────────────────
|
|
161
|
-
// Pointerdown on a [data-resize] inside one of the asides starts a drag.
|
|
162
|
-
// Leading aside grows when dragged right; trailing aside grows when
|
|
163
|
-
// dragged left (mirrors `<pane-ui>[side]` semantics).
|
|
164
|
-
|
|
165
|
-
#onPointerDown = (e) => {
|
|
166
|
-
const handle = e.target.closest('[data-resize]');
|
|
167
|
-
if (!handle) return;
|
|
168
|
-
const aside = handle.closest(':scope > aside[data-sidebar]');
|
|
169
|
-
if (!aside || aside.parentElement !== this) return;
|
|
170
|
-
|
|
171
|
-
const side = aside.getAttribute('data-sidebar'); // 'leading' | 'trailing'
|
|
172
|
-
this.#drag = {
|
|
173
|
-
aside,
|
|
174
|
-
side,
|
|
175
|
-
startX: e.clientX,
|
|
176
|
-
startW: aside.getBoundingClientRect().width,
|
|
177
|
-
};
|
|
178
|
-
handle.setPointerCapture?.(e.pointerId);
|
|
179
|
-
document.addEventListener('pointermove', this.#onResizeMove);
|
|
180
|
-
document.addEventListener('pointerup', this.#onResizeUp, { once: true });
|
|
181
|
-
e.preventDefault();
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
#onResizeMove = (e) => {
|
|
185
|
-
if (!this.#drag) return;
|
|
186
|
-
const { aside, side, startX, startW } = this.#drag;
|
|
187
|
-
const dx = e.clientX - startX;
|
|
188
|
-
const min = side === 'leading' ? this.leadingMinWidth : this.trailingMinWidth;
|
|
189
|
-
const max = side === 'leading' ? this.leadingMaxWidth : this.trailingMaxWidth;
|
|
190
|
-
const next = Math.max(min, Math.min(max, startW + (side === 'leading' ? dx : -dx)));
|
|
191
|
-
aside.style.width = `${next}px`;
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
#onResizeUp = () => {
|
|
195
|
-
this.#drag = null;
|
|
196
|
-
document.removeEventListener('pointermove', this.#onResizeMove);
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
customElements.define('app-shell-ui', AdiaAppShell);
|
|
201
|
-
|
|
202
|
-
export { AdiaAppShell };
|