@xmesh/system-design 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lit/components/date-range/index.css +324 -0
- package/dist/lit/components/date-range/index.d.ts +57 -0
- package/dist/lit/components/date-range/index.js +702 -0
- package/dist/lit/components/popover/index.css +34 -0
- package/dist/lit/components/popover/index.d.ts +29 -0
- package/dist/lit/components/popover/index.js +204 -0
- package/dist/lit/components/table/index.js +4 -9
- package/dist/lit/index.d.ts +2 -0
- package/dist/lit/index.js +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
Popover — a click-triggered anchored panel of arbitrary content.
|
|
3
|
+
|
|
4
|
+
The popover surface chrome (inverse-surface fill, inverse-on-surface ink,
|
|
5
|
+
hairline border, level3 elevation, edge-flip, open/close motion) is owned by
|
|
6
|
+
the composed <xm-overlay> in the `menu` tier. This file styles only the
|
|
7
|
+
trigger wrapper and the content panel that rides inside the overlay body.
|
|
8
|
+
|
|
9
|
+
All ink is inverse-on-surface (AD-13) — the popover/card stack ink. The panel
|
|
10
|
+
is role="dialog" and receives initial focus, so its focus outline is
|
|
11
|
+
suppressed (the moved-in focus is the affordance, not a ring on the box).
|
|
12
|
+
|
|
13
|
+
BEM block: `popover`. Registered in scripts/check-bem.sh STRICT_BLOCKS.
|
|
14
|
+
Elements: popover__trigger, popover__panel.
|
|
15
|
+
============================================ */
|
|
16
|
+
|
|
17
|
+
.popover__trigger {
|
|
18
|
+
display: inline-flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.popover__panel {
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: column;
|
|
25
|
+
gap: var(--s-2);
|
|
26
|
+
min-width: 180px;
|
|
27
|
+
color: var(--md-sys-color-inverse-on-surface);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* role="dialog" + tabindex=-1: we move focus here on open, so the box itself
|
|
31
|
+
never shows a focus ring — interactive children own their own focus states. */
|
|
32
|
+
.popover__panel:focus {
|
|
33
|
+
outline: none;
|
|
34
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { LitElement } from "lit";
|
|
2
|
+
import type { TemplateResult } from "lit";
|
|
3
|
+
import type { OverlayPlacement } from "../overlay/index.js";
|
|
4
|
+
export declare class XmPopover extends LitElement {
|
|
5
|
+
open: boolean;
|
|
6
|
+
placement: OverlayPlacement;
|
|
7
|
+
label: string;
|
|
8
|
+
private _overlay;
|
|
9
|
+
private _panel;
|
|
10
|
+
private _triggerWrap;
|
|
11
|
+
private readonly _panelId;
|
|
12
|
+
private _closeReason;
|
|
13
|
+
private _wasOpen;
|
|
14
|
+
connectedCallback(): void;
|
|
15
|
+
disconnectedCallback(): void;
|
|
16
|
+
render(): TemplateResult;
|
|
17
|
+
private _onTriggerClick;
|
|
18
|
+
private _onDocPointer;
|
|
19
|
+
private _onOverlayClose;
|
|
20
|
+
private _requestClose;
|
|
21
|
+
protected updated(changed: Map<string, unknown>): void;
|
|
22
|
+
private get _triggerEl();
|
|
23
|
+
private _wireTriggerAria;
|
|
24
|
+
}
|
|
25
|
+
declare global {
|
|
26
|
+
interface HTMLElementTagNameMap {
|
|
27
|
+
"xm-popover": XmPopover;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/*
|
|
2
|
+
popover/index.ts — <xm-popover>, a click-triggered anchored panel of
|
|
3
|
+
arbitrary content.
|
|
4
|
+
|
|
5
|
+
The generalized sibling of <xm-menu>: where the menu projects a list of
|
|
6
|
+
<xm-menu-item> children and owns the APG `menu` roving model, the popover
|
|
7
|
+
projects ANY content (a column toggle list, a small form, a filter panel) and
|
|
8
|
+
leaves interaction to that content. Both compose the same xm-overlay
|
|
9
|
+
foundation (Story 1.4) for anchored positioning, top-layer stacking, Esc
|
|
10
|
+
dismissal, and focus-restore — neither hand-rolls z-index or anchoring math.
|
|
11
|
+
We drive the overlay through its PUBLIC API only (mode / tier / placement /
|
|
12
|
+
.anchor / .opener / show / hide / xm-overlay-close) and never reach into its
|
|
13
|
+
shadow root (AD-12).
|
|
14
|
+
|
|
15
|
+
Behaviour:
|
|
16
|
+
• clicking the trigger opens an anchored panel; clicking it again, clicking
|
|
17
|
+
outside, or Esc closes it
|
|
18
|
+
• the panel is role="dialog" and takes initial focus on open; focus
|
|
19
|
+
restores to the trigger on close (delegated to the overlay via .opener)
|
|
20
|
+
• outside-click dismiss is owned here: the overlay runs popover="manual"
|
|
21
|
+
(no native light-dismiss) so a click whose composed path excludes this
|
|
22
|
+
host closes the panel
|
|
23
|
+
|
|
24
|
+
Events (AD-8 / AD-8a, tier (b) — component-specific signals):
|
|
25
|
+
xm-popover-open detail: {}
|
|
26
|
+
xm-popover-close detail: { reason } reason ∈ "trigger"|"outside"|"overlay"
|
|
27
|
+
both bubbles:true, composed:true.
|
|
28
|
+
|
|
29
|
+
Authoring:
|
|
30
|
+
<xm-popover placement="bottom-end" label="Columns">
|
|
31
|
+
<xm-button slot="trigger" variant="ghost" size="sm">Columns</xm-button>
|
|
32
|
+
<div class="my-panel">…arbitrary content…</div>
|
|
33
|
+
</xm-popover>
|
|
34
|
+
|
|
35
|
+
Shadow DOM. Depends on xm-overlay + tokens (AD-12). Lit is a bare `import`.
|
|
36
|
+
*/
|
|
37
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
38
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
39
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
40
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
41
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
42
|
+
};
|
|
43
|
+
import { LitElement, html } from "lit";
|
|
44
|
+
import { customElement, property, query } from "lit/decorators.js";
|
|
45
|
+
const POPOVER_CSS = new URL("../popover/index.css", import.meta.url).href;
|
|
46
|
+
let popoverSeq = 0;
|
|
47
|
+
let XmPopover = class XmPopover extends LitElement {
|
|
48
|
+
constructor() {
|
|
49
|
+
super(...arguments);
|
|
50
|
+
this.open = false;
|
|
51
|
+
this.placement = "bottom-start";
|
|
52
|
+
this.label = "";
|
|
53
|
+
this._panelId = `xm-popover-${++popoverSeq}`;
|
|
54
|
+
this._closeReason = "api";
|
|
55
|
+
this._wasOpen = false;
|
|
56
|
+
// ── Open / close ──────────────────────────────────────────────────────
|
|
57
|
+
this._onTriggerClick = () => {
|
|
58
|
+
if (this.open)
|
|
59
|
+
this._requestClose("trigger");
|
|
60
|
+
else
|
|
61
|
+
this.open = true;
|
|
62
|
+
};
|
|
63
|
+
this._onDocPointer = (e) => {
|
|
64
|
+
if (!this.open)
|
|
65
|
+
return;
|
|
66
|
+
if (!e.composedPath().includes(this))
|
|
67
|
+
this._requestClose("outside");
|
|
68
|
+
};
|
|
69
|
+
this._onOverlayClose = () => {
|
|
70
|
+
// Overlay self-dismissed (Esc); reflect it into our state. updated() owns
|
|
71
|
+
// the single overlay hide + xm-popover-close emission.
|
|
72
|
+
if (this.open)
|
|
73
|
+
this._requestClose("escape");
|
|
74
|
+
};
|
|
75
|
+
this._wireTriggerAria = (e) => {
|
|
76
|
+
const slot = e.target;
|
|
77
|
+
const el = slot.assignedElements()[0];
|
|
78
|
+
if (el) {
|
|
79
|
+
el.setAttribute("aria-haspopup", "dialog");
|
|
80
|
+
el.setAttribute("aria-expanded", this.open ? "true" : "false");
|
|
81
|
+
el.setAttribute("aria-controls", this._panelId);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
connectedCallback() {
|
|
86
|
+
super.connectedCallback();
|
|
87
|
+
document.addEventListener("pointerdown", this._onDocPointer, true);
|
|
88
|
+
}
|
|
89
|
+
disconnectedCallback() {
|
|
90
|
+
super.disconnectedCallback();
|
|
91
|
+
document.removeEventListener("pointerdown", this._onDocPointer, true);
|
|
92
|
+
}
|
|
93
|
+
render() {
|
|
94
|
+
return html `
|
|
95
|
+
<link rel="stylesheet" href="${POPOVER_CSS}" />
|
|
96
|
+
<style>
|
|
97
|
+
:host { display: inline-flex; }
|
|
98
|
+
</style>
|
|
99
|
+
<span class="popover__trigger" @click=${this._onTriggerClick}>
|
|
100
|
+
<slot name="trigger" @slotchange=${this._wireTriggerAria}></slot>
|
|
101
|
+
</span>
|
|
102
|
+
|
|
103
|
+
<xm-overlay
|
|
104
|
+
mode="non-modal"
|
|
105
|
+
tier="menu"
|
|
106
|
+
placement=${this.placement}
|
|
107
|
+
label=${this.label || "Popover"}
|
|
108
|
+
@xm-overlay-close=${this._onOverlayClose}
|
|
109
|
+
>
|
|
110
|
+
<div
|
|
111
|
+
class="popover__panel"
|
|
112
|
+
id=${this._panelId}
|
|
113
|
+
role="dialog"
|
|
114
|
+
aria-label=${this.label || "Popover"}
|
|
115
|
+
tabindex="-1"
|
|
116
|
+
>
|
|
117
|
+
<slot></slot>
|
|
118
|
+
</div>
|
|
119
|
+
</xm-overlay>
|
|
120
|
+
`;
|
|
121
|
+
}
|
|
122
|
+
// Record the reason and flip `open`; the actual overlay hide + the single
|
|
123
|
+
// xm-popover-close emission happen in updated(), so every close path
|
|
124
|
+
// (trigger, outside, Esc, programmatic open=false) is one code path that
|
|
125
|
+
// fires exactly once.
|
|
126
|
+
_requestClose(reason) {
|
|
127
|
+
if (!this.open)
|
|
128
|
+
return;
|
|
129
|
+
this._closeReason = reason;
|
|
130
|
+
this.open = false;
|
|
131
|
+
}
|
|
132
|
+
updated(changed) {
|
|
133
|
+
if (!changed.has("open"))
|
|
134
|
+
return;
|
|
135
|
+
const ov = this._overlay;
|
|
136
|
+
const trigger = this._triggerEl;
|
|
137
|
+
if (this.open) {
|
|
138
|
+
if (ov && trigger) {
|
|
139
|
+
ov.anchor = trigger;
|
|
140
|
+
ov.opener = trigger;
|
|
141
|
+
ov.show();
|
|
142
|
+
// Land focus in the panel so the content is reachable; the overlay
|
|
143
|
+
// restores focus to the trigger on close.
|
|
144
|
+
requestAnimationFrame(() => {
|
|
145
|
+
if (this.open)
|
|
146
|
+
this._panel?.focus();
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
this._wasOpen = true;
|
|
150
|
+
this.dispatchEvent(new CustomEvent("xm-popover-open", {
|
|
151
|
+
bubbles: true,
|
|
152
|
+
composed: true,
|
|
153
|
+
detail: {},
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
if (ov?.open)
|
|
158
|
+
ov.hide("api");
|
|
159
|
+
// Guard the initial render (open=false from the start) so it never emits
|
|
160
|
+
// a phantom close; only a real open→close transition fires the event.
|
|
161
|
+
if (this._wasOpen) {
|
|
162
|
+
this._wasOpen = false;
|
|
163
|
+
this.dispatchEvent(new CustomEvent("xm-popover-close", {
|
|
164
|
+
bubbles: true,
|
|
165
|
+
composed: true,
|
|
166
|
+
detail: { reason: this._closeReason },
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
this._closeReason = "api";
|
|
170
|
+
}
|
|
171
|
+
if (trigger) {
|
|
172
|
+
trigger.setAttribute("aria-expanded", this.open ? "true" : "false");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// Prefer the slotted trigger; fall back to the wrapper so an `open` set before
|
|
176
|
+
// the trigger element is assigned still anchors + shows (no stuck state with
|
|
177
|
+
// aria-expanded="true" and no panel).
|
|
178
|
+
get _triggerEl() {
|
|
179
|
+
const slot = this.renderRoot.querySelector('slot[name="trigger"]');
|
|
180
|
+
return (slot?.assignedElements()[0] ?? this._triggerWrap ?? null);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
__decorate([
|
|
184
|
+
property({ type: Boolean, reflect: true })
|
|
185
|
+
], XmPopover.prototype, "open", void 0);
|
|
186
|
+
__decorate([
|
|
187
|
+
property({ type: String })
|
|
188
|
+
], XmPopover.prototype, "placement", void 0);
|
|
189
|
+
__decorate([
|
|
190
|
+
property({ type: String })
|
|
191
|
+
], XmPopover.prototype, "label", void 0);
|
|
192
|
+
__decorate([
|
|
193
|
+
query("xm-overlay")
|
|
194
|
+
], XmPopover.prototype, "_overlay", void 0);
|
|
195
|
+
__decorate([
|
|
196
|
+
query(".popover__panel")
|
|
197
|
+
], XmPopover.prototype, "_panel", void 0);
|
|
198
|
+
__decorate([
|
|
199
|
+
query(".popover__trigger")
|
|
200
|
+
], XmPopover.prototype, "_triggerWrap", void 0);
|
|
201
|
+
XmPopover = __decorate([
|
|
202
|
+
customElement("xm-popover")
|
|
203
|
+
], XmPopover);
|
|
204
|
+
export { XmPopover };
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
table/index.ts — <xm-table>.
|
|
3
3
|
|
|
4
4
|
Static, semantic, hairline-ruled table. Renders a real <table> in
|
|
5
|
-
its shadow root
|
|
5
|
+
its shadow root from a data-driven { columns, rows } model (the
|
|
6
|
+
shape xm-data-table composes in Epic 6):
|
|
6
7
|
|
|
7
|
-
1. Data-driven (preferred; this is the shape xm-data-table composes
|
|
8
|
-
in Epic 6):
|
|
9
8
|
const t = document.querySelector("xm-table");
|
|
10
9
|
t.columns = [
|
|
11
10
|
{ key: "name", label: "File name" },
|
|
@@ -16,10 +15,7 @@
|
|
|
16
15
|
{ name: "routing.yaml", type: "YAML", size: "8.4 KB" },
|
|
17
16
|
{ name: "schema.json", type: "JSON", size: "412 KB" },
|
|
18
17
|
];
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
2. Slotted (escape hatch for fully bespoke markup): author your own
|
|
22
|
-
<table> structure inside the element; it is projected verbatim.
|
|
18
|
+
Or set columns/rows as JSON via the `columns` / `rows` attributes.
|
|
23
19
|
|
|
24
20
|
Column model: { key, label, numeric?, align?, muted? }.
|
|
25
21
|
numeric → right-align + tabular figures (NFR-22). Put the unit in
|
|
@@ -75,7 +71,6 @@ let XmTable = class XmTable extends LitElement {
|
|
|
75
71
|
return Boolean(col.numeric);
|
|
76
72
|
}
|
|
77
73
|
render() {
|
|
78
|
-
const dataMode = this.columns.length > 0;
|
|
79
74
|
return html `
|
|
80
75
|
<link rel="stylesheet" href="${TABLE_CSS}" />
|
|
81
76
|
<style>
|
|
@@ -86,7 +81,7 @@ let XmTable = class XmTable extends LitElement {
|
|
|
86
81
|
display: none;
|
|
87
82
|
}
|
|
88
83
|
</style>
|
|
89
|
-
${
|
|
84
|
+
${this._renderData()}
|
|
90
85
|
`;
|
|
91
86
|
}
|
|
92
87
|
_renderData() {
|
package/dist/lit/index.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export * from "./components/chip-group/index.js";
|
|
|
18
18
|
export * from "./components/code/index.js";
|
|
19
19
|
export * from "./components/composer/index.js";
|
|
20
20
|
export * from "./components/data-table/index.js";
|
|
21
|
+
export * from "./components/date-range/index.js";
|
|
21
22
|
export * from "./components/dialog/index.js";
|
|
22
23
|
export * from "./components/divider/index.js";
|
|
23
24
|
export * from "./components/empty-state/index.js";
|
|
@@ -33,6 +34,7 @@ export * from "./components/menu/index.js";
|
|
|
33
34
|
export * from "./components/navigation-drawer/index.js";
|
|
34
35
|
export * from "./components/overlay/index.js";
|
|
35
36
|
export * from "./components/pagination/index.js";
|
|
37
|
+
export * from "./components/popover/index.js";
|
|
36
38
|
export * from "./components/progress/index.js";
|
|
37
39
|
export * from "./components/radio-group/index.js";
|
|
38
40
|
export * from "./components/select/index.js";
|
package/dist/lit/index.js
CHANGED
|
@@ -27,6 +27,7 @@ export * from "./components/chip-group/index.js";
|
|
|
27
27
|
export * from "./components/code/index.js";
|
|
28
28
|
export * from "./components/composer/index.js";
|
|
29
29
|
export * from "./components/data-table/index.js";
|
|
30
|
+
export * from "./components/date-range/index.js";
|
|
30
31
|
export * from "./components/dialog/index.js";
|
|
31
32
|
export * from "./components/divider/index.js";
|
|
32
33
|
export * from "./components/empty-state/index.js";
|
|
@@ -42,6 +43,7 @@ export * from "./components/menu/index.js";
|
|
|
42
43
|
export * from "./components/navigation-drawer/index.js";
|
|
43
44
|
export * from "./components/overlay/index.js";
|
|
44
45
|
export * from "./components/pagination/index.js";
|
|
46
|
+
export * from "./components/popover/index.js";
|
|
45
47
|
export * from "./components/progress/index.js";
|
|
46
48
|
export * from "./components/radio-group/index.js";
|
|
47
49
|
export * from "./components/select/index.js";
|