@xmesh/system-design 0.0.1
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/README.md +472 -0
- package/assets/brand-lockup-dark.svg +9 -0
- package/assets/brand-lockup-light.svg +9 -0
- package/assets/brand-mark.svg +9 -0
- package/colors_and_type.css +11 -0
- package/dist/lit/components/alert/index.css +201 -0
- package/dist/lit/components/alert/index.d.ts +25 -0
- package/dist/lit/components/alert/index.js +191 -0
- package/dist/lit/components/app-bar/index.css +80 -0
- package/dist/lit/components/app-bar/index.d.ts +19 -0
- package/dist/lit/components/app-bar/index.js +120 -0
- package/dist/lit/components/artifact/index.css +166 -0
- package/dist/lit/components/artifact/index.d.ts +37 -0
- package/dist/lit/components/artifact/index.js +294 -0
- package/dist/lit/components/autocomplete/index.css +171 -0
- package/dist/lit/components/autocomplete/index.d.ts +47 -0
- package/dist/lit/components/autocomplete/index.js +404 -0
- package/dist/lit/components/avatar/index.css +62 -0
- package/dist/lit/components/avatar/index.d.ts +19 -0
- package/dist/lit/components/avatar/index.js +112 -0
- package/dist/lit/components/avatar-group/index.css +60 -0
- package/dist/lit/components/avatar-group/index.d.ts +19 -0
- package/dist/lit/components/avatar-group/index.js +97 -0
- package/dist/lit/components/badge/index.css +72 -0
- package/dist/lit/components/badge/index.d.ts +18 -0
- package/dist/lit/components/badge/index.js +115 -0
- package/dist/lit/components/brand-mark/index.css +109 -0
- package/dist/lit/components/brand-mark/index.d.ts +24 -0
- package/dist/lit/components/brand-mark/index.js +116 -0
- package/dist/lit/components/breadcrumbs/index.css +91 -0
- package/dist/lit/components/breadcrumbs/index.d.ts +19 -0
- package/dist/lit/components/breadcrumbs/index.js +104 -0
- package/dist/lit/components/bubble/index.css +182 -0
- package/dist/lit/components/bubble/index.d.ts +72 -0
- package/dist/lit/components/bubble/index.js +617 -0
- package/dist/lit/components/button/index.css +342 -0
- package/dist/lit/components/button/index.d.ts +32 -0
- package/dist/lit/components/button/index.js +202 -0
- package/dist/lit/components/card/index.css +99 -0
- package/dist/lit/components/card/index.d.ts +20 -0
- package/dist/lit/components/card/index.js +133 -0
- package/dist/lit/components/chat/index.css +292 -0
- package/dist/lit/components/chat/index.d.ts +74 -0
- package/dist/lit/components/chat/index.js +589 -0
- package/dist/lit/components/checkbox/index.css +126 -0
- package/dist/lit/components/checkbox/index.d.ts +21 -0
- package/dist/lit/components/checkbox/index.js +138 -0
- package/dist/lit/components/chip/index.css +145 -0
- package/dist/lit/components/chip/index.d.ts +30 -0
- package/dist/lit/components/chip/index.js +230 -0
- package/dist/lit/components/chip-group/index.css +19 -0
- package/dist/lit/components/chip-group/index.d.ts +24 -0
- package/dist/lit/components/chip-group/index.js +171 -0
- package/dist/lit/components/code/index.css +42 -0
- package/dist/lit/components/code/index.d.ts +12 -0
- package/dist/lit/components/code/index.js +68 -0
- package/dist/lit/components/composer/index.css +548 -0
- package/dist/lit/components/composer/index.d.ts +67 -0
- package/dist/lit/components/composer/index.js +713 -0
- package/dist/lit/components/data-table/index.css +166 -0
- package/dist/lit/components/data-table/index.d.ts +55 -0
- package/dist/lit/components/data-table/index.js +390 -0
- package/dist/lit/components/dialog/index.css +124 -0
- package/dist/lit/components/dialog/index.d.ts +24 -0
- package/dist/lit/components/dialog/index.js +199 -0
- package/dist/lit/components/divider/index.css +27 -0
- package/dist/lit/components/divider/index.d.ts +13 -0
- package/dist/lit/components/divider/index.js +67 -0
- package/dist/lit/components/empty-state/index.css +69 -0
- package/dist/lit/components/empty-state/index.d.ts +21 -0
- package/dist/lit/components/empty-state/index.js +123 -0
- package/dist/lit/components/expansion-panel/index.css +120 -0
- package/dist/lit/components/expansion-panel/index.d.ts +22 -0
- package/dist/lit/components/expansion-panel/index.js +174 -0
- package/dist/lit/components/field/index.css +223 -0
- package/dist/lit/components/field/index.d.ts +106 -0
- package/dist/lit/components/field/index.js +388 -0
- package/dist/lit/components/file-input/index.css +257 -0
- package/dist/lit/components/file-input/index.d.ts +30 -0
- package/dist/lit/components/file-input/index.js +298 -0
- package/dist/lit/components/form/index.css +29 -0
- package/dist/lit/components/form/index.d.ts +38 -0
- package/dist/lit/components/form/index.js +192 -0
- package/dist/lit/components/grid/index.css +53 -0
- package/dist/lit/components/grid/index.d.ts +14 -0
- package/dist/lit/components/grid/index.js +82 -0
- package/dist/lit/components/kbd/index.css +35 -0
- package/dist/lit/components/kbd/index.d.ts +11 -0
- package/dist/lit/components/kbd/index.js +43 -0
- package/dist/lit/components/list/index.css +15 -0
- package/dist/lit/components/list/index.d.ts +28 -0
- package/dist/lit/components/list/index.js +188 -0
- package/dist/lit/components/list-item/index.css +119 -0
- package/dist/lit/components/list-item/index.d.ts +20 -0
- package/dist/lit/components/list-item/index.js +127 -0
- package/dist/lit/components/menu/index.css +94 -0
- package/dist/lit/components/menu/index.d.ts +47 -0
- package/dist/lit/components/menu/index.js +386 -0
- package/dist/lit/components/navigation-drawer/index.css +114 -0
- package/dist/lit/components/navigation-drawer/index.d.ts +29 -0
- package/dist/lit/components/navigation-drawer/index.js +218 -0
- package/dist/lit/components/overlay/index.css +171 -0
- package/dist/lit/components/overlay/index.d.ts +65 -0
- package/dist/lit/components/overlay/index.js +566 -0
- package/dist/lit/components/pagination/index.css +102 -0
- package/dist/lit/components/pagination/index.d.ts +22 -0
- package/dist/lit/components/pagination/index.js +184 -0
- package/dist/lit/components/primitives/index.css +504 -0
- package/dist/lit/components/primitives/index.d.ts +25 -0
- package/dist/lit/components/primitives/index.js +283 -0
- package/dist/lit/components/progress/index.css +143 -0
- package/dist/lit/components/progress/index.d.ts +23 -0
- package/dist/lit/components/progress/index.js +180 -0
- package/dist/lit/components/radio-group/index.css +178 -0
- package/dist/lit/components/radio-group/index.d.ts +35 -0
- package/dist/lit/components/radio-group/index.js +292 -0
- package/dist/lit/components/select/index.css +151 -0
- package/dist/lit/components/select/index.d.ts +50 -0
- package/dist/lit/components/select/index.js +390 -0
- package/dist/lit/components/sidebar-item/index.css +133 -0
- package/dist/lit/components/sidebar-item/index.d.ts +20 -0
- package/dist/lit/components/sidebar-item/index.js +105 -0
- package/dist/lit/components/skeleton/index.css +81 -0
- package/dist/lit/components/skeleton/index.d.ts +19 -0
- package/dist/lit/components/skeleton/index.js +119 -0
- package/dist/lit/components/slider/index.css +171 -0
- package/dist/lit/components/slider/index.d.ts +36 -0
- package/dist/lit/components/slider/index.js +302 -0
- package/dist/lit/components/snackbar/index.css +279 -0
- package/dist/lit/components/snackbar/index.d.ts +33 -0
- package/dist/lit/components/snackbar/index.js +195 -0
- package/dist/lit/components/stack/index.css +41 -0
- package/dist/lit/components/stack/index.d.ts +20 -0
- package/dist/lit/components/stack/index.js +103 -0
- package/dist/lit/components/switch/index.css +126 -0
- package/dist/lit/components/switch/index.d.ts +17 -0
- package/dist/lit/components/switch/index.js +116 -0
- package/dist/lit/components/table/index.css +85 -0
- package/dist/lit/components/table/index.d.ts +25 -0
- package/dist/lit/components/table/index.js +139 -0
- package/dist/lit/components/tabs/index.css +116 -0
- package/dist/lit/components/tabs/index.d.ts +49 -0
- package/dist/lit/components/tabs/index.js +320 -0
- package/dist/lit/components/text-field/index.css +90 -0
- package/dist/lit/components/text-field/index.d.ts +17 -0
- package/dist/lit/components/text-field/index.js +101 -0
- package/dist/lit/components/textarea/index.css +55 -0
- package/dist/lit/components/textarea/index.d.ts +26 -0
- package/dist/lit/components/textarea/index.js +124 -0
- package/dist/lit/components/tooltip/index.css +37 -0
- package/dist/lit/components/tooltip/index.d.ts +31 -0
- package/dist/lit/components/tooltip/index.js +196 -0
- package/dist/lit/components/validation/index.css +386 -0
- package/dist/lit/components/validation/index.d.ts +45 -0
- package/dist/lit/components/validation/index.js +318 -0
- package/dist/lit/index.d.ts +50 -0
- package/dist/lit/index.js +59 -0
- package/package.json +81 -0
- package/styles/README.md +346 -0
- package/styles/_elevation.css +24 -0
- package/styles/_fonts.css +6 -0
- package/styles/_layout.css +37 -0
- package/styles/_primitives.css +154 -0
- package/styles/_scroll.css +75 -0
- package/styles/_semantic.css +146 -0
- package/styles/_space.css +61 -0
- package/styles/_type.css +139 -0
- package/styles/_xmesh-extensions.css +232 -0
- package/styles/index.css +44 -0
- package/styles/md3/_color.css +102 -0
- package/styles/md3/_elevation.css +26 -0
- package/styles/md3/_motion.css +35 -0
- package/styles/md3/_shape.css +22 -0
- package/styles/md3/_state.css +22 -0
- package/styles/md3/_type.css +111 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/*
|
|
2
|
+
slider/index.ts — <xm-slider>, a draggable range slider on XmField (Story 2.8).
|
|
3
|
+
|
|
4
|
+
Extends XmField for the chrome (label / helper / disabled / form association /
|
|
5
|
+
focus) and renders a track + active fill + thumb. The bordered text-field
|
|
6
|
+
control box is wrong for a slider, so this overrides render() for a
|
|
7
|
+
label-row / track-row / helper-row layout, but reuses the base's state +
|
|
8
|
+
form-association + ARIA hooks (AD-7).
|
|
9
|
+
|
|
10
|
+
Value contract (AD-6 / AD-8a): uncontrolled-first. The live numeric value is
|
|
11
|
+
owned internally and mirrored into the inherited string `value` (+
|
|
12
|
+
ElementInternals) so xm-form sees a stable serialization; the events carry the
|
|
13
|
+
real number. During drag / keyboard stepping it emits `input` (per movement);
|
|
14
|
+
on commit (pointerup / blur after keyboard) it emits `change` — both with
|
|
15
|
+
detail.value: number.
|
|
16
|
+
|
|
17
|
+
Keyboard (slider APG, AD-9a): ←/↓ step down, →/↑ step up, Home → min, End →
|
|
18
|
+
max, PageDown/PageUp step by a larger increment. Value is clamped to [min,max]
|
|
19
|
+
and snapped to step.
|
|
20
|
+
|
|
21
|
+
Active fill + thumb are the coral accent (--md-sys-color-primary); the inactive
|
|
22
|
+
track + thumb size + track height are --xm-slider-* extensions (AD-10). Coral =
|
|
23
|
+
the filled value, never severity (AD-11).
|
|
24
|
+
|
|
25
|
+
Shadow DOM. Lit is a bare `import` (peer dep).
|
|
26
|
+
*/
|
|
27
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
28
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
29
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
30
|
+
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;
|
|
31
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
32
|
+
};
|
|
33
|
+
import { html, nothing } from "lit";
|
|
34
|
+
import { customElement, property, query } from "lit/decorators.js";
|
|
35
|
+
import { XmField } from "../field/index.js";
|
|
36
|
+
const SLIDER_CSS = new URL("../slider/index.css", import.meta.url).href;
|
|
37
|
+
const PRIMITIVES_CSS = new URL("../primitives/index.css", import.meta.url).href;
|
|
38
|
+
let XmSlider = class XmSlider extends XmField {
|
|
39
|
+
constructor() {
|
|
40
|
+
super(...arguments);
|
|
41
|
+
this.min = 0;
|
|
42
|
+
this.max = 100;
|
|
43
|
+
this.step = 1;
|
|
44
|
+
/** Show the current numeric value as a label beside the track. */
|
|
45
|
+
this.showValue = false;
|
|
46
|
+
/** Optional unit appended to the displayed value (e.g. "%", "px"). */
|
|
47
|
+
this.unit = "";
|
|
48
|
+
this._dragging = false;
|
|
49
|
+
this._onPointerDown = (e) => {
|
|
50
|
+
if (this.nonInteractive)
|
|
51
|
+
return;
|
|
52
|
+
e.preventDefault();
|
|
53
|
+
this._dragging = true;
|
|
54
|
+
e.target.setPointerCapture?.(e.pointerId);
|
|
55
|
+
this._setValue(this._valueFromClientX(e.clientX), true);
|
|
56
|
+
this._focusThumb();
|
|
57
|
+
};
|
|
58
|
+
this._onPointerMove = (e) => {
|
|
59
|
+
if (!this._dragging)
|
|
60
|
+
return;
|
|
61
|
+
this._setValue(this._valueFromClientX(e.clientX), true);
|
|
62
|
+
};
|
|
63
|
+
this._onPointerUp = (e) => {
|
|
64
|
+
if (!this._dragging)
|
|
65
|
+
return;
|
|
66
|
+
this._dragging = false;
|
|
67
|
+
e.target.releasePointerCapture?.(e.pointerId);
|
|
68
|
+
this._commit();
|
|
69
|
+
};
|
|
70
|
+
// ── Keyboard (slider APG) ───────────────────────────────────────────
|
|
71
|
+
this._onKeydown = (e) => {
|
|
72
|
+
if (this.nonInteractive)
|
|
73
|
+
return;
|
|
74
|
+
const big = Math.max(this.step, (this.max - this.min) / 10);
|
|
75
|
+
let handled = true;
|
|
76
|
+
switch (e.key) {
|
|
77
|
+
case "ArrowLeft":
|
|
78
|
+
case "ArrowDown":
|
|
79
|
+
this._setValue(this._num - this.step, true);
|
|
80
|
+
break;
|
|
81
|
+
case "ArrowRight":
|
|
82
|
+
case "ArrowUp":
|
|
83
|
+
this._setValue(this._num + this.step, true);
|
|
84
|
+
break;
|
|
85
|
+
case "PageDown":
|
|
86
|
+
this._setValue(this._num - big, true);
|
|
87
|
+
break;
|
|
88
|
+
case "PageUp":
|
|
89
|
+
this._setValue(this._num + big, true);
|
|
90
|
+
break;
|
|
91
|
+
case "Home":
|
|
92
|
+
this._setValue(this.min, true);
|
|
93
|
+
break;
|
|
94
|
+
case "End":
|
|
95
|
+
this._setValue(this.max, true);
|
|
96
|
+
break;
|
|
97
|
+
default:
|
|
98
|
+
handled = false;
|
|
99
|
+
}
|
|
100
|
+
if (handled)
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
};
|
|
103
|
+
this._onBlur = () => {
|
|
104
|
+
if (!this._dragging)
|
|
105
|
+
this._commit();
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
connectedCallback() {
|
|
109
|
+
super.connectedCallback();
|
|
110
|
+
// Seed the numeric value from the uncontrolled `value` attribute, snapping
|
|
111
|
+
// into range. If unset, default to min.
|
|
112
|
+
const seed = this.initialValue === "" ? this.min : Number(this.initialValue);
|
|
113
|
+
this._value = String(this._clampSnap(Number.isFinite(seed) ? seed : this.min));
|
|
114
|
+
this.internals.setFormValue(this._value);
|
|
115
|
+
}
|
|
116
|
+
/** Typed live read; the inherited string `value` stays in sync. */
|
|
117
|
+
get valueAsNumber() {
|
|
118
|
+
return Number(this._value);
|
|
119
|
+
}
|
|
120
|
+
get _num() {
|
|
121
|
+
const n = Number(this._value);
|
|
122
|
+
return Number.isFinite(n) ? n : this.min;
|
|
123
|
+
}
|
|
124
|
+
get _fraction() {
|
|
125
|
+
const span = this.max - this.min;
|
|
126
|
+
if (span <= 0)
|
|
127
|
+
return 0;
|
|
128
|
+
return (this._num - this.min) / span;
|
|
129
|
+
}
|
|
130
|
+
_clampSnap(v) {
|
|
131
|
+
const clamped = Math.min(this.max, Math.max(this.min, v));
|
|
132
|
+
if (this.step <= 0)
|
|
133
|
+
return clamped;
|
|
134
|
+
const steps = Math.round((clamped - this.min) / this.step);
|
|
135
|
+
const snapped = this.min + steps * this.step;
|
|
136
|
+
// Avoid binary-float drift in the displayed/emitted number.
|
|
137
|
+
const decimals = (String(this.step).split(".")[1] ?? "").length;
|
|
138
|
+
return Number(Math.min(this.max, Math.max(this.min, snapped)).toFixed(decimals));
|
|
139
|
+
}
|
|
140
|
+
_setValue(v, emitInput) {
|
|
141
|
+
const next = this._clampSnap(v);
|
|
142
|
+
if (next === this._num && !emitInput)
|
|
143
|
+
return;
|
|
144
|
+
this._value = String(next);
|
|
145
|
+
this.internals.setFormValue(this._value);
|
|
146
|
+
if (emitInput) {
|
|
147
|
+
this.dispatchEvent(new CustomEvent("input", {
|
|
148
|
+
bubbles: true,
|
|
149
|
+
composed: true,
|
|
150
|
+
detail: { value: next },
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
_commit() {
|
|
155
|
+
this.dispatchEvent(new CustomEvent("change", {
|
|
156
|
+
bubbles: true,
|
|
157
|
+
composed: true,
|
|
158
|
+
detail: { value: this._num },
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
// ── Pointer drag ────────────────────────────────────────────────────
|
|
162
|
+
_valueFromClientX(clientX) {
|
|
163
|
+
const track = this._track;
|
|
164
|
+
if (!track)
|
|
165
|
+
return this._num;
|
|
166
|
+
const rect = track.getBoundingClientRect();
|
|
167
|
+
if (rect.width <= 0)
|
|
168
|
+
return this._num;
|
|
169
|
+
const frac = Math.min(1, Math.max(0, (clientX - rect.left) / rect.width));
|
|
170
|
+
return this.min + frac * (this.max - this.min);
|
|
171
|
+
}
|
|
172
|
+
_focusThumb() {
|
|
173
|
+
const thumb = this.renderRoot.querySelector(".slider__thumb");
|
|
174
|
+
thumb?.focus();
|
|
175
|
+
}
|
|
176
|
+
updated(changed) {
|
|
177
|
+
super.updated?.(changed);
|
|
178
|
+
// Re-snap if min/max/step change after seed so the value stays valid.
|
|
179
|
+
if (changed.has("min") || changed.has("max") || changed.has("step")) {
|
|
180
|
+
const re = this._clampSnap(this._num);
|
|
181
|
+
if (re !== this._num) {
|
|
182
|
+
this._value = String(re);
|
|
183
|
+
this.internals.setFormValue(this._value);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
get _valueText() {
|
|
188
|
+
return this.unit ? `${this._num} ${this.unit}` : String(this._num);
|
|
189
|
+
}
|
|
190
|
+
render() {
|
|
191
|
+
const cls = [
|
|
192
|
+
"slider",
|
|
193
|
+
this.effectiveDisabled ? "slider--disabled" : "",
|
|
194
|
+
this.isError ? "slider--error" : "",
|
|
195
|
+
]
|
|
196
|
+
.filter(Boolean)
|
|
197
|
+
.join(" ");
|
|
198
|
+
const helperText = this.isError ? this.error : this.helper;
|
|
199
|
+
const ctrl = this.controlAria;
|
|
200
|
+
const pct = `${(this._fraction * 100).toFixed(2)}%`;
|
|
201
|
+
return html `
|
|
202
|
+
<link rel="stylesheet" href="${PRIMITIVES_CSS}" />
|
|
203
|
+
<link rel="stylesheet" href="${SLIDER_CSS}" />
|
|
204
|
+
<style>
|
|
205
|
+
:host {
|
|
206
|
+
display: block;
|
|
207
|
+
}
|
|
208
|
+
:host([hidden]) {
|
|
209
|
+
display: none;
|
|
210
|
+
}
|
|
211
|
+
</style>
|
|
212
|
+
<div class="${cls}">
|
|
213
|
+
${this.label
|
|
214
|
+
? html `<div class="slider__label-row">
|
|
215
|
+
<label class="slider__label" id="${ctrl.id}-label">
|
|
216
|
+
<span class="slider__label-text">${this.label}</span>
|
|
217
|
+
${this.required
|
|
218
|
+
? html `<span class="slider__required" aria-hidden="true"
|
|
219
|
+
>*</span
|
|
220
|
+
>`
|
|
221
|
+
: nothing}
|
|
222
|
+
</label>
|
|
223
|
+
${this.showValue
|
|
224
|
+
? html `<span class="slider__value">${this._valueText}</span>`
|
|
225
|
+
: nothing}
|
|
226
|
+
</div>`
|
|
227
|
+
: this.showValue
|
|
228
|
+
? html `<div class="slider__label-row">
|
|
229
|
+
<span class="slider__value">${this._valueText}</span>
|
|
230
|
+
</div>`
|
|
231
|
+
: nothing}
|
|
232
|
+
|
|
233
|
+
<div
|
|
234
|
+
class="slider__track"
|
|
235
|
+
@pointerdown=${this._onPointerDown}
|
|
236
|
+
@pointermove=${this._onPointerMove}
|
|
237
|
+
@pointerup=${this._onPointerUp}
|
|
238
|
+
>
|
|
239
|
+
<span class="slider__rail" aria-hidden="true"></span>
|
|
240
|
+
<span class="slider__fill" style="width:${pct}" aria-hidden="true"></span>
|
|
241
|
+
<span
|
|
242
|
+
class="slider__thumb"
|
|
243
|
+
id="${ctrl.id}"
|
|
244
|
+
role="slider"
|
|
245
|
+
tabindex="${this.effectiveDisabled ? -1 : 0}"
|
|
246
|
+
aria-valuemin="${this.min}"
|
|
247
|
+
aria-valuemax="${this.max}"
|
|
248
|
+
aria-valuenow="${this._num}"
|
|
249
|
+
aria-valuetext="${this.unit ? this._valueText : nothing}"
|
|
250
|
+
aria-labelledby="${this.label ? `${ctrl.id}-label` : nothing}"
|
|
251
|
+
aria-label="${this.label ? nothing : "Value"}"
|
|
252
|
+
aria-describedby="${helperText ? ctrl.describedBy : nothing}"
|
|
253
|
+
aria-invalid="${ctrl.invalid ?? nothing}"
|
|
254
|
+
aria-disabled="${this.effectiveDisabled ? "true" : nothing}"
|
|
255
|
+
style="left:${pct}"
|
|
256
|
+
@keydown=${this._onKeydown}
|
|
257
|
+
@blur=${this._onBlur}
|
|
258
|
+
></span>
|
|
259
|
+
</div>
|
|
260
|
+
|
|
261
|
+
${helperText
|
|
262
|
+
? html `<div
|
|
263
|
+
class="slider__message ${this.isError
|
|
264
|
+
? "slider__message--error"
|
|
265
|
+
: "slider__message--helper"}"
|
|
266
|
+
id="${ctrl.describedBy}"
|
|
267
|
+
role=${this.isError ? "alert" : nothing}
|
|
268
|
+
>
|
|
269
|
+
${this.isError
|
|
270
|
+
? html `<span class="slider__error-icon" aria-hidden="true">
|
|
271
|
+
<xm-warn-icon size="14"></xm-warn-icon>
|
|
272
|
+
</span>`
|
|
273
|
+
: nothing}
|
|
274
|
+
<span class="slider__message-text">${helperText}</span>
|
|
275
|
+
</div>`
|
|
276
|
+
: nothing}
|
|
277
|
+
</div>
|
|
278
|
+
`;
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
__decorate([
|
|
282
|
+
property({ type: Number })
|
|
283
|
+
], XmSlider.prototype, "min", void 0);
|
|
284
|
+
__decorate([
|
|
285
|
+
property({ type: Number })
|
|
286
|
+
], XmSlider.prototype, "max", void 0);
|
|
287
|
+
__decorate([
|
|
288
|
+
property({ type: Number })
|
|
289
|
+
], XmSlider.prototype, "step", void 0);
|
|
290
|
+
__decorate([
|
|
291
|
+
property({ type: Boolean, attribute: "show-value" })
|
|
292
|
+
], XmSlider.prototype, "showValue", void 0);
|
|
293
|
+
__decorate([
|
|
294
|
+
property({ type: String })
|
|
295
|
+
], XmSlider.prototype, "unit", void 0);
|
|
296
|
+
__decorate([
|
|
297
|
+
query(".slider__track")
|
|
298
|
+
], XmSlider.prototype, "_track", void 0);
|
|
299
|
+
XmSlider = __decorate([
|
|
300
|
+
customElement("xm-slider")
|
|
301
|
+
], XmSlider);
|
|
302
|
+
export { XmSlider };
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
Snackbar — backend error surface
|
|
3
|
+
|
|
4
|
+
Styles for <Snackbar /> (components/snackbar/index.jsx). Lives at the bottom
|
|
5
|
+
of the chat pane, above the composer. The host owns placement (an
|
|
6
|
+
absolute container inside .chat-main); this rule set styles the card
|
|
7
|
+
itself, its severity variants, the dim backdrop overlay, and the entry
|
|
8
|
+
animation. The card is neutral; the gradient overlay uses the
|
|
9
|
+
neutral `--xm-snackbar-scrim*` tokens (no severity hue).
|
|
10
|
+
|
|
11
|
+
Note: the .failmode-* picker classes used by chat-shell.jsx are NOT
|
|
12
|
+
part of this component — they live in ../styles.css as prototype
|
|
13
|
+
chrome.
|
|
14
|
+
============================================ */
|
|
15
|
+
|
|
16
|
+
.snackbar-stack {
|
|
17
|
+
position: absolute;
|
|
18
|
+
inset: 0;
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
align-items: center;
|
|
22
|
+
justify-content: flex-end;
|
|
23
|
+
gap: var(--s-2);
|
|
24
|
+
pointer-events: none;
|
|
25
|
+
padding: 0 var(--s-6) 116px; /* sits clearly above the chat-shell__composer */
|
|
26
|
+
z-index: 40;
|
|
27
|
+
}
|
|
28
|
+
.snackbar-stack > * { pointer-events: auto; }
|
|
29
|
+
|
|
30
|
+
/* Dim backdrop behind the snackbar so the error reads as a focused
|
|
31
|
+
modal-ish surface, not a passive notification. Sits below the snackbar
|
|
32
|
+
in the same stack and fades in with the same easing.
|
|
33
|
+
|
|
34
|
+
We do NOT use backdrop-filter here — a full-pane blur is expensive on
|
|
35
|
+
every paint, and the chat behind contains a virtualized scroll list, so
|
|
36
|
+
it would regress scrolling. Instead we use a denser overlay color
|
|
37
|
+
(theme-aware) plus a faint vertical gradient so the snackbar still feels
|
|
38
|
+
"lifted" without any GPU filter work. */
|
|
39
|
+
.snackbar-overlay {
|
|
40
|
+
position: absolute;
|
|
41
|
+
inset: 0;
|
|
42
|
+
background:
|
|
43
|
+
linear-gradient(
|
|
44
|
+
to top,
|
|
45
|
+
var(--xm-snackbar-scrim-strong) 0%,
|
|
46
|
+
var(--xm-snackbar-scrim) 60%,
|
|
47
|
+
var(--xm-snackbar-scrim-soft) 100%
|
|
48
|
+
);
|
|
49
|
+
animation: snackbar-overlay-in var(--md-sys-motion-duration-medium1) cubic-bezier(0.32, 0.72, 0.24, 1) both;
|
|
50
|
+
pointer-events: auto; /* clicks land on the overlay, not the chat behind */
|
|
51
|
+
}
|
|
52
|
+
@keyframes snackbar-overlay-in {
|
|
53
|
+
from { opacity: 0; }
|
|
54
|
+
to { opacity: 1; }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.snackbar {
|
|
58
|
+
/* Component-local vars — single neutral surface for all severities.
|
|
59
|
+
Severity is communicated by the icon and body copy, not by hue. */
|
|
60
|
+
/* The card rides the surface family (--snk-bg = surface-container-high),
|
|
61
|
+
so ink must come from the on-surface family, not inverse-on-surface —
|
|
62
|
+
pairing inverse ink (a dark ink) on this dark surface reads dark-on-dark
|
|
63
|
+
and fails AA (POLICIES 7a). --snk-accent stays the brightest on-surface
|
|
64
|
+
tone so the reversed primary button keeps high contrast. */
|
|
65
|
+
--snk-bg: var(--md-sys-color-surface-container-high);
|
|
66
|
+
--snk-border: var(--md-sys-color-outline-variant);
|
|
67
|
+
--snk-accent: var(--md-sys-color-on-surface);
|
|
68
|
+
--snk-ink: var(--md-sys-color-on-surface);
|
|
69
|
+
--snk-ink-soft: var(--md-sys-color-on-surface-variant);
|
|
70
|
+
|
|
71
|
+
position: relative;
|
|
72
|
+
display: grid;
|
|
73
|
+
grid-template-columns: auto 1fr auto auto;
|
|
74
|
+
align-items: center;
|
|
75
|
+
gap: var(--s-3-5);
|
|
76
|
+
width: 100%;
|
|
77
|
+
max-width: 620px;
|
|
78
|
+
padding: var(--s-3-5) var(--s-3-5) var(--s-3-5) var(--s-4);
|
|
79
|
+
background: var(--snk-bg);
|
|
80
|
+
border: 1px solid var(--snk-border);
|
|
81
|
+
border-radius: var(--md-sys-shape-corner-medium);
|
|
82
|
+
box-shadow: var(--xm-elevation-snackbar);
|
|
83
|
+
color: var(--snk-ink);
|
|
84
|
+
font-family: var(--md-sys-typescale-body-large-font);
|
|
85
|
+
animation: snackbar-in 280ms cubic-bezier(0.32, 0.72, 0.24, 1) both;
|
|
86
|
+
}
|
|
87
|
+
.snackbar--static { animation: none; }
|
|
88
|
+
|
|
89
|
+
@keyframes snackbar-in {
|
|
90
|
+
from {
|
|
91
|
+
opacity: 0;
|
|
92
|
+
transform: translateY(8px) scale(0.985);
|
|
93
|
+
}
|
|
94
|
+
to {
|
|
95
|
+
opacity: 1;
|
|
96
|
+
transform: translateY(0) scale(1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Severity variants — class hooks kept for JSX/markup compatibility, but
|
|
101
|
+
they no longer swap colors. The icon and body copy carry the severity. */
|
|
102
|
+
|
|
103
|
+
.snackbar__icon {
|
|
104
|
+
display: inline-flex;
|
|
105
|
+
align-items: center;
|
|
106
|
+
justify-content: center;
|
|
107
|
+
width: 36px;
|
|
108
|
+
height: 36px;
|
|
109
|
+
border-radius: 10px;
|
|
110
|
+
/* Soft on-card highlight chip: stronger contrast in dark, softer in light. */
|
|
111
|
+
background: color-mix(in oklch, var(--snk-accent) 18%, transparent);
|
|
112
|
+
color: var(--snk-accent);
|
|
113
|
+
flex-shrink: 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.snackbar__body {
|
|
117
|
+
min-width: 0;
|
|
118
|
+
display: flex;
|
|
119
|
+
flex-direction: column;
|
|
120
|
+
gap: 3px;
|
|
121
|
+
}
|
|
122
|
+
.snackbar__head {
|
|
123
|
+
display: inline-flex;
|
|
124
|
+
align-items: center;
|
|
125
|
+
gap: var(--s-2);
|
|
126
|
+
font-size: 13.5px;
|
|
127
|
+
font-weight: 600;
|
|
128
|
+
letter-spacing: -0.005em;
|
|
129
|
+
color: var(--snk-ink);
|
|
130
|
+
}
|
|
131
|
+
.snackbar__code {
|
|
132
|
+
font-family: var(--xm-typescale-mono-font);
|
|
133
|
+
font-size: 10.5px;
|
|
134
|
+
font-weight: 700;
|
|
135
|
+
letter-spacing: 0.04em;
|
|
136
|
+
padding: 0;
|
|
137
|
+
background: transparent;
|
|
138
|
+
color: var(--snk-accent);
|
|
139
|
+
flex-shrink: 0;
|
|
140
|
+
}
|
|
141
|
+
.snackbar__title {
|
|
142
|
+
white-space: nowrap;
|
|
143
|
+
overflow: hidden;
|
|
144
|
+
text-overflow: ellipsis;
|
|
145
|
+
}
|
|
146
|
+
.snackbar__msg {
|
|
147
|
+
font-size: 13px;
|
|
148
|
+
color: var(--snk-ink-soft);
|
|
149
|
+
line-height: 1.45;
|
|
150
|
+
text-wrap: pretty;
|
|
151
|
+
}
|
|
152
|
+
.snackbar__meta {
|
|
153
|
+
display: inline-flex;
|
|
154
|
+
align-items: baseline;
|
|
155
|
+
gap: var(--s-1-5);
|
|
156
|
+
margin-top: var(--s-1);
|
|
157
|
+
font-size: 11px;
|
|
158
|
+
}
|
|
159
|
+
.snackbar__meta-label {
|
|
160
|
+
text-transform: uppercase;
|
|
161
|
+
letter-spacing: 0.08em;
|
|
162
|
+
color: var(--snk-ink-soft);
|
|
163
|
+
font-weight: 500;
|
|
164
|
+
opacity: 0.8;
|
|
165
|
+
}
|
|
166
|
+
.snackbar__meta-id {
|
|
167
|
+
font-family: var(--xm-typescale-mono-font);
|
|
168
|
+
color: var(--snk-ink);
|
|
169
|
+
background: transparent;
|
|
170
|
+
border: 0;
|
|
171
|
+
padding: 0;
|
|
172
|
+
font-size: 11px;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.snackbar__actions {
|
|
176
|
+
display: inline-flex;
|
|
177
|
+
align-items: center;
|
|
178
|
+
gap: var(--s-1-5);
|
|
179
|
+
flex-shrink: 0;
|
|
180
|
+
}
|
|
181
|
+
.snackbar__btn {
|
|
182
|
+
appearance: none;
|
|
183
|
+
border: 1px solid transparent;
|
|
184
|
+
background: transparent;
|
|
185
|
+
font: inherit;
|
|
186
|
+
font-size: 12.5px;
|
|
187
|
+
font-weight: 600;
|
|
188
|
+
padding: 7px 11px;
|
|
189
|
+
border-radius: 8px;
|
|
190
|
+
cursor: pointer;
|
|
191
|
+
display: inline-flex;
|
|
192
|
+
align-items: center;
|
|
193
|
+
gap: var(--s-1-5);
|
|
194
|
+
transition:
|
|
195
|
+
background var(--md-sys-motion-duration-short3) var(--md-sys-motion-easing-standard),
|
|
196
|
+
border-color var(--md-sys-motion-duration-short3) var(--md-sys-motion-easing-standard),
|
|
197
|
+
color var(--md-sys-motion-duration-short3) var(--md-sys-motion-easing-standard);
|
|
198
|
+
white-space: nowrap;
|
|
199
|
+
color: var(--snk-ink);
|
|
200
|
+
}
|
|
201
|
+
.snackbar__btn--primary {
|
|
202
|
+
background: var(--snk-accent);
|
|
203
|
+
color: var(--snk-bg); /* high-contrast inverse against the accent */
|
|
204
|
+
border-color: var(--snk-accent);
|
|
205
|
+
}
|
|
206
|
+
.snackbar__btn--primary:hover {
|
|
207
|
+
background: color-mix(in oklab, var(--snk-bg) var(--md-sys-state-hover-state-layer-opacity), var(--snk-accent));
|
|
208
|
+
}
|
|
209
|
+
.snackbar__btn--primary:active { transform: translateY(1px); }
|
|
210
|
+
.snackbar__btn--primary svg { opacity: 0.9; }
|
|
211
|
+
|
|
212
|
+
.snackbar__btn--ghost {
|
|
213
|
+
color: var(--snk-ink-soft);
|
|
214
|
+
border-color: transparent;
|
|
215
|
+
}
|
|
216
|
+
.snackbar__btn--ghost:hover {
|
|
217
|
+
color: var(--snk-ink);
|
|
218
|
+
background: color-mix(in oklch, var(--snk-accent) 14%, transparent);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.snackbar__close {
|
|
222
|
+
appearance: none;
|
|
223
|
+
border: none;
|
|
224
|
+
background: transparent;
|
|
225
|
+
color: var(--snk-ink-soft);
|
|
226
|
+
width: 28px;
|
|
227
|
+
height: 28px;
|
|
228
|
+
border-radius: 6px;
|
|
229
|
+
cursor: pointer;
|
|
230
|
+
display: inline-flex;
|
|
231
|
+
align-items: center;
|
|
232
|
+
justify-content: center;
|
|
233
|
+
transition:
|
|
234
|
+
background var(--md-sys-motion-duration-short3) var(--md-sys-motion-easing-standard),
|
|
235
|
+
color var(--md-sys-motion-duration-short3) var(--md-sys-motion-easing-standard);
|
|
236
|
+
flex-shrink: 0;
|
|
237
|
+
}
|
|
238
|
+
.snackbar__close:hover {
|
|
239
|
+
background: color-mix(in oklch, var(--snk-accent) 14%, transparent);
|
|
240
|
+
color: var(--snk-ink);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* When sitting in the bare canvas (state cards on the design surface), the
|
|
244
|
+
card needs a touch more contrast against the warm canvas background. */
|
|
245
|
+
.snackbar-card-frame {
|
|
246
|
+
background: var(--md-sys-color-surface);
|
|
247
|
+
padding: 30px var(--s-7);
|
|
248
|
+
display: flex;
|
|
249
|
+
align-items: center;
|
|
250
|
+
justify-content: center;
|
|
251
|
+
width: 100%;
|
|
252
|
+
height: 100%;
|
|
253
|
+
position: relative;
|
|
254
|
+
}
|
|
255
|
+
.snackbar-card-frame .frame-label {
|
|
256
|
+
position: absolute;
|
|
257
|
+
top: var(--s-4);
|
|
258
|
+
left: var(--s-6);
|
|
259
|
+
margin: 0;
|
|
260
|
+
}
|
|
261
|
+
.snackbar-card-frame .snackbar {
|
|
262
|
+
max-width: 580px;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/* Compact variant: when the chat shell is narrow, drop the right-side actions
|
|
266
|
+
below the body so nothing gets cut off. The 640px literal mirrors
|
|
267
|
+
--xm-breakpoint-md (styles/_layout.css) — @media can't read the token
|
|
268
|
+
(docs/adr/0001), so the literal is the source-of-truth's echo. */
|
|
269
|
+
@media (max-width: 640px) {
|
|
270
|
+
.snackbar {
|
|
271
|
+
grid-template-columns: auto 1fr auto;
|
|
272
|
+
grid-template-rows: auto auto;
|
|
273
|
+
row-gap: var(--s-2);
|
|
274
|
+
}
|
|
275
|
+
.snackbar__actions {
|
|
276
|
+
grid-column: 1 / -2;
|
|
277
|
+
justify-content: flex-end;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { LitElement } from "lit";
|
|
2
|
+
import type { TemplateResult } from "lit";
|
|
3
|
+
export type SnackKind = "auth" | "server" | "generic";
|
|
4
|
+
export interface SnackPreset {
|
|
5
|
+
kind: SnackKind;
|
|
6
|
+
code: string;
|
|
7
|
+
title: string;
|
|
8
|
+
message: string;
|
|
9
|
+
primaryLabel: string;
|
|
10
|
+
secondaryLabel: string;
|
|
11
|
+
}
|
|
12
|
+
export declare const SNACK_PRESETS: Record<SnackKind, SnackPreset>;
|
|
13
|
+
declare class XmSnackbar extends LitElement {
|
|
14
|
+
kind: SnackKind;
|
|
15
|
+
code: string;
|
|
16
|
+
title: string;
|
|
17
|
+
message: string;
|
|
18
|
+
primaryLabel: string;
|
|
19
|
+
secondaryLabel: string;
|
|
20
|
+
showDismiss: boolean;
|
|
21
|
+
static: boolean;
|
|
22
|
+
createRenderRoot(): HTMLElement | DocumentFragment;
|
|
23
|
+
connectedCallback(): void;
|
|
24
|
+
private _onPrimary;
|
|
25
|
+
private _onDismiss;
|
|
26
|
+
render(): TemplateResult;
|
|
27
|
+
}
|
|
28
|
+
declare global {
|
|
29
|
+
interface HTMLElementTagNameMap {
|
|
30
|
+
"xm-snackbar": XmSnackbar;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export {};
|