@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.
Files changed (193) hide show
  1. package/README.md +76 -47
  2. package/custom-elements.json +3049 -910
  3. package/dist/address-autocomplete.d.ts +8 -0
  4. package/dist/address-autocomplete.d.ts.map +1 -1
  5. package/dist/address-autocomplete.js +165 -15
  6. package/dist/address-autocomplete.js.map +1 -1
  7. package/dist/animate-confetti.d.ts +8 -5
  8. package/dist/animate-confetti.d.ts.map +1 -1
  9. package/dist/animate-confetti.js +46 -19
  10. package/dist/animate-confetti.js.map +1 -1
  11. package/dist/autocomplete-input.d.ts +8 -0
  12. package/dist/autocomplete-input.d.ts.map +1 -1
  13. package/dist/autocomplete-input.js +165 -15
  14. package/dist/autocomplete-input.js.map +1 -1
  15. package/dist/calendar-entry.d.ts +33 -0
  16. package/dist/calendar-entry.d.ts.map +1 -0
  17. package/dist/calendar-entry.js +64 -0
  18. package/dist/calendar-entry.js.map +1 -0
  19. package/dist/calendar-month.d.ts +67 -0
  20. package/dist/calendar-month.d.ts.map +1 -0
  21. package/dist/calendar-month.js +469 -0
  22. package/dist/calendar-month.js.map +1 -0
  23. package/dist/calendar-year.d.ts +43 -0
  24. package/dist/calendar-year.d.ts.map +1 -0
  25. package/dist/calendar-year.js +145 -0
  26. package/dist/calendar-year.js.map +1 -0
  27. package/dist/chat-message.d.ts.map +1 -1
  28. package/dist/chat-message.js +77 -24
  29. package/dist/chat-message.js.map +1 -1
  30. package/dist/confirm-dialog.d.ts +8 -1
  31. package/dist/confirm-dialog.d.ts.map +1 -1
  32. package/dist/confirm-dialog.js +130 -18
  33. package/dist/confirm-dialog.js.map +1 -1
  34. package/dist/copy-link-button.d.ts +2 -0
  35. package/dist/copy-link-button.d.ts.map +1 -1
  36. package/dist/copy-link-button.js +35 -3
  37. package/dist/copy-link-button.js.map +1 -1
  38. package/dist/data-table.d.ts +2 -0
  39. package/dist/data-table.d.ts.map +1 -1
  40. package/dist/data-table.js +83 -11
  41. package/dist/data-table.js.map +1 -1
  42. package/dist/distance-value.d.ts +0 -1
  43. package/dist/distance-value.d.ts.map +1 -1
  44. package/dist/distance-value.js +3 -8
  45. package/dist/distance-value.js.map +1 -1
  46. package/dist/distribution-chart.d.ts.map +1 -1
  47. package/dist/distribution-chart.js +54 -17
  48. package/dist/distribution-chart.js.map +1 -1
  49. package/dist/dropdown-button.d.ts.map +1 -1
  50. package/dist/dropdown-button.js +60 -11
  51. package/dist/dropdown-button.js.map +1 -1
  52. package/dist/editable-text.d.ts.map +1 -1
  53. package/dist/editable-text.js +46 -13
  54. package/dist/editable-text.js.map +1 -1
  55. package/dist/form-select.d.ts +21 -2
  56. package/dist/form-select.d.ts.map +1 -1
  57. package/dist/form-select.js +489 -37
  58. package/dist/form-select.js.map +1 -1
  59. package/dist/frame-box.d.ts.map +1 -1
  60. package/dist/frame-box.js +20 -12
  61. package/dist/frame-box.js.map +1 -1
  62. package/dist/icon-button.d.ts.map +1 -1
  63. package/dist/icon-button.js +11 -0
  64. package/dist/icon-button.js.map +1 -1
  65. package/dist/index.d.ts +4 -1
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +4 -1
  68. package/dist/index.js.map +1 -1
  69. package/dist/kbd-hint.d.ts +30 -0
  70. package/dist/kbd-hint.d.ts.map +1 -0
  71. package/dist/kbd-hint.js +184 -0
  72. package/dist/kbd-hint.js.map +1 -0
  73. package/dist/live-timer.d.ts +0 -1
  74. package/dist/live-timer.d.ts.map +1 -1
  75. package/dist/live-timer.js +1 -6
  76. package/dist/live-timer.js.map +1 -1
  77. package/dist/map-circle.d.ts +3 -0
  78. package/dist/map-circle.d.ts.map +1 -1
  79. package/dist/map-circle.js +19 -1
  80. package/dist/map-circle.js.map +1 -1
  81. package/dist/map-pin.d.ts.map +1 -1
  82. package/dist/map-pin.js +16 -1
  83. package/dist/map-pin.js.map +1 -1
  84. package/dist/percent-bar-chart.d.ts +3 -0
  85. package/dist/percent-bar-chart.d.ts.map +1 -1
  86. package/dist/percent-bar-chart.js +60 -4
  87. package/dist/percent-bar-chart.js.map +1 -1
  88. package/dist/photo-gallery.d.ts.map +1 -1
  89. package/dist/photo-gallery.js +12 -3
  90. package/dist/photo-gallery.js.map +1 -1
  91. package/dist/popover-panel.d.ts.map +1 -1
  92. package/dist/popover-panel.js +132 -23
  93. package/dist/popover-panel.js.map +1 -1
  94. package/dist/price-history-chart.d.ts.map +1 -1
  95. package/dist/price-history-chart.js +52 -18
  96. package/dist/price-history-chart.js.map +1 -1
  97. package/dist/radio-cards.d.ts +2 -0
  98. package/dist/radio-cards.d.ts.map +1 -1
  99. package/dist/radio-cards.js +51 -5
  100. package/dist/radio-cards.js.map +1 -1
  101. package/dist/radio-pills.d.ts +2 -0
  102. package/dist/radio-pills.d.ts.map +1 -1
  103. package/dist/radio-pills.js +46 -4
  104. package/dist/radio-pills.js.map +1 -1
  105. package/dist/relative-time.d.ts +0 -1
  106. package/dist/relative-time.d.ts.map +1 -1
  107. package/dist/relative-time.js +1 -6
  108. package/dist/relative-time.js.map +1 -1
  109. package/dist/reveal-button.d.ts +2 -0
  110. package/dist/reveal-button.d.ts.map +1 -1
  111. package/dist/reveal-button.js +42 -5
  112. package/dist/reveal-button.js.map +1 -1
  113. package/dist/roman-numeral.js +1 -1
  114. package/dist/roman-numeral.js.map +1 -1
  115. package/dist/slide-panel.d.ts +7 -3
  116. package/dist/slide-panel.d.ts.map +1 -1
  117. package/dist/slide-panel.js +95 -23
  118. package/dist/slide-panel.js.map +1 -1
  119. package/dist/stat-meter.d.ts.map +1 -1
  120. package/dist/stat-meter.js +17 -7
  121. package/dist/stat-meter.js.map +1 -1
  122. package/dist/status-pill.d.ts.map +1 -1
  123. package/dist/status-pill.js +26 -12
  124. package/dist/status-pill.js.map +1 -1
  125. package/dist/tile-grid.d.ts +8 -0
  126. package/dist/tile-grid.d.ts.map +1 -1
  127. package/dist/tile-grid.js +88 -12
  128. package/dist/tile-grid.js.map +1 -1
  129. package/dist/toast-notification.d.ts.map +1 -1
  130. package/dist/toast-notification.js +40 -11
  131. package/dist/toast-notification.js.map +1 -1
  132. package/dist/tokens.css +23 -0
  133. package/dist/tokens.d.ts.map +1 -1
  134. package/dist/tokens.js +16 -0
  135. package/dist/tokens.js.map +1 -1
  136. package/dist/ui-button.d.ts +2 -0
  137. package/dist/ui-button.d.ts.map +1 -1
  138. package/dist/ui-button.js +58 -9
  139. package/dist/ui-button.js.map +1 -1
  140. package/dist/user-avatar.d.ts.map +1 -1
  141. package/dist/user-avatar.js +18 -3
  142. package/dist/user-avatar.js.map +1 -1
  143. package/dist/utils/calendar.d.ts +77 -0
  144. package/dist/utils/calendar.d.ts.map +1 -0
  145. package/dist/utils/calendar.js +135 -0
  146. package/dist/utils/calendar.js.map +1 -0
  147. package/dist/utils/form.d.ts +3 -0
  148. package/dist/utils/form.d.ts.map +1 -0
  149. package/dist/utils/form.js +14 -0
  150. package/dist/utils/form.js.map +1 -0
  151. package/dist/utils/layer-stack.d.ts +9 -0
  152. package/dist/utils/layer-stack.d.ts.map +1 -0
  153. package/dist/utils/layer-stack.js +35 -0
  154. package/dist/utils/layer-stack.js.map +1 -0
  155. package/dist/weight-bar-chart.d.ts.map +1 -1
  156. package/dist/weight-bar-chart.js +27 -8
  157. package/dist/weight-bar-chart.js.map +1 -1
  158. package/docs/address-autocomplete.md +5 -0
  159. package/docs/autocomplete-input.md +5 -0
  160. package/docs/calendar-entry.md +53 -0
  161. package/docs/calendar-month.md +79 -0
  162. package/docs/calendar-year.md +53 -0
  163. package/docs/chat-message.md +4 -0
  164. package/docs/confirm-dialog.md +2 -0
  165. package/docs/copy-link-button.md +2 -0
  166. package/docs/data-table.md +2 -0
  167. package/docs/design-language.md +149 -0
  168. package/docs/distribution-chart.md +4 -0
  169. package/docs/dropdown-button.md +1 -0
  170. package/docs/form-select.md +15 -3
  171. package/docs/frame-box.md +2 -0
  172. package/docs/kbd-hint.md +42 -0
  173. package/docs/map-circle.md +4 -0
  174. package/docs/percent-bar-chart.md +4 -1
  175. package/docs/photo-gallery.md +1 -0
  176. package/docs/popover-panel.md +2 -0
  177. package/docs/price-history-chart.md +8 -1
  178. package/docs/radio-cards.md +2 -0
  179. package/docs/radio-pills.md +2 -0
  180. package/docs/reveal-button.md +3 -0
  181. package/docs/slide-panel.md +4 -2
  182. package/docs/status-pill.md +3 -0
  183. package/docs/tile-grid.md +10 -1
  184. package/docs/toast-notification.md +4 -1
  185. package/docs/ui-button.md +2 -0
  186. package/docs/user-avatar.md +1 -0
  187. package/llms.txt +175 -61
  188. package/package.json +14 -2
  189. package/dist/map-point.d.ts +0 -31
  190. package/dist/map-point.d.ts.map +0 -1
  191. package/dist/map-point.js +0 -83
  192. package/dist/map-point.js.map +0 -1
  193. package/docs/map-point.md +0 -44
@@ -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 (e.key === "Escape")
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(0 0 0 / 0.35);
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: 400px;
108
+ width: 25rem;
77
109
  }
78
110
  .panel {
79
- width: 320px;
111
+ width: 20rem;
80
112
  max-width: calc(100vw - 2rem);
81
- background: var(--ui-surface, #fff);
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 0.625rem;
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(--ui-font, ui-sans-serif, system-ui, sans-serif);
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 (!changed.has("open"))
139
- return;
140
- if (this.open) {
141
- window.addEventListener("mousedown", this.#onWindowMousedown, true);
142
- window.addEventListener("keydown", this.#onWindowKeydown);
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
- window.removeEventListener("mousedown", this.#onWindowMousedown, true);
146
- window.removeEventListener("keydown", this.#onWindowKeydown);
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 class="panel">
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
- <slot name="title">
159
- ${this.heading ? html `<span class="panel-title">${this.heading}</span>` : null}
160
- </slot>
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
- ${iconX(18)}
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":"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","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,4BAgBpB;IAEF,OAAO,CAAC,aAAa;IA8CZ,MAAM,yCAgFd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,qBAAqB,EAAE,iBAAiB,CAAC;KAC1C;CACF"}
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" fill="rgba(15,23,42,0.88)" />
143
- <text x="${tipX + tipW / 2}" y="${tipRectY + 15.5}"
144
- text-anchor="middle" fill="white" font-size="11"
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" fill="#64748b"
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="#e2e8f0" stroke-width="1" />
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" fill="#64748b"
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 offset="0%" stop-color="#4f46e5" stop-opacity="0.25" />
177
- <stop offset="100%" stop-color="#4f46e5" stop-opacity="0" />
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="#4f46e5" stroke-width="2.5"
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
- fill="#4f46e5" stroke="#fff" stroke-width="2"
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"]}
@@ -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,4BA2CpB;IAEF,wCAAwC;IACR,OAAO,EAAE,eAAe,EAAE,CAAM;IAChE,gCAAgC;IACpB,KAAK,SAAM;IAIvB,OAAO,CAAC,SAAS;IAKR,MAAM,yCAuBd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,aAAa,EAAE,UAAU,CAAC;KAC3B;CACF"}
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"}
@@ -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.6rem;
41
+ gap: 0.5rem;
40
42
  flex-wrap: wrap;
41
43
  }
42
44
  .card {
43
- flex: 1 1 180px;
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.6rem 0.7rem;
51
+ padding: 0.5rem 0.75rem;
50
52
  cursor: pointer;
51
- font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);
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.15rem;
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);