@adia-ai/web-modules 0.7.9 → 0.7.10

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.
@@ -0,0 +1,80 @@
1
+ /**
2
+ * <embed-shell> — embedded multi-surface shell.
3
+ *
4
+ * Composes a primary `[app]` surface with secondary `[panel="<name>"]` surfaces
5
+ * the consumer provides, and arranges them responsively. Peer of admin-shell /
6
+ * editor-shell / chat-shell. Light-DOM (P1). Content-agnostic: it orchestrates
7
+ * whatever `[app]` + `[panel]` children it's given — it owns layout, not content.
8
+ *
9
+ * Contract (clean attributes — ADR-0024, no data-*):
10
+ * <embed-shell>
11
+ * <x app>…</x> ← the primary surface (one)
12
+ * <y panel="chat">…</y> ← a secondary surface (any number)
13
+ * <z panel="settings">…</z>
14
+ * </embed-shell>
15
+ * …anywhere inside: <button opens="chat"> ← trigger (delegated)
16
+ * <button close> ← dismiss the open panel
17
+ *
18
+ * State (reflected, ADR-0023):
19
+ * embed-shell[panel="chat"] ← which panel is open ('' / absent = none)
20
+ * <y panel="chat" active> ← the open panel carries [active] (CSS hook)
21
+ *
22
+ * Events:
23
+ * embed:open (in) detail:{panel} ← request to open/toggle a panel
24
+ * embed:change(out) detail:{panel} ← emitted after the open panel changes
25
+ *
26
+ * Layout lives in embed-shell.css: ≥760px the open panel is a 50/50 column; <760px
27
+ * it's a cover sheet (translateY — identity at rest, so popovers inside still anchor).
28
+ */
29
+ class EmbedShell extends HTMLElement {
30
+ #wired = false;
31
+
32
+ connectedCallback() {
33
+ if (this.#wired) return;
34
+ this.#wired = true;
35
+ this.addEventListener('click', (e) => {
36
+ const opener = e.target.closest?.('[opens]');
37
+ if (opener && this.contains(opener)) { this.toggle(opener.getAttribute('opens')); return; }
38
+ const closer = e.target.closest?.('[close]');
39
+ if (closer && this.contains(closer)) this.close();
40
+ });
41
+ this.addEventListener('embed:open', (e) => this.toggle(e.detail?.panel));
42
+ this.addEventListener('keydown', (e) => {
43
+ if (e.key === 'Escape' && this.panel) { e.stopPropagation(); this.close(); }
44
+ });
45
+ }
46
+
47
+ /** the open panel's name, or '' */
48
+ get panel() { return this.getAttribute('panel') || ''; }
49
+
50
+ toggle(name) { return this.panel === name ? this.close() : this.open(name); }
51
+
52
+ open(name) {
53
+ if (!name) return;
54
+ const el = this.querySelector(`:scope > [panel="${CSS.escape(name)}"]`);
55
+ if (!el) return; // consumer must provide a matching [panel] surface
56
+ for (const p of this.querySelectorAll(':scope > [panel][active]')) {
57
+ if (p !== el) p.removeAttribute('active');
58
+ }
59
+ el.setAttribute('active', ''); // CSS show/slide hook
60
+ this.setAttribute('panel', name); // reflected state
61
+ // focus the panel without scrolling the framed surface (preventScroll)
62
+ requestAnimationFrame(() =>
63
+ el.querySelector('[autofocus], [close], button-ui, [tabindex]')?.focus?.({ preventScroll: true }));
64
+ this.#emit(name);
65
+ }
66
+
67
+ close() {
68
+ if (!this.panel) return;
69
+ this.querySelector(':scope > [panel][active]')?.removeAttribute('active');
70
+ this.removeAttribute('panel');
71
+ this.#emit('');
72
+ }
73
+
74
+ #emit(panel) {
75
+ this.dispatchEvent(new CustomEvent('embed:change', { detail: { panel }, bubbles: true }));
76
+ }
77
+ }
78
+
79
+ customElements.define('embed-shell', EmbedShell);
80
+ export { EmbedShell };
@@ -0,0 +1,74 @@
1
+ $schema: ../../../../scripts/schemas/component.yaml.schema.json
2
+ name: EmbedShell
3
+ tag: embed-shell
4
+ status: draft
5
+ component: AppShell
6
+ category: layout
7
+ version: 1
8
+ description: |
9
+ Embedded multi-surface shell — composes a primary [app] surface with secondary
10
+ [panel="<name>"] surfaces the consumer provides, arranging them responsively.
11
+ Peer of <admin-shell>. Content-agnostic: it orchestrates whatever [app] +
12
+ [panel] children it is given (it owns layout, not content), so any embedded app
13
+ supplies its own surfaces.
14
+
15
+ >=760px the open panel is an EQUAL 50/50 column beside the app and the frame
16
+ grows to fit; <760px it is a cover sheet that slides up over the app (identity
17
+ transform at rest, so popovers inside still anchor). Clean attribute contract
18
+ (ADR-0024, no data-*): [app], [panel="<name>"], trigger [opens="<name>"],
19
+ [close]; reflects [panel] on the shell + [active] on the open panel.
20
+
21
+ props:
22
+ panel:
23
+ type: string
24
+ reflect: true
25
+ description: |
26
+ The open panel's name (empty / absent = none). Reflected state. Set it by
27
+ clicking an [opens="<name>"] trigger, dispatching an `embed:open` event, or
28
+ calling .open(name) / .close() / .toggle(name).
29
+
30
+ events:
31
+ "embed:open":
32
+ description: Request to open or toggle a panel (consumed by the shell).
33
+ detail:
34
+ panel: string
35
+ "embed:change":
36
+ description: Fired after the open panel changes.
37
+ detail:
38
+ panel: string
39
+
40
+ slots:
41
+ default:
42
+ description: >-
43
+ Document-order children: one [app] primary surface + any number of
44
+ [panel="<name>"] secondary surfaces. The shell always shows [app] and reveals
45
+ the open [panel] (50/50 column >=760px, cover sheet <760px). Triggers
46
+ ([opens], [close]) may live anywhere inside.
47
+
48
+ states:
49
+ - name: idle
50
+ description: No panel open — the app fills the frame.
51
+ - name: open
52
+ description: A panel is open ([panel="<name>"] reflected; the panel carries [active]).
53
+
54
+ traits:
55
+ - resizable
56
+
57
+ a2ui:
58
+ rules:
59
+ - >-
60
+ embed-shell takes exactly one [app] child + any number of [panel="<name>"]
61
+ children. It owns layout, not content — the consumer supplies the surfaces.
62
+ - >-
63
+ Open a panel with an [opens="<name>"] trigger anywhere inside, or an
64
+ `embed:open` event; dismiss with [close]. Do not toggle visibility manually —
65
+ the shell reflects [panel] + sets [active] on the open panel.
66
+ - >-
67
+ Use clean attributes (ADR-0024) — [app], [panel], [opens], [close] — not the
68
+ legacy data-* shapes.
69
+
70
+ keywords: [embed, shell, layout, panel, multi-pane, cover-sheet, app-shell]
71
+ synonyms:
72
+ embed: [embedded, shell, app-shell, multi-pane]
73
+ panel: [pane, sheet, drawer]
74
+ related: [AdminShell, Frame, Drawer]
package/shell/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { AdminShell } from './admin-shell/admin-shell.js';
2
2
  export { AdminSidebar } from './admin-sidebar/admin-sidebar.js';
3
3
  export { AdminCommand } from './admin-command/admin-command.js';
4
+ export { EmbedShell } from './embed-shell/embed-shell.js';