@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
@@ -1 +1 @@
1
- {"version":3,"file":"distribution-chart.js","sourceRoot":"","sources":["../src/distribution-chart.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html, nothing, svg } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { scaleLinear } from \"d3-scale\";\nimport { line, area } from \"d3-shape\";\nimport { max } from \"d3-array\";\nimport { tokens } from \"./tokens.js\";\n\ninterface DistributionPoint {\n x: number;\n y: number;\n}\n\ninterface DistributionData {\n points: DistributionPoint[];\n unit: string;\n label: string;\n min: number;\n max: number;\n mean: number;\n}\n\nexport interface DistributionValue {\n /** Short label shown on the chart marker, e.g. 'A'. Empty string = show raw value instead. */\n label: string;\n value: number;\n}\n\n/** Fixed SVG height in CSS pixels — the coordinate system is always 1 user-unit = 1 px. */\nconst SVG_H = 130;\nconst PAD = { top: 22, right: 12, bottom: 18, left: 12 };\n\n/** Default marker colors (indigo/amber/teal/rose 600s), assigned in order. */\nconst DEFAULT_MARKER_COLORS = [\"#4f46e5\", \"#d97706\", \"#0d9488\", \"#e11d48\"];\n\n/**\n * Renders a KDE distribution curve for a named metric with one or more value\n * markers. The SVG viewBox is kept in sync with the element's pixel width via\n * ResizeObserver so that font sizes and stroke widths are always in real pixels\n * regardless of container width.\n *\n * Pass `fontSize` to control all text size (default 11). Pass a single\n * `{label:'', value}` for a single-value display or multiple\n * `{label:'A'|'B'|...}` entries to compare several values.\n *\n * @element distribution-chart\n */\n@customElement(\"distribution-chart\")\nexport class DistributionChart extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n width: 100%;\n }\n .wrap {\n padding: 4px 0 8px;\n }\n svg {\n display: block;\n overflow: visible;\n }\n .curve {\n fill: none;\n stroke: #4f46e5;\n stroke-width: 1.5;\n stroke-linejoin: round;\n }\n .fill {\n fill: url(#dist-fill-grad);\n }\n .skeleton {\n background: linear-gradient(90deg, #f8fafc 25%, #e2e8f0 50%, #f8fafc 75%);\n background-size: 200% 100%;\n animation: shimmer 1.4s infinite;\n border-radius: var(--ui-radius-sm, 0.25rem);\n height: 100px;\n }\n @keyframes shimmer {\n 0% {\n background-position: 200% 0;\n }\n 100% {\n background-position: -200% 0;\n }\n }\n .error {\n font-size: var(--ui-font-size-sm, 0.75rem);\n color: var(--ui-danger, #dc2626);\n padding: 8px 0;\n }\n .legend {\n display: flex;\n flex-wrap: wrap;\n gap: 6px 12px;\n padding: 2px 0 2px 8px;\n }\n .legend-item {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: var(--ui-font-size-sm, 0.75rem);\n color: var(--ui-text-muted, #64748b);\n }\n .legend-dot {\n display: inline-block;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n flex: 0 0 auto;\n }\n `,\n ];\n\n /** Metric name, fetched from `/api/distribution/<metric>` on change. */\n @property() metric = \"\";\n /** One or more values to mark on the distribution curve. */\n @property({ attribute: false }) values: DistributionValue[] = [];\n /** Colors assigned to markers in order. Defaults to indigo/amber/teal/rose 600s. */\n @property({ attribute: false }) markerColors: string[] = DEFAULT_MARKER_COLORS;\n /** Target font size in CSS pixels (default 11). Always renders at this size regardless of container width. */\n @property({ type: Number, attribute: \"font-size\" }) fontSize = 11;\n\n @state() private _data: DistributionData | null = null;\n @state() private _loading = false;\n @state() private _error: string | null = null;\n /** Measured element width in CSS pixels. 0 until ResizeObserver fires. */\n @state() private _width = 0;\n /** Inner x coordinate (px) of the current hover position, or null when not hovering. */\n @state() private _hoverX: number | null = null;\n\n private _fetchedFor = \"\";\n private _ro: ResizeObserver | null = null;\n\n override connectedCallback() {\n super.connectedCallback();\n this._maybeFetch();\n }\n\n override firstUpdated() {\n this._ro = new ResizeObserver((entries) => {\n const w = Math.floor(entries[0].contentRect.width);\n if (w > 0 && w !== this._width) this._width = w;\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 override updated(changed: Map<string, unknown>) {\n if (changed.has(\"metric\")) this._maybeFetch();\n }\n\n private _maybeFetch() {\n if (!this.metric || this._fetchedFor === this.metric) return;\n this._fetchedFor = this.metric;\n this._loading = true;\n this._error = null;\n fetch(`/api/distribution/${encodeURIComponent(this.metric)}`)\n .then((r) => {\n if (!r.ok) throw new Error(`HTTP ${r.status}`);\n return r.json() as Promise<DistributionData>;\n })\n .then((data) => {\n this._data = data;\n this._loading = false;\n })\n .catch((err: unknown) => {\n this._error = err instanceof Error ? err.message : \"Failed to load\";\n this._loading = false;\n });\n }\n\n private _renderSvg(data: DistributionData) {\n if (this._width === 0) return nothing;\n\n const innerW = this._width - PAD.left - PAD.right;\n const innerH = SVG_H - PAD.top - PAD.bottom;\n const fs = this.fontSize;\n\n const pts = data.points;\n const maxY = max(pts, (p) => p.y) ?? 1;\n\n const xScale = scaleLinear().domain([pts[0].x, pts[pts.length - 1].x]).range([0, innerW]);\n const yScale = scaleLinear().domain([0, maxY]).range([innerH, 0]);\n\n const lineFn = line<DistributionPoint>()\n .x((p) => xScale(p.x))\n .y((p) => yScale(p.y));\n\n const areaFn = area<DistributionPoint>()\n .x((p) => xScale(p.x))\n .y0(innerH)\n .y1((p) => yScale(p.y));\n\n const curvePath = lineFn(pts) ?? \"\";\n const fillPath = areaFn(pts) ?? \"\";\n\n const minLabel = data.min.toLocaleString(\"en-US\", { maximumFractionDigits: 0 });\n const maxLabel = data.max.toLocaleString(\"en-US\", { maximumFractionDigits: 0 });\n\n const hasLabels = this.values.some((v) => v.label !== \"\");\n\n const markers = this.values\n .filter((v) => v.value >= pts[0].x && v.value <= pts[pts.length - 1].x)\n .map((v, i) => {\n const color = this.markerColors[i % this.markerColors.length];\n const mx = xScale(v.value);\n const labelText = hasLabels\n ? v.label\n : `${v.value.toLocaleString(\"en-US\", { maximumFractionDigits: 0 })} ${data.unit}`;\n return svg`\n <line stroke=${color} stroke-width=\"1.5\" stroke-dasharray=\"4 3\"\n x1=${mx} y1=${0} x2=${mx} y2=${innerH} />\n <text font-size=${fs} font-weight=\"600\" fill=${color} text-anchor=\"middle\"\n x=${mx} y=${-fs * 0.4}>${labelText}</text>\n `;\n });\n\n const crosshair = this._hoverX != null ? (() => {\n const hx = Math.max(0, Math.min(innerW, this._hoverX!));\n const hVal = xScale.invert(hx);\n const hLabel = `${Math.round(hVal).toLocaleString(\"en-US\")} ${data.unit}`;\n // Keep label anchor away from edges so it doesn't clip\n const anchor = hx < innerW * 0.2 ? \"start\" : hx > innerW * 0.8 ? \"end\" : \"middle\";\n const lx = hx < innerW * 0.2 ? 0 : hx > innerW * 0.8 ? innerW : hx;\n return svg`\n <line stroke=\"#94a3b8\" stroke-width=\"1\" x1=${hx} y1=${0} x2=${hx} y2=${innerH} />\n <text font-size=${fs} fill=\"#64748b\" font-weight=\"500\"\n text-anchor=${anchor} x=${lx} y=${-fs * 0.4}>${hLabel}</text>\n `;\n })() : nothing;\n\n return svg`\n <svg viewBox=\"0 0 ${this._width} ${SVG_H}\" width=${this._width} height=${SVG_H}\n style=\"cursor:crosshair\"\n @mousemove=${(e: MouseEvent) => {\n const innerX = e.offsetX - PAD.left;\n this._hoverX = (innerX >= 0 && innerX <= innerW) ? innerX : null;\n }}\n @mouseleave=${() => { this._hoverX = null; }}>\n <defs>\n <linearGradient id=\"dist-fill-grad\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stop-color=\"#4f46e5\" stop-opacity=\"0.28\"/>\n <stop offset=\"100%\" stop-color=\"#4f46e5\" stop-opacity=\"0\"/>\n </linearGradient>\n </defs>\n <g transform=\"translate(${PAD.left},${PAD.top})\">\n <path class=\"fill\" d=${fillPath} />\n <path class=\"curve\" d=${curvePath} />\n ${markers}\n ${crosshair}\n <text font-size=${fs} fill=\"#94a3b8\" text-anchor=\"start\" x=${0} y=${innerH + fs * 1.4}>${minLabel}</text>\n <text font-size=${fs} fill=\"#94a3b8\" text-anchor=\"end\" x=${innerW} y=${innerH + fs * 1.4}>${maxLabel}</text>\n <text font-size=${fs} fill=\"#94a3b8\" text-anchor=\"middle\" x=${innerW / 2} y=${innerH + fs * 1.4}>${data.unit}</text>\n </g>\n </svg>\n `;\n }\n\n private _renderLegend(data: DistributionData) {\n const hasLabels = this.values.some((v) => v.label !== \"\");\n if (!hasLabels) return nothing;\n return html`\n <div class=\"legend\">\n ${this.values.map((v, i) => {\n const color = this.markerColors[i % this.markerColors.length];\n const formatted = v.value.toLocaleString(\"en-US\", { maximumFractionDigits: 0 });\n return html`\n <span class=\"legend-item\">\n <span class=\"legend-dot\" style=\"background:${color}\"></span>\n ${v.label}: ${formatted} ${data.unit}\n </span>\n `;\n })}\n </div>\n `;\n }\n\n override render() {\n if (this._loading || this._width === 0) return html`<div class=\"wrap\"><div class=\"skeleton\"></div></div>`;\n if (this._error) return html`<div class=\"wrap\"><div class=\"error\">Could not load distribution</div></div>`;\n if (!this._data) return nothing;\n return html`\n <div class=\"wrap\">\n ${this._renderSvg(this._data)}\n ${this._renderLegend(this._data)}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"distribution-chart\": DistributionChart;\n }\n}\n"]}
1
+ {"version":3,"file":"distribution-chart.js","sourceRoot":"","sources":["../src/distribution-chart.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html, nothing, svg } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { scaleLinear } from \"d3-scale\";\nimport { line, area } from \"d3-shape\";\nimport { max } from \"d3-array\";\nimport { tokens } from \"./tokens.js\";\n\ninterface DistributionPoint {\n x: number;\n y: number;\n}\n\ninterface DistributionData {\n points: DistributionPoint[];\n unit: string;\n label: string;\n min: number;\n max: number;\n mean: number;\n}\n\nexport interface DistributionValue {\n /** Short label shown on the chart marker, e.g. 'A'. Empty string = show raw value instead. */\n label: string;\n value: number;\n}\n\n/** Fixed SVG height in CSS pixels — the coordinate system is always 1 user-unit = 1 px. */\nconst SVG_H = 130;\nconst PAD = { top: 22, right: 12, bottom: 18, left: 12 };\n\n/** Default marker colors (indigo/amber/teal/rose 600s), assigned in order. */\nconst DEFAULT_MARKER_COLORS = [\"#4f46e5\", \"#d97706\", \"#0d9488\", \"#e11d48\"];\n\n/**\n * Renders a KDE distribution curve for a named metric with one or more value\n * markers. The SVG viewBox is kept in sync with the element's pixel width via\n * ResizeObserver so that font sizes and stroke widths are always in real pixels\n * regardless of container width.\n *\n * Pass `fontSize` to control all text size (default 11). Pass a single\n * `{label:'', value}` for a single-value display or multiple\n * `{label:'A'|'B'|...}` entries to compare several values.\n *\n * @element distribution-chart\n */\n@customElement(\"distribution-chart\")\nexport class DistributionChart extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n width: 100%;\n }\n .wrap {\n padding: 0.25rem 0 0.5rem;\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 .curve {\n fill: none;\n stroke: var(--ui-primary, #4f46e5);\n stroke-width: 1.5;\n stroke-linejoin: round;\n }\n .fill {\n fill: url(#dist-fill-grad);\n }\n .fill-start {\n stop-color: var(--ui-primary, #4f46e5);\n }\n .crosshair {\n stroke: var(--ui-text-muted, #64748b);\n }\n .chart-label {\n fill: var(--ui-text-muted, #64748b);\n }\n .skeleton {\n background: linear-gradient(\n 90deg,\n var(--ui-surface-muted, #f8fafc) 25%,\n var(--ui-border, #e2e8f0) 50%,\n var(--ui-surface-muted, #f8fafc) 75%\n );\n background-size: 200% 100%;\n animation: shimmer 1.4s infinite;\n border-radius: var(--ui-radius-sm, 0.25rem);\n height: 6.25rem;\n }\n @keyframes shimmer {\n 0% {\n background-position: 200% 0;\n }\n 100% {\n background-position: -200% 0;\n }\n }\n .error {\n font-size: var(--ui-font-size-sm, 0.75rem);\n color: var(--ui-danger, #dc2626);\n padding: 0.5rem 0;\n }\n .legend {\n display: flex;\n flex-wrap: wrap;\n gap: 0.5rem 0.75rem;\n padding: 0.25rem 0 0.25rem 0.5rem;\n }\n .legend-item {\n display: inline-flex;\n align-items: center;\n gap: 0.25rem;\n font-size: var(--ui-font-size-sm, 0.75rem);\n color: var(--ui-text-muted, #64748b);\n }\n .legend-dot {\n display: inline-block;\n width: 0.5rem;\n height: 0.5rem;\n border-radius: 50%;\n flex: 0 0 auto;\n }\n @media (prefers-reduced-motion: reduce) {\n .skeleton {\n animation: none;\n }\n }\n `,\n ];\n\n /** Metric name, fetched from `/api/distribution/<metric>` on change. */\n @property() metric = \"\";\n /** One or more values to mark on the distribution curve. */\n @property({ attribute: false }) values: DistributionValue[] = [];\n /** Colors assigned to markers in order. Defaults to indigo/amber/teal/rose 600s. */\n @property({ attribute: false }) markerColors: string[] = DEFAULT_MARKER_COLORS;\n /** Target font size in CSS pixels (default 11). Always renders at this size regardless of container width. */\n @property({ type: Number, attribute: \"font-size\" }) fontSize = 11;\n\n @state() private _data: DistributionData | null = null;\n @state() private _loading = false;\n @state() private _error: string | null = null;\n /** Measured element width in CSS pixels. 0 until ResizeObserver fires. */\n @state() private _width = 0;\n /** Inner x coordinate (px) of the current hover position, or null when not hovering. */\n @state() private _hoverX: number | null = null;\n\n private _fetchedFor = \"\";\n private _ro: ResizeObserver | null = null;\n\n override connectedCallback() {\n super.connectedCallback();\n this._maybeFetch();\n }\n\n override firstUpdated() {\n this._ro = new ResizeObserver((entries) => {\n const w = Math.floor(entries[0].contentRect.width);\n if (w > 0 && w !== this._width) this._width = w;\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 override updated(changed: Map<string, unknown>) {\n if (changed.has(\"metric\")) this._maybeFetch();\n }\n\n private _maybeFetch() {\n if (!this.metric || this._fetchedFor === this.metric) return;\n this._fetchedFor = this.metric;\n this._loading = true;\n this._error = null;\n fetch(`/api/distribution/${encodeURIComponent(this.metric)}`)\n .then((r) => {\n if (!r.ok) throw new Error(`HTTP ${r.status}`);\n return r.json() as Promise<DistributionData>;\n })\n .then((data) => {\n this._data = data;\n this._loading = false;\n })\n .catch((err: unknown) => {\n this._error = err instanceof Error ? err.message : \"Failed to load\";\n this._loading = false;\n });\n }\n\n private _renderSvg(data: DistributionData) {\n if (this._width === 0) return nothing;\n\n const innerW = this._width - PAD.left - PAD.right;\n const innerH = SVG_H - PAD.top - PAD.bottom;\n const fs = this.fontSize;\n\n const pts = data.points;\n const maxY = max(pts, (p) => p.y) ?? 1;\n\n const xScale = scaleLinear().domain([pts[0].x, pts[pts.length - 1].x]).range([0, innerW]);\n const yScale = scaleLinear().domain([0, maxY]).range([innerH, 0]);\n\n const lineFn = line<DistributionPoint>()\n .x((p) => xScale(p.x))\n .y((p) => yScale(p.y));\n\n const areaFn = area<DistributionPoint>()\n .x((p) => xScale(p.x))\n .y0(innerH)\n .y1((p) => yScale(p.y));\n\n const curvePath = lineFn(pts) ?? \"\";\n const fillPath = areaFn(pts) ?? \"\";\n\n const minLabel = data.min.toLocaleString(\"en-US\", { maximumFractionDigits: 0 });\n const maxLabel = data.max.toLocaleString(\"en-US\", { maximumFractionDigits: 0 });\n\n const hasLabels = this.values.some((v) => v.label !== \"\");\n\n const markers = this.values\n .filter((v) => v.value >= pts[0].x && v.value <= pts[pts.length - 1].x)\n .map((v, i) => {\n const color = this.markerColors[i % this.markerColors.length];\n const mx = xScale(v.value);\n const labelText = hasLabels\n ? v.label\n : `${v.value.toLocaleString(\"en-US\", { maximumFractionDigits: 0 })} ${data.unit}`;\n return svg`\n <line stroke=${color} stroke-width=\"1.5\" stroke-dasharray=\"4 3\"\n x1=${mx} y1=${0} x2=${mx} y2=${innerH} />\n <text font-size=${fs} font-weight=\"600\" fill=${color} text-anchor=\"middle\"\n x=${mx} y=${-fs * 0.4}>${labelText}</text>\n `;\n });\n\n const crosshair = this._hoverX != null ? (() => {\n const hx = Math.max(0, Math.min(innerW, this._hoverX!));\n const hVal = xScale.invert(hx);\n const hLabel = `${Math.round(hVal).toLocaleString(\"en-US\")} ${data.unit}`;\n // Keep label anchor away from edges so it doesn't clip\n const anchor = hx < innerW * 0.2 ? \"start\" : hx > innerW * 0.8 ? \"end\" : \"middle\";\n const lx = hx < innerW * 0.2 ? 0 : hx > innerW * 0.8 ? innerW : hx;\n return svg`\n <line class=\"crosshair\" stroke-width=\"1\" x1=${hx} y1=${0} x2=${hx} y2=${innerH} />\n <text class=\"chart-label\" font-size=${fs} font-weight=\"500\"\n text-anchor=${anchor} x=${lx} y=${-fs * 0.4}>${hLabel}</text>\n `;\n })() : nothing;\n\n const markerSummary = this.values.length > 0\n ? ` Markers: ${this.values\n .map((value) => `${value.label || \"value\"} ${value.value.toLocaleString(\"en-US\")} ${data.unit}`)\n .join(\", \")}.`\n : \"\";\n const summary =\n `${data.label}: ${data.min.toLocaleString(\"en-US\")} to ${data.max.toLocaleString(\"en-US\")} ${data.unit}, ` +\n `mean ${data.mean.toLocaleString(\"en-US\")} ${data.unit}.${markerSummary}`;\n\n return svg`\n <svg viewBox=\"0 0 ${this._width} ${SVG_H}\" width=${this._width} height=${SVG_H}\n role=\"img\" aria-label=${summary}\n style=\"cursor:crosshair\"\n @mousemove=${(e: MouseEvent) => {\n const innerX = e.offsetX - PAD.left;\n this._hoverX = (innerX >= 0 && innerX <= innerW) ? innerX : null;\n }}\n @mouseleave=${() => { this._hoverX = null; }}>\n <defs>\n <linearGradient id=\"dist-fill-grad\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop class=\"fill-start\" offset=\"0%\" stop-opacity=\"0.28\"/>\n <stop class=\"fill-start\" offset=\"100%\" stop-opacity=\"0\"/>\n </linearGradient>\n </defs>\n <g transform=\"translate(${PAD.left},${PAD.top})\">\n <path class=\"fill\" d=${fillPath} />\n <path class=\"curve\" d=${curvePath} />\n ${markers}\n ${crosshair}\n <text class=\"chart-label\" font-size=${fs} text-anchor=\"start\" x=${0} y=${innerH + fs * 1.4}>${minLabel}</text>\n <text class=\"chart-label\" font-size=${fs} text-anchor=\"end\" x=${innerW} y=${innerH + fs * 1.4}>${maxLabel}</text>\n <text class=\"chart-label\" font-size=${fs} text-anchor=\"middle\" x=${innerW / 2} y=${innerH + fs * 1.4}>${data.unit}</text>\n </g>\n </svg>\n `;\n }\n\n private _renderLegend(data: DistributionData) {\n const hasLabels = this.values.some((v) => v.label !== \"\");\n if (!hasLabels) return nothing;\n return html`\n <div class=\"legend\">\n ${this.values.map((v, i) => {\n const color = this.markerColors[i % this.markerColors.length];\n const formatted = v.value.toLocaleString(\"en-US\", { maximumFractionDigits: 0 });\n return html`\n <span class=\"legend-item\">\n <span class=\"legend-dot\" style=\"background:${color}\"></span>\n ${v.label}: ${formatted} ${data.unit}\n </span>\n `;\n })}\n </div>\n `;\n }\n\n override render() {\n if (this._loading || this._width === 0) return html`<div class=\"wrap\"><div class=\"skeleton\"></div></div>`;\n if (this._error) return html`<div class=\"wrap\"><div class=\"error\">Could not load distribution</div></div>`;\n if (!this._data) return nothing;\n return html`\n <div class=\"wrap\">\n ${this._renderSvg(this._data)}\n ${this._renderLegend(this._data)}\n </div>\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"distribution-chart\": DistributionChart;\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"dropdown-button.d.ts","sourceRoot":"","sources":["../src/dropdown-button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAK1E,4BAA4B;AAC5B,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;GAQG;AACH,qBACa,cAAe,SAAQ,UAAU;;IAC5C,OAAgB,MAAM,4BAoEpB;IAEF,kCAAkC;IACtB,KAAK,SAAM;IACvB,0BAA0B;IACM,OAAO,EAAE,cAAc,EAAE,CAAM;IAC/D,8DAA8D;IACjC,QAAQ,UAAS;IAErC,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,YAAY,CAAM;IAE1B,oBAAoB,IAAI,IAAI,CAGpC;IAED,UAAmB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,CAQxD;IA4CD,OAAO,CAAC,UAAU;IAsBT,MAAM,yCAgBd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,iBAAiB,EAAE,cAAc,CAAC;KACnC;CACF"}
1
+ {"version":3,"file":"dropdown-button.d.ts","sourceRoot":"","sources":["../src/dropdown-button.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAO1E,4BAA4B;AAC5B,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;GAQG;AACH,qBACa,cAAe,SAAQ,UAAU;;IAC5C,OAAgB,MAAM,4BAkGpB;IAEF,kCAAkC;IACtB,KAAK,SAAM;IACvB,0BAA0B;IACM,OAAO,EAAE,cAAc,EAAE,CAAM;IAC/D,8DAA8D;IACjC,QAAQ,UAAS;IAErC,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,YAAY,CAAM;IAG1B,oBAAoB,IAAI,IAAI,CAKpC;IAED,UAAmB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,CAYxD;IA6CD,OAAO,CAAC,UAAU;IAwBT,MAAM,yCAsBd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,iBAAiB,EAAE,cAAc,CAAC;KACnC;CACF"}
@@ -8,6 +8,7 @@ import { LitElement, css, html, nothing } from "lit";
8
8
  import { customElement, property, state } from "lit/decorators.js";
9
9
  import { iconChevronRight } from "./icons.js";
10
10
  import { tokens } from "./tokens.js";
11
+ let instanceCount = 0;
11
12
  /**
12
13
  * A primary-styled button with a label and chevron that opens an anchored
13
14
  * menu of actions — essentially `form-select` minus "current value"
@@ -28,6 +29,7 @@ let DropdownButton = class DropdownButton extends LitElement {
28
29
  this.disabled = false;
29
30
  this._open = false;
30
31
  this._activeIndex = -1;
32
+ this.#menuId = `dropdown-button-menu-${++instanceCount}`;
31
33
  this.#onWindowMousedown = (e) => {
32
34
  if (!e.composedPath().includes(this))
33
35
  this._open = false;
@@ -39,20 +41,29 @@ let DropdownButton = class DropdownButton extends LitElement {
39
41
  :host {
40
42
  display: inline-block;
41
43
  position: relative;
42
- font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);
44
+ font-family: var(
45
+ --ui-font,
46
+ ui-sans-serif,
47
+ system-ui,
48
+ sans-serif,
49
+ "Apple Color Emoji",
50
+ "Segoe UI Emoji",
51
+ "Segoe UI Symbol",
52
+ "Noto Color Emoji"
53
+ );
43
54
  }
44
55
  button.trigger {
45
56
  display: flex;
46
57
  align-items: center;
47
- gap: 0.35rem;
58
+ gap: 0.25rem;
48
59
  font: inherit;
49
60
  font-size: var(--ui-font-size-sm, 0.75rem);
50
61
  font-weight: 500;
51
- color: #fff;
62
+ color: var(--ui-on-accent, #ffffff);
52
63
  background: var(--ui-primary, #4f46e5);
53
64
  border: 1px solid transparent;
54
65
  border-radius: var(--ui-radius-sm, 0.25rem);
55
- padding: 0.5rem 0.9rem;
66
+ padding: 0.5rem 1rem;
56
67
  cursor: pointer;
57
68
  }
58
69
  button.trigger:hover:not(:disabled) {
@@ -69,7 +80,7 @@ let DropdownButton = class DropdownButton extends LitElement {
69
80
  .chevron {
70
81
  display: flex;
71
82
  transform: rotate(90deg);
72
- transition: transform 0.1s ease;
83
+ transition: transform 150ms ease;
73
84
  }
74
85
  :host([open]) .chevron {
75
86
  transform: rotate(-90deg);
@@ -82,17 +93,17 @@ let DropdownButton = class DropdownButton extends LitElement {
82
93
  min-width: 100%;
83
94
  max-height: 40vh;
84
95
  overflow-y: auto;
85
- margin: 2px 0 0;
86
- padding: 4px 0;
96
+ margin: 0.25rem 0 0;
97
+ padding: 0.25rem 0;
87
98
  list-style: none;
88
99
  white-space: nowrap;
89
- background: var(--ui-surface, #fff);
100
+ background: var(--ui-surface, #ffffff);
90
101
  border: 1px solid var(--ui-border, #e2e8f0);
91
102
  border-radius: var(--ui-radius-sm, 0.25rem);
92
103
  box-shadow: var(--ui-shadow, 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1));
93
104
  }
94
105
  li {
95
- padding: 0.35rem 0.6rem;
106
+ padding: 0.5rem 0.75rem;
96
107
  cursor: pointer;
97
108
  color: var(--ui-text, #0f172a);
98
109
  }
@@ -100,13 +111,41 @@ let DropdownButton = class DropdownButton extends LitElement {
100
111
  li:hover {
101
112
  background: var(--ui-surface-muted, #f8fafc);
102
113
  }
114
+ @media (prefers-reduced-motion: reduce) {
115
+ .chevron {
116
+ transition: none;
117
+ }
118
+ }
119
+ @media (forced-colors: active) {
120
+ button.trigger:focus-visible {
121
+ outline: 2px solid CanvasText;
122
+ outline-offset: 2px;
123
+ box-shadow: none;
124
+ }
125
+ button.trigger:disabled {
126
+ color: GrayText;
127
+ opacity: 1;
128
+ }
129
+ li.active,
130
+ li:hover {
131
+ color: HighlightText;
132
+ background: Highlight;
133
+ }
134
+ }
103
135
  `,
104
136
  ]; }
137
+ #menuId;
105
138
  disconnectedCallback() {
106
139
  super.disconnectedCallback();
107
140
  window.removeEventListener("mousedown", this.#onWindowMousedown, true);
141
+ this._open = false;
142
+ this._activeIndex = -1;
108
143
  }
109
144
  updated(changed) {
145
+ if (changed.has("disabled") && this.disabled) {
146
+ this._open = false;
147
+ this._activeIndex = -1;
148
+ }
110
149
  if (!changed.has("_open"))
111
150
  return;
112
151
  this.toggleAttribute("open", this._open);
@@ -126,6 +165,8 @@ let DropdownButton = class DropdownButton extends LitElement {
126
165
  this._activeIndex = 0;
127
166
  }
128
167
  #select(option) {
168
+ if (this.disabled)
169
+ return;
129
170
  this._open = false;
130
171
  this.dispatchEvent(new CustomEvent("select", { detail: { value: option.value } }));
131
172
  }
@@ -163,12 +204,15 @@ let DropdownButton = class DropdownButton extends LitElement {
163
204
  if (!this._open)
164
205
  return nothing;
165
206
  return html `
166
- <ul class="options" role="menu">
207
+ <ul id=${this.#menuId} class="options" role="menu" aria-label=${this.label}>
167
208
  ${this.options.map((o, i) => html `
168
209
  <li
210
+ id=${`${this.#menuId}-item-${i}`}
169
211
  role="menuitem"
170
212
  class=${i === this._activeIndex ? "active" : ""}
171
213
  @mousedown=${(e) => {
214
+ if (e.button !== 0)
215
+ return;
172
216
  e.preventDefault();
173
217
  this.#select(o);
174
218
  }}
@@ -180,18 +224,23 @@ let DropdownButton = class DropdownButton extends LitElement {
180
224
  `;
181
225
  }
182
226
  render() {
227
+ const activeDescendant = this._open && this._activeIndex >= 0
228
+ ? `${this.#menuId}-item-${this._activeIndex}`
229
+ : nothing;
183
230
  return html `
184
231
  <button
185
232
  type="button"
186
233
  class="trigger"
187
234
  aria-haspopup="menu"
188
235
  aria-expanded=${this._open}
236
+ aria-controls=${this.#menuId}
237
+ aria-activedescendant=${activeDescendant}
189
238
  ?disabled=${this.disabled}
190
239
  @click=${() => this.#toggle()}
191
240
  @keydown=${(e) => this.#onTriggerKeydown(e)}
192
241
  >
193
242
  <span>${this.label}</span>
194
- <span class="chevron">${iconChevronRight(14)}</span>
243
+ <span class="chevron" aria-hidden="true">${iconChevronRight(14)}</span>
195
244
  </button>
196
245
  ${this.renderMenu()}
197
246
  `;
@@ -1 +1 @@
1
- {"version":3,"file":"dropdown-button.js","sourceRoot":"","sources":["../src/dropdown-button.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html, nothing, type PropertyValues } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { iconChevronRight } from \"./icons.js\";\nimport { tokens } from \"./tokens.js\";\n\n/** A single menu action. */\nexport interface DropdownOption {\n value: string;\n label: string;\n}\n\n/**\n * A primary-styled button with a label and chevron that opens an anchored\n * menu of actions — essentially `form-select` minus \"current value\"\n * semantics: a menu, not a select. Use for a set of mutually exclusive\n * next-step actions (e.g. a failed task's Retry / Close / Backlog).\n *\n * @element dropdown-button\n * @fires select - Fired with `{ value: string }` when a menu item is picked.\n */\n@customElement(\"dropdown-button\")\nexport class DropdownButton extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: inline-block;\n position: relative;\n font-family: var(--ui-font, ui-sans-serif, system-ui, sans-serif);\n }\n button.trigger {\n display: flex;\n align-items: center;\n gap: 0.35rem;\n font: inherit;\n font-size: var(--ui-font-size-sm, 0.75rem);\n font-weight: 500;\n color: #fff;\n background: var(--ui-primary, #4f46e5);\n border: 1px solid transparent;\n border-radius: var(--ui-radius-sm, 0.25rem);\n padding: 0.5rem 0.9rem;\n cursor: pointer;\n }\n button.trigger:hover:not(:disabled) {\n background: var(--ui-primary-hover, #4338ca);\n }\n button.trigger:disabled {\n cursor: not-allowed;\n opacity: 0.6;\n }\n button.trigger:focus-visible {\n outline: none;\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n }\n .chevron {\n display: flex;\n transform: rotate(90deg);\n transition: transform 0.1s ease;\n }\n :host([open]) .chevron {\n transform: rotate(-90deg);\n }\n ul.options {\n position: absolute;\n top: 100%;\n right: 0;\n z-index: 10;\n min-width: 100%;\n max-height: 40vh;\n overflow-y: auto;\n margin: 2px 0 0;\n padding: 4px 0;\n list-style: none;\n white-space: nowrap;\n background: var(--ui-surface, #fff);\n border: 1px solid var(--ui-border, #e2e8f0);\n border-radius: var(--ui-radius-sm, 0.25rem);\n box-shadow: var(--ui-shadow, 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1));\n }\n li {\n padding: 0.35rem 0.6rem;\n cursor: pointer;\n color: var(--ui-text, #0f172a);\n }\n li.active,\n li:hover {\n background: var(--ui-surface-muted, #f8fafc);\n }\n `,\n ];\n\n /** The trigger button's label. */\n @property() label = \"\";\n /** The menu's actions. */\n @property({ attribute: false }) options: DropdownOption[] = [];\n /** Disables the trigger, preventing the menu from opening. */\n @property({ type: Boolean }) disabled = false;\n\n @state() private _open = false;\n @state() private _activeIndex = -1;\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n window.removeEventListener(\"mousedown\", this.#onWindowMousedown, true);\n }\n\n protected override updated(changed: PropertyValues): void {\n if (!changed.has(\"_open\")) return;\n this.toggleAttribute(\"open\", this._open);\n if (this._open) {\n window.addEventListener(\"mousedown\", this.#onWindowMousedown, true);\n } else {\n window.removeEventListener(\"mousedown\", this.#onWindowMousedown, true);\n }\n }\n\n #onWindowMousedown = (e: MouseEvent): void => {\n if (!e.composedPath().includes(this)) this._open = false;\n };\n\n #toggle(): void {\n if (this.disabled) return;\n this._open = !this._open;\n if (this._open) this._activeIndex = 0;\n }\n\n #select(option: DropdownOption): void {\n this._open = false;\n this.dispatchEvent(new CustomEvent(\"select\", { detail: { value: option.value } }));\n }\n\n #onTriggerKeydown(e: KeyboardEvent): void {\n if (e.key === \"ArrowDown\" || e.key === \"ArrowUp\" || e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n if (!this._open) {\n this.#toggle();\n return;\n }\n if (e.key === \"ArrowDown\") this.#moveActive(1);\n else if (e.key === \"ArrowUp\") this.#moveActive(-1);\n else this.#confirmActive();\n } else if (e.key === \"Escape\" && this._open) {\n e.preventDefault();\n this._open = false;\n }\n }\n\n #moveActive(delta: number): void {\n if (this.options.length === 0) return;\n const n = this.options.length;\n this._activeIndex = (this._activeIndex + delta + n) % n;\n }\n\n #confirmActive(): void {\n const option = this.options[this._activeIndex];\n if (option) this.#select(option);\n }\n\n private renderMenu() {\n if (!this._open) return nothing;\n return html`\n <ul class=\"options\" role=\"menu\">\n ${this.options.map(\n (o, i) => html`\n <li\n role=\"menuitem\"\n class=${i === this._activeIndex ? \"active\" : \"\"}\n @mousedown=${(e: MouseEvent) => {\n e.preventDefault();\n this.#select(o);\n }}\n >\n ${o.label}\n </li>\n `,\n )}\n </ul>\n `;\n }\n\n override render() {\n return html`\n <button\n type=\"button\"\n class=\"trigger\"\n aria-haspopup=\"menu\"\n aria-expanded=${this._open}\n ?disabled=${this.disabled}\n @click=${() => this.#toggle()}\n @keydown=${(e: KeyboardEvent) => this.#onTriggerKeydown(e)}\n >\n <span>${this.label}</span>\n <span class=\"chevron\">${iconChevronRight(14)}</span>\n </button>\n ${this.renderMenu()}\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"dropdown-button\": DropdownButton;\n }\n}\n"]}
1
+ {"version":3,"file":"dropdown-button.js","sourceRoot":"","sources":["../src/dropdown-button.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html, nothing, type PropertyValues } from \"lit\";\nimport { customElement, property, state } from \"lit/decorators.js\";\nimport { iconChevronRight } from \"./icons.js\";\nimport { tokens } from \"./tokens.js\";\n\nlet instanceCount = 0;\n\n/** A single menu action. */\nexport interface DropdownOption {\n value: string;\n label: string;\n}\n\n/**\n * A primary-styled button with a label and chevron that opens an anchored\n * menu of actions — essentially `form-select` minus \"current value\"\n * semantics: a menu, not a select. Use for a set of mutually exclusive\n * next-step actions (e.g. a failed task's Retry / Close / Backlog).\n *\n * @element dropdown-button\n * @fires select - Fired with `{ value: string }` when a menu item is picked.\n */\n@customElement(\"dropdown-button\")\nexport class DropdownButton extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: inline-block;\n position: relative;\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 button.trigger {\n display: flex;\n align-items: center;\n gap: 0.25rem;\n font: inherit;\n font-size: var(--ui-font-size-sm, 0.75rem);\n font-weight: 500;\n color: var(--ui-on-accent, #ffffff);\n background: var(--ui-primary, #4f46e5);\n border: 1px solid transparent;\n border-radius: var(--ui-radius-sm, 0.25rem);\n padding: 0.5rem 1rem;\n cursor: pointer;\n }\n button.trigger:hover:not(:disabled) {\n background: var(--ui-primary-hover, #4338ca);\n }\n button.trigger:disabled {\n cursor: not-allowed;\n opacity: 0.6;\n }\n button.trigger:focus-visible {\n outline: none;\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n }\n .chevron {\n display: flex;\n transform: rotate(90deg);\n transition: transform 150ms ease;\n }\n :host([open]) .chevron {\n transform: rotate(-90deg);\n }\n ul.options {\n position: absolute;\n top: 100%;\n right: 0;\n z-index: 10;\n min-width: 100%;\n max-height: 40vh;\n overflow-y: auto;\n margin: 0.25rem 0 0;\n padding: 0.25rem 0;\n list-style: none;\n white-space: nowrap;\n background: var(--ui-surface, #ffffff);\n border: 1px solid var(--ui-border, #e2e8f0);\n border-radius: var(--ui-radius-sm, 0.25rem);\n box-shadow: var(--ui-shadow, 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1));\n }\n li {\n padding: 0.5rem 0.75rem;\n cursor: pointer;\n color: var(--ui-text, #0f172a);\n }\n li.active,\n li:hover {\n background: var(--ui-surface-muted, #f8fafc);\n }\n @media (prefers-reduced-motion: reduce) {\n .chevron {\n transition: none;\n }\n }\n @media (forced-colors: active) {\n button.trigger:focus-visible {\n outline: 2px solid CanvasText;\n outline-offset: 2px;\n box-shadow: none;\n }\n button.trigger:disabled {\n color: GrayText;\n opacity: 1;\n }\n li.active,\n li:hover {\n color: HighlightText;\n background: Highlight;\n }\n }\n `,\n ];\n\n /** The trigger button's label. */\n @property() label = \"\";\n /** The menu's actions. */\n @property({ attribute: false }) options: DropdownOption[] = [];\n /** Disables the trigger, preventing the menu from opening. */\n @property({ type: Boolean }) disabled = false;\n\n @state() private _open = false;\n @state() private _activeIndex = -1;\n readonly #menuId = `dropdown-button-menu-${++instanceCount}`;\n\n override disconnectedCallback(): void {\n super.disconnectedCallback();\n window.removeEventListener(\"mousedown\", this.#onWindowMousedown, true);\n this._open = false;\n this._activeIndex = -1;\n }\n\n protected override updated(changed: PropertyValues): void {\n if (changed.has(\"disabled\") && this.disabled) {\n this._open = false;\n this._activeIndex = -1;\n }\n if (!changed.has(\"_open\")) return;\n this.toggleAttribute(\"open\", this._open);\n if (this._open) {\n window.addEventListener(\"mousedown\", this.#onWindowMousedown, true);\n } else {\n window.removeEventListener(\"mousedown\", this.#onWindowMousedown, true);\n }\n }\n\n #onWindowMousedown = (e: MouseEvent): void => {\n if (!e.composedPath().includes(this)) this._open = false;\n };\n\n #toggle(): void {\n if (this.disabled) return;\n this._open = !this._open;\n if (this._open) this._activeIndex = 0;\n }\n\n #select(option: DropdownOption): void {\n if (this.disabled) return;\n this._open = false;\n this.dispatchEvent(new CustomEvent(\"select\", { detail: { value: option.value } }));\n }\n\n #onTriggerKeydown(e: KeyboardEvent): void {\n if (e.key === \"ArrowDown\" || e.key === \"ArrowUp\" || e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n if (!this._open) {\n this.#toggle();\n return;\n }\n if (e.key === \"ArrowDown\") this.#moveActive(1);\n else if (e.key === \"ArrowUp\") this.#moveActive(-1);\n else this.#confirmActive();\n } else if (e.key === \"Escape\" && this._open) {\n e.preventDefault();\n this._open = false;\n }\n }\n\n #moveActive(delta: number): void {\n if (this.options.length === 0) return;\n const n = this.options.length;\n this._activeIndex = (this._activeIndex + delta + n) % n;\n }\n\n #confirmActive(): void {\n const option = this.options[this._activeIndex];\n if (option) this.#select(option);\n }\n\n private renderMenu() {\n if (!this._open) return nothing;\n return html`\n <ul id=${this.#menuId} class=\"options\" role=\"menu\" aria-label=${this.label}>\n ${this.options.map(\n (o, i) => html`\n <li\n id=${`${this.#menuId}-item-${i}`}\n role=\"menuitem\"\n class=${i === this._activeIndex ? \"active\" : \"\"}\n @mousedown=${(e: MouseEvent) => {\n if (e.button !== 0) return;\n e.preventDefault();\n this.#select(o);\n }}\n >\n ${o.label}\n </li>\n `,\n )}\n </ul>\n `;\n }\n\n override render() {\n const activeDescendant =\n this._open && this._activeIndex >= 0\n ? `${this.#menuId}-item-${this._activeIndex}`\n : nothing;\n return html`\n <button\n type=\"button\"\n class=\"trigger\"\n aria-haspopup=\"menu\"\n aria-expanded=${this._open}\n aria-controls=${this.#menuId}\n aria-activedescendant=${activeDescendant}\n ?disabled=${this.disabled}\n @click=${() => this.#toggle()}\n @keydown=${(e: KeyboardEvent) => this.#onTriggerKeydown(e)}\n >\n <span>${this.label}</span>\n <span class=\"chevron\" aria-hidden=\"true\">${iconChevronRight(14)}</span>\n </button>\n ${this.renderMenu()}\n `;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"dropdown-button\": DropdownButton;\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"editable-text.d.ts","sourceRoot":"","sources":["../src/editable-text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAIrD;;;;;;;;GAQG;AACH,qBACa,YAAa,SAAQ,UAAU;IAC1C,OAAgB,MAAM,4BAuDpB;IAEF,oBAAoB;IACR,KAAK,SAAM;IACvB,yEAAyE;IAC5C,SAAS,UAAS;IAC/C,0FAA0F;IAC9E,WAAW,SAAM;IAC7B,mCAAmC;IACN,QAAQ,UAAS;IAC9C,kDAAkD;IACtC,KAAK,SAAM;IAEd,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAM;IAEH,OAAO,CAAC,MAAM,CAAC,CAAyC;IAElF,kEAAkE;IAClE,OAAO,CAAC,UAAU;IAMlB;;;;;;;OAOG;IACH,OAAO,CAAC,OAAO;IAUf,iEAAiE;IACjE,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAC,oBAAoB;IAU5B,OAAO,CAAC,mBAAmB;IAU3B,sDAAsD;IACtD,OAAO,CAAC,OAAO;IAOf,UAAmB,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,QASvD;IAEQ,MAAM,yCA4Bd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,eAAe,EAAE,YAAY,CAAC;KAC/B;CACF"}
1
+ {"version":3,"file":"editable-text.d.ts","sourceRoot":"","sources":["../src/editable-text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAIrD;;;;;;;;GAQG;AACH,qBACa,YAAa,SAAQ,UAAU;IAC1C,OAAgB,MAAM,4BAqFpB;IAEF,oBAAoB;IACR,KAAK,SAAM;IACvB,yEAAyE;IAC5C,SAAS,UAAS;IAC/C,0FAA0F;IAC9E,WAAW,SAAM;IAC7B,mCAAmC;IACN,QAAQ,UAAS;IAC9C,kDAAkD;IACtC,KAAK,SAAM;IAEd,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAM;IAEH,OAAO,CAAC,MAAM,CAAC,CAAyC;IAElF,kEAAkE;IAClE,OAAO,CAAC,UAAU;IAMlB;;;;;;;OAOG;IACH,OAAO,CAAC,OAAO;IAUf,iEAAiE;IACjE,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAC,oBAAoB;IAU5B,OAAO,CAAC,mBAAmB;IAU3B,sDAAsD;IACtD,OAAO,CAAC,OAAO;IAOf,UAAmB,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,QASvD;IAEQ,MAAM,yCA+Bd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,eAAe,EAAE,YAAY,CAAC;KAC/B;CACF"}
@@ -43,26 +43,43 @@ let EditableText = class EditableText extends LitElement {
43
43
  line-height: inherit;
44
44
  }
45
45
  .display {
46
+ display: block;
47
+ width: 100%;
48
+ box-sizing: border-box;
49
+ border: 0;
50
+ color: inherit;
51
+ background: transparent;
52
+ font: inherit;
53
+ line-height: inherit;
54
+ text-align: inherit;
46
55
  cursor: text;
47
56
  border-radius: var(--ui-radius-sm, 0.25rem);
48
- padding: 0.1rem 0.3rem;
49
- margin: -0.1rem -0.3rem;
57
+ padding: 0.25rem;
58
+ margin: -0.25rem;
50
59
  }
51
60
  .display.multiline {
61
+ /* Inactive multiline text is flush; the active textarea keeps its editor inset. */
52
62
  white-space: pre-wrap;
63
+ padding: 0;
64
+ margin: 0;
53
65
  }
54
- .display:hover {
66
+ .display:hover:not(:disabled) {
55
67
  background: var(--ui-surface-muted, #f8fafc);
56
68
  }
57
69
  .display.empty {
58
70
  color: var(--ui-text-muted, #64748b);
59
71
  }
60
- .display.readonly {
72
+ .display:disabled {
61
73
  cursor: inherit;
74
+ opacity: 0.6;
62
75
  }
63
- .display.readonly:hover {
76
+ .display:disabled:hover {
64
77
  background: none;
65
78
  }
79
+ .display:focus-visible {
80
+ outline: none;
81
+ box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
82
+ }
66
83
  input,
67
84
  textarea {
68
85
  display: block;
@@ -70,15 +87,15 @@ let EditableText = class EditableText extends LitElement {
70
87
  box-sizing: border-box;
71
88
  font: inherit;
72
89
  color: inherit;
73
- background: var(--ui-surface, #fff);
90
+ background: var(--ui-surface, #ffffff);
74
91
  border: 1px solid var(--ui-border, #e2e8f0);
75
92
  border-radius: var(--ui-radius-sm, 0.25rem);
76
- padding: 0.1rem 0.3rem;
77
- margin: -0.1rem -0.3rem;
93
+ padding: 0.25rem;
94
+ margin: -0.25rem;
78
95
  outline: none;
79
96
  }
80
- input:focus,
81
- textarea:focus {
97
+ input:focus-visible,
98
+ textarea:focus-visible {
82
99
  border-color: var(--ui-primary, #4f46e5);
83
100
  box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
84
101
  }
@@ -86,6 +103,19 @@ let EditableText = class EditableText extends LitElement {
86
103
  resize: none;
87
104
  overflow: hidden;
88
105
  }
106
+ @media (forced-colors: active) {
107
+ .display:focus-visible,
108
+ input:focus-visible,
109
+ textarea:focus-visible {
110
+ outline: 2px solid CanvasText;
111
+ outline-offset: 2px;
112
+ box-shadow: none;
113
+ }
114
+ .display:disabled {
115
+ color: GrayText;
116
+ opacity: 1;
117
+ }
118
+ }
89
119
  `,
90
120
  ]; }
91
121
  /** Enters edit mode, seeding the draft from the current value. */
@@ -188,9 +218,12 @@ let EditableText = class EditableText extends LitElement {
188
218
  }
189
219
  const isEmpty = this.value.trim() === "";
190
220
  const classes = `display ${this.multiline ? "multiline" : ""} ${isEmpty ? "empty" : ""} ${this.readonly ? "readonly" : ""}`;
191
- return html `<span class=${classes} @click=${this._startEdit}
192
- >${isEmpty ? this.placeholder : this.value}</span
193
- >`;
221
+ return html `<button
222
+ type="button"
223
+ class=${classes}
224
+ ?disabled=${this.readonly}
225
+ @click=${this._startEdit}
226
+ >${isEmpty ? this.placeholder : this.value}</button>`;
194
227
  }
195
228
  };
196
229
  __decorate([
@@ -1 +1 @@
1
- {"version":3,"file":"editable-text.js","sourceRoot":"","sources":["../src/editable-text.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html, nothing } from \"lit\";\nimport { customElement, property, query, state } from \"lit/decorators.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * Jira/GitHub-style click-to-edit text: a display span that turns into an\n * `<input>` (or auto-growing `<textarea>` when `multiline`) on click. The\n * input/textarea inherits the host's font, so a title wrapped in an `<h1>`\n * edits at title size.\n *\n * @element editable-text\n * @fires change - Fired with `{ value: string }` when the committed value differs from the previous one.\n */\n@customElement(\"editable-text\")\nexport class EditableText extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n font-family: inherit;\n font-size: inherit;\n font-weight: inherit;\n line-height: inherit;\n }\n .display {\n cursor: text;\n border-radius: var(--ui-radius-sm, 0.25rem);\n padding: 0.1rem 0.3rem;\n margin: -0.1rem -0.3rem;\n }\n .display.multiline {\n white-space: pre-wrap;\n }\n .display:hover {\n background: var(--ui-surface-muted, #f8fafc);\n }\n .display.empty {\n color: var(--ui-text-muted, #64748b);\n }\n .display.readonly {\n cursor: inherit;\n }\n .display.readonly:hover {\n background: none;\n }\n input,\n textarea {\n display: block;\n width: 100%;\n box-sizing: border-box;\n font: inherit;\n color: inherit;\n background: var(--ui-surface, #fff);\n border: 1px solid var(--ui-border, #e2e8f0);\n border-radius: var(--ui-radius-sm, 0.25rem);\n padding: 0.1rem 0.3rem;\n margin: -0.1rem -0.3rem;\n outline: none;\n }\n input:focus,\n textarea:focus {\n border-color: var(--ui-primary, #4f46e5);\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n }\n textarea {\n resize: none;\n overflow: hidden;\n }\n `,\n ];\n\n /** Current text. */\n @property() value = \"\";\n /** `false` renders an `<input>`, `true` an auto-growing `<textarea>`. */\n @property({ type: Boolean }) multiline = false;\n /** Muted placeholder text shown when `value` is empty, and as the input's placeholder. */\n @property() placeholder = \"\";\n /** Disables entering edit mode. */\n @property({ type: Boolean }) readonly = false;\n /** `aria-label` applied to the input/textarea. */\n @property() label = \"\";\n\n @state() private _editing = false;\n @state() private _draft = \"\";\n\n @query(\"input, textarea\") private _field?: HTMLInputElement | HTMLTextAreaElement;\n\n /** Enters edit mode, seeding the draft from the current value. */\n private _startEdit() {\n if (this.readonly) return;\n this._draft = this.value;\n this._editing = true;\n }\n\n /** Commits the draft (reverting an empty single-line commit) and exits edit mode.\n *\n * Guarded on `_editing`: removing the focused input/textarea from the DOM\n * (which happens as soon as `_editing` flips to false) makes the browser\n * fire a native `blur` on it, re-invoking this handler a second time after\n * a cancel already ran — the guard makes that second call a no-op instead\n * of re-committing the stale draft.\n */\n private _commit() {\n if (!this._editing) return;\n const next = this._draft;\n this._editing = false;\n if (!this.multiline && next.trim() === \"\") return;\n if (next === this.value) return;\n this.value = next;\n this.dispatchEvent(new CustomEvent(\"change\", { detail: { value: next } }));\n }\n\n /** Discards the draft and exits edit mode without committing. */\n private _cancel() {\n this._editing = false;\n }\n\n private _onInput(e: Event) {\n this._draft = (e.target as HTMLInputElement | HTMLTextAreaElement).value;\n if (this.multiline) this._resize();\n }\n\n private _onSingleLineKeydown(e: KeyboardEvent) {\n if (e.key === \"Enter\") {\n e.preventDefault();\n this._commit();\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n this._cancel();\n }\n }\n\n private _onMultilineKeydown(e: KeyboardEvent) {\n if (e.key === \"Escape\") {\n e.preventDefault();\n this._cancel();\n } else if (e.key === \"Enter\" && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n this._commit();\n }\n }\n\n /** Grows the textarea's height to fit its content. */\n private _resize() {\n const field = this._field;\n if (!field) return;\n field.style.height = \"auto\";\n field.style.height = `${field.scrollHeight}px`;\n }\n\n protected override updated(changed: Map<string, unknown>) {\n if (changed.has(\"_editing\") && this._editing) {\n const field = this._field;\n if (!field) return;\n field.focus();\n const end = field.value.length;\n field.setSelectionRange(end, end);\n if (this.multiline) this._resize();\n }\n }\n\n override render() {\n if (this._editing) {\n return this.multiline\n ? html`<textarea\n rows=\"3\"\n .value=${this._draft}\n placeholder=${this.placeholder}\n aria-label=${this.label || nothing}\n @input=${this._onInput}\n @keydown=${this._onMultilineKeydown}\n @blur=${this._commit}\n ></textarea>`\n : html`<input\n type=\"text\"\n .value=${this._draft}\n placeholder=${this.placeholder}\n aria-label=${this.label || nothing}\n @input=${this._onInput}\n @keydown=${this._onSingleLineKeydown}\n @blur=${this._commit}\n />`;\n }\n\n const isEmpty = this.value.trim() === \"\";\n const classes = `display ${this.multiline ? \"multiline\" : \"\"} ${isEmpty ? \"empty\" : \"\"} ${this.readonly ? \"readonly\" : \"\"}`;\n return html`<span class=${classes} @click=${this._startEdit}\n >${isEmpty ? this.placeholder : this.value}</span\n >`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"editable-text\": EditableText;\n }\n}\n"]}
1
+ {"version":3,"file":"editable-text.js","sourceRoot":"","sources":["../src/editable-text.ts"],"names":[],"mappings":"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","sourcesContent":["import { LitElement, css, html, nothing } from \"lit\";\nimport { customElement, property, query, state } from \"lit/decorators.js\";\nimport { tokens } from \"./tokens.js\";\n\n/**\n * Jira/GitHub-style click-to-edit text: a display span that turns into an\n * `<input>` (or auto-growing `<textarea>` when `multiline`) on click. The\n * input/textarea inherits the host's font, so a title wrapped in an `<h1>`\n * edits at title size.\n *\n * @element editable-text\n * @fires change - Fired with `{ value: string }` when the committed value differs from the previous one.\n */\n@customElement(\"editable-text\")\nexport class EditableText extends LitElement {\n static override styles = [\n tokens,\n css`\n :host {\n display: block;\n font-family: inherit;\n font-size: inherit;\n font-weight: inherit;\n line-height: inherit;\n }\n .display {\n display: block;\n width: 100%;\n box-sizing: border-box;\n border: 0;\n color: inherit;\n background: transparent;\n font: inherit;\n line-height: inherit;\n text-align: inherit;\n cursor: text;\n border-radius: var(--ui-radius-sm, 0.25rem);\n padding: 0.25rem;\n margin: -0.25rem;\n }\n .display.multiline {\n /* Inactive multiline text is flush; the active textarea keeps its editor inset. */\n white-space: pre-wrap;\n padding: 0;\n margin: 0;\n }\n .display:hover:not(:disabled) {\n background: var(--ui-surface-muted, #f8fafc);\n }\n .display.empty {\n color: var(--ui-text-muted, #64748b);\n }\n .display:disabled {\n cursor: inherit;\n opacity: 0.6;\n }\n .display:disabled:hover {\n background: none;\n }\n .display:focus-visible {\n outline: none;\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n }\n input,\n textarea {\n display: block;\n width: 100%;\n box-sizing: border-box;\n font: inherit;\n color: inherit;\n background: var(--ui-surface, #ffffff);\n border: 1px solid var(--ui-border, #e2e8f0);\n border-radius: var(--ui-radius-sm, 0.25rem);\n padding: 0.25rem;\n margin: -0.25rem;\n outline: none;\n }\n input:focus-visible,\n textarea:focus-visible {\n border-color: var(--ui-primary, #4f46e5);\n box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));\n }\n textarea {\n resize: none;\n overflow: hidden;\n }\n @media (forced-colors: active) {\n .display:focus-visible,\n input:focus-visible,\n textarea:focus-visible {\n outline: 2px solid CanvasText;\n outline-offset: 2px;\n box-shadow: none;\n }\n .display:disabled {\n color: GrayText;\n opacity: 1;\n }\n }\n `,\n ];\n\n /** Current text. */\n @property() value = \"\";\n /** `false` renders an `<input>`, `true` an auto-growing `<textarea>`. */\n @property({ type: Boolean }) multiline = false;\n /** Muted placeholder text shown when `value` is empty, and as the input's placeholder. */\n @property() placeholder = \"\";\n /** Disables entering edit mode. */\n @property({ type: Boolean }) readonly = false;\n /** `aria-label` applied to the input/textarea. */\n @property() label = \"\";\n\n @state() private _editing = false;\n @state() private _draft = \"\";\n\n @query(\"input, textarea\") private _field?: HTMLInputElement | HTMLTextAreaElement;\n\n /** Enters edit mode, seeding the draft from the current value. */\n private _startEdit() {\n if (this.readonly) return;\n this._draft = this.value;\n this._editing = true;\n }\n\n /** Commits the draft (reverting an empty single-line commit) and exits edit mode.\n *\n * Guarded on `_editing`: removing the focused input/textarea from the DOM\n * (which happens as soon as `_editing` flips to false) makes the browser\n * fire a native `blur` on it, re-invoking this handler a second time after\n * a cancel already ran — the guard makes that second call a no-op instead\n * of re-committing the stale draft.\n */\n private _commit() {\n if (!this._editing) return;\n const next = this._draft;\n this._editing = false;\n if (!this.multiline && next.trim() === \"\") return;\n if (next === this.value) return;\n this.value = next;\n this.dispatchEvent(new CustomEvent(\"change\", { detail: { value: next } }));\n }\n\n /** Discards the draft and exits edit mode without committing. */\n private _cancel() {\n this._editing = false;\n }\n\n private _onInput(e: Event) {\n this._draft = (e.target as HTMLInputElement | HTMLTextAreaElement).value;\n if (this.multiline) this._resize();\n }\n\n private _onSingleLineKeydown(e: KeyboardEvent) {\n if (e.key === \"Enter\") {\n e.preventDefault();\n this._commit();\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n this._cancel();\n }\n }\n\n private _onMultilineKeydown(e: KeyboardEvent) {\n if (e.key === \"Escape\") {\n e.preventDefault();\n this._cancel();\n } else if (e.key === \"Enter\" && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n this._commit();\n }\n }\n\n /** Grows the textarea's height to fit its content. */\n private _resize() {\n const field = this._field;\n if (!field) return;\n field.style.height = \"auto\";\n field.style.height = `${field.scrollHeight}px`;\n }\n\n protected override updated(changed: Map<string, unknown>) {\n if (changed.has(\"_editing\") && this._editing) {\n const field = this._field;\n if (!field) return;\n field.focus();\n const end = field.value.length;\n field.setSelectionRange(end, end);\n if (this.multiline) this._resize();\n }\n }\n\n override render() {\n if (this._editing) {\n return this.multiline\n ? html`<textarea\n rows=\"3\"\n .value=${this._draft}\n placeholder=${this.placeholder}\n aria-label=${this.label || nothing}\n @input=${this._onInput}\n @keydown=${this._onMultilineKeydown}\n @blur=${this._commit}\n ></textarea>`\n : html`<input\n type=\"text\"\n .value=${this._draft}\n placeholder=${this.placeholder}\n aria-label=${this.label || nothing}\n @input=${this._onInput}\n @keydown=${this._onSingleLineKeydown}\n @blur=${this._commit}\n />`;\n }\n\n const isEmpty = this.value.trim() === \"\";\n const classes = `display ${this.multiline ? \"multiline\" : \"\"} ${isEmpty ? \"empty\" : \"\"} ${this.readonly ? \"readonly\" : \"\"}`;\n return html`<button\n type=\"button\"\n class=${classes}\n ?disabled=${this.readonly}\n @click=${this._startEdit}\n >${isEmpty ? this.placeholder : this.value}</button>`;\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"editable-text\": EditableText;\n }\n}\n"]}
@@ -1,8 +1,12 @@
1
- import { LitElement, type PropertyValues } from "lit";
1
+ import { LitElement, type PropertyValues, type TemplateResult } from "lit";
2
2
  /** A single selectable option. */
3
3
  export interface SelectOption {
4
4
  value: string;
5
5
  label: string;
6
+ /** Optional pre-rendered icon template displayed before the label. */
7
+ icon?: TemplateResult;
8
+ /** Square icon size in pixels. Defaults to 16 when `icon` is set. */
9
+ iconSize?: number;
6
10
  }
7
11
  /**
8
12
  * A styled dropdown select: a trigger button showing the current option's
@@ -17,6 +21,13 @@ export interface SelectOption {
17
21
  * bar, a status picker) keep shrink-to-fit auto-width unchanged. To make an
18
22
  * instance full-width, size the host itself: `form-select { width: 100%; }`.
19
23
  *
24
+ * Set `searchable` to replace the button trigger with an editable combobox
25
+ * that filters the predefined options by case-insensitive label infix. Typed
26
+ * text is only a query: `value` changes exclusively when an actual option is
27
+ * selected, and an uncommitted query is discarded when the list closes.
28
+ * Each option may also provide a pre-rendered `icon` and square `iconSize`;
29
+ * iconless options reserve no leading space.
30
+ *
20
31
  * @element form-select
21
32
  * @fires change - Fired with `{ value: string }` when a different option is picked.
22
33
  */
@@ -31,12 +42,20 @@ export declare class FormSelect extends LitElement {
31
42
  label: string;
32
43
  /** Disables the trigger, preventing the popover from opening. */
33
44
  disabled: boolean;
45
+ /**
46
+ * Enables editable, case-insensitive infix filtering by option label.
47
+ * Typed text never becomes the selected `value`.
48
+ */
49
+ searchable: boolean;
34
50
  private _open;
35
51
  private _activeIndex;
52
+ private _query;
36
53
  disconnectedCallback(): void;
37
54
  protected updated(changed: PropertyValues): void;
38
55
  private renderListbox;
39
- render(): import("lit-html").TemplateResult<1>;
56
+ private renderSearchTrigger;
57
+ private renderButtonTrigger;
58
+ render(): TemplateResult<1>;
40
59
  }
41
60
  declare global {
42
61
  interface HTMLElementTagNameMap {
@@ -1 +1 @@
1
- {"version":3,"file":"form-select.d.ts","sourceRoot":"","sources":["../src/form-select.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,KAAK,cAAc,EAAE,MAAM,KAAK,CAAC;AAK1E,kCAAkC;AAClC,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;;;;;;;;GAeG;AACH,qBACa,UAAW,SAAQ,UAAU;;IACxC,OAAgB,MAAM,4BA2EpB;IAEF,2CAA2C;IACX,OAAO,EAAE,YAAY,EAAE,CAAM;IAC7D,qEAAqE;IACzD,KAAK,SAAM;IACvB,kDAAkD;IACtC,KAAK,SAAM;IACvB,iEAAiE;IACpC,QAAQ,UAAS;IAErC,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,YAAY,CAAM;IAE1B,oBAAoB,IAAI,IAAI,CAGpC;IAED,UAAmB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,CAQxD;IAmDD,OAAO,CAAC,aAAa;IAuBZ,MAAM,yCAkBd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,aAAa,EAAE,UAAU,CAAC;KAC3B;CACF"}
1
+ {"version":3,"file":"form-select.d.ts","sourceRoot":"","sources":["../src/form-select.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EAIV,KAAK,cAAc,EACnB,KAAK,cAAc,EACpB,MAAM,KAAK,CAAC;AAOb,kCAAkC;AAClC,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,IAAI,CAAC,EAAE,cAAc,CAAC;IACtB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBACa,UAAW,SAAQ,UAAU;;IACxC,OAAgB,MAAM,4BA8MpB;IAEF,2CAA2C;IACX,OAAO,EAAE,YAAY,EAAE,CAAM;IAC7D,qEAAqE;IACzD,KAAK,SAAM;IACvB,kDAAkD;IACtC,KAAK,SAAM;IACvB,iEAAiE;IACpC,QAAQ,UAAS;IAC9C;;;OAGG;IACyC,UAAU,UAAS;IAEtD,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,MAAM,CAAuB;IAYrC,oBAAoB,IAAI,IAAI,CAWpC;IAED,UAAmB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,CA2BxD;IAkPD,OAAO,CAAC,aAAa;IA+BrB,OAAO,CAAC,mBAAmB;IA2C3B,OAAO,CAAC,mBAAmB;IA0BlB,MAAM,sBAQd;CACF;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,aAAa,EAAE,UAAU,CAAC;KAC3B;CACF"}