@f-ewald/components 0.7.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -47
- package/custom-elements.json +3049 -910
- package/dist/address-autocomplete.d.ts +8 -0
- package/dist/address-autocomplete.d.ts.map +1 -1
- package/dist/address-autocomplete.js +165 -15
- package/dist/address-autocomplete.js.map +1 -1
- package/dist/animate-confetti.d.ts +8 -5
- package/dist/animate-confetti.d.ts.map +1 -1
- package/dist/animate-confetti.js +46 -19
- package/dist/animate-confetti.js.map +1 -1
- package/dist/autocomplete-input.d.ts +8 -0
- package/dist/autocomplete-input.d.ts.map +1 -1
- package/dist/autocomplete-input.js +165 -15
- package/dist/autocomplete-input.js.map +1 -1
- package/dist/calendar-entry.d.ts +33 -0
- package/dist/calendar-entry.d.ts.map +1 -0
- package/dist/calendar-entry.js +64 -0
- package/dist/calendar-entry.js.map +1 -0
- package/dist/calendar-month.d.ts +67 -0
- package/dist/calendar-month.d.ts.map +1 -0
- package/dist/calendar-month.js +469 -0
- package/dist/calendar-month.js.map +1 -0
- package/dist/calendar-year.d.ts +43 -0
- package/dist/calendar-year.d.ts.map +1 -0
- package/dist/calendar-year.js +145 -0
- package/dist/calendar-year.js.map +1 -0
- package/dist/chat-message.d.ts.map +1 -1
- package/dist/chat-message.js +77 -24
- package/dist/chat-message.js.map +1 -1
- package/dist/confirm-dialog.d.ts +8 -1
- package/dist/confirm-dialog.d.ts.map +1 -1
- package/dist/confirm-dialog.js +130 -18
- package/dist/confirm-dialog.js.map +1 -1
- package/dist/copy-link-button.d.ts +2 -0
- package/dist/copy-link-button.d.ts.map +1 -1
- package/dist/copy-link-button.js +35 -3
- package/dist/copy-link-button.js.map +1 -1
- package/dist/data-table.d.ts +2 -0
- package/dist/data-table.d.ts.map +1 -1
- package/dist/data-table.js +83 -11
- package/dist/data-table.js.map +1 -1
- package/dist/distance-value.d.ts +0 -1
- package/dist/distance-value.d.ts.map +1 -1
- package/dist/distance-value.js +3 -8
- package/dist/distance-value.js.map +1 -1
- package/dist/distribution-chart.d.ts.map +1 -1
- package/dist/distribution-chart.js +54 -17
- package/dist/distribution-chart.js.map +1 -1
- package/dist/dropdown-button.d.ts.map +1 -1
- package/dist/dropdown-button.js +60 -11
- package/dist/dropdown-button.js.map +1 -1
- package/dist/editable-text.d.ts.map +1 -1
- package/dist/editable-text.js +46 -13
- package/dist/editable-text.js.map +1 -1
- package/dist/form-select.d.ts +21 -2
- package/dist/form-select.d.ts.map +1 -1
- package/dist/form-select.js +489 -37
- package/dist/form-select.js.map +1 -1
- package/dist/frame-box.d.ts.map +1 -1
- package/dist/frame-box.js +20 -12
- package/dist/frame-box.js.map +1 -1
- package/dist/icon-button.d.ts.map +1 -1
- package/dist/icon-button.js +11 -0
- package/dist/icon-button.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/kbd-hint.d.ts +30 -0
- package/dist/kbd-hint.d.ts.map +1 -0
- package/dist/kbd-hint.js +184 -0
- package/dist/kbd-hint.js.map +1 -0
- package/dist/live-timer.d.ts +0 -1
- package/dist/live-timer.d.ts.map +1 -1
- package/dist/live-timer.js +1 -6
- package/dist/live-timer.js.map +1 -1
- package/dist/map-circle.d.ts +3 -0
- package/dist/map-circle.d.ts.map +1 -1
- package/dist/map-circle.js +19 -1
- package/dist/map-circle.js.map +1 -1
- package/dist/map-pin.d.ts.map +1 -1
- package/dist/map-pin.js +16 -1
- package/dist/map-pin.js.map +1 -1
- package/dist/percent-bar-chart.d.ts +3 -0
- package/dist/percent-bar-chart.d.ts.map +1 -1
- package/dist/percent-bar-chart.js +60 -4
- package/dist/percent-bar-chart.js.map +1 -1
- package/dist/photo-gallery.d.ts.map +1 -1
- package/dist/photo-gallery.js +12 -3
- package/dist/photo-gallery.js.map +1 -1
- package/dist/popover-panel.d.ts.map +1 -1
- package/dist/popover-panel.js +132 -23
- package/dist/popover-panel.js.map +1 -1
- package/dist/price-history-chart.d.ts.map +1 -1
- package/dist/price-history-chart.js +52 -18
- package/dist/price-history-chart.js.map +1 -1
- package/dist/radio-cards.d.ts +2 -0
- package/dist/radio-cards.d.ts.map +1 -1
- package/dist/radio-cards.js +51 -5
- package/dist/radio-cards.js.map +1 -1
- package/dist/radio-pills.d.ts +2 -0
- package/dist/radio-pills.d.ts.map +1 -1
- package/dist/radio-pills.js +46 -4
- package/dist/radio-pills.js.map +1 -1
- package/dist/relative-time.d.ts +0 -1
- package/dist/relative-time.d.ts.map +1 -1
- package/dist/relative-time.js +1 -6
- package/dist/relative-time.js.map +1 -1
- package/dist/reveal-button.d.ts +2 -0
- package/dist/reveal-button.d.ts.map +1 -1
- package/dist/reveal-button.js +42 -5
- package/dist/reveal-button.js.map +1 -1
- package/dist/roman-numeral.js +1 -1
- package/dist/roman-numeral.js.map +1 -1
- package/dist/slide-panel.d.ts +7 -3
- package/dist/slide-panel.d.ts.map +1 -1
- package/dist/slide-panel.js +95 -23
- package/dist/slide-panel.js.map +1 -1
- package/dist/stat-meter.d.ts.map +1 -1
- package/dist/stat-meter.js +17 -7
- package/dist/stat-meter.js.map +1 -1
- package/dist/status-pill.d.ts.map +1 -1
- package/dist/status-pill.js +26 -12
- package/dist/status-pill.js.map +1 -1
- package/dist/tile-grid.d.ts +8 -0
- package/dist/tile-grid.d.ts.map +1 -1
- package/dist/tile-grid.js +88 -12
- package/dist/tile-grid.js.map +1 -1
- package/dist/toast-notification.d.ts.map +1 -1
- package/dist/toast-notification.js +40 -11
- package/dist/toast-notification.js.map +1 -1
- package/dist/tokens.css +23 -0
- package/dist/tokens.d.ts.map +1 -1
- package/dist/tokens.js +16 -0
- package/dist/tokens.js.map +1 -1
- package/dist/ui-button.d.ts +2 -0
- package/dist/ui-button.d.ts.map +1 -1
- package/dist/ui-button.js +58 -9
- package/dist/ui-button.js.map +1 -1
- package/dist/user-avatar.d.ts.map +1 -1
- package/dist/user-avatar.js +18 -3
- package/dist/user-avatar.js.map +1 -1
- package/dist/utils/calendar.d.ts +77 -0
- package/dist/utils/calendar.d.ts.map +1 -0
- package/dist/utils/calendar.js +135 -0
- package/dist/utils/calendar.js.map +1 -0
- package/dist/utils/form.d.ts +3 -0
- package/dist/utils/form.d.ts.map +1 -0
- package/dist/utils/form.js +14 -0
- package/dist/utils/form.js.map +1 -0
- package/dist/utils/layer-stack.d.ts +9 -0
- package/dist/utils/layer-stack.d.ts.map +1 -0
- package/dist/utils/layer-stack.js +35 -0
- package/dist/utils/layer-stack.js.map +1 -0
- package/dist/weight-bar-chart.d.ts.map +1 -1
- package/dist/weight-bar-chart.js +27 -8
- package/dist/weight-bar-chart.js.map +1 -1
- package/docs/address-autocomplete.md +5 -0
- package/docs/autocomplete-input.md +5 -0
- package/docs/calendar-entry.md +53 -0
- package/docs/calendar-month.md +79 -0
- package/docs/calendar-year.md +53 -0
- package/docs/chat-message.md +4 -0
- package/docs/confirm-dialog.md +2 -0
- package/docs/copy-link-button.md +2 -0
- package/docs/data-table.md +2 -0
- package/docs/design-language.md +149 -0
- package/docs/distribution-chart.md +4 -0
- package/docs/dropdown-button.md +1 -0
- package/docs/form-select.md +15 -3
- package/docs/frame-box.md +2 -0
- package/docs/kbd-hint.md +42 -0
- package/docs/map-circle.md +4 -0
- package/docs/percent-bar-chart.md +4 -1
- package/docs/photo-gallery.md +1 -0
- package/docs/popover-panel.md +2 -0
- package/docs/price-history-chart.md +8 -1
- package/docs/radio-cards.md +2 -0
- package/docs/radio-pills.md +2 -0
- package/docs/reveal-button.md +3 -0
- package/docs/slide-panel.md +4 -2
- package/docs/status-pill.md +3 -0
- package/docs/tile-grid.md +10 -1
- package/docs/toast-notification.md +4 -1
- package/docs/ui-button.md +2 -0
- package/docs/user-avatar.md +1 -0
- package/llms.txt +175 -61
- package/package.json +14 -2
- package/dist/map-point.d.ts +0 -31
- package/dist/map-point.d.ts.map +0 -1
- package/dist/map-point.js +0 -83
- package/dist/map-point.js.map +0 -1
- package/docs/map-point.md +0 -44
|
@@ -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;
|
|
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"}
|
package/dist/dropdown-button.js
CHANGED
|
@@ -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(
|
|
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.
|
|
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: #
|
|
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
|
|
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
|
|
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:
|
|
86
|
-
padding:
|
|
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, #
|
|
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.
|
|
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,
|
|
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"}
|
package/dist/editable-text.js
CHANGED
|
@@ -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.
|
|
49
|
-
margin: -0.
|
|
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
|
|
72
|
+
.display:disabled {
|
|
61
73
|
cursor: inherit;
|
|
74
|
+
opacity: 0.6;
|
|
62
75
|
}
|
|
63
|
-
.display
|
|
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, #
|
|
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.
|
|
77
|
-
margin: -0.
|
|
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 `<
|
|
192
|
-
|
|
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"]}
|
package/dist/form-select.d.ts
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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"}
|