@f-ewald/components 0.1.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/LICENSE +28 -0
- package/README.md +147 -0
- package/custom-elements.json +3398 -0
- package/dist/address-autocomplete.d.ts +75 -0
- package/dist/address-autocomplete.d.ts.map +1 -0
- package/dist/address-autocomplete.js +342 -0
- package/dist/address-autocomplete.js.map +1 -0
- package/dist/animate-confetti.d.ts +53 -0
- package/dist/animate-confetti.d.ts.map +1 -0
- package/dist/animate-confetti.js +180 -0
- package/dist/animate-confetti.js.map +1 -0
- package/dist/autocomplete-input.d.ts +73 -0
- package/dist/autocomplete-input.d.ts.map +1 -0
- package/dist/autocomplete-input.js +309 -0
- package/dist/autocomplete-input.js.map +1 -0
- package/dist/confirm-dialog.d.ts +36 -0
- package/dist/confirm-dialog.d.ts.map +1 -0
- package/dist/confirm-dialog.js +191 -0
- package/dist/confirm-dialog.js.map +1 -0
- package/dist/copy-link-button.d.ts +27 -0
- package/dist/copy-link-button.d.ts.map +1 -0
- package/dist/copy-link-button.js +84 -0
- package/dist/copy-link-button.js.map +1 -0
- package/dist/distance-value.d.ts +24 -0
- package/dist/distance-value.d.ts.map +1 -0
- package/dist/distance-value.js +50 -0
- package/dist/distance-value.js.map +1 -0
- package/dist/distribution-chart.d.ts +52 -0
- package/dist/distribution-chart.d.ts.map +1 -0
- package/dist/distribution-chart.js +299 -0
- package/dist/distribution-chart.js.map +1 -0
- package/dist/icons.d.ts +32 -0
- package/dist/icons.d.ts.map +1 -0
- package/dist/icons.js +35 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/percent-bar-chart.d.ts +29 -0
- package/dist/percent-bar-chart.d.ts.map +1 -0
- package/dist/percent-bar-chart.js +92 -0
- package/dist/percent-bar-chart.js.map +1 -0
- package/dist/price-history-chart.d.ts +39 -0
- package/dist/price-history-chart.d.ts.map +1 -0
- package/dist/price-history-chart.js +217 -0
- package/dist/price-history-chart.js.map +1 -0
- package/dist/relative-time.d.ts +26 -0
- package/dist/relative-time.d.ts.map +1 -0
- package/dist/relative-time.js +64 -0
- package/dist/relative-time.js.map +1 -0
- package/dist/reveal-button.d.ts +19 -0
- package/dist/reveal-button.d.ts.map +1 -0
- package/dist/reveal-button.js +62 -0
- package/dist/reveal-button.js.map +1 -0
- package/dist/roman-numeral.d.ts +18 -0
- package/dist/roman-numeral.d.ts.map +1 -0
- package/dist/roman-numeral.js +54 -0
- package/dist/roman-numeral.js.map +1 -0
- package/dist/slide-panel.d.ts +27 -0
- package/dist/slide-panel.d.ts.map +1 -0
- package/dist/slide-panel.js +149 -0
- package/dist/slide-panel.js.map +1 -0
- package/dist/toast-notification.d.ts +53 -0
- package/dist/toast-notification.d.ts.map +1 -0
- package/dist/toast-notification.js +173 -0
- package/dist/toast-notification.js.map +1 -0
- package/dist/tokens.css +21 -0
- package/dist/tokens.d.ts +16 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +79 -0
- package/dist/tokens.js.map +1 -0
- package/dist/utils/distance.d.ts +11 -0
- package/dist/utils/distance.d.ts.map +1 -0
- package/dist/utils/distance.js +25 -0
- package/dist/utils/distance.js.map +1 -0
- package/dist/utils/time.d.ts +10 -0
- package/dist/utils/time.d.ts.map +1 -0
- package/dist/utils/time.js +33 -0
- package/dist/utils/time.js.map +1 -0
- package/dist/weight-bar-chart.d.ts +29 -0
- package/dist/weight-bar-chart.d.ts.map +1 -0
- package/dist/weight-bar-chart.js +96 -0
- package/dist/weight-bar-chart.js.map +1 -0
- package/docs/address-autocomplete.md +68 -0
- package/docs/animate-confetti.md +34 -0
- package/docs/autocomplete-input.md +79 -0
- package/docs/confirm-dialog.md +65 -0
- package/docs/copy-link-button.md +46 -0
- package/docs/distance-value.md +38 -0
- package/docs/distribution-chart.md +51 -0
- package/docs/mcp-evaluation.md +31 -0
- package/docs/percent-bar-chart.md +40 -0
- package/docs/price-history-chart.md +44 -0
- package/docs/relative-time.md +37 -0
- package/docs/reveal-button.md +43 -0
- package/docs/roman-numeral.md +33 -0
- package/docs/slide-panel.md +56 -0
- package/docs/toast-notification.md +52 -0
- package/docs/weight-bar-chart.md +53 -0
- package/llms.txt +361 -0
- package/package.json +69 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
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;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { LitElement, html } from "lit";
|
|
8
|
+
import { customElement, property } from "lit/decorators.js";
|
|
9
|
+
const ROMAN_NUMERALS = {
|
|
10
|
+
M: 1000,
|
|
11
|
+
CM: 900,
|
|
12
|
+
D: 500,
|
|
13
|
+
CD: 400,
|
|
14
|
+
C: 100,
|
|
15
|
+
XC: 90,
|
|
16
|
+
L: 50,
|
|
17
|
+
XL: 40,
|
|
18
|
+
X: 10,
|
|
19
|
+
IX: 9,
|
|
20
|
+
V: 5,
|
|
21
|
+
IV: 4,
|
|
22
|
+
I: 1,
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Converts an integer to a roman numeral inline.
|
|
26
|
+
*
|
|
27
|
+
* @element roman-numeral
|
|
28
|
+
*/
|
|
29
|
+
let RomanNumeral = class RomanNumeral extends LitElement {
|
|
30
|
+
render() {
|
|
31
|
+
return html `${this._convertToRoman(this.value)}`;
|
|
32
|
+
}
|
|
33
|
+
_convertToRoman(num) {
|
|
34
|
+
if (!num) {
|
|
35
|
+
return "NaN";
|
|
36
|
+
}
|
|
37
|
+
let remaining = num;
|
|
38
|
+
let str = "";
|
|
39
|
+
for (const symbol of Object.keys(ROMAN_NUMERALS)) {
|
|
40
|
+
const count = Math.floor(remaining / ROMAN_NUMERALS[symbol]);
|
|
41
|
+
remaining -= count * ROMAN_NUMERALS[symbol];
|
|
42
|
+
str += symbol.repeat(count);
|
|
43
|
+
}
|
|
44
|
+
return str;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
__decorate([
|
|
48
|
+
property({ type: Number })
|
|
49
|
+
], RomanNumeral.prototype, "value", void 0);
|
|
50
|
+
RomanNumeral = __decorate([
|
|
51
|
+
customElement("roman-numeral")
|
|
52
|
+
], RomanNumeral);
|
|
53
|
+
export { RomanNumeral };
|
|
54
|
+
//# sourceMappingURL=roman-numeral.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"roman-numeral.js","sourceRoot":"","sources":["../src/roman-numeral.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE5D,MAAM,cAAc,GAA2B;IAC7C,CAAC,EAAE,IAAI;IACP,EAAE,EAAE,GAAG;IACP,CAAC,EAAE,GAAG;IACN,EAAE,EAAE,GAAG;IACP,CAAC,EAAE,GAAG;IACN,EAAE,EAAE,EAAE;IACN,CAAC,EAAE,EAAE;IACL,EAAE,EAAE,EAAE;IACN,CAAC,EAAE,EAAE;IACL,EAAE,EAAE,CAAC;IACL,CAAC,EAAE,CAAC;IACJ,EAAE,EAAE,CAAC;IACL,CAAC,EAAE,CAAC;CACL,CAAC;AAEF;;;;GAIG;AAEI,IAAM,YAAY,GAAlB,MAAM,YAAa,SAAQ,UAAU;IAKvB,MAAM;QACvB,OAAO,IAAI,CAAA,GAAG,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;IACnD,CAAC;IAEO,eAAe,CAAC,GAAY;QAClC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO,KAAK,CAAC;QACf,CAAC;QAED,IAAI,SAAS,GAAG,GAAG,CAAC;QACpB,IAAI,GAAG,GAAG,EAAE,CAAC;QAEb,KAAK,MAAM,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC;YACjD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;YAC7D,SAAS,IAAI,KAAK,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;YAC5C,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC9B,CAAC;QAED,OAAO,GAAG,CAAC;IACb,CAAC;CACF,CAAA;AAtBC;IADC,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;2CACZ;AAHJ,YAAY;IADxB,aAAa,CAAC,eAAe,CAAC;GAClB,YAAY,CAyBxB","sourcesContent":["import { LitElement, html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\n\nconst ROMAN_NUMERALS: Record<string, number> = {\n M: 1000,\n CM: 900,\n D: 500,\n CD: 400,\n C: 100,\n XC: 90,\n L: 50,\n XL: 40,\n X: 10,\n IX: 9,\n V: 5,\n IV: 4,\n I: 1,\n};\n\n/**\n * Converts an integer to a roman numeral inline.\n *\n * @element roman-numeral\n */\n@customElement(\"roman-numeral\")\nexport class RomanNumeral extends LitElement {\n /** Integer value to render as a roman numeral. */\n @property({ type: Number })\n value?: number;\n\n protected override render() {\n return html`${this._convertToRoman(this.value)}`;\n }\n\n private _convertToRoman(num?: number): string {\n if (!num) {\n return \"NaN\";\n }\n\n let remaining = num;\n let str = \"\";\n\n for (const symbol of Object.keys(ROMAN_NUMERALS)) {\n const count = Math.floor(remaining / ROMAN_NUMERALS[symbol]);\n remaining -= count * ROMAN_NUMERALS[symbol];\n str += symbol.repeat(count);\n }\n\n return str;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"roman-numeral\": RomanNumeral;\n }\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { LitElement } from "lit";
|
|
2
|
+
/**
|
|
3
|
+
* Generic sliding panel shell. Handles positioning, open/close animation,
|
|
4
|
+
* header chrome, and a close button. Body content is provided via the
|
|
5
|
+
* default slot; the consumer controls its own padding and overflow.
|
|
6
|
+
*
|
|
7
|
+
* Desktop: 300 px fixed right-side panel that slides from the right.
|
|
8
|
+
* Mobile (≤768px): bottom-sheet drawer (60vh) — reserved for future use.
|
|
9
|
+
*
|
|
10
|
+
* @element slide-panel
|
|
11
|
+
* @fires panel-close - User clicked the close (✕) button.
|
|
12
|
+
*/
|
|
13
|
+
export declare class SlidePanel extends LitElement {
|
|
14
|
+
/** Whether the panel is currently visible. */
|
|
15
|
+
open: boolean;
|
|
16
|
+
/** Title text shown in the panel header (overridable via slot="title"). */
|
|
17
|
+
heading: string;
|
|
18
|
+
static styles: import("lit").CSSResult[];
|
|
19
|
+
private _close;
|
|
20
|
+
render(): import("lit-html").TemplateResult<1>;
|
|
21
|
+
}
|
|
22
|
+
declare global {
|
|
23
|
+
interface HTMLElementTagNameMap {
|
|
24
|
+
"slide-panel": SlidePanel;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=slide-panel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slide-panel.d.ts","sourceRoot":"","sources":["../src/slide-panel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAK5C;;;;;;;;;;GAUG;AACH,qBACa,UAAW,SAAQ,UAAU;IACxC,8CAA8C;IACjB,IAAI,UAAS;IAC1C,2EAA2E;IAC/D,OAAO,SAAM;IAEzB,OAAgB,MAAM,4BA0FpB;IAEF,OAAO,CAAC,MAAM;IAIL,MAAM,yCAad;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,aAAa,EAAE,UAAU,CAAC;KAC3B;CACF"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
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;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { LitElement, css, html } from "lit";
|
|
8
|
+
import { customElement, property } from "lit/decorators.js";
|
|
9
|
+
import { iconX } from "./icons.js";
|
|
10
|
+
import { tokens } from "./tokens.js";
|
|
11
|
+
/**
|
|
12
|
+
* Generic sliding panel shell. Handles positioning, open/close animation,
|
|
13
|
+
* header chrome, and a close button. Body content is provided via the
|
|
14
|
+
* default slot; the consumer controls its own padding and overflow.
|
|
15
|
+
*
|
|
16
|
+
* Desktop: 300 px fixed right-side panel that slides from the right.
|
|
17
|
+
* Mobile (≤768px): bottom-sheet drawer (60vh) — reserved for future use.
|
|
18
|
+
*
|
|
19
|
+
* @element slide-panel
|
|
20
|
+
* @fires panel-close - User clicked the close (✕) button.
|
|
21
|
+
*/
|
|
22
|
+
let SlidePanel = class SlidePanel extends LitElement {
|
|
23
|
+
constructor() {
|
|
24
|
+
super(...arguments);
|
|
25
|
+
/** Whether the panel is currently visible. */
|
|
26
|
+
this.open = false;
|
|
27
|
+
/** Title text shown in the panel header (overridable via slot="title"). */
|
|
28
|
+
this.heading = "";
|
|
29
|
+
}
|
|
30
|
+
static { this.styles = [
|
|
31
|
+
tokens,
|
|
32
|
+
css `
|
|
33
|
+
:host {
|
|
34
|
+
display: block;
|
|
35
|
+
}
|
|
36
|
+
.panel {
|
|
37
|
+
position: fixed;
|
|
38
|
+
top: 0;
|
|
39
|
+
right: 0;
|
|
40
|
+
bottom: 0;
|
|
41
|
+
width: 300px;
|
|
42
|
+
z-index: 20;
|
|
43
|
+
background: var(--ui-surface, #fff);
|
|
44
|
+
box-shadow: var(--ui-shadow-lg, 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1));
|
|
45
|
+
transform: translateX(110%);
|
|
46
|
+
transition: transform 0.25s ease;
|
|
47
|
+
display: flex;
|
|
48
|
+
flex-direction: column;
|
|
49
|
+
overflow: hidden;
|
|
50
|
+
}
|
|
51
|
+
.panel.open {
|
|
52
|
+
transform: translateX(0);
|
|
53
|
+
}
|
|
54
|
+
.panel-header {
|
|
55
|
+
display: flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
justify-content: space-between;
|
|
58
|
+
padding: 0.5rem 0.625rem;
|
|
59
|
+
border-bottom: 1px solid var(--ui-border, #e2e8f0);
|
|
60
|
+
flex: 0 0 auto;
|
|
61
|
+
}
|
|
62
|
+
.panel-title {
|
|
63
|
+
font-weight: 600;
|
|
64
|
+
font-size: var(--ui-font-size, 0.875rem);
|
|
65
|
+
font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);
|
|
66
|
+
color: var(--ui-text, #0f172a);
|
|
67
|
+
flex: 1 1 auto;
|
|
68
|
+
overflow: hidden;
|
|
69
|
+
white-space: nowrap;
|
|
70
|
+
text-overflow: ellipsis;
|
|
71
|
+
padding-right: 0.5rem;
|
|
72
|
+
}
|
|
73
|
+
.close-btn {
|
|
74
|
+
background: none;
|
|
75
|
+
border: none;
|
|
76
|
+
cursor: pointer;
|
|
77
|
+
padding: 0.25rem 0.5rem;
|
|
78
|
+
color: var(--ui-text-muted, #64748b);
|
|
79
|
+
border-radius: var(--ui-radius-sm, 0.25rem);
|
|
80
|
+
flex: 0 0 auto;
|
|
81
|
+
display: inline-flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
justify-content: center;
|
|
84
|
+
}
|
|
85
|
+
.close-btn:hover {
|
|
86
|
+
background: var(--ui-surface-muted, #f8fafc);
|
|
87
|
+
}
|
|
88
|
+
.handle {
|
|
89
|
+
display: none;
|
|
90
|
+
}
|
|
91
|
+
@media (max-width: 768px) {
|
|
92
|
+
.panel {
|
|
93
|
+
top: auto;
|
|
94
|
+
right: 0;
|
|
95
|
+
bottom: 0;
|
|
96
|
+
left: 0;
|
|
97
|
+
width: auto;
|
|
98
|
+
height: 60vh;
|
|
99
|
+
border-radius: 14px 14px 0 0;
|
|
100
|
+
box-shadow: 0 -3px 20px rgb(0 0 0 / 0.18);
|
|
101
|
+
transform: translateY(110%);
|
|
102
|
+
}
|
|
103
|
+
.panel.open {
|
|
104
|
+
transform: translateY(0);
|
|
105
|
+
}
|
|
106
|
+
.handle {
|
|
107
|
+
display: block;
|
|
108
|
+
width: 36px;
|
|
109
|
+
height: 4px;
|
|
110
|
+
background: var(--ui-border, #e2e8f0);
|
|
111
|
+
border-radius: 2px;
|
|
112
|
+
margin: 10px auto 0;
|
|
113
|
+
flex: 0 0 auto;
|
|
114
|
+
}
|
|
115
|
+
.panel-header {
|
|
116
|
+
padding: 4px 10px 8px;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
`,
|
|
120
|
+
]; }
|
|
121
|
+
_close() {
|
|
122
|
+
this.dispatchEvent(new CustomEvent("panel-close", { bubbles: true, composed: true }));
|
|
123
|
+
}
|
|
124
|
+
render() {
|
|
125
|
+
return html `
|
|
126
|
+
<div class="panel ${this.open ? "open" : ""}">
|
|
127
|
+
<div class="handle"></div>
|
|
128
|
+
<div class="panel-header">
|
|
129
|
+
<slot name="title">
|
|
130
|
+
${this.heading ? html `<span class="panel-title">${this.heading}</span>` : null}
|
|
131
|
+
</slot>
|
|
132
|
+
<button class="close-btn" aria-label="Close panel" @click=${this._close}>${iconX(18)}</button>
|
|
133
|
+
</div>
|
|
134
|
+
<slot></slot>
|
|
135
|
+
</div>
|
|
136
|
+
`;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
__decorate([
|
|
140
|
+
property({ type: Boolean })
|
|
141
|
+
], SlidePanel.prototype, "open", void 0);
|
|
142
|
+
__decorate([
|
|
143
|
+
property()
|
|
144
|
+
], SlidePanel.prototype, "heading", void 0);
|
|
145
|
+
SlidePanel = __decorate([
|
|
146
|
+
customElement("slide-panel")
|
|
147
|
+
], SlidePanel);
|
|
148
|
+
export { SlidePanel };
|
|
149
|
+
//# sourceMappingURL=slide-panel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slide-panel.js","sourceRoot":"","sources":["../src/slide-panel.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC;;;;;;;;;;GAUG;AAEI,IAAM,UAAU,GAAhB,MAAM,UAAW,SAAQ,UAAU;IAAnC;;QACL,8CAA8C;QACjB,SAAI,GAAG,KAAK,CAAC;QAC1C,2EAA2E;QAC/D,YAAO,GAAG,EAAE,CAAC;IAgH3B,CAAC;aA9GiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAuFF;KACF,AA1FqB,CA0FpB;IAEM,MAAM;QACZ,IAAI,CAAC,aAAa,CAAC,IAAI,WAAW,CAAC,aAAa,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACxF,CAAC;IAEQ,MAAM;QACb,OAAO,IAAI,CAAA;0BACW,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;;;;cAInC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA,6BAA6B,IAAI,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC,IAAI;;sEAEpB,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC,EAAE,CAAC;;;;KAIzF,CAAC;IACJ,CAAC;CACF,CAAA;AAlH8B;IAA5B,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;wCAAc;AAE9B;IAAX,QAAQ,EAAE;2CAAc;AAJd,UAAU;IADtB,aAAa,CAAC,aAAa,CAAC;GAChB,UAAU,CAoHtB","sourcesContent":["import { LitElement, css, html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { iconX } from \"./icons.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * Generic sliding panel shell. Handles positioning, open/close animation,\n * header chrome, and a close button. Body content is provided via the\n * default slot; the consumer controls its own padding and overflow.\n *\n * Desktop: 300 px fixed right-side panel that slides from the right.\n * Mobile (≤768px): bottom-sheet drawer (60vh) — reserved for future use.\n *\n * @element slide-panel\n * @fires panel-close - User clicked the close (✕) button.\n */\n@customElement(\"slide-panel\")\nexport class SlidePanel extends LitElement {\n /** Whether the panel is currently visible. */\n @property({ type: Boolean }) open = false;\n /** Title text shown in the panel header (overridable via slot=\"title\"). */\n @property() heading = \"\";\n\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n }\n .panel {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n width: 300px;\n z-index: 20;\n background: var(--ui-surface, #fff);\n box-shadow: var(--ui-shadow-lg, 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1));\n transform: translateX(110%);\n transition: transform 0.25s ease;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n .panel.open {\n transform: translateX(0);\n }\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0.5rem 0.625rem;\n border-bottom: 1px solid var(--ui-border, #e2e8f0);\n flex: 0 0 auto;\n }\n .panel-title {\n font-weight: 600;\n font-size: var(--ui-font-size, 0.875rem);\n font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);\n color: var(--ui-text, #0f172a);\n flex: 1 1 auto;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n padding-right: 0.5rem;\n }\n .close-btn {\n background: none;\n border: none;\n cursor: pointer;\n padding: 0.25rem 0.5rem;\n color: var(--ui-text-muted, #64748b);\n border-radius: var(--ui-radius-sm, 0.25rem);\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n }\n .close-btn:hover {\n background: var(--ui-surface-muted, #f8fafc);\n }\n .handle {\n display: none;\n }\n @media (max-width: 768px) {\n .panel {\n top: auto;\n right: 0;\n bottom: 0;\n left: 0;\n width: auto;\n height: 60vh;\n border-radius: 14px 14px 0 0;\n box-shadow: 0 -3px 20px rgb(0 0 0 / 0.18);\n transform: translateY(110%);\n }\n .panel.open {\n transform: translateY(0);\n }\n .handle {\n display: block;\n width: 36px;\n height: 4px;\n background: var(--ui-border, #e2e8f0);\n border-radius: 2px;\n margin: 10px auto 0;\n flex: 0 0 auto;\n }\n .panel-header {\n padding: 4px 10px 8px;\n }\n }\n `,\n ];\n\n private _close() {\n this.dispatchEvent(new CustomEvent(\"panel-close\", { bubbles: true, composed: true }));\n }\n\n override render() {\n return html`\n <div class=\"panel ${this.open ? \"open\" : \"\"}\">\n <div class=\"handle\"></div>\n <div class=\"panel-header\">\n <slot name=\"title\">\n ${this.heading ? html`<span class=\"panel-title\">${this.heading}</span>` : null}\n </slot>\n <button class=\"close-btn\" aria-label=\"Close panel\" @click=${this._close}>${iconX(18)}</button>\n </div>\n <slot></slot>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"slide-panel\": SlidePanel;\n }\n}\n"]}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { LitElement, nothing } from "lit";
|
|
2
|
+
export type ToastVariant = "error" | "info" | "success";
|
|
3
|
+
interface ToastOptions {
|
|
4
|
+
/** Visual style; also selects the accent color. Defaults to "info". */
|
|
5
|
+
variant?: ToastVariant;
|
|
6
|
+
/** Auto-dismiss delay in ms. Pass 0 to require a manual close. Defaults to 5000. */
|
|
7
|
+
duration?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Fixed-position stack of dismissible notifications, anchored top-right
|
|
11
|
+
* (top-full-width on mobile). Not wired to any app state yet — callers add
|
|
12
|
+
* toasts imperatively via the `show()` method on a live element reference,
|
|
13
|
+
* e.g. `document.querySelector('toast-notification')?.show('Offline', { variant: 'error' })`,
|
|
14
|
+
* or via the `notifySuccess`/`notifyError`/`notifyInfo` module-level helpers
|
|
15
|
+
* exported from this file. Each toast auto-dismisses after `duration` ms and
|
|
16
|
+
* can also be dismissed via its ✕ button. Appears/disappears instantly — no
|
|
17
|
+
* slide/fade transitions.
|
|
18
|
+
*
|
|
19
|
+
* @element toast-notification
|
|
20
|
+
*/
|
|
21
|
+
export declare class ToastNotification extends LitElement {
|
|
22
|
+
private _toasts;
|
|
23
|
+
private _nextId;
|
|
24
|
+
private _timers;
|
|
25
|
+
static styles: import("lit").CSSResult[];
|
|
26
|
+
/**
|
|
27
|
+
* Queues a toast for display.
|
|
28
|
+
* @param message Text to show.
|
|
29
|
+
* @param options Variant + auto-dismiss duration (see {@link ToastOptions}).
|
|
30
|
+
* @returns The toast's id — pass to `dismiss()` to remove it early.
|
|
31
|
+
*/
|
|
32
|
+
show(message: string, options?: ToastOptions): number;
|
|
33
|
+
/**
|
|
34
|
+
* Removes a toast immediately, cancelling its pending auto-dismiss timer if any.
|
|
35
|
+
* @param id The id returned by `show()`.
|
|
36
|
+
*/
|
|
37
|
+
dismiss(id: number): void;
|
|
38
|
+
disconnectedCallback(): void;
|
|
39
|
+
render(): typeof nothing | import("lit-html").TemplateResult<1>;
|
|
40
|
+
}
|
|
41
|
+
declare global {
|
|
42
|
+
interface HTMLElementTagNameMap {
|
|
43
|
+
"toast-notification": ToastNotification;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Shows an error toast. Use for connection/network failures, not validation errors. */
|
|
47
|
+
export declare function notifyError(message: string): void;
|
|
48
|
+
/** Shows a success toast. */
|
|
49
|
+
export declare function notifySuccess(message: string): void;
|
|
50
|
+
/** Shows an info toast. */
|
|
51
|
+
export declare function notifyInfo(message: string): void;
|
|
52
|
+
export {};
|
|
53
|
+
//# sourceMappingURL=toast-notification.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toast-notification.d.ts","sourceRoot":"","sources":["../src/toast-notification.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,OAAO,EAAE,MAAM,KAAK,CAAC;AAKrD,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;AAExD,UAAU,YAAY;IACpB,uEAAuE;IACvE,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAUD;;;;;;;;;;;GAWG;AACH,qBACa,iBAAkB,SAAQ,UAAU;IACtC,OAAO,CAAC,OAAO,CAAe;IAEvC,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,OAAO,CAAoD;IAEnE,OAAgB,MAAM,4BAsEpB;IAEF;;;;;OAKG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,MAAM,CAYxD;IAED;;;OAGG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,QAOjB;IAEQ,oBAAoB,SAI5B;IAEQ,MAAM,0DAcd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,oBAAoB,EAAE,iBAAiB,CAAC;KACzC;CACF;AAMD,wFAAwF;AACxF,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,QAE1C;AAED,6BAA6B;AAC7B,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,QAE5C;AAED,2BAA2B;AAC3B,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,QAEzC"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
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;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { LitElement, css, html, nothing } from "lit";
|
|
8
|
+
import { customElement, state } from "lit/decorators.js";
|
|
9
|
+
import { iconX } from "./icons.js";
|
|
10
|
+
import { tokens } from "./tokens.js";
|
|
11
|
+
const DEFAULT_DURATION_MS = 5000;
|
|
12
|
+
/**
|
|
13
|
+
* Fixed-position stack of dismissible notifications, anchored top-right
|
|
14
|
+
* (top-full-width on mobile). Not wired to any app state yet — callers add
|
|
15
|
+
* toasts imperatively via the `show()` method on a live element reference,
|
|
16
|
+
* e.g. `document.querySelector('toast-notification')?.show('Offline', { variant: 'error' })`,
|
|
17
|
+
* or via the `notifySuccess`/`notifyError`/`notifyInfo` module-level helpers
|
|
18
|
+
* exported from this file. Each toast auto-dismisses after `duration` ms and
|
|
19
|
+
* can also be dismissed via its ✕ button. Appears/disappears instantly — no
|
|
20
|
+
* slide/fade transitions.
|
|
21
|
+
*
|
|
22
|
+
* @element toast-notification
|
|
23
|
+
*/
|
|
24
|
+
let ToastNotification = class ToastNotification extends LitElement {
|
|
25
|
+
constructor() {
|
|
26
|
+
super(...arguments);
|
|
27
|
+
this._toasts = [];
|
|
28
|
+
this._nextId = 0;
|
|
29
|
+
this._timers = new Map();
|
|
30
|
+
}
|
|
31
|
+
static { this.styles = [
|
|
32
|
+
tokens,
|
|
33
|
+
css `
|
|
34
|
+
:host {
|
|
35
|
+
position: fixed;
|
|
36
|
+
top: 1rem;
|
|
37
|
+
right: 1rem;
|
|
38
|
+
z-index: 200;
|
|
39
|
+
display: flex;
|
|
40
|
+
flex-direction: column;
|
|
41
|
+
gap: 0.5rem;
|
|
42
|
+
max-width: 360px;
|
|
43
|
+
pointer-events: none;
|
|
44
|
+
}
|
|
45
|
+
.toast {
|
|
46
|
+
pointer-events: auto;
|
|
47
|
+
display: flex;
|
|
48
|
+
align-items: flex-start;
|
|
49
|
+
gap: 0.5rem;
|
|
50
|
+
border-radius: var(--ui-radius, 0.5rem);
|
|
51
|
+
padding: 0.625rem 0.75rem;
|
|
52
|
+
box-shadow: var(
|
|
53
|
+
--ui-shadow-lg,
|
|
54
|
+
0 20px 25px -5px rgb(0 0 0 / 0.1),
|
|
55
|
+
0 8px 10px -6px rgb(0 0 0 / 0.1)
|
|
56
|
+
);
|
|
57
|
+
font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);
|
|
58
|
+
font-size: var(--ui-font-size, 0.875rem);
|
|
59
|
+
line-height: 1.4;
|
|
60
|
+
color: #fff;
|
|
61
|
+
background: var(--ui-text, #0f172a);
|
|
62
|
+
}
|
|
63
|
+
.toast.error {
|
|
64
|
+
background: var(--ui-danger, #dc2626);
|
|
65
|
+
}
|
|
66
|
+
.toast.success {
|
|
67
|
+
background: var(--ui-success, #16a34a);
|
|
68
|
+
}
|
|
69
|
+
.toast.info {
|
|
70
|
+
background: var(--ui-primary, #4f46e5);
|
|
71
|
+
}
|
|
72
|
+
.message {
|
|
73
|
+
flex: 1 1 auto;
|
|
74
|
+
word-break: break-word;
|
|
75
|
+
}
|
|
76
|
+
.close {
|
|
77
|
+
flex: 0 0 auto;
|
|
78
|
+
background: none;
|
|
79
|
+
border: none;
|
|
80
|
+
padding: 2px;
|
|
81
|
+
margin: -2px -2px -2px 0;
|
|
82
|
+
color: inherit;
|
|
83
|
+
opacity: 0.8;
|
|
84
|
+
cursor: pointer;
|
|
85
|
+
line-height: 0;
|
|
86
|
+
border-radius: var(--ui-radius-sm, 0.25rem);
|
|
87
|
+
}
|
|
88
|
+
.close:hover {
|
|
89
|
+
opacity: 1;
|
|
90
|
+
background: rgb(255 255 255 / 0.15);
|
|
91
|
+
}
|
|
92
|
+
@media (max-width: 768px) {
|
|
93
|
+
:host {
|
|
94
|
+
top: 0.75rem;
|
|
95
|
+
right: 0.75rem;
|
|
96
|
+
left: 0.75rem;
|
|
97
|
+
max-width: none;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
`,
|
|
101
|
+
]; }
|
|
102
|
+
/**
|
|
103
|
+
* Queues a toast for display.
|
|
104
|
+
* @param message Text to show.
|
|
105
|
+
* @param options Variant + auto-dismiss duration (see {@link ToastOptions}).
|
|
106
|
+
* @returns The toast's id — pass to `dismiss()` to remove it early.
|
|
107
|
+
*/
|
|
108
|
+
show(message, options = {}) {
|
|
109
|
+
const id = this._nextId++;
|
|
110
|
+
const variant = options.variant ?? "info";
|
|
111
|
+
const duration = options.duration ?? DEFAULT_DURATION_MS;
|
|
112
|
+
this._toasts = [...this._toasts, { id, message, variant }];
|
|
113
|
+
if (duration > 0) {
|
|
114
|
+
this._timers.set(id, setTimeout(() => this.dismiss(id), duration));
|
|
115
|
+
}
|
|
116
|
+
return id;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Removes a toast immediately, cancelling its pending auto-dismiss timer if any.
|
|
120
|
+
* @param id The id returned by `show()`.
|
|
121
|
+
*/
|
|
122
|
+
dismiss(id) {
|
|
123
|
+
const timer = this._timers.get(id);
|
|
124
|
+
if (timer) {
|
|
125
|
+
clearTimeout(timer);
|
|
126
|
+
this._timers.delete(id);
|
|
127
|
+
}
|
|
128
|
+
this._toasts = this._toasts.filter((t) => t.id !== id);
|
|
129
|
+
}
|
|
130
|
+
disconnectedCallback() {
|
|
131
|
+
super.disconnectedCallback();
|
|
132
|
+
for (const timer of this._timers.values())
|
|
133
|
+
clearTimeout(timer);
|
|
134
|
+
this._timers.clear();
|
|
135
|
+
}
|
|
136
|
+
render() {
|
|
137
|
+
if (this._toasts.length === 0)
|
|
138
|
+
return nothing;
|
|
139
|
+
return html `
|
|
140
|
+
${this._toasts.map((t) => html `
|
|
141
|
+
<div class="toast ${t.variant}" role="status">
|
|
142
|
+
<span class="message">${t.message}</span>
|
|
143
|
+
<button class="close" aria-label="Dismiss notification" @click=${() => this.dismiss(t.id)}>
|
|
144
|
+
${iconX(14)}
|
|
145
|
+
</button>
|
|
146
|
+
</div>
|
|
147
|
+
`)}
|
|
148
|
+
`;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
__decorate([
|
|
152
|
+
state()
|
|
153
|
+
], ToastNotification.prototype, "_toasts", void 0);
|
|
154
|
+
ToastNotification = __decorate([
|
|
155
|
+
customElement("toast-notification")
|
|
156
|
+
], ToastNotification);
|
|
157
|
+
export { ToastNotification };
|
|
158
|
+
function getToast() {
|
|
159
|
+
return document.querySelector("toast-notification");
|
|
160
|
+
}
|
|
161
|
+
/** Shows an error toast. Use for connection/network failures, not validation errors. */
|
|
162
|
+
export function notifyError(message) {
|
|
163
|
+
getToast()?.show(message, { variant: "error" });
|
|
164
|
+
}
|
|
165
|
+
/** Shows a success toast. */
|
|
166
|
+
export function notifySuccess(message) {
|
|
167
|
+
getToast()?.show(message, { variant: "success" });
|
|
168
|
+
}
|
|
169
|
+
/** Shows an info toast. */
|
|
170
|
+
export function notifyInfo(message) {
|
|
171
|
+
getToast()?.show(message, { variant: "info" });
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=toast-notification.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toast-notification.js","sourceRoot":"","sources":["../src/toast-notification.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html, nothing } from \"lit\";\nimport { customElement, state } from \"lit/decorators.js\";\nimport { iconX } from \"./icons.js\";\nimport { tokens } from \"./tokens.js\";\n\nexport type ToastVariant = \"error\" | \"info\" | \"success\";\n\ninterface ToastOptions {\n /** Visual style; also selects the accent color. Defaults to \"info\". */\n variant?: ToastVariant;\n /** Auto-dismiss delay in ms. Pass 0 to require a manual close. Defaults to 5000. */\n duration?: number;\n}\n\ninterface Toast {\n id: number;\n message: string;\n variant: ToastVariant;\n}\n\nconst DEFAULT_DURATION_MS = 5000;\n\n/**\n * Fixed-position stack of dismissible notifications, anchored top-right\n * (top-full-width on mobile). Not wired to any app state yet — callers add\n * toasts imperatively via the `show()` method on a live element reference,\n * e.g. `document.querySelector('toast-notification')?.show('Offline', { variant: 'error' })`,\n * or via the `notifySuccess`/`notifyError`/`notifyInfo` module-level helpers\n * exported from this file. Each toast auto-dismisses after `duration` ms and\n * can also be dismissed via its ✕ button. Appears/disappears instantly — no\n * slide/fade transitions.\n *\n * @element toast-notification\n */\n@customElement(\"toast-notification\")\nexport class ToastNotification extends LitElement {\n @state() private _toasts: Toast[] = [];\n\n private _nextId = 0;\n private _timers = new Map<number, ReturnType<typeof setTimeout>>();\n\n static override styles = [\n tokens,\n css`\n :host {\n position: fixed;\n top: 1rem;\n right: 1rem;\n z-index: 200;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n max-width: 360px;\n pointer-events: none;\n }\n .toast {\n pointer-events: auto;\n display: flex;\n align-items: flex-start;\n gap: 0.5rem;\n border-radius: var(--ui-radius, 0.5rem);\n padding: 0.625rem 0.75rem;\n box-shadow: var(\n --ui-shadow-lg,\n 0 20px 25px -5px rgb(0 0 0 / 0.1),\n 0 8px 10px -6px rgb(0 0 0 / 0.1)\n );\n font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);\n font-size: var(--ui-font-size, 0.875rem);\n line-height: 1.4;\n color: #fff;\n background: var(--ui-text, #0f172a);\n }\n .toast.error {\n background: var(--ui-danger, #dc2626);\n }\n .toast.success {\n background: var(--ui-success, #16a34a);\n }\n .toast.info {\n background: var(--ui-primary, #4f46e5);\n }\n .message {\n flex: 1 1 auto;\n word-break: break-word;\n }\n .close {\n flex: 0 0 auto;\n background: none;\n border: none;\n padding: 2px;\n margin: -2px -2px -2px 0;\n color: inherit;\n opacity: 0.8;\n cursor: pointer;\n line-height: 0;\n border-radius: var(--ui-radius-sm, 0.25rem);\n }\n .close:hover {\n opacity: 1;\n background: rgb(255 255 255 / 0.15);\n }\n @media (max-width: 768px) {\n :host {\n top: 0.75rem;\n right: 0.75rem;\n left: 0.75rem;\n max-width: none;\n }\n }\n `,\n ];\n\n /**\n * Queues a toast for display.\n * @param message Text to show.\n * @param options Variant + auto-dismiss duration (see {@link ToastOptions}).\n * @returns The toast's id — pass to `dismiss()` to remove it early.\n */\n show(message: string, options: ToastOptions = {}): number {\n const id = this._nextId++;\n const variant = options.variant ?? \"info\";\n const duration = options.duration ?? DEFAULT_DURATION_MS;\n this._toasts = [...this._toasts, { id, message, variant }];\n if (duration > 0) {\n this._timers.set(\n id,\n setTimeout(() => this.dismiss(id), duration),\n );\n }\n return id;\n }\n\n /**\n * Removes a toast immediately, cancelling its pending auto-dismiss timer if any.\n * @param id The id returned by `show()`.\n */\n dismiss(id: number) {\n const timer = this._timers.get(id);\n if (timer) {\n clearTimeout(timer);\n this._timers.delete(id);\n }\n this._toasts = this._toasts.filter((t) => t.id !== id);\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n for (const timer of this._timers.values()) clearTimeout(timer);\n this._timers.clear();\n }\n\n override render() {\n if (this._toasts.length === 0) return nothing;\n return html`\n ${this._toasts.map(\n (t) => html`\n <div class=\"toast ${t.variant}\" role=\"status\">\n <span class=\"message\">${t.message}</span>\n <button class=\"close\" aria-label=\"Dismiss notification\" @click=${() => this.dismiss(t.id)}>\n ${iconX(14)}\n </button>\n </div>\n `,\n )}\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"toast-notification\": ToastNotification;\n }\n}\n\nfunction getToast(): ToastNotification | null {\n return document.querySelector(\"toast-notification\");\n}\n\n/** Shows an error toast. Use for connection/network failures, not validation errors. */\nexport function notifyError(message: string) {\n getToast()?.show(message, { variant: \"error\" });\n}\n\n/** Shows a success toast. */\nexport function notifySuccess(message: string) {\n getToast()?.show(message, { variant: \"success\" });\n}\n\n/** Shows an info toast. */\nexport function notifyInfo(message: string) {\n getToast()?.show(message, { variant: \"info\" });\n}\n"]}
|
package/dist/tokens.css
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--ui-primary: #4f46e5;
|
|
3
|
+
--ui-primary-hover: #4338ca;
|
|
4
|
+
--ui-danger: #dc2626;
|
|
5
|
+
--ui-danger-hover: #b91c1c;
|
|
6
|
+
--ui-success: #16a34a;
|
|
7
|
+
--ui-text: #0f172a;
|
|
8
|
+
--ui-text-muted: #64748b;
|
|
9
|
+
--ui-border: #e2e8f0;
|
|
10
|
+
--ui-surface: #ffffff;
|
|
11
|
+
--ui-surface-muted: #f8fafc;
|
|
12
|
+
--ui-overlay: rgb(15 23 42 / 0.45);
|
|
13
|
+
--ui-radius: 0.5rem;
|
|
14
|
+
--ui-radius-sm: 0.25rem;
|
|
15
|
+
--ui-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
|
16
|
+
--ui-shadow-lg: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
|
|
17
|
+
--ui-focus-ring: 0 0 0 3px rgb(79 70 229 / 0.35);
|
|
18
|
+
--ui-font: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
19
|
+
--ui-font-size: 0.875rem;
|
|
20
|
+
--ui-font-size-sm: 0.75rem;
|
|
21
|
+
}
|
package/dist/tokens.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Design token values, sourced from Tailwind v4's default theme palette.
|
|
3
|
+
* Each entry maps a `--ui-*` custom property name to its default value.
|
|
4
|
+
* These are the same values baked in as `var(--ui-*, <fallback>)` fallbacks
|
|
5
|
+
* throughout component styles, so components render correctly with zero
|
|
6
|
+
* external CSS. Consumers may override any of these by setting the custom
|
|
7
|
+
* property on `:root` or an ancestor element (see `tokens.css`).
|
|
8
|
+
*/
|
|
9
|
+
export declare const tokenValues: Record<string, string>;
|
|
10
|
+
/**
|
|
11
|
+
* A shared `:host` stylesheet that establishes the token custom properties
|
|
12
|
+
* with their fallback values. Import and include this in every component's
|
|
13
|
+
* `static styles` array alongside its component-specific `css` block.
|
|
14
|
+
*/
|
|
15
|
+
export declare const tokens: import("lit").CSSResult;
|
|
16
|
+
//# sourceMappingURL=tokens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CA4B9C,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,MAAM,yBAuClB,CAAC"}
|
package/dist/tokens.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { css } from "lit";
|
|
2
|
+
/**
|
|
3
|
+
* Design token values, sourced from Tailwind v4's default theme palette.
|
|
4
|
+
* Each entry maps a `--ui-*` custom property name to its default value.
|
|
5
|
+
* These are the same values baked in as `var(--ui-*, <fallback>)` fallbacks
|
|
6
|
+
* throughout component styles, so components render correctly with zero
|
|
7
|
+
* external CSS. Consumers may override any of these by setting the custom
|
|
8
|
+
* property on `:root` or an ancestor element (see `tokens.css`).
|
|
9
|
+
*/
|
|
10
|
+
export const tokenValues = {
|
|
11
|
+
// Colors
|
|
12
|
+
"--ui-primary": "#4f46e5", // indigo-600
|
|
13
|
+
"--ui-primary-hover": "#4338ca", // indigo-700
|
|
14
|
+
"--ui-danger": "#dc2626", // red-600
|
|
15
|
+
"--ui-danger-hover": "#b91c1c", // red-700
|
|
16
|
+
"--ui-success": "#16a34a", // green-600
|
|
17
|
+
"--ui-text": "#0f172a", // slate-900
|
|
18
|
+
"--ui-text-muted": "#64748b", // slate-500
|
|
19
|
+
"--ui-border": "#e2e8f0", // slate-200
|
|
20
|
+
"--ui-surface": "#ffffff", // white
|
|
21
|
+
"--ui-surface-muted": "#f8fafc", // slate-50
|
|
22
|
+
"--ui-overlay": "rgb(15 23 42 / 0.45)", // slate-900 / 45%
|
|
23
|
+
// Shape / depth
|
|
24
|
+
"--ui-radius": "0.5rem", // rounded-lg
|
|
25
|
+
"--ui-radius-sm": "0.25rem", // rounded
|
|
26
|
+
"--ui-shadow": "0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)", // shadow-md
|
|
27
|
+
"--ui-shadow-lg": "0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)", // shadow-xl
|
|
28
|
+
"--ui-focus-ring": "0 0 0 3px rgb(79 70 229 / 0.35)",
|
|
29
|
+
// Type
|
|
30
|
+
"--ui-font": 'ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"',
|
|
31
|
+
"--ui-font-size": "0.875rem",
|
|
32
|
+
"--ui-font-size-sm": "0.75rem",
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* A shared `:host` stylesheet that establishes the token custom properties
|
|
36
|
+
* with their fallback values. Import and include this in every component's
|
|
37
|
+
* `static styles` array alongside its component-specific `css` block.
|
|
38
|
+
*/
|
|
39
|
+
export const tokens = css `
|
|
40
|
+
:host {
|
|
41
|
+
--ui-primary: var(--ui-primary, #4f46e5);
|
|
42
|
+
--ui-primary-hover: var(--ui-primary-hover, #4338ca);
|
|
43
|
+
--ui-danger: var(--ui-danger, #dc2626);
|
|
44
|
+
--ui-danger-hover: var(--ui-danger-hover, #b91c1c);
|
|
45
|
+
--ui-success: var(--ui-success, #16a34a);
|
|
46
|
+
--ui-text: var(--ui-text, #0f172a);
|
|
47
|
+
--ui-text-muted: var(--ui-text-muted, #64748b);
|
|
48
|
+
--ui-border: var(--ui-border, #e2e8f0);
|
|
49
|
+
--ui-surface: var(--ui-surface, #ffffff);
|
|
50
|
+
--ui-surface-muted: var(--ui-surface-muted, #f8fafc);
|
|
51
|
+
--ui-overlay: var(--ui-overlay, rgb(15 23 42 / 0.45));
|
|
52
|
+
--ui-radius: var(--ui-radius, 0.5rem);
|
|
53
|
+
--ui-radius-sm: var(--ui-radius-sm, 0.25rem);
|
|
54
|
+
--ui-shadow: var(
|
|
55
|
+
--ui-shadow,
|
|
56
|
+
0 4px 6px -1px rgb(0 0 0 / 0.1),
|
|
57
|
+
0 2px 4px -2px rgb(0 0 0 / 0.1)
|
|
58
|
+
);
|
|
59
|
+
--ui-shadow-lg: var(
|
|
60
|
+
--ui-shadow-lg,
|
|
61
|
+
0 20px 25px -5px rgb(0 0 0 / 0.1),
|
|
62
|
+
0 8px 10px -6px rgb(0 0 0 / 0.1)
|
|
63
|
+
);
|
|
64
|
+
--ui-focus-ring: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
|
|
65
|
+
--ui-font: var(
|
|
66
|
+
--ui-font,
|
|
67
|
+
ui-sans-serif,
|
|
68
|
+
system-ui,
|
|
69
|
+
sans-serif,
|
|
70
|
+
"Apple Color Emoji",
|
|
71
|
+
"Segoe UI Emoji",
|
|
72
|
+
"Segoe UI Symbol",
|
|
73
|
+
"Noto Color Emoji"
|
|
74
|
+
);
|
|
75
|
+
--ui-font-size: var(--ui-font-size, 0.875rem);
|
|
76
|
+
--ui-font-size-sm: var(--ui-font-size-sm, 0.75rem);
|
|
77
|
+
}
|
|
78
|
+
`;
|
|
79
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,WAAW,GAA2B;IACjD,SAAS;IACT,cAAc,EAAE,SAAS,EAAE,aAAa;IACxC,oBAAoB,EAAE,SAAS,EAAE,aAAa;IAC9C,aAAa,EAAE,SAAS,EAAE,UAAU;IACpC,mBAAmB,EAAE,SAAS,EAAE,UAAU;IAC1C,cAAc,EAAE,SAAS,EAAE,YAAY;IACvC,WAAW,EAAE,SAAS,EAAE,YAAY;IACpC,iBAAiB,EAAE,SAAS,EAAE,YAAY;IAC1C,aAAa,EAAE,SAAS,EAAE,YAAY;IACtC,cAAc,EAAE,SAAS,EAAE,QAAQ;IACnC,oBAAoB,EAAE,SAAS,EAAE,WAAW;IAC5C,cAAc,EAAE,sBAAsB,EAAE,kBAAkB;IAE1D,gBAAgB;IAChB,aAAa,EAAE,QAAQ,EAAE,aAAa;IACtC,gBAAgB,EAAE,SAAS,EAAE,UAAU;IACvC,aAAa,EACX,kEAAkE,EAAE,YAAY;IAClF,gBAAgB,EACd,qEAAqE,EAAE,YAAY;IACrF,iBAAiB,EAAE,iCAAiC;IAEpD,OAAO;IACP,WAAW,EACT,oHAAoH;IACtH,gBAAgB,EAAE,UAAU;IAC5B,mBAAmB,EAAE,SAAS;CAC/B,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuCxB,CAAC","sourcesContent":["import { css } from \"lit\";\n\n/**\n * Design token values, sourced from Tailwind v4's default theme palette.\n * Each entry maps a `--ui-*` custom property name to its default value.\n * These are the same values baked in as `var(--ui-*, <fallback>)` fallbacks\n * throughout component styles, so components render correctly with zero\n * external CSS. Consumers may override any of these by setting the custom\n * property on `:root` or an ancestor element (see `tokens.css`).\n */\nexport const tokenValues: Record<string, string> = {\n // Colors\n \"--ui-primary\": \"#4f46e5\", // indigo-600\n \"--ui-primary-hover\": \"#4338ca\", // indigo-700\n \"--ui-danger\": \"#dc2626\", // red-600\n \"--ui-danger-hover\": \"#b91c1c\", // red-700\n \"--ui-success\": \"#16a34a\", // green-600\n \"--ui-text\": \"#0f172a\", // slate-900\n \"--ui-text-muted\": \"#64748b\", // slate-500\n \"--ui-border\": \"#e2e8f0\", // slate-200\n \"--ui-surface\": \"#ffffff\", // white\n \"--ui-surface-muted\": \"#f8fafc\", // slate-50\n \"--ui-overlay\": \"rgb(15 23 42 / 0.45)\", // slate-900 / 45%\n\n // Shape / depth\n \"--ui-radius\": \"0.5rem\", // rounded-lg\n \"--ui-radius-sm\": \"0.25rem\", // rounded\n \"--ui-shadow\":\n \"0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)\", // shadow-md\n \"--ui-shadow-lg\":\n \"0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)\", // shadow-xl\n \"--ui-focus-ring\": \"0 0 0 3px rgb(79 70 229 / 0.35)\",\n\n // Type\n \"--ui-font\":\n 'ui-sans-serif, system-ui, sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\"',\n \"--ui-font-size\": \"0.875rem\",\n \"--ui-font-size-sm\": \"0.75rem\",\n};\n\n/**\n * A shared `:host` stylesheet that establishes the token custom properties\n * with their fallback values. Import and include this in every component's\n * `static styles` array alongside its component-specific `css` block.\n */\nexport const tokens = css`\n :host {\n --ui-primary: var(--ui-primary, #4f46e5);\n --ui-primary-hover: var(--ui-primary-hover, #4338ca);\n --ui-danger: var(--ui-danger, #dc2626);\n --ui-danger-hover: var(--ui-danger-hover, #b91c1c);\n --ui-success: var(--ui-success, #16a34a);\n --ui-text: var(--ui-text, #0f172a);\n --ui-text-muted: var(--ui-text-muted, #64748b);\n --ui-border: var(--ui-border, #e2e8f0);\n --ui-surface: var(--ui-surface, #ffffff);\n --ui-surface-muted: var(--ui-surface-muted, #f8fafc);\n --ui-overlay: var(--ui-overlay, rgb(15 23 42 / 0.45));\n --ui-radius: var(--ui-radius, 0.5rem);\n --ui-radius-sm: var(--ui-radius-sm, 0.25rem);\n --ui-shadow: var(\n --ui-shadow,\n 0 4px 6px -1px rgb(0 0 0 / 0.1),\n 0 2px 4px -2px rgb(0 0 0 / 0.1)\n );\n --ui-shadow-lg: var(\n --ui-shadow-lg,\n 0 20px 25px -5px rgb(0 0 0 / 0.1),\n 0 8px 10px -6px rgb(0 0 0 / 0.1)\n );\n --ui-focus-ring: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n --ui-font: var(\n --ui-font,\n ui-sans-serif,\n system-ui,\n sans-serif,\n \"Apple Color Emoji\",\n \"Segoe UI Emoji\",\n \"Segoe UI Symbol\",\n \"Noto Color Emoji\"\n );\n --ui-font-size: var(--ui-font-size, 0.875rem);\n --ui-font-size-sm: var(--ui-font-size-sm, 0.75rem);\n }\n`;\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats a distance in miles, switching to feet below 0.25 mi.
|
|
3
|
+
* At ≥10 mi precision drops to whole miles.
|
|
4
|
+
*/
|
|
5
|
+
export declare function formatMiles(miles: number): string;
|
|
6
|
+
/**
|
|
7
|
+
* Formats a distance in kilometers, switching to meters below 0.5 km.
|
|
8
|
+
* At ≥10 km precision drops to whole kilometers. (Future use.)
|
|
9
|
+
*/
|
|
10
|
+
export declare function formatKm(km: number): string;
|
|
11
|
+
//# sourceMappingURL=distance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"distance.d.ts","sourceRoot":"","sources":["../../src/utils/distance.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMjD;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAM3C"}
|