@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.
- package/CHANGELOG.md +55 -0
- package/chat/chat-shell/chat-shell.js +28 -40
- package/chat/chat-shell/css/chat-shell.empty.css +3 -3
- package/chat/chat-shell/css/chat-shell.layout.css +2 -2
- package/editor/editor-canvas/editor-canvas.a2ui.json +87 -0
- package/editor/editor-canvas/editor-canvas.examples.html +65 -0
- package/editor/editor-canvas/editor-canvas.html +43 -0
- package/editor/editor-canvas/editor-canvas.js +103 -0
- package/editor/editor-canvas/editor-canvas.test.js +100 -0
- package/editor/editor-canvas/editor-canvas.yaml +88 -0
- package/editor/editor-canvas-empty/editor-canvas-empty.a2ui.json +69 -0
- package/editor/editor-canvas-empty/editor-canvas-empty.examples.html +65 -0
- package/editor/editor-canvas-empty/editor-canvas-empty.html +42 -0
- package/editor/editor-canvas-empty/editor-canvas-empty.yaml +56 -0
- package/editor/editor-shell/css/editor-shell.bespoke.css +237 -0
- package/editor/editor-shell/css/editor-shell.layout.css +6 -6
- package/editor/editor-shell/editor-shell.css +1 -0
- package/editor/editor-shell/editor-shell.js +87 -30
- package/editor/editor-sidebar/editor-sidebar.a2ui.json +93 -0
- package/editor/editor-sidebar/editor-sidebar.examples.html +65 -0
- package/editor/editor-sidebar/editor-sidebar.html +43 -0
- package/editor/editor-sidebar/editor-sidebar.js +197 -0
- package/editor/editor-sidebar/editor-sidebar.test.js +145 -0
- package/editor/editor-sidebar/editor-sidebar.yaml +91 -0
- package/editor/editor-statusbar/editor-statusbar.a2ui.json +76 -0
- package/editor/editor-statusbar/editor-statusbar.examples.html +65 -0
- package/editor/editor-statusbar/editor-statusbar.html +42 -0
- package/editor/editor-statusbar/editor-statusbar.yaml +57 -0
- package/editor/editor-toolbar/editor-toolbar.a2ui.json +96 -0
- package/editor/editor-toolbar/editor-toolbar.examples.html +65 -0
- package/editor/editor-toolbar/editor-toolbar.html +43 -0
- package/editor/editor-toolbar/editor-toolbar.js +58 -0
- package/editor/editor-toolbar/editor-toolbar.test.js +99 -0
- package/editor/editor-toolbar/editor-toolbar.yaml +81 -0
- package/editor/index.js +3 -0
- package/package.json +4 -4
- package/shell/admin-shell/admin-shell.js +27 -243
- package/shell/admin-shell/css/admin-shell.bespoke.css +22 -26
- package/shell/admin-shell/css/admin-shell.main.css +2 -2
- package/shell/admin-shell/css/admin-shell.shell.css +2 -2
- 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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adia-ai/web-modules",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
37
|
-
"@adia-ai/a2ui-runtime": "^0.
|
|
38
|
-
"@adia-ai/llm": "^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
|
-
*
|
|
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
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
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="
|
|
23
|
-
*
|
|
24
|
-
* 3. [data-command-trigger] click forwarding to
|
|
25
|
-
*
|
|
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
|
-
//
|
|
63
|
-
//
|
|
64
|
-
|
|
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
|
-
|
|
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 —
|
|
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
|
-
|
|
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 —
|
|
63
|
+
// ── 2. Command triggers — forward clicks to <admin-command>.show() ──
|
|
107
64
|
|
|
108
65
|
#wireCommandTriggers() {
|
|
109
66
|
const command = this.querySelector('admin-command');
|
|
110
|
-
|
|
67
|
+
if (!command) return;
|
|
111
68
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
//
|
|
130
|
-
|
|
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 (
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
the
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 —
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
nav-ui colors)
|
|
107
|
-
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|