@adia-ai/web-modules 0.3.5 → 0.4.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.
Files changed (41) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/chat/chat-shell/chat-shell.js +28 -40
  3. package/chat/chat-shell/css/chat-shell.empty.css +3 -3
  4. package/chat/chat-shell/css/chat-shell.layout.css +2 -2
  5. package/editor/editor-canvas/editor-canvas.a2ui.json +87 -0
  6. package/editor/editor-canvas/editor-canvas.examples.html +65 -0
  7. package/editor/editor-canvas/editor-canvas.html +43 -0
  8. package/editor/editor-canvas/editor-canvas.js +103 -0
  9. package/editor/editor-canvas/editor-canvas.test.js +100 -0
  10. package/editor/editor-canvas/editor-canvas.yaml +88 -0
  11. package/editor/editor-canvas-empty/editor-canvas-empty.a2ui.json +69 -0
  12. package/editor/editor-canvas-empty/editor-canvas-empty.examples.html +65 -0
  13. package/editor/editor-canvas-empty/editor-canvas-empty.html +42 -0
  14. package/editor/editor-canvas-empty/editor-canvas-empty.yaml +56 -0
  15. package/editor/editor-shell/css/editor-shell.bespoke.css +237 -0
  16. package/editor/editor-shell/css/editor-shell.layout.css +6 -6
  17. package/editor/editor-shell/editor-shell.css +1 -0
  18. package/editor/editor-shell/editor-shell.js +87 -30
  19. package/editor/editor-sidebar/editor-sidebar.a2ui.json +93 -0
  20. package/editor/editor-sidebar/editor-sidebar.examples.html +65 -0
  21. package/editor/editor-sidebar/editor-sidebar.html +43 -0
  22. package/editor/editor-sidebar/editor-sidebar.js +197 -0
  23. package/editor/editor-sidebar/editor-sidebar.test.js +145 -0
  24. package/editor/editor-sidebar/editor-sidebar.yaml +91 -0
  25. package/editor/editor-statusbar/editor-statusbar.a2ui.json +76 -0
  26. package/editor/editor-statusbar/editor-statusbar.examples.html +65 -0
  27. package/editor/editor-statusbar/editor-statusbar.html +42 -0
  28. package/editor/editor-statusbar/editor-statusbar.yaml +57 -0
  29. package/editor/editor-toolbar/editor-toolbar.a2ui.json +96 -0
  30. package/editor/editor-toolbar/editor-toolbar.examples.html +65 -0
  31. package/editor/editor-toolbar/editor-toolbar.html +43 -0
  32. package/editor/editor-toolbar/editor-toolbar.js +58 -0
  33. package/editor/editor-toolbar/editor-toolbar.test.js +99 -0
  34. package/editor/editor-toolbar/editor-toolbar.yaml +81 -0
  35. package/editor/index.js +3 -0
  36. package/package.json +4 -4
  37. package/shell/admin-shell/admin-shell.js +27 -243
  38. package/shell/admin-shell/css/admin-shell.bespoke.css +22 -26
  39. package/shell/admin-shell/css/admin-shell.main.css +2 -2
  40. package/shell/admin-shell/css/admin-shell.shell.css +2 -2
  41. package/shell/admin-shell/css/admin-shell.sidebar.css +35 -33
@@ -0,0 +1,99 @@
1
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
2
+ import '../../../web-components/core/element.js';
3
+ import './editor-toolbar.js';
4
+
5
+ const tick = () => new Promise((r) => queueMicrotask(r));
6
+
7
+ function mount(html) {
8
+ const wrap = document.createElement('div');
9
+ wrap.innerHTML = html;
10
+ document.body.appendChild(wrap);
11
+ return wrap.firstElementChild;
12
+ }
13
+
14
+ beforeEach(() => {
15
+ document.body.innerHTML = '';
16
+ });
17
+
18
+ describe('editor-toolbar', () => {
19
+ it('registers editor-toolbar as a custom element', () => {
20
+ expect(customElements.get('editor-toolbar')).toBeDefined();
21
+ });
22
+
23
+ it('defaults to fullScreen=false', () => {
24
+ const t = mount('<editor-toolbar></editor-toolbar>');
25
+ expect(t.fullScreen).toBe(false);
26
+ });
27
+
28
+ it('reflects [full-screen] via property assignment', async () => {
29
+ const t = mount('<editor-toolbar></editor-toolbar>');
30
+ t.fullScreen = true;
31
+ await tick();
32
+ expect(t.hasAttribute('full-screen')).toBe(true);
33
+ });
34
+
35
+ it('honors initial [full-screen] attribute on connect', () => {
36
+ const t = mount('<editor-toolbar full-screen></editor-toolbar>');
37
+ expect(t.fullScreen).toBe(true);
38
+ });
39
+
40
+ it('bubbles toolbar-action event from [data-toolbar-action] children', async () => {
41
+ const wrap = document.createElement('div');
42
+ wrap.innerHTML = `<editor-toolbar>
43
+ <button data-toolbar-action="save">Save</button>
44
+ <button data-toolbar-action="undo">Undo</button>
45
+ </editor-toolbar>`;
46
+ document.body.appendChild(wrap);
47
+ const toolbar = wrap.firstElementChild;
48
+ const saveBtn = toolbar.querySelector('[data-toolbar-action="save"]');
49
+
50
+ const onAction = vi.fn();
51
+ toolbar.addEventListener('toolbar-action', onAction);
52
+
53
+ saveBtn.click();
54
+ await tick();
55
+
56
+ expect(onAction).toHaveBeenCalledTimes(1);
57
+ expect(onAction.mock.calls[0][0].detail).toEqual({ name: 'save' });
58
+ });
59
+
60
+ it('does not fire toolbar-action for clicks outside [data-toolbar-action]', async () => {
61
+ const wrap = document.createElement('div');
62
+ wrap.innerHTML = `<editor-toolbar>
63
+ <span>Title</span>
64
+ <button>Plain button</button>
65
+ </editor-toolbar>`;
66
+ document.body.appendChild(wrap);
67
+ const toolbar = wrap.firstElementChild;
68
+ const plainBtn = toolbar.querySelector('button');
69
+
70
+ const onAction = vi.fn();
71
+ toolbar.addEventListener('toolbar-action', onAction);
72
+
73
+ plainBtn.click();
74
+ await tick();
75
+
76
+ expect(onAction).not.toHaveBeenCalled();
77
+ });
78
+
79
+ it('cleanup on disconnect — removes click listener', () => {
80
+ const t = mount('<editor-toolbar></editor-toolbar>');
81
+ const removeSpy = vi.spyOn(t, 'removeEventListener');
82
+ t.remove();
83
+ const removedTypes = removeSpy.mock.calls.map((args) => args[0]);
84
+ expect(removedTypes).toContain('click');
85
+ });
86
+
87
+ it('event bubbles up beyond the toolbar', async () => {
88
+ const outer = document.createElement('div');
89
+ document.body.appendChild(outer);
90
+ outer.innerHTML = `<editor-toolbar>
91
+ <button data-toolbar-action="settings"></button>
92
+ </editor-toolbar>`;
93
+ const onOuterAction = vi.fn();
94
+ outer.addEventListener('toolbar-action', onOuterAction);
95
+ outer.querySelector('button').click();
96
+ await tick();
97
+ expect(onOuterAction).toHaveBeenCalled();
98
+ });
99
+ });
@@ -0,0 +1,81 @@
1
+ # Edit this file; run `npm run build:components` to regenerate a2ui.json.
2
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
3
+ name: EditorToolbar
4
+ tag: editor-toolbar
5
+ component: EditorToolbar
6
+ category: layout
7
+ version: 1
8
+ description: |
9
+ Module-tier editor toolbar — replaces legacy <header> chrome bar
10
+ inside <editor-shell> per ADR-0023. Owns the [full-screen] reflected
11
+ attribute (set when host enters focus mode), click-bubble for
12
+ [data-toolbar-action] buttons, and slot vocabulary routing.
13
+
14
+ Sits at the top of <editor-shell>. Authors compose actions + status
15
+ via slot vocabulary. The host (<editor-shell>) reads either
16
+ <editor-toolbar> or <header> via :is() selector for backwards compat.
17
+
18
+ props:
19
+ fullScreen:
20
+ description: Reflected — set when editor is in distraction-free / focus mode.
21
+ type: boolean
22
+ default: false
23
+ reflect: true
24
+ attribute: full-screen
25
+
26
+ events:
27
+ toolbar-action:
28
+ description: >-
29
+ Bubbles when a child element with [data-toolbar-action] is clicked.
30
+ Detail carries the action name from the clicked element's attribute.
31
+ detail:
32
+ name: string
33
+
34
+ slots:
35
+ default:
36
+ description: Default — ad-hoc inline content (legacy positional layout).
37
+ title:
38
+ description: Editor / document title cluster.
39
+ status:
40
+ description: Status indicator (saving, dirty, synced, etc.).
41
+ action:
42
+ description: Trailing action cluster (settings, share, more).
43
+ action-leading:
44
+ description: Leading action cluster (back, switcher, undo/redo).
45
+
46
+ states:
47
+ - name: idle
48
+ description: Default editor mode.
49
+ - name: full-screen
50
+ attribute: full-screen
51
+ description: Distraction-free / focus mode active.
52
+
53
+ traits: []
54
+
55
+ a2ui:
56
+ rules:
57
+ - >-
58
+ editor-toolbar replaces legacy <header> chrome bar inside
59
+ <editor-shell>. Use named slots (title / status / action /
60
+ action-leading) for canonical clusters; ad-hoc inline content
61
+ goes in the default slot.
62
+ - >-
63
+ Buttons that should trigger named actions get
64
+ [data-toolbar-action="<name>"]. The toolbar bubbles a single
65
+ 'toolbar-action' event up to the host with the name in detail.
66
+
67
+ keywords:
68
+ - editor-toolbar
69
+ - editor-titlebar
70
+ - editor-chrome
71
+ - app-header
72
+ - editor-actions
73
+
74
+ synonyms:
75
+ editor-toolbar: [editor-header, app-header, navbar, titlebar]
76
+
77
+ related:
78
+ - EditorShell
79
+ - EditorCanvas
80
+ - EditorSidebar
81
+ - EditorStatusbar
package/editor/index.js CHANGED
@@ -1 +1,4 @@
1
1
  export { EditorShell } from './editor-shell/editor-shell.js';
2
+ export { EditorToolbar } from './editor-toolbar/editor-toolbar.js';
3
+ export { EditorCanvas } from './editor-canvas/editor-canvas.js';
4
+ export { EditorSidebar } from './editor-sidebar/editor-sidebar.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adia-ai/web-modules",
3
- "version": "0.3.5",
3
+ "version": "0.4.0",
4
4
  "description": "AdiaUI composite custom elements \u2014 shell, chat, editor, runtime clusters built from @adia-ai/web-components primitives. Subpath exports per cluster.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -33,9 +33,9 @@
33
33
  "./runtime/**/*.js"
34
34
  ],
35
35
  "peerDependencies": {
36
- "@adia-ai/web-components": "^0.3.0",
37
- "@adia-ai/a2ui-runtime": "^0.3.0",
38
- "@adia-ai/llm": "^0.3.0"
36
+ "@adia-ai/web-components": "^0.4.0",
37
+ "@adia-ai/a2ui-runtime": "^0.4.0",
38
+ "@adia-ai/llm": "^0.4.0"
39
39
  },
40
40
  "publishConfig": {
41
41
  "access": "public",
@@ -7,25 +7,21 @@
7
7
  * </admin-shell>
8
8
  *
9
9
  * Module-tier app shell. Coordinates bespoke shell-tier children;
10
- * does NOT centralize their behavior. Each child owns its own
11
- * concern (resize/collapse on <admin-sidebar>, palette wiring on
12
- * <admin-command>).
10
+ * does NOT centralize their behavior. Each child owns its own concern
11
+ * (resize/collapse on <admin-sidebar>, palette wiring on <admin-command>).
13
12
  *
14
- * Backwards compatibility: legacy authoring shapes still work
15
- * <aside data-sidebar="leading"> and <dialog data-command> are
16
- * recognized + wired the same as their bespoke equivalents. Mixed
17
- * markup (some bespoke, some legacy) renders identically. See
18
- * ADR-0023 for the migration window.
13
+ * **Bespoke-only since v0.4.0** (ADR-0023 Phase 3 + ADR-0024). The
14
+ * legacy authoring shapes (`<aside data-sidebar>`, `<dialog data-command>`,
15
+ * `[data-resize]`, `<aside-ui slot>`) are no longer recognized consumers
16
+ * MUST use the bespoke vocabulary. The v0.3.x line carried backwards
17
+ * compat reads; see git history for the legacy paths.
19
18
  *
20
19
  * Host responsibilities (the only behavior that stays here):
21
20
  * 1. [mode] reflection — "rounded", "borderless", or both
22
- * 2. [data-sidebar-toggle="leading|trailing"] click forwarding to
23
- * the matching sidebar's .toggle() (works for bespoke + legacy)
24
- * 3. [data-command-trigger] click forwarding to the inner palette's
25
- * .show() (works for bespoke + legacy)
26
- * 4. Re-emit child events as host events for backwards compat
27
- * (sidebar-toggle, sidebar-resize, command-select all bubble
28
- * already, but consumers who listened on the host get them)
21
+ * 2. [data-sidebar-toggle="<name>"] click forwarding to the matching
22
+ * <admin-sidebar>'s .toggle() public method
23
+ * 3. [data-command-trigger] click forwarding to <admin-command>.show()
24
+ * 4. command-select re-emit + nav routing when a <nav-ui> is present
29
25
  */
30
26
 
31
27
  import { UIElement } from '../../../web-components/core/element.js';
@@ -37,100 +33,49 @@ class AdminShell extends UIElement {
37
33
 
38
34
  static template = () => null;
39
35
 
40
- #cmdKeyHandler = null;
41
- #legacyResizeCleanups = [];
42
- #legacySidebarRO = null;
43
- #legacySidebarWidths = new Map();
44
-
45
36
  connected() {
46
37
  this.#wireToggleButtons();
47
38
  this.#wireCommandTriggers();
48
- this.#setupLegacyShapes();
49
- }
50
-
51
- disconnected() {
52
- for (const cleanup of this.#legacyResizeCleanups) cleanup();
53
- this.#legacyResizeCleanups = [];
54
- this.#legacySidebarRO?.disconnect();
55
- this.#legacySidebarRO = null;
56
- if (this.#cmdKeyHandler) {
57
- document.removeEventListener('keydown', this.#cmdKeyHandler);
58
- this.#cmdKeyHandler = null;
59
- }
60
39
  }
61
40
 
62
- // Selector that matches BOTH bespoke <admin-sidebar slot> AND legacy
63
- // shapes. Used by toggle-button wiring + ResizeObserver setup.
64
- static #SIDEBAR_SEL =
65
- ':is(admin-sidebar[slot="leading"], admin-sidebar[slot="trailing"], ' +
66
- '[data-sidebar], aside-ui[slot="leading"], aside-ui[slot="trailing"])';
67
-
68
- #sidebarName(el) {
69
- return (
70
- el.getAttribute('name') ??
71
- el.getAttribute('data-sidebar') ??
72
- el.getAttribute('slot')
73
- );
74
- }
41
+ // No #disconnected children own their own cleanup; the host registers
42
+ // no document-level listeners and no observers. Element-level listeners
43
+ // are auto-cleaned when the element disconnects.
75
44
 
76
45
  #findSidebar(name) {
77
46
  return this.querySelector(
78
- ':is(' +
79
- `admin-sidebar[slot="${name}"], admin-sidebar[name="${name}"], ` +
80
- `[data-sidebar="${name}"], aside-ui[slot="${name}"]` +
81
- ')'
47
+ `admin-sidebar[slot="${name}"], admin-sidebar[name="${name}"]`
82
48
  );
83
49
  }
84
50
 
85
- // ── 1. Toggle buttons — works for bespoke + legacy ──
51
+ // ── 1. Toggle buttons — forward clicks to <admin-sidebar>.toggle() ──
86
52
 
87
53
  #wireToggleButtons() {
88
54
  for (const btn of this.querySelectorAll('[data-sidebar-toggle]')) {
89
55
  const name = btn.getAttribute('data-sidebar-toggle');
90
56
  btn.addEventListener('click', () => {
91
57
  const sidebar = this.#findSidebar(name);
92
- if (!sidebar) return;
93
-
94
- // Bespoke <admin-sidebar> has a public toggle() method
95
- if (typeof sidebar.toggle === 'function') {
96
- sidebar.toggle();
97
- return;
98
- }
99
-
100
- // Legacy: replicate the old in-place toggle on raw HTML
101
- this.#legacyToggle(sidebar, name);
58
+ sidebar?.toggle?.();
102
59
  });
103
60
  }
104
61
  }
105
62
 
106
- // ── 2. Command triggers — works for bespoke + legacy ──
63
+ // ── 2. Command triggers — forward clicks to <admin-command>.show() ──
107
64
 
108
65
  #wireCommandTriggers() {
109
66
  const command = this.querySelector('admin-command');
110
- const legacyDialog = command ? null : this.querySelector('dialog[data-command]');
67
+ if (!command) return;
111
68
 
112
- if (!command && !legacyDialog) return;
113
-
114
- // Bespoke path: trigger calls .show() on <admin-command>
115
- if (command) {
116
- for (const trigger of this.querySelectorAll('[data-command-trigger]')) {
117
- trigger.addEventListener('click', (e) => {
118
- e.stopPropagation();
119
- command.show();
120
- });
121
- }
122
- // <admin-command> owns its own keyboard shortcut; nothing more
123
- // for the host to wire. We DO re-listen for command-select to
124
- // wire navigation if a <nav-ui> is present.
125
- this.#wireCommandSelectToNav(command);
126
- return;
69
+ for (const trigger of this.querySelectorAll('[data-command-trigger]')) {
70
+ trigger.addEventListener('click', (e) => {
71
+ e.stopPropagation();
72
+ command.show();
73
+ });
127
74
  }
128
75
 
129
- // Legacy path: replicate the old wiring around <dialog data-command>
130
- this.#wireLegacyCommand(legacyDialog);
131
- }
132
-
133
- #wireCommandSelectToNav(command) {
76
+ // <admin-command> owns its own Cmd+K listener. The host only wires
77
+ // command-select → nav routing if a <nav-ui> is present (so clicking
78
+ // a result in the palette navigates to that route).
134
79
  const nav = this.querySelector('nav-ui');
135
80
  if (!nav) return;
136
81
  command.addEventListener('command-select', (e) => {
@@ -138,167 +83,6 @@ class AdminShell extends UIElement {
138
83
  if (item) nav.select(item);
139
84
  });
140
85
  }
141
-
142
- // ── 3. Legacy shape wiring (raw <aside data-sidebar>, <dialog data-command>) ──
143
-
144
- #setupLegacyShapes() {
145
- const legacySidebars = this.querySelectorAll(
146
- ':is([data-sidebar], aside-ui[slot="leading"], aside-ui[slot="trailing"])'
147
- );
148
- if (legacySidebars.length === 0) return;
149
-
150
- this.#restoreLegacyWidths(legacySidebars);
151
- this.#setupLegacyResizeHandles(legacySidebars);
152
- this.#setupLegacyResizeObserver(legacySidebars);
153
- }
154
-
155
- #restoreLegacyWidths(sidebars) {
156
- for (const sidebar of sidebars) {
157
- const name = this.#sidebarName(sidebar);
158
- try {
159
- const saved = localStorage.getItem(`adia-sidebar-${name}`);
160
- if (saved) {
161
- sidebar.style.width = saved;
162
- const w = parseFloat(saved);
163
- if (!isNaN(w) && w > 96) {
164
- this.#legacySidebarWidths.set(name, saved);
165
- }
166
- }
167
- } catch {}
168
- }
169
- }
170
-
171
- #legacyToggle(sidebar, name) {
172
- const isCollapsed = sidebar.getBoundingClientRect().width <= 96;
173
- if (isCollapsed) {
174
- const prev = this.#legacySidebarWidths.get(name);
175
- sidebar.style.width = prev || '';
176
- try { localStorage.setItem(`adia-sidebar-${name}`, prev || ''); } catch {}
177
- } else {
178
- this.#legacySidebarWidths.set(name, sidebar.style.width || getComputedStyle(sidebar).width);
179
- const minW = getComputedStyle(sidebar).minWidth;
180
- sidebar.style.width = minW;
181
- try { localStorage.setItem(`adia-sidebar-${name}`, minW); } catch {}
182
- }
183
- this.dispatchEvent(new CustomEvent('sidebar-toggle', {
184
- bubbles: true,
185
- detail: { sidebar: name, name, expanded: !isCollapsed },
186
- }));
187
- }
188
-
189
- #setupLegacyResizeHandles(sidebars) {
190
- for (const sidebar of sidebars) {
191
- const handle = sidebar.querySelector(':scope > [data-resize]');
192
- if (!handle) continue;
193
- const name = this.#sidebarName(sidebar);
194
- const isLeading = name === 'leading';
195
-
196
- const onPointerDown = (e) => {
197
- e.preventDefault();
198
- handle.setPointerCapture(e.pointerId);
199
- const startX = e.clientX;
200
- const startW = sidebar.getBoundingClientRect().width;
201
- sidebar.setAttribute('data-resizing', '');
202
- document.documentElement.style.cursor = 'col-resize';
203
-
204
- const onMove = (e) => {
205
- const dx = e.clientX - startX;
206
- const max = parseInt(getComputedStyle(sidebar).getPropertyValue('max-width')) || 480;
207
- const w = Math.max(48, Math.min(max, startW + (isLeading ? dx : -dx)));
208
- sidebar.style.width = `${w}px`;
209
- };
210
-
211
- const onUp = () => {
212
- sidebar.removeAttribute('data-resizing');
213
- document.documentElement.style.cursor = '';
214
- handle.removeEventListener('pointermove', onMove);
215
- handle.removeEventListener('pointerup', onUp);
216
-
217
- const w = sidebar.getBoundingClientRect().width;
218
- if (w <= 96) {
219
- sidebar.style.width = getComputedStyle(sidebar).minWidth;
220
- } else if (w < 160) {
221
- sidebar.style.width = '160px';
222
- }
223
- try { localStorage.setItem(`adia-sidebar-${name}`, sidebar.style.width); } catch {}
224
-
225
- this.dispatchEvent(new CustomEvent('sidebar-resize', {
226
- bubbles: true,
227
- detail: { sidebar: name, name, width: sidebar.getBoundingClientRect().width },
228
- }));
229
- };
230
-
231
- handle.addEventListener('pointermove', onMove);
232
- handle.addEventListener('pointerup', onUp);
233
- };
234
-
235
- handle.addEventListener('pointerdown', onPointerDown);
236
- this.#legacyResizeCleanups.push(() => handle.removeEventListener('pointerdown', onPointerDown));
237
- }
238
- }
239
-
240
- #setupLegacyResizeObserver(sidebars) {
241
- this.#legacySidebarRO = new ResizeObserver((entries) => {
242
- for (const entry of entries) {
243
- const sidebar = entry.target;
244
- const narrow = entry.contentBoxSize[0].inlineSize <= 96;
245
- for (const sel of sidebar.querySelectorAll('select-ui')) {
246
- sel.setAttribute('placement', narrow ? 'right' : 'bottom-start');
247
- }
248
- }
249
- });
250
- for (const sb of sidebars) this.#legacySidebarRO.observe(sb);
251
- }
252
-
253
- #wireLegacyCommand(dialog) {
254
- const cmdEl = dialog.querySelector('command-ui');
255
- const nav = this.querySelector('nav-ui');
256
-
257
- const openCmd = () => {
258
- dialog.showModal();
259
- if (cmdEl) { cmdEl.open = true; cmdEl.value = ''; cmdEl.focus(); }
260
- };
261
- const closeCmd = () => {
262
- dialog.close();
263
- if (cmdEl) cmdEl.open = false;
264
- };
265
-
266
- for (const trigger of this.querySelectorAll('[data-command-trigger]')) {
267
- trigger.addEventListener('click', (e) => {
268
- e.stopPropagation();
269
- openCmd();
270
- });
271
- }
272
-
273
- dialog.addEventListener('click', (e) => {
274
- if (e.target === dialog) closeCmd();
275
- });
276
-
277
- if (cmdEl) {
278
- cmdEl.addEventListener('dismiss', closeCmd);
279
- cmdEl.addEventListener('select', (e) => {
280
- closeCmd();
281
- if (nav) {
282
- const item = nav.querySelector(`nav-item-ui[value="${e.detail.value}"]`);
283
- if (item) nav.select(item);
284
- }
285
- this.dispatchEvent(new CustomEvent('command-select', {
286
- bubbles: true,
287
- detail: e.detail,
288
- }));
289
- });
290
- }
291
-
292
- // Cmd+K listener — only for legacy shape (bespoke <admin-command>
293
- // owns its own listener)
294
- this.#cmdKeyHandler = (e) => {
295
- if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
296
- e.preventDefault();
297
- dialog.open ? closeCmd() : openCmd();
298
- }
299
- };
300
- document.addEventListener('keydown', this.#cmdKeyHandler);
301
- }
302
86
  }
303
87
 
304
88
  customElements.define('admin-shell', AdminShell);
@@ -1,20 +1,16 @@
1
1
  /* ═══════════════════════════════════════════════════════════════
2
- admin-shell — Bespoke shell-tier children (Phase 2 of ADR-0023)
3
-
4
- The admin-* CSS-only structural children are styled by SHARING the
5
- same CSS rules as their legacy raw-HTML counterparts. This file
6
- re-applies the existing selectors (main / section / header / footer)
7
- to the new bespoke tags via display + structural mappings.
8
-
9
- Rather than duplicate every rule in main.css / sidebar.css /
10
- templates.css, we declare the bespoke tags as `display: contents` or
11
- the relevant layout box, then let the existing selectors (which
12
- already use :is() lifts for legacy + slot-vocabulary) match.
13
-
14
- For Phase 2, the simplest map: each bespoke tag mimics its legacy
15
- counterpart's display behavior. The host shell.css already handles
16
- admin-shell flex layout; main.css handles main-as-column; sidebar.css
17
- handles sidebar geometry.
2
+ admin-shell — Bespoke shell-tier children (canonical since v0.4.0)
3
+
4
+ Per ADR-0023 + ADR-0024 (Phase 3 deprecation, v0.4.0), this file
5
+ carries the canonical styling for the bespoke shell-tier children:
6
+ admin-content, admin-topbar, admin-statusbar, admin-scroll,
7
+ admin-page, admin-page-header, admin-page-body, admin-sidebar.
8
+
9
+ The legacy raw-HTML shape (<main>, <header>, <footer>, <aside-ui slot>,
10
+ <aside data-sidebar>, <dialog data-command>) was retired in v0.4.0;
11
+ the existing :is() lifts in layered files (main.css, sidebar.css,
12
+ templates.css) target ONLY the bespoke tags now. See git history
13
+ prior to v0.4.0 for the legacy paths.
18
14
  ═══════════════════════════════════════════════════════════════ */
19
15
 
20
16
  /* ── admin-content ≡ <main> ── */
@@ -30,7 +26,7 @@ admin-shell > admin-content {
30
26
  /* ── admin-topbar ≡ <header-ui> at shell tier ── */
31
27
  admin-content > admin-topbar,
32
28
  admin-shell > admin-topbar,
33
- :is([data-sidebar], aside-ui[slot="leading"], aside-ui[slot="trailing"], admin-sidebar) > admin-topbar {
29
+ admin-sidebar > admin-topbar {
34
30
  display: flex;
35
31
  align-items: center;
36
32
  gap: var(--page-header-gap);
@@ -44,7 +40,7 @@ admin-shell > admin-topbar,
44
40
  /* ── admin-statusbar ≡ <footer-ui> at shell tier ── */
45
41
  admin-content > admin-statusbar,
46
42
  admin-shell > admin-statusbar,
47
- :is([data-sidebar], aside-ui[slot="leading"], aside-ui[slot="trailing"], admin-sidebar) > admin-statusbar {
43
+ admin-sidebar > admin-statusbar {
48
44
  display: flex;
49
45
  align-items: center;
50
46
  gap: var(--page-header-gap);
@@ -98,13 +94,13 @@ admin-page > admin-page-body {
98
94
  flex-direction: column;
99
95
  }
100
96
 
101
- /* ── admin-sidebar — bridge to existing sidebar.css selectors ──
102
- Rather than expand every :is([data-sidebar], aside-ui[slot=…], …)
103
- selector in sidebar.css with `, admin-sidebar[slot=…]`, we apply the
104
- core sidebar geometry + container query rules here. The remaining
105
- slot-routing rules in sidebar.css (header/footer chrome, section,
106
- nav-ui colors) match by descendant selector rather than parent
107
- tag, so they pick up admin-sidebar children automatically. */
97
+ /* ── admin-sidebar — geometry + resize handle + container queries ──
98
+ Canonical styling for <admin-sidebar> per ADR-0023. Pre-v0.4.0 this
99
+ bridged to legacy <aside-ui slot> / <aside data-sidebar> selectors;
100
+ in v0.4.0 those layered-CSS legacy lifts were stripped, so this block
101
+ now carries the full sidebar styling. The descendant rules in
102
+ sidebar.css (header/footer chrome, section, nav-ui colors) still
103
+ match via tag-name descendant selectors. */
108
104
 
109
105
  :is(admin-sidebar[slot="leading"], admin-sidebar[slot="trailing"]) {
110
106
  display: flex;
@@ -131,7 +127,7 @@ admin-sidebar[slot="trailing"] {
131
127
  border-left: var(--page-border);
132
128
  }
133
129
 
134
- /* Resize handle — same shape as legacy [data-sidebar] > [data-resize] */
130
+ /* Resize handle */
135
131
  :is(admin-sidebar[slot="leading"], admin-sidebar[slot="trailing"]) > [data-resize] {
136
132
  position: absolute;
137
133
  top: 0;
@@ -36,7 +36,7 @@ admin-shell > main {
36
36
  OR a main-level chrome bar (`> main > :is(header, header-ui)` etc.).
37
37
  Either qualifies as "chrome present" and suppresses the
38
38
  corresponding margin. */
39
- admin-shell:not(:has(> :is(aside[data-sidebar="trailing"], aside-ui[slot="trailing"]):not([hidden]))) > main > :is(section, section-ui) {
39
+ admin-shell:not(:has(> :is(asideadmin-sidebar[slot="trailing"], admin-sidebar[slot="trailing"]):not([hidden]))) > main > :is(section, section-ui) {
40
40
  margin-inline-end: var(--a-space-2);
41
41
  }
42
42
  admin-shell:not(:has(> :is(header, header-ui):not([hidden]), > main > :is(header, header-ui):not([hidden]))) > main > :is(section, section-ui) {
@@ -49,7 +49,7 @@ admin-shell:not(:has(> :is(footer, footer-ui):not([hidden]), > main > :is(footer
49
49
  /* ── Main > header (topbar) ──
50
50
  Contains: sidebar toggle, breadcrumb, spacer, action buttons.
51
51
 
52
- Slot contract (shared with > main > footer and [data-sidebar] >
52
+ Slot contract (shared with > main > footer and admin-sidebar >
53
53
  header/footer) — identical to card-ui / drawer-ui / editor-shell:
54
54
  [slot="icon"] leading glyph
55
55
  [slot="heading"] primary label; strong weight + strong fg
@@ -3,7 +3,7 @@
3
3
 
4
4
  Structure (legacy raw-HTML form; slot-vocabulary equivalents in parens):
5
5
  admin-shell — root shell (flex row, fixed viewport)
6
- aside[data-sidebar="leading"] (or aside-ui[slot="leading"]) — nav sidebar (resizable, collapsible)
6
+ asideadmin-sidebar[slot="leading"] (or admin-sidebar[slot="leading"]) — nav sidebar (resizable, collapsible)
7
7
  main — center column (topbar + scroll + footer)
8
8
  header (or header-ui) — topbar
9
9
  section (or section-ui) — scroll container
@@ -11,7 +11,7 @@
11
11
  div[data-content-header] > header — sticky page title + tabs
12
12
  div[data-content-body] > section — centered reading column
13
13
  footer (or footer-ui) — status bar
14
- aside[data-sidebar="trailing"] (or aside-ui[slot="trailing"]) — inspector sidebar
14
+ asideadmin-sidebar[slot="trailing"] (or admin-sidebar[slot="trailing"]) — inspector sidebar
15
15
  dialog[data-command] — command palette
16
16
 
17
17
  Both authoring shapes are valid simultaneously. The CSS uses