@f-ewald/components 0.7.0 → 1.0.2
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 +76 -47
- package/custom-elements.json +3049 -910
- package/dist/address-autocomplete.d.ts +8 -0
- package/dist/address-autocomplete.d.ts.map +1 -1
- package/dist/address-autocomplete.js +165 -15
- package/dist/address-autocomplete.js.map +1 -1
- package/dist/animate-confetti.d.ts +8 -5
- package/dist/animate-confetti.d.ts.map +1 -1
- package/dist/animate-confetti.js +46 -19
- package/dist/animate-confetti.js.map +1 -1
- package/dist/autocomplete-input.d.ts +8 -0
- package/dist/autocomplete-input.d.ts.map +1 -1
- package/dist/autocomplete-input.js +165 -15
- package/dist/autocomplete-input.js.map +1 -1
- package/dist/calendar-entry.d.ts +33 -0
- package/dist/calendar-entry.d.ts.map +1 -0
- package/dist/calendar-entry.js +64 -0
- package/dist/calendar-entry.js.map +1 -0
- package/dist/calendar-month.d.ts +67 -0
- package/dist/calendar-month.d.ts.map +1 -0
- package/dist/calendar-month.js +469 -0
- package/dist/calendar-month.js.map +1 -0
- package/dist/calendar-year.d.ts +43 -0
- package/dist/calendar-year.d.ts.map +1 -0
- package/dist/calendar-year.js +145 -0
- package/dist/calendar-year.js.map +1 -0
- package/dist/chat-message.d.ts.map +1 -1
- package/dist/chat-message.js +77 -24
- package/dist/chat-message.js.map +1 -1
- package/dist/confirm-dialog.d.ts +8 -1
- package/dist/confirm-dialog.d.ts.map +1 -1
- package/dist/confirm-dialog.js +130 -18
- package/dist/confirm-dialog.js.map +1 -1
- package/dist/copy-link-button.d.ts +2 -0
- package/dist/copy-link-button.d.ts.map +1 -1
- package/dist/copy-link-button.js +35 -3
- package/dist/copy-link-button.js.map +1 -1
- package/dist/data-table.d.ts +2 -0
- package/dist/data-table.d.ts.map +1 -1
- package/dist/data-table.js +83 -11
- package/dist/data-table.js.map +1 -1
- package/dist/distance-value.d.ts +0 -1
- package/dist/distance-value.d.ts.map +1 -1
- package/dist/distance-value.js +3 -8
- package/dist/distance-value.js.map +1 -1
- package/dist/distribution-chart.d.ts.map +1 -1
- package/dist/distribution-chart.js +54 -17
- package/dist/distribution-chart.js.map +1 -1
- package/dist/dropdown-button.d.ts.map +1 -1
- package/dist/dropdown-button.js +60 -11
- package/dist/dropdown-button.js.map +1 -1
- package/dist/editable-text.d.ts.map +1 -1
- package/dist/editable-text.js +46 -13
- package/dist/editable-text.js.map +1 -1
- package/dist/form-select.d.ts +21 -2
- package/dist/form-select.d.ts.map +1 -1
- package/dist/form-select.js +489 -37
- package/dist/form-select.js.map +1 -1
- package/dist/frame-box.d.ts.map +1 -1
- package/dist/frame-box.js +20 -12
- package/dist/frame-box.js.map +1 -1
- package/dist/icon-button.d.ts.map +1 -1
- package/dist/icon-button.js +11 -0
- package/dist/icon-button.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/kbd-hint.d.ts +30 -0
- package/dist/kbd-hint.d.ts.map +1 -0
- package/dist/kbd-hint.js +184 -0
- package/dist/kbd-hint.js.map +1 -0
- package/dist/live-timer.d.ts +0 -1
- package/dist/live-timer.d.ts.map +1 -1
- package/dist/live-timer.js +1 -6
- package/dist/live-timer.js.map +1 -1
- package/dist/map-circle.d.ts +3 -0
- package/dist/map-circle.d.ts.map +1 -1
- package/dist/map-circle.js +19 -1
- package/dist/map-circle.js.map +1 -1
- package/dist/map-pin.d.ts.map +1 -1
- package/dist/map-pin.js +16 -1
- package/dist/map-pin.js.map +1 -1
- package/dist/percent-bar-chart.d.ts +3 -0
- package/dist/percent-bar-chart.d.ts.map +1 -1
- package/dist/percent-bar-chart.js +60 -4
- package/dist/percent-bar-chart.js.map +1 -1
- package/dist/photo-gallery.d.ts.map +1 -1
- package/dist/photo-gallery.js +12 -3
- package/dist/photo-gallery.js.map +1 -1
- package/dist/popover-panel.d.ts.map +1 -1
- package/dist/popover-panel.js +132 -23
- package/dist/popover-panel.js.map +1 -1
- package/dist/price-history-chart.d.ts.map +1 -1
- package/dist/price-history-chart.js +52 -18
- package/dist/price-history-chart.js.map +1 -1
- package/dist/radio-cards.d.ts +2 -0
- package/dist/radio-cards.d.ts.map +1 -1
- package/dist/radio-cards.js +51 -5
- package/dist/radio-cards.js.map +1 -1
- package/dist/radio-pills.d.ts +2 -0
- package/dist/radio-pills.d.ts.map +1 -1
- package/dist/radio-pills.js +46 -4
- package/dist/radio-pills.js.map +1 -1
- package/dist/relative-time.d.ts +0 -1
- package/dist/relative-time.d.ts.map +1 -1
- package/dist/relative-time.js +1 -6
- package/dist/relative-time.js.map +1 -1
- package/dist/reveal-button.d.ts +2 -0
- package/dist/reveal-button.d.ts.map +1 -1
- package/dist/reveal-button.js +42 -5
- package/dist/reveal-button.js.map +1 -1
- package/dist/roman-numeral.js +1 -1
- package/dist/roman-numeral.js.map +1 -1
- package/dist/slide-panel.d.ts +7 -3
- package/dist/slide-panel.d.ts.map +1 -1
- package/dist/slide-panel.js +95 -23
- package/dist/slide-panel.js.map +1 -1
- package/dist/stat-meter.d.ts.map +1 -1
- package/dist/stat-meter.js +17 -7
- package/dist/stat-meter.js.map +1 -1
- package/dist/status-pill.d.ts.map +1 -1
- package/dist/status-pill.js +26 -12
- package/dist/status-pill.js.map +1 -1
- package/dist/tile-grid.d.ts +8 -0
- package/dist/tile-grid.d.ts.map +1 -1
- package/dist/tile-grid.js +88 -12
- package/dist/tile-grid.js.map +1 -1
- package/dist/toast-notification.d.ts.map +1 -1
- package/dist/toast-notification.js +40 -11
- package/dist/toast-notification.js.map +1 -1
- package/dist/tokens.css +23 -0
- package/dist/tokens.d.ts.map +1 -1
- package/dist/tokens.js +16 -0
- package/dist/tokens.js.map +1 -1
- package/dist/ui-button.d.ts +2 -0
- package/dist/ui-button.d.ts.map +1 -1
- package/dist/ui-button.js +58 -9
- package/dist/ui-button.js.map +1 -1
- package/dist/user-avatar.d.ts.map +1 -1
- package/dist/user-avatar.js +18 -3
- package/dist/user-avatar.js.map +1 -1
- package/dist/utils/calendar.d.ts +77 -0
- package/dist/utils/calendar.d.ts.map +1 -0
- package/dist/utils/calendar.js +135 -0
- package/dist/utils/calendar.js.map +1 -0
- package/dist/utils/form.d.ts +3 -0
- package/dist/utils/form.d.ts.map +1 -0
- package/dist/utils/form.js +14 -0
- package/dist/utils/form.js.map +1 -0
- package/dist/utils/layer-stack.d.ts +9 -0
- package/dist/utils/layer-stack.d.ts.map +1 -0
- package/dist/utils/layer-stack.js +35 -0
- package/dist/utils/layer-stack.js.map +1 -0
- package/dist/weight-bar-chart.d.ts.map +1 -1
- package/dist/weight-bar-chart.js +27 -8
- package/dist/weight-bar-chart.js.map +1 -1
- package/docs/address-autocomplete.md +5 -0
- package/docs/autocomplete-input.md +5 -0
- package/docs/calendar-entry.md +53 -0
- package/docs/calendar-month.md +79 -0
- package/docs/calendar-year.md +53 -0
- package/docs/chat-message.md +4 -0
- package/docs/confirm-dialog.md +2 -0
- package/docs/copy-link-button.md +2 -0
- package/docs/data-table.md +2 -0
- package/docs/design-language.md +149 -0
- package/docs/distribution-chart.md +4 -0
- package/docs/dropdown-button.md +1 -0
- package/docs/form-select.md +15 -3
- package/docs/frame-box.md +2 -0
- package/docs/kbd-hint.md +42 -0
- package/docs/map-circle.md +4 -0
- package/docs/percent-bar-chart.md +4 -1
- package/docs/photo-gallery.md +1 -0
- package/docs/popover-panel.md +2 -0
- package/docs/price-history-chart.md +8 -1
- package/docs/radio-cards.md +2 -0
- package/docs/radio-pills.md +2 -0
- package/docs/reveal-button.md +3 -0
- package/docs/slide-panel.md +4 -2
- package/docs/status-pill.md +3 -0
- package/docs/tile-grid.md +10 -1
- package/docs/toast-notification.md +4 -1
- package/docs/ui-button.md +2 -0
- package/docs/user-avatar.md +1 -0
- package/llms.txt +175 -61
- package/package.json +14 -2
- package/dist/map-point.d.ts +0 -31
- package/dist/map-point.d.ts.map +0 -1
- package/dist/map-point.js +0 -83
- package/dist/map-point.js.map +0 -1
- package/docs/map-point.md +0 -44
package/dist/popover-panel.js
CHANGED
|
@@ -4,10 +4,11 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
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
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { LitElement, css, html } from "lit";
|
|
7
|
+
import { LitElement, css, html, nothing } from "lit";
|
|
8
8
|
import { customElement, property } from "lit/decorators.js";
|
|
9
9
|
import { iconX } from "./icons.js";
|
|
10
10
|
import { tokens } from "./tokens.js";
|
|
11
|
+
import { activateLayer, claimEscape, deactivateLayer, isTopLayer, } from "./utils/layer-stack.js";
|
|
11
12
|
/**
|
|
12
13
|
* Generic anchored popover shell: a floating card positioned relative to its
|
|
13
14
|
* nearest `position: relative` ancestor (place it next to its trigger button
|
|
@@ -34,7 +35,11 @@ let PopoverPanel = class PopoverPanel extends LitElement {
|
|
|
34
35
|
this.heading = "";
|
|
35
36
|
/** Render as a screen-centered modal with a backdrop instead of anchored placement. */
|
|
36
37
|
this.centered = false;
|
|
38
|
+
this.#modalActive = false;
|
|
39
|
+
this.#previousFocus = null;
|
|
37
40
|
this.#onWindowMousedown = (e) => {
|
|
41
|
+
if (!isTopLayer(this))
|
|
42
|
+
return;
|
|
38
43
|
// In centered mode the host itself renders the backdrop, so a mousedown
|
|
39
44
|
// landing directly on the host (not on .panel or its content) is a
|
|
40
45
|
// backdrop click and must close like an outside click would.
|
|
@@ -46,8 +51,35 @@ let PopoverPanel = class PopoverPanel extends LitElement {
|
|
|
46
51
|
this.#close();
|
|
47
52
|
};
|
|
48
53
|
this.#onWindowKeydown = (e) => {
|
|
49
|
-
if (
|
|
54
|
+
if (!isTopLayer(this))
|
|
55
|
+
return;
|
|
56
|
+
if (claimEscape(this, e)) {
|
|
50
57
|
this.#close();
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (!this.centered || e.key !== "Tab")
|
|
61
|
+
return;
|
|
62
|
+
const focusable = this.#getFocusableElements();
|
|
63
|
+
if (focusable.length === 0) {
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
this.#focusInitial();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const active = (this.shadowRoot?.activeElement ?? document.activeElement);
|
|
69
|
+
const index = focusable.indexOf(active);
|
|
70
|
+
if (e.shiftKey && index <= 0) {
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
focusable.at(-1)?.focus();
|
|
73
|
+
}
|
|
74
|
+
else if (!e.shiftKey && index === focusable.length - 1) {
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
focusable[0]?.focus();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
this.#onDocumentFocusIn = (event) => {
|
|
80
|
+
if (!this.#modalActive || !isTopLayer(this) || event.composedPath().includes(this))
|
|
81
|
+
return;
|
|
82
|
+
this.#focusInitial();
|
|
51
83
|
};
|
|
52
84
|
}
|
|
53
85
|
static { this.styles = [
|
|
@@ -58,7 +90,7 @@ let PopoverPanel = class PopoverPanel extends LitElement {
|
|
|
58
90
|
position: absolute;
|
|
59
91
|
top: calc(100% + 0.5rem);
|
|
60
92
|
right: 0;
|
|
61
|
-
z-index: 20;
|
|
93
|
+
z-index: var(--component-layer-z, 20);
|
|
62
94
|
}
|
|
63
95
|
:host([open]) {
|
|
64
96
|
display: block;
|
|
@@ -69,16 +101,16 @@ let PopoverPanel = class PopoverPanel extends LitElement {
|
|
|
69
101
|
display: flex;
|
|
70
102
|
align-items: center;
|
|
71
103
|
justify-content: center;
|
|
72
|
-
background: rgb(
|
|
73
|
-
z-index: 30;
|
|
104
|
+
background: var(--ui-overlay, rgb(15 23 42 / 0.45));
|
|
105
|
+
z-index: var(--component-layer-z, 30);
|
|
74
106
|
}
|
|
75
107
|
:host([centered]) .panel {
|
|
76
|
-
width:
|
|
108
|
+
width: 25rem;
|
|
77
109
|
}
|
|
78
110
|
.panel {
|
|
79
|
-
width:
|
|
111
|
+
width: 20rem;
|
|
80
112
|
max-width: calc(100vw - 2rem);
|
|
81
|
-
background: var(--ui-surface, #
|
|
113
|
+
background: var(--ui-surface, #ffffff);
|
|
82
114
|
border: 1px solid var(--ui-border, #e2e8f0);
|
|
83
115
|
border-radius: var(--ui-radius, 0.5rem);
|
|
84
116
|
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));
|
|
@@ -90,14 +122,23 @@ let PopoverPanel = class PopoverPanel extends LitElement {
|
|
|
90
122
|
display: flex;
|
|
91
123
|
align-items: center;
|
|
92
124
|
justify-content: space-between;
|
|
93
|
-
padding: 0.5rem
|
|
125
|
+
padding: 0.5rem;
|
|
94
126
|
border-bottom: 1px solid var(--ui-border, #e2e8f0);
|
|
95
127
|
flex: 0 0 auto;
|
|
96
128
|
}
|
|
97
129
|
.panel-title {
|
|
98
130
|
font-weight: 600;
|
|
99
131
|
font-size: var(--ui-font-size, 0.875rem);
|
|
100
|
-
font-family: var(
|
|
132
|
+
font-family: var(
|
|
133
|
+
--ui-font,
|
|
134
|
+
ui-sans-serif,
|
|
135
|
+
system-ui,
|
|
136
|
+
sans-serif,
|
|
137
|
+
"Apple Color Emoji",
|
|
138
|
+
"Segoe UI Emoji",
|
|
139
|
+
"Segoe UI Symbol",
|
|
140
|
+
"Noto Color Emoji"
|
|
141
|
+
);
|
|
101
142
|
color: var(--ui-text, #0f172a);
|
|
102
143
|
flex: 1 1 auto;
|
|
103
144
|
overflow: hidden;
|
|
@@ -127,40 +168,108 @@ let PopoverPanel = class PopoverPanel extends LitElement {
|
|
|
127
168
|
.close-btn:hover {
|
|
128
169
|
background: var(--ui-surface-muted, #f8fafc);
|
|
129
170
|
}
|
|
171
|
+
.close-btn:focus-visible {
|
|
172
|
+
outline: none;
|
|
173
|
+
box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
|
|
174
|
+
}
|
|
175
|
+
@media (forced-colors: active) {
|
|
176
|
+
.close-btn:focus-visible {
|
|
177
|
+
outline: 2px solid CanvasText;
|
|
178
|
+
outline-offset: 2px;
|
|
179
|
+
box-shadow: none;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
130
182
|
`,
|
|
131
183
|
]; }
|
|
184
|
+
#modalActive;
|
|
185
|
+
#previousFocus;
|
|
132
186
|
disconnectedCallback() {
|
|
133
187
|
super.disconnectedCallback();
|
|
134
188
|
window.removeEventListener("mousedown", this.#onWindowMousedown, true);
|
|
135
189
|
window.removeEventListener("keydown", this.#onWindowKeydown);
|
|
190
|
+
document.removeEventListener("focusin", this.#onDocumentFocusIn, true);
|
|
191
|
+
deactivateLayer(this);
|
|
192
|
+
if (this.#previousFocus?.isConnected)
|
|
193
|
+
this.#previousFocus.focus();
|
|
194
|
+
this.#previousFocus = null;
|
|
195
|
+
this.#modalActive = false;
|
|
196
|
+
this.open = false;
|
|
136
197
|
}
|
|
137
198
|
updated(changed) {
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
199
|
+
if (changed.has("open")) {
|
|
200
|
+
if (this.open) {
|
|
201
|
+
activateLayer(this);
|
|
202
|
+
window.addEventListener("mousedown", this.#onWindowMousedown, true);
|
|
203
|
+
window.addEventListener("keydown", this.#onWindowKeydown);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
deactivateLayer(this);
|
|
207
|
+
window.removeEventListener("mousedown", this.#onWindowMousedown, true);
|
|
208
|
+
window.removeEventListener("keydown", this.#onWindowKeydown);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const shouldBeModal = this.open && this.centered;
|
|
212
|
+
if (shouldBeModal && !this.#modalActive) {
|
|
213
|
+
this.#modalActive = true;
|
|
214
|
+
this.#previousFocus = document.activeElement;
|
|
215
|
+
document.addEventListener("focusin", this.#onDocumentFocusIn, true);
|
|
216
|
+
this.#focusInitial();
|
|
143
217
|
}
|
|
144
|
-
else {
|
|
145
|
-
|
|
146
|
-
|
|
218
|
+
else if (!shouldBeModal && this.#modalActive) {
|
|
219
|
+
this.#modalActive = false;
|
|
220
|
+
document.removeEventListener("focusin", this.#onDocumentFocusIn, true);
|
|
221
|
+
if (this.#previousFocus?.isConnected)
|
|
222
|
+
this.#previousFocus.focus();
|
|
223
|
+
this.#previousFocus = null;
|
|
147
224
|
}
|
|
148
225
|
}
|
|
149
226
|
#onWindowMousedown;
|
|
150
227
|
#onWindowKeydown;
|
|
228
|
+
#onDocumentFocusIn;
|
|
229
|
+
#getFocusableElements() {
|
|
230
|
+
const selector = "a[href], button:not(:disabled), input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex='-1'])";
|
|
231
|
+
const focusableInSlot = (slotSelector) => {
|
|
232
|
+
const slot = this.shadowRoot?.querySelector(slotSelector);
|
|
233
|
+
return (slot?.assignedElements({ flatten: true }) ?? []).flatMap((element) => [
|
|
234
|
+
...(element instanceof HTMLElement && element.matches(selector) ? [element] : []),
|
|
235
|
+
...Array.from(element.querySelectorAll(selector)),
|
|
236
|
+
]);
|
|
237
|
+
};
|
|
238
|
+
const close = this.shadowRoot?.querySelector(".close-btn");
|
|
239
|
+
return [
|
|
240
|
+
...focusableInSlot('slot[name="title"]'),
|
|
241
|
+
...focusableInSlot('slot[name="actions"]'),
|
|
242
|
+
...(close ? [close] : []),
|
|
243
|
+
...focusableInSlot("slot:not([name])"),
|
|
244
|
+
].filter((element) => !element.hasAttribute("hidden"));
|
|
245
|
+
}
|
|
246
|
+
#focusInitial() {
|
|
247
|
+
const autofocus = this.querySelector("[autofocus]");
|
|
248
|
+
const close = this.shadowRoot?.querySelector(".close-btn");
|
|
249
|
+
const panel = this.shadowRoot?.querySelector(".panel");
|
|
250
|
+
(autofocus ?? close ?? this.#getFocusableElements()[0] ?? panel)?.focus();
|
|
251
|
+
}
|
|
151
252
|
#close() {
|
|
152
253
|
this.dispatchEvent(new CustomEvent("panel-close", { bubbles: true, composed: true }));
|
|
153
254
|
}
|
|
154
255
|
render() {
|
|
256
|
+
const explicitLabel = this.getAttribute("aria-label");
|
|
155
257
|
return html `
|
|
156
|
-
<div
|
|
258
|
+
<div
|
|
259
|
+
class="panel"
|
|
260
|
+
role="dialog"
|
|
261
|
+
aria-modal=${this.centered ? "true" : nothing}
|
|
262
|
+
aria-label=${explicitLabel ?? nothing}
|
|
263
|
+
aria-labelledby=${explicitLabel ? nothing : "popover-title"}
|
|
264
|
+
tabindex="-1"
|
|
265
|
+
>
|
|
157
266
|
<div class="panel-header">
|
|
158
|
-
<
|
|
159
|
-
|
|
160
|
-
</
|
|
267
|
+
<span id="popover-title" class="panel-title">
|
|
268
|
+
<slot name="title">${this.heading || (this.centered ? "Dialog" : "Popover")}</slot>
|
|
269
|
+
</span>
|
|
161
270
|
<span class="panel-actions"><slot name="actions"></slot></span>
|
|
162
271
|
<button class="close-btn" aria-label="Close popover" @click=${() => this.#close()}>
|
|
163
|
-
|
|
272
|
+
<span aria-hidden="true">${iconX(18)}</span>
|
|
164
273
|
</button>
|
|
165
274
|
</div>
|
|
166
275
|
<slot></slot>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"popover-panel.js","sourceRoot":"","sources":["../src/popover-panel.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html, type PropertyValues } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { iconX } from \"./icons.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * Generic anchored popover shell: a floating card positioned relative to its\n * nearest `position: relative` ancestor (place it next to its trigger button\n * inside such a wrapper), as opposed to `slide-panel`'s fixed screen-edge\n * drawer. Closes on outside click or Escape. Header chrome and close button\n * match `slide-panel`'s API (`heading`, `panel-close`) so either can be\n * swapped in for the other with no consumer-side changes beyond the wrapper.\n *\n * Set `centered` to render as a screen-centered modal with a translucent\n * backdrop instead of the default anchored placement.\n *\n * @element popover-panel\n * @fires panel-close - User clicked the close (✕) button, pressed Escape, or clicked outside/the backdrop.\n * @slot title - Overrides the plain `heading` text with custom markup.\n * @slot actions - Extra header controls (e.g. an icon+label link) rendered between the title and the close button.\n * @slot - Popover body content.\n */\n@customElement(\"popover-panel\")\nexport class PopoverPanel extends LitElement {\n /** Whether the popover is currently visible. */\n @property({ type: Boolean, reflect: true }) open = false;\n /** Title text shown in the popover header. */\n @property() heading = \"\";\n /** Render as a screen-centered modal with a backdrop instead of anchored placement. */\n @property({ type: Boolean, reflect: true }) centered = false;\n\n static override styles = [\n tokens,\n css`\n :host {\n display: none;\n position: absolute;\n top: calc(100% + 0.5rem);\n right: 0;\n z-index: 20;\n }\n :host([open]) {\n display: block;\n }\n :host([centered][open]) {\n position: fixed;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgb(0 0 0 / 0.35);\n z-index: 30;\n }\n :host([centered]) .panel {\n width: 400px;\n }\n .panel {\n width: 320px;\n max-width: calc(100vw - 2rem);\n background: var(--ui-surface, #fff);\n border: 1px solid var(--ui-border, #e2e8f0);\n border-radius: var(--ui-radius, 0.5rem);\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 display: flex;\n flex-direction: column;\n overflow: hidden;\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 .panel-actions {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n flex: 0 0 auto;\n margin-right: 0.25rem;\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 `,\n ];\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n window.removeEventListener(\"mousedown\", this.#onWindowMousedown, true);\n window.removeEventListener(\"keydown\", this.#onWindowKeydown);\n }\n\n protected override updated(changed: PropertyValues): void {\n if (!changed.has(\"open\")) return;\n if (this.open) {\n window.addEventListener(\"mousedown\", this.#onWindowMousedown, true);\n window.addEventListener(\"keydown\", this.#onWindowKeydown);\n } else {\n window.removeEventListener(\"mousedown\", this.#onWindowMousedown, true);\n window.removeEventListener(\"keydown\", this.#onWindowKeydown);\n }\n }\n\n #onWindowMousedown = (e: MouseEvent): void => {\n // In centered mode the host itself renders the backdrop, so a mousedown\n // landing directly on the host (not on .panel or its content) is a\n // backdrop click and must close like an outside click would.\n if (this.centered && e.target === this) {\n this.#close();\n return;\n }\n if (!e.composedPath().includes(this)) this.#close();\n };\n\n #onWindowKeydown = (e: KeyboardEvent): void => {\n if (e.key === \"Escape\") this.#close();\n };\n\n #close(): void {\n this.dispatchEvent(new CustomEvent(\"panel-close\", { bubbles: true, composed: true }));\n }\n\n override render() {\n return html`\n <div class=\"panel\">\n <div class=\"panel-header\">\n <slot name=\"title\">\n ${this.heading ? html`<span class=\"panel-title\">${this.heading}</span>` : null}\n </slot>\n <span class=\"panel-actions\"><slot name=\"actions\"></slot></span>\n <button class=\"close-btn\" aria-label=\"Close popover\" @click=${() => this.#close()}>\n ${iconX(18)}\n </button>\n </div>\n <slot></slot>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"popover-panel\": PopoverPanel;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"popover-panel.js","sourceRoot":"","sources":["../src/popover-panel.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,EAAuB,MAAM,KAAK,CAAC;AAC1E,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;AACrC,OAAO,EACL,aAAa,EACb,WAAW,EACX,eAAe,EACf,UAAU,GACX,MAAM,wBAAwB,CAAC;AAEhC;;;;;;;;;;;;;;;;GAgBG;AAEI,IAAM,YAAY,GAAlB,MAAM,YAAa,SAAQ,UAAU;IAArC;;QACL,gDAAgD;QACJ,SAAI,GAAG,KAAK,CAAC;QACzD,8CAA8C;QAClC,YAAO,GAAG,EAAE,CAAC;QACzB,uFAAuF;QAC3C,aAAQ,GAAG,KAAK,CAAC;QAsG7D,iBAAY,GAAG,KAAK,CAAC;QACrB,mBAAc,GAAuB,IAAI,CAAC;QAyC1C,uBAAkB,GAAG,CAAC,CAAa,EAAQ,EAAE;YAC3C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;gBAAE,OAAO;YAC9B,wEAAwE;YACxE,mEAAmE;YACnE,6DAA6D;YAC7D,IAAI,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;gBACvC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACd,OAAO;YACT,CAAC;YACD,IAAI,CAAC,CAAC,CAAC,YAAY,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,IAAI,CAAC,MAAM,EAAE,CAAC;QACtD,CAAC,CAAC;QAEF,qBAAgB,GAAG,CAAC,CAAgB,EAAQ,EAAE;YAC5C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;gBAAE,OAAO;YAC9B,IAAI,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;gBACzB,IAAI,CAAC,MAAM,EAAE,CAAC;gBACd,OAAO;YACT,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,CAAC,GAAG,KAAK,KAAK;gBAAE,OAAO;YAE9C,MAAM,SAAS,GAAG,IAAI,CAAC,qBAAqB,EAAE,CAAC;YAC/C,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC3B,CAAC,CAAC,cAAc,EAAE,CAAC;gBACnB,IAAI,CAAC,aAAa,EAAE,CAAC;gBACrB,OAAO;YACT,CAAC;YACD,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,IAAI,QAAQ,CAAC,aAAa,CAAuB,CAAC;YAChG,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,MAAqB,CAAC,CAAC;YACvD,IAAI,CAAC,CAAC,QAAQ,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;gBAC7B,CAAC,CAAC,cAAc,EAAE,CAAC;gBACnB,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;YAC5B,CAAC;iBAAM,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,KAAK,KAAK,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACzD,CAAC,CAAC,cAAc,EAAE,CAAC;gBACnB,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;YACxB,CAAC;QACH,CAAC,CAAC;QAEF,uBAAkB,GAAG,CAAC,KAAiB,EAAQ,EAAE;YAC/C,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC;gBAAE,OAAO;YAC3F,IAAI,CAAC,aAAa,EAAE,CAAC;QACvB,CAAC,CAAC;IAwDJ,CAAC;aA9OiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA+FF;KACF,AAlGqB,CAkGpB;IAEF,YAAY,CAAS;IACrB,cAAc,CAA4B;IAEjC,oBAAoB;QAC3B,KAAK,CAAC,oBAAoB,EAAE,CAAC;QAC7B,MAAM,CAAC,mBAAmB,CAAC,WAAW,EAAE,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC;QACvE,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC7D,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC;QACvE,eAAe,CAAC,IAAI,CAAC,CAAC;QACtB,IAAI,IAAI,CAAC,cAAc,EAAE,WAAW;YAAE,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,CAAC;QAClE,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC3B,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;QAC1B,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC;IACpB,CAAC;IAEkB,OAAO,CAAC,OAAuB;QAChD,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACxB,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;gBACd,aAAa,CAAC,IAAI,CAAC,CAAC;gBACpB,MAAM,CAAC,gBAAgB,CAAC,WAAW,EAAE,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC;gBACpE,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;YAC5D,CAAC;iBAAM,CAAC;gBACN,eAAe,CAAC,IAAI,CAAC,CAAC;gBACtB,MAAM,CAAC,mBAAmB,CAAC,WAAW,EAAE,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC;gBACvE,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,IAAI,CAAC,gBAAgB,CAAC,CAAC;YAC/D,CAAC;QACH,CAAC;QAED,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC;QACjD,IAAI,aAAa,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YACxC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;YACzB,IAAI,CAAC,cAAc,GAAG,QAAQ,CAAC,aAAmC,CAAC;YACnE,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC;YACpE,IAAI,CAAC,aAAa,EAAE,CAAC;QACvB,CAAC;aAAM,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YAC/C,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;YAC1B,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC;YACvE,IAAI,IAAI,CAAC,cAAc,EAAE,WAAW;gBAAE,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,CAAC;YAClE,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC7B,CAAC;IACH,CAAC;IAED,kBAAkB,CAUhB;IAEF,gBAAgB,CAuBd;IAEF,kBAAkB,CAGhB;IAEF,qBAAqB;QACnB,MAAM,QAAQ,GACZ,uIAAuI,CAAC;QAC1I,MAAM,eAAe,GAAG,CAAC,YAAoB,EAAiB,EAAE;YAC9D,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,EAAE,aAAa,CAAkB,YAAY,CAAC,CAAC;YAC3E,OAAO,CAAC,IAAI,EAAE,gBAAgB,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;gBAC5E,GAAG,CAAC,OAAO,YAAY,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBACjF,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAc,QAAQ,CAAC,CAAC;aAC/D,CAAC,CAAC;QACL,CAAC,CAAC;QACF,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,EAAE,aAAa,CAAc,YAAY,CAAC,CAAC;QACxE,OAAO;YACL,GAAG,eAAe,CAAC,oBAAoB,CAAC;YACxC,GAAG,eAAe,CAAC,sBAAsB,CAAC;YAC1C,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YACzB,GAAG,eAAe,CAAC,kBAAkB,CAAC;SACvC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;IACzD,CAAC;IAED,aAAa;QACX,MAAM,SAAS,GAAG,IAAI,CAAC,aAAa,CAAc,aAAa,CAAC,CAAC;QACjE,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,EAAE,aAAa,CAAc,YAAY,CAAC,CAAC;QACxE,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,EAAE,aAAa,CAAc,QAAQ,CAAC,CAAC;QACpE,CAAC,SAAS,IAAI,KAAK,IAAI,IAAI,CAAC,qBAAqB,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC;IAC5E,CAAC;IAED,MAAM;QACJ,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,MAAM,aAAa,GAAG,IAAI,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;QACtD,OAAO,IAAI,CAAA;;;;qBAIM,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO;qBAChC,aAAa,IAAI,OAAO;0BACnB,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe;;;;;iCAKlC,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC;;;wEAGf,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE;uCACpD,KAAK,CAAC,EAAE,CAAC;;;;;KAK3C,CAAC;IACJ,CAAC;CACF,CAAA;AApP6C;IAA3C,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;0CAAc;AAE7C;IAAX,QAAQ,EAAE;6CAAc;AAEmB;IAA3C,QAAQ,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;8CAAkB;AANlD,YAAY;IADxB,aAAa,CAAC,eAAe,CAAC;GAClB,YAAY,CAsPxB","sourcesContent":["import { LitElement, css, html, nothing, type PropertyValues } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { iconX } from \"./icons.js\";\nimport { tokens } from \"./tokens.js\";\nimport {\n activateLayer,\n claimEscape,\n deactivateLayer,\n isTopLayer,\n} from \"./utils/layer-stack.js\";\n\n/**\n * Generic anchored popover shell: a floating card positioned relative to its\n * nearest `position: relative` ancestor (place it next to its trigger button\n * inside such a wrapper), as opposed to `slide-panel`'s fixed screen-edge\n * drawer. Closes on outside click or Escape. Header chrome and close button\n * match `slide-panel`'s API (`heading`, `panel-close`) so either can be\n * swapped in for the other with no consumer-side changes beyond the wrapper.\n *\n * Set `centered` to render as a screen-centered modal with a translucent\n * backdrop instead of the default anchored placement.\n *\n * @element popover-panel\n * @fires panel-close - User clicked the close (✕) button, pressed Escape, or clicked outside/the backdrop.\n * @slot title - Overrides the plain `heading` text with custom markup.\n * @slot actions - Extra header controls (e.g. an icon+label link) rendered between the title and the close button.\n * @slot - Popover body content.\n */\n@customElement(\"popover-panel\")\nexport class PopoverPanel extends LitElement {\n /** Whether the popover is currently visible. */\n @property({ type: Boolean, reflect: true }) open = false;\n /** Title text shown in the popover header. */\n @property() heading = \"\";\n /** Render as a screen-centered modal with a backdrop instead of anchored placement. */\n @property({ type: Boolean, reflect: true }) centered = false;\n\n static override styles = [\n tokens,\n css`\n :host {\n display: none;\n position: absolute;\n top: calc(100% + 0.5rem);\n right: 0;\n z-index: var(--component-layer-z, 20);\n }\n :host([open]) {\n display: block;\n }\n :host([centered][open]) {\n position: fixed;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--ui-overlay, rgb(15 23 42 / 0.45));\n z-index: var(--component-layer-z, 30);\n }\n :host([centered]) .panel {\n width: 25rem;\n }\n .panel {\n width: 20rem;\n max-width: calc(100vw - 2rem);\n background: var(--ui-surface, #ffffff);\n border: 1px solid var(--ui-border, #e2e8f0);\n border-radius: var(--ui-radius, 0.5rem);\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 display: flex;\n flex-direction: column;\n overflow: hidden;\n }\n .panel-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0.5rem;\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(\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 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 .panel-actions {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n flex: 0 0 auto;\n margin-right: 0.25rem;\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 .close-btn:focus-visible {\n outline: none;\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n }\n @media (forced-colors: active) {\n .close-btn:focus-visible {\n outline: 2px solid CanvasText;\n outline-offset: 2px;\n box-shadow: none;\n }\n }\n `,\n ];\n\n #modalActive = false;\n #previousFocus: HTMLElement | null = null;\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n window.removeEventListener(\"mousedown\", this.#onWindowMousedown, true);\n window.removeEventListener(\"keydown\", this.#onWindowKeydown);\n document.removeEventListener(\"focusin\", this.#onDocumentFocusIn, true);\n deactivateLayer(this);\n if (this.#previousFocus?.isConnected) this.#previousFocus.focus();\n this.#previousFocus = null;\n this.#modalActive = false;\n this.open = false;\n }\n\n protected override updated(changed: PropertyValues): void {\n if (changed.has(\"open\")) {\n if (this.open) {\n activateLayer(this);\n window.addEventListener(\"mousedown\", this.#onWindowMousedown, true);\n window.addEventListener(\"keydown\", this.#onWindowKeydown);\n } else {\n deactivateLayer(this);\n window.removeEventListener(\"mousedown\", this.#onWindowMousedown, true);\n window.removeEventListener(\"keydown\", this.#onWindowKeydown);\n }\n }\n\n const shouldBeModal = this.open && this.centered;\n if (shouldBeModal && !this.#modalActive) {\n this.#modalActive = true;\n this.#previousFocus = document.activeElement as HTMLElement | null;\n document.addEventListener(\"focusin\", this.#onDocumentFocusIn, true);\n this.#focusInitial();\n } else if (!shouldBeModal && this.#modalActive) {\n this.#modalActive = false;\n document.removeEventListener(\"focusin\", this.#onDocumentFocusIn, true);\n if (this.#previousFocus?.isConnected) this.#previousFocus.focus();\n this.#previousFocus = null;\n }\n }\n\n #onWindowMousedown = (e: MouseEvent): void => {\n if (!isTopLayer(this)) return;\n // In centered mode the host itself renders the backdrop, so a mousedown\n // landing directly on the host (not on .panel or its content) is a\n // backdrop click and must close like an outside click would.\n if (this.centered && e.target === this) {\n this.#close();\n return;\n }\n if (!e.composedPath().includes(this)) this.#close();\n };\n\n #onWindowKeydown = (e: KeyboardEvent): void => {\n if (!isTopLayer(this)) return;\n if (claimEscape(this, e)) {\n this.#close();\n return;\n }\n if (!this.centered || e.key !== \"Tab\") return;\n\n const focusable = this.#getFocusableElements();\n if (focusable.length === 0) {\n e.preventDefault();\n this.#focusInitial();\n return;\n }\n const active = (this.shadowRoot?.activeElement ?? document.activeElement) as HTMLElement | null;\n const index = focusable.indexOf(active as HTMLElement);\n if (e.shiftKey && index <= 0) {\n e.preventDefault();\n focusable.at(-1)?.focus();\n } else if (!e.shiftKey && index === focusable.length - 1) {\n e.preventDefault();\n focusable[0]?.focus();\n }\n };\n\n #onDocumentFocusIn = (event: FocusEvent): void => {\n if (!this.#modalActive || !isTopLayer(this) || event.composedPath().includes(this)) return;\n this.#focusInitial();\n };\n\n #getFocusableElements(): HTMLElement[] {\n const selector =\n \"a[href], button:not(:disabled), input:not(:disabled), select:not(:disabled), textarea:not(:disabled), [tabindex]:not([tabindex='-1'])\";\n const focusableInSlot = (slotSelector: string): HTMLElement[] => {\n const slot = this.shadowRoot?.querySelector<HTMLSlotElement>(slotSelector);\n return (slot?.assignedElements({ flatten: true }) ?? []).flatMap((element) => [\n ...(element instanceof HTMLElement && element.matches(selector) ? [element] : []),\n ...Array.from(element.querySelectorAll<HTMLElement>(selector)),\n ]);\n };\n const close = this.shadowRoot?.querySelector<HTMLElement>(\".close-btn\");\n return [\n ...focusableInSlot('slot[name=\"title\"]'),\n ...focusableInSlot('slot[name=\"actions\"]'),\n ...(close ? [close] : []),\n ...focusableInSlot(\"slot:not([name])\"),\n ].filter((element) => !element.hasAttribute(\"hidden\"));\n }\n\n #focusInitial(): void {\n const autofocus = this.querySelector<HTMLElement>(\"[autofocus]\");\n const close = this.shadowRoot?.querySelector<HTMLElement>(\".close-btn\");\n const panel = this.shadowRoot?.querySelector<HTMLElement>(\".panel\");\n (autofocus ?? close ?? this.#getFocusableElements()[0] ?? panel)?.focus();\n }\n\n #close(): void {\n this.dispatchEvent(new CustomEvent(\"panel-close\", { bubbles: true, composed: true }));\n }\n\n override render() {\n const explicitLabel = this.getAttribute(\"aria-label\");\n return html`\n <div\n class=\"panel\"\n role=\"dialog\"\n aria-modal=${this.centered ? \"true\" : nothing}\n aria-label=${explicitLabel ?? nothing}\n aria-labelledby=${explicitLabel ? nothing : \"popover-title\"}\n tabindex=\"-1\"\n >\n <div class=\"panel-header\">\n <span id=\"popover-title\" class=\"panel-title\">\n <slot name=\"title\">${this.heading || (this.centered ? \"Dialog\" : \"Popover\")}</slot>\n </span>\n <span class=\"panel-actions\"><slot name=\"actions\"></slot></span>\n <button class=\"close-btn\" aria-label=\"Close popover\" @click=${() => this.#close()}>\n <span aria-hidden=\"true\">${iconX(18)}</span>\n </button>\n </div>\n <slot></slot>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"popover-panel\": PopoverPanel;\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"price-history-chart.d.ts","sourceRoot":"","sources":["../src/price-history-chart.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAkB,MAAM,KAAK,CAAC;AAOjD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAiBD;;;;;;;;;;GAUG;AACH,qBACa,iBAAkB,SAAQ,UAAU;IACpB,OAAO,EAAE,UAAU,EAAE,CAAM;IACjB,OAAO,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAU;IAC5B,UAAU,SAAK;IAE7D,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,WAAW,CAAuB;IAEnD,OAAO,CAAC,GAAG,CAA+B;IAEjC,iBAAiB,SAOzB;IAEQ,oBAAoB,SAI5B;IAED,OAAO,KAAK,WAAW,GAItB;IAED,OAAO,KAAK,KAAK,GAEhB;IAED,OAAgB,MAAM,
|
|
1
|
+
{"version":3,"file":"price-history-chart.d.ts","sourceRoot":"","sources":["../src/price-history-chart.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAkB,MAAM,KAAK,CAAC;AAOjD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAiBD;;;;;;;;;;GAUG;AACH,qBACa,iBAAkB,SAAQ,UAAU;IACpB,OAAO,EAAE,UAAU,EAAE,CAAM;IACjB,OAAO,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAU;IAC5B,UAAU,SAAK;IAE7D,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,WAAW,CAAuB;IAEnD,OAAO,CAAC,GAAG,CAA+B;IAEjC,iBAAiB,SAOzB;IAEQ,oBAAoB,SAI5B;IAED,OAAO,KAAK,WAAW,GAItB;IAED,OAAO,KAAK,KAAK,GAEhB;IAED,OAAgB,MAAM,4BAiDpB;IAEF,OAAO,CAAC,aAAa;IA8CZ,MAAM,yCAoFd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,qBAAqB,EAAE,iBAAiB,CAAC;KAC1C;CACF"}
|
|
@@ -77,6 +77,39 @@ let PriceHistoryChart = class PriceHistoryChart extends LitElement {
|
|
|
77
77
|
svg {
|
|
78
78
|
display: block;
|
|
79
79
|
overflow: visible;
|
|
80
|
+
font-family: var(
|
|
81
|
+
--ui-font,
|
|
82
|
+
ui-sans-serif,
|
|
83
|
+
system-ui,
|
|
84
|
+
sans-serif,
|
|
85
|
+
"Apple Color Emoji",
|
|
86
|
+
"Segoe UI Emoji",
|
|
87
|
+
"Segoe UI Symbol",
|
|
88
|
+
"Noto Color Emoji"
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
.axis-label {
|
|
92
|
+
fill: var(--ui-text-muted, #64748b);
|
|
93
|
+
}
|
|
94
|
+
.grid-line {
|
|
95
|
+
stroke: var(--ui-border, #e2e8f0);
|
|
96
|
+
}
|
|
97
|
+
.series-line {
|
|
98
|
+
stroke: var(--ui-primary, #4f46e5);
|
|
99
|
+
}
|
|
100
|
+
.series-gradient {
|
|
101
|
+
stop-color: var(--ui-primary, #4f46e5);
|
|
102
|
+
}
|
|
103
|
+
.series-point {
|
|
104
|
+
fill: var(--ui-primary, #4f46e5);
|
|
105
|
+
stroke: var(--ui-on-accent, #ffffff);
|
|
106
|
+
cursor: pointer;
|
|
107
|
+
}
|
|
108
|
+
.tooltip {
|
|
109
|
+
fill: var(--ui-tooltip, #0f172a);
|
|
110
|
+
}
|
|
111
|
+
.tooltip-label {
|
|
112
|
+
fill: var(--ui-on-accent, #ffffff);
|
|
80
113
|
}
|
|
81
114
|
`,
|
|
82
115
|
]; }
|
|
@@ -138,11 +171,10 @@ let PriceHistoryChart = class PriceHistoryChart extends LitElement {
|
|
|
138
171
|
const tipX = Math.min(Math.max(hovPt.x - tipW / 2, ML), this._width - MR - tipW);
|
|
139
172
|
return svg `
|
|
140
173
|
<g pointer-events="none">
|
|
141
|
-
<rect x="${tipX}" y="${tipRectY}" width="${tipW}" height="${tipH}"
|
|
142
|
-
rx="4"
|
|
143
|
-
<text x="${tipX + tipW / 2}" y="${tipRectY + 15.5}"
|
|
144
|
-
text-anchor="middle"
|
|
145
|
-
font-family="ui-sans-serif,system-ui,sans-serif">${tipText}</text>
|
|
174
|
+
<rect class="tooltip" x="${tipX}" y="${tipRectY}" width="${tipW}" height="${tipH}"
|
|
175
|
+
rx="4" />
|
|
176
|
+
<text class="tooltip-label" x="${tipX + tipW / 2}" y="${tipRectY + 15.5}"
|
|
177
|
+
text-anchor="middle" font-size="11">${tipText}</text>
|
|
146
178
|
</g>
|
|
147
179
|
`;
|
|
148
180
|
})() : "";
|
|
@@ -151,8 +183,7 @@ let PriceHistoryChart = class PriceHistoryChart extends LitElement {
|
|
|
151
183
|
const label = d.toLocaleDateString("en-US", { month: "short", year: "numeric" });
|
|
152
184
|
const anchor = i === 0 ? "start" : i === arr.length - 1 ? "end" : "middle";
|
|
153
185
|
return svg `
|
|
154
|
-
<text x="${x}" y="${H - 6}" text-anchor="${anchor}" font-size="10"
|
|
155
|
-
font-family="ui-sans-serif,system-ui,sans-serif">${label}</text>
|
|
186
|
+
<text class="axis-label" x="${x}" y="${H - 6}" text-anchor="${anchor}" font-size="10">${label}</text>
|
|
156
187
|
`;
|
|
157
188
|
});
|
|
158
189
|
const yTicks = this.showY ? yScale.ticks(3).map((v, i, arr) => {
|
|
@@ -160,31 +191,34 @@ let PriceHistoryChart = class PriceHistoryChart extends LitElement {
|
|
|
160
191
|
const isFirst = i === 0;
|
|
161
192
|
const isLast = i === arr.length - 1;
|
|
162
193
|
return svg `
|
|
163
|
-
<line x1="${ML - 4}" y1="${y}" x2="${ML + plotW}" y2="${y}"
|
|
164
|
-
stroke
|
|
194
|
+
<line class="grid-line" x1="${ML - 4}" y1="${y}" x2="${ML + plotW}" y2="${y}"
|
|
195
|
+
stroke-width="1" />
|
|
165
196
|
${isFirst || isLast ? svg `
|
|
166
|
-
<text x="${ML - 6}" y="${y + 4}" text-anchor="end" font-size="10"
|
|
167
|
-
font-family="ui-sans-serif,system-ui,sans-serif">${fmtMoneyShort(v)}</text>
|
|
197
|
+
<text class="axis-label" x="${ML - 6}" y="${y + 4}" text-anchor="end" font-size="10">${fmtMoneyShort(v)}</text>
|
|
168
198
|
` : ""}
|
|
169
199
|
`;
|
|
170
200
|
}) : [];
|
|
201
|
+
const firstPoint = points[0];
|
|
202
|
+
const lastPoint = points[points.length - 1];
|
|
203
|
+
const formatDate = (date) => new Date(date).toLocaleDateString("en-US", { month: "short", year: "numeric" });
|
|
204
|
+
const summary = `${points.length} price points from ${formatDate(firstPoint.date)} at ${fmtMoney(firstPoint.price)} ` +
|
|
205
|
+
`to ${formatDate(lastPoint.date)} at ${fmtMoney(lastPoint.price)}.`;
|
|
171
206
|
return html `
|
|
172
207
|
<div class="wrap">
|
|
173
|
-
<svg width="${this._width}" height="${H}">
|
|
208
|
+
<svg width="${this._width}" height="${H}" role="img" aria-label=${summary}>
|
|
174
209
|
<defs>
|
|
175
210
|
<linearGradient id="phg" x1="0" y1="0" x2="0" y2="1">
|
|
176
|
-
<stop
|
|
177
|
-
<stop
|
|
211
|
+
<stop class="series-gradient" offset="0%" stop-opacity="0.25" />
|
|
212
|
+
<stop class="series-gradient" offset="100%" stop-opacity="0" />
|
|
178
213
|
</linearGradient>
|
|
179
214
|
</defs>
|
|
180
215
|
${yTicks}
|
|
181
216
|
<path d="${areaD}" fill="url(#phg)" />
|
|
182
|
-
<path d="${lineD}" fill="none" stroke
|
|
217
|
+
<path class="series-line" d="${lineD}" fill="none" stroke-width="2.5"
|
|
183
218
|
stroke-linejoin="round" stroke-linecap="round" />
|
|
184
219
|
${plotted.map((p, i) => svg `
|
|
185
|
-
<circle cx="${p.x}" cy="${p.y}" r="${i === hIdx ? 7 : 5}"
|
|
186
|
-
|
|
187
|
-
style="cursor:pointer"
|
|
220
|
+
<circle class="series-point" cx="${p.x}" cy="${p.y}" r="${i === hIdx ? 7 : 5}"
|
|
221
|
+
stroke-width="2"
|
|
188
222
|
@pointerenter=${() => { this._hoveredIdx = i; }}
|
|
189
223
|
@pointerleave=${() => { this._hoveredIdx = null; }} />
|
|
190
224
|
`)}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"price-history-chart.js","sourceRoot":"","sources":["../src/price-history-chart.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html, svg } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { scaleTime, scaleLinear } from \"d3-scale\";\nimport { line, area } from \"d3-shape\";\nimport { min, max } from \"d3-array\";\nimport { tokens } from \"./tokens.js\";\n\nexport interface PricePoint {\n date: string | null;\n price: number | null;\n eventType: string;\n source?: string | null;\n}\n\ninterface PlottedPoint extends PricePoint {\n x: number;\n y: number;\n}\n\nfunction fmtMoney(n: number): string {\n return `$${n.toLocaleString(\"en-US\")}`;\n}\n\nfunction fmtMoneyShort(n: number): string {\n if (n >= 1_000_000) return `$${(n / 1_000_000).toFixed(1)}M`;\n if (n >= 1_000) return `$${Math.round(n / 1_000)}K`;\n return `$${n.toLocaleString(\"en-US\")}`;\n}\n\n/**\n * D3-powered SVG line chart for property price history.\n *\n * Uses scaleTime (X) + scaleLinear (Y) from d3-scale and line/area path\n * generators from d3-shape. Adapts to container width via ResizeObserver.\n *\n * @element price-history-chart\n * @prop history - Array of price points (points with null price/date are skipped).\n * @prop yLabels - \"auto\" (default) | \"always\" | \"never\"\n * @prop maxXLabels - Max X-axis date ticks (default 3).\n */\n@customElement(\"price-history-chart\")\nexport class PriceHistoryChart extends LitElement {\n @property({ type: Array }) history: PricePoint[] = [];\n @property({ attribute: \"y-labels\" }) yLabels: \"auto\" | \"always\" | \"never\" = \"auto\";\n @property({ type: Number, attribute: \"max-x-labels\" }) maxXLabels = 3;\n\n @state() private _width = 0;\n @state() private _hoveredIdx: number | null = null;\n\n private _ro: ResizeObserver | null = null;\n\n override connectedCallback() {\n super.connectedCallback();\n this._ro = new ResizeObserver((entries) => {\n const entry = entries[0];\n if (entry) this._width = entry.contentRect.width;\n });\n this._ro.observe(this);\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this._ro?.disconnect();\n this._ro = null;\n }\n\n private get validPoints(): PricePoint[] {\n return this.history\n .filter((h) => h.price != null && h.date != null)\n .sort((a, b) => new Date(a.date!).getTime() - new Date(b.date!).getTime());\n }\n\n private get showY(): boolean {\n return this.yLabels === \"always\" || (this.yLabels === \"auto\" && this._width >= 260);\n }\n\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n width: 100%;\n }\n .wrap {\n position: relative;\n width: 100%;\n }\n svg {\n display: block;\n overflow: visible;\n }\n `,\n ];\n\n private computeLayout() {\n if (this._width < 10) return null;\n const points = this.validPoints;\n if (points.length < 2) return null;\n\n const H = 180;\n const MT = 20;\n const MB = 30;\n const MR = 12;\n const ML = this.showY ? 56 : 12;\n const plotW = Math.max(this._width - ML - MR, 1);\n const plotH = Math.max(H - MT - MB, 1);\n const bottomY = MT + plotH;\n\n const prices = points.map((p) => p.price!);\n const dates = points.map((p) => new Date(p.date!));\n const minPrice = min(prices)!;\n const maxPrice = max(prices)!;\n const pad = (maxPrice - minPrice) * 0.05 || maxPrice * 0.05;\n\n const xScale = scaleTime()\n .domain([min(dates)!, max(dates)!])\n .range([ML, ML + plotW]);\n\n const yScale = scaleLinear()\n .domain([minPrice - pad, maxPrice + pad])\n .range([bottomY, MT]);\n\n const lineFn = line<PricePoint>()\n .x((p) => xScale(new Date(p.date!)))\n .y((p) => yScale(p.price!));\n\n const areaFn = area<PricePoint>()\n .x((p) => xScale(new Date(p.date!)))\n .y0(bottomY)\n .y1((p) => yScale(p.price!));\n\n const plotted: PlottedPoint[] = points.map((p) => ({\n ...p,\n x: xScale(new Date(p.date!)),\n y: yScale(p.price!),\n }));\n\n return { points, plotted, H, MT, ML, MR, plotW, plotH, bottomY, minPrice, maxPrice, xScale, yScale, lineFn, areaFn };\n }\n\n override render() {\n const layout = this.computeLayout();\n if (!layout) return html``;\n\n const { points, plotted, H, MT, ML, MR, plotW, plotH, xScale, yScale, lineFn, areaFn } = layout;\n\n const lineD = lineFn(points) ?? \"\";\n const areaD = areaFn(points) ?? \"\";\n\n const hIdx = this._hoveredIdx;\n const hovPt = hIdx !== null ? plotted[hIdx] : null;\n\n const tooltipEl = hovPt ? (() => {\n const tipText = `${fmtMoney(hovPt.price!)} · ${hovPt.date ? new Date(hovPt.date).toLocaleDateString(\"en-US\", { month: \"short\", year: \"numeric\" }) : \"\"} · ${hovPt.eventType}`;\n const tipW = Math.min(tipText.length * 6.5 + 16, this._width - ML - MR);\n const tipH = 24;\n const tipGap = 8;\n const above = hovPt.y > plotH / 2 + MT;\n const tipRectY = above ? hovPt.y - tipGap - tipH : hovPt.y + tipGap;\n const tipX = Math.min(Math.max(hovPt.x - tipW / 2, ML), this._width - MR - tipW);\n return svg`\n <g pointer-events=\"none\">\n <rect x=\"${tipX}\" y=\"${tipRectY}\" width=\"${tipW}\" height=\"${tipH}\"\n rx=\"4\" fill=\"rgba(15,23,42,0.88)\" />\n <text x=\"${tipX + tipW / 2}\" y=\"${tipRectY + 15.5}\"\n text-anchor=\"middle\" fill=\"white\" font-size=\"11\"\n font-family=\"ui-sans-serif,system-ui,sans-serif\">${tipText}</text>\n </g>\n `;\n })() : \"\";\n\n const xTicks = xScale.ticks(this.maxXLabels).map((d, i, arr) => {\n const x = xScale(d);\n const label = d.toLocaleDateString(\"en-US\", { month: \"short\", year: \"numeric\" });\n const anchor = i === 0 ? \"start\" : i === arr.length - 1 ? \"end\" : \"middle\";\n return svg`\n <text x=\"${x}\" y=\"${H - 6}\" text-anchor=\"${anchor}\" font-size=\"10\" fill=\"#64748b\"\n font-family=\"ui-sans-serif,system-ui,sans-serif\">${label}</text>\n `;\n });\n\n const yTicks = this.showY ? yScale.ticks(3).map((v, i, arr) => {\n const y = yScale(v);\n const isFirst = i === 0;\n const isLast = i === arr.length - 1;\n return svg`\n <line x1=\"${ML - 4}\" y1=\"${y}\" x2=\"${ML + plotW}\" y2=\"${y}\"\n stroke=\"#e2e8f0\" stroke-width=\"1\" />\n ${isFirst || isLast ? svg`\n <text x=\"${ML - 6}\" y=\"${y + 4}\" text-anchor=\"end\" font-size=\"10\" fill=\"#64748b\"\n font-family=\"ui-sans-serif,system-ui,sans-serif\">${fmtMoneyShort(v)}</text>\n ` : \"\"}\n `;\n }) : [];\n\n return html`\n <div class=\"wrap\">\n <svg width=\"${this._width}\" height=\"${H}\">\n <defs>\n <linearGradient id=\"phg\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stop-color=\"#4f46e5\" stop-opacity=\"0.25\" />\n <stop offset=\"100%\" stop-color=\"#4f46e5\" stop-opacity=\"0\" />\n </linearGradient>\n </defs>\n ${yTicks}\n <path d=\"${areaD}\" fill=\"url(#phg)\" />\n <path d=\"${lineD}\" fill=\"none\" stroke=\"#4f46e5\" stroke-width=\"2.5\"\n stroke-linejoin=\"round\" stroke-linecap=\"round\" />\n ${plotted.map((p, i) => svg`\n <circle cx=\"${p.x}\" cy=\"${p.y}\" r=\"${i === hIdx ? 7 : 5}\"\n fill=\"#4f46e5\" stroke=\"#fff\" stroke-width=\"2\"\n style=\"cursor:pointer\"\n @pointerenter=${() => { this._hoveredIdx = i; }}\n @pointerleave=${() => { this._hoveredIdx = null; }} />\n `)}\n ${xTicks}\n ${tooltipEl}\n </svg>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"price-history-chart\": PriceHistoryChart;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"price-history-chart.js","sourceRoot":"","sources":["../src/price-history-chart.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html, svg } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { scaleTime, scaleLinear } from \"d3-scale\";\nimport { line, area } from \"d3-shape\";\nimport { min, max } from \"d3-array\";\nimport { tokens } from \"./tokens.js\";\n\nexport interface PricePoint {\n date: string | null;\n price: number | null;\n eventType: string;\n source?: string | null;\n}\n\ninterface PlottedPoint extends PricePoint {\n x: number;\n y: number;\n}\n\nfunction fmtMoney(n: number): string {\n return `$${n.toLocaleString(\"en-US\")}`;\n}\n\nfunction fmtMoneyShort(n: number): string {\n if (n >= 1_000_000) return `$${(n / 1_000_000).toFixed(1)}M`;\n if (n >= 1_000) return `$${Math.round(n / 1_000)}K`;\n return `$${n.toLocaleString(\"en-US\")}`;\n}\n\n/**\n * D3-powered SVG line chart for property price history.\n *\n * Uses scaleTime (X) + scaleLinear (Y) from d3-scale and line/area path\n * generators from d3-shape. Adapts to container width via ResizeObserver.\n *\n * @element price-history-chart\n * @prop history - Array of price points (points with null price/date are skipped).\n * @prop yLabels - \"auto\" (default) | \"always\" | \"never\"\n * @prop maxXLabels - Max X-axis date ticks (default 3).\n */\n@customElement(\"price-history-chart\")\nexport class PriceHistoryChart extends LitElement {\n @property({ type: Array }) history: PricePoint[] = [];\n @property({ attribute: \"y-labels\" }) yLabels: \"auto\" | \"always\" | \"never\" = \"auto\";\n @property({ type: Number, attribute: \"max-x-labels\" }) maxXLabels = 3;\n\n @state() private _width = 0;\n @state() private _hoveredIdx: number | null = null;\n\n private _ro: ResizeObserver | null = null;\n\n override connectedCallback() {\n super.connectedCallback();\n this._ro = new ResizeObserver((entries) => {\n const entry = entries[0];\n if (entry) this._width = entry.contentRect.width;\n });\n this._ro.observe(this);\n }\n\n override disconnectedCallback() {\n super.disconnectedCallback();\n this._ro?.disconnect();\n this._ro = null;\n }\n\n private get validPoints(): PricePoint[] {\n return this.history\n .filter((h) => h.price != null && h.date != null)\n .sort((a, b) => new Date(a.date!).getTime() - new Date(b.date!).getTime());\n }\n\n private get showY(): boolean {\n return this.yLabels === \"always\" || (this.yLabels === \"auto\" && this._width >= 260);\n }\n\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n width: 100%;\n }\n .wrap {\n position: relative;\n width: 100%;\n }\n svg {\n display: block;\n overflow: visible;\n font-family: 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 }\n .axis-label {\n fill: var(--ui-text-muted, #64748b);\n }\n .grid-line {\n stroke: var(--ui-border, #e2e8f0);\n }\n .series-line {\n stroke: var(--ui-primary, #4f46e5);\n }\n .series-gradient {\n stop-color: var(--ui-primary, #4f46e5);\n }\n .series-point {\n fill: var(--ui-primary, #4f46e5);\n stroke: var(--ui-on-accent, #ffffff);\n cursor: pointer;\n }\n .tooltip {\n fill: var(--ui-tooltip, #0f172a);\n }\n .tooltip-label {\n fill: var(--ui-on-accent, #ffffff);\n }\n `,\n ];\n\n private computeLayout() {\n if (this._width < 10) return null;\n const points = this.validPoints;\n if (points.length < 2) return null;\n\n const H = 180;\n const MT = 20;\n const MB = 30;\n const MR = 12;\n const ML = this.showY ? 56 : 12;\n const plotW = Math.max(this._width - ML - MR, 1);\n const plotH = Math.max(H - MT - MB, 1);\n const bottomY = MT + plotH;\n\n const prices = points.map((p) => p.price!);\n const dates = points.map((p) => new Date(p.date!));\n const minPrice = min(prices)!;\n const maxPrice = max(prices)!;\n const pad = (maxPrice - minPrice) * 0.05 || maxPrice * 0.05;\n\n const xScale = scaleTime()\n .domain([min(dates)!, max(dates)!])\n .range([ML, ML + plotW]);\n\n const yScale = scaleLinear()\n .domain([minPrice - pad, maxPrice + pad])\n .range([bottomY, MT]);\n\n const lineFn = line<PricePoint>()\n .x((p) => xScale(new Date(p.date!)))\n .y((p) => yScale(p.price!));\n\n const areaFn = area<PricePoint>()\n .x((p) => xScale(new Date(p.date!)))\n .y0(bottomY)\n .y1((p) => yScale(p.price!));\n\n const plotted: PlottedPoint[] = points.map((p) => ({\n ...p,\n x: xScale(new Date(p.date!)),\n y: yScale(p.price!),\n }));\n\n return { points, plotted, H, MT, ML, MR, plotW, plotH, bottomY, minPrice, maxPrice, xScale, yScale, lineFn, areaFn };\n }\n\n override render() {\n const layout = this.computeLayout();\n if (!layout) return html``;\n\n const { points, plotted, H, MT, ML, MR, plotW, plotH, xScale, yScale, lineFn, areaFn } = layout;\n\n const lineD = lineFn(points) ?? \"\";\n const areaD = areaFn(points) ?? \"\";\n\n const hIdx = this._hoveredIdx;\n const hovPt = hIdx !== null ? plotted[hIdx] : null;\n\n const tooltipEl = hovPt ? (() => {\n const tipText = `${fmtMoney(hovPt.price!)} · ${hovPt.date ? new Date(hovPt.date).toLocaleDateString(\"en-US\", { month: \"short\", year: \"numeric\" }) : \"\"} · ${hovPt.eventType}`;\n const tipW = Math.min(tipText.length * 6.5 + 16, this._width - ML - MR);\n const tipH = 24;\n const tipGap = 8;\n const above = hovPt.y > plotH / 2 + MT;\n const tipRectY = above ? hovPt.y - tipGap - tipH : hovPt.y + tipGap;\n const tipX = Math.min(Math.max(hovPt.x - tipW / 2, ML), this._width - MR - tipW);\n return svg`\n <g pointer-events=\"none\">\n <rect class=\"tooltip\" x=\"${tipX}\" y=\"${tipRectY}\" width=\"${tipW}\" height=\"${tipH}\"\n rx=\"4\" />\n <text class=\"tooltip-label\" x=\"${tipX + tipW / 2}\" y=\"${tipRectY + 15.5}\"\n text-anchor=\"middle\" font-size=\"11\">${tipText}</text>\n </g>\n `;\n })() : \"\";\n\n const xTicks = xScale.ticks(this.maxXLabels).map((d, i, arr) => {\n const x = xScale(d);\n const label = d.toLocaleDateString(\"en-US\", { month: \"short\", year: \"numeric\" });\n const anchor = i === 0 ? \"start\" : i === arr.length - 1 ? \"end\" : \"middle\";\n return svg`\n <text class=\"axis-label\" x=\"${x}\" y=\"${H - 6}\" text-anchor=\"${anchor}\" font-size=\"10\">${label}</text>\n `;\n });\n\n const yTicks = this.showY ? yScale.ticks(3).map((v, i, arr) => {\n const y = yScale(v);\n const isFirst = i === 0;\n const isLast = i === arr.length - 1;\n return svg`\n <line class=\"grid-line\" x1=\"${ML - 4}\" y1=\"${y}\" x2=\"${ML + plotW}\" y2=\"${y}\"\n stroke-width=\"1\" />\n ${isFirst || isLast ? svg`\n <text class=\"axis-label\" x=\"${ML - 6}\" y=\"${y + 4}\" text-anchor=\"end\" font-size=\"10\">${fmtMoneyShort(v)}</text>\n ` : \"\"}\n `;\n }) : [];\n\n const firstPoint = points[0];\n const lastPoint = points[points.length - 1];\n const formatDate = (date: string | null) =>\n new Date(date!).toLocaleDateString(\"en-US\", { month: \"short\", year: \"numeric\" });\n const summary =\n `${points.length} price points from ${formatDate(firstPoint.date)} at ${fmtMoney(firstPoint.price!)} ` +\n `to ${formatDate(lastPoint.date)} at ${fmtMoney(lastPoint.price!)}.`;\n\n return html`\n <div class=\"wrap\">\n <svg width=\"${this._width}\" height=\"${H}\" role=\"img\" aria-label=${summary}>\n <defs>\n <linearGradient id=\"phg\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop class=\"series-gradient\" offset=\"0%\" stop-opacity=\"0.25\" />\n <stop class=\"series-gradient\" offset=\"100%\" stop-opacity=\"0\" />\n </linearGradient>\n </defs>\n ${yTicks}\n <path d=\"${areaD}\" fill=\"url(#phg)\" />\n <path class=\"series-line\" d=\"${lineD}\" fill=\"none\" stroke-width=\"2.5\"\n stroke-linejoin=\"round\" stroke-linecap=\"round\" />\n ${plotted.map((p, i) => svg`\n <circle class=\"series-point\" cx=\"${p.x}\" cy=\"${p.y}\" r=\"${i === hIdx ? 7 : 5}\"\n stroke-width=\"2\"\n @pointerenter=${() => { this._hoveredIdx = i; }}\n @pointerleave=${() => { this._hoveredIdx = null; }} />\n `)}\n ${xTicks}\n ${tooltipEl}\n </svg>\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"price-history-chart\": PriceHistoryChart;\n }\n}\n"]}
|
package/dist/radio-cards.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export declare class RadioCards extends LitElement {
|
|
|
21
21
|
options: RadioCardOption[];
|
|
22
22
|
/** Currently selected value. */
|
|
23
23
|
value: string;
|
|
24
|
+
/** Disables every native radio in the group. */
|
|
25
|
+
disabled: boolean;
|
|
24
26
|
private _onChange;
|
|
25
27
|
render(): import("lit-html").TemplateResult<1>;
|
|
26
28
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"radio-cards.d.ts","sourceRoot":"","sources":["../src/radio-cards.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAKrD,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAID;;;;;;;;;GASG;AACH,qBACa,UAAW,SAAQ,UAAU;;IACxC,OAAgB,MAAM,
|
|
1
|
+
{"version":3,"file":"radio-cards.d.ts","sourceRoot":"","sources":["../src/radio-cards.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAKrD,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAID;;;;;;;;;GASG;AACH,qBACa,UAAW,SAAQ,UAAU;;IACxC,OAAgB,MAAM,4BAiFpB;IAEF,wCAAwC;IACR,OAAO,EAAE,eAAe,EAAE,CAAM;IAChE,gCAAgC;IACpB,KAAK,SAAM;IACvB,gDAAgD;IACnB,QAAQ,UAAS;IAI9C,OAAO,CAAC,SAAS;IAMR,MAAM,yCAwBd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,aAAa,EAAE,UAAU,CAAC;KAC3B;CACF"}
|
package/dist/radio-cards.js
CHANGED
|
@@ -26,6 +26,8 @@ let RadioCards = class RadioCards extends LitElement {
|
|
|
26
26
|
this.options = [];
|
|
27
27
|
/** Currently selected value. */
|
|
28
28
|
this.value = "";
|
|
29
|
+
/** Disables every native radio in the group. */
|
|
30
|
+
this.disabled = false;
|
|
29
31
|
this.#name = `radio-cards-${++instanceCount}`;
|
|
30
32
|
}
|
|
31
33
|
static { this.styles = [
|
|
@@ -36,19 +38,28 @@ let RadioCards = class RadioCards extends LitElement {
|
|
|
36
38
|
}
|
|
37
39
|
.options {
|
|
38
40
|
display: flex;
|
|
39
|
-
gap: 0.
|
|
41
|
+
gap: 0.5rem;
|
|
40
42
|
flex-wrap: wrap;
|
|
41
43
|
}
|
|
42
44
|
.card {
|
|
43
|
-
flex: 1 1
|
|
45
|
+
flex: 1 1 11.25rem;
|
|
44
46
|
display: flex;
|
|
45
47
|
align-items: flex-start;
|
|
46
48
|
gap: 0.5rem;
|
|
47
49
|
border: 1px solid var(--ui-border, #e2e8f0);
|
|
48
50
|
border-radius: var(--ui-radius-sm, 0.25rem);
|
|
49
|
-
padding: 0.
|
|
51
|
+
padding: 0.5rem 0.75rem;
|
|
50
52
|
cursor: pointer;
|
|
51
|
-
font-family: var(
|
|
53
|
+
font-family: var(
|
|
54
|
+
--ui-font,
|
|
55
|
+
ui-sans-serif,
|
|
56
|
+
system-ui,
|
|
57
|
+
sans-serif,
|
|
58
|
+
"Apple Color Emoji",
|
|
59
|
+
"Segoe UI Emoji",
|
|
60
|
+
"Segoe UI Symbol",
|
|
61
|
+
"Noto Color Emoji"
|
|
62
|
+
);
|
|
52
63
|
font-size: var(--ui-font-size-sm, 0.75rem);
|
|
53
64
|
}
|
|
54
65
|
.card:has(input:checked) {
|
|
@@ -57,7 +68,7 @@ let RadioCards = class RadioCards extends LitElement {
|
|
|
57
68
|
}
|
|
58
69
|
.card input {
|
|
59
70
|
width: auto;
|
|
60
|
-
margin-top: 0.
|
|
71
|
+
margin-top: 0.25rem;
|
|
61
72
|
accent-color: var(--ui-primary, #4f46e5);
|
|
62
73
|
cursor: pointer;
|
|
63
74
|
}
|
|
@@ -70,10 +81,41 @@ let RadioCards = class RadioCards extends LitElement {
|
|
|
70
81
|
color: var(--ui-text-muted, #64748b);
|
|
71
82
|
font-weight: 400;
|
|
72
83
|
}
|
|
84
|
+
.card:has(input:focus-visible) {
|
|
85
|
+
outline: none;
|
|
86
|
+
box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
|
|
87
|
+
}
|
|
88
|
+
.card:has(input:disabled) {
|
|
89
|
+
cursor: not-allowed;
|
|
90
|
+
opacity: 0.6;
|
|
91
|
+
}
|
|
92
|
+
.card:has(input:disabled) input {
|
|
93
|
+
cursor: not-allowed;
|
|
94
|
+
}
|
|
95
|
+
@media (forced-colors: active) {
|
|
96
|
+
.card:has(input:focus-visible) {
|
|
97
|
+
outline: 2px solid CanvasText;
|
|
98
|
+
outline-offset: 2px;
|
|
99
|
+
box-shadow: none;
|
|
100
|
+
}
|
|
101
|
+
.card:has(input:checked) {
|
|
102
|
+
border-color: Highlight;
|
|
103
|
+
}
|
|
104
|
+
.card:has(input:disabled) {
|
|
105
|
+
color: GrayText;
|
|
106
|
+
opacity: 1;
|
|
107
|
+
}
|
|
108
|
+
.card:has(input:disabled) .card-label,
|
|
109
|
+
.card:has(input:disabled) .card-description {
|
|
110
|
+
color: GrayText;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
73
113
|
`,
|
|
74
114
|
]; }
|
|
75
115
|
#name;
|
|
76
116
|
_onChange(value) {
|
|
117
|
+
if (this.disabled)
|
|
118
|
+
return;
|
|
77
119
|
this.value = value;
|
|
78
120
|
this.dispatchEvent(new CustomEvent("change", { detail: { value }, bubbles: true, composed: true }));
|
|
79
121
|
}
|
|
@@ -86,6 +128,7 @@ let RadioCards = class RadioCards extends LitElement {
|
|
|
86
128
|
type="radio"
|
|
87
129
|
name=${this.#name}
|
|
88
130
|
?checked=${this.value === opt.value}
|
|
131
|
+
?disabled=${this.disabled}
|
|
89
132
|
@change=${() => this._onChange(opt.value)}
|
|
90
133
|
/>
|
|
91
134
|
<span>
|
|
@@ -104,6 +147,9 @@ __decorate([
|
|
|
104
147
|
__decorate([
|
|
105
148
|
property()
|
|
106
149
|
], RadioCards.prototype, "value", void 0);
|
|
150
|
+
__decorate([
|
|
151
|
+
property({ type: Boolean })
|
|
152
|
+
], RadioCards.prototype, "disabled", void 0);
|
|
107
153
|
RadioCards = __decorate([
|
|
108
154
|
customElement("radio-cards")
|
|
109
155
|
], RadioCards);
|