@f-ewald/components 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +147 -0
  3. package/custom-elements.json +3398 -0
  4. package/dist/address-autocomplete.d.ts +75 -0
  5. package/dist/address-autocomplete.d.ts.map +1 -0
  6. package/dist/address-autocomplete.js +342 -0
  7. package/dist/address-autocomplete.js.map +1 -0
  8. package/dist/animate-confetti.d.ts +53 -0
  9. package/dist/animate-confetti.d.ts.map +1 -0
  10. package/dist/animate-confetti.js +180 -0
  11. package/dist/animate-confetti.js.map +1 -0
  12. package/dist/autocomplete-input.d.ts +73 -0
  13. package/dist/autocomplete-input.d.ts.map +1 -0
  14. package/dist/autocomplete-input.js +309 -0
  15. package/dist/autocomplete-input.js.map +1 -0
  16. package/dist/confirm-dialog.d.ts +36 -0
  17. package/dist/confirm-dialog.d.ts.map +1 -0
  18. package/dist/confirm-dialog.js +191 -0
  19. package/dist/confirm-dialog.js.map +1 -0
  20. package/dist/copy-link-button.d.ts +27 -0
  21. package/dist/copy-link-button.d.ts.map +1 -0
  22. package/dist/copy-link-button.js +84 -0
  23. package/dist/copy-link-button.js.map +1 -0
  24. package/dist/distance-value.d.ts +24 -0
  25. package/dist/distance-value.d.ts.map +1 -0
  26. package/dist/distance-value.js +50 -0
  27. package/dist/distance-value.js.map +1 -0
  28. package/dist/distribution-chart.d.ts +52 -0
  29. package/dist/distribution-chart.d.ts.map +1 -0
  30. package/dist/distribution-chart.js +299 -0
  31. package/dist/distribution-chart.js.map +1 -0
  32. package/dist/icons.d.ts +32 -0
  33. package/dist/icons.d.ts.map +1 -0
  34. package/dist/icons.js +35 -0
  35. package/dist/icons.js.map +1 -0
  36. package/dist/index.d.ts +18 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +18 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/percent-bar-chart.d.ts +29 -0
  41. package/dist/percent-bar-chart.d.ts.map +1 -0
  42. package/dist/percent-bar-chart.js +92 -0
  43. package/dist/percent-bar-chart.js.map +1 -0
  44. package/dist/price-history-chart.d.ts +39 -0
  45. package/dist/price-history-chart.d.ts.map +1 -0
  46. package/dist/price-history-chart.js +217 -0
  47. package/dist/price-history-chart.js.map +1 -0
  48. package/dist/relative-time.d.ts +26 -0
  49. package/dist/relative-time.d.ts.map +1 -0
  50. package/dist/relative-time.js +64 -0
  51. package/dist/relative-time.js.map +1 -0
  52. package/dist/reveal-button.d.ts +19 -0
  53. package/dist/reveal-button.d.ts.map +1 -0
  54. package/dist/reveal-button.js +62 -0
  55. package/dist/reveal-button.js.map +1 -0
  56. package/dist/roman-numeral.d.ts +18 -0
  57. package/dist/roman-numeral.d.ts.map +1 -0
  58. package/dist/roman-numeral.js +54 -0
  59. package/dist/roman-numeral.js.map +1 -0
  60. package/dist/slide-panel.d.ts +27 -0
  61. package/dist/slide-panel.d.ts.map +1 -0
  62. package/dist/slide-panel.js +149 -0
  63. package/dist/slide-panel.js.map +1 -0
  64. package/dist/toast-notification.d.ts +53 -0
  65. package/dist/toast-notification.d.ts.map +1 -0
  66. package/dist/toast-notification.js +173 -0
  67. package/dist/toast-notification.js.map +1 -0
  68. package/dist/tokens.css +21 -0
  69. package/dist/tokens.d.ts +16 -0
  70. package/dist/tokens.d.ts.map +1 -0
  71. package/dist/tokens.js +79 -0
  72. package/dist/tokens.js.map +1 -0
  73. package/dist/utils/distance.d.ts +11 -0
  74. package/dist/utils/distance.d.ts.map +1 -0
  75. package/dist/utils/distance.js +25 -0
  76. package/dist/utils/distance.js.map +1 -0
  77. package/dist/utils/time.d.ts +10 -0
  78. package/dist/utils/time.d.ts.map +1 -0
  79. package/dist/utils/time.js +33 -0
  80. package/dist/utils/time.js.map +1 -0
  81. package/dist/weight-bar-chart.d.ts +29 -0
  82. package/dist/weight-bar-chart.d.ts.map +1 -0
  83. package/dist/weight-bar-chart.js +96 -0
  84. package/dist/weight-bar-chart.js.map +1 -0
  85. package/docs/address-autocomplete.md +68 -0
  86. package/docs/animate-confetti.md +34 -0
  87. package/docs/autocomplete-input.md +79 -0
  88. package/docs/confirm-dialog.md +65 -0
  89. package/docs/copy-link-button.md +46 -0
  90. package/docs/distance-value.md +38 -0
  91. package/docs/distribution-chart.md +51 -0
  92. package/docs/mcp-evaluation.md +31 -0
  93. package/docs/percent-bar-chart.md +40 -0
  94. package/docs/price-history-chart.md +44 -0
  95. package/docs/relative-time.md +37 -0
  96. package/docs/reveal-button.md +43 -0
  97. package/docs/roman-numeral.md +33 -0
  98. package/docs/slide-panel.md +56 -0
  99. package/docs/toast-notification.md +52 -0
  100. package/docs/weight-bar-chart.md +53 -0
  101. package/llms.txt +361 -0
  102. package/package.json +69 -0
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Formats a distance in miles, switching to feet below 0.25 mi.
3
+ * At ≥10 mi precision drops to whole miles.
4
+ */
5
+ export function formatMiles(miles) {
6
+ if (miles < 0.25) {
7
+ return `${Math.round(miles * 5280).toLocaleString("en-US")} ft`;
8
+ }
9
+ if (miles >= 10)
10
+ return `${Math.round(miles).toLocaleString("en-US")} mi`;
11
+ return `${miles.toFixed(1)} mi`;
12
+ }
13
+ /**
14
+ * Formats a distance in kilometers, switching to meters below 0.5 km.
15
+ * At ≥10 km precision drops to whole kilometers. (Future use.)
16
+ */
17
+ export function formatKm(km) {
18
+ if (km < 0.5) {
19
+ return `${Math.round(km * 1000).toLocaleString("en-US")} m`;
20
+ }
21
+ if (km >= 10)
22
+ return `${Math.round(km).toLocaleString("en-US")} km`;
23
+ return `${km.toFixed(1)} km`;
24
+ }
25
+ //# sourceMappingURL=distance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"distance.js","sourceRoot":"","sources":["../../src/utils/distance.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,IAAI,KAAK,GAAG,IAAI,EAAE,CAAC;QACjB,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC;IAClE,CAAC;IACD,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC;IAC1E,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;AAClC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,EAAU;IACjC,IAAI,EAAE,GAAG,GAAG,EAAE,CAAC;QACb,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC;IAC9D,CAAC;IACD,IAAI,EAAE,IAAI,EAAE;QAAE,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC;IACpE,OAAO,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;AAC/B,CAAC","sourcesContent":["/**\n * Formats a distance in miles, switching to feet below 0.25 mi.\n * At ≥10 mi precision drops to whole miles.\n */\nexport function formatMiles(miles: number): string {\n if (miles < 0.25) {\n return `${Math.round(miles * 5280).toLocaleString(\"en-US\")} ft`;\n }\n if (miles >= 10) return `${Math.round(miles).toLocaleString(\"en-US\")} mi`;\n return `${miles.toFixed(1)} mi`;\n}\n\n/**\n * Formats a distance in kilometers, switching to meters below 0.5 km.\n * At ≥10 km precision drops to whole kilometers. (Future use.)\n */\nexport function formatKm(km: number): string {\n if (km < 0.5) {\n return `${Math.round(km * 1000).toLocaleString(\"en-US\")} m`;\n }\n if (km >= 10) return `${Math.round(km).toLocaleString(\"en-US\")} km`;\n return `${km.toFixed(1)} km`;\n}\n"]}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Parses a timestamp from either SQLite's `datetime('now')` (a space-separated
3
+ * UTC string with no zone marker, e.g. "2026-07-15 10:23:00") or a standard
4
+ * ISO 8601 string. JS's `Date` parser treats the space-separated form as
5
+ * local time, so it's coerced to `T...Z` first to anchor it to UTC.
6
+ */
7
+ export declare function parseTimestamp(s: string): Date;
8
+ /** Formats `date` relative to `now` (e.g. "3 hours ago", "in 2 days"). */
9
+ export declare function formatRelativeTime(date: Date, now?: number): string;
10
+ //# sourceMappingURL=time.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time.d.ts","sourceRoot":"","sources":["../../src/utils/time.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAG9C;AAaD,0EAA0E;AAC1E,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,GAAE,MAAmB,GAAG,MAAM,CAU/E"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Parses a timestamp from either SQLite's `datetime('now')` (a space-separated
3
+ * UTC string with no zone marker, e.g. "2026-07-15 10:23:00") or a standard
4
+ * ISO 8601 string. JS's `Date` parser treats the space-separated form as
5
+ * local time, so it's coerced to `T...Z` first to anchor it to UTC.
6
+ */
7
+ export function parseTimestamp(s) {
8
+ const iso = s.includes(" ") && !s.includes("T") ? `${s.replace(" ", "T")}Z` : s;
9
+ return new Date(iso);
10
+ }
11
+ const RELATIVE_UNITS = [
12
+ ["year", 365 * 24 * 60 * 60],
13
+ ["month", 30 * 24 * 60 * 60],
14
+ ["week", 7 * 24 * 60 * 60],
15
+ ["day", 24 * 60 * 60],
16
+ ["hour", 60 * 60],
17
+ ["minute", 60],
18
+ ];
19
+ const relativeFormatter = new Intl.RelativeTimeFormat(undefined, { numeric: "auto" });
20
+ /** Formats `date` relative to `now` (e.g. "3 hours ago", "in 2 days"). */
21
+ export function formatRelativeTime(date, now = Date.now()) {
22
+ const diffSeconds = (date.getTime() - now) / 1000;
23
+ const absSeconds = Math.abs(diffSeconds);
24
+ if (absSeconds < 60)
25
+ return "just now";
26
+ for (const [unit, secondsInUnit] of RELATIVE_UNITS) {
27
+ if (absSeconds >= secondsInUnit) {
28
+ return relativeFormatter.format(Math.round(diffSeconds / secondsInUnit), unit);
29
+ }
30
+ }
31
+ return relativeFormatter.format(Math.round(diffSeconds / 60), "minute");
32
+ }
33
+ //# sourceMappingURL=time.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time.js","sourceRoot":"","sources":["../../src/utils/time.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,CAAS;IACtC,MAAM,GAAG,GAAG,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAChF,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;AACvB,CAAC;AAED,MAAM,cAAc,GAAiD;IACnE,CAAC,MAAM,EAAE,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAC5B,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAC5B,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAC1B,CAAC,KAAK,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACrB,CAAC,MAAM,EAAE,EAAE,GAAG,EAAE,CAAC;IACjB,CAAC,QAAQ,EAAE,EAAE,CAAC;CACf,CAAC;AAEF,MAAM,iBAAiB,GAAG,IAAI,IAAI,CAAC,kBAAkB,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;AAEtF,0EAA0E;AAC1E,MAAM,UAAU,kBAAkB,CAAC,IAAU,EAAE,GAAG,GAAW,IAAI,CAAC,GAAG,EAAE;IACrE,MAAM,WAAW,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC;IAClD,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IACzC,IAAI,UAAU,GAAG,EAAE;QAAE,OAAO,UAAU,CAAC;IACvC,KAAK,MAAM,CAAC,IAAI,EAAE,aAAa,CAAC,IAAI,cAAc,EAAE,CAAC;QACnD,IAAI,UAAU,IAAI,aAAa,EAAE,CAAC;YAChC,OAAO,iBAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,aAAa,CAAC,EAAE,IAAI,CAAC,CAAC;QACjF,CAAC;IACH,CAAC;IACD,OAAO,iBAAiB,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,EAAE,CAAC,EAAE,QAAQ,CAAC,CAAC;AAC1E,CAAC","sourcesContent":["/**\n * Parses a timestamp from either SQLite's `datetime('now')` (a space-separated\n * UTC string with no zone marker, e.g. \"2026-07-15 10:23:00\") or a standard\n * ISO 8601 string. JS's `Date` parser treats the space-separated form as\n * local time, so it's coerced to `T...Z` first to anchor it to UTC.\n */\nexport function parseTimestamp(s: string): Date {\n const iso = s.includes(\" \") && !s.includes(\"T\") ? `${s.replace(\" \", \"T\")}Z` : s;\n return new Date(iso);\n}\n\nconst RELATIVE_UNITS: Array<[Intl.RelativeTimeFormatUnit, number]> = [\n [\"year\", 365 * 24 * 60 * 60],\n [\"month\", 30 * 24 * 60 * 60],\n [\"week\", 7 * 24 * 60 * 60],\n [\"day\", 24 * 60 * 60],\n [\"hour\", 60 * 60],\n [\"minute\", 60],\n];\n\nconst relativeFormatter = new Intl.RelativeTimeFormat(undefined, { numeric: \"auto\" });\n\n/** Formats `date` relative to `now` (e.g. \"3 hours ago\", \"in 2 days\"). */\nexport function formatRelativeTime(date: Date, now: number = Date.now()): string {\n const diffSeconds = (date.getTime() - now) / 1000;\n const absSeconds = Math.abs(diffSeconds);\n if (absSeconds < 60) return \"just now\";\n for (const [unit, secondsInUnit] of RELATIVE_UNITS) {\n if (absSeconds >= secondsInUnit) {\n return relativeFormatter.format(Math.round(diffSeconds / secondsInUnit), unit);\n }\n }\n return relativeFormatter.format(Math.round(diffSeconds / 60), \"minute\");\n}\n"]}
@@ -0,0 +1,29 @@
1
+ import { LitElement } from "lit";
2
+ export interface WeightBarItem {
3
+ id: string;
4
+ label: string;
5
+ /** Normalized fraction (fractions across all items sum to ~1). */
6
+ value: number;
7
+ }
8
+ /**
9
+ * Sorted horizontal bar chart of labeled weights (normalized fractions
10
+ * summing to ~1). Bars sort descending — the order IS the priority ranking.
11
+ * Widths scale relative to the largest weight (which fills its track); the
12
+ * percent labels carry the absolute values. Rows are keyed by item id
13
+ * (repeat directive) so a re-render with new weights moves the existing
14
+ * rows; bar widths animate via CSS, re-sorting is instant.
15
+ *
16
+ * @element weight-bar-chart
17
+ */
18
+ export declare class WeightBarChart extends LitElement {
19
+ /** Items to render as weighted rows, sorted descending by value. */
20
+ items: WeightBarItem[];
21
+ static styles: import("lit").CSSResult[];
22
+ render(): import("lit-html").TemplateResult<1> | null;
23
+ }
24
+ declare global {
25
+ interface HTMLElementTagNameMap {
26
+ "weight-bar-chart": WeightBarChart;
27
+ }
28
+ }
29
+ //# sourceMappingURL=weight-bar-chart.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"weight-bar-chart.d.ts","sourceRoot":"","sources":["../src/weight-bar-chart.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAa,MAAM,KAAK,CAAC;AAK5C,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;GASG;AACH,qBACa,cAAe,SAAQ,UAAU;IAC5C,oEAAoE;IACpC,KAAK,EAAE,aAAa,EAAE,CAAM;IAE5D,OAAgB,MAAM,4BAuCpB;IAEO,MAAM,gDAuBd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,kBAAkB,EAAE,cAAc,CAAC;KACpC;CACF"}
@@ -0,0 +1,96 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { LitElement, css, html } from "lit";
8
+ import { customElement, property } from "lit/decorators.js";
9
+ import { repeat } from "lit/directives/repeat.js";
10
+ import { tokens } from "./tokens.js";
11
+ /**
12
+ * Sorted horizontal bar chart of labeled weights (normalized fractions
13
+ * summing to ~1). Bars sort descending — the order IS the priority ranking.
14
+ * Widths scale relative to the largest weight (which fills its track); the
15
+ * percent labels carry the absolute values. Rows are keyed by item id
16
+ * (repeat directive) so a re-render with new weights moves the existing
17
+ * rows; bar widths animate via CSS, re-sorting is instant.
18
+ *
19
+ * @element weight-bar-chart
20
+ */
21
+ let WeightBarChart = class WeightBarChart extends LitElement {
22
+ constructor() {
23
+ super(...arguments);
24
+ /** Items to render as weighted rows, sorted descending by value. */
25
+ this.items = [];
26
+ }
27
+ static { this.styles = [
28
+ tokens,
29
+ css `
30
+ :host {
31
+ display: block;
32
+ }
33
+ .row {
34
+ display: flex;
35
+ align-items: center;
36
+ gap: 8px;
37
+ margin: 6px 0;
38
+ }
39
+ .label {
40
+ flex: 0 0 90px;
41
+ font-size: var(--ui-font-size-sm, 0.75rem);
42
+ font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);
43
+ color: var(--ui-text, #0f172a);
44
+ }
45
+ .track {
46
+ flex: 1 1 auto;
47
+ height: 12px;
48
+ background: var(--ui-surface-muted, #f8fafc);
49
+ border-radius: var(--ui-radius-sm, 0.25rem);
50
+ overflow: hidden;
51
+ }
52
+ .bar {
53
+ height: 100%;
54
+ background: var(--ui-primary, #4f46e5);
55
+ border-radius: 0 var(--ui-radius-sm, 0.25rem) var(--ui-radius-sm, 0.25rem) 0;
56
+ transition: width 0.15s ease-out;
57
+ }
58
+ .pct {
59
+ flex: 0 0 40px;
60
+ text-align: right;
61
+ font-size: var(--ui-font-size-sm, 0.75rem);
62
+ font-variant-numeric: tabular-nums;
63
+ color: var(--ui-text-muted, #64748b);
64
+ }
65
+ `,
66
+ ]; }
67
+ render() {
68
+ if (this.items.length === 0)
69
+ return null;
70
+ // Stable sort: ties keep the incoming item order, so the chart never flickers.
71
+ const sorted = [...this.items].sort((a, b) => b.value - a.value);
72
+ const max = Math.max(0.01, ...sorted.map((item) => item.value));
73
+ const summary = sorted.map((item) => `${item.label} ${Math.round(item.value * 100)}%`).join(", ");
74
+ return html `
75
+ <div role="img" aria-label="Weights: ${summary}">
76
+ ${repeat(sorted, (item) => item.id, (item) => html `
77
+ <div class="row">
78
+ <span class="label">${item.label}</span>
79
+ <div class="track">
80
+ <div class="bar" style="width: ${(item.value / max) * 100}%"></div>
81
+ </div>
82
+ <span class="pct">${Math.round(item.value * 100)}%</span>
83
+ </div>
84
+ `)}
85
+ </div>
86
+ `;
87
+ }
88
+ };
89
+ __decorate([
90
+ property({ attribute: false })
91
+ ], WeightBarChart.prototype, "items", void 0);
92
+ WeightBarChart = __decorate([
93
+ customElement("weight-bar-chart")
94
+ ], WeightBarChart);
95
+ export { WeightBarChart };
96
+ //# sourceMappingURL=weight-bar-chart.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"weight-bar-chart.js","sourceRoot":"","sources":["../src/weight-bar-chart.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AASrC;;;;;;;;;GASG;AAEI,IAAM,cAAc,GAApB,MAAM,cAAe,SAAQ,UAAU;IAAvC;;QACL,oEAAoE;QACpC,UAAK,GAAoB,EAAE,CAAC;IAmE9D,CAAC;aAjEiB,WAAM,GAAG;QACvB,MAAM;QACN,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAoCF;KACF,AAvCqB,CAuCpB;IAEO,MAAM;QACb,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACzC,+EAA+E;QAC/E,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;QACjE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAChE,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClG,OAAO,IAAI,CAAA;6CAC8B,OAAO;UAC1C,MAAM,CACN,MAAM,EACN,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,EACjB,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAA;;oCAEY,IAAI,CAAC,KAAK;;iDAEG,CAAC,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC,GAAG,GAAG;;kCAEvC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,GAAG,CAAC;;WAEnD,CACF;;KAEJ,CAAC;IACJ,CAAC;CACF,CAAA;AAnEiC;IAA/B,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;6CAA6B;AAFjD,cAAc;IAD1B,aAAa,CAAC,kBAAkB,CAAC;GACrB,cAAc,CAqE1B","sourcesContent":["import { LitElement, css, html } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\nimport { repeat } from \"lit/directives/repeat.js\";\nimport { tokens } from \"./tokens.js\";\n\nexport interface WeightBarItem {\n id: string;\n label: string;\n /** Normalized fraction (fractions across all items sum to ~1). */\n value: number;\n}\n\n/**\n * Sorted horizontal bar chart of labeled weights (normalized fractions\n * summing to ~1). Bars sort descending — the order IS the priority ranking.\n * Widths scale relative to the largest weight (which fills its track); the\n * percent labels carry the absolute values. Rows are keyed by item id\n * (repeat directive) so a re-render with new weights moves the existing\n * rows; bar widths animate via CSS, re-sorting is instant.\n *\n * @element weight-bar-chart\n */\n@customElement(\"weight-bar-chart\")\nexport class WeightBarChart extends LitElement {\n /** Items to render as weighted rows, sorted descending by value. */\n @property({ attribute: false }) items: WeightBarItem[] = [];\n\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n }\n .row {\n display: flex;\n align-items: center;\n gap: 8px;\n margin: 6px 0;\n }\n .label {\n flex: 0 0 90px;\n font-size: var(--ui-font-size-sm, 0.75rem);\n font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);\n color: var(--ui-text, #0f172a);\n }\n .track {\n flex: 1 1 auto;\n height: 12px;\n background: var(--ui-surface-muted, #f8fafc);\n border-radius: var(--ui-radius-sm, 0.25rem);\n overflow: hidden;\n }\n .bar {\n height: 100%;\n background: var(--ui-primary, #4f46e5);\n border-radius: 0 var(--ui-radius-sm, 0.25rem) var(--ui-radius-sm, 0.25rem) 0;\n transition: width 0.15s ease-out;\n }\n .pct {\n flex: 0 0 40px;\n text-align: right;\n font-size: var(--ui-font-size-sm, 0.75rem);\n font-variant-numeric: tabular-nums;\n color: var(--ui-text-muted, #64748b);\n }\n `,\n ];\n\n override render() {\n if (this.items.length === 0) return null;\n // Stable sort: ties keep the incoming item order, so the chart never flickers.\n const sorted = [...this.items].sort((a, b) => b.value - a.value);\n const max = Math.max(0.01, ...sorted.map((item) => item.value));\n const summary = sorted.map((item) => `${item.label} ${Math.round(item.value * 100)}%`).join(\", \");\n return html`\n <div role=\"img\" aria-label=\"Weights: ${summary}\">\n ${repeat(\n sorted,\n (item) => item.id,\n (item) => html`\n <div class=\"row\">\n <span class=\"label\">${item.label}</span>\n <div class=\"track\">\n <div class=\"bar\" style=\"width: ${(item.value / max) * 100}%\"></div>\n </div>\n <span class=\"pct\">${Math.round(item.value * 100)}%</span>\n </div>\n `,\n )}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"weight-bar-chart\": WeightBarChart;\n }\n}\n"]}
@@ -0,0 +1,68 @@
1
+ # `<address-autocomplete>`
2
+
3
+ Form-associated text input with a suggestion dropdown. Works as a
4
+ drop-in replacement for a plain `<input name="address">`: consumers keep
5
+ reading `new FormData(form).get("address")` and calling `form.reset()`
6
+ unchanged.
7
+
8
+ Supports two suggestion sources:
9
+ - **API** (default): fetches from `endpoint`, a Mapbox Geocoding v6-shaped
10
+ forward-geocode URL, debounced as the user types.
11
+ - **Local array**: set `suggestions` to a fixed `AddressSuggestion[]` and
12
+ the component filters it client-side instead of making any network
13
+ request. Useful for small/fixed address lists, offline use, or tests.
14
+ Takes priority over `endpoint` whenever it's non-null.
15
+
16
+ ## Install
17
+
18
+ ```js
19
+ import "@f-ewald/components/address-autocomplete.js";
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ ```html
25
+ <address-autocomplete
26
+ placeholder="Start typing an address…"
27
+ access-token="pk.your-mapbox-token"
28
+ ></address-autocomplete>
29
+ ```
30
+
31
+ ## Attributes / properties
32
+
33
+ | Property | Attribute | Type | Default | Description |
34
+ | --- | --- | --- | --- | --- |
35
+ | `value` | `value` | `string` | `""` | Current input value; also the form-associated value submitted with the form. |
36
+ | `placeholder` | `placeholder` | `string` | `""` | Placeholder text shown when the input is empty. |
37
+ | `required` | `required` | `boolean` | `false` | Marks the input as required for native form validation. |
38
+ | `types` | `types` | `string` | `"address"` | Mapbox `types` param, e.g. "address" or "address,poi". |
39
+ | `endpoint` | `endpoint` | `string` | `"https://api.mapbox.com/search/geocode/v6/forward"` | Geocoding endpoint URL. Defaults to the Mapbox Geocoding v6 forward URL. |
40
+ | `accessToken` | `access-token` | `string` | `""` | Mapbox access token. Required for requests to succeed. |
41
+ | `bbox` | `bbox` | `string` | `""` | Optional bounding box bias, comma-separated `west,south,east,north`. |
42
+ | `proximity` | `proximity` | `string` | `""` | Optional proximity bias, comma-separated `lng,lat`. |
43
+ | `debounce` | `debounce` | `number` | `300` | Debounce delay (ms) before firing a geocode request after typing. |
44
+ | `minLength` | `min-length` | `number` | `3` | Minimum query length before suggestions are fetched. |
45
+ | `suggestions` | _(JS property only)_ | `AddressSuggestion[] | null` | `null` | Fixed candidate list to filter locally instead of fetching from `endpoint`. When set (non-null), no network request is ever made — this takes priority over the API mode. |
46
+ | `selectedSuggestion` | _(JS property only)_ | `AddressSuggestion | null` | `—` | Last-picked suggestion, or null once the input diverges from it. _(read-only)_ |
47
+
48
+ ## Events
49
+
50
+ | Event | Description |
51
+ | --- | --- |
52
+ | `address-select` | A suggestion was picked; detail: AddressSuggestion. |
53
+
54
+ ## Slots
55
+
56
+ _None._
57
+
58
+ ## CSS custom properties
59
+
60
+ | Custom property |
61
+ | --- |
62
+ | `--ui-border` |
63
+ | `--ui-font` |
64
+ | `--ui-font-size-sm` |
65
+ | `--ui-radius-sm` |
66
+ | `--ui-shadow` |
67
+ | `--ui-surface` |
68
+ | `--ui-surface-muted` |
@@ -0,0 +1,34 @@
1
+ # `<animate-confetti>`
2
+
3
+ Fullscreen confetti animation overlay, rendered on a fixed-position canvas.
4
+ Starts automatically on first render and stops after `duration` ms.
5
+
6
+ ## Install
7
+
8
+ ```js
9
+ import "@f-ewald/components/animate-confetti.js";
10
+ ```
11
+
12
+ ## Usage
13
+
14
+ ```html
15
+ <animate-confetti duration="6000"></animate-confetti>
16
+ ```
17
+
18
+ ## Attributes / properties
19
+
20
+ | Property | Attribute | Type | Default | Description |
21
+ | --- | --- | --- | --- | --- |
22
+ | `duration` | `duration` | `number` | `6000` | How long the confetti streams before stopping, in ms. |
23
+
24
+ ## Events
25
+
26
+ _None._
27
+
28
+ ## Slots
29
+
30
+ _None._
31
+
32
+ ## CSS custom properties
33
+
34
+ _None._
@@ -0,0 +1,79 @@
1
+ # `<autocomplete-input>`
2
+
3
+ Generic form-associated text input with a suggestion dropdown, for any
4
+ "type to filter a list of `{key, value}` options" use case. Works as a
5
+ drop-in replacement for a plain `<input>` inside a `<form>`: set `name` on
6
+ the element itself and consumers keep reading
7
+ `new FormData(form).get(name)` and calling `form.reset()` unchanged — the
8
+ submitted value is the picked option's `value`, while `key` is available
9
+ via the `option-select` event and the `selectedOption` getter for cases
10
+ that need the underlying id rather than the display text.
11
+
12
+ Supports two suggestion sources:
13
+ - **API**: set `endpoint` to a URL that, given a `?<query-param>=<text>`
14
+ query string, responds with a JSON array of `{key, value}` objects.
15
+ Requests are debounced as the user types.
16
+ - **Local array**: set `options` to a fixed `AutocompleteOption[]` and the
17
+ component filters it client-side instead of making any network
18
+ request. Useful for small/fixed lists, offline use, or tests. Takes
19
+ priority over `endpoint` whenever it's non-null.
20
+
21
+ ## Install
22
+
23
+ ```js
24
+ import "@f-ewald/components/autocomplete-input.js";
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ```html
30
+ <form>
31
+ <autocomplete-input name="language" placeholder="Start typing a language…"></autocomplete-input>
32
+ </form>
33
+ <script type="module">
34
+ // Local mode: filters client-side, no network request.
35
+ document.querySelector("autocomplete-input").options = [
36
+ { key: "ts", value: "TypeScript" },
37
+ { key: "py", value: "Python" },
38
+ ];
39
+
40
+ // API mode: omit `options` and set `endpoint` instead — it's queried as
41
+ // `${endpoint}?${queryParam}=<text>` and must respond with [{key, value}].
42
+ </script>
43
+ ```
44
+
45
+ ## Attributes / properties
46
+
47
+ | Property | Attribute | Type | Default | Description |
48
+ | --- | --- | --- | --- | --- |
49
+ | `value` | `value` | `string` | `""` | Current input value; also the form-associated value submitted with the form. |
50
+ | `placeholder` | `placeholder` | `string` | `""` | Placeholder text shown when the input is empty. |
51
+ | `required` | `required` | `boolean` | `false` | Marks the input as required for native form validation. |
52
+ | `endpoint` | `endpoint` | `string` | `""` | API endpoint queried in API mode. Ignored when `options` is set. |
53
+ | `queryParam` | `query-param` | `string` | `"q"` | Query string parameter name the current input text is sent under. |
54
+ | `debounce` | `debounce` | `number` | `300` | Debounce delay (ms) before firing a request/filter after typing. |
55
+ | `minLength` | `min-length` | `number` | `3` | Minimum query length before suggestions are shown. |
56
+ | `options` | _(JS property only)_ | `AutocompleteOption[] | null` | `null` | Fixed candidate list to filter locally instead of fetching from `endpoint`. When set (non-null), no network request is ever made — this takes priority over the API mode. |
57
+ | `selectedOption` | _(JS property only)_ | `AutocompleteOption | null` | `—` | Last-picked option, or null once the input diverges from it. _(read-only)_ |
58
+
59
+ ## Events
60
+
61
+ | Event | Description |
62
+ | --- | --- |
63
+ | `option-select` | An option was picked; detail: AutocompleteOption. |
64
+
65
+ ## Slots
66
+
67
+ _None._
68
+
69
+ ## CSS custom properties
70
+
71
+ | Custom property |
72
+ | --- |
73
+ | `--ui-border` |
74
+ | `--ui-font` |
75
+ | `--ui-font-size-sm` |
76
+ | `--ui-radius-sm` |
77
+ | `--ui-shadow` |
78
+ | `--ui-surface` |
79
+ | `--ui-surface-muted` |
@@ -0,0 +1,65 @@
1
+ # `<confirm-dialog>`
2
+
3
+ Reusable confirmation dialog: overlay + centered card with a slotted body,
4
+ an optional error line, and Cancel/Confirm actions. Instant `display:none`
5
+ → `display:flex` toggle (no transitions). Fires `confirm`/`cancel`
6
+ (bubbling, composed) instead of owning any deletion logic itself —
7
+ callers stay in charge of the request.
8
+
9
+ ## Install
10
+
11
+ ```js
12
+ import "@f-ewald/components/confirm-dialog.js";
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```html
18
+ <confirm-dialog open confirm-label="Delete" cancel-label="Cancel">
19
+ Are you sure you want to delete this item?
20
+ </confirm-dialog>
21
+ ```
22
+
23
+ ## Attributes / properties
24
+
25
+ | Property | Attribute | Type | Default | Description |
26
+ | --- | --- | --- | --- | --- |
27
+ | `open` | `open` | `boolean` | `false` | Whether the dialog is visible. |
28
+ | `confirmLabel` | `confirm-label` | `string` | `"Delete"` | Label for the confirm button. |
29
+ | `cancelLabel` | `cancel-label` | `string` | `"Cancel"` | Label for the cancel button. |
30
+ | `danger` | `danger` | `boolean` | `true` | Danger (red) vs. primary (indigo) styling for the confirm button. |
31
+ | `busy` | `busy` | `boolean` | `false` | Shows a spinner and disables both buttons while a request is in flight. |
32
+ | `error` | `error` | `string | null` | `null` | Inline error line shown below the body, or null for none. |
33
+
34
+ ## Events
35
+
36
+ | Event | Description |
37
+ | --- | --- |
38
+ | `confirm` | User clicked the confirm button. |
39
+ | `cancel` | User clicked the cancel button. |
40
+
41
+ ## Slots
42
+
43
+ | Slot | Description |
44
+ | --- | --- |
45
+ | `(default)` | Dialog body content. |
46
+
47
+ ## CSS custom properties
48
+
49
+ | Custom property |
50
+ | --- |
51
+ | `--ui-border` |
52
+ | `--ui-danger` |
53
+ | `--ui-danger-hover` |
54
+ | `--ui-font` |
55
+ | `--ui-font-size` |
56
+ | `--ui-font-size-sm` |
57
+ | `--ui-overlay` |
58
+ | `--ui-primary` |
59
+ | `--ui-primary-hover` |
60
+ | `--ui-radius` |
61
+ | `--ui-radius-sm` |
62
+ | `--ui-shadow-lg` |
63
+ | `--ui-surface` |
64
+ | `--ui-text` |
65
+ | `--ui-text-muted` |
@@ -0,0 +1,46 @@
1
+ # `<copy-link-button>`
2
+
3
+ Small icon button that copies `value` to the clipboard and shows a toast
4
+ on success/failure (if a `<toast-notification>` element is present), and
5
+ always dispatches a `copy-success`/`copy-error` CustomEvent so consumers
6
+ without a toast element can react. Defaults to the current page URL if
7
+ `value` is unset.
8
+
9
+ ## Install
10
+
11
+ ```js
12
+ import "@f-ewald/components/copy-link-button.js";
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```html
18
+ <copy-link-button value="https://example.com/listing/42" label="Copy listing link"></copy-link-button>
19
+ ```
20
+
21
+ ## Attributes / properties
22
+
23
+ | Property | Attribute | Type | Default | Description |
24
+ | --- | --- | --- | --- | --- |
25
+ | `value` | `value` | `string` | `""` | Text to copy. Defaults to `window.location.href` at click time. |
26
+ | `label` | `label` | `string` | `"Copy link"` | Accessible label / tooltip text. |
27
+
28
+ ## Events
29
+
30
+ | Event | Description |
31
+ | --- | --- |
32
+ | `copy-success` | The value was copied to the clipboard. |
33
+ | `copy-error` | Copying to the clipboard failed. |
34
+
35
+ ## Slots
36
+
37
+ _None._
38
+
39
+ ## CSS custom properties
40
+
41
+ | Custom property |
42
+ | --- |
43
+ | `--ui-radius-sm` |
44
+ | `--ui-surface-muted` |
45
+ | `--ui-text` |
46
+ | `--ui-text-muted` |
@@ -0,0 +1,38 @@
1
+ # `<distance-value>`
2
+
3
+ Inline distance display. Renders miles/feet or km/m, switching units at
4
+ sensible thresholds (< 0.25 mi → ft; < 0.5 km → m).
5
+
6
+ Supply exactly one of `miles` or `km`; the other stays null.
7
+ km support is present but reserved for future use.
8
+
9
+ ## Install
10
+
11
+ ```js
12
+ import "@f-ewald/components/distance-value.js";
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```html
18
+ <distance-value miles="5"></distance-value>
19
+ ```
20
+
21
+ ## Attributes / properties
22
+
23
+ | Property | Attribute | Type | Default | Description |
24
+ | --- | --- | --- | --- | --- |
25
+ | `miles` | `miles` | `number | null` | `null` | Distance in miles (imperial). Switches to feet below 0.25 mi. |
26
+ | `km` | `km` | `number | null` | `null` | Distance in kilometers (metric, future). Switches to meters below 0.5 km. |
27
+
28
+ ## Events
29
+
30
+ _None._
31
+
32
+ ## Slots
33
+
34
+ _None._
35
+
36
+ ## CSS custom properties
37
+
38
+ _None._
@@ -0,0 +1,51 @@
1
+ # `<distribution-chart>`
2
+
3
+ Renders a KDE distribution curve for a named metric with one or more value
4
+ markers. The SVG viewBox is kept in sync with the element's pixel width via
5
+ ResizeObserver so that font sizes and stroke widths are always in real pixels
6
+ regardless of container width.
7
+
8
+ Pass `fontSize` to control all text size (default 11). Pass a single
9
+ `{label:'', value}` for a single-value display or multiple
10
+ `{label:'A'|'B'|...}` entries to compare several values.
11
+
12
+ ## Install
13
+
14
+ ```js
15
+ import "@f-ewald/components/distribution-chart.js";
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ```html
21
+ <distribution-chart metric="sqft"></distribution-chart>
22
+ <script type="module">
23
+ document.querySelector("distribution-chart").values = [{ label: "", value: 1450 }];
24
+ </script>
25
+ ```
26
+
27
+ ## Attributes / properties
28
+
29
+ | Property | Attribute | Type | Default | Description |
30
+ | --- | --- | --- | --- | --- |
31
+ | `metric` | `metric` | `string` | `""` | Metric name, fetched from `/api/distribution/<metric>` on change. |
32
+ | `values` | _(JS property only)_ | `DistributionValue[]` | `[]` | One or more values to mark on the distribution curve. |
33
+ | `markerColors` | _(JS property only)_ | `string[]` | `["#4f46e5", "#d97706", "#0d9488", "#e11d48"]` | Colors assigned to markers in order. Defaults to indigo/amber/teal/rose 600s. |
34
+ | `fontSize` | `font-size` | `number` | `11` | Target font size in CSS pixels (default 11). Always renders at this size regardless of container width. |
35
+
36
+ ## Events
37
+
38
+ _None._
39
+
40
+ ## Slots
41
+
42
+ _None._
43
+
44
+ ## CSS custom properties
45
+
46
+ | Custom property |
47
+ | --- |
48
+ | `--ui-danger` |
49
+ | `--ui-font-size-sm` |
50
+ | `--ui-radius-sm` |
51
+ | `--ui-text-muted` |
@@ -0,0 +1,31 @@
1
+ # MCP server evaluation
2
+
3
+ **Recommendation: do not build an MCP server for this package now.**
4
+
5
+ ## Rationale
6
+
7
+ The component catalog is small (14 tags). `llms.txt` ships in the npm
8
+ package and is fetchable directly from unpkg or GitHub raw, so any LLM or
9
+ agent can load the full API surface — every component's attributes,
10
+ properties, events, slots, tokens, and a usage example — in one small file,
11
+ with no server, process, or additional versioning/packaging burden. An MCP
12
+ server would duplicate that same data behind a stdio process that needs its
13
+ own install, auth-free-but-still-a-process lifecycle, and release cadence
14
+ kept in sync with the package — for zero functional gain at this scale.
15
+
16
+ ## When to revisit
17
+
18
+ - The catalog grows past a size where `llms.txt` stops being comfortable to
19
+ load into a single context window (rule of thumb: ~30+ components).
20
+ - Consumers want live search, fuzzy matching, or filtered/paginated results
21
+ instead of "load everything."
22
+ - Consumers want examples or docs that go beyond what's derivable from
23
+ `custom-elements.json` (e.g. runnable playground snippets, live preview).
24
+
25
+ ## Upgrade path
26
+
27
+ If/when it's warranted, a thin stdio MCP server exposing two tools —
28
+ `list_components` and `get_component_docs(tag)` — backed by the same
29
+ `custom-elements.json` this package already generates and ships. No new
30
+ data source would be needed; the server would just be a different transport
31
+ over data that already exists.